mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage-server
synced 2026-05-18 20:08:19 +00:00
feat(calls): consolidate agent identity via Ozonetel CDR
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>
This commit is contained in:
@@ -2,6 +2,7 @@ import { Controller, Post, Get, Body, Query, Logger, HttpException } from '@nest
|
||||
import { OzonetelAgentService } from './ozonetel-agent.service';
|
||||
import { MissedQueueService } from '../worklist/missed-queue.service';
|
||||
import { PlatformGraphqlService } from '../platform/platform-graphql.service';
|
||||
import { AgentLookupService } from '../platform/agent-lookup.service';
|
||||
import { EventBusService } from '../events/event-bus.service';
|
||||
import { Topics } from '../events/event-types';
|
||||
import { TelephonyConfigService } from '../config/telephony-config.service';
|
||||
@@ -28,6 +29,7 @@ export class OzonetelAgentController {
|
||||
private readonly platform: PlatformGraphqlService,
|
||||
private readonly eventBus: EventBusService,
|
||||
private readonly supervisor: SupervisorService,
|
||||
private readonly agentLookup: AgentLookupService,
|
||||
) {}
|
||||
|
||||
private requireAgentId(agentId: string | undefined | null): string {
|
||||
@@ -174,6 +176,14 @@ export class OzonetelAgentController {
|
||||
startedAt,
|
||||
endedAt,
|
||||
};
|
||||
// Persist UCID so the CDR enrichment cron and backfill can
|
||||
// resolve the authoritative agent relation even if the initial
|
||||
// lookup misses.
|
||||
if (body.ucid) callData.ucid = body.ucid;
|
||||
// Resolve the agent relation from the logged-in agentId. For
|
||||
// outbound, the dispatching agent IS the handler — no transfer.
|
||||
const agentUuid = await this.agentLookup.resolveByOzonetelId(agentId);
|
||||
if (agentUuid) callData.agentId = agentUuid;
|
||||
if (body.leadId) callData.leadId = body.leadId;
|
||||
if (body.leadName) callData.leadName = body.leadName;
|
||||
|
||||
@@ -210,6 +220,17 @@ export class OzonetelAgentController {
|
||||
if (handlingSec !== null) updateData.handlingTimeS = handlingSec;
|
||||
if (wrapupSec !== null) updateData.acwDurationS = wrapupSec;
|
||||
if (holdSec !== null) updateData.holdDurationS = holdSec;
|
||||
// Overwrite agent relation with CDR's AgentID (the
|
||||
// actual final handler; may differ from the caller
|
||||
// agentId if Ozonetel transferred the dial).
|
||||
const cdrAgentId = record?.AgentID;
|
||||
if (cdrAgentId) {
|
||||
const cdrAgentUuid = await this.agentLookup.resolveByOzonetelId(cdrAgentId);
|
||||
if (cdrAgentUuid) updateData.agentId = cdrAgentUuid;
|
||||
if (record.AgentName) updateData.agentName = record.AgentName;
|
||||
}
|
||||
if (record?.TransferredTo) updateData.transferredTo = record.TransferredTo;
|
||||
if (record?.TransferType) updateData.transferType = record.TransferType;
|
||||
if (Object.keys(updateData).length > 0) {
|
||||
await this.platform.queryWithAuth<any>(
|
||||
`mutation($id: UUID!, $data: CallUpdateInput!) { updateCall(id: $id, data: $data) { id } }`,
|
||||
|
||||
Reference in New Issue
Block a user