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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user