From a3d2960c98b0a1f97c49b841f674591bb247ae8f Mon Sep 17 00:00:00 2001 From: Summit Dental Care Date: Wed, 23 Sep 2026 16:26:17 -0400 Subject: [PATCH] feat: rename DDMA to ClaimMD across selenium service, remove MH/CMSP eligibility feature Rename ddma_browser_manager/helpers/worker files to claimmd_* naming in SeleniumService. Remove the MassHealth (MH) and CMSP eligibility-check feature end to end: buttons and dead placeholder buttons on the insurance eligibility page, their handlers and auto-trigger wiring, the chatbot's MassHealth-only manual paste flow, backend routes/queue job types/processors, and the corresponding Python Selenium worker endpoints. Multi-provider eligibility checking (DDMA, Delta, Tufts SCO, United SCO, CCA, BCBS MA) and the Twilio SMS eligibility flow are untouched. Co-Authored-By: Claude Sonnet 5 --- .turbo/preferences/tui.json | 2 +- apps/Backend/src/queue/jobRunner.ts | 33 - ...mspEligibilityHistoryRemainingProcessor.ts | 169 ----- .../processors/eligibilityHistoryProcessor.ts | 167 ----- apps/Backend/src/queue/queues.ts | 3 - .../src/queue/workers/seleniumWorker.ts | 14 +- apps/Backend/src/routes/insuranceStatus.ts | 156 +--- apps/Frontend/index.html | 2 +- .../insurance-status/ddma-buton-modal.tsx | 2 +- .../src/components/layout/chatbot.tsx | 197 +----- .../src/components/layout/top-app-bar.tsx | 2 +- apps/Frontend/src/components/ui/dateInput.tsx | 22 + apps/Frontend/src/pages/appointments-page.tsx | 14 +- apps/Frontend/src/pages/auth-page.tsx | 2 +- .../src/pages/insurance-status-page.tsx | 667 +----------------- apps/SeleniumService/agent.py | 85 +-- ..._manager.py => claimmd_browser_manager.py} | 0 ...lity.py => helpers_claimmd_eligibility.py} | 22 +- apps/SeleniumService/helpers_ddma_claim.py | 6 +- ..._eligibilityHistoryRemainingCheckWorker.py | 453 ------------ ...elenium_ClaimMD_eligibilityCheckWorker.py} | 485 +++++++------ .../selenium_DDMA_claimSubmitWorker.py | 2 +- ...lenium_MH_eligibilityHistoryCheckWorker.py | 471 ------------- install-steps-5-13.sh | 6 +- package-lock.json | 26 + .../migration.sql | 16 + .../.prisma-zod-generator-manifest.json | 4 +- packages/db/shared/helpers/decimal-helpers.ts | 7 +- .../AppointmentProcedureCreateInput.schema.ts | 13 +- ...cedureCreateManyAppointmentInput.schema.ts | 7 +- ...ointmentProcedureCreateManyInput.schema.ts | 7 +- ...cedureCreateManyNpiProviderInput.schema.ts | 7 +- ...tProcedureCreateManyPatientInput.schema.ts | 7 +- ...ureCreateWithoutAppointmentInput.schema.ts | 11 +- ...ureCreateWithoutNpiProviderInput.schema.ts | 11 +- ...ocedureCreateWithoutPatientInput.schema.ts | 11 +- ...intmentProcedureScalarWhereInput.schema.ts | 21 +- ...reScalarWhereWithAggregatesInput.schema.ts | 21 +- ...entProcedureUncheckedCreateInput.schema.ts | 7 +- ...kedCreateWithoutAppointmentInput.schema.ts | 7 +- ...kedCreateWithoutNpiProviderInput.schema.ts | 7 +- ...checkedCreateWithoutPatientInput.schema.ts | 7 +- ...entProcedureUncheckedUpdateInput.schema.ts | 21 +- ...rocedureUncheckedUpdateManyInput.schema.ts | 21 +- ...pdateManyWithoutAppointmentInput.schema.ts | 21 +- ...pdateManyWithoutNpiProviderInput.schema.ts | 19 +- ...kedUpdateManyWithoutPatientInput.schema.ts | 21 +- ...kedUpdateWithoutAppointmentInput.schema.ts | 21 +- ...kedUpdateWithoutNpiProviderInput.schema.ts | 19 +- ...checkedUpdateWithoutPatientInput.schema.ts | 21 +- .../AppointmentProcedureUpdateInput.schema.ts | 23 +- ...ProcedureUpdateManyMutationInput.schema.ts | 17 +- ...ureUpdateWithoutAppointmentInput.schema.ts | 21 +- ...ureUpdateWithoutNpiProviderInput.schema.ts | 21 +- ...ocedureUpdateWithoutPatientInput.schema.ts | 21 +- .../AppointmentProcedureWhereInput.schema.ts | 33 +- .../CommissionBatchCreateInput.schema.ts | 11 +- .../CommissionBatchCreateManyInput.schema.ts | 10 +- ...nBatchCreateManyNpiProviderInput.schema.ts | 10 +- ...sionBatchCreateWithoutItemsInput.schema.ts | 9 +- ...tchCreateWithoutNpiProviderInput.schema.ts | 9 +- .../CommissionBatchItemCreateInput.schema.ts | 9 +- ...emCreateManyCommissionBatchInput.schema.ts | 8 +- ...mmissionBatchItemCreateManyInput.schema.ts | 8 +- ...nBatchItemCreateManyPaymentInput.schema.ts | 8 +- ...reateWithoutCommissionBatchInput.schema.ts | 7 +- ...tchItemCreateWithoutPaymentInput.schema.ts | 7 +- ...missionBatchItemScalarWhereInput.schema.ts | 9 +- ...emScalarWhereWithAggregatesInput.schema.ts | 9 +- ...ionBatchItemUncheckedCreateInput.schema.ts | 8 +- ...reateWithoutCommissionBatchInput.schema.ts | 8 +- ...checkedCreateWithoutPaymentInput.schema.ts | 8 +- ...ionBatchItemUncheckedUpdateInput.schema.ts | 9 +- ...atchItemUncheckedUpdateManyInput.schema.ts | 9 +- ...eManyWithoutCommissionBatchInput.schema.ts | 9 +- ...kedUpdateManyWithoutPaymentInput.schema.ts | 9 +- ...pdateWithoutCommissionBatchInput.schema.ts | 9 +- ...checkedUpdateWithoutPaymentInput.schema.ts | 9 +- .../CommissionBatchItemUpdateInput.schema.ts | 11 +- ...BatchItemUpdateManyMutationInput.schema.ts | 7 +- ...pdateWithoutCommissionBatchInput.schema.ts | 9 +- ...tchItemUpdateWithoutPaymentInput.schema.ts | 9 +- .../CommissionBatchItemWhereInput.schema.ts | 17 +- .../CommissionBatchScalarWhereInput.schema.ts | 15 +- ...chScalarWhereWithAggregatesInput.schema.ts | 15 +- ...missionBatchUncheckedCreateInput.schema.ts | 9 +- ...UncheckedCreateWithoutItemsInput.schema.ts | 10 +- ...kedCreateWithoutNpiProviderInput.schema.ts | 9 +- ...missionBatchUncheckedUpdateInput.schema.ts | 17 +- ...ionBatchUncheckedUpdateManyInput.schema.ts | 15 +- ...pdateManyWithoutNpiProviderInput.schema.ts | 15 +- ...UncheckedUpdateWithoutItemsInput.schema.ts | 15 +- ...kedUpdateWithoutNpiProviderInput.schema.ts | 17 +- .../CommissionBatchUpdateInput.schema.ts | 17 +- ...sionBatchUpdateManyMutationInput.schema.ts | 13 +- ...sionBatchUpdateWithoutItemsInput.schema.ts | 15 +- ...tchUpdateWithoutNpiProviderInput.schema.ts | 15 +- .../CommissionBatchWhereInput.schema.ts | 21 +- ...ecimalFieldUpdateOperationsInput.schema.ts | 16 +- .../schemas/objects/DecimalFilter.schema.ts | 21 +- .../objects/DecimalNullableFilter.schema.ts | 21 +- ...imalNullableWithAggregatesFilter.schema.ts | 25 +- .../DecimalWithAggregatesFilter.schema.ts | 25 +- .../objects/NestedDecimalFilter.schema.ts | 22 +- .../NestedDecimalNullableFilter.schema.ts | 22 +- ...imalNullableWithAggregatesFilter.schema.ts | 23 +- ...estedDecimalWithAggregatesFilter.schema.ts | 23 +- ...ecimalFieldUpdateOperationsInput.schema.ts | 16 +- .../objects/PaymentCreateInput.schema.ts | 33 +- .../objects/PaymentCreateManyInput.schema.ts | 19 +- ...aymentCreateManyNpiProviderInput.schema.ts | 19 +- .../PaymentCreateManyPatientInput.schema.ts | 19 +- .../PaymentCreateManyUpdatedByInput.schema.ts | 19 +- .../PaymentCreateWithoutClaimInput.schema.ts | 31 +- ...WithoutCommissionBatchItemsInput.schema.ts | 31 +- ...entCreateWithoutNpiProviderInput.schema.ts | 31 +- ...PaymentCreateWithoutPatientInput.schema.ts | 31 +- ...houtServiceLineTransactionsInput.schema.ts | 31 +- ...ntCreateWithoutServiceLinesInput.schema.ts | 31 +- ...ymentCreateWithoutUpdatedByInput.schema.ts | 31 +- .../objects/PaymentScalarWhereInput.schema.ts | 33 +- ...ntScalarWhereWithAggregatesInput.schema.ts | 33 +- .../PaymentUncheckedCreateInput.schema.ts | 25 +- ...UncheckedCreateWithoutClaimInput.schema.ts | 25 +- ...WithoutCommissionBatchItemsInput.schema.ts | 23 +- ...kedCreateWithoutNpiProviderInput.schema.ts | 25 +- ...checkedCreateWithoutPatientInput.schema.ts | 25 +- ...houtServiceLineTransactionsInput.schema.ts | 23 +- ...edCreateWithoutServiceLinesInput.schema.ts | 23 +- ...eckedCreateWithoutUpdatedByInput.schema.ts | 25 +- .../PaymentUncheckedUpdateInput.schema.ts | 39 +- .../PaymentUncheckedUpdateManyInput.schema.ts | 33 +- ...pdateManyWithoutNpiProviderInput.schema.ts | 33 +- ...kedUpdateManyWithoutPatientInput.schema.ts | 33 +- ...dUpdateManyWithoutUpdatedByInput.schema.ts | 33 +- ...UncheckedUpdateWithoutClaimInput.schema.ts | 39 +- ...WithoutCommissionBatchItemsInput.schema.ts | 37 +- ...kedUpdateWithoutNpiProviderInput.schema.ts | 39 +- ...checkedUpdateWithoutPatientInput.schema.ts | 39 +- ...houtServiceLineTransactionsInput.schema.ts | 37 +- ...edUpdateWithoutServiceLinesInput.schema.ts | 37 +- ...eckedUpdateWithoutUpdatedByInput.schema.ts | 39 +- .../objects/PaymentUpdateInput.schema.ts | 45 +- .../PaymentUpdateManyMutationInput.schema.ts | 31 +- .../PaymentUpdateWithoutClaimInput.schema.ts | 43 +- ...WithoutCommissionBatchItemsInput.schema.ts | 43 +- ...entUpdateWithoutNpiProviderInput.schema.ts | 43 +- ...PaymentUpdateWithoutPatientInput.schema.ts | 43 +- ...houtServiceLineTransactionsInput.schema.ts | 43 +- ...ntUpdateWithoutServiceLinesInput.schema.ts | 43 +- ...ymentUpdateWithoutUpdatedByInput.schema.ts | 43 +- .../objects/PaymentWhereInput.schema.ts | 55 +- .../objects/ServiceLineCreateInput.schema.ts | 21 +- .../ServiceLineCreateManyClaimInput.schema.ts | 15 +- .../ServiceLineCreateManyInput.schema.ts | 15 +- ...erviceLineCreateManyPaymentInput.schema.ts | 15 +- ...rviceLineCreateWithoutClaimInput.schema.ts | 19 +- ...iceLineCreateWithoutPaymentInput.schema.ts | 19 +- ...houtServiceLineTransactionsInput.schema.ts | 19 +- .../ServiceLineScalarWhereInput.schema.ts | 31 +- ...neScalarWhereWithAggregatesInput.schema.ts | 31 +- ...erviceLineTransactionCreateInput.schema.ts | 13 +- ...ceLineTransactionCreateManyInput.schema.ts | 9 +- ...ransactionCreateManyPaymentInput.schema.ts | 9 +- ...actionCreateManyServiceLineInput.schema.ts | 9 +- ...sactionCreateWithoutPaymentInput.schema.ts | 11 +- ...ionCreateWithoutServiceLineInput.schema.ts | 11 +- ...eLineTransactionScalarWhereInput.schema.ts | 19 +- ...onScalarWhereWithAggregatesInput.schema.ts | 19 +- ...eTransactionUncheckedCreateInput.schema.ts | 9 +- ...checkedCreateWithoutPaymentInput.schema.ts | 9 +- ...kedCreateWithoutServiceLineInput.schema.ts | 9 +- ...eTransactionUncheckedUpdateInput.schema.ts | 19 +- ...nsactionUncheckedUpdateManyInput.schema.ts | 19 +- ...kedUpdateManyWithoutPaymentInput.schema.ts | 19 +- ...pdateManyWithoutServiceLineInput.schema.ts | 19 +- ...checkedUpdateWithoutPaymentInput.schema.ts | 19 +- ...kedUpdateWithoutServiceLineInput.schema.ts | 19 +- ...erviceLineTransactionUpdateInput.schema.ts | 21 +- ...ansactionUpdateManyMutationInput.schema.ts | 17 +- ...sactionUpdateWithoutPaymentInput.schema.ts | 19 +- ...ionUpdateWithoutServiceLineInput.schema.ts | 19 +- ...ServiceLineTransactionWhereInput.schema.ts | 27 +- .../ServiceLineUncheckedCreateInput.schema.ts | 17 +- ...UncheckedCreateWithoutClaimInput.schema.ts | 17 +- ...checkedCreateWithoutPaymentInput.schema.ts | 17 +- ...houtServiceLineTransactionsInput.schema.ts | 15 +- .../ServiceLineUncheckedUpdateInput.schema.ts | 33 +- ...viceLineUncheckedUpdateManyInput.schema.ts | 31 +- ...eckedUpdateManyWithoutClaimInput.schema.ts | 31 +- ...kedUpdateManyWithoutPaymentInput.schema.ts | 31 +- ...UncheckedUpdateWithoutClaimInput.schema.ts | 33 +- ...checkedUpdateWithoutPaymentInput.schema.ts | 33 +- ...houtServiceLineTransactionsInput.schema.ts | 31 +- .../objects/ServiceLineUpdateInput.schema.ts | 33 +- ...rviceLineUpdateManyMutationInput.schema.ts | 27 +- ...rviceLineUpdateWithoutClaimInput.schema.ts | 31 +- ...iceLineUpdateWithoutPaymentInput.schema.ts | 31 +- ...houtServiceLineTransactionsInput.schema.ts | 31 +- .../objects/ServiceLineWhereInput.schema.ts | 41 +- setup-desktop-shortcut.sh | 2 +- start-app.sh | 2 +- stop-app.sh | 2 +- 203 files changed, 2221 insertions(+), 4409 deletions(-) delete mode 100644 apps/Backend/src/queue/processors/cmspEligibilityHistoryRemainingProcessor.ts delete mode 100644 apps/Backend/src/queue/processors/eligibilityHistoryProcessor.ts rename apps/SeleniumService/{ddma_browser_manager.py => claimmd_browser_manager.py} (100%) rename apps/SeleniumService/{helpers_ddma_eligibility.py => helpers_claimmd_eligibility.py} (92%) delete mode 100644 apps/SeleniumService/selenium_CMSP_eligibilityHistoryRemainingCheckWorker.py rename apps/SeleniumService/{selenium_DDMA_eligibilityCheckWorker.py => selenium_ClaimMD_eligibilityCheckWorker.py} (59%) delete mode 100644 apps/SeleniumService/selenium_MH_eligibilityHistoryCheckWorker.py create mode 100644 packages/db/prisma/migrations/20260922041143_sync_schema_drift/migration.sql diff --git a/.turbo/preferences/tui.json b/.turbo/preferences/tui.json index f46ddd7d..efb36255 100644 --- a/.turbo/preferences/tui.json +++ b/.turbo/preferences/tui.json @@ -1,4 +1,4 @@ { "is_task_list_visible": true, - "active_task": "backend#dev" + "active_task": "frontend#dev" } \ No newline at end of file diff --git a/apps/Backend/src/queue/jobRunner.ts b/apps/Backend/src/queue/jobRunner.ts index 98016a69..f0bb2ebf 100644 --- a/apps/Backend/src/queue/jobRunner.ts +++ b/apps/Backend/src/queue/jobRunner.ts @@ -7,7 +7,6 @@ */ import { InProcessQueue } from "./inProcessQueue"; import { io } from "../socket"; -import { runEligibilityProcessor } from "./processors/eligibilityProcessor"; import { runClaimStatusProcessor } from "./processors/claimStatusProcessor"; import { runClaimSubmitProcessor } from "./processors/claimSubmitProcessor"; import { runOcrProcessor } from "./processors/ocrProcessor"; @@ -23,8 +22,6 @@ import { runUnitedDHClaimProcessor } from "./processors/unitedDHClaimProcessor"; import { runUnitedDHPreAuthProcessor } from "./processors/unitedDHPreAuthProcessor"; import { runTuftsSCOPreAuthProcessor } from "./processors/tuftsSCOPreAuthProcessor"; import { runTuftsSCOClaimProcessor } from "./processors/tuftsSCOClaimProcessor"; -import { runEligibilityHistoryProcessor } from "./processors/eligibilityHistoryProcessor"; -import { runCmspEligibilityHistoryRemainingProcessor } from "./processors/cmspEligibilityHistoryRemainingProcessor"; import { runBcbsMaEligibilityProcessor } from "./processors/bcbsMaEligibilityProcessor"; import type { SeleniumJobData, OcrJobData } from "./queues"; @@ -59,16 +56,6 @@ export function enqueueSeleniumJob(data: SeleniumJobData): string { message: "Selenium browser starting…", }); - if (jobType === "eligibility-check") { - return runEligibilityProcessor({ - enrichedPayload: data.enrichedPayload, - userId: data.userId, - insuranceId: data.insuranceId!, - formFirstName: data.formFirstName, - formLastName: data.formLastName, - formDob: data.formDob, - }); - } if (jobType === "claim-status-check") { return runClaimStatusProcessor({ enrichedPayload: data.enrichedPayload, @@ -231,26 +218,6 @@ export function enqueueSeleniumJob(data: SeleniumJobData): string { job.id ); } - if (jobType === "cmsp-eligibility-history-remaining-check") { - return runCmspEligibilityHistoryRemainingProcessor({ - enrichedPayload: data.enrichedPayload, - userId: data.userId, - insuranceId: data.insuranceId!, - formFirstName: data.formFirstName, - formLastName: data.formLastName, - formDob: data.formDob, - }); - } - if (jobType === "mh-eligibility-history-check") { - return runEligibilityHistoryProcessor({ - enrichedPayload: data.enrichedPayload, - userId: data.userId, - insuranceId: data.insuranceId!, - formFirstName: data.formFirstName, - formLastName: data.formLastName, - formDob: data.formDob, - }); - } if (jobType === "bcbs-ma-eligibility-check") { return runBcbsMaEligibilityProcessor( { diff --git a/apps/Backend/src/queue/processors/cmspEligibilityHistoryRemainingProcessor.ts b/apps/Backend/src/queue/processors/cmspEligibilityHistoryRemainingProcessor.ts deleted file mode 100644 index c1c82336..00000000 --- a/apps/Backend/src/queue/processors/cmspEligibilityHistoryRemainingProcessor.ts +++ /dev/null @@ -1,169 +0,0 @@ -/** - * Processor for "cmsp-eligibility-history-remaining-check" jobs. - * Saves eligibility PDF, service history PDF, and accumulator PDF - * to the patient's ELIGIBILITY_STATUS document group. - */ -import fs from "fs/promises"; -import path from "path"; -import { storage } from "../../storage"; -import { emptyFolderContainingFile } from "../../utils/emptyTempFolder"; -import forwardToPatientDataExtractorService from "../../services/patientDataExtractorService"; -import { - callPythonSync, - splitName, - createOrUpdatePatientByInsuranceId, -} from "./_shared"; - -export interface CmspEligibilityHistoryRemainingProcessorInput { - enrichedPayload: any; - userId: number; - insuranceId: string; - formFirstName?: string; - formLastName?: string; - formDob?: string; -} - -export interface CmspEligibilityHistoryRemainingProcessorResult { - patientUpdateStatus?: string; - pdfUploadStatus?: string; - pdfFileId?: number | null; - pdfFilename?: string | null; - memberDetailsPdfFileId?: number | null; - memberDetailsPdfFilename?: string | null; - historyPdfFileId?: number | null; - historyPdfFilename?: string | null; - accumulatorPdfFileId?: number | null; - accumulatorPdfFilename?: string | null; -} - -export async function runCmspEligibilityHistoryRemainingProcessor( - input: CmspEligibilityHistoryRemainingProcessorInput -): Promise { - const { enrichedPayload, userId, insuranceId, formFirstName, formLastName, formDob } = input; - - const seleniumResult = await callPythonSync("/cmsp-eligibility-history-remaining-check", { - data: enrichedPayload, - }); - - const outputResult: CmspEligibilityHistoryRemainingProcessorResult = {}; - - // Extract patient name - const seleniumFirst = seleniumResult.firstName?.trim() || null; - const seleniumLast = seleniumResult.lastName?.trim() || null; - const seleniumName = seleniumResult.name?.trim() || null; - const seleniumInsurance = seleniumResult.insurance?.trim() || null; - - const extracted: { firstName?: string | null; lastName?: string | null } = {}; - - if (seleniumFirst || seleniumLast) { - extracted.firstName = seleniumFirst; - extracted.lastName = seleniumLast; - } else if (seleniumName) { - const parts = splitName(seleniumName); - extracted.firstName = parts.firstName || null; - extracted.lastName = parts.lastName || null; - } - - if (!extracted.firstName && !extracted.lastName && seleniumResult?.pdf_path?.endsWith(".pdf")) { - try { - const pdfBuffer = await fs.readFile(seleniumResult.pdf_path); - const extraction = await forwardToPatientDataExtractorService({ - buffer: pdfBuffer, - originalname: path.basename(seleniumResult.pdf_path), - mimetype: "application/pdf", - } as any); - if (extraction.name) { - const parts = splitName(extraction.name); - extracted.firstName = parts.firstName || null; - extracted.lastName = parts.lastName || null; - } - } catch (e) { - console.error("[cmspProcessor] PDF name extraction failed:", e); - } - } - - const preferFirst = extracted.firstName || formFirstName || null; - const preferLast = extracted.lastName || formLastName || null; - - let patient; - try { - patient = await createOrUpdatePatientByInsuranceId({ - insuranceId, - firstName: preferFirst, - lastName: preferLast, - dob: formDob, - userId, - }); - } catch (e: any) { - throw new Error(`Failed to create/update patient: ${e.message}`); - } - - if (patient && patient.id !== undefined) { - let newStatus = "UNKNOWN"; - if (seleniumResult.eligibility === "Y") newStatus = "ACTIVE"; - else if (seleniumResult.eligibility === "N") newStatus = "INACTIVE"; - - const updates: any = { status: newStatus }; - if (seleniumInsurance) updates.insuranceProvider = seleniumInsurance; - await storage.updatePatient(patient.id, updates); - outputResult.patientUpdateStatus = `Patient status updated to ${newStatus}`; - - // Helper: save a single PDF to the patient's ELIGIBILITY_STATUS group - const saveToGroup = async (pdfPath: string): Promise => { - try { - const buffer = await fs.readFile(pdfPath); - const groupTitleKey = "ELIGIBILITY_STATUS"; - const groupTitle = "Eligibility Status"; - let group = await storage.findPdfGroupByPatientTitleKey(patient!.id, groupTitleKey); - if (!group) group = await storage.createPdfGroup(patient!.id, groupTitle, groupTitleKey); - if (!group?.id) throw new Error("PDF group creation failed"); - const created = await storage.createPdfFile(group.id, path.basename(pdfPath), buffer); - if (created && typeof created === "object" && "id" in created) { - return Number(created.id); - } - return null; - } catch (e: any) { - console.error("[cmspProcessor] saveToGroup failed:", e.message); - return null; - } - }; - - // Save eligibility PDF - if (seleniumResult.pdf_path?.endsWith(".pdf")) { - outputResult.pdfFileId = await saveToGroup(seleniumResult.pdf_path); - outputResult.pdfFilename = path.basename(seleniumResult.pdf_path); - outputResult.pdfUploadStatus = outputResult.pdfFileId ? "Eligibility PDF saved" : "Eligibility PDF upload failed"; - } else { - outputResult.pdfUploadStatus = "No eligibility PDF returned by Selenium"; - } - - // Save member details PDF - if (seleniumResult.member_details_pdf_path?.endsWith(".pdf")) { - outputResult.memberDetailsPdfFileId = await saveToGroup(seleniumResult.member_details_pdf_path); - outputResult.memberDetailsPdfFilename = path.basename(seleniumResult.member_details_pdf_path); - } - - // Save history PDF - if (seleniumResult.history_pdf_path?.endsWith(".pdf")) { - outputResult.historyPdfFileId = await saveToGroup(seleniumResult.history_pdf_path); - outputResult.historyPdfFilename = path.basename(seleniumResult.history_pdf_path); - } - - // Save accumulator PDF - if (seleniumResult.accumulator_pdf_path?.endsWith(".pdf")) { - outputResult.accumulatorPdfFileId = await saveToGroup(seleniumResult.accumulator_pdf_path); - outputResult.accumulatorPdfFilename = path.basename(seleniumResult.accumulator_pdf_path); - } - } else { - outputResult.patientUpdateStatus = "Patient not found or missing ID; no update performed"; - } - - // Cleanup temp files - try { - if (seleniumResult.pdf_path) await emptyFolderContainingFile(seleniumResult.pdf_path); - } catch (e) { - console.error("[cmspProcessor] cleanup failed:", e); - } - - return outputResult; -} diff --git a/apps/Backend/src/queue/processors/eligibilityHistoryProcessor.ts b/apps/Backend/src/queue/processors/eligibilityHistoryProcessor.ts deleted file mode 100644 index 2e4ede8b..00000000 --- a/apps/Backend/src/queue/processors/eligibilityHistoryProcessor.ts +++ /dev/null @@ -1,167 +0,0 @@ -/** - * Processor for "mh-eligibility-history-check" jobs. - * Runs MH eligibility + service history selenium flow and saves both PDFs - * to the patient's ELIGIBILITY_STATUS document group. - */ -import fs from "fs/promises"; -import path from "path"; -import { storage } from "../../storage"; -import { emptyFolderContainingFile } from "../../utils/emptyTempFolder"; -import forwardToPatientDataExtractorService from "../../services/patientDataExtractorService"; -import { - callPythonSync, - splitName, - createOrUpdatePatientByInsuranceId, -} from "./_shared"; - -export interface EligibilityHistoryProcessorInput { - enrichedPayload: any; - userId: number; - insuranceId: string; - formFirstName?: string; - formLastName?: string; - formDob?: string; -} - -export interface EligibilityHistoryProcessorResult { - patientUpdateStatus?: string; - pdfUploadStatus?: string; - pdfFileId?: number | null; - pdfFilename?: string | null; - memberDetailsPdfFileId?: number | null; - memberDetailsPdfFilename?: string | null; - historyPdfFileId?: number | null; - historyPdfFilename?: string | null; -} - -export async function runEligibilityHistoryProcessor( - input: EligibilityHistoryProcessorInput -): Promise { - const { enrichedPayload, userId, insuranceId, formFirstName, formLastName, formDob } = input; - - const seleniumResult = await callPythonSync("/mh-eligibility-history-check", { - data: enrichedPayload, - }); - - const outputResult: EligibilityHistoryProcessorResult = {}; - - // Extract patient name - const seleniumFirst = seleniumResult.firstName?.trim() || null; - const seleniumLast = seleniumResult.lastName?.trim() || null; - const seleniumName = seleniumResult.name?.trim() || null; - const seleniumInsurance = seleniumResult.insurance?.trim() || null; - - const extracted: { firstName?: string | null; lastName?: string | null } = {}; - - if (seleniumFirst || seleniumLast) { - extracted.firstName = seleniumFirst; - extracted.lastName = seleniumLast; - } else if (seleniumName) { - const parts = splitName(seleniumName); - extracted.firstName = parts.firstName || null; - extracted.lastName = parts.lastName || null; - } - - if (!extracted.firstName && !extracted.lastName && seleniumResult?.pdf_path?.endsWith(".pdf")) { - try { - const pdfBuffer = await fs.readFile(seleniumResult.pdf_path); - const extraction = await forwardToPatientDataExtractorService({ - buffer: pdfBuffer, - originalname: path.basename(seleniumResult.pdf_path), - mimetype: "application/pdf", - } as any); - if (extraction.name) { - const parts = splitName(extraction.name); - extracted.firstName = parts.firstName || null; - extracted.lastName = parts.lastName || null; - } - } catch (e) { - console.error("[eligibilityHistoryProcessor] PDF name extraction failed:", e); - } - } - - const preferFirst = extracted.firstName || formFirstName || null; - const preferLast = extracted.lastName || formLastName || null; - - let patient; - try { - patient = await createOrUpdatePatientByInsuranceId({ - insuranceId, - firstName: preferFirst, - lastName: preferLast, - dob: formDob, - userId, - }); - } catch (e: any) { - throw new Error(`Failed to create/update patient: ${e.message}`); - } - - if (patient && patient.id !== undefined) { - let newStatus = "UNKNOWN"; - if (seleniumResult.eligibility === "Y") newStatus = "ACTIVE"; - else if (seleniumResult.eligibility === "N") newStatus = "INACTIVE"; - - const updates: any = { status: newStatus }; - if (seleniumInsurance) updates.insuranceProvider = seleniumInsurance; - await storage.updatePatient(patient.id, updates); - outputResult.patientUpdateStatus = `Patient status updated to ${newStatus}`; - - // Helper: save a PDF buffer to the patient's ELIGIBILITY_STATUS group - const saveToGroup = async (pdfPath: string): Promise => { - try { - const buffer = await fs.readFile(pdfPath); - const groupTitleKey = "ELIGIBILITY_STATUS"; - const groupTitle = "Eligibility Status"; - let group = await storage.findPdfGroupByPatientTitleKey(patient!.id, groupTitleKey); - if (!group) group = await storage.createPdfGroup(patient!.id, groupTitle, groupTitleKey); - if (!group?.id) throw new Error("PDF group creation failed"); - const created = await storage.createPdfFile(group.id, path.basename(pdfPath), buffer); - if (created && typeof created === "object" && "id" in created) { - return Number(created.id); - } - return null; - } catch (e: any) { - console.error("[eligibilityHistoryProcessor] saveToGroup failed:", e.message); - return null; - } - }; - - // Save eligibility PDF - let eligibilityPdfFileId: number | null = null; - if (seleniumResult.pdf_path?.endsWith(".pdf")) { - eligibilityPdfFileId = await saveToGroup(seleniumResult.pdf_path); - outputResult.pdfUploadStatus = eligibilityPdfFileId - ? "Eligibility PDF saved" - : "Eligibility PDF upload failed"; - outputResult.pdfFilename = path.basename(seleniumResult.pdf_path); - } else { - outputResult.pdfUploadStatus = "No eligibility PDF returned by Selenium"; - } - outputResult.pdfFileId = eligibilityPdfFileId; - - // Save member details PDF - if (seleniumResult.member_details_pdf_path?.endsWith(".pdf")) { - outputResult.memberDetailsPdfFileId = await saveToGroup(seleniumResult.member_details_pdf_path); - outputResult.memberDetailsPdfFilename = path.basename(seleniumResult.member_details_pdf_path); - } - - // Save history PDF - let historyPdfFileId: number | null = null; - if (seleniumResult.history_pdf_path?.endsWith(".pdf")) { - historyPdfFileId = await saveToGroup(seleniumResult.history_pdf_path); - outputResult.historyPdfFilename = path.basename(seleniumResult.history_pdf_path); - } - outputResult.historyPdfFileId = historyPdfFileId; - } else { - outputResult.patientUpdateStatus = "Patient not found or missing ID; no update performed"; - } - - // Cleanup temp files - try { - if (seleniumResult.pdf_path) await emptyFolderContainingFile(seleniumResult.pdf_path); - } catch (e) { - console.error("[eligibilityHistoryProcessor] cleanup failed:", e); - } - - return outputResult; -} diff --git a/apps/Backend/src/queue/queues.ts b/apps/Backend/src/queue/queues.ts index 9e1f64d8..86a66a40 100644 --- a/apps/Backend/src/queue/queues.ts +++ b/apps/Backend/src/queue/queues.ts @@ -3,7 +3,6 @@ import { redisConnection } from "./connection"; /** Job types dispatched to the selenium Python worker. */ export type SeleniumJobType = - | "eligibility-check" | "claim-status-check" | "claim-submit" | "claim-pre-auth" @@ -19,8 +18,6 @@ export type SeleniumJobType = | "uniteddh-preauth-submit" | "tuftssco-preauth-submit" | "tuftssco-eligibility-check" - | "mh-eligibility-history-check" - | "cmsp-eligibility-history-remaining-check" | "bcbs-ma-eligibility-check"; export interface SeleniumJobData { diff --git a/apps/Backend/src/queue/workers/seleniumWorker.ts b/apps/Backend/src/queue/workers/seleniumWorker.ts index 7a9bc84a..4aee33c1 100644 --- a/apps/Backend/src/queue/workers/seleniumWorker.ts +++ b/apps/Backend/src/queue/workers/seleniumWorker.ts @@ -2,7 +2,6 @@ import { Worker, Job } from "bullmq"; import { redisConnection } from "../connection"; import { SeleniumJobData } from "../queues"; import { io } from "../../socket"; -import { runEligibilityProcessor } from "../processors/eligibilityProcessor"; import { runClaimStatusProcessor } from "../processors/claimStatusProcessor"; import { runClaimSubmitProcessor } from "../processors/claimSubmitProcessor"; @@ -27,7 +26,7 @@ function emitJobUpdate( } async function processSeleniumJob(job: Job) { - const { jobType, userId, socketId, enrichedPayload } = job.data; + const { jobType, socketId, enrichedPayload } = job.data; const jobId = job.id ?? job.name; emitJobUpdate(socketId, jobId, jobType, "active", { @@ -37,16 +36,7 @@ async function processSeleniumJob(job: Job) { try { let result: any; - if (jobType === "eligibility-check") { - result = await runEligibilityProcessor({ - enrichedPayload, - userId, - insuranceId: job.data.insuranceId!, - formFirstName: job.data.formFirstName, - formLastName: job.data.formLastName, - formDob: job.data.formDob, - }); - } else if (jobType === "claim-status-check") { + if (jobType === "claim-status-check") { result = await runClaimStatusProcessor({ enrichedPayload, insuranceId: job.data.insuranceId!, diff --git a/apps/Backend/src/routes/insuranceStatus.ts b/apps/Backend/src/routes/insuranceStatus.ts index e3f4646f..5e126174 100755 --- a/apps/Backend/src/routes/insuranceStatus.ts +++ b/apps/Backend/src/routes/insuranceStatus.ts @@ -15,7 +15,7 @@ import { } from "../../../../packages/db/types/patient-types"; import { formatDobForAgent } from "../utils/dateUtils"; import { seleniumQueue } from "../queue/queues"; -import { enqueueSeleniumJob, getSeleniumJobStatus } from "../queue/jobRunner"; +import { getSeleniumJobStatus } from "../queue/jobRunner"; const router = Router(); @@ -113,160 +113,6 @@ async function createOrUpdatePatientByInsuranceId(options: { } } -/** - * /eligibility-check - * - run selenium - * - if pdf created -> call extractor -> get name - * - create or update patient (by memberId) - * - attach PDF to patient (create pdf group/file) - * - return { patient, pdfFileId, extractedName ... } - */ -router.post( - "/eligibility-check", - async (req: Request, res: Response): Promise => { - if (!req.body.data) { - return res - .status(400) - .json({ error: "Missing Insurance Eligibility data for selenium" }); - } - if (!req.user || !req.user.id) { - return res.status(401).json({ error: "Unauthorized: user info missing" }); - } - - const insuranceEligibilityData = - typeof req.body.data === "string" - ? JSON.parse(req.body.data) - : req.body.data; - - const credentials = await storage.getInsuranceCredentialByUserAndSiteKey( - req.user.id, - insuranceEligibilityData.insuranceSiteKey - ); - if (!credentials) { - return res.status(404).json({ - error: - "No insurance credentials found for this provider, Kindly Update this at Settings Page.", - }); - } - - const insuranceId = String(insuranceEligibilityData.memberId ?? "").trim(); - if (!insuranceId) { - return res.status(400).json({ error: "Missing memberId" }); - } - - const enrichedData = { - ...insuranceEligibilityData, - massdhpUsername: credentials.username, - massdhpPassword: credentials.password, - }; - - const job = await seleniumQueue.add("eligibility-check", { - jobType: "eligibility-check", - userId: req.user.id, - socketId: req.body.socketId, - enrichedPayload: enrichedData, - insuranceId, - formFirstName: insuranceEligibilityData.firstName, - formLastName: insuranceEligibilityData.lastName, - formDob: insuranceEligibilityData.dateOfBirth, - }); - - return res.json({ jobId: job.id, status: "queued" }); - } -); - -router.post( - "/eligibility-history-check", - async (req: Request, res: Response): Promise => { - if (!req.body.data) { - return res.status(400).json({ error: "Missing Insurance Eligibility data for selenium" }); - } - if (!req.user || !req.user.id) { - return res.status(401).json({ error: "Unauthorized: user info missing" }); - } - - const data = - typeof req.body.data === "string" ? JSON.parse(req.body.data) : req.body.data; - - const credentials = await storage.getInsuranceCredentialByUserAndSiteKey(req.user.id, "MH"); - if (!credentials) { - return res.status(404).json({ - error: "No MassHealth credentials found. Please update them in Settings.", - }); - } - - const insuranceId = String(data.memberId ?? "").trim(); - if (!insuranceId) { - return res.status(400).json({ error: "Missing memberId" }); - } - - const enrichedData = { - ...data, - massdhpUsername: credentials.username, - massdhpPassword: credentials.password, - }; - - const jobId = enqueueSeleniumJob({ - jobType: "mh-eligibility-history-check", - userId: req.user.id, - socketId: req.body.socketId, - enrichedPayload: enrichedData, - insuranceId, - formFirstName: data.firstName, - formLastName: data.lastName, - formDob: data.dateOfBirth, - }); - - return res.json({ jobId, status: "queued" }); - } -); - -router.post( - "/cmsp-eligibility-history-remaining-check", - async (req: Request, res: Response): Promise => { - if (!req.body.data) { - return res.status(400).json({ error: "Missing data for selenium" }); - } - if (!req.user || !req.user.id) { - return res.status(401).json({ error: "Unauthorized: user info missing" }); - } - - const data = - typeof req.body.data === "string" ? JSON.parse(req.body.data) : req.body.data; - - const credentials = await storage.getInsuranceCredentialByUserAndSiteKey(req.user.id, "MH"); - if (!credentials) { - return res.status(404).json({ - error: "No MassHealth credentials found. Please update them in Settings.", - }); - } - - const insuranceId = String(data.memberId ?? "").trim(); - if (!insuranceId) { - return res.status(400).json({ error: "Missing memberId" }); - } - - const enrichedData = { - ...data, - massdhpUsername: credentials.username, - massdhpPassword: credentials.password, - }; - - const jobId = enqueueSeleniumJob({ - jobType: "cmsp-eligibility-history-remaining-check", - userId: req.user.id, - socketId: req.body.socketId, - enrichedPayload: enrichedData, - insuranceId, - formFirstName: data.firstName, - formLastName: data.lastName, - formDob: data.dateOfBirth, - }); - - return res.json({ jobId, status: "queued" }); - } -); - router.post( "/claim-status-check", async (req: Request, res: Response): Promise => { diff --git a/apps/Frontend/index.html b/apps/Frontend/index.html index 9089baf2..ca855f7a 100755 --- a/apps/Frontend/index.html +++ b/apps/Frontend/index.html @@ -7,7 +7,7 @@ - My Dental Office Management + Wellspring Acupuncture Office Management
diff --git a/apps/Frontend/src/components/insurance-status/ddma-buton-modal.tsx b/apps/Frontend/src/components/insurance-status/ddma-buton-modal.tsx index c6d4fc0f..08eb6c0c 100755 --- a/apps/Frontend/src/components/insurance-status/ddma-buton-modal.tsx +++ b/apps/Frontend/src/components/insurance-status/ddma-buton-modal.tsx @@ -419,7 +419,7 @@ export function DdmaEligibilityButton({ ) : ( <> - Delta MA + Claim MD )} diff --git a/apps/Frontend/src/components/layout/chatbot.tsx b/apps/Frontend/src/components/layout/chatbot.tsx index 281fffe8..8347ec1e 100644 --- a/apps/Frontend/src/components/layout/chatbot.tsx +++ b/apps/Frontend/src/components/layout/chatbot.tsx @@ -2,7 +2,6 @@ import { useState, useRef, useEffect } from "react"; import { Bot, X, - ChevronRight, Stethoscope, Calendar, FileText, @@ -13,7 +12,6 @@ import { Paperclip, } from "lucide-react"; import { Button } from "@/components/ui/button"; -import { Label } from "@/components/ui/label"; import { useLocation } from "wouter"; import { cn } from "@/lib/utils"; import { apiRequest } from "@/lib/queryClient"; @@ -21,8 +19,6 @@ import { setChatbotPendingFiles } from "@/lib/chatbotFileStore"; type Step = | "menu" - | "eligibility-input" - | "eligibility-confirm" | "ai-loading" | "patient-found" | "eligibility-id-ready" @@ -53,12 +49,6 @@ interface PatientResult { dateOfBirth: string | null; } -interface EligibilityData { - memberId: string; - dob: string; - dobISO: string; -} - interface CheckAndClaimData { patient: PatientResult | null; memberId: string; @@ -75,40 +65,6 @@ function makeMsg(role: "bot" | "user", text: string, isLoading = false): Message return { id: ++msgCounter, role, text, isLoading }; } -function getAutoCheck(dobISO: string): "mh" | "cmsp" { - const [y, m, d] = dobISO.split("-").map(Number); - const today = new Date(); - let age = today.getFullYear() - (y ?? 0); - const monthDiff = today.getMonth() + 1 - (m ?? 0); - if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < (d ?? 0))) age--; - return age >= 21 ? "mh" : "cmsp"; -} - -function parseEligibilityInput( - raw: string -): { memberId: string; display: string; iso: string } | null { - const dateMatch = raw.match(/\b(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{4})\b/); - if (!dateMatch) return null; - const m = dateMatch[1]; - const d = dateMatch[2]; - const y = dateMatch[3]; - if (!m || !d || !y) return null; - const month = parseInt(m, 10); - const day = parseInt(d, 10); - const year = parseInt(y, 10); - if (month < 1 || month > 12 || day < 1 || day > 31 || year < 1900) return null; - - const withoutDate = raw.replace(dateMatch[0], ""); - const memberId = (withoutDate.match(/[a-zA-Z0-9]/g) ?? []).join(""); - if (!memberId) return null; - - return { - memberId, - display: `${m.padStart(2, "0")}/${d.padStart(2, "0")}/${y}`, - iso: `${y}-${m.padStart(2, "0")}-${d.padStart(2, "0")}`, - }; -} - const CHAT_STORAGE_KEY = "chatbot_messages"; const CHATBOT_JOB_TS_KEY = "chatbot_job_started_at"; @@ -158,9 +114,6 @@ export function ChatbotButton() { const [open, setOpen] = useState(false); const [step, setStep] = useState("menu"); const [messages, setMessages] = useState(loadSavedMessages); - const [pasteInput, setPasteInput] = useState(""); - const [parseError, setParseError] = useState(""); - const [eligibilityData, setEligibilityData] = useState(null); const [freeTextInput, setFreeTextInput] = useState(""); const [patientResult, setPatientResult] = useState(null); const [eligibilityIdData, setEligibilityIdData] = useState<{ memberId: string; dob: string; siteKey: string; autoCheck: string; patient: PatientResult | null; appointmentDate?: string | null } | null>(null); @@ -206,7 +159,6 @@ export function ChatbotButton() { const [pendingFiles, setPendingFiles] = useState([]); const [, setLocation] = useLocation(); const messagesEndRef = useRef(null); - const pasteRef = useRef(null); const freeTextRef = useRef(null); const fileInputRef = useRef(null); @@ -244,9 +196,6 @@ export function ChatbotButton() { }, [messages]); useEffect(() => { - if (step === "eligibility-input") { - setTimeout(() => pasteRef.current?.focus(), 50); - } if (step === "menu") { setTimeout(() => freeTextRef.current?.focus(), 50); } @@ -266,9 +215,6 @@ export function ChatbotButton() { // Resets step/data only — keeps message history const resetStep = () => { setStep("menu"); - setPasteInput(""); - setParseError(""); - setEligibilityData(null); setFreeTextInput(""); setPatientResult(null); setEligibilityIdData(null); @@ -300,7 +246,7 @@ export function ChatbotButton() { resetStep(); }; - const handleOptionSelect = (option: "eligibility" | "schedule" | "claims") => { + const handleOptionSelect = (option: "schedule" | "claims") => { if (option === "schedule") { addMsg("user", "Schedule an appointment"); addMsg("bot", "Opening the appointments page..."); @@ -309,67 +255,9 @@ export function ChatbotButton() { addMsg("user", "View claims"); addMsg("bot", "Opening the claims page..."); setTimeout(() => { setLocation("/claims"); setOpen(false); resetStep(); }, 600); - } else if (option === "eligibility") { - addMsg("user", "Check Eligibility"); - addMsg("bot", "Please enter the patient's Member ID and Date of Birth:"); - setStep("eligibility-input"); } }; - const handleEligibilitySubmit = () => { - const parsed = parseEligibilityInput(pasteInput); - if (!parsed) { - setParseError("Couldn't find both a Member ID and a date. Try: 123456789 01/15/1985"); - return; - } - setParseError(""); - const data: EligibilityData = { memberId: parsed.memberId, dob: parsed.display, dobISO: parsed.iso }; - setEligibilityData(data); - addMsg("user", pasteInput.trim()); - addMsg("bot", `Ready to check MassHealth eligibility for:\n• Member ID: ${parsed.memberId}\n• Date of Birth: ${parsed.display}\n\nShall I proceed?`); - setStep("eligibility-confirm"); - }; - - const handleConfirm = () => { - if (!eligibilityData) return; - addMsg("user", "Yes, check now"); - addMsg("bot", "Opening the eligibility check page with this patient..."); - sessionStorage.setItem("chatbot_eligibility", JSON.stringify({ - memberId: eligibilityData.memberId, - dob: eligibilityData.dobISO, - autoCheck: getAutoCheck(eligibilityData.dobISO), - })); - window.dispatchEvent(new CustomEvent("chatbot:eligibility-prefill")); - setTimeout(() => { setLocation("/insurance-status"); setOpen(false); resetStep(); }, 600); - }; - - const handleEligibilityFromPatient = () => { - if (!patientResult?.insuranceId || !patientResult?.dateOfBirth) return; - addMsg("user", "Check eligibility now"); - addMsg("bot", "Opening the eligibility check page..."); - prefillAndNavigate(patientResult.insuranceId, patientResult.dateOfBirth, getAutoCheck(patientResult.dateOfBirth)); - }; - - const handleEligibilityAndAppointmentFromPatient = async () => { - if (!patientResult?.insuranceId || !patientResult?.dateOfBirth) return; - addMsg("user", "Check eligibility & add to schedule (today)"); - addMsg("bot", "Creating appointment for today...", true); - try { - const res = await apiRequest("POST", "/api/ai/create-appointment-today", { - patientId: patientResult.id, - }); - const data = await res.json(); - if (!res.ok) { - replaceLastMsg(data.message ?? "Could not create appointment."); - return; - } - replaceLastMsg(`Appointment added at ${data.startTime} (${data.column ?? "Column A"}) — opening eligibility check page...`); - } catch { - replaceLastMsg("Could not create appointment — opening eligibility check page..."); - } - prefillAndNavigate(patientResult.insuranceId, patientResult.dateOfBirth, getAutoCheck(patientResult.dateOfBirth)); - }; - const prefillAndNavigate = (memberId: string, dobISO: string, autoCheck: string) => { sessionStorage.setItem("chatbot_eligibility", JSON.stringify({ memberId, dob: dobISO, autoCheck })); window.dispatchEvent(new CustomEvent("chatbot:eligibility-prefill")); @@ -728,14 +616,7 @@ export function ChatbotButton() { {/* Quick-action buttons (menu step) */} {step === "menu" && ( -
- +
)} - {/* Eligibility manual input */} - {step === "eligibility-input" && ( -
- -