feat: persist AI conversation state in DB and fix LangGraph flow bugs
- Replace in-memory Maps in aiHandoffStore with DB-backed async functions using new patient_conversation table (stage + aiHandoff per patient) - Add afterHoursEnabled to ai_settings table (persists across restarts) - Fix runtime crash in reschedule-graph: mon/tue/wed variables were out of scope in the next-week fallback branch (ReferenceError) - Wire rescheduleGreeting and generalFallback chat templates through to LangGraph nodes so user-configured messages take effect - Add otherNode to reminder-graph to handle unclassified patient replies (e.g. "I want another appointment") and route to booking flow - Fetch chatTemplates once per webhook request instead of per stage Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,7 @@ export const AiSettingsAggregateResultSchema = z.object({ _count: z.object({
|
||||
id: z.number(),
|
||||
userId: z.number(),
|
||||
apiKey: z.number(),
|
||||
afterHoursEnabled: z.number(),
|
||||
user: z.number()
|
||||
}).optional(),
|
||||
_sum: z.object({
|
||||
|
||||
@@ -3,5 +3,6 @@ export const AiSettingsCreateResultSchema = z.object({
|
||||
id: z.number().int(),
|
||||
userId: z.number().int(),
|
||||
apiKey: z.string(),
|
||||
afterHoursEnabled: z.boolean(),
|
||||
user: z.unknown()
|
||||
});
|
||||
@@ -3,5 +3,6 @@ export const AiSettingsDeleteResultSchema = z.nullable(z.object({
|
||||
id: z.number().int(),
|
||||
userId: z.number().int(),
|
||||
apiKey: z.string(),
|
||||
afterHoursEnabled: z.boolean(),
|
||||
user: z.unknown()
|
||||
}));
|
||||
@@ -3,5 +3,6 @@ export const AiSettingsFindFirstResultSchema = z.nullable(z.object({
|
||||
id: z.number().int(),
|
||||
userId: z.number().int(),
|
||||
apiKey: z.string(),
|
||||
afterHoursEnabled: z.boolean(),
|
||||
user: z.unknown()
|
||||
}));
|
||||
@@ -4,6 +4,7 @@ export const AiSettingsFindManyResultSchema = z.object({
|
||||
id: z.number().int(),
|
||||
userId: z.number().int(),
|
||||
apiKey: z.string(),
|
||||
afterHoursEnabled: z.boolean(),
|
||||
user: z.unknown()
|
||||
})),
|
||||
pagination: z.object({
|
||||
|
||||
@@ -3,5 +3,6 @@ export const AiSettingsFindUniqueResultSchema = z.nullable(z.object({
|
||||
id: z.number().int(),
|
||||
userId: z.number().int(),
|
||||
apiKey: z.string(),
|
||||
afterHoursEnabled: z.boolean(),
|
||||
user: z.unknown()
|
||||
}));
|
||||
@@ -3,10 +3,12 @@ export const AiSettingsGroupByResultSchema = z.array(z.object({
|
||||
id: z.number().int(),
|
||||
userId: z.number().int(),
|
||||
apiKey: z.string(),
|
||||
afterHoursEnabled: z.boolean(),
|
||||
_count: z.object({
|
||||
id: z.number(),
|
||||
userId: z.number(),
|
||||
apiKey: z.number(),
|
||||
afterHoursEnabled: z.number(),
|
||||
user: z.number()
|
||||
}).optional(),
|
||||
_sum: z.object({
|
||||
|
||||
@@ -3,5 +3,6 @@ export const AiSettingsUpdateResultSchema = z.nullable(z.object({
|
||||
id: z.number().int(),
|
||||
userId: z.number().int(),
|
||||
apiKey: z.string(),
|
||||
afterHoursEnabled: z.boolean(),
|
||||
user: z.unknown()
|
||||
}));
|
||||
@@ -3,5 +3,6 @@ export const AiSettingsUpsertResultSchema = z.object({
|
||||
id: z.number().int(),
|
||||
userId: z.number().int(),
|
||||
apiKey: z.string(),
|
||||
afterHoursEnabled: z.boolean(),
|
||||
user: z.unknown()
|
||||
});
|
||||
@@ -28,7 +28,8 @@ export const PatientAggregateResultSchema = z.object({ _count: z.object({
|
||||
groups: z.number(),
|
||||
payment: z.number(),
|
||||
communications: z.number(),
|
||||
documents: z.number()
|
||||
documents: z.number(),
|
||||
conversation: z.number()
|
||||
}).optional(),
|
||||
_sum: z.object({
|
||||
id: z.number().nullable(),
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import * as z from 'zod';
|
||||
export const PatientConversationAggregateResultSchema = z.object({ _count: z.object({
|
||||
id: z.number(),
|
||||
patientId: z.number(),
|
||||
userId: z.number(),
|
||||
stage: z.number(),
|
||||
aiHandoff: z.number(),
|
||||
updatedAt: z.number(),
|
||||
patient: z.number(),
|
||||
user: z.number()
|
||||
}).optional(),
|
||||
_sum: z.object({
|
||||
id: z.number().nullable(),
|
||||
patientId: z.number().nullable(),
|
||||
userId: z.number().nullable()
|
||||
}).nullable().optional(),
|
||||
_avg: z.object({
|
||||
id: z.number().nullable(),
|
||||
patientId: z.number().nullable(),
|
||||
userId: z.number().nullable()
|
||||
}).nullable().optional(),
|
||||
_min: z.object({
|
||||
id: z.number().int().nullable(),
|
||||
patientId: z.number().int().nullable(),
|
||||
userId: z.number().int().nullable(),
|
||||
stage: z.string().nullable(),
|
||||
updatedAt: z.date().nullable()
|
||||
}).nullable().optional(),
|
||||
_max: z.object({
|
||||
id: z.number().int().nullable(),
|
||||
patientId: z.number().int().nullable(),
|
||||
userId: z.number().int().nullable(),
|
||||
stage: z.string().nullable(),
|
||||
updatedAt: z.date().nullable()
|
||||
}).nullable().optional()});
|
||||
@@ -0,0 +1,2 @@
|
||||
import * as z from 'zod';
|
||||
export const PatientConversationCountResultSchema = z.number();
|
||||
@@ -0,0 +1,4 @@
|
||||
import * as z from 'zod';
|
||||
export const PatientConversationCreateManyResultSchema = z.object({
|
||||
count: z.number()
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
import * as z from 'zod';
|
||||
export const PatientConversationCreateResultSchema = z.object({
|
||||
id: z.number().int(),
|
||||
patientId: z.number().int(),
|
||||
userId: z.number().int(),
|
||||
stage: z.string(),
|
||||
aiHandoff: z.boolean(),
|
||||
updatedAt: z.date(),
|
||||
patient: z.unknown(),
|
||||
user: z.unknown()
|
||||
});
|
||||
@@ -0,0 +1,4 @@
|
||||
import * as z from 'zod';
|
||||
export const PatientConversationDeleteManyResultSchema = z.object({
|
||||
count: z.number()
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
import * as z from 'zod';
|
||||
export const PatientConversationDeleteResultSchema = z.nullable(z.object({
|
||||
id: z.number().int(),
|
||||
patientId: z.number().int(),
|
||||
userId: z.number().int(),
|
||||
stage: z.string(),
|
||||
aiHandoff: z.boolean(),
|
||||
updatedAt: z.date(),
|
||||
patient: z.unknown(),
|
||||
user: z.unknown()
|
||||
}));
|
||||
@@ -0,0 +1,11 @@
|
||||
import * as z from 'zod';
|
||||
export const PatientConversationFindFirstResultSchema = z.nullable(z.object({
|
||||
id: z.number().int(),
|
||||
patientId: z.number().int(),
|
||||
userId: z.number().int(),
|
||||
stage: z.string(),
|
||||
aiHandoff: z.boolean(),
|
||||
updatedAt: z.date(),
|
||||
patient: z.unknown(),
|
||||
user: z.unknown()
|
||||
}));
|
||||
@@ -0,0 +1,21 @@
|
||||
import * as z from 'zod';
|
||||
export const PatientConversationFindManyResultSchema = z.object({
|
||||
data: z.array(z.object({
|
||||
id: z.number().int(),
|
||||
patientId: z.number().int(),
|
||||
userId: z.number().int(),
|
||||
stage: z.string(),
|
||||
aiHandoff: z.boolean(),
|
||||
updatedAt: z.date(),
|
||||
patient: z.unknown(),
|
||||
user: z.unknown()
|
||||
})),
|
||||
pagination: z.object({
|
||||
page: z.number().int().min(1),
|
||||
pageSize: z.number().int().min(1),
|
||||
total: z.number().int().min(0),
|
||||
totalPages: z.number().int().min(0),
|
||||
hasNext: z.boolean(),
|
||||
hasPrev: z.boolean()
|
||||
})
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
import * as z from 'zod';
|
||||
export const PatientConversationFindUniqueResultSchema = z.nullable(z.object({
|
||||
id: z.number().int(),
|
||||
patientId: z.number().int(),
|
||||
userId: z.number().int(),
|
||||
stage: z.string(),
|
||||
aiHandoff: z.boolean(),
|
||||
updatedAt: z.date(),
|
||||
patient: z.unknown(),
|
||||
user: z.unknown()
|
||||
}));
|
||||
@@ -0,0 +1,43 @@
|
||||
import * as z from 'zod';
|
||||
export const PatientConversationGroupByResultSchema = z.array(z.object({
|
||||
id: z.number().int(),
|
||||
patientId: z.number().int(),
|
||||
userId: z.number().int(),
|
||||
stage: z.string(),
|
||||
aiHandoff: z.boolean(),
|
||||
updatedAt: z.date(),
|
||||
_count: z.object({
|
||||
id: z.number(),
|
||||
patientId: z.number(),
|
||||
userId: z.number(),
|
||||
stage: z.number(),
|
||||
aiHandoff: z.number(),
|
||||
updatedAt: z.number(),
|
||||
patient: z.number(),
|
||||
user: z.number()
|
||||
}).optional(),
|
||||
_sum: z.object({
|
||||
id: z.number().nullable(),
|
||||
patientId: z.number().nullable(),
|
||||
userId: z.number().nullable()
|
||||
}).nullable().optional(),
|
||||
_avg: z.object({
|
||||
id: z.number().nullable(),
|
||||
patientId: z.number().nullable(),
|
||||
userId: z.number().nullable()
|
||||
}).nullable().optional(),
|
||||
_min: z.object({
|
||||
id: z.number().int().nullable(),
|
||||
patientId: z.number().int().nullable(),
|
||||
userId: z.number().int().nullable(),
|
||||
stage: z.string().nullable(),
|
||||
updatedAt: z.date().nullable()
|
||||
}).nullable().optional(),
|
||||
_max: z.object({
|
||||
id: z.number().int().nullable(),
|
||||
patientId: z.number().int().nullable(),
|
||||
userId: z.number().int().nullable(),
|
||||
stage: z.string().nullable(),
|
||||
updatedAt: z.date().nullable()
|
||||
}).nullable().optional()
|
||||
}));
|
||||
@@ -0,0 +1,4 @@
|
||||
import * as z from 'zod';
|
||||
export const PatientConversationUpdateManyResultSchema = z.object({
|
||||
count: z.number()
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
import * as z from 'zod';
|
||||
export const PatientConversationUpdateResultSchema = z.nullable(z.object({
|
||||
id: z.number().int(),
|
||||
patientId: z.number().int(),
|
||||
userId: z.number().int(),
|
||||
stage: z.string(),
|
||||
aiHandoff: z.boolean(),
|
||||
updatedAt: z.date(),
|
||||
patient: z.unknown(),
|
||||
user: z.unknown()
|
||||
}));
|
||||
@@ -0,0 +1,11 @@
|
||||
import * as z from 'zod';
|
||||
export const PatientConversationUpsertResultSchema = z.object({
|
||||
id: z.number().int(),
|
||||
patientId: z.number().int(),
|
||||
userId: z.number().int(),
|
||||
stage: z.string(),
|
||||
aiHandoff: z.boolean(),
|
||||
updatedAt: z.date(),
|
||||
patient: z.unknown(),
|
||||
user: z.unknown()
|
||||
});
|
||||
@@ -28,5 +28,6 @@ export const PatientCreateResultSchema = z.object({
|
||||
groups: z.array(z.unknown()),
|
||||
payment: z.array(z.unknown()),
|
||||
communications: z.array(z.unknown()),
|
||||
documents: z.array(z.unknown())
|
||||
documents: z.array(z.unknown()),
|
||||
conversation: z.unknown().optional()
|
||||
});
|
||||
@@ -28,5 +28,6 @@ export const PatientDeleteResultSchema = z.nullable(z.object({
|
||||
groups: z.array(z.unknown()),
|
||||
payment: z.array(z.unknown()),
|
||||
communications: z.array(z.unknown()),
|
||||
documents: z.array(z.unknown())
|
||||
documents: z.array(z.unknown()),
|
||||
conversation: z.unknown().optional()
|
||||
}));
|
||||
@@ -28,5 +28,6 @@ export const PatientFindFirstResultSchema = z.nullable(z.object({
|
||||
groups: z.array(z.unknown()),
|
||||
payment: z.array(z.unknown()),
|
||||
communications: z.array(z.unknown()),
|
||||
documents: z.array(z.unknown())
|
||||
documents: z.array(z.unknown()),
|
||||
conversation: z.unknown().optional()
|
||||
}));
|
||||
@@ -29,7 +29,8 @@ export const PatientFindManyResultSchema = z.object({
|
||||
groups: z.array(z.unknown()),
|
||||
payment: z.array(z.unknown()),
|
||||
communications: z.array(z.unknown()),
|
||||
documents: z.array(z.unknown())
|
||||
documents: z.array(z.unknown()),
|
||||
conversation: z.unknown().optional()
|
||||
})),
|
||||
pagination: z.object({
|
||||
page: z.number().int().min(1),
|
||||
|
||||
@@ -28,5 +28,6 @@ export const PatientFindUniqueResultSchema = z.nullable(z.object({
|
||||
groups: z.array(z.unknown()),
|
||||
payment: z.array(z.unknown()),
|
||||
communications: z.array(z.unknown()),
|
||||
documents: z.array(z.unknown())
|
||||
documents: z.array(z.unknown()),
|
||||
conversation: z.unknown().optional()
|
||||
}));
|
||||
@@ -49,7 +49,8 @@ export const PatientGroupByResultSchema = z.array(z.object({
|
||||
groups: z.number(),
|
||||
payment: z.number(),
|
||||
communications: z.number(),
|
||||
documents: z.number()
|
||||
documents: z.number(),
|
||||
conversation: z.number()
|
||||
}).optional(),
|
||||
_sum: z.object({
|
||||
id: z.number().nullable(),
|
||||
|
||||
@@ -28,5 +28,6 @@ export const PatientUpdateResultSchema = z.nullable(z.object({
|
||||
groups: z.array(z.unknown()),
|
||||
payment: z.array(z.unknown()),
|
||||
communications: z.array(z.unknown()),
|
||||
documents: z.array(z.unknown())
|
||||
documents: z.array(z.unknown()),
|
||||
conversation: z.unknown().optional()
|
||||
}));
|
||||
@@ -28,5 +28,6 @@ export const PatientUpsertResultSchema = z.object({
|
||||
groups: z.array(z.unknown()),
|
||||
payment: z.array(z.unknown()),
|
||||
communications: z.array(z.unknown()),
|
||||
documents: z.array(z.unknown())
|
||||
documents: z.array(z.unknown()),
|
||||
conversation: z.unknown().optional()
|
||||
});
|
||||
@@ -23,7 +23,8 @@ export const UserAggregateResultSchema = z.object({ _count: z.object({
|
||||
officeHours: z.number(),
|
||||
officeContact: z.number(),
|
||||
procedureTimeslot: z.number(),
|
||||
insuranceContacts: z.number()
|
||||
insuranceContacts: z.number(),
|
||||
patientConversations: z.number()
|
||||
}).optional(),
|
||||
_sum: z.object({
|
||||
id: z.number().nullable()
|
||||
|
||||
@@ -23,5 +23,6 @@ export const UserCreateResultSchema = z.object({
|
||||
officeHours: z.unknown().optional(),
|
||||
officeContact: z.unknown().optional(),
|
||||
procedureTimeslot: z.unknown().optional(),
|
||||
insuranceContacts: z.array(z.unknown())
|
||||
insuranceContacts: z.array(z.unknown()),
|
||||
patientConversations: z.array(z.unknown())
|
||||
});
|
||||
@@ -23,5 +23,6 @@ export const UserDeleteResultSchema = z.nullable(z.object({
|
||||
officeHours: z.unknown().optional(),
|
||||
officeContact: z.unknown().optional(),
|
||||
procedureTimeslot: z.unknown().optional(),
|
||||
insuranceContacts: z.array(z.unknown())
|
||||
insuranceContacts: z.array(z.unknown()),
|
||||
patientConversations: z.array(z.unknown())
|
||||
}));
|
||||
@@ -23,5 +23,6 @@ export const UserFindFirstResultSchema = z.nullable(z.object({
|
||||
officeHours: z.unknown().optional(),
|
||||
officeContact: z.unknown().optional(),
|
||||
procedureTimeslot: z.unknown().optional(),
|
||||
insuranceContacts: z.array(z.unknown())
|
||||
insuranceContacts: z.array(z.unknown()),
|
||||
patientConversations: z.array(z.unknown())
|
||||
}));
|
||||
@@ -24,7 +24,8 @@ export const UserFindManyResultSchema = z.object({
|
||||
officeHours: z.unknown().optional(),
|
||||
officeContact: z.unknown().optional(),
|
||||
procedureTimeslot: z.unknown().optional(),
|
||||
insuranceContacts: z.array(z.unknown())
|
||||
insuranceContacts: z.array(z.unknown()),
|
||||
patientConversations: z.array(z.unknown())
|
||||
})),
|
||||
pagination: z.object({
|
||||
page: z.number().int().min(1),
|
||||
|
||||
@@ -23,5 +23,6 @@ export const UserFindUniqueResultSchema = z.nullable(z.object({
|
||||
officeHours: z.unknown().optional(),
|
||||
officeContact: z.unknown().optional(),
|
||||
procedureTimeslot: z.unknown().optional(),
|
||||
insuranceContacts: z.array(z.unknown())
|
||||
insuranceContacts: z.array(z.unknown()),
|
||||
patientConversations: z.array(z.unknown())
|
||||
}));
|
||||
@@ -29,7 +29,8 @@ export const UserGroupByResultSchema = z.array(z.object({
|
||||
officeHours: z.number(),
|
||||
officeContact: z.number(),
|
||||
procedureTimeslot: z.number(),
|
||||
insuranceContacts: z.number()
|
||||
insuranceContacts: z.number(),
|
||||
patientConversations: z.number()
|
||||
}).optional(),
|
||||
_sum: z.object({
|
||||
id: z.number().nullable()
|
||||
|
||||
@@ -23,5 +23,6 @@ export const UserUpdateResultSchema = z.nullable(z.object({
|
||||
officeHours: z.unknown().optional(),
|
||||
officeContact: z.unknown().optional(),
|
||||
procedureTimeslot: z.unknown().optional(),
|
||||
insuranceContacts: z.array(z.unknown())
|
||||
insuranceContacts: z.array(z.unknown()),
|
||||
patientConversations: z.array(z.unknown())
|
||||
}));
|
||||
@@ -23,5 +23,6 @@ export const UserUpsertResultSchema = z.object({
|
||||
officeHours: z.unknown().optional(),
|
||||
officeContact: z.unknown().optional(),
|
||||
procedureTimeslot: z.unknown().optional(),
|
||||
insuranceContacts: z.array(z.unknown())
|
||||
insuranceContacts: z.array(z.unknown()),
|
||||
patientConversations: z.array(z.unknown())
|
||||
});
|
||||
@@ -388,3 +388,16 @@ export { ProcedureTimeslotDeleteManyResultSchema } from './ProcedureTimeslotDele
|
||||
export { ProcedureTimeslotAggregateResultSchema } from './ProcedureTimeslotAggregateResult.schema';
|
||||
export { ProcedureTimeslotGroupByResultSchema } from './ProcedureTimeslotGroupByResult.schema';
|
||||
export { ProcedureTimeslotCountResultSchema } from './ProcedureTimeslotCountResult.schema';
|
||||
export { PatientConversationFindUniqueResultSchema } from './PatientConversationFindUniqueResult.schema';
|
||||
export { PatientConversationFindFirstResultSchema } from './PatientConversationFindFirstResult.schema';
|
||||
export { PatientConversationFindManyResultSchema } from './PatientConversationFindManyResult.schema';
|
||||
export { PatientConversationCreateResultSchema } from './PatientConversationCreateResult.schema';
|
||||
export { PatientConversationCreateManyResultSchema } from './PatientConversationCreateManyResult.schema';
|
||||
export { PatientConversationUpdateResultSchema } from './PatientConversationUpdateResult.schema';
|
||||
export { PatientConversationUpdateManyResultSchema } from './PatientConversationUpdateManyResult.schema';
|
||||
export { PatientConversationUpsertResultSchema } from './PatientConversationUpsertResult.schema';
|
||||
export { PatientConversationDeleteResultSchema } from './PatientConversationDeleteResult.schema';
|
||||
export { PatientConversationDeleteManyResultSchema } from './PatientConversationDeleteManyResult.schema';
|
||||
export { PatientConversationAggregateResultSchema } from './PatientConversationAggregateResult.schema';
|
||||
export { PatientConversationGroupByResultSchema } from './PatientConversationGroupByResult.schema';
|
||||
export { PatientConversationCountResultSchema } from './PatientConversationCountResult.schema';
|
||||
|
||||
Reference in New Issue
Block a user