mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage-server
synced 2026-05-18 20:08:19 +00:00
feat(supervisor): team-performance reads AgentSession first, Ozonetel as fallback
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) <noreply@anthropic.com>
This commit is contained in:
@@ -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<Map<string, any>> {
|
||||
const map = new Map<string, any>();
|
||||
try {
|
||||
const data = await this.platform.query<any>(
|
||||
`{ 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) {
|
||||
|
||||
Reference in New Issue
Block a user