# My Performance — Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Build a personal performance dashboard for the CC agent showing call stats, conversion metrics, time utilization, and trends — aggregated from both Ozonetel CDR and platform data. **Architecture:** A new sidecar endpoint `/api/performance` aggregates data from three sources: Ozonetel CDR (call stats), Ozonetel Agent State Summary (time utilization), and platform GraphQL (appointment conversions). The frontend renders KPI cards, an ECharts call volume chart, and a disposition breakdown donut. **Tech Stack:** NestJS sidecar, Ozonetel APIs (CDR + Agent Summary + AHT), Platform GraphQL, React 19 + ECharts --- ## Data Sources | Metric | Source | API | |--------|--------|-----| | Total calls today | Ozonetel CDR | `GET /ca_reports/fetchCDRDetails` | | Inbound / Outbound split | Ozonetel CDR | Same (field: `Type`) | | Answered / Missed split | Ozonetel CDR | Same (field: `Status`) | | Avg call duration | Ozonetel CDR | Same (field: `TalkTime`) | | Avg handling time | Ozonetel AHT | `GET /ca_apis/aht` | | Login duration | Ozonetel Summary | `POST /ca_reports/summaryReport` | | Idle / Busy / Pause time | Ozonetel Summary | Same | | Appointments booked | Platform | Query calls with disposition `APPOINTMENT_BOOKED` | | Conversion rate | Platform | Appointments booked / total calls | | Call volume trend (7 days) | Ozonetel CDR | Fetch per day for last 7 days | | Disposition breakdown | Ozonetel CDR | Group by `Disposition` field | ## File Map ### Sidecar | File | Action | |------|--------| | `src/ozonetel/ozonetel-agent.service.ts` | Modify: add `getAgentSummary()`, `getAHT()` | | `src/ozonetel/ozonetel-agent.controller.ts` | Modify: add `GET /api/ozonetel/performance` | ### Frontend | File | Action | |------|--------| | `src/pages/my-performance.tsx` | Create: KPI cards + charts page | | `src/components/layout/sidebar.tsx` | Modify: add My Performance nav item for cc-agent | | `src/main.tsx` | Modify: add route | --- ## Task 1: Add Agent Summary and AHT service methods **Files:** - Modify: `helix-engage-server/src/ozonetel/ozonetel-agent.service.ts` - [ ] **Step 1: Add `getAgentSummary()` method** ```typescript 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; } } ``` - [ ] **Step 2: Add `getAHT()` method** ```typescript 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'; } } ``` - [ ] **Step 3: Type check and commit** ``` feat: add agent summary and AHT service methods ``` --- ## Task 2: Add performance aggregation endpoint **Files:** - Modify: `helix-engage-server/src/ozonetel/ozonetel-agent.controller.ts` - [ ] **Step 1: Add `GET /api/ozonetel/performance` endpoint** This endpoint fetches today's CDR, agent summary, and AHT in parallel, then aggregates: ```typescript @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}`); // Fetch all data in parallel const [cdr, summary, aht] = await Promise.all([ this.ozonetelAgent.fetchCDR({ date: targetDate }), this.ozonetelAgent.getAgentSummary(this.defaultAgentId, targetDate), this.ozonetelAgent.getAHT(this.defaultAgentId), ]); // Aggregate CDR stats const totalCalls = cdr.length; const inbound = cdr.filter(c => c.Type === 'InBound').length; const outbound = cdr.filter(c => c.Type === 'Manual' || c.Type === 'Progressive').length; const answered = cdr.filter(c => c.Status === 'Answered').length; const missed = cdr.filter(c => c.Status === 'Unanswered' || c.Status === 'NotAnswered').length; // Average talk time in seconds const talkTimes = cdr .filter(c => c.TalkTime && c.TalkTime !== '00:00:00') .map(c => { 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, b) => a + b, 0) / talkTimes.length) : 0; // Disposition breakdown const dispositions: Record = {}; for (const c of cdr) { const d = c.Disposition || 'No Disposition'; dispositions[d] = (dispositions[d] ?? 0) + 1; } // Appointments booked (disposition contains "Appointment" or "General Enquiry" with appointment) const appointmentsBooked = cdr.filter(c => c.Disposition?.includes('Appointment') || c.Disposition?.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, }; } ``` - [ ] **Step 2: Type check and commit** ``` feat: add performance aggregation endpoint ``` --- ## Task 3: Build My Performance page **Files:** - Create: `helix-engage/src/pages/my-performance.tsx` - [ ] **Step 1: Create the page** The page has: 1. **Header row**: "My Performance" + date picker (today/yesterday/this week) 2. **KPI cards row**: Total Calls, Answered, Appointments Booked, Avg Handle Time, Conversion Rate, Login Time 3. **Charts row**: Call Volume bar chart (inbound vs outbound) + Disposition donut chart 4. **Time utilization bar**: horizontal stacked bar showing Busy/Idle/Pause/Wrapup proportions The page fetches data from `GET /api/ozonetel/performance?date=YYYY-MM-DD` on mount and when the date changes. KPI cards follow the same pattern as the existing reports page — icon, value, label, with trend badge. Charts use ECharts (already installed as `echarts-for-react`). For the 7-day call volume chart, the page fetches performance for each of the last 7 days (7 API calls). To avoid hitting the CDR rate limit (2 req/min), it fetches sequentially with a small delay, or better — the sidecar caches/batches this. **Simpler approach for v1**: Only show today's data. The 7-day trend chart can be added later when we have the real-time event subscription storing historical data. - [ ] **Step 2: Type check and commit** ``` feat: add My Performance page with KPI cards and charts ``` --- ## Task 4: Wire into navigation and routing **Files:** - Modify: `helix-engage/src/components/layout/sidebar.tsx` - Modify: `helix-engage/src/main.tsx` - [ ] **Step 1: Add nav item for cc-agent** In sidebar.tsx, add to the cc-agent section: ```typescript { label: 'Call Center', items: [ { label: 'Call Desk', href: '/', icon: IconPhone }, { label: 'Call History', href: '/call-history', icon: IconClockRewind }, { label: 'My Performance', href: '/my-performance', icon: IconChartMixed }, ]}, ``` - [ ] **Step 2: Add route in main.tsx** ```typescript import { MyPerformancePage } from '@/pages/my-performance'; // In routes: } /> ``` - [ ] **Step 3: Type check and commit** ``` feat: add My Performance to CC agent navigation ``` --- ## Task 5: Deploy and verify - [ ] **Step 1: Build and deploy sidecar** - [ ] **Step 2: Build and deploy frontend** - [ ] **Step 3: Test performance endpoint** ```bash curl -s "https://engage-api.srv1477139.hstgr.cloud/api/ozonetel/performance?date=2026-03-20" | python3 -m json.tool ``` - [ ] **Step 4: Test the page** 1. Login as CC agent 2. Navigate to My Performance 3. Verify KPI cards show real data 4. Verify charts render --- ## Notes - **CDR rate limit is 2 req/min** — the performance endpoint makes 1 CDR call per request. For 7-day trend, we'd need 7 calls which would take 3.5 minutes. Defer 7-day trend to later. - **Agent Summary rate limit is 5 req/min** — fine for single-day queries. - **AHT rate limit is 50 req/min** — no concern. - **Time format from Ozonetel** is `HH:MM:SS` — need to parse to seconds for display. - **ECharts colors** should use the new brand blue scale. The existing reports page has hardcoded colors — update to match.