feat: batch eligibility by patient name + "Check All & Appointment Today" option

- Add batch_eligibility_by_name intent so "check Mary and Sinthia" resolves multiple names
- Add "Check All & Appointment Today" button to batch eligibility UI — creates appointment for each patient after their eligibility check completes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
ff
2026-06-19 23:32:05 -04:00
parent e081f32648
commit 60689e58f6
4 changed files with 136 additions and 8 deletions

View File

@@ -6,6 +6,7 @@ export type InternalChatIntent =
| "check_eligibility" // by patient name → look up in DB
| "eligibility_by_id" // by explicit memberId + dob (no name)
| "batch_eligibility" // multiple patients by memberId + dob
| "batch_eligibility_by_name" // multiple patients by name (no memberId)
| "batch_claim" // claim same procedures for multiple patients by name
| "batch_check_and_claim" // eligibility + claim for multiple patients by memberId+dob
| "check_and_claim" // eligibility + claim procedures
@@ -81,6 +82,11 @@ Intents:
e.g. "check 100xxxx, 10/10/1988 and 200xxxx, 5/5/2000"
Use this ONLY when TWO OR MORE distinct memberId+dob pairs are given.
Put each pair into the "patients" array. Also set insuranceHint if stated.
- batch_eligibility_by_name : user wants to check eligibility for MULTIPLE patients identified by NAME
e.g. "check Mary and Sinthia", "check eligibility for John, Jane, and Bob"
e.g. "verify insurance for Mary and Sinthia"
Use this when TWO OR MORE patient names are given WITHOUT member IDs.
Put each patient name into the "patientNames" array.
- batch_claim : user wants to claim the SAME procedures for MULTIPLE patients identified by NAME
e.g. "claim perio exam and adult prophy for Jackaline and Keioson"
e.g. "perio exam, adult cleaning for Maria and John"

View File

@@ -323,6 +323,10 @@ export async function runInternalChatWorkflow(
return await handleBatchEligibility(classification, storage);
}
if (intent === "batch_eligibility_by_name") {
return await handleBatchEligibilityByName(classification, storage);
}
// ── Check eligibility + claim procedures ──────────────────────────────────
if (intent === "check_and_claim") {
@@ -525,6 +529,95 @@ async function handleBatchEligibility(
};
}
// ─── batch_eligibility_by_name ───────────────────────────────────────────────
async function handleBatchEligibilityByName(
c: ChatClassification,
storage: StorageLike
): Promise<ChatResponse> {
const names = c.patientNames ?? [];
if (names.length < 2) {
return { reply: "Please include at least two patient names to batch-check eligibility." };
}
const resolved: {
memberId: string;
dob: string;
siteKey: string;
autoCheck: string;
patient: ResolvedPatient | null;
}[] = [];
const notFound: string[] = [];
const noInsurance: string[] = [];
for (const name of names) {
const trimmed = name.trim();
if (!trimmed) continue;
const raw = await findPatientByName(trimmed, storage);
if (!raw) {
notFound.push(trimmed);
continue;
}
const patient = patientToResult(raw);
const fullName = `${patient.firstName ?? ""} ${patient.lastName ?? ""}`.trim();
if (!patient.insuranceId) {
noInsurance.push(fullName);
continue;
}
const resolvedDob = patient.dateOfBirth ?? null;
if (!resolvedDob) {
noInsurance.push(fullName);
continue;
}
const siteKey = resolveSiteKey(
patient.insuranceProvider ?? null,
c.insuranceHint ?? null
) ?? "MH";
resolved.push({
memberId: patient.insuranceId,
dob: resolvedDob,
siteKey,
autoCheck: siteKeyToAutoCheck(siteKey, resolvedDob),
patient,
});
}
if (resolved.length === 0) {
if (notFound.length > 0) {
return { reply: `Could not find any patients matching: ${notFound.join(", ")}. Please check the spelling.` };
}
if (noInsurance.length > 0) {
return { reply: `Found ${noInsurance.join(", ")} but they have no Member ID or DOB on file. Please add their insurance info first.` };
}
return { reply: "Could not resolve any patients for eligibility check." };
}
const labels = resolved.map((r) => {
const name = r.patient
? `${r.patient.firstName ?? ""} ${r.patient.lastName ?? ""}`.trim()
: `ID ${r.memberId}`;
return name;
});
let reply = `Ready to check eligibility for ${resolved.length} patients: ${labels.join(", ")}.`;
if (notFound.length > 0) {
reply += ` Could not find: ${notFound.join(", ")}.`;
}
if (noInsurance.length > 0) {
reply += ` Missing insurance info: ${noInsurance.join(", ")}.`;
}
return {
reply,
action: "batch_eligibility_ready",
actionData: { queue: resolved },
};
}
// ─── batch_check_and_claim ────────────────────────────────────────────────────
async function handleBatchCheckAndClaim(