feat: chatbot screenshot-only eligibility detect, Sun Life/DentaQuest auto-check, chatbot attachment handoff to preauth; fix TuftsSCO preauth Selenium reliability

- Chatbot: submitting with only a screenshot attached (no text) now triggers the same
  "AI Detect & Check Eligibility" flow as the Copy Agent page
- detect-eligibility-info now also extracts the visible insurance payer name and picks
  Tufts SCO auto-check when both Sun Life and DentaQuest are detected, else MassHealth
- Chatbot-attached files are now handed off to both claim and preauth forms unconditionally,
  not just claims
- TuftsSCO preauth Selenium worker: verify-and-retry the Tooth field (typing was silently
  getting reset by duplicate-procedure-code warning banners), add a final re-verification
  pass across all rows, fix acknowledgement-checkbox targeting/verification, and verify
  the "Next step" click actually advances the wizard instead of trusting a blind click
This commit is contained in:
2026-07-25 00:10:05 -04:00
parent 6b9b3c41f2
commit 870bda5950
8 changed files with 237 additions and 724 deletions
+13 -3
View File
@@ -378,7 +378,8 @@ router.post(
type: "text",
text:
"Extract the Member ID and Date of Birth from this dental/insurance eligibility screenshot. " +
'Respond with strict JSON only, no prose, no markdown fences: {"memberId": string|null, "dob": "YYYY-MM-DD"|null}',
"Also read any visible insurance payer/plan name text (e.g. \"Sun Life\", \"DentaQuest\", \"MassHealth\") exactly as shown. " +
'Respond with strict JSON only, no prose, no markdown fences: {"memberId": string|null, "dob": "YYYY-MM-DD"|null, "insuranceProvider": string|null}',
},
...files.map((f) => ({
type: "image_url",
@@ -390,10 +391,11 @@ router.post(
let memberId: string | null = null;
let dob: string | null = null;
let insuranceProvider: string | null = null;
try {
const raw = String(response.content).trim();
const jsonStr = raw.replace(/^```json\s*/i, "").replace(/```\s*$/, "").trim();
const parsed = JSON.parse(jsonStr) as { memberId?: string | null; dob?: string | null };
const parsed = JSON.parse(jsonStr) as { memberId?: string | null; dob?: string | null; insuranceProvider?: string | null };
// Sanitize once here so this is the single source of truth for the memberId
// used downstream (temp-patient creation, chatbot prefill, real eligibility check) —
// any mismatch in formatting would cause createOrUpdatePatientByInsuranceId to create
@@ -401,11 +403,19 @@ router.post(
const cleanedMemberId = (parsed.memberId || "").replace(/[^A-Za-z0-9]/g, "");
memberId = cleanedMemberId || null;
dob = parsed.dob || null;
insuranceProvider = parsed.insuranceProvider || null;
} catch (parseErr) {
console.error("[detect-eligibility-info] failed to parse AI response", parseErr);
}
return res.status(200).json({ error: false, data: { memberId, dob } });
// Sun Life now brands the DentaQuest/Tufts SCO portal — screenshots showing both
// names together mean the Tufts SCO auto-check, not the MassHealth default.
const providerText = (insuranceProvider || "").toLowerCase();
const hasSunLife = /sun\s*life/.test(providerText);
const hasDentaQuest = /denta\s*quest/.test(providerText);
const autoCheck = hasSunLife && hasDentaQuest ? "tufts-sco" : "mh";
return res.status(200).json({ error: false, data: { memberId, dob, insuranceProvider, autoCheck } });
} catch (err) {
console.error("[detect-eligibility-info]", err);
return res.status(500).json({ error: true, message: "Failed to detect eligibility info", details: String(err) });