From ce25cdd2a2b759dfdc6e48c50a9f32eb9abc7f44 Mon Sep 17 00:00:00 2001 From: Gitead Date: Sun, 20 Sep 2026 15:56:03 -0400 Subject: [PATCH] fix: DDMA claim checkbox, eligibility patient name, and ID+DOB patient lookup - Claim step2: robust member checkbox selection with page-wide fallback - Eligibility: take patient name from 'Benefits for' heading, not subscriber name - Processor: update the patient just created/matched by ID+DOB, not first ID match Co-Authored-By: Claude Sonnet 5 --- apps/Backend/src/queue/processors/_shared.ts | 9 ++++-- .../processors/ddmaEligibilityProcessor.ts | 11 +++++-- .../selenium_DDMA_claimSubmitWorker.py | 29 +++++++++++++++++-- .../selenium_DDMA_eligibilityCheckWorker.py | 12 ++++++++ 4 files changed, 55 insertions(+), 6 deletions(-) diff --git a/apps/Backend/src/queue/processors/_shared.ts b/apps/Backend/src/queue/processors/_shared.ts index 559a8a19..6d966700 100644 --- a/apps/Backend/src/queue/processors/_shared.ts +++ b/apps/Backend/src/queue/processors/_shared.ts @@ -167,12 +167,17 @@ export async function createOrUpdatePatientByInsuranceId(options: { } try { - await storage.createPatient(patientData); + const created = await storage.createPatient(patientData); console.log(`[createOrUpdatePatient] patient created successfully for insuranceId="${normalizedId}"`); + // Return the record just created — family members share an insuranceId, so an + // id-only lookup could return a different member. + if (created?.id) return created; } catch (dbErr: any) { console.error(`[createOrUpdatePatient] DB create failed:`, dbErr?.message ?? dbErr); throw dbErr; } - return storage.getPatientByInsuranceId(normalizedId); + return dobDate + ? storage.getPatientByInsuranceIdAndDob(normalizedId, dobDate) + : storage.getPatientByInsuranceId(normalizedId); } diff --git a/apps/Backend/src/queue/processors/ddmaEligibilityProcessor.ts b/apps/Backend/src/queue/processors/ddmaEligibilityProcessor.ts index 4fe6de1d..190517c7 100644 --- a/apps/Backend/src/queue/processors/ddmaEligibilityProcessor.ts +++ b/apps/Backend/src/queue/processors/ddmaEligibilityProcessor.ts @@ -119,7 +119,7 @@ async function processDdmaResult( } // 2) Create / update patient - await createOrUpdatePatientByInsuranceId({ + const upsertedPatient = await createOrUpdatePatientByInsuranceId({ insuranceId, firstName, lastName, @@ -129,7 +129,14 @@ async function processDdmaResult( // 3) Fetch patient (needed for ID) const normalizedInsuranceId = insuranceId.replace(/\s+/g, ""); - const patient = await storage.getPatientByInsuranceId(normalizedInsuranceId); + // Use the patient just created/updated (family members share an insuranceId, so an + // id-only lookup could return a different member). + const dobForLookup = formDob ? new Date(formDob) : null; + const patient = + upsertedPatient ?? + (dobForLookup && !isNaN(dobForLookup.getTime()) + ? await storage.getPatientByInsuranceIdAndDob(normalizedInsuranceId, dobForLookup) + : await storage.getPatientByInsuranceId(normalizedInsuranceId)); log("ddma-processor", `patient lookup after create: ${patient ? `id=${patient.id}` : "NOT FOUND"} for insuranceId="${normalizedInsuranceId}"`); if (!patient?.id) { output.patientUpdateStatus = "Patient not found; no update performed"; diff --git a/apps/SeleniumService/selenium_DDMA_claimSubmitWorker.py b/apps/SeleniumService/selenium_DDMA_claimSubmitWorker.py index 68065764..3069c6f7 100644 --- a/apps/SeleniumService/selenium_DDMA_claimSubmitWorker.py +++ b/apps/SeleniumService/selenium_DDMA_claimSubmitWorker.py @@ -397,8 +397,33 @@ class AutomationDDMAClaimSubmit: return "ERROR: step2 failed: no family member rows found" try: - checkbox = target_row.find_element(By.XPATH, ".//i[contains(@class,'fa-check-square')]") - checkbox.click() + # The unchecked box may render as an icon (fa-square / fa-square-o / + # fa-check-square), a native/ARIA checkbox, or just a clickable first cell. + checkbox = None + for xp in ( + ".//i[contains(@class,'fa-check-square') or contains(@class,'fa-square')]", + ".//input[@type='checkbox']", + ".//*[@role='checkbox']", + "./td[1]//i", + "./td[1]//*[self::span or self::label or self::div]", + "./td[1]", + ): + found = target_row.find_elements(By.XPATH, xp) + if found: + checkbox = found[0] + break + if checkbox is None: + # Icon may render late or outside the (e.g. a fixed-column table) + checkbox = WebDriverWait(self.driver, 10).until( + EC.presence_of_element_located(( + By.XPATH, + "//i[contains(@class,'far') and (contains(@class,'fa-check-square') or contains(@class,'fa-square'))]", + )) + ) + try: + checkbox.click() + except Exception: + self.driver.execute_script("arguments[0].click();", checkbox) print("[DDMA Claim step2] Checked the matching family member row") except Exception as e: print(f"[DDMA Claim step2] Warning: Could not click member checkbox: {e}") diff --git a/apps/SeleniumService/selenium_DDMA_eligibilityCheckWorker.py b/apps/SeleniumService/selenium_DDMA_eligibilityCheckWorker.py index b1c7bbe6..d60f12e2 100755 --- a/apps/SeleniumService/selenium_DDMA_eligibilityCheckWorker.py +++ b/apps/SeleniumService/selenium_DDMA_eligibilityCheckWorker.py @@ -542,9 +542,21 @@ class AutomationDeltaDentalMAEligibilityCheck: return False return True + # Preferred: the "Benefits for JOSHUA ZHANG" heading names the actual + # patient. "Subscriber name:" is the policy holder (e.g. a parent), so + # it is only used below if this heading is missing. + for line in page_lines: + heading = re.match(r'^benefits\s+for\s+(.+)$', line, re.IGNORECASE) + if heading and looks_like_name(heading.group(1)): + patientName = heading.group(1).strip() + print(f"[DDMA step2] Extracted name from 'Benefits for' heading: '{patientName}'") + break + # Scan lines for the "Name" label. When "Name" appears alone we scan # forward past accessibility text to the first plausible name value. for i, line in enumerate(page_lines): + if patientName: + break # Case 1: "Name : Value" or "Name: Value" on the same line # (DDMA's detail page labels this "Subscriber name:", so that # prefix must be accepted rather than excluded)