Files
helix-engage/docs/superpowers/plans/2026-03-24-supervisor-module.md
2026-03-24 13:22:31 +05:30

17 KiB

Supervisor Module 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 the supervisor module with team performance dashboard (PP-5), live call monitor (PP-6), master data pages, and admin sidebar restructure.

Architecture: Frontend pages query platform GraphQL directly for entity data (calls, appointments, leads, agents). Sidecar provides Ozonetel-specific data (agent time breakdown, active calls via event subscription). No hardcoded/mock data anywhere.

Tech Stack: React + Tailwind + ECharts (frontend), NestJS sidecar (Ozonetel integration), Fortytwo platform GraphQL

Spec: docs/superpowers/specs/2026-03-24-supervisor-module.md


File Map

Frontend (helix-engage/src/)

File Action Responsibility
pages/team-performance.tsx Create PP-5 full dashboard
pages/live-monitor.tsx Create PP-6 active call table
pages/call-recordings.tsx Create Calls with recordings master
pages/missed-calls.tsx Create Missed calls master (supervisor view)
components/layout/sidebar.tsx Modify Admin nav restructure
main.tsx Modify Add new routes

Sidecar (helix-engage-server/src/)

File Action Responsibility
supervisor/supervisor.service.ts Create Team perf aggregation + active call tracking
supervisor/supervisor.controller.ts Create REST endpoints
supervisor/supervisor.module.ts Create Module registration
app.module.ts Modify Import SupervisorModule

Task 1: Admin Sidebar Nav + Routes

Files:

  • Modify: helix-engage/src/components/layout/sidebar.tsx

  • Modify: helix-engage/src/main.tsx

  • Step 1: Add new icon imports to sidebar

In sidebar.tsx, add to the FontAwesome imports:

import {
    // existing imports...
    faRadio,
    faFileAudio,
    faPhoneMissed,
    faChartLine,
} from '@fortawesome/pro-duotone-svg-icons';

Add icon wrappers:

const IconRadio = faIcon(faRadio);
const IconFileAudio = faIcon(faFileAudio);
const IconPhoneMissed = faIcon(faPhoneMissed);
const IconChartLine = faIcon(faChartLine);
  • Step 2: Restructure admin nav

Replace the admin nav section (currently has Overview + Management + Admin groups) with:

if (role === 'admin') {
    return [
        { label: 'Supervisor', items: [
            { label: 'Dashboard', href: '/', icon: IconGrid2 },
            { label: 'Team Performance', href: '/team-performance', icon: IconChartLine },
            { label: 'Live Call Monitor', href: '/live-monitor', icon: IconRadio },
        ]},
        { label: 'Data & Reports', items: [
            { label: 'Lead Master', href: '/leads', icon: IconUsers },
            { label: 'Patient Master', href: '/patients', icon: IconHospitalUser },
            { label: 'Appointment Master', href: '/appointments', icon: IconCalendarCheck },
            { label: 'Call Log Master', href: '/call-history', icon: IconClockRewind },
            { label: 'Call Recordings', href: '/call-recordings', icon: IconFileAudio },
            { label: 'Missed Calls', href: '/missed-calls', icon: IconPhoneMissed },
        ]},
        { label: 'Admin', items: [
            { label: 'Settings', href: '/settings', icon: IconGear },
        ]},
    ];
}
  • Step 3: Add routes in main.tsx

