From ca568de8174c0e187e008e6b8b6c119375ea5216 Mon Sep 17 00:00:00 2001 From: Gitead Date: Mon, 20 Jul 2026 20:05:13 -0400 Subject: [PATCH] =?UTF-8?q?fix:=20make=20chatbot=20patient-found=20DOB/Mem?= =?UTF-8?q?ber=20ID=20editable=20and=20fix=20eligibility=E2=86=92appointme?= =?UTF-8?q?nt=20ordering?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Member ID and DOB in the AI chat "patient found" confirmation card are now editable inputs, and edits are persisted back to the patient record before proceeding so corrections don't revert to stale values. - Fix auto-lookup-by-member-ID effect on the insurance status page clobbering a hand-edited DOB with the stale DB value during chatbot prefill. - CMSP (under-14) eligibility flow was missing the appointment-creation hook that the adult MassHealth flow already had, so under-14 patients never got scheduled after a chatbot-initiated "eligibility & appointment" request. - For already-known patients, the chatbot was creating the appointment before running the eligibility check instead of after; now eligibility is always checked first and the appointment created only afterward, and only when the resulting patient status is ACTIVE. --- .../src/components/layout/chatbot.tsx | 106 ++++++++++-------- .../src/pages/insurance-status-page.tsx | 32 +++++- 2 files changed, 88 insertions(+), 50 deletions(-) diff --git a/apps/Frontend/src/components/layout/chatbot.tsx b/apps/Frontend/src/components/layout/chatbot.tsx index a2c44c84..412086ab 100644 --- a/apps/Frontend/src/components/layout/chatbot.tsx +++ b/apps/Frontend/src/components/layout/chatbot.tsx @@ -197,6 +197,9 @@ export function ChatbotButton() { const [eligibilityData, setEligibilityData] = useState(null); const [freeTextInput, setFreeTextInput] = useState(""); const [patientResult, setPatientResult] = useState(null); + // Snapshot of insuranceId/dateOfBirth as originally fetched, so we can tell whether the + // user hand-edited them in the patient-found card and only PUT to the DB when they did. + const originalPatientResultRef = useRef<{ insuranceId: string | null; dateOfBirth: string | null } | null>(null); const [eligibilityIdData, setEligibilityIdData] = useState<{ memberId: string; dob: string; siteKey: string; autoCheck: string; patient: PatientResult | null; appointmentDate?: string | null } | null>(null); const [batchEligibilityData, setBatchEligibilityData] = useState<{ memberId: string; dob: string; siteKey: string; autoCheck: string; patient: PatientResult | null }[] | null>(null); const [checkAndClaimData, setCheckAndClaimData] = useState(null); @@ -435,30 +438,41 @@ export function ChatbotButton() { setTimeout(() => { setLocation("/insurance-status"); setOpen(false); resetStep(); }, 600); }; - const handleEligibilityFromPatient = () => { + // If the user hand-edited Member ID / DOB on the patient-found card, save the correction + // back to the patient record so it doesn't revert to the stale value next time. + const persistPatientEditsIfChanged = async (patient: PatientResult) => { + const original = originalPatientResultRef.current; + if (!original) return; + const changed = + patient.insuranceId !== original.insuranceId || patient.dateOfBirth !== original.dateOfBirth; + if (!changed) return; + try { + await apiRequest("PUT", `/api/patients/${patient.id}`, { + insuranceId: patient.insuranceId, + dateOfBirth: patient.dateOfBirth, + }); + originalPatientResultRef.current = { insuranceId: patient.insuranceId, dateOfBirth: patient.dateOfBirth }; + } catch { + // Non-fatal — the edited values are still used for this eligibility check below. + } + }; + + const handleEligibilityFromPatient = async () => { if (!patientResult?.insuranceId || !patientResult?.dateOfBirth) return; addMsg("user", "Check eligibility now"); addMsg("bot", "Opening the eligibility check page..."); + await persistPatientEditsIfChanged(patientResult); prefillAndNavigate(patientResult.insuranceId, patientResult.dateOfBirth, getAutoCheck(patientResult.dateOfBirth)); }; const handleEligibilityAndAppointmentFromPatient = async () => { if (!patientResult?.insuranceId || !patientResult?.dateOfBirth) return; addMsg("user", "Check eligibility & add to schedule (today)"); - addMsg("bot", "Creating appointment for today...", true); - try { - const res = await apiRequest("POST", "/api/ai/create-appointment-today", { - patientId: patientResult.id, - }); - const data = await res.json(); - if (!res.ok) { - replaceLastMsg(data.message ?? "Could not create appointment."); - return; - } - replaceLastMsg(`Appointment added at ${data.startTime} (${data.column ?? "Column A"}) — opening eligibility check page...`); - } catch { - replaceLastMsg("Could not create appointment — opening eligibility check page..."); - } + addMsg("bot", "Running eligibility check — will add to today's schedule after..."); + await persistPatientEditsIfChanged(patientResult); + // Create the appointment only after eligibility is confirmed, not before — the eligibility + // page's tryAppointmentFromChatbot picks this up once the check running below completes. + sessionStorage.setItem("chatbot_appt_after_eligibility", JSON.stringify({ memberId: patientResult.insuranceId, date: null })); prefillAndNavigate(patientResult.insuranceId, patientResult.dateOfBirth, getAutoCheck(patientResult.dateOfBirth)); }; @@ -532,30 +546,11 @@ export function ChatbotButton() { : "today"; addMsg("user", `Check eligibility & add to schedule (${dateLabel})`); await syncEditedPatientFields(eligibilityIdData.memberId, eligibilityIdData.dob, eligibilityIdData.patient?.id); - - if (!eligibilityIdData.patient) { - addMsg("bot", `Running eligibility check — will add patient and create appointment for ${dateLabel} after...`); - sessionStorage.setItem("chatbot_appt_after_eligibility", JSON.stringify({ memberId: eligibilityIdData.memberId, date: targetDate ?? null })); - prefillAndNavigate(eligibilityIdData.memberId, eligibilityIdData.dob, eligibilityIdData.autoCheck); - return; - } - - addMsg("bot", `Creating appointment for ${dateLabel}...`, true); - try { - const res = await apiRequest("POST", "/api/ai/create-appointment-today", { - patientId: eligibilityIdData.patient.id, - date: targetDate ?? undefined, - }); - const data = await res.json(); - if (!res.ok) { - replaceLastMsg(data.message ?? "Could not create appointment."); - return; - } - replaceLastMsg(`Appointment added at ${data.startTime} (${data.column ?? "Column A"}) for ${data.dateLabel} — opening eligibility check page...`); - prefillAndNavigate(eligibilityIdData.memberId, eligibilityIdData.dob, eligibilityIdData.autoCheck); - } catch { - replaceLastMsg("Could not create appointment. Please try again."); - } + addMsg("bot", `Running eligibility check — will add${eligibilityIdData.patient ? "" : " patient and"} to schedule for ${dateLabel} after...`); + // Create the appointment only after eligibility is confirmed, not before — the eligibility + // page's tryAppointmentFromChatbot picks this up once the check running below completes. + sessionStorage.setItem("chatbot_appt_after_eligibility", JSON.stringify({ memberId: eligibilityIdData.memberId, date: targetDate ?? null })); + prefillAndNavigate(eligibilityIdData.memberId, eligibilityIdData.dob, eligibilityIdData.autoCheck); }; const handleCheckAndClaimRun = () => { @@ -614,6 +609,10 @@ export function ChatbotButton() { data.actionData?.patient ) { setPatientResult(data.actionData.patient); + originalPatientResultRef.current = { + insuranceId: data.actionData.patient.insuranceId, + dateOfBirth: data.actionData.patient.dateOfBirth, + }; setStep("patient-found"); return; } @@ -933,12 +932,29 @@ export function ChatbotButton() { {patientResult.insuranceProvider && (

{patientResult.insuranceProvider}

)} - {patientResult.insuranceId && ( -

ID: {patientResult.insuranceId}

- )} - {patientResult.dateOfBirth && ( -

DOB: {patientResult.dateOfBirth}

- )} +
+ + + setPatientResult((prev) => (prev ? { ...prev, insuranceId: e.target.value } : prev)) + } + className="h-7 text-xs bg-white" + /> +
+
+ + + setPatientResult((prev) => + prev ? { ...prev, dateOfBirth: mdyToISO(e.target.value) } : prev + ) + } + placeholder="MM/DD/YYYY" + className="h-7 text-xs bg-white" + /> +
{patientResult.insuranceId && patientResult.dateOfBirth && ( <> diff --git a/apps/Frontend/src/pages/insurance-status-page.tsx b/apps/Frontend/src/pages/insurance-status-page.tsx index 74095db5..86b5a32c 100755 --- a/apps/Frontend/src/pages/insurance-status-page.tsx +++ b/apps/Frontend/src/pages/insurance-status-page.tsx @@ -173,6 +173,9 @@ export default function InsuranceStatusPage() { const [triggerTarget, setTriggerTarget] = useState(null); const pendingScrollTo = useRef(null); const [prefillTick, setPrefillTick] = useState(0); + // Set when the chatbot prefill supplies a DOB, so the auto-lookup-by-member-ID effect below + // doesn't clobber a deliberately hand-edited DOB with the (possibly stale/wrong) one on file. + const suppressAutoLookupDobRef = useRef(false); // Prefill from chatbot useEffect(() => { @@ -192,6 +195,7 @@ export default function InsuranceStatusPage() { ? (() => { const [m, d, y] = dob.split("/"); return `${y}-${m!.padStart(2,"0")}-${d!.padStart(2,"0")}`; })() : dob; setDateOfBirth(parseLocalDate(normalized)); + suppressAutoLookupDobRef.current = true; } catch (dobErr) { console.error("[insurance-status] failed to parse prefilled DOB:", dob, dobErr); toast({ @@ -254,11 +258,15 @@ export default function InsuranceStatusPage() { if (patient) { setFirstName(patient.firstName ?? ""); setLastName(patient.lastName ?? ""); - const dob = - typeof patient.dateOfBirth === "string" - ? parseLocalDate(patient.dateOfBirth) - : patient.dateOfBirth ?? null; - setDateOfBirth(dob); + if (suppressAutoLookupDobRef.current) { + suppressAutoLookupDobRef.current = false; + } else { + const dob = + typeof patient.dateOfBirth === "string" + ? parseLocalDate(patient.dateOfBirth) + : patient.dateOfBirth ?? null; + setDateOfBirth(dob); + } } } catch { // silently ignore lookup errors @@ -709,6 +717,9 @@ export default function InsuranceStatusPage() { variant: "default", }); + const claimed = await tryClaimFromChatbot(selectedPatient?.id); + void tryAppointmentFromChatbot(); + if (claimed) return; setSelectedPatient(null); await queryClient.invalidateQueries({ queryKey: QK_PATIENTS_BASE }); processNextInQueue(); @@ -831,6 +842,17 @@ export default function InsuranceStatusPage() { const patient = await lookupRes.json(); if (!patient?.id) return; + // The eligibility check that just ran saved the result to patient.status — only book + // the appointment when insurance came back ACTIVE. + if (patient.status !== "ACTIVE") { + toast({ + title: "No appointment created", + description: `${patient.firstName ?? ""} ${patient.lastName ?? ""} insurance is ${patient.status === "INACTIVE" ? "inactive" : "not active"} — please add manually if needed.`.trim(), + variant: "destructive", + }); + return; + } + const apptRes = await apiRequest("POST", "/api/ai/create-appointment-today", { patientId: patient.id, date: useDate ?? undefined }); const apptData = await apptRes.json(); if (apptRes.ok) {