feat: enhance new-patient AI chat flow with full scheduling and eligibility
- Add 3-message intro (self-intro → empathetic ack → new/existing question) via single TwiML response to guarantee delivery order - Detect reschedule intent from first message; look up existing appointment date - New patient flow: ask insurance type → MassHealth consent → member ID + DOB → Selenium eligibility check - Post-eligibility: active → ask appointment date/time with office-hours validation; inactive → ask other insurance or collect contact info - Date/time collection mirrors reschedule flow: check office day open, ask time, validate against office hours - Auto-create appointment in schedule for known patients on confirmation; use first available staff member - Add openPhoneReply toggle (Settings → AI Chat) to respond to any number at any time - Add 5-minute inactivity timeout: reset conversation to initial stage and clear pending state - Normalize MassHealth DOB to zero-padded MM/DD/YYYY before Selenium submission - Expand isExistingPatient classifier to recognize "old patient", "old", "previous", "prior" - Existing patient confirmation message now acknowledges patient type before asking about insurance Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
c1f55778ca
commit
c71624f7e7
@@ -58,7 +58,7 @@ function getNextWeekDateObjects() {
|
||||
}
|
||||
|
||||
/** Format "HH:MM" (24-h) → "H:MM am/pm" */
|
||||
function timeLabel(hhmm: string): string {
|
||||
export function timeLabel(hhmm: string): string {
|
||||
const [h, m] = hhmm.split(":").map(Number);
|
||||
const h12 = (h! % 12) || 12;
|
||||
const ampm = (h! >= 12) ? "pm" : "am";
|
||||
@@ -80,7 +80,7 @@ function messageHasTime(msg: string): boolean {
|
||||
* Parse only a date (no time) from a message like "5/18" or "next Monday".
|
||||
* Returns a UTC-midnight Date and a date-only display label, or null.
|
||||
*/
|
||||
async function parseDateOnlyFromMessage(
|
||||
export async function parseDateOnlyFromMessage(
|
||||
message: string,
|
||||
apiKey: string,
|
||||
): Promise<{ date: Date; dateLabel: string } | null> {
|
||||
@@ -249,7 +249,7 @@ Rules:
|
||||
* Returns true if the given date+time is within the office's configured hours.
|
||||
* Falls back to true (unrestricted) if no hours are configured.
|
||||
*/
|
||||
async function isWithinOfficeHours(
|
||||
export async function isWithinOfficeHours(
|
||||
date: Date,
|
||||
time: string,
|
||||
userId: number,
|
||||
@@ -285,7 +285,7 @@ async function isWithinOfficeHours(
|
||||
* Returns whether the office is open at all on a given date (day-level check).
|
||||
* Also returns the display day name (e.g. "Sunday") for use in error messages.
|
||||
*/
|
||||
async function isOfficeDayOpen(
|
||||
export async function isOfficeDayOpen(
|
||||
date: Date,
|
||||
userId: number,
|
||||
): Promise<{ open: boolean; displayDay: string }> {
|
||||
@@ -319,7 +319,7 @@ async function isOfficeDayOpen(
|
||||
* e.g. "9:00 am – 12:00 pm and 1:00 pm – 5:00 pm".
|
||||
* Returns null if no hours are configured or the day is closed.
|
||||
*/
|
||||
async function getOfficeHoursDisplay(date: Date, userId: number): Promise<string | null> {
|
||||
export async function getOfficeHoursDisplay(date: Date, userId: number): Promise<string | null> {
|
||||
try {
|
||||
const record = await storage.getOfficeHours(userId);
|
||||
if (!record?.data) return null;
|
||||
@@ -387,7 +387,7 @@ async function isSlotAvailable(
|
||||
|
||||
// ── Time parsing (legacy) ─────────────────────────────────────────────────────
|
||||
|
||||
async function parseTime(message: string, apiKey: string): Promise<string | null> {
|
||||
export async function parseTime(message: string, apiKey: string): Promise<string | null> {
|
||||
const t = message.toLowerCase();
|
||||
if (/\bmorning\b|mañana|manhã|上午|صباح|maten/i.test(t)) return "09:00";
|
||||
if (/\bafternoon\b|tarde|après-midi|下午|مساء|aprèmidi/i.test(t)) return "13:00";
|
||||
|
||||
Reference in New Issue
Block a user