We match each spoken segment to a person using three layers, in order:
- Direct caption labels. YouTube captions on most Bellingham meetings tag each speaker by name. We collapse name variants (Bob/Robert, Council Member Smith, etc.) to a canonical roster entry. Highest confidence.
- Roll call + chair attribution. For meetings transcribed from audio (no built-in speaker labels), we read the opening roll call and chair acknowledgements ("Council Member X moves to…") to map Azure-Speech speaker numbers to councilmembers. Confidence shown on each match.
- Public-comment exclusion. Citizens who introduce themselves with a non-roster name during public comment are tagged as public-comment content, never attributed to a councilmember. (Robert's Rules: councilmembers don't speak back during public comment, so the chair just thanks the speaker and moves on.)
Where attribution is uncertain — usually because Azure's voice diarization conflated multiple similar-sounding voices into one Speaker N — we show the raw "Speaker N" label rather than guess. The raw text is always exact; only the speaker label is a best-effort match.