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, opportunity, 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 exactly these seven kinds:
| 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. |
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 opportunity (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.