mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage-server
synced 2026-05-18 20:08:19 +00:00
fix(my-performance): LOGIN TIME uses AgentSession rollup, not Ozonetel summary
Ozonetel's summaryReport only tallies CLOSED login→logout pairs — an
agent who's still logged in reports 00:00:00, so the KPI card on My
Performance always showed 0s for the current session.
Our AgentSession rollup already caps open sessions at "now" when it
runs. Endpoint now:
1. Triggers an on-demand rollupSessions(targetDate) to refresh the
AgentSession row (no 15-min wait after login)
2. Reads AgentSession and renders in the HH:MM:SS shape the frontend
expects
3. Falls back to Ozonetel's summaryReport when AgentSession is
empty (brand-new agent, workspace missing AgentEvent entity)
Works transparently — same timeUtilization shape as before, frontend
unchanged.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,7 @@ import { EventBusService } from '../events/event-bus.service';
|
||||
import { Topics } from '../events/event-types';
|
||||
import { TelephonyConfigService } from '../config/telephony-config.service';
|
||||
import { SupervisorService } from '../supervisor/supervisor.service';
|
||||
import { AgentHistoryService } from '../supervisor/agent-history.service';
|
||||
|
||||
// Convert Ozonetel "HH:MM:SS" (or null/empty) to integer seconds.
|
||||
// Returns null when input is missing or all-zero.
|
||||
@@ -30,6 +31,7 @@ export class OzonetelAgentController {
|
||||
private readonly eventBus: EventBusService,
|
||||
private readonly supervisor: SupervisorService,
|
||||
private readonly agentLookup: AgentLookupService,
|
||||
private readonly agentHistory: AgentHistoryService,
|
||||
) {}
|
||||
|
||||
private requireAgentId(agentId: string | undefined | null): string {
|
||||
@@ -405,12 +407,28 @@ export class OzonetelAgentController {
|
||||
const targetDate = date ?? new Date().toISOString().split('T')[0];
|
||||
this.logger.log(`Performance: date=${targetDate} agent=${agent}`);
|
||||
|
||||
const [cdr, summary, aht] = await Promise.all([
|
||||
// Trigger an on-demand rollup for the requested date so the
|
||||
// AgentSession row reflects the current open session (caps at now)
|
||||
// instead of waiting up to 15 min for the background tick. Fire-and-
|
||||
// forget with a short await so we don't block the whole response on
|
||||
// cache-refresh tail but still hand the read a fresh row when Redpanda
|
||||
// is quiet. Safe to error — AgentSession just stays stale.
|
||||
await this.agentHistory.rollupSessions(targetDate).catch(() => {});
|
||||
|
||||
const [cdr, summary, aht, agentSessionBreakdown] = await Promise.all([
|
||||
this.ozonetelAgent.fetchCDR({ date: targetDate }),
|
||||
this.ozonetelAgent.getAgentSummary(agent, targetDate),
|
||||
this.ozonetelAgent.getAHT(agent),
|
||||
this.fetchAgentSessionTimeBreakdown(agent, targetDate),
|
||||
]);
|
||||
|
||||
// Prefer our AgentSession rollup when present — it correctly counts
|
||||
// the current OPEN session (caps at now), while Ozonetel's summaryReport
|
||||
// only tallies CLOSED login→logout pairs. Fall back to Ozonetel if
|
||||
// our rollup hasn't captured this agent yet (e.g., brand-new agent,
|
||||
// workspace without AgentEvent entity synced).
|
||||
const timeUtilization = agentSessionBreakdown ?? summary;
|
||||
|
||||
// Filter CDR to this agent only — fetchCDR returns all agents' calls
|
||||
// Use case-insensitive matching — Ozonetel field casing varies
|
||||
const agentLower = agent.toLowerCase();
|
||||
@@ -460,7 +478,7 @@ export class OzonetelAgentController {
|
||||
avgHandlingTime: aht,
|
||||
conversionRate: totalCalls > 0 ? Math.round((appointmentsBooked / totalCalls) * 100) : 0,
|
||||
appointmentsBooked,
|
||||
timeUtilization: summary,
|
||||
timeUtilization,
|
||||
dispositions,
|
||||
};
|
||||
}
|
||||
@@ -480,4 +498,52 @@ export class OzonetelAgentController {
|
||||
};
|
||||
return map[disposition] ?? 'General Enquiry';
|
||||
}
|
||||
|
||||
// Convert our AgentSession rollup (seconds per category) into the HH:MM:SS
|
||||
// shape the frontend expects — so My Performance gets LOGIN TIME with the
|
||||
// current open session included, not just closed sessions from Ozonetel.
|
||||
private async fetchAgentSessionTimeBreakdown(ozonetelAgentId: string, date: string): Promise<{
|
||||
totalLoginDuration: string;
|
||||
totalBusyTime: string;
|
||||
totalIdleTime: string;
|
||||
totalPauseTime: string;
|
||||
totalWrapupTime: string;
|
||||
totalDialTime: string;
|
||||
} | null> {
|
||||
try {
|
||||
const agentUuid = await this.agentLookup.resolveByOzonetelId(ozonetelAgentId);
|
||||
if (!agentUuid) return null;
|
||||
const data = await this.platform.query<any>(
|
||||
`{ agentSessions(first: 1, filter: {
|
||||
agentId: { eq: "${agentUuid}" },
|
||||
date: { eq: "${date}" }
|
||||
}) { edges { node {
|
||||
loginDurationS busyTimeS idleTimeS pauseTimeS wrapupTimeS dialTimeS
|
||||
} } } }`,
|
||||
);
|
||||
const node = data?.agentSessions?.edges?.[0]?.node;
|
||||
if (!node) return null;
|
||||
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.toString().padStart(2, '0')}:${m.toString().padStart(2, '0')}:${r.toString().padStart(2, '0')}`;
|
||||
};
|
||||
// If the entire rollup is zero, treat as "no data yet" — fall back
|
||||
// to Ozonetel's summaryReport so the KPI isn't all zeroes.
|
||||
const total = (node.loginDurationS ?? 0) + (node.busyTimeS ?? 0) + (node.idleTimeS ?? 0) + (node.pauseTimeS ?? 0) + (node.wrapupTimeS ?? 0);
|
||||
if (total === 0) return null;
|
||||
return {
|
||||
totalLoginDuration: hms(node.loginDurationS),
|
||||
totalBusyTime: hms(node.busyTimeS),
|
||||
totalIdleTime: hms(node.idleTimeS),
|
||||
totalPauseTime: hms(node.pauseTimeS),
|
||||
totalWrapupTime: hms(node.wrapupTimeS),
|
||||
totalDialTime: hms(node.dialTimeS),
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user