Part of #66. Storage for speaker labels as a derived, re-runnable layer. The transcript_utterances.source column (mic/system ground truth) is never modified.
Scope
New migration (follow the checklist in CLAUDE.md: sequential SQL file, register in migrations(), bump total, update tests/common/mod.rs, add migration tests) adding:
speaker_labels: one row per labeled utterance. Columns along the lines of: utterance_id (FK), speakers (JSON array of person identifiers; also representing unknown and media sentinels), label_source (human | llm), confidence (REAL, NULL for human), model (TEXT, NULL for human), segmentation_version (INTEGER), labeled_at (TEXT). Unique on utterance_id + label_source so a human correction can coexist with (and take precedence over) an LLM label, and an LLM re-run can replace its own layer without touching human rows.
speaker_label_segments (optional per-sentence detail): utterance_id, label_source, sentence_index, speakers (JSON array). Only present when an utterance was labeled sentence-by-sentence; sentence indices are only meaningful together with segmentation_version.
Design notes
- Speaker identity: use email where available (stable across name variants), falling back to the
people/document_people identifier; decide and document in the migration.
- Deleting/re-running LLM attribution =
DELETE WHERE label_source='llm' (optionally scoped to a model), leaving human rows intact.
- Reads resolve precedence human > llm at query time.
Part of #66. Storage for speaker labels as a derived, re-runnable layer. The
transcript_utterances.sourcecolumn (mic/system ground truth) is never modified.Scope
New migration (follow the checklist in CLAUDE.md: sequential SQL file, register in
migrations(), bumptotal, updatetests/common/mod.rs, add migration tests) adding:speaker_labels: one row per labeled utterance. Columns along the lines of:utterance_id(FK),speakers(JSON array of person identifiers; also representingunknownandmediasentinels),label_source(human|llm),confidence(REAL, NULL for human),model(TEXT, NULL for human),segmentation_version(INTEGER),labeled_at(TEXT). Unique onutterance_id+label_sourceso a human correction can coexist with (and take precedence over) an LLM label, and an LLM re-run can replace its own layer without touching human rows.speaker_label_segments(optional per-sentence detail):utterance_id,label_source,sentence_index,speakers(JSON array). Only present when an utterance was labeled sentence-by-sentence; sentence indices are only meaningful together withsegmentation_version.Design notes
people/document_peopleidentifier; decide and document in the migration.DELETE WHERE label_source='llm'(optionally scoped to a model), leaving human rows intact.