feat: AI chatbot preauth intent, UnitedDH pre-auth improvements

- Add preauth intent to AI chatbot (classifier, workflow, frontend UI card)
- Auto-prefill preauth form with CDT codes, service date, and mapped prices
- Auto-trigger preauth Selenium handler by insurance siteKey (MH/Tufts/CCA/UnitedDH)
- Default tentative service date to today+3 for preauth (user didn't pick a date)
- Fix #number always means tooth number, distributes to all procedures in comma list
- Fix bare "post"/"pos" → D2954 (was matching D2955 via keyword scorer)
- UnitedDH pre-auth: fill procedure date with Ctrl+A to overwrite prefilled value
- UnitedDH pre-auth: select Location "Summit Dental Care" in step1 (same as billing entity)
- UnitedDH pre-auth: remove page refresh in step9 to preserve pre-auth number
- UnitedDH pre-auth: wait for table rows before extracting pre-auth number
- UnitedDH pre-auth/claim: explicit wait for Submit button after file upload (no sleep)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
ff
2026-06-17 01:21:51 -04:00
parent 43340ab39d
commit 8e011c5a29
8 changed files with 311 additions and 19 deletions

View File

@@ -49,6 +49,7 @@ export interface ChatResponse {
| "need_insurance_clarification"
| "appointment_created"
| "claim_only_ready"
| "preauth_ready"
| "need_appointment_selection"
| "need_cdt_clarification";
actionData?: Record<string, any>;
@@ -325,6 +326,12 @@ export async function runInternalChatWorkflow(
return await handleClaimOnly(classification, storage, customAliases);
}
// ── Pre-authorization ──────────────────────────────────────────────────────
if (intent === "preauth") {
return await handlePreauth(classification, storage, customAliases);
}
// ── Schedule appointment ───────────────────────────────────────────────────
if (intent === "schedule_appointment") {
@@ -692,6 +699,70 @@ async function handleClaimOnly(
};
}
// ─── preauth ──────────────────────────────────────────────────────────────────
async function handlePreauth(
c: ChatClassification,
storage: StorageLike,
customAliases: { phrase: string; cdtCode: string }[]
): Promise<ChatResponse> {
let patient: ResolvedPatient | null = null;
if (c.memberId?.trim()) {
const existing = await findPatientByMemberId(c.memberId.trim(), c.dob, storage);
if (existing) patient = patientToResult(existing);
} else if (c.patientName?.trim()) {
const raw = await findPatientByName(c.patientName.trim(), storage);
if (raw) patient = patientToResult(raw);
}
if (!patient) {
return { reply: "Please include a patient name or Member ID so I can look them up." };
}
const procedureNames = stripAttachmentRefs(c.procedureNames ?? []);
if (procedureNames.length === 0) {
return { reply: "Please specify which procedures to pre-authorize (e.g. rct, post, crown)." };
}
const cdtResults = lookupCdtCodes(procedureNames, customAliases);
const matched = cdtResults.filter((r) => r.code !== null);
const unmatched = cdtResults.filter((r) => r.code === null);
if (unmatched.length > 0) {
const phrases = unmatched.map((r) => r.input);
return {
reply: `I don't recognize ${phrases.map((p) => `"${p}"`).join(", ")}. What CDT code${phrases.length > 1 ? "s are" : " is"} ${phrases.length > 1 ? "they" : "it"}? (e.g. D3320)`,
action: "need_cdt_clarification",
actionData: { unknownPhrases: phrases, matchedSoFar: matched.map((r) => ({ code: r.code!, description: r.description })) },
};
}
// Use explicit date from message; otherwise today+3 (pre-auth is for a future appointment)
let serviceDate: string = c.appointmentDate ?? (() => {
const d = new Date();
d.setDate(d.getDate() + 3);
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
})();
const siteKey = resolveSiteKey(patient.insuranceProvider ?? null, c.insuranceHint ?? null) ?? "MH";
const fullName = `${patient.firstName ?? ""} ${patient.lastName ?? ""}`.trim();
const [sy, sm, sd] = serviceDate.split("-");
const dateLabel = `${sm}/${sd}/${sy}`;
return {
reply: `Opening pre-auth for ${fullName} (tentative date ${dateLabel}): ${matched.map((r) => `${r.code} (${r.description})`).join(", ")}.`,
action: "preauth_ready",
actionData: {
patient,
siteKey,
serviceDate,
matchedCodes: matched.map((r) => ({ code: r.code!, description: r.description, toothNumber: r.toothNumber, toothSurface: r.toothSurface, quad: r.quad })),
renderingProvider: c.renderingProvider ?? null,
},
};
}
// ─── schedule_appointment ─────────────────────────────────────────────────────
const DEFAULT_STAFF_ID = 1; // Column A