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) {