Patient records created from the enquiry form now get a platform title
from the typed name. Cosmetic fix — frontend was already showing the
fullName, only platform admin browsing showed "Untitled".
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
usePerformanceAlerts now fetches /api/supervisor/performance-alerts
every 60s instead of computing client-side. Dismiss + dismiss-all hit
the sidecar so state survives reload. Toast fires when new alerts arrive.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1. notification-bell: drop the DEMO_ALERTS fallback (Riya Mehta etc.).
Empty state ("No active alerts") shows when the live computation
returns nothing — which is the truthful state until thresholds are
set on Agent records.
2. use-performance-alerts: bucket calls by c.agentId === agent.id when
the relation is set; fall back to legacy agentName matching only for
un-enriched rows. Fixes conversion% calc going to 0 after backfill.
3. agent-table: Link target uses agent.id (UUID or "legacy:NAME") so
the URL is a stable identifier instead of a display string.
4. agent-detail: parse the route param into UUID vs legacy:NAME, filter
calls by c.agentId or c.agentName accordingly, and resolve display
name via the platform Agents list.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The Agent Performance table on the team dashboard was bucketing by raw
call.agentName — the field that holds Ozonetel's transfer-chain string
("RamaiahAdmin -> GlobalHealthX") and collides for distinct AgentIDs
that share a Full Name. Result: 7 rows for 3 real agents.
Now buckets by call.agent.id when the CDR enrichment has populated it,
falls back to legacy agentName grouping otherwise. Calls without any
agent info are dropped from the agent rollup (instead of being
collapsed under "Unknown").
Pulls agent { id name ozonetelAgentId } + transferredTo + transferType
on CALLS_QUERY.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Prefer call.agent.id (set by CDR enrichment) over call.agentName string
matching. Falls back to the raw agentName only when the row hasn't been
enriched yet. Eliminates the "RamaiahAdmin -> GlobalHealthX" transfer-chain
rows and the display-name collisions (two distinct AgentIDs with the same
Full Name).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- appointment-form: normalize phone to +91XXXXXXXXXX before patient create
- appointment-form: use empty string not 'Unknown' for name fallback
- call-history: show formatted phone number instead of 'Unknown' when no lead
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Appointments page was using department for the Branch column. Now fetches
doctor.clinic.clinicName from the GraphQL query and displays that. Search
filter also updated.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Caller resolver now returns empty IDs for unrecognized numbers instead
of eagerly creating lead+patient records. Records are created when the
agent explicitly books an appointment or logs an enquiry — per PRD.
- caller-resolution.service.ts: return unresolved result, don't create
- call-desk.tsx: toast changed to 'No existing records found'
- appointment-form.tsx: create patient on save if none exists
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When a new caller's patient record is created by the caller resolver,
the name is empty. The agent types a name in the appointment form but
the patient was never updated (Bug #527 removed all patient updates).
Now updates patient name only when the initial name was empty — existing
patients with names are not affected.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Slots were fetched by doctor+date but not filtered by clinic. A doctor
visiting multiple branches showed all slots regardless of selected branch.
Now filters by clinicId when a clinic is selected.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The logout POST to /auth/logout was getting cancelled when the page
navigated to /login before the fetch completed. keepalive: true ensures
the request survives page unload so the Redis session lock is released.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
use-sip-phone.ts reads VITE_SIP_* as fallback before login response
provides per-agent config. Keep them for safety.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
VITE_API_URL is now empty (same-origin). Caddy proxies /auth and /api
to the sidecar on the same domain. The old VPS URL caused 'User not
found' errors because the VPS has different workspace users.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace openssl pkey decryption with direct sshpass passphrase handling.
Use original key file directly. Added VPN note.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Left panel: KPI cards + clickable call table (row selection highlights).
Right panel (380px): caller context (name, phone, source, AI summary,
appointments) + BargeControls component. Fetches lead data by phone match
on selection. Auto-clears when selected call ends. Removed disabled
Listen/Whisper/Barge buttons — replaced with integrated barge drawer.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
BargeControls component with 4 states: idle → connecting → connected → ended.
Connected state shows Listen/Whisper/Barge mode tabs (DTMF 4/5/6), live
duration counter, hang up button. Auto-cleanup on unmount. Mode changes
notify sidecar for agent SSE events.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Separate from agent sip-client.ts — different lifecycle (on-demand per
barge session, not persistent). Auto-answers incoming Ozonetel calls.
DTMF mode switching: 4=listen, 5=whisper, 6=barge. Event-driven with
registered/callConnected/callEnded events. Audio via hidden <audio> element.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Admin username + password inputs in the Ozonetel section for supervisor
barge/whisper/listen access. Follows existing masked password pattern.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
SIP-only supervisor barge with DTMF mode switching (4=listen, 5=whisper,
6=barge). Live monitor split layout with context panel. Agent indicator
on whisper/barge only. Auto-disconnect on call end. Dynamic SIP from
Ozonetel pool.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- requirements.md: full 16-user-story tracker with verified implementation
status, code references, Ozonetel API findings, platform capability notes,
and implementation guides for search (includeInSearch), barge/whisper, and
appointment notifications
- ozonetel-cdr-api-reference.md: all 42 CDR fields, 3 endpoints (detailed,
UCID, paginated), sidecar mapping status, known gotchas (nullable fields,
field name inconsistency, rate limits)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Covers Gitea + Woodpecker + MinIO pipeline setup, Teams
notifications, test report publishing, mirrored repos,
secrets config, troubleshooting, and E2E test coverage.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- 13 Global Hospital smoke tests (CC Agent + Supervisor)
- Auto-unlock agent session in test setup via maint API
- agent-status-toggle sends agentId from localStorage (was missing)
- maint-otp-modal injects agentId from localStorage into all calls
- SIP manager logs agent identity on connect/disconnect/state changes
- seed-data.ts: added CC agent + marketing users, idempotent member
creation, cleanup phase before seeding
- .gitignore: exclude test-results/ and playwright-report/
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Rewrote developer-operations-runbook.md to reflect the current EC2
multi-tenant deployment (was VPS-only). Covers SSH key setup, all
containers, accounts, deploy steps, E2E tests, Redis ops, DB access,
and troubleshooting. Removed duplicate runbook.md.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- 27 Playwright E2E tests covering login (3 roles), CC Agent pages
(call desk, call history, patients, appointments, my performance,
sidebar, sign-out), and Supervisor pages (all 11 pages + sidebar)
- Tests run against live EC2 at ramaiah.engage.healix360.net
- Last test completes sign-out to release agent session for next run
- Architecture doc with updated Mermaid diagram including telephony
dispatcher, service discovery, and multi-tenant topology
- Operations runbook with SSH access (VPS + EC2), accounts, container
reference, deploy steps, Redis ops, and troubleshooting guide
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
#533: Remove redundant Call History top header (duplicate TopBar)
#531: Block logout during active call (confirm dialog + UCID check)
#529: Block outbound calls when agent is on Break/Training
#527: Remove updatePatient during appointment creation (was mutating
shared Patient entity, affecting all past appointments)
#547: SLA rules seeded via API (config issue, not code)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>