feat: unify AI voice calls with SMS conversation rules, add Call Templates

AI phone calls now route through the same routePatientTurn() rule engine
as SMS (office-hours checks, slot-conflict checks, MassHealth eligibility,
staff escalation) instead of a freeform LLM chat, and speak in the
patient's preferred language (English/Spanish). Adds a separate "Call
Templates" settings section so spoken wording can be customized
independently from SMS wording.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
ff
2026-07-31 23:39:31 -04:00
co-authored by Claude Sonnet 5
parent 089e94ba88
commit 06dc63c53e
6 changed files with 973 additions and 726 deletions
+32 -3
View File
@@ -77,11 +77,10 @@ export const twilioStorage = {
generalFallback: all["_ai_chat_general_fallback"] ?? "",
rescheduleGreeting: all["_ai_chat_reschedule_greeting"] ?? "",
reminderSms: all["_ai_chat_reminder_sms"] ?? "",
callTemplate: all["_ai_chat_call_template"] ?? "",
};
},
async saveAiChatTemplates(userId: number, templates: { reminderGreeting?: string; newPatientGreeting?: string; generalFallback?: string; rescheduleGreeting?: string; reminderSms?: string; callTemplate?: string }) {
async saveAiChatTemplates(userId: number, templates: { reminderGreeting?: string; newPatientGreeting?: string; generalFallback?: string; rescheduleGreeting?: string; reminderSms?: string }) {
const settings = await db.twilioSettings.findUnique({ where: { userId } });
const existing = (settings?.templates as Record<string, string>) || {};
const updated: Record<string, string> = { ...existing };
@@ -90,7 +89,37 @@ export const twilioStorage = {
if (templates.generalFallback !== undefined) updated["_ai_chat_general_fallback"] = templates.generalFallback;
if (templates.rescheduleGreeting !== undefined) updated["_ai_chat_reschedule_greeting"] = templates.rescheduleGreeting;
if (templates.reminderSms !== undefined) updated["_ai_chat_reminder_sms"] = templates.reminderSms;
if (templates.callTemplate !== undefined) updated["_ai_chat_call_template"] = templates.callTemplate;
return db.twilioSettings.upsert({
where: { userId },
update: { templates: updated },
create: { userId, accountSid: "", authToken: "", phoneNumber: "", templates: updated },
});
},
// ── Call Templates — wording spoken during live AI phone calls, kept
// separate from the SMS wording above (routePatientTurn() consumes
// whichever set the caller passes in). ────────────────────────────────────
async getAiCallTemplates(userId: number): Promise<Record<string, string>> {
const settings = await db.twilioSettings.findUnique({ where: { userId } });
const all = (settings?.templates as Record<string, string>) || {};
return {
greeting: all["_ai_call_greeting"] ?? all["_ai_chat_call_template"] ??
"Hi {firstName}, this is Lisa, the AI assistant at {officeName}. How can I help you today?",
reminderGreeting: all["_ai_call_reminder_greeting"] ?? "Sure, I can help with that.",
newPatientGreeting: all["_ai_call_new_patient_greeting"] ?? "No problem, let's get you set up.",
generalFallback: all["_ai_call_general_fallback"] ?? "Sure, how can I help?",
};
},
async saveAiCallTemplates(userId: number, templates: { greeting?: string; reminderGreeting?: string; newPatientGreeting?: string; generalFallback?: string }) {
const settings = await db.twilioSettings.findUnique({ where: { userId } });
const existing = (settings?.templates as Record<string, string>) || {};
const updated: Record<string, string> = { ...existing };
if (templates.greeting !== undefined) updated["_ai_call_greeting"] = templates.greeting;
if (templates.reminderGreeting !== undefined) updated["_ai_call_reminder_greeting"] = templates.reminderGreeting;
if (templates.newPatientGreeting !== undefined) updated["_ai_call_new_patient_greeting"] = templates.newPatientGreeting;
if (templates.generalFallback !== undefined) updated["_ai_call_general_fallback"] = templates.generalFallback;
return db.twilioSettings.upsert({
where: { userId },
update: { templates: updated },