Call detail
The three-pane call viewer: diarized transcript, typed AI insights with transcript citations, and human-approved CRM update proposals.
Call detail
The call detail view at /calls/{id} is where a processed recording becomes usable. It renders three panes — transcript, AI insights, and proposed CRM updates — all served by GET /v1/calls/{id} (RecordingDetail), which returns the recording, its transcript bundle, its extractions, and its proposals in one payload.
Live surface
Transcript rendering, insight tabs with click-to-cite, and the approve/reject proposal loop are all backed by real endpoints. The view polls while a call is still transcribing/extracting and stops once it reaches ready, failed, or consent_blocked.
Header and status
The header shows the title, linked contact, deal, duration, and language, plus the current status chip. A Reprocess button re-queues the pipeline (disabled for consent_blocked rows). If the row is consent_blocked, a prominent panel explains that the recording was preserved for audit without transcription and links to the consent ledger to grant call-recording consent.
Transcript pane
The transcript is a list of diarized segments. Each SpeakerSegment carries a start/end timestamp in milliseconds, the speaker label, a resolved speaker_role (rep / prospect), and the text. Segments are color-coded by role, and clicking a citation elsewhere in the view highlights the matching segment — the timestamps are the join key between an insight and its evidence.
AI insights pane
Insights are grouped into tabs by kind. The extractor (core/conversation_intel/extract.py) emits these seven general kinds, and every call shows a tab for each:
| Kind | What it captures |
|---|---|
pain | Pain points the prospect raised. |
objection | Objections or blockers. |
competitor | Competitors mentioned. |
criterion | Stated decision criteria. |
next_step | Agreed next steps. |
signal | Buying signals. |
sentiment | Sentiment readings across the call. |
A workspace on the MEDDPICC extraction schema (core/conversation_intel/extraction_config.py) also gets the eight meddic_* axes: metrics, economic buyer, decision criteria, decision process, paper process, identified pain, champion and competition. Each of those gets a tab only on a call that has an insight of that kind. An insight whose kind the page does not recognize lands under Other, labelled with its own kind name, so nothing the extractor stored is hidden.
Each ExtractionItem includes the insight text, an optional evidence quote, a confidence score, a speaker_label, and citation_start_ms — clicking the timestamp jumps the transcript to the moment the insight came from. When the AI client is unavailable the extractor degrades to a heuristic source rather than fabricating insights; a kind with no support simply renders empty.
Proposed CRM updates pane
The most consequential pane. During extraction the LLM also drafts call_proposed_updates — concrete field changes on the linked deal (e.g. move stage, set a criterion). Each ProposalItem shows the field, the operation, the current value, the proposed value, and a rationale, in a pending / approved / rejected state.
Human-in-the-loop by design
No proposal mutates the opportunity until a rep approves it. Approve (POST /v1/calls/{id}/proposals/{proposal_id}/approve) applies the change, snapshots the prior value onto the proposal, and emits a call_proposal.approved domain event. Reject records the decision and emits call_proposal.rejected. A proposal with no linked opportunity cannot be approved (400), and an already-decided proposal returns 409.
transcript segment ──cite──▶ insight (kind, evidence, confidence)
│
▼
proposed opportunity update
│ approve (human)
▼
opportunity field written + event emitted
Empty and error states
A 404 (deleted recording or stale link) renders a benign "Call not found" empty state rather than an error. A call that has not finished processing shows "No transcript yet" with its current phase, so the view is coherent at every point in the lifecycle.