a discord bot for the tootsies server. ask, recap, discuss, ship features by typing.
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.
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.
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.
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.
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.
| # | 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. |
| # | 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. |
| # | 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. |
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. |
Checked and kept deterministic, with the reason:
db.bookie_bet_outcome, cogs/bookie.py _winning_team, every api_sports status set, every price threshold. A model must not decide a payout._MEMORY_FENCE, output_checks.memory_note_violations, roles.SAFE_PERMISSIONS, healthcheck._is_read_only, names / aliases (privacy). Never a model.ungrounded_numbers, ungrounded_row_tally, _AGE_CLAIM_RE, retrospective_values, strip_position_claims. The regex counts better than a model.is_empty_response, every _parse_* closed-set validator, track: / xpost: tags, <voice> / <gif> / <image> tags. They validate a model answer; the classification is already the model.x_reply_draft._TICS (naming the phrase works; showing drafts made tics worse, 9/35 to 19/36), slop_score (the independent lexicon signal is the point), /ask grounding pre-classifiers (tried and removed, see claude_client.py near classify_mention_intent), catalog_art.ad_banner_reject (docstring rejects a vision judge, measured).engagement.py (300k-message scan), games._grade_guess (every chat message during a round; the EASY near-miss band already escalates to judge_song_guess), versuz.is_play_command (per voice-chat message).market_art.norm_name (wrong politician), steam_art.pick_app (Uno), espn.match_team_logo (ambiguity refuses), deezer._credit_matches_contributors (media-gate exemption), _artist_named_in_strict (#2691), _IDENTITY_VERIFIED_ART.radio.COVERED_FORMATS, artist_socials._ARTIST_SOCIALS, music_news._TRUSTED, riaa._CURATED_OMITTED_CREDITS, music_policy allow list (fast path).utils/dedup.arbitrated_match + topic_duplicate, cogs/x_mentions._resolve_subject, chart_boards album-track recovery + match_album_tracks, watch_link rung above the Haiku pickers, sports_desk._resolve_unknown_sports.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.
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:
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”. |
claude.match_album_tracks already does exactly this shape for tracklists and is the template.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.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 |
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.
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.
| 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 |
| 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.
| 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.
| 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 |
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.
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.
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.
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.
| 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.
| 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.
(artist.lower(), title.lower()) :619, chart-pos join :658, _parse_date 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, …).
| 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.
_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.
_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.
| 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 |
_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.
| 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 |
_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.
_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.
| 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.
_YT_NOISE_RE:375 (official|lyric|audio) title cleanup, “- Topic” strip, role=="featured" upstream field: 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.
_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).
match_album_tracks already recovers spelling misses (:213-234)."*" feature mark, row_is_new flag).| 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. |
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.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.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.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.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.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).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.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.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.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).
| 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.
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):
claude.classify_market_intent (intent + league from a user query; classify_intent / detect_league are its regex fallback).claude.image_subjects / image_subject (kind + name + artist + sport + team, from any title or headline; called by entity_image.resolve_desk_image and by market_subject_image.resolve_market_image).claude.market_take_names_leader (Netflix fabrication judge on a composed take).claude.classify_sports_beats (sport tag for stories the regex could not read).claude.drop_score, winner_line_score, sports_desk_score (0.6 ship floors).claude.outcome_extract, winner_extract (winner name from web text).find_subject_image / pick_subject_image / gated_source_photo (vision gates).topic_duplicate (Haiku text dedup on every ScheduledPoster).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.
| 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.
| 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.
| 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.
| 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.
| 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.
| 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.
| 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.
| 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.
| 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.
| 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.
| 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.
| 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.
| 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.
| 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.
| 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.
| 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.
| 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.
| 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.
| 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 |
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.
| 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.
| 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.
| 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.
| 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.
| 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.
| 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.
| 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.
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._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._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.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._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.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.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.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).qualify_platform_metric _PLATFORM_PATTERNS. Nine hand-listed platforms. A new one ships an ambiguous “Views” title on the card. Cache by event ticker.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.
| 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.
claude_client.py 20105 lines, cogs/ask.py 5092, cogs/games.py 3673, cogs/settings.py 2974) were swept with a regex grep for re.compile|frozenset|startswith|endswith|_WORDS|_TERMS|_KEYWORDS|_PATTERNS|_RE|_HINTS|_MARKERS|_CUES|_PHRASES|_BANNED|.lower() in|in ("...") and every hit region was read. claude_client.py had every def _parse_* / classifier method read in full.tests/ names the symbol (grep). “0” means no test references the symbol by name; it may still be covered indirectly.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 |
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) |
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.pyStructural 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 |
utils/link_enrich.py, utils/watch_link.py, utils/url_guardrail.py, utils/comments.py, utils/reddit.py| 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 |
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.pyNo 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 |
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.pyNo 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 |
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 |
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 |
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.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.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.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.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.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.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.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.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.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).
| 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 |
cogs/settings.py (2974 lines) and cogs/ask.py (5092) were grep-swept, not read line by line; the grep patterns are broad but a bare if "word" in text with no helper name could have been missed. Same for cogs/games.py outside the regions read.utils/feeds.py (is_channel_dead, room_is_quiet), utils/markets.py (_team_match), utils/names.py (name_in_text), utils/artist_watch.py (credit_parties, fold_tokens), utils/news_age.py, utils/music_news.py were referenced by in-scope files but are OUT of the given scope; several are keyword sites (room quietness, team-name matching, name matching) and belong in the sibling audits.verify_music_claim’s model id was not confirmed (method body not read); listed with “?”.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.
| 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).
| 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_RE → title_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").
| 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.
| 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.
| 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_RE → parse_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.
| 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.
| 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.
_bucket unicode ranges L274; _drawable emoji strip L435; _CUT_NOTE_RE L760; ACCENTS L103.
| 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 |
_TEXT_COLUMNS L180; _movement L198; chart.kind == "radio" routing; names.display_case.
subject fallback keys (artist / headlineartist / entry); latest_issue_id date compare.
| 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 |
_TITLE_HEADERS {single, song, title} L42; startswith("artist") L68; "discography" in t.lower() L128 (table-shape detection).
_COLUMNS header→field map L127; _header_fields; _parse_trajectory; money/pct regexes.
| 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_SUFFIXES → short_studio; _NAME_PARTICLES / _NAME_SUFFIXES → short_director |
studio / director strings | card text | cosmetic | yes (1 / 1) | KEEP | |
| cinema_boards.py:113-123 | _PACKED_SCORE_RE, _ROW_POSITION_RE, _POSITION_CLAIM_RE → strip_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.
| 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.
| 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.
| 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.
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.
| 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").
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.
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).
job == "Director" L235; youtube/trailer/teaser/official L556; date window L479. FLAG search_movie L521 returns results[0] unverified (see cinema_desk.py:839).
_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).
_FAILED_STATUSES, _died_in_build, substring log filter L392, sev != "INFO")."column limit" in detail or "exceed" in detail L218 classifies a 400 body; logger-name / "EVENT " guards L369/372).| 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.
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.
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.
| 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.
_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.
| 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_PREFIXES → health_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. |
"exist" in str(body).lower() L2501 (409/422 twin); _COL_ALIAS / _COUNT_CALL L2521-2522 (bars-vs-line render rule); startswith("_") system fields L2712.
_SAME_RECORDING (album version / explicit / remastered / radio edit …, NOT remix/live), _NOT_AN_ACT_NAME (soundtrack, orchestra, tribute, karaoke, cover, band, ensemble), accept() three rules. S but MANUAL — output is a list to check by hand. KEEP; a Haiku “same recording / is this an act” second opinion would fit here better than anywhere at runtime, because a human reads the result. Rejects 46/169 candidates, all correct."equivalent" not in title.lower() — S, manual; KEEP._VISUALISER_RE / _LYRIC_RE on a YouTube title → video kind label — S, manual mod tool; KEEP._SCORE_RE, _LOOSE_FEAT_RE, _SUFFIX_RE; L191-196 token overlap; L276 startswith — S, manual; KEEP.a_ prefix; roster name lowercase compare).scripts/dryrun_*.py, ablate_*, audition_voice.py, refresh_patrons_wall.py, versuz_recap.py etc. are one-off harnesses; not read line by line (out of the runtime-classification bar).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).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).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).display_title recasing — ALL-CAPS → title case by rules; SZA→”Sza”, DNA.→”Dna.” on public cards. HYBRID/HAIKU per card. Tested (3).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)._FACTOID_INTENT — regex intent read on user questions. HYBRID via the shared intent classifier. Tested.find() title containment — ‘Golden’-class ambiguity backs a wire claim with the wrong row. HYBRID on >1 match. Tested (indirect)._NOT_AN_ACT in own_credit — 11-item list decides act vs cast/compilation. HYBRID on unresolved cells. Tested (1)._clean_news — phrase list decides whether a Perplexity answer is “no news”. HYBRID (prompt sentinel first). Untested.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).
| 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.
is_functional_audio has one test file, _clean_news, _BASE_LABEL_RE, _debut_lead have none by name).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.
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.
| 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 _fetchable — any(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 | — | — |
| 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_url — is_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 | — | — | — |
| 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 |
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).
| file:line | pattern | input | decision | fail today | tested? | verdict | reason |
|---|---|---|---|---|---|---|---|
L104-140 _matching_date — t_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 |
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_key — re.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.
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).
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.
| 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 |
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 |
| file:line | pattern | input | decision | fail today | tested? | verdict | reason |
|---|---|---|---|---|---|---|---|
L230 _classify_failure — status == 429 and "insufficient_quota" in text → quota; 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_model — model.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 text → reasoning_exhausted; L652 responses_exhausted — status == "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_body — item["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.
| 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 |
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.
| file:line | pattern | input | decision | fail today | tested? | verdict | reason |
|---|---|---|---|---|---|---|---|
L204 comment “No deterministic host/caption denylist” + L329/L340 pick_subject_image → confirm_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.
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 |
These are keyword/regex decisions that the in-scope files INVOKE; the owner should read them with the sites above:
utils/markets.py:5950-5964 _SPORTS_PATTERNS / _PM_PATTERNS / _WILL_X_BY_RE / _LEAGUE_KEYWORDS (classify_intent, detect_league, looks_like_sports) — used by discourse L133, L1621. A Haiku market_intent classifier already exists behind markets.get_context; the keyword layer is its cost prefilter.utils/curator.py:224 _IMAGE_TOPIC_TERMS (topic_is_image_room) — used by curate L800.utils/artist_watch.py:46 _CONNECTORS, :809 _PLACEHOLDER_CREDITS, lead_artist, credit_parties, fold_tokens — used throughout chart_standings + chart_ages.utils/output_checks.py:289 _SELF_CORRECTION_RE (+ _DRAFT_SEPARATOR_RE) via ship_guard.drop_self_correction — used at 4 discourse lanes.claude_client.py:2718 is_empty_response sentinel — used in every cog here.utils/social_search.py:159 parse_trending_phrases — used by discourse L816.utils/reference.py:583 the _SOURCES matcher chain (genius_matches, wikidata_matches, musicbrainz_matches) — siblings of movie_matches.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.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.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.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”.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.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.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).
| 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).