fix: disposition for answered inbound calls + SLA timing wiring + backfill

Three related fixes:

1. Disposition for answered inbound calls
   Previously the dispose endpoint sent the agent's choice to Ozonetel
   but never wrote it back to the platform Call record. The webhook's
   pre-disposition value ("General Enquiry" → INFO_PROVIDED) persisted.
   Now: dispose endpoint finds the Call by UCID and updates disposition
   to the agent's actual selection.

2. SLA timing wiring (assignedAt / answeredAt / responseTimeS)
   patchCallTiming() existed but was never called. Now wired into
   handleCallEvent:
   - "Calling" event → writes assignedAt (ring start)
   - "Answered" event → writes answeredAt + computes responseTimeS
     (answeredAt - startedAt = caller wait time)
   Uses patchCallTimingByUcid helper that looks up Call by UCID.

3. Backfill maint endpoint: POST /api/maint/backfill-call-disposition-timing
   Walks calls for a given date, joins to CDR by UCID (both legs),
   patches disposition (from CDR's mapped value, always overwrites),
   timing fields (answeredAt, assignedAt, responseTimeS from CDR),
   and CDR-specific durations (handlingTimeS, acwDurationS, holdDurationS).
   Idempotent — safe to run multiple times.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-16 18:02:49 +05:30
parent 2d8308bed8
commit a6f4c51ca9
3 changed files with 194 additions and 0 deletions

View File

@@ -278,6 +278,34 @@ export class OzonetelAgentController {
}
}
// Update disposition on answered inbound calls. The webhook creates
// the Call record with the Ozonetel default disposition ("General
// Enquiry" → INFO_PROVIDED) before the agent disposes. Now that the
// agent has submitted their actual disposition, write it back to the
// platform Call record by matching on UCID.
//
// Skipped for outbound (already created with correct disposition
// above) and for missed-call callbacks (handled in the block above).
if (!body.missedCallId && body.direction !== 'OUTBOUND' && body.ucid) {
try {
const callData = await this.platform.query<any>(
`{ calls(first: 1, filter: { ucid: { eq: "${body.ucid}" } }) { edges { node { id } } } }`,
);
const callId = callData?.calls?.edges?.[0]?.node?.id;
if (callId) {
await this.platform.query<any>(
`mutation($id: UUID!, $data: CallUpdateInput!) { updateCall(id: $id, data: $data) { id } }`,
{ id: callId, data: { disposition: body.disposition } },
);
this.logger.log(`[DISPOSE] Updated inbound call ${callId} disposition → ${body.disposition}`);
} else {
this.logger.warn(`[DISPOSE] No Call found for ucid=${body.ucid} — disposition not persisted`);
}
} catch (err: any) {
this.logger.warn(`[DISPOSE] Failed to update inbound call disposition: ${err.message}`);
}
}
// Auto-assign next missed call to this agent
try {
await this.missedQueue.assignNext(agentId);