From 9472f83cd812039ca28c178d1fb54acd765bdfe9 Mon Sep 17 00:00:00 2001 From: saridsa2 Date: Wed, 15 Apr 2026 07:14:16 +0530 Subject: [PATCH] feat(supervisor): team-performance reads AgentSession first, Ozonetel as fallback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 3 — wire the dashboard to the new metrics path without touching the frontend. getTeamPerformance now: 1. Fetches AgentSession rows for the given IST date (keyed by agent UUID) 2. For each agent: uses AgentSession data rendered as HH:MM:SS if a row exists, otherwise falls back to Ozonetel summaryReport 3. Returns timeBreakdownSource so the frontend can optionally show which source was used Frontend continues to parse the existing HH:MM:SS shape via parseTime() — no UI change needed. Historical dates without AgentSession rows still render via Ozonetel. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/supervisor/supervisor.service.ts | 68 ++++++++++++++++++++++++++-- 1 file changed, 65 insertions(+), 3 deletions(-) diff --git a/src/supervisor/supervisor.service.ts b/src/supervisor/supervisor.service.ts index 8ee956b..1f85527 100644 --- a/src/supervisor/supervisor.service.ts +++ b/src/supervisor/supervisor.service.ts @@ -274,6 +274,11 @@ export class SupervisorService implements OnModuleInit { ); const agents = agentData?.agents?.edges?.map((e: any) => e.node) ?? []; + // Fetch AgentSession rows for this date — the authoritative source + // for time breakdowns now that Phase 2 ingest is live. Keyed by + // agentId (UUID on platform) so we can match back by agent.id. + const sessionByAgentId = await this.fetchAgentSessionsByDate(date); + // Fetch CDR for the entire account for this date (one call, not per-agent) let allCdr: any[] = []; try { @@ -282,12 +287,23 @@ export class SupervisorService implements OnModuleInit { this.logger.warn(`Failed to fetch CDR for ${date}: ${err}`); } - // Fetch Ozonetel time summary per agent + compute call metrics from CDR + // Merge AgentSession → timeBreakdown (Ozonetel shape for UI compat); + // fall back to Ozonetel summary when no session row exists. const summaries = await Promise.all( agents.map(async (agent: any) => { if (!agent.ozonetelAgentId) return { ...agent, timeBreakdown: null, calls: null }; try { - const summary = await this.ozonetel.getAgentSummary(agent.ozonetelAgentId, date); + let timeBreakdown: any = null; + let source: 'AGENT_SESSION' | 'OZONETEL_SUMMARY' | 'NONE' = 'NONE'; + + const session = sessionByAgentId.get(agent.id); + if (session) { + timeBreakdown = this.sessionToTimeBreakdown(session); + source = 'AGENT_SESSION'; + } else { + timeBreakdown = await this.ozonetel.getAgentSummary(agent.ozonetelAgentId, date); + if (timeBreakdown) source = 'OZONETEL_SUMMARY'; + } // Filter CDR to this agent const agentCdr = allCdr.filter( @@ -301,7 +317,8 @@ export class SupervisorService implements OnModuleInit { return { ...agent, - timeBreakdown: summary, + timeBreakdown, + timeBreakdownSource: source, calls: { total: totalCalls, inbound, outbound, answered, missed }, }; } catch (err) { @@ -323,6 +340,51 @@ export class SupervisorService implements OnModuleInit { return { date, agents: summaries, teamTotals }; } + // Pull AgentSession rows for the given IST date, keyed by agent UUID so + // getTeamPerformance can look them up per-agent. + private async fetchAgentSessionsByDate(date: string): Promise> { + const map = new Map(); + try { + const data = await this.platform.query( + `{ agentSessions(first: 100, filter: { date: { eq: "${date}" } }) { + edges { node { + agentId loginDurationS busyTimeS idleTimeS pauseTimeS + wrapupTimeS dialTimeS avgHandlingTimeS source lastSyncedAt + } } + } }`, + ); + const edges = data?.agentSessions?.edges ?? []; + for (const e of edges) { + if (e.node?.agentId) map.set(e.node.agentId, e.node); + } + } catch (err) { + this.logger.warn(`[PERF] Failed to fetch AgentSession rows for ${date}: ${err}`); + } + return map; + } + + // Render AgentSession seconds in the HH:MM:SS shape the frontend expects + // (matches Ozonetel's summary so team-performance.tsx can parseTime() it + // without changing the page code). + private sessionToTimeBreakdown(session: any): any { + const hms = (sec: number | null | undefined): string => { + const s = Math.max(0, Math.round(sec ?? 0)); + const h = Math.floor(s / 3600); + const m = Math.floor((s % 3600) / 60); + const r = s % 60; + return `${h}:${String(m).padStart(2, '0')}:${String(r).padStart(2, '0')}`; + }; + return { + totalLoginTime: hms(session.loginDurationS), + totalBusyTime: hms(session.busyTimeS), + totalIdleTime: hms(session.idleTimeS), + totalPauseTime: hms(session.pauseTimeS), + totalWrapupTime: hms(session.wrapupTimeS), + totalDialTime: hms(session.dialTimeS), + avgHandlingTime: hms(session.avgHandlingTimeS), + }; + } + // --- Barge session management --- getBargeSession(agentId: string) {