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:
@@ -5,6 +5,7 @@ import { getLlm, type AiProvider } from "./llm-factory";
|
||||
export type InternalChatIntent =
|
||||
| "check_eligibility" // by patient name → look up in DB
|
||||
| "eligibility_by_id" // by explicit memberId + dob (no name)
|
||||
| "eligibility_by_name_dob" // by explicit first name + last name + dob (no memberId) — United Ucard name search
|
||||
| "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
|
||||
@@ -26,6 +27,8 @@ export interface ChatClassification {
|
||||
patientName?: string; // for check_eligibility / find_patient / schedule_appointment
|
||||
memberId?: string; // for eligibility_by_id / check_and_claim
|
||||
dob?: string; // for eligibility_by_id / check_and_claim (MM/DD/YYYY)
|
||||
firstName?: string; // for eligibility_by_name_dob
|
||||
lastName?: string; // for eligibility_by_name_dob
|
||||
// --- batch eligibility (multiple patients) ---
|
||||
patients?: { memberId: string; dob: string }[]; // for batch_eligibility
|
||||
// --- batch claim (same procedures for multiple patients by name) ---
|
||||
@@ -58,6 +61,8 @@ Respond ONLY with valid JSON (no markdown fences):
|
||||
"intent": "<intent>",
|
||||
"patientName": "<full name if mentioned by name>",
|
||||
"memberId": "<member/insurance ID if given explicitly or found in history>",
|
||||
"firstName": "<first name, only for eligibility_by_name_dob>",
|
||||
"lastName": "<last name, only for eligibility_by_name_dob>",
|
||||
"dob": "<date of birth in MM/DD/YYYY if given explicitly or found in history>",
|
||||
"patients": [{"memberId": "<id>", "dob": "<MM/DD/YYYY>"}, ...],
|
||||
"patientNames": ["<name1>", "<name2>", ...],
|
||||
@@ -86,6 +91,10 @@ Intents:
|
||||
e.g. "check mh for 100xxxx, 10/10/1988 and schedule on 4/10/2026"
|
||||
e.g. "check mh for 100xxxx, 10/10/1988 and make appointment on 5/1/2026"
|
||||
In these cases set appointmentDate to the mentioned date (YYYY-MM-DD)
|
||||
- eligibility_by_name_dob : user provides a first name, last name AND date of birth but NO member ID
|
||||
e.g. "check ucard Kai Gao 08/30/1997", "Kai Gao, 8/30/1997"
|
||||
e.g. "canin gomez, 08/30/1997, ucard" → firstName "canin", lastName "gomez" (ALWAYS: first word = first name, all remaining words = last name, e.g. "min ping wang" → firstName "min", lastName "ping wang")
|
||||
Set firstName, lastName and dob (MM/DD/YYYY). Set insuranceHint if stated (e.g. "ucard").
|
||||
- batch_eligibility : user provides MULTIPLE member IDs with dates of birth in one message
|
||||
e.g. "check mh for 100xxxx 10/10/1988 and 200xxxx 5/5/2000"
|
||||
e.g. "check 100xxxx, 10/10/1988 and 200xxxx, 5/5/2000"
|
||||
@@ -236,6 +245,19 @@ function parseBareIdDob(message: string): { memberId: string; dob: string } | nu
|
||||
return null;
|
||||
}
|
||||
|
||||
// Deterministic fast-path for a bare "First Last DOB" (or "DOB First Last") message,
|
||||
// e.g. "Kai Gao 08/30/1997" or "Kai Gao, 8/30/1997". Two or more alphabetic words: first word = first name, the rest = last name.
|
||||
function parseBareNameDob(message: string): { firstName: string; lastName: string; dob: string } | null {
|
||||
const text = message.trim();
|
||||
const dob = "(\\d{1,2}[\\/\\-]\\d{1,2}[\\/\\-]\\d{4})";
|
||||
const name = "([A-Za-z][A-Za-z'\\-]*)\\s+([A-Za-z][A-Za-z'\\-]*(?:\\s+[A-Za-z][A-Za-z'\\-]*)*)";
|
||||
const nameThenDob = text.match(new RegExp(`^${name}[\\s,]+${dob}$`));
|
||||
if (nameThenDob) return { firstName: nameThenDob[1]!, lastName: nameThenDob[2]!, dob: nameThenDob[3]!.replace(/-/g, "/") };
|
||||
const dobThenName = text.match(new RegExp(`^${dob}[\\s,]+${name}$`));
|
||||
if (dobThenName) return { firstName: dobThenName[2]!, lastName: dobThenName[3]!, dob: dobThenName[1]!.replace(/-/g, "/") };
|
||||
return null;
|
||||
}
|
||||
|
||||
export async function classifyInternalChat(
|
||||
message: string,
|
||||
apiKey: string,
|
||||
@@ -261,6 +283,17 @@ export async function classifyInternalChat(
|
||||
};
|
||||
}
|
||||
|
||||
const bareName = parseBareNameDob(message);
|
||||
if (bareName) {
|
||||
return {
|
||||
intent: "eligibility_by_name_dob",
|
||||
firstName: bareName.firstName,
|
||||
lastName: bareName.lastName,
|
||||
dob: bareName.dob,
|
||||
fallbackReply: `Checking eligibility for ${bareName.firstName} ${bareName.lastName}...`,
|
||||
};
|
||||
}
|
||||
|
||||
if (!apiKey) return fallback;
|
||||
|
||||
// Prefer the client's local date (avoids UTC midnight rollover for US timezones)
|
||||
|
||||
Reference in New Issue
Block a user