Context
Shipped in commit eb9ae71: agenda-preview summaries inject relevant TopicBriefing content into the analyze_meeting_content prompt when a topic name or alias appears as a substring in the agenda text. This lets the AI ground preview summaries in established civic concerns rather than treating each agenda item as an isolated title.
The Limitation
Substring matching has poor recall when agenda phrasing diverges from how a topic was named during extraction. Real example from meeting 173 (Advisory Recreation Board, 2026-04-15):
- Agenda item: "Cemetery Flower Planting Ordinance Change"
- Existing topic: #563 cemetery perpetual care flowers (impact score 3, 5 appearances)
- Topic aliases:
cemetery perpetual care flowers funding, perpetual care flower fund for the cemetery, update on cemetery perpetual flower program, investment fund for perpetual care flowers and community band
- Topic briefing headline: "Council voted 9-0 to fund cemetery flowers for 2026, but the paperwork didn't match the dollar plan"
No alias is a literal substring of the agenda phrasing (Cemetery Flower Planting), so the briefing never surfaces. The AI writes a flat sentence about perennials vs annuals, missing the live paperwork-mismatch context residents have been tracking.
Same pattern likely repeats for other committee agendas where civic topic names are formal (perpetual care flowers funding) but agenda items use different wording (Flower Planting Ordinance Change).
Possible Approaches (ordered by scope)
1. Admin-managed alias broadening
When a topic's briefing is created/refreshed, surface candidate agenda phrasings and let admins add them as TopicAlias rows. Lowest code effort, highest manual burden.
2. Token-overlap matching
Replace pure substring match with a bag-of-content-words comparison. For each topic (and its aliases), check if N% of its content tokens appear within a window of agenda text. Would catch cemetery + flower + planting overlap between cemetery perpetual care flowers and Cemetery Flower Planting. Needs tuning to avoid false positives on generic vocabulary.
3. Vector-similarity match against topic embeddings
Embed each topic's name + description + briefing headline into pgvector. Split agenda text into items; embed each; for each agenda item, cosine-match against topic vectors; inject the top matches. Most semantically aware, highest infrastructure cost, but reuses existing VectorService stack.
4. Hybrid
Start with approach 2 (token overlap) as an additive path beside the current substring match. If that still misses important cases, layer in approach 3. Approach 1 is always available as a manual escape hatch.
Notes
- The current substring matcher ships in
app/jobs/summarize_meeting_job.rb as agenda_topic_context. It's scoped to agenda_preview mode only; full mode (packet / transcript / minutes) does not use topic-context injection yet.
- Any richer matching approach should also consider extending to full mode — minutes summaries currently don't receive per-topic briefing context at the meeting level either, though they do get it per-topic inside
generate_topic_summaries.
- Filter thresholds in the current implementation (
AGENDA_TOPIC_MIN_WORDS = 2, AGENDA_TOPIC_MIN_CHARS = 15, AGENDA_TOPIC_MIN_IMPACT = 3) are tuned to avoid over-matching. Any replacement should preserve the over-match protection.
Acceptance criteria (future)
Context
Shipped in commit
eb9ae71: agenda-preview summaries inject relevantTopicBriefingcontent into theanalyze_meeting_contentprompt when a topic name or alias appears as a substring in the agenda text. This lets the AI ground preview summaries in established civic concerns rather than treating each agenda item as an isolated title.The Limitation
Substring matching has poor recall when agenda phrasing diverges from how a topic was named during extraction. Real example from meeting 173 (Advisory Recreation Board, 2026-04-15):
cemetery perpetual care flowers funding,perpetual care flower fund for the cemetery,update on cemetery perpetual flower program,investment fund for perpetual care flowers and community bandNo alias is a literal substring of the agenda phrasing (
Cemetery Flower Planting), so the briefing never surfaces. The AI writes a flat sentence about perennials vs annuals, missing the live paperwork-mismatch context residents have been tracking.Same pattern likely repeats for other committee agendas where civic topic names are formal (
perpetual care flowers funding) but agenda items use different wording (Flower Planting Ordinance Change).Possible Approaches (ordered by scope)
1. Admin-managed alias broadening
When a topic's briefing is created/refreshed, surface candidate agenda phrasings and let admins add them as
TopicAliasrows. Lowest code effort, highest manual burden.2. Token-overlap matching
Replace pure substring match with a bag-of-content-words comparison. For each topic (and its aliases), check if N% of its content tokens appear within a window of agenda text. Would catch
cemetery+flower+plantingoverlap betweencemetery perpetual care flowersandCemetery Flower Planting. Needs tuning to avoid false positives on generic vocabulary.3. Vector-similarity match against topic embeddings
Embed each topic's name + description + briefing headline into pgvector. Split agenda text into items; embed each; for each agenda item, cosine-match against topic vectors; inject the top matches. Most semantically aware, highest infrastructure cost, but reuses existing
VectorServicestack.4. Hybrid
Start with approach 2 (token overlap) as an additive path beside the current substring match. If that still misses important cases, layer in approach 3. Approach 1 is always available as a manual escape hatch.
Notes
app/jobs/summarize_meeting_job.rbasagenda_topic_context. It's scoped toagenda_previewmode only; full mode (packet / transcript / minutes) does not use topic-context injection yet.generate_topic_summaries.AGENDA_TOPIC_MIN_WORDS = 2,AGENDA_TOPIC_MIN_CHARS = 15,AGENDA_TOPIC_MIN_IMPACT = 3) are tuned to avoid over-matching. Any replacement should preserve the over-match protection.Acceptance criteria (future)
constructionnot injected for every meeting that mentions the word).