- Add floating chat window Hand-off to AI toggle (per-patient) and after-hours AI toggle (global) - Add LangGraph-powered appointment reminder flow: AI introduces itself, classifies YES/NO, handles confirmation with appointment date/time - Add multi-step rescheduling flow: ASAP vs next week, tomorrow offer, Mon/Tue/Wed picker, morning/afternoon time slot — automatically updates appointment in DB - Add new patient / after-hours flow: new vs existing patient, dental insurance check, MassHealth Selenium eligibility check (auto-uses saved member ID + DOB for existing patients), self-pay fallback - Add AI Chat Settings page (Settings → Advanced) with editable greeting templates and LangGraph flow diagrams for both reminder and new-patient flows - Add Schedule a New Patient template option in chat window, starts new-patient conversation flow - Add GET/PUT endpoints for AI handoff, after-hours handoff, and AI chat templates - Add multilingual support (7 languages) across all AI reply nodes with LLM generation and hardcoded fallbacks - Add pending reschedule in-memory store and conversation stage tracking across all flows Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
96 lines
4.7 KiB
TypeScript
96 lines
4.7 KiB
TypeScript
// In-memory store for per-patient AI handoff toggle, conversation stage,
|
|
// and per-user after-hours handoff toggle.
|
|
// Conversation key: `${userId}:${patientId}`
|
|
|
|
export type ConversationStage =
|
|
| "initial" // default — no active conversation
|
|
| "reminder_initial" // office sent a reminder, waiting for first patient reply
|
|
| "greeted" // reminder intro sent, waiting for yes/no
|
|
| "done" // conversation complete
|
|
| "new_patient_greeted" // new-patient greeting sent, waiting for patient intent
|
|
| "asked_new_or_existing" // AI asked "new or existing patient?"
|
|
| "asked_new_patient_insurance" // AI asked new patient about insurance
|
|
| "asked_existing_insurance" // AI asked existing patient about same insurance
|
|
| "asked_appointment_time" // AI asked when they'd like to come
|
|
| "awaiting_masshealth_info" // AI asked for Member ID + DOB, waiting for reply
|
|
| "asked_appointment_preference" // Selenium: ACTIVE — AI asked check-up vs problem
|
|
| "asked_self_pay" // Selenium: INACTIVE — AI asked if self-pay exam
|
|
| "asked_reschedule_confirm" // AI asked "Would you like to reschedule?"
|
|
| "asked_reschedule_preference" // AI asked ASAP vs next week
|
|
| "asked_reschedule_asap" // AI asked "Can you come tomorrow?"
|
|
| "asked_reschedule_next_week" // AI offered Mon/Tue/Wed next week
|
|
| "asked_reschedule_time"; // Day confirmed — AI asked morning or afternoon
|
|
|
|
const handoffStore = new Map<string, boolean>();
|
|
const stageStore = new Map<string, ConversationStage>();
|
|
const afterHoursStore = new Map<number, boolean>(); // keyed by userId
|
|
|
|
function convKey(userId: number, patientId: number): string {
|
|
return `${userId}:${patientId}`;
|
|
}
|
|
|
|
// ── Per-patient handoff toggle (default ON) ───────────────────────────────────
|
|
|
|
export function getHandoff(userId: number, patientId: number): boolean {
|
|
const k = convKey(userId, patientId);
|
|
return handoffStore.has(k) ? handoffStore.get(k)! : true;
|
|
}
|
|
|
|
export function setHandoff(userId: number, patientId: number, enabled: boolean): void {
|
|
handoffStore.set(convKey(userId, patientId), enabled);
|
|
}
|
|
|
|
// ── Per-user after-hours handoff toggle (default ON) ─────────────────────────
|
|
|
|
export function getAfterHoursHandoff(userId: number): boolean {
|
|
return afterHoursStore.has(userId) ? afterHoursStore.get(userId)! : true;
|
|
}
|
|
|
|
export function setAfterHoursHandoff(userId: number, enabled: boolean): void {
|
|
afterHoursStore.set(userId, enabled);
|
|
}
|
|
|
|
// ── Conversation stage ────────────────────────────────────────────────────────
|
|
|
|
export function getStage(userId: number, patientId: number): ConversationStage {
|
|
return stageStore.get(convKey(userId, patientId)) ?? "initial";
|
|
}
|
|
|
|
export function setStage(userId: number, patientId: number, stage: ConversationStage): void {
|
|
stageStore.set(convKey(userId, patientId), stage);
|
|
}
|
|
|
|
// Called when office sends an outbound reminder — marks next patient reply
|
|
// as the start of a reminder conversation.
|
|
export function resetConversation(userId: number, patientId: number): void {
|
|
stageStore.set(convKey(userId, patientId), "reminder_initial");
|
|
}
|
|
|
|
// Called when office sends the new-patient greeting — marks next patient reply
|
|
// as the start of the new-patient conversation flow.
|
|
export function startNewPatientConversation(userId: number, patientId: number): void {
|
|
stageStore.set(convKey(userId, patientId), "new_patient_greeted");
|
|
}
|
|
|
|
// ── Pending reschedule data ───────────────────────────────────────────────────
|
|
// Holds the confirmed new date while AI waits for a time-slot answer.
|
|
|
|
interface PendingReschedule {
|
|
newDate: Date; // JS Date for the new appointment day (midnight UTC)
|
|
dayLabel: string; // human-readable, e.g. "Tuesday, May 19"
|
|
}
|
|
|
|
const pendingRescheduleStore = new Map<string, PendingReschedule>();
|
|
|
|
export function setPendingReschedule(userId: number, patientId: number, data: PendingReschedule): void {
|
|
pendingRescheduleStore.set(convKey(userId, patientId), data);
|
|
}
|
|
|
|
export function getPendingReschedule(userId: number, patientId: number): PendingReschedule | undefined {
|
|
return pendingRescheduleStore.get(convKey(userId, patientId));
|
|
}
|
|
|
|
export function clearPendingReschedule(userId: number, patientId: number): void {
|
|
pendingRescheduleStore.delete(convKey(userId, patientId));
|
|
}
|