tootsies

a discord bot for the tootsies server. ask, recap, discuss, ship features by typing.


Project maintained by mejasonmejason Hosted on GitHub Pages — Theme by mattgraham

Classifier audit: every keyword list, string match and regex that makes a decision

Audit date: 2026-09-18. Owner ask: “audit repo wide where we are using dictionary and string matching and regex for classification and decisions, enumerate exhaustively; we should go one by one and see possibly replace them all with Haiku.”

This document has two parts. Part 1 is the WORKLIST: the sites ranked for replacement, in the order to do them. Part 2 is the INVENTORY: every site found, grouped by file, with the verdict and the reason. The inventory is the exhaustive enumeration. The worklist is the subset worth a PR.

How to read this document

Scope. Every .py file under cogs/, utils/, the repo root, and the runtime scripts (scripts/ops_monitor.py, scripts/axiom_setup.py, the manual mod tools). Tests are excluded. Five audit passes each read one partition of the tree in full. The partitions are the five inventory sections below.

What counts as a site. A regex, a keyword list / set / dict of terms, a substring or prefix / suffix test, or a dict lookup keyed on free text, that decides one of:

A site is SEMANTIC when the input is natural language and the rule approximates a judgment. A site is STRUCTURAL when the input is a URL, a payload key, a number, a date, a filename, a log line, or our own enum. Structural sites are listed but never candidates.

Verdicts.

verdict meaning
HAIKU Replace the rule with a small-model call. The rule stays as the fallback when the model errors.
HYBRID Keep the rule as the fast path or the fence. Add a model call only for the ambiguous residue.
KEEP Leave as code. The reason column says why: structural, money path, safety fence, hot path, privacy, or already behind a model judge.

The rule that decides the verdict is already in docs/ENGINEERING.md, “The deterministic / model-judged boundary”: repeatable behavior lives in code, model judgment is for taste and language, and “don’t hand-code brittle heuristics for a genuine judgment call”. This audit applies that rule site by site.

Line numbers are anchors from the audit date. They drift. The stable reference is the symbol name in each row. Every symbol in the worklist was re-checked to exist on 2026-09-18.

The numbers

Site counts per inventory section. The counts are the audit passes’ own tallies.

section semantic sites structural sites HAIKU HYBRID KEEP
Music, newsroom, identity, feeds ~118 ~110 ~9 ~25 ~84
Markets, betting, sports, media 94 154 9 22 63
Conversation, X, gates, output checks ~80 ~190 ~6 ~12 ~62
Charts, cinema, ops, infra 76 ~110 3 12 61
The 16 remaining files 41 39 1 8 32
total ~410 ~600 ~28 ~79 ~300

About three quarters of the semantic sites stay as code. That is the expected result: settlement math, dedup keys, identity joins with a documented incident, the constitutional fences, the permission allowlist, and the per-message hot paths are all deliberately deterministic.

Cost baseline. Axiom, 7 days to 2026-09-18, claude_api events:

model family calls / week
Haiku 41,069
Sonnet + Opus 1,455
total 42,524

Haiku already carries 97% of the bot’s model calls. Tier A below adds zero calls. Tier B adds cached per-ticker or per-channel calls, in the low thousands per week at most. No item on the worklist changes the cost picture.

Three findings that shape the worklist

1. The biggest keyword surface sits on top of an existing Haiku classifier. classify_music_news (Haiku) already returns kind / subject / claim / figure / cert_id / milestone_id / outcome / source. About 20 regex parsers in utils/music_news.py and cogs/music_news.py then re-parse its claim prose to recover facts the classifier could have emitted as fields: chart key, debut vs re-entry, projection vs settled, threshold vs count, stream-claim shape, market outcome / odds / venue / deadline / award, cert territory, tour / live / non-audio. The same shape appears in markets (market_subject_image re-derives what image_subjects already classified) and in X mentions (asks_question runs beside scan_music_mention). The cheapest fix for these is “add a field to the existing call”, not “add a call”. That is Tier A.

2. The model-output FENCES are the second-biggest class, and most of them stay. utils/output_checks.py, claude_client.is_empty_response, ship_guard, the ungrounded_* number checks and the retrospective arithmetic are deterministic on purpose: each cites a measured judge miss (a career claim the judge scored 0.92, four decline narrations that shipped at 0.72 to 0.78). The number and arithmetic checks stay. The PROSE fences (decline narration, self-correction, cross-source comparison, career / all-time claims) grow one phrase per incident and are the ones a model reads better. Those are Tier B4 and Tier C8, with the regex kept as the free first pass.

3. Identity matching is deterministic by design, and the fix is a rescue rung, not a swap. apple_music, deezer, artist_watch, chart_credits, catalog_art, steam_art, market_art, espn.match_team_logo each carry a documented incident and Codex rounds. A model here re-opens the wrong-face / wrong-cover class. The one measured gap is the UNKNOWN band: 10 of 150 unknown-artist cuts in 30 days were watched acts lost to spelling artifacts, and _title_matches at ratio 0.6 to 0.85 picked the wrong link three times. The pattern that already works is claude.match_album_tracks: deterministic fold first, cheap prefilter, Haiku closed-set confirm on the residue only. That is Tier C.

Part 1: the worklist

Do them in tier order. Within a tier, the order is brittleness times user impact. Each item names the site, the shape of the fix, the added call volume, and the fail direction the replacement must keep.

Every replacement PR carries the same five things. A new method on ClaudeClient following classify_abuse (label) or topic_duplicate (JSON tri-state) with skip_persona=True and the judge temperature; the old rule kept as the fallback when the model errors; the surface’s fail direction preserved (gates fail closed, reads fail open); an event with a purpose= so the ops monitor sees it; and a golden fixture that must keep failing. There is no generic classify(text, labels) helper in the client today. The first PR should add one, with a durable cache keyed by the caller’s id (ticker, post id, channel id), so the rest of the list is a prompt, a parser and a golden each.

Tier A: fold into an existing Haiku call (zero added calls)

