[Table Tools] Quote board and session highlights #116

Closed
opened 2026-07-14 19:52:26 +00:00 by claude-bot · 1 comment
Contributor

Context / Motivation

Transcripts are diarized with speaker attribution (process_audio merges per-speaker segments {start, end, text, speaker} into [HH:MM:SS] Speaker: text lines via merge_attributed_transcript, webapp/backend/app/services/audio_service.py:401; speaker labels are rewritten to character names at tasks/reminder_tasks.py:1584), and a summarization LLM pass already runs in the same task. "Quote of the session" is therefore nearly free — and it's the delight feature competitors without recording can't copy.

Spec

Extraction — extend the summarization step inside process_audio (webapp/backend/app/tasks/reminder_tasks.py:1461, success path :1631-1646) to also extract:

  • up to N memorable quotes (campaign config, default 5): {speaker, quote, timestamp_ref} where timestamp_ref is the [HH:MM:SS] transcript marker
  • 2-3 key moments (one-line descriptions)

Prompt-engineering note: instruct the model to prefer verbatim lines with speaker attribution lifted from the diarized transcript (the [HH:MM:SS] Speaker: text format makes this checkable — validate that returned quotes substring-match the transcript and drop hallucinated ones).

Storage — judged against existing summary storage: Session.summary is a flat Text column (models/session.py:115), but quotes need per-row GM edit/delete and a campaign-wide query surface, so use a new table SessionHighlight rather than a JSON column: id, session_id (FK CASCADE, indexed), campaign_id (denormalized FK, indexed for the Highlights page), kind: quote | moment, speaker (nullable for moments), text, timestamp_ref (nullable), approved bool default false, created_at, edited_by_id (nullable). (A JSONB column on sessions was considered and rejected: no per-row edit history, awkward cross-session listing.)

Approval gate — same gate as the summary: highlights stay GM-only while audio_processing_status is ready; they become player-visible when the GM approves via approve_audio (routers/sessions.py:436-504; enum AudioProcessingStatus, models/session.py:27-34). GM can edit text/speaker, delete rows, or add manual quotes on the session page before approving.

Surfaces:

  • Session page: "Highlights" panel (GM: editable pre-approval; players: visible post-approval).
  • Campaign "Highlights" page: approved quotes across sessions, newest first, filter by speaker.
  • Bot (optional flag, default on): include the top approved quote as a field in the session_summary_approved embed the bot already posts (event published in approve_audio; handled in bot/questboard_bot/cogs/notifications.py).

Out of scope

  • Player reactions/voting on quotes.
  • Audio clip extraction for quotes.
  • Retroactive extraction for previously processed sessions (manual add covers it).

Acceptance criteria

  • Pipeline produces ≤ N structured quotes + key moments for a transcript fixture; hallucinated (non-substring) quotes are dropped.
  • Highlights invisible to players until summary approval; GM edit/delete before approval works; manual add works.
  • Campaign Highlights page lists approved quotes across sessions with speaker filter.
  • With the bot flag on, the approved-summary embed contains the top quote; off restores current embed.

References

  • webapp/backend/app/tasks/reminder_tasks.py:1461 (process_audio), :1584 (_apply_character_names), :1631-1646 (success path)
  • webapp/backend/app/services/audio_service.py:274/:329 (segment shape), :401 (merge_attributed_transcript)
  • webapp/backend/app/models/session.py:115 (summary), :27-34 (AudioProcessingStatus), :161 (summary_discord_message_id)
  • webapp/backend/app/routers/sessions.py:436 (approve_audio — approval gate + session_summary_approved event)
  • bot/questboard_bot/cogs/notifications.py (summary-approved embed)

Filed from the July 2026 full-project review.

