Ozonetel's webhook AgentName is a transfer-chain display string — same
display can collide (two agents both named "GlobalHealthX" with distinct
agent IDs), and chained like "RamaiahAdmin -> Ganesh Bandi -> GlobalHealthX".
Team Performance was bucketing every unique raw string as a separate
"agent", producing 7 rows for 3 real agents.
Fix — authoritative agent link via CDR AgentID (unique):
- New AgentLookupService (platform module): case-insensitive
ozonetelAgentId → Agent UUID cache, shared across webhook / dispose /
enrichment / backfill paths
- Webhook + outbound-dispose now persist UCID on Call so CDR can join
- Outbound dispose resolves agent relation at create time and overwrites
from CDR AgentID post-hoc (catches dial transfers)
- New CdrEnrichmentService: every 30 min fetches today + yesterday CDR,
patches Calls missing agentId / transferredTo / transferType by UCID
join. Well under Ozonetel's 2 req/min cap.
- Historical backfill maint endpoint: /api/maint/enrich-call-agents
with configurable day window (default 2, max 15). Rate-limited at 35s
between dates.
Call schema additions (synced on Global + Ramaiah): agent relation,
ucid, transferredTo, transferType. agentName remains for legacy/display.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When Ozonetel sends an ACW event, starts a 30-second timer. If no
/api/ozonetel/dispose call arrives within that window (frontend
crashed, tab closed, page refreshed), auto-disposes with "General
Enquiry" + autoRelease:true. Agent exits ACW automatically.
Timer is cancelled when:
- Frontend submits disposition normally (cancelAcwTimer in controller)
- Agent transitions to Ready or Offline
- Agent logs out
Wiring: OzonetelAgentModule now imports SupervisorModule (forwardRef
for circular dep), controller injects SupervisorService to cancel
the timer on successful dispose.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- MissedQueueService: polls Ozonetel abandonCalls every 30s, dedup by phone
- Auto-assigns oldest PENDING_CALLBACK call on agent Ready (dispose + state change)
- GET /api/worklist/missed-queue, PATCH /api/worklist/missed-queue/:id/status
- Worklist query updated with callback fields and FIFO ordering
- PlatformGraphqlService.query() made public for server-to-server ops
- forwardRef circular dependency resolution between WorklistModule and OzonetelAgentModule
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When customer answers outbound call, Kookoo hits /kookoo/ivr which
returns <dial record="true">523590</dial> to bridge the call to
the agent's SIP extension. Agent's browser rings, both connect.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>