# site fix fail direction
A1 (SHIPPED 2026-09-18, #3413) cogs/music_news.py _TOUR_CLAIM_CUES, _TOUR_CLAIM_ANTI_CUES, _NONAUDIO_RELEASE_CUES, _LIVE_CLAIM_CUES, _LIVE_CLAIM_ANTI_CUES, _LIVE_VERB_RE, _played_at_subject, _TOUR_UPDATE_CUES, _is_tour_subject Add event_shape: release / tour / tour_update / live / nonaudio to classify_music_news. Retires five phrase lists, two anti-lists and a subject-anchored regex grown over four owner reports in two weeks. Each miss ships the wrong kicker, the wrong art and a listen link on a concert. Unknown shape reads as release (today’s default).
A2 (SHIPPED 2026-09-18, #3414) utils/music_news.py charts_named, sole_weekly_chart, sole_chart_text, _forecast_near_chart, unsettled_chart_label, named_chart_label, _CHART_PATTERNS, _FORECAST_WORDING_RE Add chart_key (closed set from CHART_SOURCES) and is_projection to the classifier. The regex stays as the tiebreak. A wrong read today becomes absent and the stale gate drops a true story. No key: fall to the regex; still-no-key: web verify (today’s path).
A3 (SHIPPED 2026-09-18, #3415) utils/music_news.py stream_claim (_STREAM_*_RE), entry_count_claim Add stream_shape / rung / count fields on the milestone kind. The regex already produced a false CONFIRM (Codex #2774). No fields: fall to the regex.
A4 (SHIPPED 2026-09-18, #3416) utils/music_news.py figure_is_passed_threshold (_THRESHOLD_CUE_RE + duration / plus regexes) Add count and threshold_passed to verify_music_claim. Five Codex rounds to tell “100 entries” (a rung) from “107” (the count). Absent field: keep the wire figure (today).
A5 (SHIPPED 2026-09-18, #3417) utils/music_news.py market_claim_parts, market_award, market_caption, _MARKET_ODDS_LEAD_RE, _MARKET_VENUE_TAIL_RE, _MARKET_DEADLINE_RE, _MARKET_AWARD_RE, _PROBABILITY_FIGURE_RE Ask the classifier for outcome / odds / venue / deadline / award on the market kind. Five regexes parse the classifier’s own prose today. Missing slot: blank caption (today).
A6 (SHIPPED 2026-09-18, #3418) utils/music_news.py claims_debut, claims_reentry, _DEBUT_RE, _REENTRY_RE (has_debut_cue + its two cues retired: no production caller) Added chart_event: debut / reentry / move / hold / none on the chart kind; music_news.chart_event_of reads it ahead of the regexes at the debut dedup key, the deep-platform exemption, the repost gate and the unconfirmed-debut rewrite. Bounded: a debut / re-entry needs the wording somewhere in the wire, a move / hold can only narrow. ”” (no field): the regexes decide as before.
A7 (SHIPPED 2026-09-18, #3419) utils/x_mentions.py asks_question, _QUESTION_STARTS, _REQUEST_CUES, _REQUEST_RE Added asks_question to scan_music_mention (the same Haiku read). x_mentions.mention_asks reads it ahead of the regex; a true on a wordless text (emoji, a bare link) is bounded back to the regex; no flag falls back to the regex. False: parent not fetched, no_facts (today).
A8 (SHIPPED 2026-09-18, #3420) utils/market_outcome.py names_match, normalize_name, _in_field winner_extract already hands the model the leg labels and takes an INDEX back, so the 6-char prefix matcher was a second matcher that could only fire on a pick the extractor cannot produce. Retired names_match; _in_field is an exact re-check of the closed-slate contract. No index: no winner line (absent, today).
A9 (KEEP, decision on #3421, 2026-09-18) utils/market_subject_image.py detect_media_brand, is_youtube_video_market, is_youtube_daily_chart_market, is_netflix_rank_market, artist_subject_name; utils/market_cards.py is_music_chart_market The premise was wrong: the card build does not call image_subjects before these gates (image_subject runs only at step 3 of the art resolver, for an un-hinted market), so the fix would add a call per card. The detectors read Kalshi’s own fixed series titles and scope a fail-CLOSED suppression gate: a structured source, kept deterministic per the ENGINEERING boundary. Unchanged.
A10 (SHIPPED 2026-09-18, #3422) cogs/discourse.py drop_self_correction at four lanes Measured first: three drafting shapes the regex misses (“no wait, scratch that:”, an Option 1 / Option 2 list, “actually, let me try that again:”) scored 0.78-0.87 with the judge and would have shipped. The discourse_score rubric now carries a SHOWS ITS WORKING clause (0.0-0.2); the regex stays as the pre-judge backstop. Three eval goldens pin it. Low score: skip (today).
A11 (SHIPPED 2026-09-18, #3423) cogs/music_news.py _songstats_verify ("stream" in claim.lower()) _songstats_verify reads mn.milestone_metric(milestone_id) (the classifier’s canonical id) ahead of the claim substring; the substring is the fallback for a story with no id. Same.

Tier B: a new cached Haiku call (low volume, high user impact)

# site fix volume fail direction
B1 utils/market_cards.py hero / outcome cluster: outcome_split and the 232-entry _OUTCOME_VERBS, _field_clause, outcome_subject, named_rank, title_rank, winner_rank, award_slot, split_rank_authority, event_title_split, statement_title, how_much_title, subject_hero; plus cogs/market_drop.py _slot_figure / _RANK_WINNER_TITLE_RE / _kalshi_event_subject, cogs/market_alert.py _ending_soon_subject / _clean_authority, utils/alert_triggers.py outcome_label One call per market ticker returning {hero, caption, subject, outcome, rank, authority}, durable-cached. The regex ladder stays as the fallback. Retires about 25 regexes across four files. A verb outside the registry ships a raw question as the hero today. one per new ticker Model error: regex ladder (today).
B2 utils/markets.py _is_cut_runner_up_event, _names_non_leader_slot, _looks_like_music One call per new event ticker: “is this market about a non-leader chart slot?”. Drops whole event families at discovery today; a false positive is silent. a few per day Model error: keep the event (fail open) and emit.
B3 utils/retrospective.py _FRAMES, is_retrospective, retrospective_frame, states_past_interval Per wire candidate on four desks, cached by post id: {is_throwback, count, unit, year}. Keep retrospective_values / look_back_values arithmetic on the returned fields. The docstring lists the phrasings the anchored regex cannot reach; a miss is the 2026-09-14 incident. per candidate, cached Model error: regex (today).
B4 utils/output_checks.py decline-narration battery (decline_narration_hits), _SELF_CORRECTION_RE / has_self_correction, claude_client._SELF_CORRECTION_MARKERS, utils/wheel.py is_narration / _NARRATION_PHRASES One “finished post, or the model’s working?” judge, purpose="decline_narration", over the composed text. Fail toward drop. Covers three phrase lists that each grow one incident at a time. one per compose Model error: regex battery (today).
B5 utils/markets.py _kalshi_competition_to_sport (_KALSHI_SOCCER_CUES + substring ladder) Per new series ticker, cached. The tag selects the settlement feed and logo host; “football” defaults to NFL today. rare Model error: substring ladder.
B6 utils/curator.py _IMAGE_TOPIC_TERMS, topic_is_image_room, prefers_media; cogs/curator.py mode decision; cogs/curate.py prefers_media call Per channel, cached a day: “image room or link room?” over name + topic + six sample posts. A false negative starves a visual room today (the #backpage bug). per room per day Model error: density ratio (today).
B7 The /ask reference router: utils/reference.py _SOURCES chain, _CHART_INTENT, _TABULAR_CUE; utils/genius.py _DETAIL_INTENT / _SAMPLE_INTENT / _MEANING_INTENT / _LYRIC_INTENT; utils/musicbrainz.py _MUSIC_INTENT; utils/reference_movie.py _FILM_WORDS / _STRONG_CUES; utils/wikidata.py _FACTOID_INTENT; utils/chart_data.py detect_chart One call per reference lookup returning {source, clean_title, year, aspect, chart}. Replaces six regex lists ordered by hand-tuned shadowing rules, and fixes the missing “is the TMDB hit the right title” check. The calling model already decided “this is a reference question”. per /ask tool turn Model error: regex chain.
B8 (SHIPPED 2026-09-18, #3426) utils/sports_stories.py classify_sport (_SPORT_KEYWORDS, about 250 words) Inverted: sports_desk._resolve_sports asks classify_sports_beats for EVERY story (cached 48h by signature, 72 new per pass in calls of 24); the handle map and the keyword table decide only a story the model did not place. Measured on 951 live stories: the table left 175 of 309 unknown vs the model’s 116, was wrong on all 6 disagreements, and the handle map was wrong 23 of 515 (WNBA on @nba). about 40 cached per slot Model error: regex.
B9 utils/kworb.py is_functional_audio (_FUNCTIONAL_AUDIO_RE + stream-shape gate) Keep the regex as the bulk filter over 1000 rows; run Haiku only on rows that enter the rendered top-N of a daily-ranked board. A miss ships white noise at #1 to X. about 20 per fresh fetch Model error: regex.
B10 utils/markets.py kalshi_channel_routing (_MUSIC_CUES, _CINEMA_CUES, _TV_CUES, _POP_CUES) Per channel, cached until rename. A channel without a cue word is dark today. per channel Model error: cues.
B11 utils/markets.py qualify_platform_metric (_PLATFORM_PATTERNS, nine platforms) Per event ticker, cached. A new platform ships an ambiguous “Views” title. per new ticker Model error: no rewrite (today).
B12 utils/banger_lane.py search_terms (_STOP, _QUOTED_RE, caps-run heuristic) Entity extraction from a headline: “which names should we search X for?”. a few per day Model error: regex.
B13 utils/luminate.py metric_from_text (ALBUM_METRICS), parse_subject (_SUBJECT_RE) One call returning {artist, release, metric} on the no-cue and two-cue residue (#3008, the Tyla / Ariana class). per Kalshi event Model error: cue list.
B14 utils/perplexity.py is_hedged (_HEDGE_MARKERS) as used by claude_client._has_perplexity_grounding “Did this answer find anything?” on the answer text. Gates the forced web-search retry. one per room post using Perplexity Model error: phrase list.
B15 utils/reference.py _SUBPAGE_HINT, _ASPECT_SYNONYMS, _aspect_weights, _relevant_subpage A pick_*-style call over the subpage titles given the question. Untested today. Folds into B7. per /ask reference Model error: main page.
B16 utils/riaa.py display_title recasing (_INITIALS_RE, _SMALL_WORDS, _ROMAN_RE, …) One recase call over a card’s rows. Rules print SZA as “Sza” and DNA. as “Dna.” on public cards. per card Model error: rules.

Tier C: hybrid rescue on the ambiguous band only

# site fix fail direction
C1 utils/artist_watch.py lenient_tier, s_variants, _ALIASES / canonical_credit; utils/music_news.py wire_spelling, grounded_subject, artist_grounded_in_wire, corrected_name Haiku “is X one of [these candidates]” ONLY on an UNKNOWN verdict that is about to CUT a story (about 5 per day). The hot bulk loop stays deterministic. 10 of 150 unknown cuts in 30 days were watched acts. Model error: cut (today).
C2 utils/apple_music.py _title_matches, _artist_matches, _credit_similar; utils/deezer.py album_title_matches Keep as prefilter. Closed-set Haiku confirm on the 0.6 to 0.85 band or when more than one row hits. Template: claude.match_album_tracks. Incidents: Fukk Sleep, Cinderella, Slime Language. Model error: no link (absent).
C3 utils/release_type.py is_side_version (_SIDE_MARKERS, _BARE_TRAIL_MARKERS, _FEAT_RE, …) Keep on bulk rows. Haiku “is this a re-cut of an existing song?” only on the announce paths (release board, new_entry debut), about 10 to 20 titles per slot. Six consumers share the one list (Codex #2455). Model error: regex.
C4 utils/market_subject_image.py take_references_leg and cogs/market_drop.py fabrication-gate routing Regex as the pre-check, the existing Haiku market_take_names_leader on a “no”, and run the judge for every chart family (two get one today). Token folding drops a correct paraphrase (“Abel” for “The Weeknd”). Model error: drop (fail closed, today).
C5 utils/music_policy.py _IN_HOUSE_TAGS / in_house_genre Keep the exact allow list as the owner chose. Haiku only on OFF-list tags with artist + title + tag. Turns a silent skipped slot into a graded decision, a handful of calls per day. Model error: refuse (today).
C6 utils/chart_ages.py _matching_date Haiku confirm on the chosen MusicBrainz group vs the row. Containment both ways plus earliest-date pick is the “1962 cover date on a 2026 single” shape the module promises never to print. Up to 15 paced lookups per build. Model error: undated (absent).
C7 cogs/music_releases.py _REISSUE_RE Regex as the cheap yes. Haiku on titles carrying Deluxe / Edition / Version / Live / Expanded. Model error: omit the stat.
C8 utils/output_checks.py has_career_claim, has_alltime_claim, ungrounded_career_ordinals, ungrounded_ranking_claims, has_cross_source_comparison, metric_foreign_word Regex as the trigger, the existing qualifier_verify judge as the arbiter with the source block. The source-name lists drift as sources are added. Model error: drop (today).
C9 utils/music_markets.py title_names_act Haiku “is this market about X?” on the token hits (about 70 per day). The capitalised-word mononym heuristic is a semantic guess. Model error: skip.
C10 utils/music_news.py chart_cue_strength slate ordering, chart_reportable unnamed fallthrough, cert_reportable territory prose, _RUNG_CLAIM_RE / quotes_ladder_rung Slate: one “rank these 40 headlines by numbers-relevance” per slot. The others: fields on the classifier (A2 / A5 shape). Model error: cues.
C11 utils/kworb.py find_video_match non-unique picks; utils/billboard.py find() with more than one match; utils/riaa.py _NOT_AN_ACT unresolved cells; utils/riaa_boards.py, utils/cinema_numbers.py _clean_news; cogs/discourse.py looks_like_sports; utils/retrospective.py frames_as_past Each: Haiku only on the ambiguous case the regex already flags. Model error: today’s path.
C12 utils/wikidata.py _MUSIC_OCCUPATIONS / is_music_act unlisted classes; utils/artist_watch.py first_credit_party art-miss path, _PLACEHOLDER_CREDITS Rescue rung on the miss side only. Model error: absent.

Tier D: deterministic fixes found on the way (not model work)

These are bugs the audit found in KEEP sites. Each is a small code fix.

site fix
utils/billboard.py _lead_credit + _history_slug “Earth, Wind & Fire” folds to “Earth”, so a solo act “Earth” can take EWF’s slug. Require a full party via artist_watch.credit_parties.
cogs/cinema_desk.py budget lookup + utils/tmdb.py search_movie The first TMDB hit is used unverified. Pass the year and check title_matches, the guard omdb.by_title already has (the Moana incident).
utils/api_sports.py _team_logo_from_rows “First row with a logo” ships a guessed crest. Return None on no exact match.
utils/radio.py standing A second substring matcher beside the house identity (song_key / fold_tokens). Wire the home in.
utils/riaa_boards.py _BRACKET_RE Drops “(Taylor’s Version)”. Allowlist identity-bearing parentheticals.
utils/kworb.py find_chart_entry loose mode ‘Future’ matches ‘Future Islands’. Callers stay on strict.
utils/markets.py detect_league direct use in sgo_snapshots Confirm every direct call sits behind the Haiku rung; else a “cowboys” query defaults to NBA.
utils/kalshi_ladder.py companion pairing A Kalshi title-template change breaks the pairing silently. Add an event.

Deliberately not candidates

Checked and kept deterministic, with the reason:

Part 2: the inventory

Five sections, one per audit partition. Each is the audit pass’s full report. Tables carry file:line | pattern | input | decision | fail direction | tested? | verdict | reason. “tested?” is a grep of tests/ for the symbol name: a count or yes / no. It says a test names the symbol, not that the brittle case is covered.


Inventory: music, newsroom, identity, feeds

Every file in the assigned scope was read end to end (53,569 lines). No file was modified. Columns: file:line | pattern | input | decision | fail direction today | tested? | verdict | reason. “Model nearby” notes name an existing claude.* / judge call in the same flow. Structural sites are one compressed table per file.

Three cross-cutting findings up front:

  1. The newsroom’s biggest keyword surface sits ON TOP OF an existing Haiku classifier. classify_music_news (Haiku) already returns kind / subject / claim / figure / cert_id / milestone_id / outcome / source. About 20 regex parsers in utils/music_news.py + cogs/music_news.py then re-parse its claim prose to recover facts the classifier could have emitted as fields (chart_key, debut reentry move, is_projection, threshold_passed, stream claim shape/rung/count, market outcome/odds/venue/deadline/award, cert territory, tour live nonaudio). Most of the top-10 below are “add a field to the classifier, retire the regex”, not “add a call”.
  2. Identity matching (apple_music / deezer / artist_watch / chart_credits / catalog_art) is deterministic by design and each matcher carries a documented incident + Codex rounds. Verdict everywhere is KEEP, with one shared HYBRID: a Haiku closed-set confirm only on the AMBIGUOUS band (ratio 0.6-0.85, or an UNKNOWN-tier verdict that is about to CUT a story). claude.match_album_tracks already does exactly this shape for tracklists and is the template.
  3. Keyword sites just OUTSIDE this scope that these files depend on (flag for the owner’s other audit): utils/output_checks.py (has_career_claim, has_decline_narration, has_alltime_claim, has_row_arithmetic, attribution_tag_hits, ungrounded_* , metric_foreign_word, album_metric_named), utils/ship_guard.py (drop_self_correction, drop_decline_narration), utils/retrospective.py (throwback frame regex on wire text AND on the composed take), utils/perplexity.py (is_hedged, strip_industry_projection), utils/luminate.py (metric_from_text, release_stage), utils/music_policy.py (in_house_genre), utils/chart_boards.py (genre_group_for_apple / confirmed_genre_group, title match for stream totals), utils/starboard.is_embed_fixer.

A. Newsroom

utils/music_news.py (4,680 lines) – 41 semantic sites, ~14 structural

Model nearby for every row: claude.classify_music_news (Haiku) upstream; claude.verify_music_claim judge + music_desk_score downstream. Volume: <=12 classified stories/slot, ~15 slots/day; the prefilters run on every polled wire post (~40 handles x ~20 posts).

file:line pattern input decision fail direction tested? verdict reason
music_news.py:92 _TRUSTED handle registry + may_relay_number wire handle may an unverified number ship in her voice miss: wire treated as verify-or-drop yes KEEP editorial trust config
:234/:267 _NUMBER_CUES looks_like_numbers_story wire tweet text admit to paid Haiku classify wide by design; false hit costs one Haiku yes KEEP it IS the prefilter for the model
:287/:326/:208 _CHART_CUES chart_cue_strength strength_buckets wire text ORDER the culture slate and DROP zero-strength posts (admission) measured false hits (“A$AP” via $); real stories outside the top-12 never classified yes HYBRID a one-shot Haiku “rank these 40 headlines by numbers-relevance” per slot replaces cue counting; keep cues as fallback
:370-480 KEPT_PATTERNS/CUT_PATTERNS chart_reportable wire text + classifier claim fail-CLOSED chart allowlist (“unnamed” dropped) miss: true story dies as unnamed (~2% measured); 4 review rounds on adjective leaks yes HYBRID keep deny-first regex; Haiku “which chart family” only for the unnamed fallthrough, or a classifier chart_key field
:674-687 _DEBUT_CUE_RE _REENTRY_CUE_RE has_debut_cue wire text gate the career-entry ordinal clause fail closed (no clause) yes RETIRED (A6, #3418) had no production caller; the ordinal gate reads _debut_confirmed
:698 career_clause_in_line composed take (model output) telemetry: did the clause ship none yes KEEP telemetry
:736-1006 _fold identity vs Billboard chart-history rows (entry_ordinal_fact, peak_history_fact, chart_run_fact, is_latest_entry, history_debut_confirmed) Billboard page + classifier subject grounded career fact / debut confirmation miss: no clause (absent) yes KEEP deterministic numbers off first-party page
:1015 corrected_name SequenceMatcher>=0.85 + first letter wire vs model-resolved name accept a spelling fix false hit: rename (guarded) yes KEEP cheap guarded proxy
:1069 wire_spelling n-gram search + _POSSESSIVES classifier artist vs wire text re-spell the artist 10 wrong cuts/30d documented from this family (“Karol G y Bruno Mars”, “The Weeknd’s”) yes HYBRID ask the classifier to quote the artist VERBATIM from the wire; keep regex as backstop
:1139/:1188/:1204 named_by_mention, artist_grounded_in_wire, grounded_subject whole-word grounding classifier artist vs wire text + @mention map drop artist to bare title (prefer absent) miss: handle/display-name forms read as ungrounded yes HYBRID same family; Haiku “does the wire support X as the artist”
:1268/:1293 _RUNG_CLAIM_RE quotes_ladder_rung classifier claim trigger live Kalshi/Polymarket re-read instead of relaying a % verb list grew after misses (reach/hit); miss = stale rung relayed yes (quotes_ladder_rung) HAIKU “is this % a threshold-rung quote” is semantic; market kind only, low volume
:1312 _RANGE_FIGURE_RE novelty_is_distribution classifier figure+claim drop a distribution as novelty shape test yes KEEP shape
:1367 _RANK_WORDS figure_is_rank_word; :1428 collapse_rank_run; :1486 lead_figure; :1536 split_figure_block; :1589 figure_is_phrase classifier/judge figure string what heroes the card each born from an incident; miss = bad hero yes KEEP cheap post-processing of the model’s own field; alternative is the classifier prompt  
:1635-1719 _THRESHOLD_CUE_RE + duration/plus regexes figure_is_passed_threshold wire text + figure is the figure a passed RUNG (swap for research count) 5 Codex rounds; miss = “100 entries” carded for 107 yes HYBRID/HAIKU “count vs threshold” is a semantic read; a threshold_passed field on the verify judge replaces it
:1753 count_past_threshold judge output pick the researched count parse yes KEEP model-output parse
:1806-2062 _MARKET_ODDS_LEAD_RE, _MARKET_VENUE_TAIL_RE, _PROBABILITY_FIGURE_RE, _MARKET_DEADLINE_RE, _MARKET_AWARD_RE (+particles), market_caption (_DANGLING_TAIL_WORDS), market_claim_parts classifier claim prose card slots: outcome / odds / venue / deadline / award each a Codex/owner round; miss = wrong headline or blank caption yes HYBRID a second parser over a model’s prose; ask the classifier for these five fields
:2147-2286 _CERT_CONJ_RE, _ARTIST_FEATURE_RE, _TITLE_FEATURE_RE, _release_identity, _cert_release_identity, _cert_identity_forms, cert_story_keys, milestone_story_key, chart_debut_story_key classifier subject + ids permanent dedup keys no key -> visible dup (not silent) yes KEEP exact settled-event keys must be deterministic; model already supplies ids
:2310 _LATIN_PROGRAM_RE claim suppress units clause for Latin program miss: wrong unit count no KEEP tiny
:2477/:2486 _OUT_OF_SCOPE_CERT_PROSE_RE cert_reportable cert_id body, else claim+post prose (“in ", SNEP/ARIA...) drop non-US/UK cert (fail open) miss: foreign cert ships yes HYBRID classifier already emits body; add territory field, drop prose regex
:2681/:3072-3083 _REENTRY_RE _DEBUT_RE claims_debut claims_reentry wire text / claim exempt a deep platform position; repost gate wide on purpose; code comment says “explicit classifier re-entry field is the robust follow-up (#2523)” yes SHIPPED (A6, #3418) classifier chart_event field, bounded by these regexes’ wording families (chart_event_of)
:3095-3162 named_chart_label, _APPLE_*_RE, _forecast_near_chart, unsettled_chart_label, sole_chart_text (+_AIRPLAY, _BILLBOARD_CATCH_ALL, _ON_BILLBOARD_RE) claim / post prose which chart tags the card when unsettled multi-round (#3090) yes HYBRID closed-set chart_key from the classifier
:3213 _FORECAST_WORDING_RE claim/wire treat as projection (no chart label) miss: projection tagged as chart no (indirect) HYBRID classifier is_projection
:3421-3540 _CHART_PATTERNS (+_FLAGSHIP_PAIR_RE, _US_QUALIFIER_RE 3 rounds, _SPOTIFY_GLOBAL_RE) charts_named sole_weekly_chart claim text WHICH live chart settles the claim; nested-name ordering “is the whole design” miss: web verify instead of live settle; false hit: wrong chart read -> absent -> stale drop yes HYBRID closed-set Haiku classification to CHART_SOURCES keys as primary; regex as tiebreak
:3554-3618 _fold, _FEATURE_RE/_strip_features/lead_act, _CREDIT_SPLIT_RE, _artist_agrees subset rule chart rows vs classifier subject does a chart row = the wire subject absent on a title-spelling miss -> stale gate can drop a true story yes KEEP (HYBRID-lite) identity; a Haiku “is any row this record” on the absent reason would rescue spelling misses
:3648/:3654/:4235 _TOP_N_RE claim_top_n; _CHART_KIND claim_chart_kind songs/albums noun claim disambiguate top-N count chart miss: ambiguous refusal yes HYBRID classifier field
:3732/:3739 _PROJECTION_CUES names_projection claim (+kind) settle against HITS doc patches classifier misses yes HYBRID classifier kind=first_week already exists; cues are the patch
:3975-4041 _STREAM_METRIC_RE _SPOTIFY_RE _STREAM_COUNT_RE _STREAM_SCOPED_RE _STREAM_RUNG_RE stream_claim claim (catalog/track/career, rung, count) to settle off kworb Codex #2774: a false CONFIRM shipped yes HAIKU textbook JSON extraction from one sentence; regex already produced a false TRUE
:4345/:4348 _ENTRY_COUNT_RE entry_count_claim claim career-entry count board same family yes HYBRID same
:4249-4631 resolve_chart_position / resolve_artist_count / recover_chart_artist / resolve_stream_* chart rows vs subject live settle joins reason codes (ambiguous/absent/unresolvable) yes KEEP identity joins
:4549 _ARTIST_PREFIX_SEPS strip_artist_prefix subject headline formatting yes KEEP formatting

Structural (compressed): _PULL_DATE_RE:531 (model date), _CLAIM_POS_RE:3080, _BATCH_FIGURE_RE:2390, _valid_cert_id/_valid_milestone_id, _TITLE_SMALL_WORDS casing, humanize_iso_dates, CHART_SOURCES table, _CERT_LADDERS, _TIER_WEIGHTS, parse_chart_mark/format_chart_mark, _TOP_RUNG_RE.

cogs/music_news.py (6,333 lines) – 7 own semantic groups + ~25 delegations, ~8 structural

Model nearby: classify_music_news, verify_music_claim, is_stale_recap (Grok), compose_music_record/compose_market_drop, music_desk_score, topic_duplicate, resolve_desk_image_subject.

file:line pattern input decision fail direction tested? verdict reason
cogs/music_news.py:617 _is_tour_subject (“tour” word in title) classifier subject photo-first art, no cover, no Deezer miss: tour borrows an album cover yes HAIKU (family) see next row
:692/:704/:819 _TOUR_CLAIM_CUES + _TOUR_CLAIM_ANTI_CUES _claim_is_tour classifier claim re-kind a release as TOUR (kicker, purple tag, photo, no listen link) owner report 2026-09-01 (Kanye concerts shipped as album) yes HAIKU phrase list; a classifier event_shape: release|tour|tour_update|live|nonaudio field retires four lists
:728/:735 _NONAUDIO_RELEASE_CUES _claim_is_nonaudio claim refuse music link for film/doc/merch miss: Eras Tour film links the album yes HAIKU (family) same
:741 _played_at_subject (regex built from subject title after on/at/during, _LIVE_VERB_RE) claim + subject live-moment detection the phrase list cannot reach 2026-09-15 LE SSERAFIM / BMI dinner reports via _claim_is_live HAIKU (family) already a hand-built approximation of a semantic read
:782/:908/:929/:941 _LIVE_CLAIM_CUES + _LIVE_CLAIM_ANTI_CUES + _LIVE_VERB_RE _claim_is_live claim “live” kicker, guest’s photo, no link grew on 3 owner reports; each miss = wrong kicker + wrong art + link on a concert yes HAIKU strongest cog-level candidate; ~<=6 release stories/slot
:897/:985 _TOUR_UPDATE_CUES _tour_kicker claim “tour - update” vs “tour - announcement” miss: wrong kicker yes HAIKU (family) same field
:2328 kind=="milestone" and "stream" in claim.lower() _songstats_verify claim spend a metered Songstats slot inconsistent with #2352 rule “never regex the claim” yes SHIPPED (A11, #3423) reads mn.milestone_metric(milestone_id) first; the substring is the no-id fallback
:1590 head.upper()=="NONE" + is_hedged Perplexity answer drop pull answer protocol yes (indirect) KEEP model-output protocol
:236 _artistpull_desk_owns kind + milestone_metric != "units" classifier fields drop desk-owned pull story fixed-key yes KEEP fixed-key
:516/:538 _names_the_collection (normalize+equality) _listen_link catalog row vs subject song page vs album page link Codex #3206 “Love”/”Love Yourself” fixed by identity test via _listen_link KEEP identity
:634 _row_is_a_different_record -> states_past_interval (retrospective, out of scope) catalog date + claim refuse old cover on a “new release” card throwback carve-out yes KEEP date math + out-of-scope regex
:1157 _recent_release date window + copyright_year reissue lag Apple rows which album is a fresh drop numeric yes KEEP numeric
:2050/:2097/:2162 _chart_in_scope -> chart_reportable; _cert_in_scope -> cert_reportable; _artist_in_tier -> lenient_tier + corrected_subject_from_wire + _recover_bare_artist claim+post scope cuts (stamped seen) see utils rows yes HYBRID (delegated) see utils/music_news + artist_watch
:2611-4826 delegations: novelty_is_distribution, claims_debut (x3), claims_reentry, sole_weekly_chart, charts_named, sole_chart_text, claimed_position, quotes_ladder_rung, figure_is_*, count_past_threshold, is_probability_figure, threshold_metric, poly_event_matches, claim_top_n, claim_chart_kind, entry_count_claim, stream_claim, names_projection, unsettled_chart_label, cert_batch_count, catalog_count_tag, market_award/market_claim_parts/humanize_iso_dates classifier claim the whole settle/card pipeline see utils rows yes (see utils) verdicts above
:3858 kind in _RECORD_LANES classifier kind which compose prompt fixed-key yes KEEP fixed-key
:5256 src != "riaa" data_source plaques-board authority fence fixed yes KEEP fixed

Structural: _split_subject:604 (“ – “/” - “ protocol), _COLLECTION_TYPE_SUFFIX_RE:513, _album_link:494 URL param strip, src[:4]=="the " :5655, _KIND_ACCENT_KEYS/_KIND_NOUNS/_tag_accent, _FEED_PLATFORMS, _first_party_rank registry, _rotate_window PRNG, _artist_new_release edition-collapse alnum key.


B. Identity / credit matching

utils/artist_watch.py – 9 semantic, ~4 structural (hot bulk loop over every chart row every slot)

file:line pattern input decision fail direction tested? verdict reason
artist_watch.py:46/63/66 _CONNECTORS, _POSSESSIVE_RE, fold_tokens (“$”->s, drop “the”) chart credit / wire subject normalized identity key yes KEEP bulk, deterministic
:109/:161/:187 _names_a_credit, Watchlist.contains, recognition_tier credit WATCHED/KNOWN/UNKNOWN -> floors and cuts miss: watched act cut as unknown yes KEEP segment-boundary match; fail-open on empty list
:222/:238 s_variants, lenient_tier two credit spellings rescue trailing-s / raw-vs-corrected measured 10/150 unknown cuts were watched acts yes HYBRID Haiku “is X one of [candidates]” ONLY on an UNKNOWN verdict that will CUT a story (~5/day)
:290 _ALIASES (Ye, Hannah Montana, Pink…) canonical_credit credit alias -> canonical miss: alias gap yes HYBRID (same rescue) alias gaps are what a model knows
:385-671 _PARTY_SPLIT_RE, _PARTY_SPLIT_AND_RE, _LIST_SHAPE_RE, is_solo_credit, credit_parties, lead_party, first_party (corroborated against known list) credit split joint credits for counting/grouping absent over invented; residual “Sam and Dave” accepted yes KEEP auditable; Codex #2618
:608/:613 _FEATURE_CLAUSE_RE lead_artist credit art lookup lead yes KEEP feat parse
:718-729 _JOINT_CREDIT_RE + _ARTICLE_TAIL_RE first_credit_party (“+ the …” heuristic) credit art-miss fallback party semantic guess on articles yes (first_party) KEEP (HYBRID-lite) rare per-card; a Haiku “one band or two acts” only on the art-miss path
:752/:755/:896/:935 _FEATURE_MARKER_RE featured_credit, _TITLE_WITH_RE title_feature, row_credit credit / title is the row theirs; feature parse yes KEEP feat parse
:809 _PLACEHOLDER_CREDITS is_placeholder_credit credit skip portrait lookup miss: “Cast Recording” gets a portrait search no KEEP (HYBRID-lite) tiny list, prefer-absent
:1133 chart_stories rung logic, _STORY_WHAT/_STORY_EYEBROW our kinds numeric yes KEEP numeric/fixed-key

utils/apple_music.py – 11 semantic, ~5 structural (busiest identity hub, hundreds of resolves/day)

file:line pattern input decision fail direction tested? verdict reason
apple_music.py:197 _EDITION_NOISE search term strip noise no KEEP structural-lite
:221-298 _TITLE_TAIL_NOISE, _ROMAN_VALUES, _sequence_marker, sequence_mismatch title pair installment guard (Slime Language 3 vs 2) yes KEEP deterministic guard fuzzy cannot do
:320/:363 _normalize_title + _title_matches (substring>=3 OR ratio>=0.6; non-Latin = mismatch) model/chart title vs iTunes trackName which link/cover ships false hit: wrong link/cover (Fukk Sleep, Cinderella dog, Slime Language incidents) yes HYBRID keep as prefilter; Haiku closed-set confirm on the ambiguous band (0.6-0.85, multiple hits) – claude.match_album_tracks is the template
:407-546 _norm_artist, _FEAT_CREDIT_RE/_PAREN_ZONE_RE _featured_credit, _BILLING_SEPARATOR_RE _lead_credit, _credit_similar (ratio>=0.8 / word-sorted), _artist_matches, _primary_artist_matches credit vs result artistName homonym guard false hit: tribute/karaoke row yes HYBRID (same band) same
:578 pick_apple_music_url artist_present / title-only fallback logic results link choice documented yes KEEP logic
:935 _is_single_or_short (endswith “- single” or <=2 tracks) collection album vs single no KEEP structural-lite
:1411 _exact_artist_row exact normalized name rows discography row miss: no row no KEEP exact by design
:1547 _pick_album fold containment rows album row yes KEEP identity
:1861/:1917 _BAD_VERSION_RE _is_guessable_title (no lead vocals, live, cover, spanish version…) title drop from /guess pool miss: a bad version in the game yes KEEP bulk loop per chart row
:1874 _ALT_VERSION_RE _is_clean_original / targets_alt_version title prefer clean original preference no KEEP preference
:1898/:1907 _COMPILATION_RE + “various” in collectionArtistName _is_compilation collection prefer own album art over comp miss: generic comp cover ships yes KEEP (HYBRID-lite) preference not filter

Structural: copyright_year:690 (℗ parse), apple_music_id URL, breaker/limiter.

utils/deezer.py – 8 semantic, 2 structural

file:line pattern input decision fail direction tested? verdict reason
deezer.py:456-501 _TITLE_STOPWORDS, _norm_title, _core_title, album_title_matches (>=60% token overlap + sequence_mismatch) album row vs request release date / genres / top track fuzzy yes KEEP (HYBRID same band) same as apple_music
:556-581 _initialism, _BILLING_SEPARATORS, _split_billing, _credit_matches_contributors (subset / corroborated initialism; BTS vs “Behind The Scenes”) contributors accept cover as VERIFIED (media-gate exempt) high stakes; 4 Codex rounds yes KEEP auditable; exemption from the vision gate must stay deterministic
:673 album_cover / _cover_from_rows / _cover_from_artist_albums core-title equality + token subset rows which cover yes KEEP identity
:980/:992 _EXACT_MIN_FANS + artist_picture_match token overlap + fan rank artist rows portrait + exact flag vision judge downstream (x_crosspost) yes KEEP numeric-backed
:1193/:1204 _artist_overlaps (60%), _artist_is_billed credits identity yes KEEP identity
:1328 _is_varied playlist validation numeric yes KEEP numeric

Structural: _is_real_deezer_image:86 md5 sentinel + URL regex.

utils/chart_credits.py – 2 semantic

chart_credits.py:113 _artist_matches fold containment either way chart credit vs Genius primary artist accept/reject Genius credits false hit: a cover’s credits attach; miss: verified-miss cached 7d yes KEEP (HYBRID-lite) <=20 lookups/build
:208 credit_matches (artist_watch) credit drop self-credit rows yes KEEP identity

utils/catalog_art.py – 0 own semantic; delegates

is_blank_image:70 pixel spread, ad_banner_reject:118 geometry (numeric; docstring explicitly rejects a vision judge, measured) KEEP. _pick_artist_row:255 multi-artist ambiguity guard, _cover_credits, _rank_rows, _row_title_matches -> apple_music matchers KEEP.

utils/names.py, utils/aliases.py – 3 semantic (identity of a person in prose)

names.name_pattern/name_in_text:644-700 and AliasIndex.users_in (Discord prose -> which member is mentioned; drives /forget and attribution): KEEP, privacy-load-bearing, must be deterministic. _EP_MARKER_RE:596 strip “(EP)” for identity join KEEP. KEEP_CAPS:732 acronym casing structural.

utils/song_pool.py – 0 own keyword sites

song_key:39 identity (apple_music normalizers) KEEP; recognizable:84 numeric + watchlist KEEP; genre->chart dicts (DEEZER_GENRE_CHART, RSS_GENRE_IDS, GENRE_BUNDLES) fixed-key routing on a menu value KEEP.

utils/hits.py – 1 semantic, ~8 structural

projection_row:831 forgiving SUBSTRING title/artist match over _norm_name (which HITS row a debut card reads; false hit “Views” in “Views From the 6”) KEEP, low volume. find_upcoming:581 exact match KEEP. Structural: _to_int, _COLUMN_FIELDS label->field, _spins_agree, _slash_date, TSV parsers, decap_title:885 caps formatting, benchmark_key, is_new_entry flag.

utils/music_markets.py – 4 semantic, ~6 structural

music_markets.py:249-368 pick_album_art_url (artist substring either way + _title_matches by subject_kind), _is_full_album (“- single”/”- ep”), pick_artist_album_art_url (“deluxe” tiebreak), pick_any_art_url (no name check, last rung) iTunes rows vs subject which cover ships last rung ships any art yes (2 of 4) KEEP vision media gate downstream
:824 _subject_tier_hits token-set tiers (unique hit only) wire/mention text vs Kalshi subjects which ladder answers ambiguous -> none no KEEP identity
:1173 _FIELD_LABELS regex on Kalshi market TITLE upstream title label “a #1 song”/Grammy X wrong label in context no KEEP (could be HYBRID) closed set, cheap
:1342 related_market_context _artist_key substring (>=5 chars) subject vs market titles cross-market odds line yes KEEP identity
:1927 title_names_act (fold run + capitalised-word mononym heuristic “Drake Baldwin”) Kalshi event title new-listing lane for a watched act false hit: unrelated market posts as artist yes HYBRID capitalisation is a semantic guess; Haiku “is this market about X” on ~70 title-token hits/day

Structural: _DEBUT_TICKER_DATE_RE:617, _RELIST_SUFFIX_RE:672, _SALES_PAIR_METRICS:653 via luminate.metric_from_text (out of scope), "::" subtitle split, "PUREALBUM" in ticker / "stream" in metric :1489/:1587, ARTIST_MARKET_SKIP_SERIES startswith :1966.


C. Release type

utils/release_type.py – whole module semantic (7 patterns), 0 structural

release_type.py:34/116/74/83/98/105/201 _SIDE_MARKERS, _BARE_TRAIL_MARKERS, _FEAT_RE, _BARE_FEAT_RE, _TRAILING_DASH_RE, _FORMAT_TAG_RE, _RADIO_EDIT_RE -> is_side_version / is_side_version_beyond_radio_edit track title (Apple/Deezer/kworb/radio feed) suppress a release/debut post; down-rank a Billboard move; hold out of presence memory false neg: a remix posts as new music (the “Mercy (Live)” motivating post); false pos: a real release suppressed yes HYBRID Codex #2455 saga shows the list is brittle; keep as prefilter on bulk rows, Haiku “is this a re-cut of an existing song” only on the ANNOUNCE paths (release board, new_entry debut) which are ~10-20 titles/slot

Consumers: release_board.py:611, music_alert.py:1445/1463/1764/1858/1876, music_desk.py:2549/4858/4865/7321/7836, music_news.py:4621.

utils/release_board.py – 0 semantic; dedup on (artist.lower(), title.lower()) :619, chart-pos join :658, _parse_date structural.


D. Desk / alert cogs

cogs/music_desk.py (10,547 lines) – 0 own keyword lists; ~12 delegations; ~20 structural

file:line pattern input decision fail direction tested? verdict reason
music_desk.py:2501/2513/2568/2589/6606/6719/7007/7018/7499/7692, :6954 _is_recognized wl.contains / known.contains / recognition_tier chart-row credit notability floors, rank caps, exits, sweeps fail-open on empty list yes KEEP list membership; see artist_watch HYBRID rescue
:2549/4858/4865/7321/7836 is_side_version row title down-rank / hold out of presence / drop new_entry see release_type yes HYBRID (delegated)  
:9318/:9473 _drop_before_gate/_unshippable has_cross_source_comparison, has_self_correction, has_career_claim, has_decline_narration, has_row_arithmetic, ungrounded_row_tally, has_alltime_claim, has_attribution_tag keyed by _BARE_ROW_LANES:9236 _POSITION_LANES:9245 _BOARD_LANES:9249 _ALLTIME_EXEMPT_LANES:9284 composed take (model output) shape reject before the judge each cites a measured judge miss (career claim scored 0.92) yes KEEP deterministic backstops behind music_desk_score; regexes live in out-of-scope output_checks
:3513 _detag_credit; :9339 _drop_ungrounded; :9380 _reask_ungrounded; :9187 metric_foreign_word; :8590 _drop_for_ungrounded_ranking output_checks regexes take drop / re-ask measured yes KEEP same
:10397 _sched_dedup_history album_metric_named(line) + metric_from_text our own past posts exempt other-metric line from Haiku topic_duplicate doc: prompt lever measured 0/5 yes KEEP deterministic by measurement
:2184 is_hedged, strip_industry_projection (perplexity, out of scope) research text drop / strip yes KEEP-lite  
:5091 _genre_debut_groups cb.genre_group_for_apple + cb.confirmed_genre_group (chart_boards, out of scope) Apple primaryGenreName + Deezer genre names genre board membership two-source confirm; B’Day incident yes KEEP structured tags
:8205 _kworb_career_row kworb_fold exact name board rows career rung identity exact by design (#2546) no KEEP  
:7593 _lead_act -> aw.lead_party; st.known_acts/credit_parties; cb.presence_leaders; aw.credited_names :3978/4161/4208/4248 credits grouping / attribution corroborated split yes KEEP    
:8755 mq.on_chart(qual_source, scope) verified qualifier text drop off-chart qualifier Steve Lacy fusion, 73 gate fails yes KEEP tiny substring
:9634/:6033 mn.is_latest_entry, entry_ordinal_fact, projected_entry_fact Billboard page career clause absent on miss yes KEEP  

Structural: r.metric.lower() != _SETTLED_UNITS_METRIC :6209/6231; sales_metric_key()=="pure sales" :2003/2034; release_stage(...) :2000/2225/8413/8956 (luminate, out of scope); row.format.upper()=="ALBUM" :4282; " -- " subject splits :2257/8271/9896/9930; slug keys re.sub(r"[^a-z0-9]+","-") x13; startswith(prefix)/endswith(today) rotation reads :2082/4654/4934/5239/9305; re.sub(r'(?i) chart$','') :7542; fixed lane sets (_CHART_CARD_STORIES, _CHANGE_GATED_BOARD_KINDS, _STORY_EXPERIMENT, _LANE_PRIORITY, …).

cogs/music_alert.py (2,987 lines) – 1 semantic, ~6 structural groups

music_alert.py:417 _ARTIST_MARKET_NOT_ACTS {“disney”,”various artists”,”soundtrack”} Kalshi market subject refuse artist-market drop for a non-act miss: “Original Broadway Cast” gets an artist drop no KEEP (ride title_names_act HYBRID) 3 items

Structural: _RANK_FIGURE_RE:311 (#\d+ on classifier figure), _DAY_ON_CHART:267, _CROWN_LANES:383 (skips Haiku topic dedup per #2989), "stream" in metric :787/1587/2796, lane.startswith(...) on our own lane names :2431/2520/2598, evt.get("sport") flags. Delegations: is_side_version* x5, title_names_act x3, recognition_tier/debut_clears_depth, match_album_tracks (Haiku) :949.

cogs/music.py – 0 semantic

_MUSIC_LINK_HOSTS:97/_has_music_link:145/_apple_url:151 URL structural; drop_song_key:116 “ - “ partition on model TRACK line; _house_gate:595 -> music_policy.in_house_genre (out-of-scope genre list) + watchlist + RIAA, KEEP.

cogs/pop_desk.py – 0 semantic

_VISION_HOSTS:139 URL structural. Deliberately no keyword politics filter; model gates: is_stale_recap, resolve_thread_identity, compose_pop_take EMPTY, pop_desk_score, resolve_desk_image. Depends on out-of-scope retrospective.retrospective_note/frames_as_past.


E. Search / intent / retrieval

utils/genius.py – 5 semantic

genius.py:42-60/:98/:405-432 _DETAIL_INTENT _SAMPLE_INTENT _MEANING_INTENT _LYRIC_INTENT in matches() + routing in lookup() Discord /ask question does Genius claim the question (reference router utils/reference.py:582) AND which render (credits / samples / meaning / identify) miss: falls to Wikipedia; false hit: wrong render or None yes (test_matches_*) HYBRID the reference router (reference.py:555) is a pure regex dispatcher with NO model call; one Haiku “which source/aspect” per /ask reference lookup (user-driven, low volume) replaces four regex lists across genius + musicbrainz
:361 _best_annotation word overlap question vs annotation fragments which annotation to show irrelevant pick yes (test_lookup_annotation_matches_quoted_line) KEEP (HAIKU-lite) relevance pick, low volume

utils/musicbrainz.py – 2 semantic

_MUSIC_INTENT:256 matches:51 on /ask text -> route to MusicBrainz (yes tested; HYBRID, same router as genius). "feat" in joinphrase:291 main vs featured (upstream MB field) KEEP. _pretty_date structural.

utils/banger_lane.py – 2 semantic

banger_lane.py:36/47/50/63 _STOP + _QUOTED_RE + _WORD_RE caps-run heuristic search_terms desk story subject/headline which names to search X for (entity extraction) miss: no_terms / off-moment candidates yes HAIKU entity extraction from a headline is what a small model does well; few calls/day
:55/:240 _SPAM_RE candidate tweet text drop reply target false hit drops a real banger yes (test_spam_dropped) HYBRID keep regex, Haiku judge on the 2 picked

utils/ktt2.py, utils/ktt2_chatter.py – 1 semantic

_STOPWORDS:357 + _tokens + match_score:370 (title-word vs entity hits) -> which forum threads answer a topic query; result is model CONTEXT (yes tested test_match_threads_*) KEEP (retrieval ranking). _is_watched via Watchlist KEEP. Structural: _NEXT_DATA_RE, _HEADING_RE, _RULE_RE, slugs. ktt2_chatter: numeric floors, delegates.

utils/pop_stories.py – 2 semantic

_STOPWORDS:156 + story_signature:226 first-5-content-token moment key (wire text -> durable dedup; reworded dups caught downstream by topic_duplicate; false collision drops a distinct moment) yes tested, KEEP. _MIN_HEADLINE_CHARS:151 length gate KEEP. Account lists structural. No politics keyword filter by design.


F. Versuz

utils/versuz.py – 4 semantic-lite, ~10 structural

| versuz.py:74/81 | PLAY_COMMAND_RE/STRICT_PLAY_COMMAND_RE is_play_command | Discord user text in a voice chat | play command -> attribution window / host offer | false hit resets attribution (“play nice”); strict form fixed it | yes | KEEP | per message, must be instant | | :87/:90/:586/:570 | SHORTHAND_RE parse_shorthand slot_for_name | “name: song” | slot assignment | – | yes | KEEP | structural | | :106 | _title_key loose identity | Jockie titles | queue attribution, repeat detection | – | via _handles tests | KEEP | | | :188-235 | _FEAT_RE, _ARTIST_SPLIT_RE, _EDITION_WORDS base_title (strip remaster/deluxe, keep remix/live) | Jockie title | what the board prints | display | yes | KEEP | catalog credits preferred | | :1026 | answer_order name/handle/title substring | hand-made poll answers | which slot; else ASK the host | never guesses | yes | KEEP | owner decision |

Structural: Jockie embed regexes :48-66 parse_jockie_embed, _SMALL_WORDS smart_title, _URL_RE/_TEXT_SPLIT_RE parse_song_input, _SCORE_RE, _MESSAGE_LINK_RE, link_host.

utils/versuz_catalog.py – 0 own; delegates to apple_music matchers; _YT_NOISE_RE:375 (official|lyric|audio) title cleanup, “- Topic” strip, role=="featured" upstream field: structural.

utils/versuz_card.py – 0.

cogs/versuz.py (4,147 lines) – 2 semantic-lite, ~5 structural

_handles:458 / _member_id_named:491 / _pop_pre_queued:2767 lowercase-equal member-name match on Jockie “Requested by” (miss = unattributed) KEEP exact by design. parse_shorthand + by_name and open_rnd and not (url or artist) :2601 (chat vs correction while a poll is up) KEEP, deliberately conservative. Structural: _SHOWN_LINK_RE:178, _CUSTOM_ID_RE:205, _cue_miss:223 (“library needed” in str(exc)), _CUE_HARD_MISSES, reason.startswith("extra_"). 0 HAIKU candidates: the cog is buttons-not-guesses by owner ask.


G. Milestones / streams

utils/milestone_qualifier.py – 4 semantic (all on MODEL output)

_NO_QUALIFIER_MARKERS:74 is_no_qualifier:104 (Perplexity/Grok read -> skip judge) KEEP protocol; on_chart:128 chart-label substring (scope gate) KEEP; nth_release_qualifier:247 fold identity KEEP; _RELATIVE_SCOPE_MARKERS:411 -> cache TTL KEEP. Model nearby: claude.qualifier_verify. Tested yes (is_no_qualifier, on_chart, verified_qualifier, nth_release_qualifier).

utils/stream_totals.py – 0 own; delegates to chart_boards title match (out of scope); Haiku match_album_tracks already recovers spelling misses (:213-234).

utils/milestones.py, streaming_stats.py, catalog_stats.py, lifetime_boards.py, chart_presence.py – 0 semantic (numeric ladders, fixed-key labels, "*" feature mark, row_is_new flag).


H. Structural-only / zero files

| file | sites | |—|—| | utils/feeds.py | attachment_is_image:79 MIME+ext whitelist; _HTML_TAG_RE:113; mention regexes :140; _SOURCE_PATTERNS:550/_classify_url:565 host->label; room_is_quiet:1072 (>=4 words or attachment; miss = padded recap #880) KEEP; is_human_message -> out-of-scope is_embed_fixer. 0 keyword semantic. | | utils/news_age.py | _VERDICT_RE:166 parses Grok VERDICT. Model already judges. | | utils/media_signal.py | _OP_ALIASES:61 etc. parse the model tag. | | utils/wire_identity.py | _NAME_PREFIX:99 parses model verdict; Haiku wire_subject_named/identify_from_replies/identity_frame_mismatch already exist. | | utils/artist_caption.py | _HAS_WORD_RE:446 decoration-only caption (quote vs “new photo”) KEEP-lite; URL/hashtag/markdown regexes. | | utils/wire_lanes.py, wire_sources.py, industry_feeds.py, songstats.py, spotify.py, apple_releases.py, alt_merge.py, music_links.py, utils/music.py | registries, numeric decay, RSS/URL regexes, _HOT_CHARTS fixed-key, _CATALOG_MISS sentinel. 0 semantic. |


Top 10 HAIKU candidates in this scope (brittleness x user impact)

  1. cogs/music_news.py tour / live / nonaudio / tour-update cue lists (:692, :704, :728, :908, :929, :941, :741, :897) – five phrase lists + two anti-lists + a verb list + a subject-anchored regex, grown across four owner reports in two weeks; each miss ships the wrong kicker, the wrong art and a listen link on a concert. Replace with one event_shape field on the existing classify_music_news call. Zero added calls.
  2. utils/music_news.py:3421-3540 charts_named / sole_chart_text / _forecast_near_chart / unsettled_chart_label – ordered regex resolver deciding WHICH live chart settles a claim; a wrong read becomes absent and the stale gate drops a true story. Closed-set classification to CHART_SOURCES keys is exactly Haiku-shaped; add chart_key to the classifier, keep regex as tiebreak.
  3. utils/music_news.py:3975-4041 stream_claim (+ :4345 entry_count_claim) – five regexes extracting (shape, rung, count) from one sentence; already shipped a false CONFIRM (Codex #2774). Structured JSON extraction on the milestone kind only.
  4. utils/music_news.py:1635-1719 figure_is_passed_threshold – five Codex rounds to tell “100 entries” (a rung) from “107” (the count); a count + threshold_passed field on verify_music_claim retires it.
  5. utils/music_news.py:1806-2062 market_claim_parts / market_award / market_caption / is_probability_figure – five regexes parsing the classifier’s prose into outcome / odds / venue / deadline / award; ask the classifier for the five fields.
  6. utils/release_type.py is_side_version – the one list six consumers share (release board, desk, alert, newsroom presence memory); Codex #2455 saga. Keep on bulk rows; Haiku on the announce paths only (~10-20 titles/slot).
  7. utils/genius.py + utils/musicbrainz.py intent regexes -> utils/reference.py router – the /ask reference dispatcher has no model call at all; four regex lists decide which source answers a user question and which render ships. One Haiku “source + aspect” per reference lookup, user-driven volume.
  8. utils/banger_lane.py:63 search_terms – caps-run + stopword entity extraction from a headline to pick X search terms; a miss is no_terms or an off-moment reply. Pure entity extraction, a few calls a day.
  9. utils/artist_watch.py lenient_tier / _ALIASES (with wire_spelling, grounded_subject, corrected_name in music_news) – deterministic identity is right for the bulk loop, but 10/150 unknown-artist CUTS in 30 days were watched acts lost to spelling/punctuation artifacts. A Haiku “is X one of [these 3 candidates]” only on an UNKNOWN verdict that is about to cut a story (~5/day) rescues the class without touching the hot path.
  10. utils/apple_music.py _title_matches / _artist_matches (and deezer.album_title_matches) – ratio>=0.6 substring fuzz picks the link and cover the reader sees; incidents: Fukk Sleep, Cinderella, Slime Language. Keep as prefilter; add a closed-set Haiku confirm on the ambiguous band (0.6-0.85 or >1 hit), modeled on claude.match_album_tracks.

Honourable mentions (HYBRID, lower impact): chart_cue_strength slate ordering (:287), chart_reportable unnamed fallthrough (:480), cert_reportable territory prose (:2486), claims_debut/claims_reentry (:3072), title_names_act (music_markets:1927), _RUNG_CLAIM_RE (:1268), _songstats_verify “stream” in claim (cogs/music_news:2328, contradicts the repo’s own #2352 rule).

Deliberately NOT candidates: every dedup key, every identity join with a documented incident, every output_checks/ship_guard backstop (each cites a measured judge miss), versuz (owner: buttons not guesses), names/aliases (privacy), catalog_art image checks (docstring rejects a vision judge, measured).


Count summary (semantic sites / structural sites)

file semantic structural
utils/music_news.py 41 14
cogs/music_news.py 7 own (+25 delegations) 8
utils/artist_watch.py 9 4
utils/apple_music.py 11 5
utils/deezer.py 8 2
utils/release_type.py 7 (one module) 0
utils/music_markets.py 4 6
utils/genius.py 5 1
utils/musicbrainz.py 2 1
utils/banger_lane.py 2 0
utils/milestone_qualifier.py 4 0
utils/versuz.py 4 10
cogs/versuz.py 2 5
cogs/music_desk.py 0 own (12 delegations) ~20
cogs/music_alert.py 1 6
utils/chart_credits.py 2 0
utils/hits.py 1 8
utils/names.py + aliases.py 3 2
utils/pop_stories.py 2 2
utils/ktt2.py (+chatter) 1 4
utils/artist_caption.py 1 3
utils/feeds.py 1 (room_is_quiet) 5
utils/catalog_art.py 0 2
utils/song_pool.py, versuz_catalog.py, release_board.py, stream_totals.py, cogs/music.py, cogs/pop_desk.py 0 own (delegations) 2-3 each
utils/news_age.py, media_signal.py, wire_identity.py 0 (model-output parse) 1 each
utils/milestones.py, streaming_stats.py, catalog_stats.py, lifetime_boards.py, chart_presence.py, wire_lanes.py, wire_sources.py, industry_feeds.py, songstats.py, spotify.py, apple_releases.py, alt_merge.py, music_links.py, versuz_card.py, utils/music.py 0 0-3 each

Test coverage note: every named function above was grepped in tests/; the only semantic sites with NO direct test are _ARTIST_MARKET_NOT_ACTS (music_alert:417), _PLACEHOLDER_CREDITS (artist_watch:809), _LATIN_PROGRAM_RE (music_news:2310), _FIELD_LABELS / _subject_tier_hits (music_markets), _EDITION_NOISE, _is_single_or_short, _exact_artist_row, _ALT_VERSION_RE (apple_music), and _kworb_career_row (music_desk). _played_at_subject, _names_the_collection, _RUNG_CLAIM_RE, _MUSIC_INTENT and the genius intents are covered through their wrappers.


Inventory: markets, betting, sports, media

Scope: the 51 files assigned. Every file was read in full. No file was modified. “tested?” = number of files under tests/ that reference the symbol by name (grep -w); 0 means no direct test names it (it may still be covered indirectly).

Legend for “verdict”: HAIKU = replace the decision with a small-model call (cache the answer). HYBRID = keep the rule as the fast path or the fence, add a model rung for the residue, or unify with a model judgment that already exists next to it. KEEP = leave as code.

Model judgments that already exist in this scope (the “duplicate / prefilter” reference points):


utils/markets.py

Semantic sites:

file:line pattern input decision it drives fail direction today tested? verdict reason
markets.py:4580-4640 _PLATFORM_PATTERNS (Apple Music, YouTube, Spotify, Netflix, TikTok, Twitch, Instagram, SoundCloud, Shazam) + _UNQUALIFIED_METRIC_RE in qualify_platform_metric(title, rules) Kalshi market title + settlement rules (upstream API text) rewrites the card title to name the platform (“Spotify streams”) when exactly one platform is named in the rules no rewrite; the card reads an ambiguous “Views” 2 HYBRID closed list of 9 platforms. A new platform on Kalshi ships an ambiguous title. Runs per market snapshot (hourly walk), so cache a Haiku answer by event_ticker.
markets.py:4643-4653 _RUNNER_UP_RE, _RANK_HASH_RE, _SEED_RANK_RE, _AUCTION_RANK_RE in _names_non_leader_slot(title) Kalshi event title flags an event as a non-#1 chart slot not flagged (event kept) 1 HAIKU four regexes guess whether a title asks about a runner-up. Wording drift on Kalshi (“second place”, “runner-up”, “#2”, “2nd seed”) is unbounded.
markets.py:4687-4700 _MUSIC_MARKET_CUES (“song”, “album”, “spotify”, “billboard”, “hot 100”, “rnb”, “r&b”, “rap”, “hip hop”…) in _looks_like_music(title) Kalshi event title exempts a music event from the runner-up cut exempt (kept) 0 direct HYBRID genre word list; a music market with none of these words is cut. image_subject already returns kind=music_release for the same title.
markets.py:4702-4735 _is_cut_runner_up_event(event_title, category): category == "entertainment" and non-leader and not music Kalshi category + title DROPS the event at discovery (market_filtered reason=non_leader_chart) dropped events never post; a false positive is silent 1 HAIKU the composite gate removes whole market families from every surface. One Haiku call per NEW event ticker (“is this market about a non-leader chart slot?”) with a durable cache costs a few calls a day. Top-10 item.
markets.py:4738-4776 _NETFLIX_VIEWS_TICKER_CUE = "NETFLIXTOPVIEWS" + "netflix" in title and "how many views" in title in _is_cut_netflix_views_event Kalshi event ticker + title drops the Netflix view-count board dropped 1 KEEP the ticker prefix is the real key; the title check is a backup. Structural.
markets.py:5950-5964 _SPORTS_PATTERNS, _PM_PATTERNS, _WILL_X_BY_RE in classify_intent(query) Discord user text routes a market query to the sports lane or the prediction-market lane None -> caller default 1 KEEP this IS the fallback for claude.classify_market_intent. Keep as the no-model path.
markets.py:5975-5994 _LEAGUE_KEYWORDS in detect_league(query) Discord user text picks the league for an SGO fetch None -> _resolve_intent_league defaults NBA 1 HYBRID fallback for the Haiku classifier, but sgo_snapshots also calls it directly. Confirm every direct call sits behind the model rung; else a “cowboys” query defaults to NBA.
markets.py:6017-6060 _LEAGUE_SENTINELS (“none”, “unknown”, …) in _resolve_intent_league model output treats a sentinel league as absent default NBA 1 KEEP model-output sentinel parse.
markets.py:6060-6115 _SPORTS_WORD_RE = r"\bsport" in looks_like_sports(channel_name, topic) Discord channel name / topic (mod-set) gates sports behavior on a channel not sports 2 KEEP one word on a mod-set string; cheap, rarely wrong.
markets.py:6116-6187 _KALSHI_SPORT_TAGS in kalshi_sport_label(tags) Kalshi taxonomy tags internal sport tag None 1 KEEP taxonomy map on a closed upstream enum.
markets.py:6188-6195 _TAG_KIND_CUES in derive_kind_tags Kalshi tags + title kind tags for routing no tag 1 KEEP thin, cued by tags first.
markets.py:6196-6262 _MUSIC_CUES, _CINEMA_CUES, _TV_CUES, _POP_CUES in kalshi_channel_routing(channel_name, topic) Discord channel name + topic which Kalshi lanes post into a channel no lane (channel dark) 1 HYBRID a channel named “#the-lounge” with a slang topic gets nothing. One Haiku read per channel (cached until the channel is renamed) removes the cue lists. Top-10 item.
markets.py:6263 MUSIC_SUBTAGS Kalshi tags music sub-lane none 1 (via callers) KEEP taxonomy.
markets.py:6453-6540 _KALSHI_SOCCER_CUES + substring ladder (baseball / basketball / hockey / ufc,mma / tennis,atp,wta / soccer cues / “football” -> americanfootball) in _kalshi_competition_to_sport(competition) Kalshi series / competition title (upstream) the internal sport tag: drives which settlement feed, logo host and watched-sport filter a game gets None -> the event is not watched 1 HAIKU “football” defaults to NFL unless a soccer cue matches; a new competition name (“Copa Libertadores”, “AFL”, “Liga MX”) misroutes or drops. Runs on the hourly open-events walk, but only NEW series need a call; cache by series ticker. Top-10 item.
markets.py:7710 "kalshi" in lower and "polymarket" not in lower Discord user text source ordering for a query default order 0 KEEP user names the venue; substring is fine.
markets.py:7861 _clean_kalshi_label rsplit(“: “) in fold_kalshi_winner_snapshots Kalshi yes_label strips a period prefix so a “Draw” leg is detected draw not detected -> 2-way fold 1 KEEP label format is Kalshi’s own structure.
markets.py:4306-4330, 6824 _norm_game_tokens drops {“vs”,”v”,”at”,”and”,”the”}; team-token precision guard in kalshi_player_props; _kalshi_player_from_label split “:” Kalshi titles / labels attaches a prop to the right game prop dropped 1 (props), 0 (helpers) KEEP identity by team tokens after canonical_name; deterministic on purpose.
markets.py:2443 binary vs multi by label count market payload card shape binary KEEP numeric.
markets.py:1960 _POLY_QUERY_STRIP Discord user text strips filler before a Polymarket search none KEEP query cleanup; the model classifier precedes it.

Structural (compressed): L150 _PARSER_SPORTS, L164 _SGO_SPORT_LABEL, L186 league constants; L615 _classify_fetch_error (HTTP status); L1621 bookmaker preference tuple; L1730-1773 _PROP_STAT_PRIORITY / _PROP_STAT_LABELS / _PROP_PERIOD_LABELS + soccer stat == "points" relabel (SGO statID enum); L1835 bet not in ("ou","yn"); L4779 _BLOCKED_TICKER_PREFIXES (empty) + ticker_matches_prefixes; L4816 SHOW_CATALOG ticker prefixes; L4899 event_ticker.startswith("KXMVE"); L5319/5759 yes/no label formatting; L5995 _SGO_LEAGUE_ALIASES. Count: 18 semantic, 12 structural.

utils/market_cards.py

file:line pattern input decision fail today tested? verdict reason
market_cards.py:116-119 figure_is_number (has digit, <= 8 letters) derived figure string number card vs text card text 1 KEEP measured cap, structural.
market_cards.py:157-186 _MUSIC_CHART_RE in is_music_chart_market(title) Kalshi/Poly title music-chart rendering + report-lock branch in market_drop not music 1 HYBRID duplicates image_subject kind=music_release, which the same card build already calls. Read the kind from the classifier result instead of re-deriving it.
market_cards.py:187-198 _MUSIC_SURFACES in is_music_lane, ships_bare_art surface name (ours) art treatment default 1 KEEP surface registry.
market_cards.py:292-336 _BARS_ONLY_SURFACES, _SURFACE_LABELS surface name chart layout / label default KEEP registry.
market_cards.py:437-496 _SPORTS_CATEGORY, _POLY_SPORTS_TAG, _snapshot_is_sports, _is_sports_card (surface.startswith("bet")) meta category / tags / surface sports card branch (logo art, matchup layout) not sports 0/1 KEEP taxonomy on upstream category + our surface name.
market_cards.py:904 _chartable_items (label == title) market labels which legs chart all KEEP structural.
market_cards.py:1195-1299 _fold_cardtext, redundant (lf in ("yes","no") or lf.startswith(tf)) labels vs title hides a redundant label row shown KEEP closed vocabulary.
market_cards.py:1811 proposition_clause (label lower in question) labels + title clause text for a proposition card title 1 KEEP substring; low risk.
market_cards.py:1835-1934 _RANK_RE, _RUNNER_UP_RE, _TOP_SLOT_RE (^\s*top\s+daily\b), _THRESHOLD_NUMBER, _THRESHOLD_RE, _THRESHOLD_LABEL_RE in named_rank, title_rank; _TOP_BOARD_RE in winner_rank market title (upstream) the card HERO text (“#1”, “top 10”) and rank framing no rank hero -> falls to subject / clause 1-2 HAIKU (as part of the hero cluster below) user-facing copy on every card.
market_cards.py:1959 _AWARD_HEAD_RE in award_slot title award-card hero none 1 HAIKU (cluster) same.
market_cards.py:2002-2019 _AUTHORITY_PREFIX_RE, _RANK_MEDIUM_RE, _RANK_AUTHORITIES {“televisionstats”, “television stats”} in split_rank_authority title which words are the authority vs the medium on a rank card no split 1 HAIKU (cluster) a two-entry authority set.
market_cards.py:2101-2145 _YT_CHART_TAG_RE, _BILLBOARD_TAG_RE, _SPOTIFY_TAG_RE, _NETFLIX_TAG_RE, _METRIC_WORDS / _METRIC_TAIL / _METRIC_TITLE_RE / _METRIC_TAGS in market_chart_tag, split_metric_title title + ticker chart eyebrow tag + metric/title split no tag 2 / 1 HYBRID ticker-prefix rules are structural; the metric-word list is the brittle half.
market_cards.py:2186-2202 _EVENT_HEAD_RE, _EVENT_TAIL_RE, _EVENT_HEAD_BAD_RE, _EVENT_HEAD_MAX_CHARS=48 in event_title_split event title head/tail split on the card whole title 1 HAIKU (cluster) wording-dependent.
market_cards.py:2244-2260 _RANK_HERO_RE, _region_word title hero region word none KEEP small.
market_cards.py:2448-2600 _OUTCOME_VERBS (about 200 base -> third-person verb pairs), _OUTCOME_ANCHOR, _WILL_PREFIX, _THIRD_PERSON_VERBS, _FIELD_HEAD, _FIELD_ANCHOR, _OUTCOME_DANGLING, _OUTCOME_DET, _OUTCOME_QUOTES in outcome_split, _field_clause, outcome_subject, existential_question, _outcome_plural, outcome_caption market title splits “Will X do Y?” into subject + outcome -> hero + caption on every card no split -> statement / raw title as hero (measured 337/349 split) 1 (_field_clause 0) HAIKU a hand-kept 200-verb registry is the definition of brittle. Any verb not in the list (“headline”, “cameo”, “sweep”) leaves a raw question as the hero. Top-10 item #1.
market_cards.py:2730-2790 _INTERROGATIVE_WORD in statement_title; _HOW_QUESTION in how_much_title; _FORMAT_CHARS_RE title question -> statement rewrite raw title 1 HAIKU (cluster) same cluster.
market_cards.py:2795-2860 subject_hero ladder (rank -> threshold -> subject/clause) title picks the hero rung title 2 HAIKU (cluster) the ladder is the orchestrator of the regexes above.
market_cards.py:3269 _SINGLE_LEG_HERO_MIN_PCT = 50 price hero eligibility none KEEP numeric.

Structural: card_block / number_card_tag_rung category ladder; emit_card_hero rung names; L525 _CONTAIN_SOURCES; L599 _valid_http_url. Count: 17 semantic, 6 structural.

Note on the cluster: named_rank, title_rank, winner_rank, award_slot, split_rank_authority, event_title_split, outcome_split family, statement_title, how_much_title, subject_hero, and market_drop._slot_figure / _RANK_WINNER_TITLE_RE all read the SAME title to produce the SAME card copy. One Haiku call per market (“return hero, caption, rank, authority, subject as JSON”), cached by ticker with the regex ladder as the fallback, replaces about 20 regexes.

utils/market_subject_image.py

file:line pattern input decision fail today tested? verdict reason
market_subject_image.py:175-218 _GENERIC_OUTCOME_WORDS, _NUM, _DASHES, _MONTHS, _GENERIC_OUTCOME_RE in _is_generic_outcome(label) market leg label skips Yes/No/month/number legs as image subjects skip 3 KEEP closed vocabulary; well tested.
market_subject_image.py:230-290 _top_charted_leg, _featured_label (label lower in title) labels + title which leg is the image subject first charted 0 / 1 KEEP structural.
market_subject_image.py:292-340 _subject_query (prefix <= 16 chars before “:”), _extraction_text title the search / classifier text whole title 1 KEEP feeds image_subject, which does the judgment.
market_subject_image.py:344-373 _MEDIA_BRANDS / _BRAND_PATTERNS in detect_media_brand(title) title rung 3b: ship an org logo skip rung 1 HYBRID image_subject already returns kind=org for the same title one rung later. Duplicate; read the classifier’s kind.
market_subject_image.py:560 subtitle_catalog_hint (raw.startswith("::")) Kalshi market_subtitle catalog-cover rung none 2 KEEP our own encoding.
market_subject_image.py:590-680 is_youtube_video_market (“music video” and “youtube”); _YT_DAILY_CHART_RE; _CHART_DAY_RE / _MONTH_INDEX in youtube_chart_day, youtube_chart_is_current; is_youtube_daily_chart_market title YouTube chart-day image rung + the YouTube fabrication-gate route in market_drop not YouTube 1 each HYBRID the date parse is structural; the family cue is a title substring. market_chart_tag in market_cards derives the same family from the ticker. One family classifier, not three.
market_subject_image.py:683-724 is_netflix_rank_market (“netflix” + show/movie, not view/”how many”) + _NETFLIX_RANK_LEAD_RE title routes the take to the Haiku market_take_names_leader judge not routed (no fabrication check) 1 HYBRID a prefilter in front of a model judge. A miss skips the judge. Cheap to widen: run the judge for every chart family.
market_subject_image.py:726-900 _LEG_NOISE_WORDS, _LEG_QUALIFIER_RE, _PHRASE_MATCH_MAX_TOKENS=3, _POSSESSIVE_RE, _fold_text / _fold_tokens / _fold_phrase / leg_identity_tokens in take_references_leg(take, label) composed take (model output) + leg label fabrication gate: drop the drop when the take does not name the leg CLOSED (drop) 1 HYBRID a deterministic fence is the house pattern, but the Netflix sibling uses a Haiku judge for the same question. Token folding cannot see a paraphrase (“Abel” for “The Weeknd”) and drops a correct take. Unify on the Haiku judge with this as the pre-check. Top-10 item.
market_subject_image.py:915-950 _ARTIST_COUNT_METRIC_RE, _ARTIST_QUESTION_RE, _ARTIST_SUBJECT_MAX_CHARS=48 in artist_subject_name, _subject_splits, _names_youtube title the artist name for the portrait rung no name -> next rung 1 HAIKU entity extraction by regex on a free-text title. image_subject returns kind=musician + name for the same text. Duplicate.
market_subject_image.py:resolve_market_image rung ladder (catalog hint -> YouTube chart -> team logos -> image_subject + _deterministic_art -> media brand -> vision -> crest -> Poly leg art -> native) mixed image source branded floor 3 KEEP routing on the classifier’s output.

Count: 9 semantic, 2 structural.

utils/market_outcome.py

file:line pattern input decision fail today tested? verdict reason
market_outcome.py:273 is_no_result (is_hedged + lead word “none”) model output no-result sentinel treated as result 1 KEEP sentinel parse.
market_outcome.py:284-330 _SOURCE_URL_RE, _X_HOSTS, _MARKET_HOSTS in cited_hosts / cited_host URLs in a web read whether a citation counts as an independent source (market hosts do not) counts 0 / 1 KEEP URL routing (structural) but it gates the winner line.
market_outcome.py:614-640 DECIDED_LOCK_PRICE 0.95 etc.; _GENERIC_LABELS prices / labels decided state undecided KEEP numeric + closed set.
market_outcome.py:747-830 _NAME_NOISE in normalize_name; names_match (prefix >= 6 chars); _in_field Haiku winner_extract output vs market labels matches the extracted winner to a leg -> which leg the winner line names no match -> no winner line (absent) 0 / 1 / 1 SHIPPED (A8, #3420) winner_extract already returned an index into the slate; the prefix matcher was dead in production and is retired. _in_field is an exact normalized re-check.
market_outcome.py:972-1030 _MARKET_TALK regex in has_market_talk / market_talk_phrase model output (winner line) deterministic ban -> one re-ask re-ask, then drop 1 / 0 KEEP a fence over model output; house pattern.
market_outcome.py:1039 _SETTLED_STATUSES upstream status enum settled not settled KEEP structural.

Count: 4 semantic, 2 structural.

utils/market_chart.py (Pillow rendering)

file:line pattern input decision fail today tested? verdict reason
market_chart.py:1444 stamp_is_redundant (source label word inside eyebrow) our labels hides a duplicate stamp shown 2 KEEP cosmetic.
market_chart.py:1944-2000 _side_tokens (>= 3-char tokens) + _order_two_up (match outcome labels to matchup sides after removing shared tokens) outcome labels vs matchup string which bar is home / away input order 0 / 1 KEEP deterministic identity; a wrong order is a rendering bug the label still disambiguates.
market_chart.py:2022 _lower_medium / _chart_medium pluralization chart tag caption noun tag 0 KEEP cosmetic.

Structural: _mark_is_dark luminance, size/aspect math. Count: 3 semantic, 3 structural.

utils/alert_triggers.py, utils/market_triggers.py, utils/market_siblings.py, utils/kalshi_price.py

file:line pattern input decision fail today tested? verdict reason
alert_triggers.py:127-160 _TITLE_OUTCOME = ^will\s+(.+?)\s+win\b in outcome_label(title, yes_label); claim_key lower/strip Kalshi title the entity that keys the cross-surface claim dedup (market_alert + market_drop) falls back to yes_label -> two phrasings of one claim post twice 0 / 1 HYBRID only “win” titles get an entity. “Will X be nominated” / “top the chart” fall to the raw label. Shares the cluster fix in market_cards.
market_triggers.py classify_market_event bands (_LOCK 0.90, _FAVORITE_LO 0.65 …), charted_move prices alert kind none yes KEEP numeric.
market_siblings.py generic group-by spine KEEP no patterns.
kalshi_price.py WIDE_SPREAD 0.10, LAST_TRADE_TOL 0.05 book numbers trusted price dropped rung yes KEEP numeric.

Count: 1 semantic (alert_triggers), 0 structural; others 0/0.

utils/kalshi_ladder.py

file:line pattern input decision fail today tested? verdict reason  
kalshi_ladder.py:389-415 _NUMERIC_STRIKE_TYPES + _THRESHOLD_LABEL_RE in is_ladder_rung(meta) Kalshi strike_type + label rung vs outcome leg outcome 2 KEEP strike_type is upstream structure; the label regex is a backup.  
kalshi_ladder.py:416-448 _STRIKE_PHRASE_RE, _STRIKE_NUMBER_RE in _rung_subject, rungs_share_subject rung labels same-ladder identity not shared -> separate cards 0 / 1 KEEP numeric strip of a label.  
kalshi_ladder.py:449-560 ladder_unit (residue after the number, % / $), is_bucket_leg label unit on the card none 1 / 1 KEEP structural.  
kalshi_ladder.py:588-660 _MONTH_NAMES, _DATE_RUNG_LABEL_RE in date_rung_deadline, is_date_ladder; is_threshold_ladder, is_threshold_family labels date-ladder vs threshold-ladder shape threshold 1 / 2 / 3 / 1 KEEP date parse.  
kalshi_ladder.py:821-839 decided thresholds prices decided none yes KEEP numeric.  
kalshi_ladder.py:1123-1260 _COMPANION_LADDERS registry {“KXYTVIEWS” -> “KXYTVIEWSHIGH”, “KXALBUMDEBUT” -> “KXALBUMEQUIV”}; is_lone_scalar_threshold, is_lone_outcome_binary, companion_ladder; _norm_match_text; companion_subject_period (“: “); companion_event_matches (subject AND period substring); companion_outcome_subject (“ ”); companion_outcome_period_end (strptime “%B %d, %Y”); companion_outcome_matches (equality) Kalshi titles / subtitles across two ticker families pairs a scalar ladder with its outcome ladder on one card no pair (absent) 1 each KEEP ticker-family registry is structural; the subject match is a substring on titles Kalshi generates from one template. Flag: a template change breaks the pairing silently (no event).

Count: 6 semantic, 0 structural.

cogs/market_drop.py

file:line pattern input decision fail today tested? verdict reason
market_drop.py:225, 254, 640, 1291 _POLY_TAG category -> slug; _GAME_RISK_CATEGORIES; _REPORT_CATEGORIES + _is_music_chart in _is_report_lock; _POLY_GAME_TAG = "games" in _is_tagged_game upstream category / tags drop family + report-lock branch default branch 1 each KEEP taxonomy on closed enums; _is_music_chart inherits the market_cards HYBRID note.
market_drop.py:413-760 _is_competing_field (sum <= 1.5), _is_threshold_leg, _is_priced_threshold_ladder, _is_flat_wall, _is_unpriced_board, _soft_priced_labels, _is_locked_leader, _frontrunner, _clear_winner_lock, _frontrunner_report prices which drop shape / skip skip 1 each KEEP numeric.
market_drop.py:706 _take_catalog_hint reads Haiku image_subject kind == “music_release” model output catalog cover rung none 1 KEEP routing on the classifier.
market_drop.py:810-830 _RANK_WINNER_TITLE_RE; _slot_figure title the figure on a rank-winner card no figure 0 / 1 HAIKU (cards cluster) same title, same copy.
market_drop.py:924 titled_leg casefold not in {“yes”,”no”} labels named leg none 0 KEEP closed set.
market_drop.py:1009-1030 _source_artist split “::”; _companion_names_artist (substring) Kalshi subtitle / title companion ladder pairing by artist no pair 0 / 0 KEEP our own subtitle encoding.
market_drop.py:1193 _DRAW_LABEL_RE = ^(draw\|tie)\b in _is_single_game labels single-game vs field field 1 KEEP closed set.
market_drop.py:1220-1290 _MATCHUP_RE, _SIDE_SUFFIX_RE, _SIDE_PAREN_RE in _clean_side, _matchup_sides, _slate_pairs, _matchup_covered (via canonical_name) Kalshi / SGO game titles same game across sources (one drop, not two) not covered -> both post 0 / 1 / 1 / 1 KEEP identity by canonical team name; the house rule says team identity lives in sportsdata.names.
market_drop.py:1301 _norm_topic (strip “the “) topic string topic dedup key raw 1 KEEP cosmetic fold.
market_drop.py:1522-1540 _KALSHI_TITLE_SUBJECT = \bwin (?:the )?(.+?)\s*\??$ in _kalshi_event_subject; _alert_already_said (own DB) Kalshi title the topic-pulse subject and the “already said” dedup falls back to the title 1 / 0 HYBRID same shape as alert_triggers.outcome_label; one subject extractor for both.
market_drop.py:2988-3040 fabrication-gate routing: YouTube daily chart -> take_references_leg (deterministic), Netflix -> Haiku market_take_names_leader (fail-open); then image_subject(line) for the catalog hint title family + composed take which fabrication judge runs none for other families yes (cog tests) HYBRID two families get a judge, every other chart family gets none. Run the Haiku judge for all chart-family drops.

Also read: drop_self_correction, has_cross_source_comparison, ungrounded_numbers, drop_score gates (KEEP; deterministic fences + the model floor). Count: 11 semantic, 3 structural.

cogs/market_alert.py

file:line pattern input decision fail today tested? verdict reason
market_alert.py:420, 650 _GENERIC_LABELS; _PULSE_SUBTAGS = MUSIC_SUBTAGS in _is_pulse_market labels / tags pulse lane not pulse 1 KEEP closed sets.
market_alert.py:707 _clean_authority title authority text on the card raw 0 HYBRID same authority split as split_rank_authority; cluster.
market_alert.py:717-760 _VS_SPLIT in _headtohead_favorite (label lower in side) title + labels which side is the favorite no favorite named 1 KEEP “vs” split is structural.
market_alert.py:816, 1050, 3512 _settled_out, _soft_priced, _headline_license (>= 55 / <= 45) prices alert shape none 1 each KEEP numeric.
market_alert.py:1273-1300 _LABEL_UNIT + _UNIT_SUFFIX in _forecast_figure (“$” check) rung label unit on the forecast figure no unit 0 / 1 KEEP structural.
market_alert.py:1345 _quotes_range (number-in-line check) model output drops a line that quotes the wrong range drop 1 KEEP deterministic fence.
market_alert.py:1482-1500 _race_label, _named_leg_event labels race framing none 0 / 1 KEEP small.
market_alert.py:1745, 3664 _non_leader_reason (exact label == leader); ending-soon leader compare labels leader identity none 1 KEEP exact compare.
market_alert.py:2101 _ending_soon_subject title subject of the ending-soon line title 1 HYBRID cards cluster.
market_alert.py:3817 has_self_correction model output drop drop yes KEEP fence.
market_alert.py:4110 rt_reconcile.is_rt_event / film_from_event title Rotten Tomatoes family none (module out of scope) see rt_reconcile audit.

Count: 5 semantic, 6 structural/numeric.

cogs/bookie.py, cogs/betting_board.py, cogs/betting_alert.py, cogs/betting_value.py, utils/bet_corrections.py, utils/odds_adapt.py, utils/odds_compare.py, utils/called_shot.py, utils/leaderboard.py, utils/table_card.py, utils/ranking_strip.py

file:line pattern input decision fail today tested? verdict reason
bookie.py:201-269 _THREE_WAY_SPORTS, _BACKSTOP_LEGS, _ODDS_API_SPORT_KEY, _ODDS_API_EXTRA_KEYS, _ODDS_API_PRESEASON_KEYS, odds_api_sport_key (league in WNBA_LEAGUES), _ODDS_API_FOLD_ONLY_SPORTS our sport / league tags which odds feed + 3-way settlement none 1 KEEP registry on our own enums.
bookie.py:417-440 _split_matchup (“ @ “); _winning_team (score + is_single_leg_knockout / knockout_advancer) our title format + feed scores who won -> payout void / push 0 / 3 KEEP structural + numeric; settlement must stay deterministic.
bookie.py:1254-1268 _line_is_known_stale (odds_source), _is_degenerate_side our meta reprice none 0 / 0 KEEP numeric.
bookie.py:1373-1410 _WORLD_CUP_FOOTBALL_LEAGUES / _is_world_cup_football; _KNOCKOUT_NATIONAL_LEAGUES league ids knockout rules league play 0 KEEP id sets.
bookie.py:1537-1562, 2562 legacy key split “:”; _needs_pricing (odds_source in _PREDICTION_SOURCES) our DB keys migration + pricing path none 0 KEEP structural.
betting_board.py:223-304 _SPORT_SUBJECT_WORD; _norm_matchup; _label source -> label our tags copy default 0 / 1 / – KEEP registry.
betting_board.py:687-811 is_effectively_decided (DECIDED_PCT 97); _BOOK_LINE_NON_SPORTSBOOK; _watched_only via is_watched prices / our tags board rows shown 1 / – / 1 KEEP numeric.
betting_alert.py:152-209 _favorite, _classify_move (-> shared classify_leader_move), _has_started / _is_in_play (flags / score) prices / feed flags alert kind none 1 / 1 / 2 / 1 KEEP numeric.
betting_value.py:85-154 _COHERENT_MIN/MAX, _coherent, _has_book_line, _value_edge, _edge_signature prices value alert none 1 each KEEP numeric.
called_shot.py verdict_for (CLOSE_WITHIN 2), units_story (4% / 10%) numbers verdict none 1 / 1 KEEP numeric.
bet_corrections.py, odds_adapt.py (snap.source routing), odds_compare.py, leaderboard.py, table_card.py, ranking_strip.py data list / source routing / math / rendering KEEP no text classification.

Count: bookie 0 semantic / 7 structural; betting_board 0 / 4; betting_alert 0 / 3; betting_value 0 / 4; called_shot 0 / 2; the six utils 0 / 1 each.

utils/sports_stories.py

file:line pattern input decision fail today tested? verdict reason
sports_stories.py:101-190 _STOPWORDS (about 60 words), _TOKEN_RE, _URL_RE, _HANDLE_RE, _SIGNATURE_TOKENS = 5 in _content_tokens, story_signature, dedup_key wire post text (X, upstream) the per-moment dedup key: the same happening posts ONCE in 36h distinct key -> a reworded repost can post twice; the ScheduledPoster topic_duplicate Haiku dedup backs it on the composed text 0 / 3 / 3 KEEP first-5-content-tokens is crude but a Haiku dedup already sits downstream. Not worth a second model call.
sports_stories.py:202-208 is_postable_moment (not reply, >= 24 chars) wire post postable skipped 3 KEEP length floor.
sports_stories.py:291-314 SPORT_BY_HANDLE (about 50 handles -> sport) wire author handle sport tag, checked BEFORE the keyword scan falls to keywords 0 direct KEEP authoritative for single-beat accounts; grows by evidence.
sports_stories.py:323-439 _SPORT_KEYWORDS (10 sports, about 250 team / league / position words, word-bounded) in classify_sport(handle, headline) wire headline (upstream text) the sport beat tag -> sport_penalty halves the story’s score per recent same-sport post -> WHICH story posts this slot UNKNOWN -> no penalty (open) and the story goes to Haiku classify_sports_beats (cached 48h by signature) 1 SHIPPED (B8, #3426): model-first, rules fallback already hybrid, but the regex answers FIRST and a WRONG tag never reaches the model: “Cardinals baseball” is needed to avoid the NFL Cardinals, “Jets” tags NFL for an NHL story (“jets hockey” is in the NHL list but “jets” alone matches NFL first), “Eagles”/”Lions”/”Giants” are shared names, “Kings” was left out on purpose. A wrong tag penalizes the wrong beat and is invisible (the event stamps the tag as confident). The model rung, the cache and the vocabulary fence all exist; flipping the order costs about 40 cached calls per slot at most. Top-10 item.
sports_stories.py:442-503 sport_penalty, apply_sport_diversity (SPORT_DIVERSITY_DECAY ** n, lookback 6) our tags score re-weight none 2 KEEP numeric.

Count: 4 semantic, 1 structural.

cogs/sports_desk.py

file:line pattern input decision fail today tested? verdict reason
sports_desk.py:169-181 _VISION_HOSTS substring in _fetchable, _fetchable_photos media URL which photos go to the vision gate photo not judged 0 KEEP URL routing (structural).
sports_desk.py:310-379 _resolve_unknown_sports (model rung + cache) headlines sport tag for the regex misses unknown 1 KEEP this is the model rung.
sports_desk.py:541-548, 679-685 retrospective_note / retrospective_frame on the headline; frames_as_past on the take wire text / model output throwback framing + drop see retrospective.py see retrospective.py
sports_desk.py:664-720 drop_self_correction, ungrounded_numbers, sports_desk_score >= 0.6 model output ship drop yes KEEP fences + floor.

Count: 1 semantic (delegated), 3 structural.

utils/api_sports.py

file:line pattern input decision fail today tested? verdict reason
api_sports.py:121-151 _FOOTBALL_LIVE, _FOOTBALL_DONE, _BASKETBALL_DONE/_DEAD, _BASEBALL_*, _HOCKEY_*, _NFL_*, _MMA_* status-code sets; _TEAM_STATUS_SETS API-Sports status.short (upstream enum) live / completed -> settlement over-live is harmless; over-done pays a wrong bet 1-2 (via snapshot parsers) KEEP upstream enum, verified live; must stay deterministic for settlement.
api_sports.py:170-195 _norm_team_name, _team_logo_from_rows (exact casefold name match, else first row with a logo) API-Sports /teams rows vs our team name which crest ships on a card first row with a logo (a guessed crest) 1 KEEP, but flag the “first row with a logo” fallback ships a possibly wrong crest for “Boston Red Sox” -> an affiliate. Prefer absent over invented says return None on no exact match. Not a model job.
api_sports.py:253-271 _count_goal_events (type == "Goal" and detail != "Missed Penalty") API-Sports events goal trigger for the commentator falls to score field 0 KEEP upstream enum.
api_sports.py:366-374 .removesuffix(" W") on basketball team names API-Sports name WNBA name fold so bets reconcile strands a WNBA bet 1 (parser) KEEP structural.
api_sports.py:524-532 _event_kind (“goal” / “card” / “subst”) upstream type event kind “other” 0 KEEP enum map.
api_sports.py:561-580, 632-714, 846-893 _FOOTBALL_STAT_LABELS; _football_leaders relevance (goals4 + assists2 + rating-6; shots >= 3, passes >= 40, tackles >= 3 …); _basketball_leaders (ast >= 4, reb >= 7, 3PT >= 40 …) upstream stats which players and stats reach the commentate prompt fewer bits 1 / 2 / – KEEP editorial thresholds; the model picks the angle downstream.
api_sports.py:198-224 _response_error (non-empty errors) API body failure vs healthy-empty ok=False 2 KEEP structural.

Count: 0 semantic (all upstream enums / numeric), 8 structural.

utils/espn.py

file:line pattern input decision fail today tested? verdict reason
espn.py:99-107 _ENDPOINTS, _SINGLES_SLUGS {“mens-singles”, “womens-singles”} ESPN grouping slug keep singles only doubles dropped 0 KEEP upstream enum.
espn.py:132-146 _competitor_name (athlete displayName, else athletes list -> “”) payload shape 1v1 vs doubles dropped 0 KEEP structural.
espn.py:377-403 TeamGame.upcoming_at (state == "pre", clock), three_way ESPN state + clock hero eligibility not upcoming (via upcoming_board) KEEP structural.
espn.py:487-508 TEAM_LEAGUES (sport -> ESPN paths + league ids) our sport tag which ESPN leagues a crest / score lookup searches none KEEP registry.
espn.py:548-568 match_team_logo (canonical_team fold OR norm(query) == short; 2+ hits -> “”) market side name vs ESPN team names which crest ships ”” (absent over invented) 1 KEEP identity via the shared sportsdata.names home; ambiguity refuses. Correct shape.
espn.py:616-641 _stat_int(stats, "playoffSeed") or _stat_int(stats, "rank"); "Last Ten Games" etc. by name ESPN stat names rank / record fields position fallback 1 KEEP upstream field names.
espn.py:647-665 _leader_display ("," not in display) ESPN displayValue MLB batting-line vs number reformat 1 KEEP structural.
espn.py:771-842 _american, _side_at, _moneyline_pair, _scoreboard_odds (close then open, both sides same snapshot) ESPN odds block the card’s line unpriced 0 / 0 / 0 / 1 KEEP numeric.

Count: 0 semantic, 8 structural.

utils/highlightly.py

file:line pattern input decision fail today tested? verdict reason
highlightly.py:46-79 _HOSTS (football / basketball); _parse_highlights (type == "VERIFIED") our sport tag; upstream type which clip may post none – / 1 KEEP enum.

Count: 0 semantic, 2 structural.

cogs/sports_boards.py

file:line pattern input decision fail today tested? verdict reason
sports_boards.py:114-150 _ODDS_KINDS, _CHANGE_GATED_KINDS, kind.startswith("leaders_") in _change_gate_depth our board kinds experiment lane + change gate daily cadence 1 KEEP own enum.
sports_boards.py:152-167, 387-406, 969-1005 dedup key parsing spb:<kind>:<league>:<date> (split(":"), startswith, rsplit) in posted_today_for, _last_posted, _league_order our own DB keys per-league daily cap + rotation order fail-open 1 / 1 / 1 KEEP own key format.
sports_boards.py:202-227 _ODDS_PROP_MARKETS, _SGO_BOARD_LEAGUE our league key which feed / market no board KEEP registry.
sports_boards.py:340-375 _on_today_et, _upcoming_tonight (ISO parse, unparseable -> True) upstream commence_time tonight filter kept 0 / 1 KEEP date.
sports_boards.py:624-631 p.side != "Over" Odds API side one row per player dropped 1 (via _odds_api_props) KEEP enum.
sports_boards.py:1281-1315 has_self_correction, has_row_arithmetic, has_row_tally, ungrounded_numbers, drop_score >= 0.6 model output ship drop yes KEEP fences + floor.

Count: 0 semantic, 6 structural.

utils/sports_boards.py

file:line pattern input decision fail today tested? verdict reason
sports_boards.py:66-117 floors (MIN_TABLE_GAMES 5, MIN_STREAK 4, MIN_UPCOMING_COVERAGE 0.7 …) counts board eligibility None (no post) yes KEEP numeric.
sports_boards.py:358-369 short_stat (removesuffix “ per game”), fixture ESPN label row wording raw 1 / – KEEP cosmetic.
sports_boards.py:505-556 _snap_is_prop (meta has player), _game_props_join_key (event_id), _game_props_variant, _game_props_keep (odds keys) SGO snapshot meta game + props sibling join dropped – / – / 0 / 1 KEEP structural.
sports_boards.py:559-597 _game_line_figure (equal -> “PK”, 3-way strict shortest) prices favorite named on the card ”” / “PK” 1 KEEP numeric; correct absent-over-invented shape.
sports_boards.py:734-749 league_is_live (any completed game in 7 days) ESPN games offseason gate no board 1 KEEP signal, not text.
sports_boards.py:752-775, 854-862 _strip_points_suffix (split “,”), _record, _streak_length (“W”/”L” + int) ESPN strings row text / streak rank 0 0 / 4 / 0 KEEP upstream string format.
sports_boards.py:1063-1117 _with_implied_pct (last token sign check, “PK” passthrough), _favorite_cell (draw included, tie -> PK) our cell text implied % gloss / favorite unchanged 1 / 1 KEEP numeric.
sports_boards.py:1120-1331 upcoming_board hero rules (pick’em, draw-shortest, coverage), one-book source pill prices hero / caption None 1 KEEP numeric.

Count: 0 semantic, 8 structural.

utils/playercard.py

file:line pattern input decision fail today tested? verdict reason
playercard.py:66-91 _SENT_SPLIT + utils.names.name_pattern / match_forms in about_member_text(summary, names) memory notes (our DB, model-written prose) + member aliases which sentences of a daily note feed the Opus hype-up blurb ”” -> the blurb sees nothing about them (the Sun blank-hero incident) 2 KEEP name-mention filter before an Opus call; the model then judges. The alias fold fixed the measured miss.
playercard.py:257-283, 453-484 _STATS terms + weights; RATING_KEYS; archetype_for (top blend >= 0.60; OVR < 60 -> Rookie) counts ratings, archetype label Franchise / Rookie 1 KEEP numeric.
playercard.py:402-434 _BADGE_RULES (top 10% + floors) counts badges none 1 KEEP numeric.
playercard.py:608-672 board_ranking aliases (“overall”,”ovr”,”reactions”,”reacts”); format_card_pages which in ("all","full","everything","card","everypage","every") / (“overview”,”hero”,”summary”) / which in p.title.lower() model tool argument (the engagement_lookup tool) which board / page renders [] / “(card pages are: …)” hint 1 / 1 KEEP the model already chose the argument; the alias set is a tolerant parser with a self-describing miss.
playercard.py:963-979 top_public_channel (is_public callback) our counts + Discord perms home channel on a public card omitted 1 KEEP permission check.
playercard.py:1102-1155 _SUPERLATIVES floors, superlatives (top 25% or top 5) counts yearbook chips none 0 / 3 KEEP numeric.

Count: 1 semantic (about_member_text, KEEP), 5 structural/numeric.

cogs/playercard.py

file:line pattern input decision fail today tested? verdict reason
playercard.py:269-312 _who_autocomplete: fold_for_search contains-match; name.strip().lower() == "deleted user" skip Discord user keystrokes + identity map autocomplete list [] 1 KEEP search fold; a miss is visible to the user.
playercard.py:338-361 _resolve_target (digits -> uid; fold exact, then contains, first hit) Discord user text whose card renders not_found reply 1 KEEP a contains-match can pick the wrong member on a shared substring, but the user sees the name on the card and the autocomplete is the primary path.
playercard.py:452-476 _resolve_member_id (mention regex, 15-25 digit id, exact name, else first substring) model tool argument (target) which member the /ask tool reads not_found 0 KEEP model already chose the name; first-substring can misresolve (“sam” -> “samantha” before “sam”). Low volume.
playercard.py:495-501, 546-548 scope aliases (“everyone”,”all”,”all-time”,”alltime”,”departed”); board aliases (“besties”,”duos”,”pairs”) model tool argument scope / board current / bad_board hint 1 KEEP tolerant parser on a model-chosen enum.

Count: 4 semantic (all KEEP), 0 structural.

utils/retrospective.py

file:line pattern input decision fail today tested? verdict reason    
retrospective.py:60-138 _FRAMES: ago_today, this_day_ago, this_day_in (+ past-year check), today_in, today_marks, throwback_tag (_TAG_RE), all anchored to the post START; in _match -> retrospective_frame, retrospective_phrase, is_retrospective, retrospective_note wire post text (X, upstream) on all four wire desks (a) hands the compose a THROWBACK instruction; (b) music_news skips the live-chart settle; (c) drop of the take if it does not frame the past a miss ships a year-old milestone as current news (the measured 2026-09-14 incident that created this module) 2 / 0 / 1 / 3 HAIKU the docstring itself lists the class this cannot reach: any anniversary that leads with a different phrasing (“Ten years since…”, “A decade on,”, “Remember when”, a quoted date “September 14, 2018:”, a non-English lead). The match is anchored narrow ON PURPOSE because a regex has no way to weigh mid-sentence context; a Haiku call (“is this post about something that already happened, and when?”) is the judgment the anchor approximates. Volume: every candidate on 4 desks per slot, cheap, cache by post id. Top-10 item.    
retrospective.py:176-209 _PAST_INTERVAL_RE in states_past_interval wire text music_news: old catalog cover vs act photo False -> act photo 2 HYBRID folds into the same Haiku answer (“how long ago”).    
retrospective.py:218-334 _PAST_MARKERS (8 regexes: countable interval, ordinal anniversary, “last year”, “years since”, tag, “in " + `_FUNCTION_WORD` list, "'s", " debut release …”) in frames_as_past(take) composed take (model output) DROPS a throwback take that states no look-back marker CLOSED (drop); every drop is a lost post that music_news marks SEEN 2 HYBRID a fence over model output (house pattern) but this fence judges PROSE, not a number. Five Codex-review patches in one PR (#3271: “Million Years Ago”, “1989”, “28 Years Later”, “Anniversary” the album, “2000 theaters”) show the shape: each title-shaped false positive or negative needs a new sub-rule. A Haiku judge (“would a reader of ONLY this line know it happened then?”) is the stated stop condition. Keep the regex as the cheap pre-pass, ask Haiku only when it says no.
retrospective.py:337-490 _COUNT_WORDS, _FRAME_LEAD_RE, _INTERVAL_COUNT_RE, _UNIT_YEARS, _interval_count, _frame_numbers, retrospective_values, look_back_values, strip_look_back, retrospective_grounding matched phrase / take the numbers a throwback take may state (feeds ungrounded_numbers) drop as retro_interval on a mismatch 1 each (_interval_count 0) KEEP number grounding; deterministic by design. If the detection moves to Haiku, ask it to return the count/unit/year as fields and keep this arithmetic.    

Count: 4 semantic, 0 structural.

utils/scrapecreators.py

file:line pattern input decision fail today tested? verdict reason
scrapecreators.py:146-224 sc_tweet_to_post: retweeted_status_result -> surface the original; quoted_status_result when not post.has_media -> surface the quote; in_reply_to_* -> is_reply X GraphQL JSON what the curator judges (original vs quote) post as-is 1 KEEP shape mapping.
scrapecreators.py:541-571 _tt_frames (cover / origin_cover / dynamic_cover); _tt_media_kind (image_post_info.images -> photo / carousel, video -> video) TikTok JSON media kind on the SourcePost ”” 1 / 0 KEEP shape.
scrapecreators.py:684-720 resolve_short_link host rewrite to www.tiktok.com URL short-link resolve None 1 KEEP URL.
scrapecreators.py:788-800 _IG_MEDIA_TYPES {1: photo, 2: video, 8: carousel} IG media_type code media kind ”” 0 KEEP upstream enum.
scrapecreators.py:427-536 FailoverProvider._active (primary.degraded and backup.provisioned), __getattr__ forwarding breaker state which provider answers primary 1 KEEP routing on breaker state.

Count: 0 semantic, 5 structural.

utils/steam_art.py

file:line pattern input decision fail today tested? verdict reason
steam_art.py:64-100 _fold (alphanumerics only) + pick_app (exact fold match, type == "app", 2+ hits -> None) Kalshi leg name vs Steam storesearch names which appid’s art ships None (absent) 4 / 1 KEEP deliberately an identity test, not fuzzy; the docstring records the “Uno” measurement showing why the CLASSIFIER (image_subject kind=video_game) gates the rung and the match stays exact. Correct split.
steam_art.py:103-118 first_screenshot (path_full startswith “http”) Steam JSON fallback art None 1 KEEP shape.

Count: 1 semantic (KEEP), 1 structural.

utils/entity_image.py

file:line pattern input decision fail today tested? verdict reason
entity_image.py:94-101, 202-229 _SPORT_SEARCH_WORD map; subject_search_query (athlete -> name + sport word; music_release -> name + artist unless artist.lower() in name.lower() or placeholder) Haiku image_subjects output the open-web photo query bare name 1 KEEP routing on the classifier’s fields.
entity_image.py:232-391 _deterministic_art kind ladder (music_release -> catalog cover -> Deezer portrait only if artist_grounded; musician -> Deezer; person -> TMDB; film/tv -> TMDB poster; org -> Wikidata logo; video_game -> Steam then Wikipedia; place/thing -> Wikipedia) classifier kind which catalog answers None -> vision 2 KEEP routing on a model output; every branch’s fail direction is absent-over-invented.
entity_image.py:190-199, 294-299 _artist_rung (exact + grounded -> deezer_artist_exact, the media-gate-exempt rung) Deezer match flag + artist_grounded whether the delivery media gate may overrule the picture gated 1 KEEP flag routing.
entity_image.py:437-487 _BRAND_SUFFIXES (token in purpose) in _brand_suffix; _card_bare_logo (src != "wiki_logo" passthrough) our purpose string / rung name wordmark on a logo card ”” 1 / 1 KEEP own strings.
entity_image.py:401-434, 490-589 _team_fallback_card (API-Sports then ESPN crest); _resolve_primary order (source photo gate -> team photo -> crest card; person/athlete -> vision -> TMDB / club crest; else deterministic -> vision) classifier kind rung order (None, None, “none”) 1 / 1 KEEP routing.
entity_image.py:619-643, 723-762 resolve_desk_image_subject walks image_subjects best-first; resolve_desk_image_from_take re-runs on the composed take when img_source == "none" model output art none 4 / 2 KEEP the model rung.

Count: 0 semantic (all routing on image_subjects), 7 structural.

utils/vision_image.py, utils/xai_image.py, utils/image_gen.py, utils/image_inspect.py, utils/grid_vision.py, utils/image_hash.py, utils/image_codec.py

file:line pattern input decision fail today tested? verdict reason
vision_image.py:41-54, 133-150 sniff_media_type magic bytes; normalize_vision_image transcode bytes media type sent inline None (drop frame) 1 / 1 KEEP structural.
vision_image.py:163-189 bounded_vision_url (hostname == "pbs.twimg.com" -> name=large) URL pixel bound unchanged 1 KEEP URL.
image_gen.py:101-110, 304-328 _FAILOVER_KINDS {safety_reject, transient}; _SAFETY_SIGNATURES (“safety system”, “moderation”, “content_policy”, “content policy”, “safety”) substring on the 400 body in _is_safety_reject OpenAI error body (upstream API text) whether a refused image request fails over to Grok no failover -> text reply, no picture 0 / 1 KEEP error-body classification; a miss costs one picture on a rare path. List it, do not model it.
image_gen.py:73-96, xai_image.py:67-82 _ASPECT_SIZES / _ASPECT_RATIOS on the model’s aspect keyword model output (“square” / “landscape” / “portrait”) canvas size square 1 / 1 KEEP enum on model output.
image_inspect.py:20-34, 101-102, 120 _NAMED color anchors; is_grid (sx > 0.25, px >= 8); brightness bands (80 / 175) pixels facts handed to the vision model “no grid” 10 (name collision; describe is common) KEEP numeric; exists to ground the model.
grid_vision.py:31, 56-98 _MAX_CLEAN_DIST 70; saturation > 60 / 0.6 in _largest_saturated_bbox pixels grid parse confidence drop 1 / 0 KEEP numeric, deliberately non-LLM.
image_hash.py:40, 74-77 DEFAULT_MATCH_THRESHOLD 12 in is_match hashes pre-filter candidates for the vision confirm vision decides 1 KEEP the docstring already frames it as find-first, vision-decides.
image_codec.py limits + JSON decode 1 KEEP structural.

Count: vision_image 0/3; xai_image 0/2; image_gen 1 semantic (KEEP) / 2; image_inspect 0/3; grid_vision 0/2; image_hash 0/1; image_codec 0/2.

utils/media_edit.py, utils/video_trim.py, utils/clip_pick.py, utils/video_fetch.py, utils/video_ingest.py, utils/audio.py, utils/audio_tags.py

file:line pattern input decision fail today tested? verdict reason
media_edit.py:190-239 _HEIC_BRANDS, _VIDEO_EXTS, _IMAGE_EXTS, kind_from_name (content-type / extension), kind_of (magic bytes: GIF -> video, ftyp brand, matroska) upload bytes / name / content type image vs video op list ”” (unsupported) 1 / 1 KEEP structural.
media_edit.py:153-169, 253-309 _OP_SPECS registry; normalize clamps; direction.lower().startswith("v"); fmt in _IMAGE_FORMATS model-written <media> tag numbers op bounds clamped 1 KEEP numeric clamps on model output.
media_edit.py:484-517, 648-693 _COPY_CONTAINERS by source extension; _copies; _next_attempt / _smaller retry ladder our request container / retry None 0 / 1 / 0 KEEP structural.
video_trim.py:69, 132-157 _PTS_RE on ffmpeg stdout; pick_cut_point (window) ffmpeg output cut point fixed cut 1 / 1 KEEP log parsing + numeric; the docstring records why this is not a vision pass.
clip_pick.py:106-138, 177-221 most_replayed_peak (marker key names startMillis / start_ms / startTime / start, weights intensityScoreNormalized / intensity / score, skip first 10s); loudest_window; window_start ScrapeCreators JSON / ffmpeg loudness where the clip starts next signal / 0 1 each KEEP measured signals; the docstring records the owner question and the reasoned “no prompt for now”.
video_fetch.py:126-215 YOUTUBE_HOSTS, TIKTOK_HOSTS, TWITCH_HOSTS, X_HOSTS, INSTAGRAM_HOSTS, _MEDIA_FILE_EXTS, _host_matches, is_video_url, is_direct_media_url, is_x_url, is_tiktok_url, is_instagram_url, is_tiktok_photo_url (“/photo/” in path) URL which fetch route (fxtwitter / ScrapeCreators / yt-dlp / none) not a video 1-2 each KEEP URL routing.
video_fetch.py:218-290 canonical_video_url, youtube_id (_YOUTUBE_ID_RE, path prefixes), canonical_key (yt: / x: / url:) URL cache key (one paid fetch per clip) None 1 / 1 / 2 KEEP URL.
video_fetch.py:150, 293-328 _CAPTION_LANGS preference, pick_caption_track (human before auto, json3 before vtt, any language fallback) yt-dlp subtitle maps which caption track audio + STT 1 KEEP upstream keys.
video_fetch.py:346-366, 683-691 flatten_vtt line filters (" --> ", “WEBVTT”, startswith “Kind:” / “Language:” / “NOTE”, isdigit, _VTT_TAG_RE); sc_flatten_transcript (startswith “WEBVTT” or “ –> “) caption file text transcript text leaks a header line 1 / 1 KEEP format parsing.
video_fetch.py:1060-1072 exceeds_x_video_limit (> 120s), x_trim_target_secs duration trim before X upload not trimmed (unknown duration) 1 KEEP numeric.
video_ingest.py:166-173, 243-251 _enabled (env in (“1”,”true”,”yes”,”on”)); _needs_summary (lang not startswith “en” or > 1500 chars) env / yt-dlp lang code summarize or translate via the Haiku summarizer raw transcript – / 0 KEEP the model pass is the summarizer itself.
audio.py _clip_args, _video_clip_args, _OBSCURE_FILTER, bounds numbers ffmpeg argv None 1 / 1 KEEP structural.
audio_tags.py:31-85 ALLOWED_TAGS allowlist (11 tags) + _TAG_RE (lowercase words in brackets) in filter_to_allowlist, strip_all_tags model output (voiced line) which [tag]s reach ElevenLabs; strips all tags from every text path drop the tag (absent) 1 / 1 KEEP an allowlist is the right fence: an unknown tag is spoken aloud, so the fail direction must be deterministic.

Count: media_edit 0/5; video_trim 0/2; clip_pick 0/3; video_fetch 0/8; video_ingest 0/2; audio 0/2; audio_tags 1 semantic (KEEP) / 0.

utils/radio.py

file:line pattern input decision fail today tested? verdict reason
radio.py:112-131, 293-300 COVERED_FORMATS {“rhythmic”, “top 40”, “urban”} whitelist (+ CUT_FORMATS as the record) in moves (fmt.lower() not in COVERED_FORMATS) Mediabase format names (upstream) which format charts produce stories a renamed or new format is silently OUT (documented: prefer absent) 3 KEEP an owner-curated whitelist with the reasoning recorded; a model cannot know the room’s taste.
radio.py:79-86, 133-148, 185-238 MIN_SPINS 200, MIN_SPIN_PCT 25, MIN_ADDS 5, lands_high rank cap, _breakouts (rank fell -> not a breakout), _adds (median-relative) numbers which moves are stories none 2 / 0 / 0 KEEP numeric, measured.
radio.py:309-349 standing(rows, title, artist): _norm (accent fold, punctuation strip) then want_t in _norm(r.track) AND want_a in _norm(r.artist) Mediabase rows vs a chart lane’s song the radio context block attached to another lane’s post ”” (absent) 29 (name collision) / – KEEP, flag substring identity on both sides; song_key / fold_tokens is the house identity (used in artist_board two functions down). Wire the home in rather than a second matcher. Not a model job.
radio.py:501-529 board_groups via lead_party(artist, known) + fold_tokens Mediabase credit vs watchlist which moves fold under one act raw lead 1 KEEP uses the shared identity home.
radio.py:154, 374-389, 410-421 _KIND_EYEBROW, card_fields, _radio_row our kind copy “Move” 6 KEEP own enum.

Count: 2 semantic (KEEP), 4 structural.

utils/artist_socials.py

file:line pattern input decision fail today tested? verdict reason
artist_socials.py:52-156 _ARTIST_SOCIALS curated map (29 artists) keyed by fold_tokens; socials_for, name_for_handle, roster_handles (plat in ("instagram","tiktok")) watchlist artist name / fetched handle which official accounts seed the artist curator; the name a caption may use skip (absent, never guessed) 1 / 1 / 1 KEEP the docstring makes the case: a guessed handle seeds an impersonator; a model would guess. Curated on purpose.

Count: 1 semantic (KEEP), 0 structural.


Top 10 HAIKU candidates in this scope (ranked by brittleness x user impact)

  1. market_cards.py:2448-2860 hero / outcome cluster (outcome_split + the 200-entry _OUTCOME_VERBS, named_rank, title_rank, winner_rank, award_slot, split_rank_authority, event_title_split, statement_title, how_much_title, subject_hero; plus market_drop _slot_figure, market_alert _ending_soon_subject / _clean_authority, alert_triggers outcome_label, market_drop _kalshi_event_subject). Every market card’s hero and caption, on every market post. A verb or phrasing outside the registry ships a raw question as the hero. One Haiku call per market ticker returning {hero, caption, subject, outcome, rank, authority}, durable-cached, with the regex ladder as the fallback, retires about 25 regexes across 4 files.
  2. markets.py:4643-4735 _is_cut_runner_up_event / _names_non_leader_slot / _looks_like_music. Drops whole events at discovery. A false positive removes a family silently; a false negative posts a “#2” market as if it were the leader. One cached call per new event ticker.
  3. retrospective.py:60-138 _FRAMES (+ states_past_interval). Throwback detection on wire posts for four desks. The regex is anchored narrow by design and its own docstring lists the phrasings it cannot reach. A miss reproduces the 2026-09-14 incident (stale news shipped as current). Cheap per candidate, cache by post id.
  4. sports_stories.py:323-439 classify_sport (regex-first). The tag decides the diversity penalty, which decides which story posts. A wrong regex tag (shared team names: Jets, Giants, Eagles, Cardinals, Kings) never reaches the Haiku rung and is stamped as confident. The model rung, cache and vocabulary fence already exist (classify_sports_beats); invert the order.
  5. markets.py:6453-6540 _kalshi_competition_to_sport. Substring ladder from a competition name to the internal sport tag, which selects the settlement feed and logo host. “football” defaults to NFL. Only NEW series need a call; cache by series ticker.
  6. market_outcome.py:747-830 names_match / normalize_name / _in_field. Matches the Haiku-extracted winner to a market leg by a 6-char prefix. Fold into the existing winner_extract call (return the label index); zero extra calls.
  7. market_subject_image.py:344-373, 590-724, 915-950 detect_media_brand, is_youtube_daily_chart_market family cues, is_netflix_rank_market, artist_subject_name. Four regex extractors on the same title that image_subjects already classifies (kind=org / music_release / musician + name). Read the classifier’s answer; drop the regexes.
  8. market_subject_image.py:726-900 take_references_leg (YouTube fabrication gate). Token folding cannot see a paraphrase, so a correct take is dropped (fail-closed) while the Netflix sibling already uses the Haiku market_take_names_leader judge. Unify: regex as the pre-check, Haiku on the “no” answer, and run the judge for every chart family (market_drop.py:2988-3022 today covers two).
  9. markets.py:4580-4640 qualify_platform_metric _PLATFORM_PATTERNS. Nine hand-listed platforms. A new one ships an ambiguous “Views” title on the card. Cache by event ticker.
  10. markets.py:6196-6262 kalshi_channel_routing cue sets. Decides which lanes a channel gets from its name and topic; a channel without one of the cue words is dark. One call per channel, cached until rename.

Honorable mentions (HYBRID, lower impact): detect_league direct use in sgo_snapshots (markets.py:5975); market_chart_tag metric-word list (market_cards.py:2101); is_music_chart_market (market_cards.py:157, duplicate of the classifier kind); frames_as_past output fence (retrospective.py:265-334, five title-shaped patches in one review cycle).

Deliberately NOT candidates (checked, keep deterministic): all settlement paths (api_sports status sets, _winning_team, bookie league sets), all price / count thresholds, take_references_leg-style number grounding (ungrounded_numbers, retrospective_values), audio_tags allowlist, artist_socials and radio.COVERED_FORMATS curated maps, steam_art.pick_app exact identity, espn.match_team_logo (ambiguity refuses), URL / host / magic-byte routing everywhere.

Count summary (semantic / structural sites per file)

file semantic structural
utils/markets.py 18 12
utils/market_cards.py 17 6
utils/market_subject_image.py 9 2
utils/market_outcome.py 4 2
utils/market_chart.py 3 3
utils/alert_triggers.py 1 0
utils/market_triggers.py 0 1
utils/market_siblings.py 0 0
utils/kalshi_price.py 0 1
utils/kalshi_ladder.py 6 0
cogs/market_drop.py 11 3
cogs/market_alert.py 5 6
cogs/bookie.py 0 7
cogs/betting_board.py 0 4
cogs/betting_alert.py 0 3
cogs/betting_value.py 0 4
utils/called_shot.py 0 2
utils/bet_corrections.py, odds_adapt.py, odds_compare.py, leaderboard.py, table_card.py, ranking_strip.py 0 1 each
utils/sports_stories.py 4 1
cogs/sports_desk.py 1 3
utils/api_sports.py 0 8
utils/espn.py 0 8
utils/highlightly.py 0 2
cogs/sports_boards.py 0 6
utils/sports_boards.py 0 8
utils/playercard.py 1 5
cogs/playercard.py 4 0
utils/retrospective.py 4 0
utils/scrapecreators.py 0 5
utils/steam_art.py 1 1
utils/entity_image.py 0 7
utils/vision_image.py 0 3
utils/xai_image.py 0 2
utils/image_gen.py 1 2
utils/image_inspect.py 0 3
utils/grid_vision.py 0 2
utils/image_hash.py 0 1
utils/image_codec.py 0 2
utils/media_edit.py 0 5
utils/video_trim.py 0 2
utils/clip_pick.py 0 3
utils/video_fetch.py 0 8
utils/video_ingest.py 0 2
utils/audio.py 0 2
utils/audio_tags.py 1 0
utils/radio.py 2 4
utils/artist_socials.py 1 0
total 94 154

Verdict tally over the 94 semantic sites: HAIKU 9 (counting the cards cluster as one), HYBRID 22, KEEP 63.

Coverage gaps found while grepping tests (0 direct test references for a decision symbol): outcome_label (alert_triggers), _looks_like_music, _norm_game_tokens, _kalshi_player_from_label, _field_clause, cited_hosts, normalize_name, market_talk_phrase, _clean_authority, _race_label, _rung_subject, _split_matchup, _is_degenerate_side, _is_world_cup_football, _line_is_known_stale, _needs_pricing, _resolve_member_id, _interval_count, _streak_length, _table_order, _on_today_et. Most are covered through their callers; outcome_label and _resolve_member_id are the two where a direct test would pin a user-visible decision.

Files that persisted to disk during reading (large Bash outputs) were read back in full; nothing was sampled. No files were modified.

Inventory: conversation, X, gates, output checks

Method and caveats


A. Model-output checks and output hygiene

claude_client.py (sites that inspect model OUTPUT or user INPUT)

file:line pattern input inspected decision it drives fail direction tested? verdict reason
claude_client.py:2718-2760 is_empty_response "EMPTY" exact / prefix (“E”,”EM”,”EMP”) / first line / last sentence every compose surface’s model output skip the slot vs ship fail toward SKIP (false positive drops one post) 3 files KEEP sentinel protocol; incident-backed (2026-09-05/06 leaked reasoning, 2026-09-11 “EM” fragment)
claude_client.py:1649-1725 _SELF_CORRECTION_MARKERS + _strip_self_correction / _after_correction_clause 13 lowercase phrases (“let me redo”, “scratch that”, “follow my rules”…) matched per paragraph model output (market drop, bucket board, wheel items) keep last real paragraph vs ship whole deliberation fail toward SHIP (no marker = unchanged) 0 by name (has_self_correction twin in output_checks has 2) HYBRID the list grows one incident at a time (“let me read” added after a live leak); a Haiku “is this a finished post or the model’s working?” judge would catch the next shape. Keep the list as the free fast path
claude_client.py:1628-1641 _final_card_line last paragraph == "EMPTY" /card blurb output blank vs use last paragraph fail toward ship last paragraph 0 KEEP protocol
claude_client.py:1206-1233 _extract_track_line startswith("track:") per line, then rfind("track:") + " - " music_post output split body vs TRACK query fail toward no link 0 KEEP protocol tag
claude_client.py:1236-1262 _extract_xpost_directive startswith("xpost:"), mode in (tweet, art, both), "x.com/" in token music_post output crosspost mode + exact URL fail toward default art 0 KEEP protocol tag
claude_client.py:404-409 _TRENDING_LABEL_LEAK \[[A-Za-z0-9]\] or clip [A-Z0-9] trending react output retry the react (label leaked) fail toward ship 0 KEEP narrow scaffolding leak check
claude_client.py:412-422 _clip_for_reaction link in text react output which clip the react picked None -> retry cannot resolve 0 KEEP structural
claude_client.py:18276-18293 preflight_order verdict parse upper.startswith("ALLOW"/"PLUMBING"/"REJECT") Sonnet output /order allow / plumbing / reject fail CLOSED (unparseable -> reject) 5 files KEEP the protected-path matching is ALREADY the model (Sonnet reads the path list in the prompt); no deterministic path list exists in code
claude_client.py:18356 classify_abuse parse startswith("ABUSE") Haiku output abuse flag fail OPEN (False) 4 KEEP label protocol
claude_client.py:14455 wire_subject_named not startswith("NO") Haiku output skip the identity fetch fail OPEN (True = named) via test_claude_client? (0 by name) KEEP label protocol
claude_client.py:14595-14597 crosspost_media_mismatch verdict startswith("COPY"/"UNRELATED"/"NO") vision judge output drop the attached image fail toward ok 3 KEEP label protocol
claude_client.py:14678, 14753, 14794 lines[0].upper().startswith("NO"/"YES") vision judge outputs (clip mismatch, tour admat, identity frame) drop clip / classify admat / identity mismatch mixed (documented per method) 1-3 KEEP label protocol
claude_client.py:867-875 _parse_match_verdict '"match":true' substring or == "true" Haiku song-guess judge output award the point fail CLOSED 1 KEEP protocol
claude_client.py:878-910 _extract_scored_json + _parse_chimein_score (913) / _parse_quiet_room_score (952) / _parse_discourse_score (1265) fence-strip regex, first {...}, salvage "score": <n> Haiku scorer outputs score vs 0.0 skip fail toward SKIP (0.0) except discourse salvage 1 each KEEP the reusable scored-JSON spine
claude_client.py:1361-1400 _parse_topic_duplicate JSON + salvage "duplicate": true/false Haiku dedup judge tri-state verdict None = cannot judge (caller keeps mechanical verdict) 1 KEEP protocol
claude_client.py:1403-1443 _parse_qualifier_verify, 1446-1471 _parse_names_leader, 1315-1358 _parse_outcome_value / _parse_winner_pick JSON literal-boolean checks, bool-is-int guards Haiku judge outputs attach qualifier / suppress fabrication / agreement gate tri-state / fail-open / fail-closed as documented 1 each KEEP protocol
claude_client.py:137-171 _parse_market_intent, 429-462 _parse_mention_intent (_MENTION_INTENTS, _MENTION_PERIODS), 465-492 _parse_music_mention ("none"/"null"/"n/a"/"unknown" -> empty), 501-595 _parse_music_news (_MUSIC_NEWS_KINDS), 617-670 _parse_verify_claim, 184-280 _parse_image_subject(s) (_IMAGE_SUBJECT_KINDS, _IMAGE_SUBJECT_SPORTS, “soccer”->”football”), 1104-1138 _parse_video_subject (VIDEO_SUBJECT_KINDS, _ANCHORED_VIDEO_KINDS), 99-134 _parse_sport_beats, 1044-1078 _parse_gif_pick, 750-776 _parse_picked_tickers (startswith("NONE")), 779-811 _parse_album_track_matches, 673-697 _parse_keyword_terms (":" in term drops preamble), 700-723 _parse_song_pool (" - " in line) closed-set enum validation of model JSON Haiku/Sonnet classifier outputs route / drop each documented; mostly fail-open to “none”/None, verify_claim + album_track fail CLOSED 1 each (music_mention 0) KEEP these VALIDATE a model answer; the classification is already the model
claude_client.py:283-369 _artist_named_in / _artist_named_in_strict / _mark_grounded_artists accent/case-folded whole-word substring; strict = case-sensitive too classifier’s artist name vs SOURCE text artist_grounded stamp -> media-gate exemption + portrait attempt fail toward UNGROUNDED (image still faces the gate) via test_entity_image (5 files hit _IDENTITY_VERIFIED_ART) KEEP incident-backed (#2691 “Future”/”Common”/”王菲 DJ”); this is a groundedness check on the model, so it must stay outside the model
claude_client.py:726-747 _parse_wheel_items has_self_correction then utils.wheel.is_narration per line Sonnet wheel output drop a slice fail toward KEEP slice 1 HYBRID see utils/wheel.py below
claude_client.py:19992-20021 draft_x_reply tic guard tic_hits(first) -> one re-ask naming the phrase; retry taken only if clean Sonnet X reply draft re-ask vs ship fail toward ORIGINAL draft via test_x_reply_draft (1) KEEP measured: naming the phrase works, showing drafts made tics worse (9/35 -> 19/36)
claude_client.py:20025, 20103 draft.upper().startswith("EMPTY") or len(draft) > 400 X reply / mention reply draft drop draft fail toward drop 1 KEEP protocol + length cap
claude_client.py:999-1041 _IMAGE_FAILURE_PHRASES / _GENERIC_FAILURE_PHRASES / _IMAGE_BLOCK_RE / _is_image_error substring of API error text; content.(\d+).image Anthropic 400 error message drop ONE candidate image vs treat as request error fail toward “not an image error” 0 by name KEEP structural error-string parsing (Codex review PR #2743)
claude_client.py:1566-1625 _SAMPLING_MODELS, _THINKING_ON_BY_DEFAULT, _ALWAYS_THINKING, _OPENAI_TRUNCATED_STOPS model id in frozenset model id request shape (temperature / thinking / tool_choice) 400 if wrong via test_claude_client KEEP capability table; CLAUDE.md says measure and extend
claude_client.py:10428-10464 _MEMORY_FENCE prompt text only (no code check) n/a n/a n/a test_memory patches _call KEEP (constitutional) the fence is prompt; the CODE check is utils/output_checks.memory_note_violations (url/mention/discord_id regexes)
claude_client.py:10762 memory_rollup stop_reason == "max_tokens" -> raise MemoryRollupTruncated stop reason never persist a truncated rollup fail CLOSED 3 KEEP structural
claude_client.py:1728-1744 _has_perplexity_grounding is_hedged(perplexity_context) Perplexity block skip forced web_search retry fail toward forced retry via test_perplexity HYBRID (see perplexity) rides on the _HEDGE_MARKERS list
claude_client.py:3215, 7087-7188, 7708 type.startswith("web_search") tool spec dicts tool routing structural - KEEP structural

Structural (compressed): _parse_* fence-strip regex ^\w*\s*|$ (many), _image_block_source startswith("data:") (1163), _recent_self_block, _format_kalshi_candidate sub.lower() not in title.lower() (850), _within_platform_rank_labels, _time_context, _openai_stop_reason.

utils/output_checks.py (Tier-1 deterministic checks on model output)

file:line pattern input decision fail direction tested? verdict reason
L34-41 _TOOL_NARRATION_RE, L45-48 _THINK_PIECE_WORDS regex battery + word list model output check_text flags flag = drop/retry per surface test_output_checks KEEP mechanical hygiene
L62-66 _CONTEXT_TAG_RE, has_context_tag_leak leaked <attached_images> etc. model output drop fail toward ship yes KEEP protocol leak
L102-113 strip_em_dashes, strip_markdown_emphasis, _CITE_TAG_RE rewrite model output formatting n/a yes KEEP (formatting) excluded class
L193-209 too_short_to_ship (MIN_POST_CHARS=15) length model output skip fail toward skip yes KEEP length gate
L285-301 _SELF_CORRECTION_RE / _DRAFT_SEPARATOR_RE, L828 has_self_correction regex model output drop line (x_mentions, wheel, boards) fail toward ship 2 HYBRID same class as _SELF_CORRECTION_MARKERS; one incident-per-phrase growth pattern
L325-335 _ALLTIME_CLAIM_RE, L350-411 _ARTIST_POSSESSIVE / _NON_ARTIST_POSSESSIVE / _ORDINAL / _POSSESSIVE_CLAIM_RE, has_career_claim / has_alltime_claim regex over superlatives / ordinals model output (music desk, x_mentions) drop an ungrounded career/all-time claim fail toward DROP 1-2 HYBRID the judge (music_desk_score) already scores groundedness; these exist because it scored true-but-banned lines 0.7+. A Haiku “does this line make a career/all-time claim the source block does not state?” call with the source block is the semantic form. Keep regexes as the free first pass
L458-535 decline-narration battery (decline_narration_hits / has_decline_narration) ~15 regexes (“nothing to add”, “no new info”, “same story as”…) model output drop a post that is the model narrating its own decline fail toward drop 3 HAIKU this is exactly “is this a post or a refusal?” – the brittle list already overlaps is_empty_response’s trailing-sentinel fix. A Haiku classifier (purpose="decline_narration", fail toward drop) replaces the whole battery and the next incident
L570 _ROW_ARITHMETIC_RE, L597-671 _HITS_SOURCE_RE / _MARKET_SOURCE_RE / _CROSS_SOURCE_COMPARATIVE_RE / _SETTLED_COUNT_RE, names_hits_source / has_cross_source_comparison regex model output (music desk) drop cross-source comparison (“Luminate says X vs Kalshi Y”) fail toward drop 0-1 HYBRID the rule is semantic (“compares two sources’ numbers”); the regex list of source names drifts as sources are added. Haiku with the two source names as input
L705-710 _ROW_TALLY_RE, row_tallies / ungrounded_row_tally / has_row_tally regex counting “N of the top 10” model output vs source rows drop ungrounded tally fail toward drop 1 KEEP arithmetic check against the source block; a model cannot count reliably
L799-804 _ATTRIBUTION_TAG_RE regex model output strip/flag n/a yes KEEP protocol
L866-881 _METRIC_FOREIGN_WORDS, metric_foreign_word / album_metric_named word list (streams / units / sales) model output vs the metric of the source drop a line that names the wrong metric fail toward drop 1 HYBRID the vocabulary is finite (3 metric families) but grows with new ladders; Haiku with the source metric named is the robust form
L903-940 memory_note_violations _MENTION_RE, _DISCORD_ID_RE, URL regex memory note output reject the note (fence) fail CLOSED yes KEEP (constitutional) must stay deterministic; it backs the fence
L973-1007 check_text per-surface aggregator model output which checks apply n/a yes KEEP dispatcher
L1013-1101 ungrounded numbers (_WORD_VALUES, _MAGNITUDES, _NUMBER_RE, _ORDINAL_DIGITS_RE) number extraction + set difference vs source model output vs source block drop a number the source does not contain fail toward drop yes KEEP numeric grounding is the one thing the regex does better than a model
L1115-1153 _AGE_CLAIM_RE / _AGE_GROUNDED_RE regex model output vs source drop an ungrounded age fail toward drop yes KEEP numeric grounding
L1205-1210 _CAREER_ORDINAL_RE, ungrounded_career_ordinals regex “third No. 1” model output vs source drop fail toward drop 2 HYBRID ordinal extraction is mechanical; whether the source SUPPORTS it is semantic (the qualifier_verify Haiku judge already exists for the newsroom path – reuse it)
L1283-1407 _RANKING_CLAIM_RE / _RANKING_STOP, ungrounded_ranking_claims regex + stopword list model output vs source drop an ungrounded ranking claim fail toward drop 2 HYBRID same as career ordinals

Module docstring states these are deliberately the deterministic Tier-1 half beside the model judges. Volume: every desk compose (music_desk, x_mentions, pop/sports/cinema desks) runs check_text per slot; a Haiku replacement adds one cheap call per compose.

utils/slop_score.py

| L46-64 _NOT_X_BUT_Y regex battery, L82-87 _lexicon_re() over data/slop_lexicon.json, L90-116 slop_hits/slop_score | vendored EQ-Bench lexicon | model output (X reply edits, evals) | telemetry score on x_reply_edit; eval trend | n/a (score only) | 4 files | KEEP | a deterministic lexicon score is the POINT (it is the cheap complement to the LLM register judge in scripts/dryrun_x_reply_voice.py); replacing it with a model removes the independent signal |

utils/x_reply_draft.py

| L68-83 _TICS, tic_hits | 16 phrases | Sonnet draft | one bounded re-ask | fail toward original | 1 | KEEP | calibrated (26% vs 7% baseline); list is the owner’s named tics. A model “does this lean on a stock phrase” is the thing that measurably made it worse | | L110-126 DRAFT_MARKER / is_draft_message, L132-137 _STAGING_MARKER / is_staged_audit | substring markers on bot-logs messages | Discord message text | is this a draft / an audit | structural | 1 | KEEP | own protocol | | find_x_status_link, beat_subject sentence split | regex | text | structural | - | 1 | KEEP | structural |

utils/perplexity.py

| L107-119 _HEDGE_MARKERS, is_hedged | ~10 phrases (“couldn’t find”, “no recent information”…) | Perplexity answer text | (a) hedged telemetry flag; (b) claude_client._has_perplexity_grounding -> skip the forced web_search retry | fail toward NOT hedged (grounding trusted) | 2 | HYBRID | (b) is a real decision on prose; a Haiku “did this answer actually find anything?” is better than the phrase list. Volume: one per room post that uses Perplexity | | L702-748 _MAGNITUDE_RE / _NOT_UNITS_RE / _SENTENCE_SPLIT_RE, strip_industry_projection | regex | Perplexity text (model INPUT) | drop HITS first-week projection sentences before the model sees them | fail toward keep sentence | 1 | KEEP | measured 3/3 -> 0/3; numeric pattern | | L79-95 _SEARCH_CONFIG, L417-480 _CATEGORY_QUERIES, L506-696 build_search_query | purpose -> recency/query templates | surface name | search recency | n/a | yes | KEEP | config table |


B. Dedup, engagement, curation

utils/dedup.py

| L48-77 thresholds (0.6 text sim, 40-char shared run, 0.5 content overlap, MIN_CONTENT_TOKENS 8), L83-89 _STOPWORDS, L199-238 duplicate_match | token/link mechanics | candidate post vs recent posts | mechanical dup signal | catch direction fail-open | 0 by name (arbitrated_match 1) | KEEP | already HYBRID: arbitrated_match (L282-342) hands every mechanical hit AND every miss to claude.topic_duplicate (tri-state); this is the pattern the rest of the repo should copy |

utils/engagement.py (bulk ~300k-message scan, hot path)

| L67-93 _MEE6_TRIGGER_DOMAINS, has_music_trigger_domain | host substrings | message content | MEE6 fingerprint (music trigger) | fail toward not-trigger | 1 | KEEP | hot path; domain match is structural | | L44-51 LAUGH_EMOJI / BANGER_EMOJI, L107 _LAUGH_RE, L250 is_own_laugh | emoji set + regex | reactions / content | laugh / banger counts | n/a | 1 | KEEP | counting rule; a model per message is not viable at 300k | | L616 question = endswith("?"), L620-622 caps ratio > 0.7 | shape heuristics | content | “asks questions” / “shouts” stat | n/a | via test_engagement | KEEP | hot path; stats not decisions | | L159-175 attachment_kind ext lists, author_token via is_embed_fixer | ext / name | attachment / author | bucket | n/a | yes | KEEP | structural |

utils/curator.py

| L224-228 _IMAGE_TOPIC_TERMS, L231-262 topic_is_image_room / prefers_media | word list (“art”, “photo”, “fit”, “pics”…) matched in channel topic/name | channel topic + name | route the slot to the IMAGE judge (pick_curator) vs the TEXT judge (pick_curator_text) | false negative -> image reposts go to the text judge, which rejects them (surface goes quiet in that room) | 1 | HAIKU | per-slot per-channel (low volume, ~once per curator tick per room); a Haiku “is this room a visual room?” over name + topic + 6 example posts, cached per channel for a day, ends the word-list drift | | L49-55 _RESERVED handles, _PLATFORM_ALIASES, is_postable | handle / host sets | URL / handle | skip own + platform accounts | fail toward post | 0 | KEEP | structural |

cogs/curator.py

| L171-178 _VISION_UNFETCHABLE_HOSTS, _needs_vision_download | host substrings | media URL | download bytes vs let the API fetch | fail toward fetch | 1 | KEEP | structural | | L181-191 _human_endorsed | reaction emoji in ENDORSING_EMOJIS + non-bot reactor | reactions | seed eligibility | fail toward not endorsed | 1 | KEEP | rule | | L345-370 mode decision | prefers_media(...) + candidate media count | channel | image vs text judge | as above | 1 | HAIKU (same site) | same as utils/curator.prefers_media | | pick_curator / pick_curator_text with pick_error vs none_fit | Haiku vision/text judges | candidates | which post to repost | pick_error = skip slot, none_fit = skip | 3 | KEEP | already model |

utils/curate.py

| L56-65 is_curate_post (lstrip().startswith("🔁") or "via @"), L75 ENDORSING_EMOJIS | markers | message content | curator seed eligibility | fail toward not a curate post | 1 | KEEP | own repost marker |

cogs/clipboard.py / utils/clipboard.py

| route_caption slug, _PERIOD_DAYS, out == "curate" | structural | args | routing | n/a | yes | KEEP | routing itself is claude.route_curator (Haiku vision) |


C. X pipeline

utils/x_mentions.py

| L185-189 _QUESTION_STARTS frozenset (“is”, “did”, “how”, “what”…) + L197-204 _REQUEST_CUES / _REQUEST_RE, L208-224 asks_question | first-word set + cue regex + “?” | mention tweet text (user INPUT) | fetch the parent post and run a SECOND Haiku scan on it (PR #3081) | fail toward NOT a question (parent not fetched -> no_facts -> no reply draft) | 1 (_QUESTION_STARTS 0) | SHIPPED (A7, #3419): the scan’s asks_question flag through mention_asks, regex fallback | scan_music_mention (Haiku) already runs on the same text; add "asks_question": bool to that one JSON answer -> zero extra calls, no cue list. Volume: per webhook mention (low) | | L60-70 clean_scan_text, L73-94 scan_watched via utils.names.name_in_text | handle/URL strip + name match | tweet text | watchlist hit | fail toward no hit | 1 | KEEP | already arbitrated by scan_music_mention in cogs/x_mentions._resolve_subject (L664-733: error sentinel keeps lexical hits, clean empty read drops them) – this is the good HYBRID shape | | L101-111 _RETWEET_KEYS / _RETWEET_FLAGS is_retweet, L125-138 _BOT_AUTHOR_FLAGS, _REPLY_TO_USERNAME_KEYS is_reply_to_own, _REPLY_TO_ID_KEYS | payload key sets | twitterapi.io payload | skip RT / own reply | structural | 1 / 0 | KEEP | structural |

cogs/x_mentions.py

| L664-733 _resolve_subject | lexical + Haiku with error sentinel; aw.fold_tokens song ownership | tweet | which artist/song the lanes run on | documented above | 1 | KEEP (HYBRID exists) | reference implementation | | _is_stale 24h, _targets_open_game_round key list, _entry_marker | time / keys | tweet | skip | structural | yes | KEEP | structural | | L1092-1108 & board lane: is_empty_response, has_self_correction, ungrounded_row_tally, music_desk_score >= 0.6, _topic_dupe | see A | compose output | ship / skip | see A | yes | KEEP | already the layered gate |

utils/x_crosspost.py

| L647-650 _IDENTITY_VERIFIED_ART frozenset (art rungs that skip the vision judge), L662 _DROP_VERDICTS = {"subject","unrelated"}, L665-784 _media_gate (second-vote confirm), L787-857 _clip_gate | rung name in set; verdict in set | art source rung / judge verdict | attach image or drop | fail toward DROP the image (never the tweet) | 5 / 4 / 1 | KEEP | the gate IS a model (crosspost_media_mismatch / crosspost_clip_mismatch); the deterministic exemptions are incident-backed (#2691) | | L204-296 _MARKET_LINK_HOSTS is_market_link / strip_market_links, L208-211 _BARE_SOCIAL_RE, markdown strip, L233-264 _dedup_key sha256 | hosts / regex | line text | strip link / dedup | structural | yes | KEEP | structural |

utils/x_reply_draft.py (cog) cogs/x_reply_draft.py

| _OWN_HANDLES, L427 "tootsiesbar" in tweet.author.lower(), _CHANNEL_SOURCES allowlist, banger lane floors | handle / source sets, numeric floors | tweet | skip own / lane | structural | yes | KEEP | structural |

cogs/x_audience.py, utils/x_audience.py, utils/x_followers.py

| _LEADING_HANDLES_RE / _CANON_RE reply_edit_metrics (pure_cut), slop_score(posted); LOW_SIGNAL_FOLLOWERS=20 / LOW_SIGNAL_STATUSES=10 low_signal, MIN_COVERAGE=0.9 snapshot_trust | regex / numeric | posted reply vs draft; follower rows | telemetry; “low-signal” label in report; refuse to diff | measured thresholds | yes | KEEP | telemetry + numeric guards (measured 2026-09-14) |

utils/x_game.py, cogs/x_game.py

| L120-130 _artist_matches exact _norm_artist equality; grade_guess(..., DIFFICULTY_HARD); _is_guessable_title; _MIN_YEAR; parse_blurb_art _ART_TAG | normalized equality / shared grader / regex tag | player reply | award the round | fail CLOSED (no point) | 5 / 2 | KEEP | HARD mode is an owner ask (“name it right”); the Haiku judge_song_guess exists for the EASY near-miss band in /guess and is deliberately not used here |

utils/x_poster.py, utils/x_fetch.py, utils/twitterio.py, utils/x_filter_rules.py, utils/social_search.py, utils/social_graph.py, utils/social_profile.py, utils/grok_search.py

Structural only: _reject_detail JSON keys, x_len URL weight; _STATUS_RE, mp4 rendition pick (".m3u8", _RES_RE), type == "video"; _credits_exhausted_message = "credit" in detail.lower() (L336-340, drives a quota alert – KEEP, 1 test), _has_video_media type in (video, animated_gif), fetch_account_status status == "error" -> gone; _REL_RE published_age_days, parse_trending_phrases (list-marker strip, 2-8 words, <=60 chars, no “:”/no sentence end – KEEP, it validates a model list), _VISION_EXTS _cover_ok, verified = "verif" in custom_verify; _norm_platform aliases; _X_URL_RE / _X_HOST_RE citation parse (the freshness VERDICT is asked of Grok itself).


utils/reference.py

| L115-120 _CHART_INTENT regex | “chart”, “top 10”, “ranking”… | /ask user text | route to the tabular/chart extractor | fail toward prose page | 1 | HYBRID | intent from user text; the @Toots router already has classify_mention_intent (Sonnet); a Haiku “does this ask for a chart/table?” fits. Keep regex as a free positive shortcut | | L101-108 _TABULAR_CUE | cue list | page text | tabular extraction | fail toward prose | 0 | HYBRID | same | | L426-430 _SUBPAGE_HINT, L433-439 _ASPECT_SYNONYMS, _aspect_weights (regex weight 4), _relevant_subpage | synonym table | user text vs subpage titles | which subpage to fetch | fail toward main page | 0 | HAIKU | picking the relevant subpage title from a list given the question is a textbook small-model pick (pick_* spine exists); untested today | | L172-208 _TITLE_STOPWORDS / _title_tokens / _title_names_subject | token overlap | page title vs subject | reject a lead image / page that does not name the subject (#2663 WARDOGS) | fail toward REJECT | 1 | KEEP | groundedness guard on an external page; keep deterministic | | _EXTRACTORS, _SOURCES matchers | host / kind tables | URL | extractor | structural | yes | KEEP | structural |

| link_enrich L197-211 detect_platform, L240-266 link_bucket, _IMAGE_EXT / _VIDEO_EXT, L616 reddit "[deleted]"/"[removed]" skip, verify_url_alive 404/410 | host / ext / literal | URL / body | routing / drop | structural | yes | KEEP | structural | | watch_link L131-162 source_video_link host predicates, L231-275 _AGE_RE published_age_days + _MOMENT_MAX_AGE_DAYS=14 fresh_moment_results (drops UNDATED, fail closed), _PICKERS -> claude.pick_music_video/pick_trailer/pick_moment_clip, L507 claude.video_subject | host / date regex | search results | which candidates reach the Haiku picker | fail CLOSED | 1 | KEEP | deterministic rung ABOVE an existing Haiku picker; already HYBRID | | url_guardrail URL_RE, _BARE_WWW_RE, _TRACKING_PARAM_KEYS, _MD_LINK_RE, HOST_ALIASES / FIXUP_HOSTS, enforce_allowlist (strips a model-output URL not in the allowlist), verify_live_links | URL parsing | model output | strip link | fail toward STRIP (a real link can be lost) | 1 | KEEP | structural | | comments _REDDIT_HOSTS _route; reddit over_18 drop | host / flag | URL / payload | route / drop | structural | yes | KEEP | structural |

utils/message_search.py, cogs/message_search.py, utils/semantic_index.py, utils/embeddings.py

| message_search L181-203 parse_has alias dict, L234-243 parse_author_type, L246-252 parse_order, L172-178 normalize_keywords substring AND, L414-456 parse_match_reply (last ANSWER: line, NONE, digits-only) | query DSL / Haiku output | tool args | filter / parse | structural | 1 | KEEP | DSL + protocol | | cog: _URL_RE, mention/role/channel/snowflake regexes, _resolve_members exact-then-substring, _resolve_mentions, _extract_kinds content_type startswith image/video/audio, _DISCORD_CDN_HOSTS, dhash reverse-image with vision confirm | Discord parsing | args / messages | resolve | structural | yes | KEEP | structural; the ambiguous half is already vision | | embeddings SIMILARITY_FLOOR=0.2, rank_by_similarity; semantic_index | numeric | vectors | rank | n/a | yes | KEEP | already semantic |


E. Games, wheel, starboard, awards, polls, roles, scheduling

utils/wheel.py

| L133-141 _NARRATION_PHRASES, L144-147 _NARRATION_OPENERS, L150-164 is_narration (endswith(":") heading, openers startswith, phrases anywhere) | phrase lists | Sonnet wheel-slice lines (model OUTPUT) | drop a line that is the model narrating (“Actually let me redo cleanly:”) | fail toward KEEP the line (a bad slice can win the spin) | 1 | HAIKU | known false positives on song titles (narrowed after incident); per regenerate (low volume, already a paid Sonnet call); a Haiku “which of these N lines are not options?” over the whole list is one cheap call and removes both lists plus _SELF_CORRECTION_MARKERS for this surface | | _SPLIT_RE / _MARKER_RE, _unwrap_quotes, normalize_items, MIN/MAX_SLICES | parsing | typed text | items | structural | yes | KEEP | structural |

cogs/wheel.py

No semantic sites (custom_id regex _CUSTOM_ID_RE, saved/taken/failed result strings, isdigit parts box). All structural. KEEP.

cogs/games.py

| L732-806 _grade_guess (+ _tight, _mentions whole-word, _artist_named longest-token >=4 + SequenceMatcher >= _ARTIST_FUZZY_RATIO 0.82, _WIN_RATIO easy 0.80, _CLOSE_RATIO 0.75, echoes-a-word rule) | normalized containment + fuzzy ratio | chat guess (user INPUT) vs answer | win / artist / close / miss | fail CLOSED on win; “close” escalates to Haiku judge_song_guess (L2326-2329) in EASY only | test_games 2 + test_x_game | KEEP (HYBRID exists) | runs on EVERY chat message during a round (no model call allowed there); the near-miss band already escalates to Haiku | | L587-596 _FEAT_RE _extract_features, L604-634 _REMIX_CREDIT_RE + _NON_ARTIST_CREDIT_WORDS _title_credit_artists (startswith(("feat ","ft ",...))) | regex + word list | raw catalog title | who counts as a creditable guest | fail toward no credit | via test_games | KEEP | title-string parsing; a mis-parse costs one bonus point | | L296-316 _DECADE_TITLE_MARKERS _decade_title_ok | marker list per decade | playlist title (external) | accept a fuzzy playlist hit as era-grounded | fail CLOSED (reject) | via test_games | KEEP | guards an external fuzzy search; documented leak case | | L500-524 _MUSIC_HOSTS _music_lines_from | host list | message content / embed label | taste-signal lines for house mix | fail toward none | - | KEEP | structural | | L2923 is_rap = genre in ("hip-hop","rap"), _genre_for_model bundles | enum | genre arg | chart cap split | n/a | - | KEEP | config |

utils/game_lookup.py, cogs/awards.py, cogs/calendar_view.py, cogs/settings.py, cogs/tune.py

| awards L801-820 _quotable (EMOJI_RE, URL strip, <(?:a?:|[@#!&])[^>]*> strip, one [^\W\d_]{2,} word) | regex | highlight text | quote it in the reel or skip | fail toward SKIP | via test_awards | KEEP | “has at least one word” is mechanical; the reel already ranks by reactions | | calendar_view L90 _BALANCE_EXCLUDED_SURFACES, L631 s in ("curator","artist_curator") default 0; settings L197 _SINGLETON_CHANNEL_KEYS; game_lookup ASPECTS; tune unit labels | enums | config | UI defaults | n/a | yes | KEEP | config |

utils/starboard.py, cogs/starboard.py

| L117-126 looks_like_wall_channel ("fame" and ("wall" or "hall")) | substrings on channel name | channel names | auto-detect the MEE6 wall channel when none is pinned | fail toward None (report then says “couldn’t read the wall”; /star channel: pins it) | 1 | KEEP | override exists; a model per channel name is not worth a call; if it drifts, the fix is the pin | | STAR_EMOJIS, _FIXER_RE / _DISCRIM_RE is_embed_fixer / clean_author_name, _AVATAR_ID_RE, _JUMP_RE, classify_message; cog _find_member_by_name exact casefold (ambiguous -> None), _member_roster | regex / name equality | authors / names | fold reposts onto real members | fail toward drop | 1 / 0 | KEEP | structural |

utils/polls.py, cogs/polls.py, utils/roles.py, cogs/roles.py, utils/discord_info.py, cogs/discord_info.py, cogs/scheduled_events.py, utils/scheduled_events.py, utils/schedule_calendar.py

| roles L39-69 _EVERYONE_TOKENS / _BOTS_TOKENS, L93-149 SAFE_PERMISSIONS parse_permissions (refuses admin/ban/manage_*), _NAMED_COLOURS; cog _SELF_TOKENS, exact-then-substring resolvers | token sets | mod command args | grant a permission or refuse | fail CLOSED | 1 / 0 | KEEP (safety fence) | a permission allowlist must never be a model judgment | | polls _DURATION_RE / _UNIT_HOURS, _CUSTOM_EMOJI_RE / clean_emoji; discord_info _CHANNEL_KIND, _JUMP_RE / _MSG_ID_RE, resolvers; scheduled_events _resolve_event casefold substring; schedule_calendar | parsing | args | resolve | structural | yes | KEEP | structural |


F. Conversation, memory, chime-in, recap, commentator, scheduling

cogs/chimein.py

| L138 SKIP_VIBES = {"vulnerable","catchup","other"} | vibe label in set | Haiku chimein_score output | skip the slot | fail toward skip | via test_chimein | KEEP | closed-set policy over a model label | | REACT_THRESHOLD 0.45, CHIMEIN_QUALITY_THRESHOLD 0.6 (discourse_score), VOICE_PRESENT_THRESHOLD_DROP, arbitrated_match(catch=False), quiet_room_score, _pick_react_emoji | numeric on model scores | scores | post / react / skip | fail toward skip | yes | KEEP | already model-driven |

cogs/memory.py, utils/memory_context.py, utils/context_beat.py, utils/attribution.py, utils/nickname_seed.py, utils/users.py

| memory: is_empty_response, ACTIVITY_THRESHOLD, tag_memory_note (Haiku keyword tags), memory_note / memory_rollup + forgotten_names, _prepend_legend | sentinel / numeric | model output | store / skip | fail toward skip | 8 / 3 | KEEP (constitutional) | fence lives in prompt + memory_note_violations; /order must not touch | | context_beat framing-string routing; memory_context; attribution prompt text; nickname_seed; users mention_or_name | string routing | config | prompt framing | n/a | yes | KEEP | structural |

cogs/ask.py

| L248 _TOOLSIDE_WEB_MODELS | label set | model label | tool-side web search vs server tool | n/a | - | KEEP | capability table | | L1448-1451 find_image match in ("exact","text","content") inferred from args; L1688-1735 game_lookup aspect alias sets (“balance”,”record”,”me”,”bets” / “bookie_board”… / “guess_board”…) | enum aliasing | MODEL tool-call args | tool routing | bad aspect -> help string | via test_ask | KEEP | validating tool args from the model | | L2871 player-prop name substring both ways; L3079-3082 "stream" in wanted.lower(), metric equality | substring | model tool args vs API rows | filter props / add “no figure” line | fail toward include | - | KEEP | structural matching | | L4373 intent in ("discourse","icebreaker") | Sonnet classify_mention_intent label | routing | fail toward ask | yes | KEEP | already model | | Note L18358-18365 in claude_client: /ask grounding pre-classifiers (keyword list, Haiku classify_pure_computation) were tried and REMOVED; do not re-add | | | | | | | history | | _strip_html, re.sub(rf"<@!?{me.id}>"), url.startswith(("http://","https://","data:")), ctype.startswith("image/") | parsing | message | strip | structural | - | KEEP | structural |

cogs/recap.py

No keyword sites. _est_tokens / _clip_tail_to_tokens size guard, _PERIOD_MSG_CEILING, is_channel_dead / room_is_quiet (in utils.feeds, out of scope). KEEP.

cogs/commentator.py

| _MILESTONE_TRIGGERS, _RETRY_TRIGGERS, _ODDS_OPENING_TRIGGERS, _ODDS_SUPPRESSED_TRIGGERS, _CONTEXT_TRIGGERS frozensets; _SGO_LEAGUE / _ODDS_EDGE_SPORT_KEY / _SPORT_KEY maps; L1235-1237 "moneyline_home"/"moneyline_away" in game.odds; L1249 trigger in _ODDS_OPENING_TRIGGERS | trigger label in set | internal trigger labels / payload keys | prompt framing, retry, odds labelling | n/a | 0 by name | KEEP | internal enums, not text classification | | L1396-1426 _passes_quality commentate_score >= _SCORE_FLOOR | numeric on model score | model output | ship / skip | scorer EXCEPTION ships (fail open), low score skips | yes | KEEP | already model | | _team_match, is_watched (utils.markets, out of scope) | | | | | | KEEP | out of scope |

cogs/scheduled_poster.py, cogs/event_poster.py, utils/history_gate.py, utils/abuse_tracker.py, utils/reactions.py, utils/tunables.py, experiments.py, utils/permissions.py, utils/gates.py, utils/kill_switch.py

| scheduled_poster: too_short_to_ship, arbitrated_match + _topic_verdict (claude.topic_duplicate), _sched_dedup_wording_only, plan_shrank | see A/B | model output | ship / skip | see A | yes | KEEP | already HYBRID | | abuse_tracker: detection is ClaudeClient.classify_abuse (Haiku), tracker counts strikes | numeric | Haiku labels | mute engagement | fail open | yes | KEEP | already model | | tunables surface_model label validation, MODEL_SURFACES; experiments GRADUATED_*; permissions _member_tag (mod/girls/new/bot); gates; kill_switch; reactions; history_gate | enums / numeric | config | gating | n/a | yes | KEEP | config |

Voice: utils/voice_signal.py, utils/gif_signal.py, utils/image_signal.py, utils/emoji.py, utils/should_voice.py, utils/voice_cues.py, utils/tts.py, utils/stt.py, utils/voice_ingest.py, utils/discord_voice.py, cogs/voice.py, utils/voice.py, utils/gifs.py

| voice_signal L32-35 _OPEN_RE/_CLOSE_RE (<voice>/<sing>), gif_signal L28-34 _WRAP_RE/_INLINE_RE (<gif: q>), image_signal L57-60 _TAG_RE + L72-101 _ASPECT_SYNONYMS (<image aspect=>/<remix>/[art]), emoji regexes, tts text.startswith("[") sing tag | XML/tag protocol | model output | nominate voice / gif / image | fail toward plain text | yes | KEEP | the model nominates; the regex only reads the tag | | should_voice MAX_VOICE_CHARS 320 / MAX_SINGCHARS 1200; gifs rendition ladders, _RATING; stt _diarize; voice canned quips; cogs/voice reply-to-her summon | numeric / config | - | - | - | yes | KEEP | structural |

persona.py, constitution.py, config.py, cogs/help.py, cogs/models.py, cogs/menu_style.py, cogs/order.py, utils/reference_types.py, utils/wheel_card.py, utils/engagement_window.py, utils/schedule_calendar.py

| config _flag in (“1”,”true”,”yes”,”on”) | literal | env | boolean | n/a | - | KEEP | structural | | order L385-425 preflight_order verdict != "allow", "plumbing" mapping; L89-90 _FIX_ISSUE_LABELS / _FIX_INTERNAL_LABELS _raw_fix_issues; recent_fix_status substring query; L171-186 _resolve_member_mention casefold | label mapping / GitHub labels / name equality | Sonnet verdict / issues / args | order state | preflight fails CLOSED | 5 | KEEP | the classification is Sonnet | | persona / constitution / help / models / menu_style / reference_types / wheel_card / engagement_window / schedule_calendar | none | | | | | KEEP | no sites |


Existing model-classifier spine (claude_client.py + utils/)

Shared entry point. Every classifier goes through ClaudeClient._call(*, model, user_message, system_extra, max_tokens, purpose, temperature, skip_persona, image_urls, images_required, ...) -> ClaudeResult(.text, .stop_reason, ...) (L6777). Pure classifiers pass skip_persona=True (persona made Haiku answer in-voice ~21% of the time, #136) and temperature=_JUDGE_TEMPERATURE, max_tokens=_SCORE_MAX_TOKENS. Reusable parse helpers: _extract_scored_json (score 0..1 + dict), the fence-strip + first-{...} pattern, salvage regexes for truncated JSON, closed-set enum validation. There is NO generic classify(labels) helper – each judge is its own method with its own prompt and parser. A new Haiku classifier = one method following classify_abuse (label) or topic_duplicate (JSON tri-state) plus a _parse_* pure function.

How tests patch it. patch.object(client, "_call", fake) returning a ClaudeResult-like object with .text (tests/test_memory.py:182, tests/test_claude_client.py). Parsers are tested pure.

method purpose= model returns fail direction golden / eval  
classify_abuse (18295) classify_abuse HAIKU, skip_persona bool open (False) tests  
preflight_order (18197) order_preflight SONNET (“allow”/”plumbing”/”reject”, reason) closed (reject) tests, eval_preflight? (5 test files)  
classify_mention_intent (18367) mention_intent SONNET {intent, period} open (ask) tests  
scan_music_mention (18491) music_mention_scan HAIKU, skip_persona {artist, song, error?} open + error sentinel tests  
classify_music_news (18590) music_news_classify HAIKU kind/subject/claim/… open (none) tests + eval  
verify_music_claim (~18960) music_news_verify ? (verified, contradicted, note, figure) closed tests  
topic_duplicate (12681) topic_dedup HAIKU (bool None, reason) tri-state None = keep mechanical tests
discourse_score (15872) discourse_score HAIKU (score, reason) 0.0 skip; salvage tests + eval  
chimein_score (15991) chimein_score HAIKU (score, vibe, hook, reaction, target) 0.0/other skip tests  
quiet_room_score (16079) quiet_room_score HAIKU (score, reason) 0.0 tests  
commentate_score (12587) commentate_score HAIKU (score, reason) exception ships tests  
board_score (13231), drop_score (13728), music_desk_score (14082), cinema_desk_score (14301), pop_desk_score (14987), sports_desk_score (15258), cinema_news_score (15696), winner_line_score (12846) *_score HAIKU (score, reason) closed (0.6 floor) tests + evals  
qualifier_verify (12935) qualifier_verify HAIKU (bool None, qualifier, kind) tri-state tests
wire_subject_named (14430) wire_identity HAIKU bool open (True) -  
crosspost_media_mismatch (14457), crosspost_clip_mismatch (14601), identity_frame_mismatch (14757) media_coherence / clip_coherence / wire_identity vision (Haiku) verdict str / bool drop image, keep tweet tests  
classify_sports_beats (15192) sports_beat HAIKU {key: tag} open (drop unknown) tests  
image_subject(s) (15760) image_subject HAIKU dict/list open (None) tests  
classify_market_intent (16498), pick_kalshi_series/market/events (16564-16706) market_intent / kalshi_* HAIKU dict / tickers open tests  
describe_image(s) (16761), pick_gif (16950), route_curator (~18140), pick_curator (17710), pick_curator_text (17991), pick_subject_image (17394), confirm_subject_image (17650), video_subject (17032), pick_music_video/trailer/moment_clip (17191-17324) image_look / gif_pick / curate_route / curator_pick / subject_image_pick / video_subject / *_pick HAIKU (vision) index / dict open (None = skip) tests  
judge_song_guess (19514) song_guess_judge HAIKU, skip_persona bool closed (False) tests  
tag_memory_note (19215), expand_memory_query memory_tag / memory_query_expand HAIKU list[str] open ([]) tests  
memory_note (10477) / memory_rollup (10686) memory_hourly / memory_daily / memory_self_* SONNET (never Haiku; fence eval) str rollup raises MemoryRollupTruncated scripts/eval_memory_fence.py  
draft_x_reply (19671), draft_mention_reply (20029) x_reply_draft / x_mention_reply SONNET (opt-in) str ”” dryrun script + slop score  

Top 10 HAIKU candidates (ranked by brittleness x user impact)

  1. utils/output_checks.py decline-narration battery (L458-535) – 15 regexes for “the model is narrating that it has nothing to say”. Highest brittleness: every incident adds a phrase; a miss publishes private reasoning. Replace with one Haiku label, fail toward drop.
  2. utils/curator.py _IMAGE_TOPIC_TERMS / prefers_media (L224-262) + cogs/curator.py L345-370 – routes a room to the image vs text judge off a word list in the channel topic. A false negative silently starves a visual room. Haiku over name + topic + examples, cached per channel.
  3. utils/x_mentions.py asks_question (L185-224) – first-word set + cue regex decides whether to fetch the parent post and draft a reply. Fold into the existing scan_music_mention JSON ("asks_question": bool): zero extra calls.
  4. utils/wheel.py is_narration (L133-164) + claude_client._parse_wheel_items – phrase lists over model list output; known song-title false positives; a bad slice can win a spin. One Haiku “which lines are not options” per regenerate.
  5. utils/reference.py _SUBPAGE_HINT / _ASPECT_SYNONYMS / _aspect_weights (L426-439) – untested synonym table picks a subpage for /ask; a pick_* style Haiku call over the subpage titles is the natural fit.
  6. claude_client.py _SELF_CORRECTION_MARKERS (L1649) + output_checks.has_self_correction (L285-301) – the “let me read” phrase was added after a live leak; same class as #1. A single “finished post vs working” judge covers #1, #4 and this.
  7. utils/output_checks.py cross-source comparison battery (L597-671) – source-name regexes drift as sources are added; the rule (“compares two sources’ figures”) is semantic. HYBRID: keep regex as fast path.
  8. utils/output_checks.py career / all-time / ranking claim batteries (L325-411, L1205-1210, L1283-1407) – the qualifier_verify Haiku judge already exists for the newsroom; reuse it as the arbiter, keep the regex as the trigger.
  9. utils/perplexity.py is_hedged (L107-119) – phrase list decides whether Perplexity “found anything”, which gates the forced web_search retry in claude_client._has_perplexity_grounding. Haiku on the answer text.
  10. utils/reference.py _CHART_INTENT / _TABULAR_CUE (L101-120) – user-intent regex on /ask text; HYBRID with a Haiku intent read (the mention router already runs Sonnet intent).

Deliberately NOT candidates: is_empty_response (protocol, incident-hardened), _TICS (measured: the model form was worse), slop_score (independent lexicon signal by design), grade_guess (per-message hot path; Haiku escalation already exists for EASY), engagement.py heuristics (300k-message scan), roles.SAFE_PERMISSIONS (safety fence), memory_note_violations + _MEMORY_FENCE (constitutional), _artist_named_in_strict and _title_names_subject (groundedness checks that must sit outside the model), _IDENTITY_VERIFIED_ART (incident-backed exemption), looks_like_wall_channel (a pin override exists), all _parse_* closed-set validators (they validate a model answer).


Count summary (semantic sites / structural groups)

file semantic structural groups
claude_client.py 12 (is_empty_response, self-correction x3, final_card_line, tic guard, preflight parse, abuse parse, wire_subject_named, mismatch verdict parses, _artist_named_in*, _parse_wheel_items, _has_perplexity_grounding) ~25 (parse* validators, track/xpost tags, label leak, image-error strings, capability sets, fences)
utils/output_checks.py 16 6
utils/slop_score.py 2 1
utils/x_reply_draft.py 1 3
utils/perplexity.py 2 3
utils/dedup.py 1 (already hybrid) 2
utils/engagement.py 4 3
utils/curator.py / cogs/curator.py 2 + 3 3
utils/curate.py 1 2
utils/x_mentions.py / cogs/x_mentions.py 2 + 2 6
utils/x_crosspost.py 3 5
cogs/x_reply_draft.py, x_audience, x_followers, x_game (utils+cog), x_poster, x_fetch, twitterio, x_filter_rules, social_* , grok_search 4 ~20
utils/reference.py 5 3
link_enrich, watch_link, url_guardrail, comments, reddit 2 (watch_link freshness, enforce_allowlist) 12
message_search (utils+cog), semantic_index, embeddings 1 12
utils/wheel.py, cogs/wheel.py 1 5
cogs/games.py 4 4
awards, calendar_view, settings, tune, game_lookup 1 5
starboard (utils+cog) 1 6
polls, roles, discord_info, scheduled_events, schedule_calendar 1 (roles fence) 10
cogs/chimein.py 2 3
cogs/memory.py + memory utils 1 4
cogs/ask.py 2 8
cogs/recap.py 0 3
cogs/commentator.py 1 6
scheduled_poster, event_poster, history_gate, abuse_tracker, reactions, tunables, experiments, permissions, gates, kill_switch 1 10
voice/gif/image/emoji/tts/stt/voice_* , gifs 0 9
persona, constitution, config, help, models, menu_style, order, reference_types, wheel_card, engagement_window 1 (order preflight mapping) 4
total ~80 semantic ~190 structural groups

Not verified / gaps


Inventory: charts, cinema, ops, infra

Read-only. No file was modified. Every in-scope file was read in full except utils/events.py, where lines 1-3683 are the event-ledger docstring (skipped) and the code (L3685-4066) was read. Test coverage was checked with grep -rl over tests/ per symbol (direct name or the public wrapper that exercises it).

Legend: S = semantic (drives a classification/decision), ST = structural. Verdicts: HAIKU / HYBRID / KEEP. “Volume” is inferred from call shape. Sibling audits of the other scopes (music/news, conversation/x/gates, markets/sports, and the 16-file remainder) ran as separate agents; their reports were handed to their own caller and are not merged here.


utils/kworb.py (2517 lines)

file:line pattern input decision fail direction tested? verdict reason
kworb.py:605 _BASE_LABEL_RE drops a trailing (...) from a Kalshi leg label Kalshi leg label the phrase searched on the video board strips meaningful parentheticals too (a “(Remix)” leg collapses onto the original) no (0 files) HYBRID Per-leg, ~15/day. A vision media gate in x_crosspost already re-checks the picked video; a Haiku “is this the same recording” check would close the remaining gap.
kworb.py:608-702 find_video_match whole-phrase containment via _fold_phrase, most-viewed tie-break, retry with the parenthetical dropped, returns unique flag leg label vs kworb YouTube board titles which video row a Kalshi leg maps to OPEN (a containment false-positive picks the wrong video; #2286 “ICONIC BY MISTAKE”) yes (1) HYBRID Low volume, public X output, an existing vision gate reads unique. Haiku confirm on the non-unique picks only.
kworb.py:946 _strip_feature \s*[\(\[](?:w/\|feat\.?\|ft\.?\|with)\b.*$ song title cell title used for identity fold strips a bracketed “with” that is part of a title no KEEP Bulk per-row fold; deterministic identity must stay reproducible.
kworb.py:958 _ARTIST_CREDIT_SPLIT (&, feat, ft, featuring, with, vs; NOT comma/x) artist cell credit parties for identity match comma/x collabs read as one act → misses toward absence no (indirect via find_* tests) KEEP Deliberate: a comma inside a band name is worse than a missed collab.
kworb.py:997 find_chart_entry loose folded containment, strict credit equality mode artist name vs ranking rows which row is the watched act loose mode: ‘Future’ ⊂ ‘Future Islands’ (documented hazard); strict measured 267/1000 fewer mismatches yes (1) KEEP Callers should stay on strict; the loose mode is the risk, not the regex.
kworb.py:1068 / 1524 / 2499 find_song_entry, _find_by_artist (strict credits), resolve_artist_id (exact fold) title/artist vs rows row or id lookup CLOSED (no match → None) yes (3 / 1 / 1) KEEP Exact folds over ~1000-row tables.
kworb.py:1338-1373 _FUNCTIONAL_AUDIO_RE (white/pink/brown noise, rain/ocean sounds, “music for sleep”, asmr, binaural, \d+ ?hz, …) + _FUNCTIONAL_MIN_TOTAL=5B + _FUNCTIONAL_MAX_DAILY_SHARE=0.01 in is_functional_audio; applied per row by _clean_ranking (emits artist_filter) artist + title text, stream totals drop a row from the ranking board (never shipped) OPEN on a false negative (an unlisted functional act reaches #1 and ships to X); a false positive silently removes a real act yes (1) HYBRID → HAIKU on the top-N Highest brittleness × impact in scope. Keep the regex + stream-shape gate as the bulk filter; run Haiku “is this functional/background audio?” only on the rows that enter the rendered top-N of a daily-ranked board (≈20 rows per fresh fetch).

Structural (one row per group): “ - “ artist/title splits L402/815/884/1221; NEW/RE movement markers L411/821-837; _PEAK_DAYS_RE; link/href regexes; “total”/”global” header detection L1756; _PER_ENTITY_QUERIES prefix L1985; _BLOCKED_STATUSES={401,403,451} L2082; "anglo" in path L2159; _US_VIDEO_CHART_SIZES={20,100} L765 (shape-change guard, #3042).

utils/riaa.py (2186)

file:line pattern input decision fail direction tested? verdict reason
riaa.py:469 _TITLE_FEATURE_RE (3 feature-clause shapes; bracketed “with” only) award title cell guest credits for search/attribution miss → guest uncounted (absent) yes (indirect, 3 files on display_title/credit) KEEP Bulk per award row (~1800/yr feed); RIAA’s own convention, deterministic.
riaa.py:489 _TITLE_ID_FEATURE_REtitle_identity title dedupe key across award rows over-merge collapses two recordings yes (1) KEEP An identity key must be deterministic.
riaa.py:525 _PERSONA_AS_RE (“X as Y”) artist cell persona split rare indirect KEEP  
riaa.py:570 _CURATED_OMITTED_CREDITS + generated OMITTED_CREDITS_PATH JSON (artist, title) key adds guests RIAA left off the row wrong entry hands an act a plaque yes (audit script + riaa tests) KEEP Hand-checked curated list; the audit script is the right tool, not a runtime model.
riaa.py:799-804 _NOT_AN_ACT {soundtrack, various, various artists, original soundtrack, cast, studio cast, london cast, original cast, original broadway cast, original london cast, traditional} in own_credit artist cell is this row an act’s own credit OPEN: an unlisted cast/compilation name is treated as an act yes (1) HYBRID Small list, low volume (unresolved cells only); Haiku on the miss side only.
riaa.py:862 _FEATURE_AND_RE bare “and” split in _feature_guests (all pieces must resolve) feature clause guest list CLOSED (unresolved piece → no split) no direct KEEP Guarded by the resolver.
riaa.py:1273-1322 display_title: _INITIALS_RE, _SMALL_WORDS, _ROMAN_RE, _SLASH_ACT_RE, _WORD_START_RE, _CLAUSE_END recasing ALL-CAPS RIAA cells title/artist text rendered card text documented miscase: SZA→”Sza”, DNA.→”Dna.” yes (3) HYBRID/HAIKU Per card row (~10/card), public card text. A Haiku recase call over the card’s rows would fix stylised names the rules cannot know.
riaa.py:1328 _CREDIT_JOIN_RE / _CREDIT_SLASH_RE in display_credit credit cell joiner rendering cosmetic yes (1) KEEP  

Structural: _LEVEL_LABEL_RE L225, _LATIN_LABEL_RE L230 (timeline_ladder); _MIN_ALIAS_SEARCH=4; "default-award" not in first L1786; "awards_by_artist" not in page L2127 (page-shape guards); format.upper() in ("ALBUM","SINGLE").

utils/riaa_boards.py (1179)

file:line pattern input decision fail direction tested? verdict reason
riaa_boards.py:366 _BRACKET_RE drops any bracketed clause for the row sub-line title rendered sub-line drops “(Taylor’s Version)” and other meaningful subtitles indirect (26 files match riaa_boards) KEEP (flag) Presentational; the flag is that identity-bearing parentheticals vanish. A one-line allowlist beats a model here.

Structural: _fold L108; chart-key prefix routing L846-848/903-1105; _CERT_PREFIX L797; fmt.upper()=="ALBUM" L490/702.

utils/billboard.py (1712)

file:line pattern input decision fail direction tested? verdict reason
billboard.py:301-307 _CREDIT_HANDOVER_RE (,, &, +, \bx\b, and, with, duet, feat, ft, featuring) → _lead_credit artist credit the “lead” act FLAG: “Earth, Wind & Fire” → “Earth”; _history_slug L1233 accepts want in (_norm(row.artist), _norm(_lead_credit(row.artist))), so a solo act “Earth” could take EWF’s slug. Contradicts its own comment. yes (3 / 1) KEEP + fix A fragment trap, not a model problem: require the lead to be a full party (use artist_watch.credit_parties).
billboard.py:718 _KIND_WEIGHT / movements() notability score row movement kinds which movements are newsworthy deterministic yes (3) KEEP  
billboard.py:1344 find() title containment, highest-charting row wins wire claim title which chart row backs a claim OPEN: ‘Golden’ matches several titles yes (indirect) HYBRID Per wire claim. Haiku confirm only when >1 row contains the title.
billboard.py:1364 / 1622 rank_of exact; chart_context containment title OR artist (context only, min rank) title/artist rank / context line CLOSED / OPEN yes (1) KEEP  

Structural: _STAT_LABELS L464; _DEBUT_BADGE/_REENTRY_BADGE gated on LW dash L478-654; _join_credit L572; _missing_credits partial-credits guard L597; _norm + strip_ep_marker L1353; _MIN_ROW_RATIO; 404 → unknown_slug; _BLOCKED_STATUSES.

utils/luminate.py (762)

file:line pattern input decision fail direction tested? verdict reason
luminate.py:77-88 ALBUM_METRICS cue list → metric_from_text (order matters; first_week_units last, #3008) Kalshi market/series title which unit the card prints (album-equivalent / pure / streams / sales) OPEN: an unlisted phrasing picks the wrong unit on an X card yes (1; event_metric 2, series_metric 3) HYBRID Per Kalshi event (low volume, public card). Haiku on the “no cue matched” and “two cues matched” cases.
luminate.py:147 / 495 _SUBJECT_REparse_subject (release / artist from market title) market title subject the card names OPEN: the metric word landed in the album name (the Tyla/Ariana class) yes (1; display_subject 2) HYBRID Same shape as above; one Haiku call could return {artist, release, metric} together.
luminate.py:277-289 release_stage ticker substrings (STREAMSY / ALBUMEQUIVY / PUREALBUMSY / ARTISTSTREAMS / ALBUMEQUIV) + metric words Kalshi ticker which stage the market is CLOSED (unknown → None) yes (2) KEEP Ticker naming is Kalshi’s schema, not prose.
luminate.py:520 _KALSHI_CATEGORY_LABELS exact denylist category string skip category tiny indirect KEEP  
luminate.py:586 "first week" in title title first-week framing OPEN indirect KEEP  

Structural: "lumin" settlement source L104; _METRIC_TAIL_RE L505; _PERIOD_RE; _NUM_RE; resolved statuses L602; is_streams_reading ("stream" in metric, derived) L144.

utils/chart_boards.py (4191)

file:line pattern input decision fail direction tested? verdict reason
chart_boards.py:371 _match_hits exact key, then title + artist_watch.credit_matches chart row vs hits row join across sources CLOSED yes (1) KEEP  
chart_boards.py:627-660 APPLE_GENRE_GROUPS, DEEZER_GENRE_GROUPS, confirmed_genre_group (tagged / other_genre / genre_unconfirmed / genre_disagree) published platform genre tags genre group on the board unmapped tag → absent (never guessed) yes (1) KEEP Two-source confirmation over published tags; a model would add invention.
chart_boards.py:826 _label_case (≤4-char caps kept) label casing cosmetic no KEEP  
chart_boards.py:1200 NEW_RELEASE_MAX_AGE_DAYS=45 catalog-surge cut release age new vs catalog deterministic indirect KEEP  
chart_boards.py:2256 _album_track_fold (paren + feat strip) with existing Haiku recovery: recovery_candidates L2424 token-overlap prefilter, _RECOVERY_MAX_MULTIPLE=4, recovered_total L2462, recovered_aliases L2524 album track titles vs chart titles which chart rows are the album’s tracks fold miss → Haiku confirm; fail CLOSED on the model side no direct / yes (1 / 1) KEEP This is the worked HYBRID example the rest of the scope should copy: deterministic fold first, cheap prefilter, Haiku on the residue.
chart_boards.py:2549/2570/2592 fold-equality lookups (song_total_streams etc.) title/artist row lookup CLOSED indirect KEEP  

Structural: _norm join key L203; _PERIOD_END_RE L3165; "stream" in metric L3344; kind frozensets _IMPLICIT_RANK_KINDS / _PLATFORM_KINDS / _US_WEEKLY_KINDS; PLATFORM_CHART_ROWS.

utils/chart_data.py (424)

file:line pattern input decision fail direction tested? verdict reason
chart_data.py:59 detect_chart keyword routing (billboard 200\|200 chart\|us albums, \buk\b\|u\.k\.\|british\|britain, canad, hot 100, album) a user’s /ask question which chart table is fetched as reference OPEN: “is the uk tour selling” routes to the UK chart; no chart word → default yes (1) HAIKU Free-text intent read, low volume (per reference question), a wrong chart feeds the answer. A Haiku intent classifier already exists for markets (classify_market_intent); extend that rather than a second regex.

Structural: _clean_title quoted span L218; _is_chart_col link target L264; render_tables keyword row filter L397.

utils/chart_cards.py (1484) — structural only

_bucket unicode ranges L274; _drawable emoji strip L435; _CUT_NOTE_RE L760; ACCENTS L103.

utils/chart_records.py (1167)

file:line pattern input decision fail direction tested? verdict reason
chart_records.py:102 RECORDS curated all-time table record claims curated indirect KEEP  
chart_records.py:297 / 423 _same_song fold equality; _credit_corroboration_set via is_solo_credit/credit_parties/is_placeholder_credit title/credit identity for a record claim CLOSED no direct (artist_watch tested) KEEP  
chart_records.py:877 _record_phrase margin thresholds numbers phrasing deterministic no KEEP  

utils/pollstar_boards.py (565) — structural only

_TEXT_COLUMNS L180; _movement L198; chart.kind == "radio" routing; names.display_case.

utils/pollstar_charts.py (495) — structural only

subject fallback keys (artist / headlineartist / entry); latest_issue_id date compare.

utils/wikidata.py (665)

file:line pattern input decision fail direction tested? verdict reason
wikidata.py:38 / 176 _FACTOID_INTENT regex (born, birth, how old, age, died, death, founded, inception, established, what year) → matches() a user question route to the Wikidata factoid path OPEN: a false positive falls through to Wikipedia; a miss skips a valid factoid yes (2-3) HYBRID Low volume per question. The markets Haiku intent classifier is the natural home; keep the regex as the zero-cost prefilter.
wikidata.py:120-161, 533 _MUSIC_OCCUPATIONS Q-ids, _MUSIC_GROUP_CLASSES, is_music_act (P31 group class OR human + P264/P1303/P106); _MUSIC_DESCRIPTION, _WORK_DESCRIPTION order candidates Wikidata claims / description is the hit a music act; which hit to try first CLOSED (unlisted class → not an act) yes (1) KEEP (HYBRID for unlisted classes) Structured ids, not prose; a model only helps on the unlisted-class residue.
wikidata.py:507 / 638 _norm_act_name exact; want_founding regex name / question match; founding vs birth CLOSED yes (1) KEEP  

utils/wiki_hits.py (180) — structural only

_TITLE_HEADERS {single, song, title} L42; startswith("artist") L68; "discography" in t.lower() L128 (table-shape detection).

utils/box_office.py (410) — structural only

_COLUMNS header→field map L127; _header_fields; _parse_trajectory; money/pct regexes.

utils/cinema_boards.py (856)

file:line pattern input decision fail direction tested? verdict reason
cinema_boards.py:69 _debut_lead (pct_change None, weeks ≤1, gross ≥3M) BOM row numbers debut framing deterministic yes (1) KEEP  
cinema_boards.py:319-337 _STUDIO_SUFFIXESshort_studio; _NAME_PARTICLES / _NAME_SUFFIXESshort_director studio / director strings card text cosmetic yes (1 / 1) KEEP  
cinema_boards.py:113-123 _PACKED_SCORE_RE, _ROW_POSITION_RE, _POSITION_CLAIM_REstrip_position_claims model output strip rank claims the numbers do not back CLOSED yes (1) KEEP Numeric backstop on model text; must stay deterministic.

Structural: _move L161.

utils/cinema_numbers.py (1043)

file:line pattern input decision fail direction tested? verdict reason
cinema_numbers.py:141 / 157 _is_new_opener ratio 1.6; _notable_holdover numbers which films the take names deterministic yes (1) KEEP  
cinema_numbers.py:496 _clean_news: NONE marker + “no notable” / “no major” / “nothing “ / “no wide release” on a <200-char Perplexity answer Perplexity output drop the news line OPEN: an unlisted “nothing to report” phrasing ships as news no HYBRID (low) Cheapest fix is a prompt-side sentinel; the regex is the fallback.
cinema_numbers.py:888 norm_subject film title identity across sources CLOSED yes (1) KEEP  

Structural: _RELEASE_POP_FLOOR=40.

utils/cinema_news_stories.py (252)

file:line pattern input decision fail direction tested? verdict reason
cinema_news_stories.py:88 / 159 _STOPWORDS + story_signature (first 5 content tokens) wire text dedupe key over-merge drops a real story; under-merge → Haiku topic dedup downstream (ScheduledPoster.TOPIC_DEDUP) yes (3) KEEP Prefilter in front of an existing model judgment.

Structural: is_postable_moment ≥24 chars.

cogs/cinema_desk.py (1619)

file:line pattern input decision fail direction tested? verdict reason
cinema_desk.py:1299 _board_unshippable (output_checks backstops: self-correction, row arithmetic, tally) model output block the post CLOSED yes (1) KEEP Deterministic backstop behind cinema_desk_score.
cinema_desk.py:666 / 1260 ungrounded_numbers model output vs source numbers block CLOSED yes (output_checks) KEEP  
cinema_desk.py:839 tmdb.search_movie(row.title) with no year → first hit used unverified (budget crossings) title which film’s budget is quoted FLAG: OPEN, a same-title older film can be quoted no KEEP + fix Same class as the OMDb Moana incident (omdb.by_title now refuses a bare title); TMDB has no such guard. Pass the year and check title_matches.
cinema_desk.py:983 _critic_scores previous-December year retry year OMDb lookup CLOSED indirect KEEP  

Structural: kind sets _SUBJECT_DEDUP_KINDS, _NEWS_KINDS, _CARDED_KINDS, _SHAPE_GUARDED_KINDS; _card_source L371.

cogs/cinema_news.py (775)

S (out-of-scope module, listed for completeness): retrospective_note / frames_as_past L385/499 and drop_self_correction L484 (utils.retrospective, utils.output_checks) — KEEP, deterministic backstops behind the cinema_news_score judge. Structural: _VISION_HOSTS L139; is_source_photo.

utils/rotten_tomatoes.py (403)

file:line pattern input decision fail direction tested? verdict reason
rotten_tomatoes.py:193-226 normalize_title (_TRAILING_YEAR_RE, &→and, alnum fold) + title_matches exact + year_matches slack 1 title/year across sources is this the same film CLOSED (no match → absent) yes (7 files touch rotten_tomatoes; normalize/year 1 each) KEEP Exact identity with the year is the right shape.

Structural: @type in ("Movie","CreativeWork").

utils/rt_reconcile.py (328)

is_rt_event (KXRT prefix) L106, film_from_event tail strip L112, threshold_from_label L130, _resolved L142, verdict thresholds L89 — KEEP, tested (1 each). Kalshi ticker schema, not prose.

utils/omdb.py (281) — structural only

Source names L114; "N/A"; is_usable_year L87 (the guard that refuses a bare-title lookup — the model this scope should copy for TMDB).

utils/tmdb.py (602) — structural

job == "Director" L235; youtube/trailer/teaser/official L556; date window L479. FLAG search_movie L521 returns results[0] unverified (see cinema_desk.py:839).

utils/netflix_top10.py, utils/numfmt.py — structural only.

utils/events.py (code L3685-4066) — structural only

_VENDOR_BY_KIND L3709, _VENDOR_FROM_SOURCE / _VENDOR_FROM_SOURCE_MAP, _fold_detail, _TOOL_PROMOTED L3915; auto slop_score stamp on post_preview (the model-adjacent bit lives in utils.slop_score, out of scope).

utils/event_schema.py (410) — structural registries only.

utils/railway.py (414) — structural (_FAILED_STATUSES, _died_in_build, substring log filter L392, sev != "INFO").

utils/axiom.py (401) — structural ("column limit" in detail or "exceed" in detail L218 classifies a 400 body; logger-name / "EVENT " guards L369/372).

utils/healthcheck.py (375)

file:line pattern input decision fail direction tested? verdict reason
healthcheck.py:134 _is_read_only (head in select/with/table/values/show/explain, no ;) SQL text allow the debug query CLOSED, plus a readonly txn behind it yes (1) KEEP Security gate; never a model.

Structural: Bearer strip; rule_tag == "xmentions" L357.

utils/health.py (569)

WATCHES registry ok_when lambdas (songstats benign daily_cap / unprovisioned L419; kworb result_count>0 L451; odds max_divergence<15 L384) — KEEP, deterministic health rules.

cogs/health.py, utils/integration_probes.py, utils/http_retry.py, utils/retry.py, utils/source_providers.py, utils/watchlist_source.py, utils/ship_guard.py, utils/github.py, utils/bot_logs.py, utils/metrics.py, utils/stats.py, cogs/stats.py, utils/usage.py, utils/durable_cache.py, utils/async_cache.py, utils/lru.py, utils/fanout.py, utils/resumable_scan.py, utils/break_latency.py, utils/event_trigger.py, utils/instrument.py, utils/circuit_breaker.py, utils/rate_limiter.py, utils/rate_limits.py, model_rules.py, models.py

No semantic sites. Structural: _AUTH_FAIL_STATUSES={401,402,403} (probes L283), api_sports errors/subscription body shape L203-216; MEDIA_KINDS + plain_text (source_providers); min_names floor (watchlist_source); LEVELS + asyncpg/anthropic isinstance routing (bot_logs); PREPAID_FLOORS, LOW_PCT, removeprefix("per-") (usage); velocity thresholds (event_trigger); period != "minute" (cogs/health). ship_guard.py is a thin wrapper over output_checks — KEEP as the deterministic backstop.

db.py (8600+)

file:line pattern input decision fail direction tested? verdict reason
db.py:47-113 bookie_bet_outcome: _canonical_name(nm, sport) fold, sl == _canon(winner) / sl in (home, away) / else void bet side label vs final’s team names won / lost / void CLOSED to void (refund) on any name gap — never wrongly loses yes (_canonical_name 2 files; outcome via bookie tests) KEEP Money path; a model must not decide a payout.
db.py:118-129 _MEMORY_QUERY_TOKEN + drop literal “or” → _or_search_terms memory query tsquery OR rewrite recall widening yes (1) KEEP  
db.py:3878 side_label case-insensitive compare on void→won correction label correction applies CLOSED indirect KEEP  
db.py:8459-8522 alias_index(exclude_nicks) lower-cased drop; aliases_for_text a.lower() not in low nickname policy (data/nicknames.json no_count), query text which nicknames count / widen recall curated yes (2) KEEP Curated ambiguity list (“cole” = the rapper); the matcher is utils.names/utils.aliases.

Structural: sql_op regexes L158/169 (log label); jsonb codec L2146; handle lowercasing L5096/5101.

bot.py (1044) — structural

_LOGIN_RETRY_STATUSES={429} + status >= 500 L152; _is_discord_rate_limit L164; name == "menu" gate L223; _ESPN_ALWAYS_ON. Note: MarketsManager already wires Haiku classify_market_intent / pick_kalshi_* with a regex fallback — the pattern detect_chart and _FACTOID_INTENT should join.

scripts/ops_monitor.py (~5000)

file:line pattern input decision fail direction tested? verdict reason
ops_monitor.py:69-200 LATENCY_CEILINGS_MS, LATENCY_CEILING_PREFIXES (pplx_, db:), ceiling_for, latency_label surface label gated or reported-only; label bucket ungated on miss latency_label 0 direct (via evaluate tests) KEEP  
ops_monitor.py:298-321 _DEBUT_GATE_SHIPS, _DEBUT_GATE_NON_CANDIDATES {charted, side_version} gate reason field ship vs reject vs non-decision for gate_dark an unregistered reason counts as a rejection (false gate_dark) yes (test_ops_monitor) KEEP Enum vocabulary the emitters own; register, do not classify.
ops_monitor.py:590-602 _SCRAPECREATORS_ASK_EVENTS, _INTEGRATION_BENIGN_ERRORS, _MEDIA_EDIT_BENIGN_ERRORS error/reason strings exclude from health rate unlisted benign reason → false integration_unhealthy yes KEEP  
ops_monitor.py:620-626 _LOG_SIG_DIGITS_RE + _log_error_signature (text before first ‘:’, digit runs masked) forwarded ERROR log line error grouping / burst detection over-split → burst never fires; over-merge → one group yes (1) KEEP (HYBRID if grouping drifts) Cheap and stable; a Haiku grouper would only earn its cost when messages lose the desc: context convention.
ops_monitor.py:723-787 NON_SCORE_REASONS, NON_SCORE_PHASES, LANE_PHASE_SURFACES, is_judge_score reason/phase is a *_scored row a real judge verdict unregistered marker → counted as a 0.0 score (skews low_quality, #3357) yes (1) KEEP Enum registry; the recurring miss is a missing registration, which a test can catch.
ops_monitor.py:1187 _PCT_HERO_RE card figure percent-hero finding deterministic yes KEEP  
ops_monitor.py:1305-1340 _MODEL_PRICING longest-substring _price_for; _provider_of (gpt/o1/o3 prefix) model id cost estimate / bill unpriced → flagged any_unpriced yes (1) KEEP  
ops_monitor.py:1764-1806 _PER_SOURCE_MISC, _PER_QUERY_MISC, _OPAQUE_QUERY_PREFIXEShealth_query_key source/query health bucket granularity collapse only on listed prefixes yes (1) KEEP  
ops_monitor.py:1936-4114 in-evaluate literal sets: level in ("ERROR","CRITICAL"), mode in ("media","number_fallback"), src in ("captions","audio","x_text"), reason in ("blocked","shape_change","partial_credits"), error not in ("no_voice","stage","busy"), reason in ("game_gone","no_line"), fell_to.startswith("gpt"), purpose.endswith(("_score","_pick","_confirm")) event fields finding branch / severity enum yes (evaluate tests) KEEP Event-vocabulary routing.

scripts/axiom_setup.py — structural only

"exist" in str(body).lower() L2501 (409/422 twin); _COL_ALIAS / _COUNT_CALL L2521-2522 (bars-vs-line render rule); startswith("_") system fields L2712.

Other runtime scripts


Top 10 HAIKU candidates in this scope (brittleness × user impact)

  1. kworb.py:1338 is_functional_audio — regex + stream-shape gate over ~1000 rows per fetch; a miss ships white noise at #1 to X. HYBRID: Haiku only on the rendered top-N of daily-ranked boards. Tested (1).
  2. chart_data.py:59 detect_chart — keyword routing of a user question to a chart; wrong chart feeds a wrong answer. HAIKU (fold into the existing markets intent classifier). Tested (1).
  3. luminate.py:77 metric_from_text + :147 parse_subject — cue lists over Kalshi titles decide the unit and subject on a public card (#3008, Tyla/Ariana class). HYBRID: one Haiku call returning {artist, release, metric} on the ambiguous residue. Tested (1 each).
  4. riaa.py:1273 display_title recasing — ALL-CAPS → title case by rules; SZA→”Sza”, DNA.→”Dna.” on public cards. HYBRID/HAIKU per card. Tested (3).
  5. kworb.py:608 find_video_match (+ :605 _BASE_LABEL_RE) — containment pick of a YouTube row per Kalshi leg; #2286. HYBRID: Haiku confirm on non-unique picks; the vision gate already covers part. Tested (1 / 0).
  6. wikidata.py:38 _FACTOID_INTENT — regex intent read on user questions. HYBRID via the shared intent classifier. Tested.
  7. billboard.py:1344 find() title containment — ‘Golden’-class ambiguity backs a wire claim with the wrong row. HYBRID on >1 match. Tested (indirect).
  8. riaa.py:799 _NOT_AN_ACT in own_credit — 11-item list decides act vs cast/compilation. HYBRID on unresolved cells. Tested (1).
  9. cinema_numbers.py:496 _clean_news — phrase list decides whether a Perplexity answer is “no news”. HYBRID (prompt sentinel first). Untested.
  10. cinema_desk.py:839 + tmdb.py:521 search_movie(title) first-hit — not a keyword site but the same class as the OMDb Moana incident: an unverified same-title pick quotes the wrong film’s budget. Fix is deterministic (year + title_matches), listed because it outranks several regexes on impact.

Deliberate KEEPs with a concrete fix instead of a model: billboard _lead_credit fragment trap in _history_slug (require a full party); riaa_boards _BRACKET_RE (allowlist identity-bearing parentheticals); kworb find_chart_entry loose mode (callers on strict).

Count summary (semantic / structural sites)

file S ST
utils/kworb.py 7 9
utils/riaa.py 8 5
utils/riaa_boards.py 1 4
utils/billboard.py 4 7
utils/luminate.py 5 6
utils/chart_boards.py 6 5
utils/chart_data.py 1 3
utils/chart_cards.py 0 4
utils/chart_records.py 3 0
utils/pollstar_boards.py 0 4
utils/pollstar_charts.py 0 2
utils/wikidata.py 3 0
utils/wiki_hits.py 0 3
utils/box_office.py 0 4
utils/cinema_boards.py 3 1
utils/cinema_numbers.py 3 1
utils/cinema_news_stories.py 1 1
cogs/cinema_desk.py 4 5
cogs/cinema_news.py 2 2
utils/rotten_tomatoes.py 1 1
utils/rt_reconcile.py 5 (KEEP) 0
utils/omdb.py 0 3
utils/tmdb.py 0 (1 flag) 3
utils/netflix_top10.py / numfmt.py 0 2
utils/events.py 0 5
utils/event_schema.py / railway.py / axiom.py 0 8
utils/healthcheck.py 1 2
utils/health.py 1 0
infra group (24 files listed above) 0 9
db.py 4 3
bot.py 0 4
scripts/ops_monitor.py 9 0
scripts/axiom_setup.py 0 3
manual scripts (riaa_credit_audit, called_shot_scorecard, music_drop_manual, versuz_card_manual, gen_yearbook) 4 (manual) 1
Total 76 (of which 3 HAIKU, 12 HYBRID, 61 KEEP) ~110

Existing model judgments adjacent to keyword sites (do not duplicate): x_crosspost vision media gate (kworb video legs), Haiku album-track recovery (chart_boards), Haiku topic dedup (ScheduledPoster.TOPIC_DEDUP, cinema stories), cinema_desk_score / cinema_news_score judges, MarketsManager.classify_market_intent (bot.py; the home for detect_chart and _FACTOID_INTENT), resolve_desk_image classifier.

Not verified: call volumes are inferred from call shape, not measured in Axiom; test counts are file-level greps, not assertions that the brittle case is covered (e.g. is_functional_audio has one test file, _clean_news, _BASE_LABEL_RE, _debut_lead have none by name).

Inventory: the 16 remaining files

Scope read in full: cogs/artist_curator.py, cogs/curate.py, cogs/discourse.py, cogs/music_releases.py, utils/awards.py, utils/chart_ages.py, utils/chart_standings.py, utils/feed_status.py, utils/filler_solver.py, utils/market_art.py, utils/music_policy.py, utils/openai_chat.py, utils/reference_movie.py, utils/release_age.py, utils/subject_image.py, utils/the_odds_api.py. No files were modified.

Column key: input = what text is inspected; fail today = what happens on a miss / false hit; tested? = a test in tests/ exercises the symbol directly (grep-verified). Verdicts: HAIKU / HYBRID / KEEP.


cogs/artist_curator.py

Semantic sites: 0 in-file. The whole pick is already a Haiku vision judge (claude.pick_curator, L261). The only gates are structural or live out of scope:

file:line pattern input decision fail today tested? verdict reason
L198 filter_candidates(..., require_media=True) + L200 is_fresh(p, now) timestamp window (utils/curate.is_fresh, out of scope) upstream API post timestamp drop stale / media-less posts before the judge undated post is KEPT (fail-open) yes (test_curate.py) KEEP structural date parse; the judge sits right behind it
L209 channel.topic or parent.topic attribute fallback Discord channel topic judge context string default topic _DEFAULT_TOPIC n/a KEEP not a classification

Structural: none beyond the above.


cogs/curate.py

file:line pattern input decision fail today tested? verdict reason
L724 artist.lower() == source.lower() case-fold identity upstream X handles (reposted_handle vs configured account) “is this a repost of a DISTINCT artist” → caption or bare link equal → no caption (fail closed to bare link) yes (test_curate.py::_compose_caption) KEEP exact handle identity; deterministic by design
L737 "\n" in cap or link_count(cap) or visible_len(cap) > _CAPTION_MAX shape gate model output (Haiku caption) drop caption to bare link false hit → lose a good caption (fail closed) yes KEEP cheap deterministic post-check on a model output; already backed by the Haiku compose it gates
L800 prefers_media(media_ct, content_ct, ratio, topic=topic)utils/curator.topic_is_image_room (_IMAGE_TOPIC_TERMS: “selfie”, “lingerie”, “glamour”, “baddie”, “models”, “portrait”, “photograph”, “photos”, “painting”, …) substring term list, out of scope but the decision is made HERE Discord channel topic (mod-written) IMAGE mode (vision judge, require media) vs TEXT mode (text judge) — a routing choice of judge + prompt miss → density ratio decides; a text-heavy image room routes reposts to the text judge which rejects bare image links (the #backpage bug that spawned the list) yes (test_curator.py) HYBRID read once per channel per tick (memoized 90s, ~6 slots/day/room) so a Haiku “is this room an image room?” on the topic+6 sample posts is cheap; the term list is positive-only and admits it only rescues known cases. Model routing already exists downstream (route_curator), so the mode read could be folded into that call
L616 home.get(p.external_id) == channel_id routed-home equality our own routing map (built by claude.route_curator) which room gets the post model-decided yes (_best_home_map) KEEP already Haiku
L735 / L318 is_empty_response(...) sentinel “EMPTY” first/last line (claude_client, out of scope) model output skip vs ship a model that ignores the sentinel ships prose yes KEEP protocol sentinel, not a judgment

Structural (KEEP):

file:line pattern input notes
L900 _fetchableany(h in url for h in _VISION_HOSTS) host substring media URL which frames the vision API can fetch; untested
L924 _platform_label"tiktok" in host, "instagram" in host, host == "x.com" or "twitter" in host host substring over canonical_host permalink “via tiktok” caption word; "" on unknown → caption names artist only; tested
L774 parse_external_id(u) + pe[0] == _PLATFORM URL regex (utils/curator) Discord message URLs in-channel tweet-id dedup
L790 lower-case dedup of phrases n/a

cogs/discourse.py

file:line pattern input decision fail today tested? verdict reason
L133 classify_intent(f"{channel_name} {channel_topic}") (utils/markets._SPORTS_PATTERNS “nba”,”nfl”,”spread”,”parlay”,”fanduel”… + _PM_PATTERNS “election”,”odds of “,”chance of “ + _WILL_X_BY_RE) substring term list + regex, out of scope, decision made here Discord channel name + topic sportsy_channel → whether to even build the routing query and call markets.get_context (which itself runs the Haiku market_intent classifier) miss on a quiet non-sports slot → no market fetch (intended); false hit (“passport”, “football” in a film room topic) → wasted Haiku + SGO call yes (test_discourse_skip.py::_market_routing_query) KEEP (as prefilter) it is an explicit cost gate in front of an existing Haiku classifier; the ambiguous middle already goes to the model when convo_text is non-empty
L1621 looks_like_sports(f"{channel.name} {channel_topic}") (classify_intent == "sports" OR \bsport regex) same term list + word-boundary regex Discord channel name + topic icebreaker: fetch a market line at all miss → no market hook in a sports room (icebreaker loses material, fail-open); false hit → betting-flavored line in a non-sports room indirect (test_markets.py) HYBRID ~1 call per scheduled icebreaker fallback (rare); a Haiku yes/no on “is this a sports room” from name+topic+3 sample lines would be trivially cheap and remove the “transport” class of bugs the regex comment already fights
L581, L697, L1159 drop_self_correction(line, ...); L1710 has_self_correction(line) (utils/output_checks._SELF_CORRECTION_RE: a “wait,” correction marker at line start / after a clause break, out of scope) regex on model output Sonnet/Opus discourse take drop the take → icebreaker/quip fallback false hit → a good take with “– wait,” in-voice is dropped (the regex comment already carves out “the wait is over”); miss → a redraft ships yes (test_output_checks.py, test_ship_guard.py) KEEP a Haiku discourse_score already runs 40 lines later on the same text; the right move is to add “shows its working” to that rubric rather than a second call. Flag: the SCORE judge sees the same string, so this regex is a pre-filter duplicate of a model judgment that could be moved into the existing rubric at zero extra calls
L1740, L1907 arbitrated_match(line, recent, claude.topic_duplicate, catch=False) (utils/dedup, out of scope) text-ratio / shared-run / same-link mechanical dedup, arbitrated by Haiku ONLY on a mechanical hit model output vs our own DB history drop a duplicate take mechanical miss (same subject, fresh wording) ships — the acknowledged Yamal-repeat class, mitigated by prompt context L1118 rather than a judge yes (test_dedup.py) HYBRID (already) this IS the hybrid shape; note catch=False was a deliberate cost call (#2083: ~9 fires/month). The trending SUBJECT dedup (L1109-1124) is prompt-only — a candidate if Yamal-class repeats recur
L1088 canonical_url(c[3]) not in posted_links URL canonical fold our DB + upstream clip links drop already-posted clips fold miss → repost (the Madeon case, fixed by widening the window) yes KEEP structural identity
L1187 next(c[3] for c in clips if c[3] in line) substring: which clip link appears in the take model output telemetry only (platform, framed) none no KEEP observability, no decision
L948 t != "(no caption)" sentinel literal our own placeholder exclude from Grok prompt none no KEEP own sentinel
L816 parse_trending_phrases(text) (utils/social_search, regex: strips SOURCES: block, list markers, keeps 2-8 word lines) regex over Perplexity output model output which phrases get searched (10 keyword searches/slot fan-out) a prose line slips in → junk search; a good phrase >60 chars is dropped yes (test_social_search.py) KEEP structural parse of a line-per-phrase format; the phrases themselves are then Haiku-filtered on the native-X leg (filter_trending_topics, L833) — worth noting the Perplexity leg has NO fit filter, only the shape parse
L1099 clips[:_TRENDING_KEEP] after filter_recent(..., keep_undated=False) (social_search, date parse of “3 days ago” / ISO) date parse upstream API dates drop >2-day clips AND undated ones undated dropped (fail closed) yes KEEP structural

Structural (KEEP):

file:line pattern input notes
L292 _trending_clip_urlis_tiktok_url / is_instagram_url / youtube_id on defixup_links(url) host allow-lists + YouTube id regex (utils/video_fetch) URLs in the take picks the crosspost path (upload/link vs quote); tested
L1379 x_fetch.x_tweet_id(u) _STATUS_RE URLs in the take X-video re-host path
L277 _parse_retry_after_seconds header parse Anthropic error retry wait
L790 / L1082 lower/canonical dedup

cogs/music_releases.py

file:line pattern input decision fail today tested? verdict reason
L81 _REISSUE_RE = r"\b(\d+\s*[-\s]?\s*year\|anniversary\|reissue\|remaster)" used L516 regex on album TITLE upstream Apple Music album title skip the first-night STREAM stat for a reissue (the stat would be an old song’s plays) miss (“Deluxe” is deliberately excluded; “Expanded Edition”, “Taylor’s Version”, “Super Deluxe”, “Legacy Edition”, “Live at …” all pass) → a years-old song’s daily plays render on the card as a first-night figure (a public number card, ONE-WAY-door-ish); false hit → stat omitted (fail closed) yes (test_music_releases.py:189) HYBRID ≤4 titles per slot, 1-2 slots/day, non-latency-sensitive; keep the regex as the cheap yes, and put the ambiguous remainder (“Deluxe”, “Edition”, “Version”, “Live”) through a Haiku “is this a reissue / re-recording / live album of older material?” — the downstream date-equality check (L536) already catches most of the damage, so this is medium priority
L536 song_date != rel.released date equality iTunes date vs Apple feed date credit the charting track to THIS release mismatch → no stat (fail closed) yes KEEP structural, and the honesty rule
L538 entry.title.casefold() == rel.title.casefold() title identity kworb title vs Apple title render song name or blank miss → the song name is shown (harmless) yes KEEP identity
L341 kworb.album_chart_entry(chart, artist, title) (out of scope, “strict match”) folded exact match kworb vs Apple Top Drops rank miss → album left off (fail closed) yes (test_kworb.py) KEEP documented prefer-absent
L607 label == "new music friday" own label our own plan label caption text none KEEP own enum

utils/awards.py

Semantic sites: 0. All string ops are on our own metric keys (metric.split("+"), L159/L273; "{month}" in t, L51; _FIELD_LABEL lookup, L261). Structural, KEEP. _pick_single/_metric_value untested directly (covered via compute_awards tests).


utils/chart_ages.py

file:line pattern input decision fail today tested? verdict reason
L104-140 _matching_datet_key == rg_title or t_key in rg_title or rg_title in t_key AND a_key in rg_artist or rg_artist in a_key over fold_tokens fold + containment-either-way match upstream MusicBrainz release-group title + rebuilt artist-credit vs Billboard row “is this MB release-group THE chart row” → the row’s ORIGINAL release year on the oldest-songs card miss → row undated (fail closed, counts toward coverage floor); false hit → wrong year on a public card. Containment either way is loose: a row “Love” matches every MB group whose folded title contains “love” by that artist; a chart credit “Drake” is contained in any collab credit that includes Drake, so a Drake feature on someone’s 1998 album could date a 2026 single. The earliest-date pick amplifies a false hit toward the OLDEST wrong group yes (test_chart_ages.py:188,241) HYBRID up to 15 paced MB lookups/build (1.1s each) so latency is not the constraint; the deterministic fold is right as the cheap path, but the loose containment on BOTH fields is exactly the “cover’s 1962 date on a 2026 single” fabrication the docstring promises never to make. A Haiku confirm on the chosen group (title/credit/date vs row) at ~15 calls/build is cheap and would let containment stay loose without the false-hit tail. Also note first_release_date (L154) feeds the RIAA plaques card through the same matcher
L78 _lucene_escape, L82 _year_of regex query building / year parse yes KEEP structural
L86 _credit_name joinphrase rebuild string join MB JSON normalization for the match above yes KEEP structural

utils/chart_standings.py

Every decision here is a fold/identity over Billboard rows; the natural-language parsing of CREDITS lives in utils/artist_watch (out of scope) but is invoked from here and decides what the boards say publicly.

file:line pattern input decision fail today tested? verdict reason
L261 credit_parties(row.artist, known) + L263 is_placeholder_credit(party) (artist_watch._PLACEHOLDER_CREDITS frozenset “soundtrack”, “variousartists”, “originalcast”, “karaoke”…; _CONNECTORS “feat”,”featuring”,”ft”,”with”,”x”,”vs”,”and”,”y”) connector tokenization + corroboration set + placeholder deny-list upstream Billboard credit strings which ACTS an entry counts for on the census / catalog / debut boards (a public “Drake has 11 albums” claim) placeholder miss → a non-act (“Cast Recording”, “Original Score”?) takes a census crown (the #2739 class); over-split → invented act (guarded by known); under-split → a collab’s second act loses an entry yes (test_artist_watch.py, test_chart_standings.py) KEEP bulk (100-200 rows × several charts per slot) and must be auditable; the corroboration-set design already fails toward absent. Note for the owner: the placeholder list is the one term list here that grows by incident
L338 _match_keyre.sub(r"[^a-z0-9]+","",title) + fold_tokens(lead_artist(artist)) normalization identity Billboard vs kworb vs sales rows attach a streams/sales figure to a row connector-class mismatch → blank cell (documented, fail closed) yes (4 files) KEEP structural identity; prefer-absent
L559 fold_tokens(board.subject) not in notable set membership kworb top-artist names suppress a genre board whose leader is unknown empty notable → fail OPEN (owner call) yes KEEP own list
L604-611 lead-act tally via lead_artist / fold_tokens connector strip Billboard credits “from N different acts” in the headline wrong N is a public fact error (the Rod Wave 3-vs-6 case, fixed by grouping on lead) yes KEEP same as above
L701 row_key lower-case identity cluster fold yes KEEP structural

Structural: _KIND_TITLE / _KIND_COLUMN_NOTE dict lookups keyed on our own kind (L1030-1062). No regex on free text beyond _match_key.


utils/feed_status.py

Semantic sites: 0. _PROVIDERS / _FAILOVER are dicts keyed on our own source ids; the only tests are getattr(client, "enabled"/"degraded") flags and a numeric pct >= LOW_PCT. KEEP; tested (test_feed_status.py).


utils/filler_solver.py

Semantic sites: 0. _OPPOSITE / corner_cell are keyed on our own corner names (L143-165, corner in _OPPOSITE at L182 with a silent default to “bottom-left”); the palette is RGB nearest-color in grid_vision. Pure game solver, KEEP; tested.


utils/market_art.py

file:line pattern input decision fail today tested? verdict reason
L68 norm_name (NFKD, strip punctuation, casefold) + L110 norm_name(label) == key in leg_image_in / find_leg_image exact normalized name identity upstream Polymarket groupItemTitle / question vs our resolved subject name use that leg’s portrait on a market card miss (“J.D. Vance” vs “JD Vance” folds equal; “Vance” vs “JD Vance” does not) → falls to next rung (fail closed); false hit is near-impossible by construction yes (test_market_art.py, test_names.py) KEEP deliberately exact after the wrong-politician incident; a model here would re-open the wrong-face class
L108 img == event_img URL identity upstream reject the repeated event icon yes KEEP structural
L129 big_enough image dims bytes refuse small art yes KEEP structural

utils/music_policy.py (the file to look at hardest)

Every term list, what it gates, and where:

file:line pattern input decision fail today tested? verdict reason
L78 _RAP_TAGS = {“hip-hop/rap”,”hip-hop”,”rap”,”rap & hip-hop”,”trap”,”gangsta rap”,”hardcore rap”,”underground rap”,”east coast rap”,”west coast rap”,”dirty south”,”southern hip-hop”,”alternative rap”} exact-tag allow list upstream iTunes primaryGenreName rap family → in-house a rap record tagged outside the list (“Drill”, “Grime”, “Hip-Hop/Rap” spelled “Hip Hop / Rap”?) is refused as off_genre (fail closed, one skipped slot) yes (test_music_policy.py) HYBRID see combined row below
L83 _RNB_TAGS = {“r&b/soul”,”r&b”,”soul”,”contemporary r&b”,”neo-soul”,”funk”,”motown”,”quiet storm”} exact-tag allow list same R&B family → in-house “Afrobeats”/”Afro-Soul”/”Dance”/”Electronic” R&B-adjacent records refused; Prince’s “Kiss” tagged Soundtrack refused (documented cost) yes HYBRID
L87 _POP_TAGS = {“pop”} exact single tag same pop → in-house “Dance Pop”, “Pop/Rock”, “K-Pop”, “Latin Pop”, “Adult Contemporary”, “Alternative” refused. False hit: a record Apple files under bare “Pop” that the owner would call indie (the exact-tag rule stops “Indie Pop” but not a mistagged “Pop”) yes HYBRID
L90 _IN_HOUSE_TAGS union used by L106 in_house_genre(tag)normalize_tag(tag) in _IN_HOUSE_TAGS membership same THE genre rule of drop_verdict — absolute first gate on whether the music DROP posts a record at all (cogs/music.py:669-676, per drop compose, 3-5 slots/day + retries) miss → slot skipped and music_house_gate.genre records the tag (the designed feedback loop); blank tag → refused yes HYBRID the allow list is the right deterministic fast path and the owner explicitly chose it over a deny list. The gap is the documented “coarse iTunes tag on an old record” cost (Soundtrack, Alternative, Singer/Songwriter, Dance) — a Haiku call ONLY on the not-on-list tags, given artist + title + tag, asking “is this rap / R&B / pop as a Miami bar would file it?” turns each rejection into a graded decision instead of a silent skip. Volume: only the off-list fraction of ~5 drops/day → a handful of Haiku calls/day. Keep the exact list as the fail-closed default when the model errors
L95 normalize_tag"hip hop"→"hip-hop", "neo soul"→"neo-soul", "r and b"→"r&b" literal spelling folds same make the allow list match Apple’s spellings an unfolded spelling variant (“Hip Hop” with a slash-space) is refused indirect (via in_house_genre tests; normalize_tag itself untested by name) KEEP structural normalization
L67 APPLE_GENRE_IDS = {14,15,18} used by L116 in_house_genre_ids integer id set Apple RSS genres ids filters the “just dropped” pool in utils/music.py:205 (bulk: every row of the 100-row feed) a sub-genre id (Apple’s rap sub-ids ≠ 18?) is refused yes KEEP bulk loop over 100 rows/slot; ids are structured, not free text
L134 is_new_release ISO date parse + window catalog date familiarity rule bypass unparseable/future → False → must clear watched/certified yes (2 files) KEEP structural
L151 drop_verdict ordered boolean gate the above + watched/certified bools (kworb list membership + RIAA search, resolved in the cog) post / skip + reason slug unknown_artist on an act on neither list (CMAT case, intended) yes KEEP (the familiarity half) list membership against real registries is the right shape; note the watched set is a NAME match against kworb (_is_watched, in cogs/music.py, out of scope) — worth a separate look for fold quality

utils/openai_chat.py (model-output inspection + error mapping)

file:line pattern input decision fail today tested? verdict reason
L230 _classify_failurestatus == 429 and "insufficient_quota" in textquota; status in (429,500,502,503,504)provider; (401,403,404)auth; else request substring on error body + status set upstream OpenAI error text FAILOVER to Claude (provider/quota/unprovisioned) vs SURFACE to the canned fallback (auth/request/malformed) — FALLOVER_CATEGORIES L212 a 429 whose body wording changes → provider (still fails over, retried first); a 400 for a model-capability mismatch surfaces the canned line instead of failing over yes (test_openai_chat.py) KEEP must be deterministic and instant on the error path; the one string test is OpenAI’s documented error code
L257 _read_api_response — same "insufficient_quota" substring substring same do NOT retry an exhausted balance wording change → the 429 is retried (latency only) no (indirect) KEEP
L146/L155 is_reasoning_modelmodel.lower().startswith(("gpt-5","gpt-6")); L152 _NO_EFFORT_NONE = {“gpt-6-astra”}; L163 min_effort prefix + set on model id our own configured model id (env-overridable) add token reserve + send reasoning_effort; map “none”→”low” a new family (gpt-7) is treated as non-reasoning → its thinking eats the message budget (the #2869 blackout shape, called out in the comment); a gpt-4o override never gets an effort param (correct) yes KEEP own config string; the fix is extend the tuple when measuring a new id (same rule as claude_client’s capability sets)
L392 finish == "length" and not textreasoning_exhausted; L652 responses_exhaustedstatus == "incomplete" and no text and no function call enum-literal checks on model response upstream OpenAI body retry the turn once with lower effort + higher ceiling a truncated-but-non-empty message is NOT retried (ships a fragment) yes KEEP protocol fields, not language
L481 _content_to_text — returns None on empty content (a refusal) → malformed → surface shape check model response treat a refusal as a failed call (canned fallback) a refusal never fails over to Claude (by design: switching providers “won’t fix it”) yes KEEP note: a refusal is a semantic event but the router already treats it as terminal; no language inspection needed
L593-649 parse_responses_bodyitem["type"] in {"message","function_call","web_search_call"}, ann["type"] == "url_citation" fixed-key tags model response build the function-call loop + link allowlist yes KEEP structural

No sentiment/quality inspection of model TEXT happens in this file; that lives in claude_client.


utils/reference_movie.py

file:line pattern input decision fail today tested? verdict reason
L39 _FILM_WORDS = (“movie”,”film”,”cinema”,”sequel”,”prequel”,”tv show”,”series”,”sitcom”); L40 _STRONG_CUES = (“directed”,”director”,”who direct”,”box office”,”grossed”,”how much did”,”screenplay”,”starring”,”who stars”,”oscar”,”academy award”,”rotten tomatoes”,”metacritic”,”box-office”,”opening weekend”,”who plays”); used by L73 movie_matches: any(w in t ...) or any(c in t ...) substring lists over lower-cased text Discord user text (the /ask question + aspect), reached through the lookup_reference TOOL the model already chose to call (claude_client.py:4931, utils/reference.py:588), 4th in a keyword-router chain Genius → Wikidata → MusicBrainz → Movie → Wikipedia route the reference lookup to TMDB+OMDb vs fall through to Wikipedia miss (“who was in Dune”, “is Sinners any good”, “how long is Oppenheimer”) → Wikipedia catch-all (fail open, weaker citation); false hit (“film” inside “filmed”, “series” in “World Series”, “grossed” in a music-sales question, “who plays” in an NBA question) → a TMDB title search on a non-film query, which returns the first fuzzy movie hit and composes a fact block about the WRONG thing (movie_lookup has no relevance check on tmdb.search_movie L99 — the only guard is len(lines)==1) yes (test_reference_movie.py) HAIKU (the whole _SOURCES matcher chain, not just this one) per-/ask reference call (already a paid tool turn, latency-tolerant); the four sibling matchers (genius_matches, wikidata_matches, musicbrainz_matches) are the same shape and the registry comment already documents ordering hacks (“Wikidata BEFORE MusicBrainz so ‘featuring’ doesn’t shadow”). One Haiku call returning {source, title, year, aspect} replaces four term lists AND fixes the missing “is this hit the right title” check, since it would also hand movie_lookup a clean title + year to search. Note the calling model already decided “this is a reference question”; the keyword chain is a second, dumber router under a smart one
L102 top.release_date[:4] / L149 len(lines) == 1 shape TMDB payload year for OMDb; “nothing resolved” yes KEEP structural

utils/release_age.py

Semantic sites: 0. _MISS_PREFIX startswith (L116, L188) is our own cache-encoding sentinel; date.fromisoformat(raw[:10]) is a date parse. The identity decision (“is this catalog row the same release”) lives in deezer.track_release_date / resolve_music_row (out of scope). KEEP; tested via release_age_days (4 files); cached_release_date untested by name.


utils/subject_image.py

file:line pattern input decision fail today tested? verdict reason
L204 comment “No deterministic host/caption denylist” + L329/L340 pick_subject_imageconfirm_subject_image (Haiku group rank, Sonnet confirm) already a model judgment downloaded candidates + origin host + caption context which photo ships, or none (fail closed) yes (2 files) KEEP this file is the worked example of a term list REMOVED in favor of a vision gate (#1897 → Sonnet confirm)
L370 is_source_photo(src)src in (SOURCE_PHOTO, SOURCE_PHOTO_PICK) own enum our rung string delivery gate trust level yes KEEP own enum
L134 ctx = title + _host(origin_url) context assembly upstream fed to the vision picker (not a decision here) KEEP

Structural (KEEP): L239 url.startswith(("http://","https://")); L245 ctype.startswith("image/") content-type guard; L221 max(im.size) <= _MAX_IMAGE_DIM; L100 _host.


utils/the_odds_api.py

Semantic sites: 0 — every decision is on fixed API keys or HTTP status. Structural table:

file:line pattern input notes
L142/L190/L242 md.get("key") != "h2h" / != key; L470 mkey.startswith("player_"); L801 == "outrights" fixed market-key literals Odds API JSON which market tree to read; a renamed key silently yields no board (fail closed); tested
L158 _clean_link"{" in u or "}" in u, startswith("https://") placeholder detection bookmaker link drop {state}-templated links; tested
L500 _is_permanent_4xx (400-499 except 429), L507 _request_ok status classes HTTP breaker health accounting (permanent 4xx = healthy host); tested
L636 usage headers x-requests-* parse header parse HTTP credit budget telemetry
L393-411 score keyed by team name == home_team/away_team exact identity Odds API JSON home/away score mapping; a renamed team string → None score (fail closed)
L263 counts.most_common(1) consensus line numeric point-market line-shop

Out-of-scope helpers these files depend on (flagged, not audited in depth)

These are keyword/regex decisions that the in-scope files INVOKE; the owner should read them with the sites above:


Top HAIKU / HYBRID candidates in this scope (ranked by brittleness × user impact)

  1. utils/reference_movie.py:39-82 movie_matches (+ the sibling _SOURCES matchers in utils/reference.py) — HAIKU. Substring lists over user questions, ordered by hand-tuned shadowing rules, with no relevance check after the TMDB title search. A false hit composes a fact block about the wrong film into an /ask answer. Per-call volume is one already-paid tool turn. One Haiku router returning {source, clean_title, year, aspect} replaces four lists and fixes the missing title-verification at the same time.
  2. utils/music_policy.py:78-113 _IN_HOUSE_TAGS / in_house_genre — HYBRID. The exact-tag allow list is correct as the fast path and owner-mandated, but every off-list tag is a silent skipped slot (Soundtrack, Alternative, Dance, Afrobeats, Latin Pop, Singer/Songwriter), and the list only grows after a rejection is noticed in telemetry. A Haiku call on just the off-list fraction (artist + title + tag → rap/R&B/pop?) at a few calls/day converts those into graded decisions while keeping the fail-closed default. Highest user impact in scope: it decides what the drop lane posts to X.
  3. utils/chart_ages.py:104-140 _matching_date — HYBRID. Containment-either-way on BOTH title and artist-credit, then take the EARLIEST date, is a loose match whose false-hit direction (an older wrong group) is exactly the “1962 cover date on a 2026 single” the module promises never to print, on a public card and on the RIAA plaques card via first_release_date. Volume ≤15 paced lookups/build; a Haiku confirm on the picked group costs nothing against the 1.1s MusicBrainz pacing already paid.
  4. cogs/music_releases.py:81 _REISSUE_RE — HYBRID. Four literals decide whether a years-old song’s plays print as a “first-night” number on the card. The L536 date-equality check catches most damage, so medium priority; run Haiku only on titles carrying “Deluxe / Edition / Version / Live / Expanded”.
  5. cogs/discourse.py:1621 looks_like_sports (via utils/markets) — HYBRID. \bsport + the league/betting list on channel name+topic decides whether a betting-flavored line enters an icebreaker. Rare call, trivial to model, and the regex comment already lists the false-hit words it fights.
  6. cogs/curate.py:800 prefers_media(..., topic) / _IMAGE_TOPIC_TERMS — HYBRID. A positive-only term list on the mod-written topic picks WHICH judge and prompt a room gets; the fallback is a media-density ratio that mis-routed #backpage. One read per room per tick (memoized) — cheap to ask Haiku “image room or link room?” on topic + 6 sample posts, and route_curator already sees the same rooms.
  7. cogs/discourse.py self-correction regex (4 lanes) — KEEP, but fold into the existing discourse_score rubric. Not a new call: the Haiku scorer already reads the same string 40 lines later; adding “shows its working / multiple drafts” to that rubric retires a brittle regex at zero marginal cost. Listed because it is a duplicate of a model judgment, not a gap.

Deliberately NOT recommended for a model: market_art.norm_name exact match (a model re-opens the wrong-face class), openai_chat._classify_failure (error path must be instant + deterministic), all of chart_standings credit splitting (bulk, auditable, already fails toward absent), the_odds_api (fixed API keys), and subject_image (already vision-gated; the term list was removed there on purpose).


Count summary

file semantic sites structural sites notes
cogs/artist_curator.py 0 2 judge already Haiku
cogs/curate.py 5 (1 HYBRID, 4 KEEP) 4  
cogs/discourse.py 9 (1 HYBRID new, 1 HYBRID existing, 1 KEEP-as-prefilter, 6 KEEP) 4 2 of the KEEPs are duplicates of a downstream Haiku judge
cogs/music_releases.py 5 (1 HYBRID, 4 KEEP) 0  
utils/awards.py 0 3  
utils/chart_ages.py 1 (HYBRID) 3  
utils/chart_standings.py 5 (all KEEP) 2 credit parsing owned by artist_watch
utils/feed_status.py 0 2  
utils/filler_solver.py 0 2  
utils/market_art.py 1 (KEEP) 2  
utils/music_policy.py 7 (4 HYBRID rows on one list, 3 KEEP) 0  
utils/openai_chat.py 6 (all KEEP) 1 error-map + model-output shape checks
utils/reference_movie.py 1 (HAIKU) 2  
utils/release_age.py 0 2  
utils/subject_image.py 1 (KEEP, already model) + 1 own-enum 4  
utils/the_odds_api.py 0 6  
total 41 semantic rows (1 HAIKU, 8 HYBRID, 32 KEEP) 39 structural  

Test coverage gaps found while grepping (no direct test by symbol name): cogs/curate._fetchable, cogs/discourse._x_clip and _emit_trending_decline, utils/music_policy.normalize_tag, utils/openai_chat._read_api_response, utils/awards._pick_single/_metric_value (covered only via compute_awards), utils/release_age.cached_release_date, utils/chart_standings._entries_by_act (covered via the board builders), utils/reference_movie._FILM_WORDS/_STRONG_CUES (only via movie_matches).