mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage
synced 2026-04-11 18:28:15 +00:00
- My Performance page with KPI cards, ECharts, DatePicker, time utilization - Sidecar: agent summary + AHT + performance aggregation endpoint - Logout confirmation modal - Removed Patients from CC agent nav Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
305 lines
10 KiB
Markdown
305 lines
10 KiB
Markdown
# 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<string> {
|
|
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<string, number> = {};
|
|
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:
|
|
<Route path="/my-performance" element={<MyPerformancePage />} />
|
|
```
|
|
|
|
- [ ] **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.
|