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
co-authored by Claude Sonnet 4.6
parent 43340ab39d
commit 8e011c5a29
8 changed files with 311 additions and 19 deletions
@@ -82,6 +82,10 @@ interface ClaimFormProps {
autoSubmit?: boolean;
/** When set, autoSubmit triggers this insurance's Selenium worker instead of MH */
autoSubmitSiteKey?: string;
/** When set, opens this tab by default (e.g. "preauth") */
initialTab?: string;
/** When set, auto-triggers the matching preauth Selenium handler (siteKey value) */
autoSubmitPreauth?: string;
/** When true: form saves to AppointmentProcedure (Select Procedures flow), shows only Save button */
proceduresOnly?: boolean;
onSubmit: (data: ClaimFormData) => Promise<Claim>;
@@ -107,6 +111,8 @@ export function ClaimForm({
appointmentId,
autoSubmit,
autoSubmitSiteKey,
initialTab,
autoSubmitPreauth,
proceduresOnly = false,
onHandleAppointmentSubmit,
onHandleUpdatePatient,
@@ -128,6 +134,7 @@ export function ClaimForm({
const [prefillDone, setPrefillDone] = useState(false);
const autoSubmittedRef = useRef(false);
const preauthAutoSubmittedRef = useRef(false);
// Read chatbot-requested rendering provider synchronously at mount (before any effects run)
// so the npiProviders effect always sees it, even when the provider list is already cached.
const [chatbotRenderingProvider] = useState<string | null>(() => {
@@ -568,6 +575,50 @@ export function ClaimForm({
} catch {}
}, []);
// Prefill service lines and service date from chatbot preauth flow, then map prices
useEffect(() => {
const raw = sessionStorage.getItem("chatbot_preauth_prefill");
if (!raw) return;
try {
const { codes, serviceDate, siteKey } = JSON.parse(raw) as {
codes: { code: string; description: string; toothNumber?: string; toothSurface?: string; quad?: string }[];
serviceDate?: string;
siteKey?: string;
};
sessionStorage.removeItem("chatbot_preauth_prefill");
if (!codes?.length) return;
if (serviceDate) {
try {
const d = parseLocalDate(serviceDate);
setServiceDateValue(d);
setServiceDate(formatLocalDate(d));
} catch {}
}
setForm((prev) => {
const date = serviceDate ? serviceDate : prev.serviceDate;
const insuranceSiteKey = siteKey ? siteKey.replace(/_/g, "").toLowerCase() : prev.insuranceSiteKey;
const updatedLines = [...prev.serviceLines];
codes.forEach((c, i) => {
if (i < updatedLines.length) {
updatedLines[i] = {
...updatedLines[i]!,
procedureCode: c.code,
procedureDate: date,
...(c.toothNumber ? { toothNumber: c.toothNumber } : {}),
...(c.toothSurface ? { toothSurface: c.toothSurface } : {}),
...(c.quad ? { quad: c.quad } : {}),
};
}
});
const filled = { ...prev, insuranceSiteKey, serviceLines: updatedLines };
return mapPricesForForm({ form: filled, patientDOB: "", insuranceSiteKey });
});
setPrefillDone(true);
} catch {}
}, []);
// Restore NPI provider from saved procedures when npiProviders list loads after 2b
useEffect(() => {
if (!savedProcNpiId || !npiProviders.length) return;
@@ -1891,6 +1942,26 @@ export function ClaimForm({
}
}, [autoSubmit, autoSubmitSiteKey, prefillDone, isFormReady]);
// Auto-trigger preauth Selenium handler when autoSubmitPreauth is set
useEffect(() => {
if (!autoSubmitPreauth) return;
if (!prefillDone) return;
if (!isFormReady) return;
if (preauthAutoSubmittedRef.current) return;
preauthAutoSubmittedRef.current = true;
const key = autoSubmitPreauth.toLowerCase();
if (key === "tufts_sco" || key === "tuftsco" || key === "tufts sco") {
handleTuftsSCOPreAuth();
} else if (key === "cca") {
handleCCAPreAuth();
} else if (key === "united_sco" || key === "unitedco" || key === "dentalhub") {
handleUnitedDHPreAuth();
} else {
handleMHPreAuth();
}
}, [autoSubmitPreauth, prefillDone, isFormReady]);
// overlay click handler (close when clicking backdrop)
const onOverlayMouseDown = (e: React.MouseEvent<HTMLDivElement>) => {
// only close if clicked the backdrop itself (not inner modal)
@@ -1903,6 +1974,7 @@ export function ClaimForm({
return () => {
// reset when ClaimForm unmounts (modal closes)
autoSubmittedRef.current = false;
preauthAutoSubmittedRef.current = false;
setPrefillDone(false);
};
}, []);
@@ -1913,7 +1985,7 @@ export function ClaimForm({
onMouseDown={onOverlayMouseDown}
>
<Card className="w-[90vw] h-[90vh] max-w-none overflow-auto bg-white relative">
<Tabs defaultValue="claim">
<Tabs defaultValue={initialTab ?? "claim"}>
<CardHeader className="sticky top-0 z-20 pb-2 border-b bg-white/95 backdrop-blur-sm">
<div className="flex flex-row items-center justify-between mb-2">
<CardTitle className="text-xl font-bold">