fix: clinicId on all appointment paths + startedAt on call records

- AI chat book_appointment tool: accepts optional clinicId
- Widget booking: passes clinicId from request
- LiveKit agent: passes clinicId from doctor context if available
- Dispose endpoint: sets startedAt/endedAt on outbound call records
  (computed from durationSec). Fixes null timestamps in call history.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-13 14:52:30 +05:30
parent a4ff052fef
commit b6b597fdda
5 changed files with 16 additions and 4 deletions

View File

@@ -380,17 +380,18 @@ export class AiChatController {
}),
book_appointment: tool({
description: 'Book an appointment for a patient. Collect patient name, phone, department, doctor, preferred date/time, and reason before calling this.',
description: 'Book an appointment for a patient. Collect patient name, phone, department, doctor, clinic/branch, preferred date/time, and reason before calling this.',
inputSchema: z.object({
patientName: z.string().describe('Full name of the patient'),
phoneNumber: z.string().describe('Patient phone number'),
department: z.string().describe('Department for the appointment'),
doctorName: z.string().describe('Doctor name'),
clinicId: z.string().optional().describe('Clinic/branch ID — get from lookup_doctor results'),
scheduledAt: z.string().describe('Date and time in ISO format (e.g. 2026-04-01T10:00:00)'),
reason: z.string().describe('Reason for visit'),
}),
execute: async ({ patientName, phoneNumber, department, doctorName, scheduledAt, reason }) => {
this.logger.log(`[TOOL] book_appointment: ${patientName} | ${phoneNumber} | ${department} | ${doctorName} | ${scheduledAt}`);
execute: async ({ patientName, phoneNumber, department, doctorName, clinicId, scheduledAt, reason }) => {
this.logger.log(`[TOOL] book_appointment: ${patientName} | ${phoneNumber} | ${department} | ${doctorName} | clinic=${clinicId ?? 'none'} | ${scheduledAt}`);
try {
const result = await platformService.queryWithAuth<any>(
`mutation($data: AppointmentCreateInput!) { createAppointment(data: $data) { id } }`,
@@ -402,6 +403,7 @@ export class AiChatController {
doctorName,
department,
reasonForVisit: reason,
...(clinicId ? { clinicId } : {}),
},
},
auth,

View File

@@ -128,6 +128,7 @@ const bookAppointment = llm.tool({
doctorName: doctor?.name ?? doctorName ?? 'To be assigned',
department,
reasonForVisit: reason,
...((doctor as any)?.clinicId ? { clinicId: (doctor as any).clinicId } : {}),
},
},
);

View File

@@ -147,14 +147,21 @@ export class OzonetelAgentController {
// only place that creates the call record for outbound dials.
if (body.direction === 'OUTBOUND' && body.callerPhone) {
try {
const durationSec = body.durationSec ?? 0;
const endedAt = new Date().toISOString();
const startedAt = durationSec > 0
? new Date(Date.now() - durationSec * 1000).toISOString()
: endedAt;
const callData: Record<string, any> = {
name: `Outbound — ${body.callerPhone}`,
direction: 'OUTBOUND',
callStatus: 'COMPLETED',
callerNumber: { primaryPhoneNumber: `+91${body.callerPhone.replace(/^\+?91/, '')}` },
agentName: agentId,
durationSec: body.durationSec ?? 0,
durationSec,
disposition: body.disposition,
startedAt,
endedAt,
};
if (body.leadId) callData.leadId = body.leadId;

View File

@@ -204,6 +204,7 @@ export class WidgetService {
department: req.departmentId,
reasonForVisit: req.chiefComplaint ?? '',
patientId,
...(req.clinicId ? { clinicId: req.clinicId } : {}),
} },
this.auth,
);

View File

@@ -15,6 +15,7 @@ export type WidgetInitResponse = {
export type WidgetBookRequest = {
departmentId: string;
doctorId: string;
clinicId?: string;
scheduledAt: string;
patientName: string;
patientPhone: string;