feat: AI chat scheduling, claim automation, and session improvements
- Internal AI chat: schedule_appointment intent books earliest available slot in Column A using office hours; claim_only intent looks up latest past appointment for service date, asks user when two appointments are within 7 days, auto-triggers correct Selenium worker with mapped prices - Gemini model updated to gemini-flash-latest; conversation history (15 messages) passed for pronoun/reference resolution; history trimmed to start with user turn so Gemini doesn't reject the context - Insurance alias file (insuranceAliases.json) replaces hardcoded siteKey matching; "tufs" now resolves to TUFTS_SCO - DOB format normalized (MM/DD/YYYY → YYYY-MM-DD) before parseLocalDate; autoCheck now fires for all insurance types, not just MH/CMSP - Claim form auto-submit: all handlers (MH, CCA, DDMA, UnitedDH, Tufts) accept formToUse and receive fee-schedule-priced form; prefillDone set after chatbot code prefill so autoSubmit gate opens correctly - Chatbot: chat history persisted in sessionStorage, cleared on logout and auto-logout; Clear button writes fresh state synchronously; message history window increased to 15 - DentaQuest/TuftsSCO Selenium: "Remember me" checkbox clicked before sign-in to persist OTP trust cookie across sessions Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,8 @@ export type InternalChatIntent =
|
||||
| "eligibility_by_id" // by explicit memberId + dob (no name)
|
||||
| "check_and_claim" // eligibility + claim procedures
|
||||
| "find_patient" // look up patient record only
|
||||
| "schedule_appointment" // add patient to today's (or specified) schedule
|
||||
| "claim_only" // submit claim for procedures (no eligibility check)
|
||||
| "navigate_claims"
|
||||
| "navigate_schedule"
|
||||
| "general";
|
||||
@@ -14,13 +16,16 @@ export type InternalChatIntent =
|
||||
export interface ChatClassification {
|
||||
intent: InternalChatIntent;
|
||||
// --- patient resolution (one of name OR id+dob) ---
|
||||
patientName?: string; // for check_eligibility / find_patient
|
||||
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)
|
||||
// --- insurance hint (only if explicitly stated in the message) ---
|
||||
insuranceHint?: string; // raw text, e.g. "masshealth", "BCBS", "CCA"
|
||||
// --- procedures (raw text, NOT CDT codes — CDT lookup is done in workflow) ---
|
||||
procedureNames?: string[]; // for check_and_claim, e.g. ["perio exam", "adult cleaning"]
|
||||
// --- scheduling ---
|
||||
appointmentDate?: string; // for schedule_appointment, YYYY-MM-DD (omit = today)
|
||||
appointmentTime?: string; // for schedule_appointment, HH:MM 24h (omit = 09:00)
|
||||
fallbackReply: string;
|
||||
}
|
||||
|
||||
@@ -38,43 +43,61 @@ Respond ONLY with valid JSON (no markdown fences):
|
||||
"dob": "<date of birth in MM/DD/YYYY if given>",
|
||||
"insuranceHint": "<insurance name only if explicitly stated in the message, e.g. 'masshealth', 'BCBS MA', 'CCA'>",
|
||||
"procedureNames": ["<raw procedure name>", ...],
|
||||
"appointmentDate": "<YYYY-MM-DD if a specific date is mentioned, omit for today>",
|
||||
"appointmentTime": "<HH:MM 24h if a specific time is mentioned, omit if not stated>",
|
||||
"fallbackReply": "<1-2 sentence reply to show the user>"
|
||||
}
|
||||
|
||||
Omit any field that is not present in the message.
|
||||
|
||||
Intents:
|
||||
- check_eligibility : user wants to check insurance for a patient identified by NAME only
|
||||
e.g. "check Maria Jesus", "verify insurance for John Smith"
|
||||
- eligibility_by_id : user provides a member ID and date of birth (no patient name)
|
||||
e.g. "check masshealth for 100xxxx, 10/10/1988"
|
||||
- check_and_claim : user wants to check eligibility AND submit procedures as claims
|
||||
e.g. "check masshealth for 100xxxx, 10/10/1988 and claim perio exam and adult cleaning"
|
||||
e.g. "check Maria Jesus and claim D0120 D1110"
|
||||
- find_patient : look up a patient record only, no eligibility
|
||||
e.g. "find patient John", "look up Smith"
|
||||
- navigate_claims : open the claims page
|
||||
- navigate_schedule : open the appointments/schedule page
|
||||
- general : anything else
|
||||
- check_eligibility : user wants to check insurance for a patient identified by NAME only
|
||||
e.g. "check Maria Jesus", "verify insurance for John Smith"
|
||||
- eligibility_by_id : user provides a member ID and date of birth (no patient name)
|
||||
e.g. "check masshealth for 100xxxx, 10/10/1988"
|
||||
- check_and_claim : user wants to check eligibility AND submit procedures as claims
|
||||
e.g. "check masshealth for 100xxxx, 10/10/1988 and claim perio exam and adult cleaning"
|
||||
e.g. "check Maria Jesus and claim D0120 D1110"
|
||||
- find_patient : look up a patient record only, no eligibility
|
||||
e.g. "find patient John", "look up Smith"
|
||||
- schedule_appointment : add a patient to the schedule (today or a specified date/time)
|
||||
e.g. "put John Smith in today's schedule"
|
||||
e.g. "schedule Maria at 2pm tomorrow"
|
||||
e.g. "add Jane Doe at 10:30"
|
||||
- claim_only : submit a claim for procedures WITHOUT an eligibility check
|
||||
e.g. "claim comprehensive exam and Pano for her"
|
||||
e.g. "claim D0120 and D1110 for John Smith"
|
||||
e.g. "bill adult cleaning for Maria"
|
||||
Use this when no eligibility check is requested — just billing/claiming services
|
||||
- navigate_claims : open the claims page
|
||||
- navigate_schedule : open the appointments/schedule page
|
||||
- general : anything else
|
||||
|
||||
Rules:
|
||||
- For check_and_claim, procedureNames should be the RAW user text
|
||||
- For check_and_claim and claim_only, procedureNames should be the RAW user text
|
||||
(e.g. "perio exam", "adult cleaning", "D0120") — do NOT translate to codes
|
||||
- insuranceHint is only set when the user explicitly names an insurance in the message
|
||||
- Keep fallbackReply to 1-2 sentences
|
||||
- For navigate intents, fallbackReply = "Opening the [page] page..."`;
|
||||
- For navigate intents, fallbackReply = "Opening the [page] page..."
|
||||
- For schedule_appointment, appointmentDate omitted means today; appointmentTime omitted means no preference
|
||||
- IMPORTANT: Use the conversation history to resolve pronouns and references.
|
||||
If the user says "her", "him", "them", "the patient", or "same patient", look back through
|
||||
the conversation history to find the patient name that was mentioned most recently.
|
||||
Always populate patientName (or memberId) from history when a pronoun is used.
|
||||
Never return an empty patientName just because the current message uses a pronoun.`;
|
||||
|
||||
// ─── Classifier ───────────────────────────────────────────────────────────────
|
||||
|
||||
export async function classifyInternalChat(
|
||||
message: string,
|
||||
apiKey: string,
|
||||
extraSystemPrompt?: string
|
||||
extraSystemPrompt?: string,
|
||||
history: { role: "user" | "assistant"; text: string }[] = []
|
||||
): Promise<ChatClassification> {
|
||||
const fallback: ChatClassification = {
|
||||
intent: "general",
|
||||
fallbackReply:
|
||||
"I can search for a patient, check eligibility, run check & claim, or navigate to claims or appointments.",
|
||||
"I can search for a patient, check eligibility, run check & claim, schedule appointments, or navigate to claims or appointments.",
|
||||
};
|
||||
|
||||
if (!apiKey) return fallback;
|
||||
@@ -84,9 +107,24 @@ export async function classifyInternalChat(
|
||||
: BASE_SYSTEM_PROMPT;
|
||||
|
||||
try {
|
||||
const llm = new ChatGoogleGenerativeAI({ model: "gemini-1.5-flash", apiKey });
|
||||
const llm = new ChatGoogleGenerativeAI({ model: "gemini-flash-latest", apiKey });
|
||||
|
||||
// Gemini requires conversation to start with a user turn — drop any leading assistant messages
|
||||
const trimmedHistory = history.slice(
|
||||
history.findIndex((h) => h.role === "user")
|
||||
).filter((_, i, arr) => {
|
||||
// Also drop consecutive same-role messages (keep last of each run)
|
||||
if (i === arr.length - 1) return true;
|
||||
return arr[i]!.role !== arr[i + 1]!.role;
|
||||
});
|
||||
|
||||
const historyMessages = trimmedHistory.map((h) => ({
|
||||
role: h.role,
|
||||
content: h.text,
|
||||
}));
|
||||
const response = await llm.invoke([
|
||||
{ role: "system", content: systemPrompt },
|
||||
...historyMessages,
|
||||
{ role: "user", content: message },
|
||||
]);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user