feat: United Ucard name+DOB eligibility search via chat, duplicate-record handling, Product ID as member ID

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-20 22:30:01 -04:00
co-authored by Claude Sonnet 5
parent ce25cdd2a2
commit 7ec82902d9
7 changed files with 366 additions and 64 deletions
@@ -113,10 +113,10 @@ export function UnitedUcardEligibilityButton({
const [isSubmittingOtp, setIsSubmittingOtp] = useState(false);
const handleStart = async () => {
if (!memberId || !dateOfBirth) {
if (!dateOfBirth || (!memberId && !(firstName && lastName))) {
toast({
title: "Missing fields",
description: "Member ID and Date of Birth are required.",
description: "Date of Birth plus either Member ID or First + Last Name are required.",
variant: "destructive",
});
return;
@@ -231,7 +231,7 @@ export function ChatbotButton() {
// 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 [eligibilityIdData, setEligibilityIdData] = useState<{ memberId: string; firstName?: string; lastName?: 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<CheckAndClaimData | null>(null);
const [clarificationData, setClarificationData] = useState<{ memberId: string; dob: string; patient: PatientResult | null; procedureNames: string[]; options: string[] } | null>(null);
@@ -562,8 +562,8 @@ export function ChatbotButton() {
prefillAndNavigate(patientResult.insuranceId, patientResult.dateOfBirth, getAutoCheck(patientResult.dateOfBirth));
};
const prefillAndNavigate = (memberId: string, dobISO: string, autoCheck: string) => {
sessionStorage.setItem("chatbot_eligibility", JSON.stringify({ memberId, dob: dobISO, autoCheck }));
const prefillAndNavigate = (memberId: string, dobISO: string, autoCheck: string, names?: { firstName?: string; lastName?: string }) => {
sessionStorage.setItem("chatbot_eligibility", JSON.stringify({ memberId, dob: dobISO, autoCheck, firstName: names?.firstName, lastName: names?.lastName }));
window.dispatchEvent(new CustomEvent("chatbot:eligibility-prefill"));
markJobStarted();
if (pendingFolderRenameRef.current) {
@@ -598,8 +598,13 @@ export function ChatbotButton() {
if (!eligibilityIdData) return;
addMsg("user", "Check eligibility now");
addMsg("bot", "Opening the eligibility check page...");
await syncEditedPatientFields(eligibilityIdData.memberId, eligibilityIdData.dob, eligibilityIdData.patient?.id);
prefillAndNavigate(eligibilityIdData.memberId, eligibilityIdData.dob, eligibilityIdData.autoCheck);
if (eligibilityIdData.memberId) {
await syncEditedPatientFields(eligibilityIdData.memberId, eligibilityIdData.dob, eligibilityIdData.patient?.id);
}
prefillAndNavigate(eligibilityIdData.memberId, eligibilityIdData.dob, eligibilityIdData.autoCheck, {
firstName: eligibilityIdData.firstName,
lastName: eligibilityIdData.lastName,
});
};
const handleBatchEligibilityRun = () => {
@@ -759,6 +764,8 @@ export function ChatbotButton() {
if (data.action === "eligibility_id_ready" && data.actionData) {
setEligibilityIdData({
memberId: data.actionData.memberId,
firstName: data.actionData.firstName,
lastName: data.actionData.lastName,
dob: data.actionData.dob,
siteKey: data.actionData.siteKey,
autoCheck: data.actionData.autoCheck,
@@ -1134,6 +1141,30 @@ export function ChatbotButton() {
className="h-7 text-xs bg-white"
/>
</div>
{eligibilityIdData.firstName !== undefined && (
<div className="grid grid-cols-2 gap-2">
<div className="space-y-1">
<Label className="text-[10px] text-gray-500">First Name</Label>
<Input
value={eligibilityIdData.firstName ?? ""}
onChange={(e) =>
setEligibilityIdData((prev) => (prev ? { ...prev, firstName: e.target.value } : prev))
}
className="h-7 text-xs bg-white"
/>
</div>
<div className="space-y-1">
<Label className="text-[10px] text-gray-500">Last Name</Label>
<Input
value={eligibilityIdData.lastName ?? ""}
onChange={(e) =>
setEligibilityIdData((prev) => (prev ? { ...prev, lastName: e.target.value } : prev))
}
className="h-7 text-xs bg-white"
/>
</div>
</div>
)}
<div className="space-y-1">
<Label className="text-[10px] text-gray-500">Date of Birth (MM/DD/YYYY)</Label>
<Input
@@ -1316,6 +1347,8 @@ export function ChatbotButton() {
} else if (data.action === "eligibility_id_ready" && data.actionData) {
setEligibilityIdData({
memberId: data.actionData.memberId,
firstName: data.actionData.firstName,
lastName: data.actionData.lastName,
dob: data.actionData.dob,
siteKey: data.actionData.siteKey,
autoCheck: data.actionData.autoCheck,
@@ -115,6 +115,8 @@ export default function InsuranceStatusPage() {
const [firstName, setFirstName] = useState("");
const [lastName, setLastName] = useState("");
const isFormIncomplete = !memberId || !dateOfBirth;
// United Ucard can also search by first name + last name + DOB when there's no member ID.
const isUcardFormIncomplete = !dateOfBirth || (!memberId && !(firstName && lastName));
const [isCheckingEligibilityStatus, setIsCheckingEligibilityStatus] =
useState(false);
const [isCheckingEligibilityAppointment, setIsCheckingEligibilityAppointment] =
@@ -186,8 +188,10 @@ export default function InsuranceStatusPage() {
const raw = sessionStorage.getItem("chatbot_eligibility");
if (!raw) return;
try {
const { memberId: id, dob, autoCheck: ac } = JSON.parse(raw);
const { memberId: id, dob, autoCheck: ac, firstName: fn, lastName: ln } = JSON.parse(raw);
if (id) setMemberId(id);
if (fn) setFirstName(fn);
if (ln) setLastName(ln);
if (dob) {
// dob may arrive as MM/DD/YYYY or YYYY-MM-DD — normalize to YYYY-MM-DD.
// A malformed date (e.g. hand-edited in the chatbot) must not abort the rest of
@@ -749,7 +753,8 @@ export default function InsuranceStatusPage() {
// Auto-trigger after prefill (from chatbot or schedule page "Check Eligibility")
useEffect(() => {
if (!pendingAutoCheck.current || !memberId || !dateOfBirth) return;
if (!pendingAutoCheck.current || !dateOfBirth) return;
if (!memberId && !(pendingAutoCheck.current === "united-ucard" && firstName && lastName)) return;
const check = pendingAutoCheck.current;
pendingAutoCheck.current = null;
if (check === "mh" || check === "mh-history") {
@@ -1209,7 +1214,7 @@ export default function InsuranceStatusPage() {
dateOfBirth={dateOfBirth}
firstName={firstName}
lastName={lastName}
isFormIncomplete={isFormIncomplete}
isFormIncomplete={isUcardFormIncomplete}
autoTrigger={triggerTarget === "united-ucard"}
onAutoTriggered={() => setTriggerTarget(null)}
onPdfReady={async (pdfId, fallbackFilename) => {