feat: add multi_claim intent for different procedures per patient + fix batch claim PDF race

- Add multi_claim intent so AI correctly handles "claim X for patient A and Y for patient B"
  instead of applying all procedures to all patients (batch_claim)
- Each patient carries their own matchedCodes in the queue
- Fix batch claim PDF race condition: chatbot queue no longer advances in closeClaim(),
  instead advances after selenium PDF is downloaded (matching column claim behavior)
- Align United SCO eligibility worker with claim worker: only fill subscriber ID + DOB,
  use treatmentLocation by ID instead of arrow-wrapper click

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-29 01:08:41 -04:00
co-authored by Claude Sonnet 4.6
parent cb49298b66
commit cdda91f2b4
5 changed files with 324 additions and 96 deletions
+89 -74
View File
@@ -143,6 +143,7 @@ export default function ClaimsPage() {
// CCA result: pdfFileId is already saved by the processor — open preview directly
if (jobResult.pdfFileId) {
advanceAiClaimQueue();
advanceChatbotClaimQueue();
setPreviewPdfId(jobResult.pdfFileId);
setPreviewFallbackFilename(jobResult.pdfFilename ?? `cca_claim_${jobResult.claimNumber ?? "unknown"}.pdf`);
setPreviewOpen(true);
@@ -333,6 +334,82 @@ export default function ClaimsPage() {
}
};
// Advance the chatbot_claim_queue / chatbot_check_claim_queue after selenium PDF is saved.
// NOT called from closeClaim — called only after the selenium job completes and PDF is downloaded.
const advanceChatbotClaimQueue = () => {
try {
const raw = sessionStorage.getItem("chatbot_check_claim_queue");
if (raw) {
const parsed = JSON.parse(raw);
const remaining = parsed?.remaining as any[] | undefined;
const matchedCodes = parsed?.matchedCodes ?? [];
const renderingProvider = parsed?.renderingProvider ?? null;
if (remaining && remaining.length > 0) {
const [next, ...rest] = remaining;
if (rest.length > 0) {
sessionStorage.setItem("chatbot_check_claim_queue", JSON.stringify({ remaining: rest, matchedCodes, renderingProvider }));
} else {
sessionStorage.removeItem("chatbot_check_claim_queue");
}
sessionStorage.setItem("chatbot_claim_codes", JSON.stringify({
codes: matchedCodes,
siteKey: next.siteKey,
patientId: next.patient?.id ?? null,
memberId: next.memberId,
dob: next.dob,
serviceDate: null,
renderingProvider,
}));
sessionStorage.setItem("chatbot_eligibility", JSON.stringify({
memberId: next.memberId,
dob: next.dob,
autoCheck: next.autoCheck,
}));
setTimeout(() => {
window.dispatchEvent(new CustomEvent("chatbot:eligibility-prefill"));
setWouterLocation("/insurance-status");
}, 500);
return;
}
sessionStorage.removeItem("chatbot_check_claim_queue");
}
} catch {}
try {
const raw = sessionStorage.getItem("chatbot_claim_queue");
if (raw) {
const parsed = JSON.parse(raw);
const remaining = parsed?.remaining as any[] | undefined;
const sharedMatchedCodes = parsed?.matchedCodes ?? [];
const renderingProvider = parsed?.renderingProvider ?? null;
if (remaining && remaining.length > 0) {
const [next, ...rest] = remaining;
if (rest.length > 0) {
sessionStorage.setItem("chatbot_claim_queue", JSON.stringify({ remaining: rest, matchedCodes: sharedMatchedCodes, renderingProvider }));
} else {
sessionStorage.removeItem("chatbot_claim_queue");
}
const codes = next.matchedCodes?.length > 0 ? next.matchedCodes : sharedMatchedCodes;
if (next.patient?.id && codes.length > 0) {
sessionStorage.setItem("chatbot_claim_prefill", JSON.stringify({
codes,
siteKey: next.siteKey,
serviceDate: next.serviceDate,
autoSubmit: true,
renderingProvider,
dob: next.patient.dateOfBirth ?? null,
}));
}
setTimeout(() => {
setWouterLocation(`/claims?newPatient=${next.patient.id}`);
}, 500);
return;
}
sessionStorage.removeItem("chatbot_claim_queue");
}
} catch {}
};
// Advance the ai_claim_queue by removing the first item (current patient).
// Called only on successful submission so that cancel leaves the queue intact.
const advanceAiClaimQueue = () => {
@@ -370,7 +447,10 @@ export default function ClaimsPage() {
const data = await res.json();
if (!res.ok) throw new Error(data?.message || "Failed to save claim");
queryClient.invalidateQueries({ queryKey: QK_CLAIMS_BASE });
if (!isDraft) advanceAiClaimQueue();
if (!isDraft) {
advanceAiClaimQueue();
advanceChatbotClaimQueue();
}
return data;
};
@@ -884,7 +964,10 @@ export default function ClaimsPage() {
duration: isPreAuth ? 10000 : 5000,
});
if (!isPreAuth) advanceAiClaimQueue();
if (!isPreAuth) {
advanceAiClaimQueue();
advanceChatbotClaimQueue();
}
// Pop up the final PDF so the user doesn't need to navigate to Documents
if (result.pdfFileId) {
@@ -933,78 +1016,10 @@ export default function ClaimsPage() {
}
} catch {}
// If a chatbot batch check+claim queue is pending, navigate to eligibility for the next patient
try {
const raw = sessionStorage.getItem("chatbot_check_claim_queue");
if (raw) {
const parsed = JSON.parse(raw);
const remaining = parsed?.remaining as any[] | undefined;
const matchedCodes = parsed?.matchedCodes ?? [];
const renderingProvider = parsed?.renderingProvider ?? null;
if (remaining && remaining.length > 0) {
const [next, ...rest] = remaining;
if (rest.length > 0) {
sessionStorage.setItem("chatbot_check_claim_queue", JSON.stringify({ remaining: rest, matchedCodes, renderingProvider }));
} else {
sessionStorage.removeItem("chatbot_check_claim_queue");
}
sessionStorage.setItem("chatbot_claim_codes", JSON.stringify({
codes: matchedCodes,
siteKey: next.siteKey,
patientId: next.patient?.id ?? null,
memberId: next.memberId,
dob: next.dob,
serviceDate: null,
renderingProvider,
}));
sessionStorage.setItem("chatbot_eligibility", JSON.stringify({
memberId: next.memberId,
dob: next.dob,
autoCheck: next.autoCheck,
}));
setTimeout(() => {
window.dispatchEvent(new CustomEvent("chatbot:eligibility-prefill"));
setWouterLocation("/insurance-status");
}, 500);
return;
}
sessionStorage.removeItem("chatbot_check_claim_queue");
}
} catch {}
// If a chatbot batch claim queue is pending, open the next patient
try {
const raw = sessionStorage.getItem("chatbot_claim_queue");
if (raw) {
const parsed = JSON.parse(raw);
const remaining = parsed?.remaining as any[] | undefined;
const matchedCodes = parsed?.matchedCodes ?? [];
const renderingProvider = parsed?.renderingProvider ?? null;
if (remaining && remaining.length > 0) {
const [next, ...rest] = remaining;
if (rest.length > 0) {
sessionStorage.setItem("chatbot_claim_queue", JSON.stringify({ remaining: rest, matchedCodes, renderingProvider }));
} else {
sessionStorage.removeItem("chatbot_claim_queue");
}
if (next.patient?.id && matchedCodes.length > 0) {
sessionStorage.setItem("chatbot_claim_prefill", JSON.stringify({
codes: matchedCodes,
siteKey: next.siteKey,
serviceDate: next.serviceDate,
autoSubmit: true,
renderingProvider,
dob: next.patient.dateOfBirth ?? null,
}));
}
setTimeout(() => {
setWouterLocation(`/claims?newPatient=${next.patient.id}`);
}, 500);
return;
}
sessionStorage.removeItem("chatbot_claim_queue");
}
} catch {}
// Chatbot batch queues (chatbot_claim_queue, chatbot_check_claim_queue) are NOT
// advanced here — they are advanced in advanceChatbotClaimQueue() which is called
// only after the selenium job completes and PDF is downloaded. This prevents the
// next patient's claim from starting before the current patient's PDF is saved.
};
// Pre Auth section