## Context / Motivation Transcripts are diarized with speaker attribution (`process_audio` merges per-speaker segments `{start, end, text, speaker}` into `[HH:MM:SS] Speaker: text` lines via `merge_attributed_transcript`, `webapp/backend/app/services/audio_service.py:401`; speaker labels are rewritten to character names at `tasks/reminder_tasks.py:1584`), and a summarization LLM pass already runs in the same task. "Quote of the session" is therefore nearly free — and it's the delight feature competitors without recording can't copy. ## Spec **Extraction** — extend the summarization step inside `process_audio` (`webapp/backend/app/tasks/reminder_tasks.py:1461`, success path `:1631-1646`) to also extract: - up to N memorable quotes (campaign config, default 5): `{speaker, quote, timestamp_ref}` where `timestamp_ref` is the `[HH:MM:SS]` transcript marker - 2-3 key moments (one-line descriptions) **Prompt-engineering note**: instruct the model to prefer **verbatim lines with speaker attribution** lifted from the diarized transcript (the `[HH:MM:SS] Speaker: text` format makes this checkable — validate that returned quotes substring-match the transcript and drop hallucinated ones). **Storage** — judged against existing summary storage: `Session.summary` is a flat Text column (`models/session.py:115`), but quotes need per-row GM edit/delete and a campaign-wide query surface, so use a **new table** `SessionHighlight` rather than a JSON column: `id`, `session_id` (FK CASCADE, indexed), `campaign_id` (denormalized FK, indexed for the Highlights page), `kind`: `quote` | `moment`, `speaker` (nullable for moments), `text`, `timestamp_ref` (nullable), `approved` bool default false, `created_at`, `edited_by_id` (nullable). (A JSONB column on sessions was considered and rejected: no per-row edit history, awkward cross-session listing.) **Approval gate** — same gate as the summary: highlights stay GM-only while `audio_processing_status` is `ready`; they become player-visible when the GM approves via `approve_audio` (`routers/sessions.py:436-504`; enum `AudioProcessingStatus`, `models/session.py:27-34`). GM can edit text/speaker, delete rows, or add manual quotes on the session page before approving. **Surfaces**: - Session page: "Highlights" panel (GM: editable pre-approval; players: visible post-approval). - Campaign "Highlights" page: approved quotes across sessions, newest first, filter by speaker. - Bot (optional flag, default on): include the top approved quote as a field in the `session_summary_approved` embed the bot already posts (event published in `approve_audio`; handled in `bot/questboard_bot/cogs/notifications.py`). ## Out of scope - Player reactions/voting on quotes. - Audio clip extraction for quotes. - Retroactive extraction for previously processed sessions (manual add covers it). ## Acceptance criteria - Pipeline produces ≤ N structured quotes + key moments for a transcript fixture; hallucinated (non-substring) quotes are dropped. - Highlights invisible to players until summary approval; GM edit/delete before approval works; manual add works. - Campaign Highlights page lists approved quotes across sessions with speaker filter. - With the bot flag on, the approved-summary embed contains the top quote; off restores current embed. ## References - `webapp/backend/app/tasks/reminder_tasks.py:1461` (`process_audio`), `:1584` (`_apply_character_names`), `:1631-1646` (success path) - `webapp/backend/app/services/audio_service.py:274/:329` (segment shape), `:401` (`merge_attributed_transcript`) - `webapp/backend/app/models/session.py:115` (`summary`), `:27-34` (`AudioProcessingStatus`), `:161` (`summary_discord_message_id`) - `webapp/backend/app/routers/sessions.py:436` (`approve_audio` — approval gate + `session_summary_approved` event) - `bot/questboard_bot/cogs/notifications.py` (summary-approved embed) _Filed from the July 2026 full-project review._
Author
Contributor

Done — merged in PR #194 (backend+bot 86e6e66, frontend). CI green.

Shipped:

  • SessionHighlight model (quote/moment, speaker, text, timestamp_ref, approved, edited_by) + migration b4c5d6e7f8a9 (round-trips). Campaign settings highlights_max_quotes (5) + highlights_in_discord (true).
  • extract_highlights LLM pass in process_audio: verbatim-quote prompt, defensive JSON parsing, and the hallucination filter — a quote is dropped unless it substring-matches the diarized transcript (whitespace/case-normalised). Non-fatal (a failure never fails audio processing) and idempotent (delete-then-reinsert). Never logs transcript/quote text.
  • Endpoints: session highlight CRUD (GM full; members see approved only), campaign quote board (?speaker= filter); approve_audio flips highlights to approved.
  • Bot: optional "💬 Quote of the session" embed field on the approved-summary post — additive, no BOT_CONTRACT_VERSION bump.
  • Frontend: SessionHighlights panel (GM edit + draft badges), CampaignHighlights quote board page + nav link, the two campaign settings.

Tests: backend 478 pass (+12 test_highlights.py: 6 extraction/validator + 6 API covering the visibility gate, approval flip, and quote board); frontend 229 (+23).

Acceptance criteria met: ≤N structured quotes + moments per transcript with hallucinated (non-substring) quotes dropped; highlights invisible to players until approval; GM edit/delete/manual-add work; campaign quote board lists approved quotes with a speaker filter; the bot flag adds the top quote to the approved-summary embed (off restores the prior embed).

Closing.

Done — merged in PR #194 (backend+bot `86e6e66`, frontend). CI green. **Shipped:** - `SessionHighlight` model (`quote`/`moment`, speaker, text, timestamp_ref, approved, edited_by) + migration `b4c5d6e7f8a9` (round-trips). Campaign settings `highlights_max_quotes` (5) + `highlights_in_discord` (true). - `extract_highlights` LLM pass in `process_audio`: verbatim-quote prompt, defensive JSON parsing, and the **hallucination filter** — a quote is dropped unless it substring-matches the diarized transcript (whitespace/case-normalised). Non-fatal (a failure never fails audio processing) and idempotent (delete-then-reinsert). Never logs transcript/quote text. - Endpoints: session highlight CRUD (GM full; members see approved only), campaign quote board (`?speaker=` filter); `approve_audio` flips highlights to approved. - Bot: optional "💬 Quote of the session" embed field on the approved-summary post — additive, no `BOT_CONTRACT_VERSION` bump. - Frontend: `SessionHighlights` panel (GM edit + draft badges), `CampaignHighlights` quote board page + nav link, the two campaign settings. **Tests:** backend 478 pass (+12 `test_highlights.py`: 6 extraction/validator + 6 API covering the visibility gate, approval flip, and quote board); frontend 229 (+23). **Acceptance criteria** met: ≤N structured quotes + moments per transcript with hallucinated (non-substring) quotes dropped; highlights invisible to players until approval; GM edit/delete/manual-add work; campaign quote board lists approved quotes with a speaker filter; the bot flag adds the top quote to the approved-summary embed (off restores the prior embed). Closing.
rbrooks referenced this issue from a commit 2026-07-18 04:59:00 +00:00
Sign in to join this conversation.
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
rbrooks/Quest-Board#116
No description provided.