Import new page components (they'll be created in later tasks — use placeholder components for now):

import { TeamPerformancePage } from "@/pages/team-performance";
import { LiveMonitorPage } from "@/pages/live-monitor";
import { CallRecordingsPage } from "@/pages/call-recordings";
import { MissedCallsPage } from "@/pages/missed-calls";

Add routes:

<Route path="/team-performance" element={<TeamPerformancePage />} />
<Route path="/live-monitor" element={<LiveMonitorPage />} />
<Route path="/call-recordings" element={<CallRecordingsPage />} />
<Route path="/missed-calls" element={<MissedCallsPage />} />
  • Step 4: Create placeholder pages

Create minimal placeholder files for each new page so the build doesn't fail:

// src/pages/team-performance.tsx
export const TeamPerformancePage = () => <div>Team Performance  coming soon</div>;

// src/pages/live-monitor.tsx
export const LiveMonitorPage = () => <div>Live Call Monitor  coming soon</div>;

// src/pages/call-recordings.tsx
export const CallRecordingsPage = () => <div>Call Recordings  coming soon</div>;

// src/pages/missed-calls.tsx
export const MissedCallsPage = () => <div>Missed Calls  coming soon</div>;
  • Step 5: Verify build
cd helix-engage && npm run build
  • Step 6: Commit
git add src/components/layout/sidebar.tsx src/main.tsx src/pages/team-performance.tsx src/pages/live-monitor.tsx src/pages/call-recordings.tsx src/pages/missed-calls.tsx
git commit -m "feat: admin sidebar restructure + placeholder pages for supervisor module"

Task 2: Call Recordings Page

Files:

  • Modify: helix-engage/src/pages/call-recordings.tsx

  • Step 1: Implement call recordings page

Query platform for calls with recordings. Reuse patterns from call-history.tsx.

// Query: calls where recording primaryLinkUrl is not empty
const QUERY = `{ calls(first: 100, filter: {
    recording: { primaryLinkUrl: { neq: "" } }
}, orderBy: [{ startedAt: DescNullsLast }]) { edges { node {
    id direction callStatus callerNumber { primaryPhoneNumber }
    agentName startedAt durationSec disposition
    recording { primaryLinkUrl primaryLinkLabel }
} } } }`;

Table columns: Agent, Caller (PhoneActionCell), Type (In/Out badge), Date, Duration, Disposition, Recording (play button).

Search by agent name or phone number. Date filter optional.

  • Step 2: Verify build
cd helix-engage && npm run build
  • Step 3: Commit
git add src/pages/call-recordings.tsx
git commit -m "feat: call recordings master page"

Task 3: Missed Calls Page (Supervisor View)

Files:

  • Modify: helix-engage/src/pages/missed-calls.tsx

  • Step 1: Implement missed calls page

Query platform for all missed calls — no agent filter (supervisor sees all).

const QUERY = `{ calls(first: 100, filter: {
    callStatus: { eq: MISSED }
}, orderBy: [{ startedAt: DescNullsLast }]) { edges { node {
    id callerNumber { primaryPhoneNumber } agentName
    startedAt callsourcenumber callbackstatus missedcallcount callbackattemptedat
} } } }`;

Table columns: Caller (PhoneActionCell), Date/Time, Branch (callsourcenumber), Agent, Callback Status (badge), SLA (computed from startedAt).

Tabs: All | Pending (PENDING_CALLBACK) | Attempted (CALLBACK_ATTEMPTED) | Completed (CALLBACK_COMPLETED + WRONG_NUMBER).

Search by phone or agent.

  • Step 2: Verify build
cd helix-engage && npm run build
  • Step 3: Commit
git add src/pages/missed-calls.tsx
git commit -m "feat: missed calls master page for supervisors"

Task 4: Sidecar — Supervisor Module

Files:

  • Create: helix-engage-server/src/supervisor/supervisor.service.ts

  • Create: helix-engage-server/src/supervisor/supervisor.controller.ts

  • Create: helix-engage-server/src/supervisor/supervisor.module.ts

  • Modify: helix-engage-server/src/app.module.ts

  • Step 1: Create supervisor service

// supervisor.service.ts
// Two responsibilities:
// 1. Aggregate Ozonetel agent summary across all agents
// 2. Track active calls from Ozonetel real-time events

import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { PlatformGraphqlService } from '../platform/platform-graphql.service';
import { OzonetelAgentService } from '../ozonetel/ozonetel-agent.service';

type ActiveCall = {
    ucid: string;
    agentId: string;
    callerNumber: string;
    callType: string;
    startTime: string;
    status: 'active' | 'on-hold';
};

@Injectable()
export class SupervisorService implements OnModuleInit {
    private readonly logger = new Logger(SupervisorService.name);
    private readonly activeCalls = new Map<string, ActiveCall>();

    constructor(
        private platform: PlatformGraphqlService,
        private ozonetel: OzonetelAgentService,
        private config: ConfigService,
    ) {}

    async onModuleInit() {
        // Subscribe to Ozonetel events (fire and forget)
        // Will be implemented when webhook URL is configured
        this.logger.log('Supervisor service initialized');
    }

    // Called by webhook when Ozonetel pushes call events
    handleCallEvent(event: any) {
        const { action, ucid, agent_id, caller_id, call_type, event_time } = event;
        if (action === 'Answered' || action === 'Calling') {
            this.activeCalls.set(ucid, {
                ucid, agentId: agent_id, callerNumber: caller_id,
                callType: call_type, startTime: event_time, status: 'active',
            });
        } else if (action === 'Disconnect') {
            this.activeCalls.delete(ucid);
        }
    }

    // Called by webhook when Ozonetel pushes agent events
    handleAgentEvent(event: any) {
        this.logger.log(`Agent event: ${event.agentId}${event.action}`);
    }

    getActiveCalls(): ActiveCall[] {
        return Array.from(this.activeCalls.values());
    }

    // Aggregate time breakdown across all agents
    async getTeamPerformance(date: string): Promise<any> {
        // Get all agent IDs from platform
        const agentData = await this.platform.query<any>(
            `{ agents(first: 20) { edges { node {
                id name ozonetelagentid npsscore maxidleminutes minnpsthreshold minconversionpercent
            } } } }`,
        );
        const agents = agentData?.agents?.edges?.map((e: any) => e.node) ?? [];

        // Fetch Ozonetel summary per agent
        const summaries = await Promise.all(
            agents.map(async (agent: any) => {
                try {
                    const summary = await this.ozonetel.getAgentSummary(agent.ozonetelagentid, date);
                    return { ...agent, timeBreakdown: summary };
                } catch {
                    return { ...agent, timeBreakdown: null };
                }
            }),
        );

        return { date, agents: summaries };
    }
}
  • Step 2: Create supervisor controller
// supervisor.controller.ts
import { Controller, Get, Post, Body, Query, Logger } from '@nestjs/common';
import { SupervisorService } from './supervisor.service';

@Controller('api/supervisor')
export class SupervisorController {
    private readonly logger = new Logger(SupervisorController.name);

    constructor(private readonly supervisor: SupervisorService) {}

    @Get('active-calls')
    getActiveCalls() {
        return this.supervisor.getActiveCalls();
    }

    @Get('team-performance')
    async getTeamPerformance(@Query('date') date?: string) {
        const targetDate = date ?? new Date().toISOString().split('T')[0];
        return this.supervisor.getTeamPerformance(targetDate);
    }

    @Post('call-event')
    handleCallEvent(@Body() body: any) {
        // Ozonetel pushes events here
        const event = body.data ?? body;
        this.logger.log(`Call event: ${event.action} ucid=${event.ucid} agent=${event.agent_id}`);
        this.supervisor.handleCallEvent(event);
        return { received: true };
    }

    @Post('agent-event')
    handleAgentEvent(@Body() body: any) {
        const event = body.data ?? body;
        this.logger.log(`Agent event: ${event.action} agent=${event.agentId}`);
        this.supervisor.handleAgentEvent(event);
        return { received: true };
    }
}
  • Step 3: Create supervisor module and register
// supervisor.module.ts
import { Module } from '@nestjs/common';
import { PlatformModule } from '../platform/platform.module';
import { OzonetelAgentModule } from '../ozonetel/ozonetel-agent.module';
import { SupervisorController } from './supervisor.controller';
import { SupervisorService } from './supervisor.service';

@Module({
    imports: [PlatformModule, OzonetelAgentModule],
    controllers: [SupervisorController],
    providers: [SupervisorService],
})
export class SupervisorModule {}

Add to app.module.ts:

import { SupervisorModule } from './supervisor/supervisor.module';
// Add to imports array
  • Step 4: Verify sidecar build
cd helix-engage-server && npm run build
  • Step 5: Commit
git add src/supervisor/ src/app.module.ts
git commit -m "feat: supervisor module with team performance + active calls endpoints"

Task 5: Team Performance Dashboard (PP-5)

Files:

  • Modify: helix-engage/src/pages/team-performance.tsx

This is the largest task. The page queries platform directly for calls/appointments/leads and the sidecar for time breakdown.

  • Step 1: Build the full page

The page has 6 sections. Use apiClient.graphql() for platform data and apiClient.get() for sidecar data.

Queries needed:

  • Calls by date range: calls(first: 500, filter: { startedAt: { gte: "...", lte: "..." } })
  • Appointments by date range: appointments(first: 200, filter: { scheduledAt: { gte: "...", lte: "..." } })
  • Leads: leads(first: 200)
  • Follow-ups: followUps(first: 200)
  • Agents with thresholds: agents(first: 20) { ... npsscore maxidleminutes minnpsthreshold minconversionpercent }
  • Sidecar: GET /api/supervisor/team-performance?date=YYYY-MM-DD

Date range logic:

  • Today: today start → now
  • Week: Monday of current week → now
  • Month: 1st of current month → now
  • Year: Jan 1 → now
  • Custom: user-selected range

Sections to implement:

  1. Key Metrics bar (6 cards in a row)
  2. Call Breakdown Trends (2 ECharts line charts side by side)
  3. Agent Performance table (sortable)
  4. Time Breakdown (team average + per-agent stacked bars)
  5. NPS + Conversion Metrics (donut + cards)
  6. Performance Alerts (threshold comparison)

Check if ECharts is already installed:

grep echarts helix-engage/package.json

If not, install: npm install echarts echarts-for-react

Follow the existing My Performance page (my-performance.tsx) for ECharts patterns.

  • Step 2: Verify build
cd helix-engage && npm run build
  • Step 3: Test locally
cd helix-engage && npm run dev

Navigate to /team-performance as admin user. Verify all 6 sections render with real data.

  • Step 4: Commit
git add src/pages/team-performance.tsx package.json package-lock.json
git commit -m "feat: team performance dashboard (PP-5) with 6 data sections"

Task 6: Live Call Monitor (PP-6)

Files:

  • Modify: helix-engage/src/pages/live-monitor.tsx

  • Step 1: Build the live monitor page

Page polls GET /api/supervisor/active-calls every 5 seconds.

Structure:

  1. TopBar: "Live Call Monitor" with subtitle "Listen, whisper, or barge into active calls"
  2. Three KPI cards: Active Calls, On Hold, Avg Duration
  3. Active Calls table: Agent, Caller, Type, Department, Duration (live counter), Status, Actions
  4. Actions: Listen / Whisper / Barge buttons — all disabled with tooltip "Coming soon — pending Ozonetel API"
  5. Empty state: headphones icon + "No active calls"

Duration should be a live counter — calculated client-side from startTime in the active call data. Use setInterval to update every second.

Caller name: attempt to match callerNumber against leads from useData(). If matched, show lead name + phone. If not, show phone only.

  • Step 2: Verify build
cd helix-engage && npm run build
  • Step 3: Test locally

Navigate to /live-monitor. Verify empty state renders. If Ozonetel events are flowing, verify active calls appear.

  • Step 4: Commit
git add src/pages/live-monitor.tsx
git commit -m "feat: live call monitor page (PP-6) with polling + KPI cards"

Task 7: Local Testing + Final Verification

  • Step 1: Run both locally

Terminal 1: cd helix-engage-server && npm run start:dev Terminal 2: cd helix-engage && npm run dev

  • Step 2: Test admin login

Login as admin (sanjay.marketing@globalhospital.com). Verify:

  • Sidebar shows new nav structure (Supervisor + Data & Reports sections)

  • Dashboard loads

  • Team Performance shows data from platform

  • Live Monitor shows empty state or active calls

  • All master data pages load (Lead, Patient, Appointment, Call Log, Call Recordings, Missed Calls)

  • Step 3: Commit any fixes

  • Step 4: Push to Azure

cd helix-engage && git push origin dev
cd helix-engage-server && git push origin dev