Files
helix-engage/docs/superpowers/plans/2026-03-21-my-performance.md
saridsa2 721c2879ec feat: My Performance page + logout modal + sidebar cleanup
- 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>
2026-03-21 13:40:37 +05:30

10 KiB

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

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
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:

@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:

{ 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
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
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.