feat: AI SMS reminder flow with two-message intro, smart reschedule with availability checks

- Reminder flow: send AI self-introduction as message 1 (Twilio REST API), intent response as message 2 (TwiML) so intro always arrives first
- LangGraph reminder graph: classify yes/no/other from patient reply; 'no' now asks 'When would you like to reschedule?' directly
- Reschedule flow: new asked_reschedule_datetime stage replaces multi-step ASAP/next-week flow
  - Date-only reply (e.g. '5/18'): ask for time separately, then confirm
  - Date+time reply (e.g. '5/18 at 10am'): go straight to confirmation
  - new asked_reschedule_time_for_date and asked_reschedule_confirm_datetime stages
- Date/time parsing: regex handles M/D and am/pm formats first; falls back to Gemini for natural language
- Day-level office hours check: if requested day is closed (e.g. Sunday), reply 'Our office is closed on [date]. Choose another day?'
- Time-level office hours check: if requested time is outside working hours (e.g. 12pm during lunch), reply with actual hours (e.g. '9:00 am – 12:00 pm and 1:00 pm – 5:00 pm')
- Slot availability check: verifies no conflicting appointment for same staff member
- After appointment confirmed: patient thank-you reply triggers warm closing with upcoming appointment time
- Schedule page: office hours summary bar above grid showing today's configured hours with link to settings

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Gitead
2026-05-11 16:01:23 -04:00
parent 585b448b6e
commit 1ff843bc79
5 changed files with 842 additions and 149 deletions

View File

@@ -17,7 +17,10 @@ export type ConversationStage =
| "asked_reschedule_preference"
| "asked_reschedule_asap"
| "asked_reschedule_next_week"
| "asked_reschedule_time";
| "asked_reschedule_time"
| "asked_reschedule_datetime"
| "asked_reschedule_time_for_date"
| "asked_reschedule_confirm_datetime";
// ── Conversation stage + AI handoff per patient (DB-persisted) ────────────────
@@ -75,8 +78,9 @@ export async function startRescheduleConversation(userId: number, patientId: num
// ── Pending reschedule (in-memory — seconds-lived within a single exchange) ───
interface PendingReschedule {
newDate: Date;
dayLabel: string;
newDate: Date;
dayLabel: string;
startTime?: string; // "HH:MM" — set when full datetime was parsed together
}
const pendingRescheduleStore = new Map<string, PendingReschedule>();