From 445691cdd0724391f66a854a55e03b487681140e Mon Sep 17 00:00:00 2001 From: Emile Date: Tue, 10 Feb 2026 20:55:26 -0500 Subject: [PATCH] feat(eligibility-check) - enhance DDMA and DentaQuest workflows with flexible input handling; added detailed logging for patient data processing and eligibility status updates; improved browser cache management in Selenium service --- apps/Backend/.env.example | 5 +- apps/Backend/src/app.ts | 3 +- .../Backend/src/routes/insuranceStatusDDMA.ts | 144 ++++++++++-- .../src/routes/insuranceStatusDentaQuest.ts | 21 +- .../insurance-status/ddma-buton-modal.tsx | 11 +- apps/SeleniumService/ddma_browser_manager.py | 32 +++ .../selenium_DDMA_eligibilityCheckWorker.py | 222 +++++++++++++----- ...enium_DentaQuest_eligibilityCheckWorker.py | 89 ++++--- 8 files changed, 394 insertions(+), 133 deletions(-) diff --git a/apps/Backend/.env.example b/apps/Backend/.env.example index 7f2e8e2..d8545b6 100644 --- a/apps/Backend/.env.example +++ b/apps/Backend/.env.example @@ -1,11 +1,12 @@ NODE_ENV="development" HOST=0.0.0.0 PORT=5000 -FRONTEND_URLS=http://localhost:3000,http://192.168.1.8:3000 +# FRONTEND_URLS=http://localhost:3000,http://192.168.1.8:3000 +FRONTEND_URLS=http://localhost:3000 SELENIUM_AGENT_BASE_URL=http://localhost:5002 JWT_SECRET = 'dentalsecret' DB_HOST=localhost DB_USER=postgres DB_PASSWORD=mypassword DB_NAME=dentalapp -DATABASE_URL=postgresql://postgres:mypassword@localhost:5432/dentalapp \ No newline at end of file +DATABASE_URL=postgresql://postgres:mypassword@localhost:5432/dentalapp diff --git a/apps/Backend/src/app.ts b/apps/Backend/src/app.ts index ba0dd03..ad0d2f0 100644 --- a/apps/Backend/src/app.ts +++ b/apps/Backend/src/app.ts @@ -41,7 +41,8 @@ function isOriginAllowed(origin?: string | null) { // Dev mode: allow localhost origins automatically if ( origin.startsWith("http://localhost") || - origin.startsWith("http://127.0.0.1") + origin.startsWith("http://127.0.0.1") || + origin.startsWith("http://192.168.0.238") ) return true; // allow explicit FRONTEND_URLS if provided diff --git a/apps/Backend/src/routes/insuranceStatusDDMA.ts b/apps/Backend/src/routes/insuranceStatusDDMA.ts index 4398f8c..ab6d5ad 100644 --- a/apps/Backend/src/routes/insuranceStatusDDMA.ts +++ b/apps/Backend/src/routes/insuranceStatusDDMA.ts @@ -136,36 +136,135 @@ async function handleDdmaCompletedJob( // We'll wrap the processing in try/catch/finally so cleanup always runs try { - // 1) ensuring memberid. const insuranceEligibilityData = job.insuranceEligibilityData; - const insuranceId = String(insuranceEligibilityData.memberId ?? "").trim(); + + // DEBUG: Log the raw selenium result + console.log(`[ddma-eligibility] === DEBUG: Raw seleniumResult ===`); + console.log(`[ddma-eligibility] seleniumResult.patientName: '${seleniumResult?.patientName}'`); + console.log(`[ddma-eligibility] seleniumResult.memberId: '${seleniumResult?.memberId}'`); + console.log(`[ddma-eligibility] seleniumResult.status: '${seleniumResult?.status}'`); + + // 1) Get insuranceId - prefer from Selenium result (flexible search support) + let insuranceId = String(seleniumResult?.memberId || "").trim(); if (!insuranceId) { - throw new Error("Missing memberId for ddma job"); + insuranceId = String(insuranceEligibilityData.memberId ?? "").trim(); } + console.log(`[ddma-eligibility] Resolved insuranceId: ${insuranceId || "(none)"}`); - // 2) Create or update patient (with name from selenium result if available) + // 2) Get patient name - prefer from Selenium result const patientNameFromResult = typeof seleniumResult?.patientName === "string" ? seleniumResult.patientName.trim() : null; + + console.log(`[ddma-eligibility] patientNameFromResult: '${patientNameFromResult}'`); - const { firstName, lastName } = splitName(patientNameFromResult); + // Get name from input data as fallback + let firstName = String(insuranceEligibilityData.firstName || "").trim(); + let lastName = String(insuranceEligibilityData.lastName || "").trim(); + + // Override with name from Selenium result if available + if (patientNameFromResult) { + const parsedName = splitName(patientNameFromResult); + console.log(`[ddma-eligibility] splitName result: firstName='${parsedName.firstName}', lastName='${parsedName.lastName}'`); + if (parsedName.firstName) firstName = parsedName.firstName; + if (parsedName.lastName) lastName = parsedName.lastName; + } + console.log(`[ddma-eligibility] Resolved name: firstName='${firstName}', lastName='${lastName}'`); - await createOrUpdatePatientByInsuranceId({ - insuranceId, - firstName, - lastName, - dob: insuranceEligibilityData.dateOfBirth, - userId: job.userId, - }); - - // 3) Update patient status + PDF upload - const patient = await storage.getPatientByInsuranceId( - insuranceEligibilityData.memberId - ); + // 3) Find or create patient + let patient: any = null; + + // First, try to find by insuranceId if available + if (insuranceId) { + patient = await storage.getPatientByInsuranceId(insuranceId); + if (patient) { + console.log(`[ddma-eligibility] Found patient by insuranceId: ${patient.id}`); + + // Update name if we have better data + const updates: any = {}; + if (firstName && String(patient.firstName ?? "").trim() !== firstName) { + updates.firstName = firstName; + } + if (lastName && String(patient.lastName ?? "").trim() !== lastName) { + updates.lastName = lastName; + } + if (Object.keys(updates).length > 0) { + await storage.updatePatient(patient.id, updates); + console.log(`[ddma-eligibility] Updated patient name to: ${firstName} ${lastName}`); + } + } + } + + // If not found by ID, try to find by name + if (!patient && firstName && lastName) { + try { + console.log(`[ddma-eligibility] Looking up patient by name: ${firstName} ${lastName}`); + const patients = await storage.getPatientsByUserId(job.userId); + patient = patients.find( + (p: any) => + String(p.firstName ?? "").toLowerCase() === firstName.toLowerCase() && + String(p.lastName ?? "").toLowerCase() === lastName.toLowerCase() + ) || null; + if (patient) { + console.log(`[ddma-eligibility] Found patient by name: ${patient.id}`); + // Update insuranceId if we have it + if (insuranceId && String(patient.insuranceId ?? "").trim() !== insuranceId) { + await storage.updatePatient(patient.id, { insuranceId }); + console.log(`[ddma-eligibility] Updated patient insuranceId to: ${insuranceId}`); + } + } + } catch (err: any) { + console.log(`[ddma-eligibility] Error finding patient by name: ${err.message}`); + } + } + + // Determine eligibility status from Selenium result + const eligibilityStatus = seleniumResult.eligibility === "active" ? "ACTIVE" : "INACTIVE"; + console.log(`[ddma-eligibility] Eligibility status from Delta MA: ${eligibilityStatus}`); + + // If still not found, create new patient + console.log(`[ddma-eligibility] Patient creation check: patient=${patient?.id || 'null'}, firstName='${firstName}', lastName='${lastName}'`); + if (!patient && firstName && lastName) { + console.log(`[ddma-eligibility] Creating new patient: ${firstName} ${lastName} with status: ${eligibilityStatus}`); + try { + let parsedDob: Date | undefined = undefined; + if (insuranceEligibilityData.dateOfBirth) { + try { + parsedDob = new Date(insuranceEligibilityData.dateOfBirth); + if (isNaN(parsedDob.getTime())) parsedDob = undefined; + } catch { + parsedDob = undefined; + } + } + + const newPatientData: InsertPatient = { + firstName, + lastName, + dateOfBirth: parsedDob || new Date(), // Required field + insuranceId: insuranceId || undefined, + insuranceProvider: "Delta MA", // Set insurance provider + gender: "Unknown", // Required field - default value + phone: "", // Required field - default empty + userId: job.userId, // Required field + status: eligibilityStatus, // Set status from eligibility check + }; + + const validation = insertPatientSchema.safeParse(newPatientData); + if (validation.success) { + patient = await storage.createPatient(validation.data); + console.log(`[ddma-eligibility] Created new patient: ${patient.id} with status: ${eligibilityStatus}`); + } else { + console.log(`[ddma-eligibility] Patient validation failed: ${validation.error.message}`); + } + } catch (createErr: any) { + console.log(`[ddma-eligibility] Failed to create patient: ${createErr.message}`); + } + } + if (!patient?.id) { outputResult.patientUpdateStatus = - "Patient not found; no update performed"; + "Patient not found and could not be created; no update performed"; return { patientUpdateStatus: outputResult.patientUpdateStatus, pdfUploadStatus: "none", @@ -173,11 +272,10 @@ async function handleDdmaCompletedJob( }; } - // update patient status. - const newStatus = - seleniumResult.eligibility === "active" ? "ACTIVE" : "INACTIVE"; - await storage.updatePatient(patient.id, { status: newStatus }); - outputResult.patientUpdateStatus = `Patient status updated to ${newStatus}`; + // Update patient status from Delta MA eligibility result + await storage.updatePatient(patient.id, { status: eligibilityStatus }); + outputResult.patientUpdateStatus = `Patient ${patient.id} status set to ${eligibilityStatus} (Delta MA eligibility: ${seleniumResult.eligibility})`; + console.log(`[ddma-eligibility] ${outputResult.patientUpdateStatus}`); // Handle PDF or convert screenshot -> pdf if available let pdfBuffer: Buffer | null = null; diff --git a/apps/Backend/src/routes/insuranceStatusDentaQuest.ts b/apps/Backend/src/routes/insuranceStatusDentaQuest.ts index 9c1d378..b231408 100644 --- a/apps/Backend/src/routes/insuranceStatusDentaQuest.ts +++ b/apps/Backend/src/routes/insuranceStatusDentaQuest.ts @@ -200,9 +200,13 @@ async function handleDentaQuestCompletedJob( } } + // Determine eligibility status from Selenium result + const eligibilityStatus = seleniumResult.eligibility === "active" ? "ACTIVE" : "INACTIVE"; + console.log(`[dentaquest-eligibility] Eligibility status from DentaQuest: ${eligibilityStatus}`); + // If still no patient found, CREATE a new one with the data we have if (!patient?.id && firstName && lastName) { - console.log(`[dentaquest-eligibility] Creating new patient: ${firstName} ${lastName}`); + console.log(`[dentaquest-eligibility] Creating new patient: ${firstName} ${lastName} with status: ${eligibilityStatus}`); const createPayload: any = { firstName, @@ -212,6 +216,8 @@ async function handleDentaQuestCompletedJob( phone: "", userId: job.userId, insuranceId: insuranceId || null, + insuranceProvider: "DentaQuest", // Set insurance provider + status: eligibilityStatus, // Set status from eligibility check }; try { @@ -219,7 +225,7 @@ async function handleDentaQuestCompletedJob( const newPatient = await storage.createPatient(patientData); if (newPatient) { patient = newPatient; - console.log(`[dentaquest-eligibility] Created new patient with ID: ${patient.id}`); + console.log(`[dentaquest-eligibility] Created new patient with ID: ${patient.id}, status: ${eligibilityStatus}`); } } catch (err: any) { // Try without dateOfBirth if it fails @@ -230,7 +236,7 @@ async function handleDentaQuestCompletedJob( const newPatient = await storage.createPatient(patientData); if (newPatient) { patient = newPatient; - console.log(`[dentaquest-eligibility] Created new patient (no DOB) with ID: ${patient.id}`); + console.log(`[dentaquest-eligibility] Created new patient (no DOB) with ID: ${patient.id}, status: ${eligibilityStatus}`); } } catch (err2: any) { console.error(`[dentaquest-eligibility] Failed to create patient: ${err2?.message}`); @@ -248,11 +254,10 @@ async function handleDentaQuestCompletedJob( }; } - // update patient status. - const newStatus = - seleniumResult.eligibility === "active" ? "ACTIVE" : "INACTIVE"; - await storage.updatePatient(patient.id, { status: newStatus }); - outputResult.patientUpdateStatus = `Patient status updated to ${newStatus}`; + // Update patient status from DentaQuest eligibility result + await storage.updatePatient(patient.id, { status: eligibilityStatus }); + outputResult.patientUpdateStatus = `Patient ${patient.id} status set to ${eligibilityStatus} (DentaQuest eligibility: ${seleniumResult.eligibility})`; + console.log(`[dentaquest-eligibility] ${outputResult.patientUpdateStatus}`); // Handle PDF or convert screenshot -> pdf if available let pdfBuffer: Buffer | null = null; 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 061f67d..5b5ffde 100644 --- a/apps/Frontend/src/components/insurance-status/ddma-buton-modal.tsx +++ b/apps/Frontend/src/components/insurance-status/ddma-buton-modal.tsx @@ -119,6 +119,10 @@ export function DdmaEligibilityButton({ const { toast } = useToast(); const dispatch = useAppDispatch(); + // Flexible validation: require DOB + at least one identifier (memberId OR firstName OR lastName) + const isDdmaFormIncomplete = + !dateOfBirth || (!memberId && !firstName && !lastName); + const socketRef = useRef(null); const connectingRef = useRef | null>(null); @@ -371,10 +375,11 @@ export function DdmaEligibilityButton({ }; const startDdmaEligibility = async () => { - if (!memberId || !dateOfBirth) { + // Flexible validation: require DOB + at least one identifier + if (!dateOfBirth || (!memberId && !firstName && !lastName)) { toast({ title: "Missing fields", - description: "Member ID and Date of Birth are required.", + description: "Date of Birth and at least one identifier (Member ID, First Name, or Last Name) are required.", variant: "destructive", }); return; @@ -539,7 +544,7 @@ export function DdmaEligibilityButton({