From 8de7d7d802c314ea8cdd0306b6d9904ccf0f864f Mon Sep 17 00:00:00 2001 From: saridsa2 Date: Wed, 15 Apr 2026 08:07:05 +0530 Subject: [PATCH] fix(team-dashboard): agent-table buckets by authoritative agent.id MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Agent Performance table on the team dashboard was bucketing by raw call.agentName — the field that holds Ozonetel's transfer-chain string ("RamaiahAdmin -> GlobalHealthX") and collides for distinct AgentIDs that share a Full Name. Result: 7 rows for 3 real agents. Now buckets by call.agent.id when the CDR enrichment has populated it, falls back to legacy agentName grouping otherwise. Calls without any agent info are dropped from the agent rollup (instead of being collapsed under "Unknown"). Pulls agent { id name ozonetelAgentId } + transferredTo + transferType on CALLS_QUERY. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/components/dashboard/agent-table.tsx | 29 +++++++++++++++++------- src/lib/queries.ts | 2 ++ src/lib/transforms.ts | 4 ++++ src/types/entities.ts | 6 +++++ 4 files changed, 33 insertions(+), 8 deletions(-) diff --git a/src/components/dashboard/agent-table.tsx b/src/components/dashboard/agent-table.tsx index 506c363..e82fe75 100644 --- a/src/components/dashboard/agent-table.tsx +++ b/src/components/dashboard/agent-table.tsx @@ -26,14 +26,27 @@ interface AgentTableProps { export const AgentTable = ({ calls }: AgentTableProps) => { const agents = useMemo(() => { - const agentMap = new Map(); + // Bucket by authoritative agent.id when present (from CDR enrichment); + // fall back to raw agentName for legacy rows that haven't been + // enriched yet. Skips rows with no agent info at all. + const agentMap = new Map(); for (const call of calls) { - const agent = call.agentName ?? 'Unknown'; - if (!agentMap.has(agent)) agentMap.set(agent, []); - agentMap.get(agent)!.push(call); + let key: string; + let displayName: string; + if (call.agent?.id) { + key = call.agent.id; + displayName = call.agent.name ?? call.agent.ozonetelAgentId ?? 'Unknown'; + } else if (call.agentName) { + key = `legacy:${call.agentName}`; + displayName = call.agentName; + } else { + continue; + } + if (!agentMap.has(key)) agentMap.set(key, { displayName, calls: [] }); + agentMap.get(key)!.calls.push(call); } - return Array.from(agentMap.entries()).map(([name, agentCalls]) => { + return Array.from(agentMap.entries()).map(([key, { displayName, calls: agentCalls }]) => { const inbound = agentCalls.filter((c) => c.callDirection === 'INBOUND').length; const outbound = agentCalls.filter((c) => c.callDirection === 'OUTBOUND').length; const missed = agentCalls.filter((c) => c.callStatus === 'MISSED').length; @@ -43,11 +56,11 @@ export const AgentTable = ({ calls }: AgentTableProps) => { const avgHandle = completedCalls.length > 0 ? Math.round(totalDuration / completedCalls.length) : 0; const booked = agentCalls.filter((c) => c.disposition === 'APPOINTMENT_BOOKED').length; const conversion = total > 0 ? (booked / total) * 100 : 0; - const nameParts = name.split(' '); + const nameParts = displayName.split(' '); return { - id: name, - name, + id: key, + name: displayName, initials: getInitials(nameParts[0] ?? '', nameParts[1] ?? ''), inbound, outbound, missed, total, avgHandle, conversion, }; diff --git a/src/lib/queries.ts b/src/lib/queries.ts index 86ed50e..90ff5c5 100644 --- a/src/lib/queries.ts +++ b/src/lib/queries.ts @@ -54,6 +54,8 @@ export const CALLS_QUERY = `{ calls(first: 100, orderBy: [{ startedAt: DescNulls startedAt endedAt durationSec recording { primaryLinkUrl } disposition sla patientId appointmentId leadId + agentId agent { id name ozonetelAgentId } + transferredTo transferType } } } }`; export const DOCTORS_QUERY = `{ doctors(first: 20) { edges { node { diff --git a/src/lib/transforms.ts b/src/lib/transforms.ts index 6255c87..41e0035 100644 --- a/src/lib/transforms.ts +++ b/src/lib/transforms.ts @@ -150,6 +150,10 @@ export function transformCalls(data: any): Call[] { patientId: n.patientId, appointmentId: n.appointmentId, leadId: n.leadId, + agentId: n.agentId ?? null, + agent: n.agent ?? null, + transferredTo: n.transferredTo ?? null, + transferType: n.transferType ?? null, })); } diff --git a/src/types/entities.ts b/src/types/entities.ts index fd778ca..3261ee3 100644 --- a/src/types/entities.ts +++ b/src/types/entities.ts @@ -276,6 +276,12 @@ export type Call = { appointmentId: string | null; leadId: string | null; sla?: number | null; + // Authoritative agent link from CDR enrichment. agentName remains the + // raw Ozonetel string (may be a transfer chain) for display fallback. + agentId?: string | null; + agent?: { id: string; name: string | null; ozonetelAgentId: string | null } | null; + transferredTo?: string | null; + transferType?: string | null; // Denormalized for display leadName?: string; leadPhone?: string;