From 8ba326589c6bf8f42bf465f831c9130e7fe585a6 Mon Sep 17 00:00:00 2001 From: saridsa2 Date: Sat, 21 Mar 2026 13:41:05 +0530 Subject: [PATCH] feat: agent summary, AHT, and performance aggregation endpoint Co-Authored-By: Claude Opus 4.6 (1M context) --- src/ozonetel/ozonetel-agent.controller.ts | 49 +++++++++++++++ src/ozonetel/ozonetel-agent.service.ts | 75 +++++++++++++++++++++++ 2 files changed, 124 insertions(+) diff --git a/src/ozonetel/ozonetel-agent.controller.ts b/src/ozonetel/ozonetel-agent.controller.ts index 8f95b6c..5849852 100644 --- a/src/ozonetel/ozonetel-agent.controller.ts +++ b/src/ozonetel/ozonetel-agent.controller.ts @@ -202,6 +202,55 @@ export class OzonetelAgentController { return result; } + @Get('performance') + async performance(@Query('date') date?: string) { + const targetDate = date ?? new Date().toISOString().split('T')[0]; + this.logger.log(`Performance: date=${targetDate} agent=${this.defaultAgentId}`); + + const [cdr, summary, aht] = await Promise.all([ + this.ozonetelAgent.fetchCDR({ date: targetDate }), + this.ozonetelAgent.getAgentSummary(this.defaultAgentId, targetDate), + this.ozonetelAgent.getAHT(this.defaultAgentId), + ]); + + const totalCalls = cdr.length; + const inbound = cdr.filter((c: any) => c.Type === 'InBound').length; + const outbound = cdr.filter((c: any) => c.Type === 'Manual' || c.Type === 'Progressive').length; + const answered = cdr.filter((c: any) => c.Status === 'Answered').length; + const missed = cdr.filter((c: any) => c.Status === 'Unanswered' || c.Status === 'NotAnswered').length; + + const talkTimes = cdr + .filter((c: any) => c.TalkTime && c.TalkTime !== '00:00:00') + .map((c: any) => { + const parts = c.TalkTime.split(':').map(Number); + return parts[0] * 3600 + parts[1] * 60 + parts[2]; + }); + const avgTalkTimeSec = talkTimes.length > 0 + ? Math.round(talkTimes.reduce((a: number, b: number) => a + b, 0) / talkTimes.length) + : 0; + + const dispositions: Record = {}; + for (const c of cdr) { + const d = (c as any).Disposition || 'No Disposition'; + dispositions[d] = (dispositions[d] ?? 0) + 1; + } + + const appointmentsBooked = cdr.filter((c: any) => + c.Disposition?.toLowerCase().includes('appointment'), + ).length; + + return { + date: targetDate, + calls: { total: totalCalls, inbound, outbound, answered, missed }, + avgTalkTimeSec, + avgHandlingTime: aht, + conversionRate: totalCalls > 0 ? Math.round((appointmentsBooked / totalCalls) * 100) : 0, + appointmentsBooked, + timeUtilization: summary, + dispositions, + }; + } + private mapToOzonetelDisposition(disposition: string): string { // Campaign only has 'General Enquiry' configured currently const map: Record = { diff --git a/src/ozonetel/ozonetel-agent.service.ts b/src/ozonetel/ozonetel-agent.service.ts index 395692a..8267b1d 100644 --- a/src/ozonetel/ozonetel-agent.service.ts +++ b/src/ozonetel/ozonetel-agent.service.ts @@ -352,6 +352,81 @@ export class OzonetelAgentService { } } + async getAgentSummary(agentId: string, date: string): Promise<{ + totalLoginDuration: string; + totalBusyTime: string; + totalIdleTime: string; + totalPauseTime: string; + totalWrapupTime: string; + totalDialTime: string; + } | null> { + const url = `https://${this.apiDomain}/ca_reports/summaryReport`; + + try { + const token = await this.getToken(); + const response = await axios({ + method: 'GET', + url, + headers: { + Authorization: `Bearer ${token}`, + 'Content-Type': 'application/json', + }, + data: JSON.stringify({ + userName: this.accountId, + agentId, + fromDate: `${date} 00:00:00`, + toDate: `${date} 23:59:59`, + }), + }); + + const data = response.data; + if (data.status === 'success' && data.message) { + const record = Array.isArray(data.message) ? data.message[0] : data.message; + return { + totalLoginDuration: record.TotalLoginDuration ?? '00:00:00', + totalBusyTime: record.TotalBusyTime ?? '00:00:00', + totalIdleTime: record.TotalIdleTime ?? '00:00:00', + totalPauseTime: record.TotalPauseTime ?? '00:00:00', + totalWrapupTime: record.TotalWrapupTime ?? '00:00:00', + totalDialTime: record.TotalDialTime ?? '00:00:00', + }; + } + return null; + } catch (error: any) { + this.logger.error(`Agent summary failed: ${error.message}`); + return null; + } + } + + async getAHT(agentId: string): Promise { + const url = `https://${this.apiDomain}/ca_apis/aht`; + + try { + const token = await this.getToken(); + const response = await axios({ + method: 'GET', + url, + headers: { + Authorization: `Bearer ${token}`, + 'Content-Type': 'application/json', + }, + data: JSON.stringify({ + userName: this.accountId, + agentId, + }), + }); + + const data = response.data; + if (data.status === 'success') { + return data.AHT ?? '00:00:00'; + } + return '00:00:00'; + } catch (error: any) { + this.logger.error(`AHT failed: ${error.message}`); + return '00:00:00'; + } + } + async logoutAgent(params: { agentId: string; password: string;