name_search corrupts Japanese names: diacritic stripper drops dakuten/handakuten (イーブイ → イーフイ) on 14.5k rows #579
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What
card_search._strip_diacritics(backend/app/services/card_search.py:45-53) normalizes to NFD and drops every char whose Unicode category isMn. In NFD, voiced katakana/hiragana decompose into base kana + a combining dakuten (U+3099) or handakuten (U+309A) — bothMn— so they are deleted:Every writer routes through
_name_search:scrydex_canonical_ingest.py:202,214,tcgcsv_canonical_ingest.py:274,285,canonical_write.py:279,justtcg_history_rescue.parse_slug. Socanonical_card.name_searchANDcard_cache.name_searchare wrong, and the trigram GIN index (main.py:421) is built over the corrupted text.Prod impact (measured 2026-09-02)
card_cacherows havename_search <> lower(name)(every name containing a voiced kana). Plus 10 stray rows in lorcana/gundam/one-piece.routes/scan.py:72-76 _scan_name_search) applies the same strip to the query — but it uses NFKD while the writer uses NFD, so reader/writer already diverge on compatibility chars, and ブ/フ, ポ/ホ, バ/ハ etc. now collide.name_searchfails for JP: the graded ladder's name alignment (external_scan.pytier a/a2),_attach_existing_canonical(canonical_write.py:191).Fix shape (do not fix yet — scoping only)
Mnfor Latin base chars), and have reader + writer share ONE normalizer.UPDATE canonical_card SET name_search = f(name)thenreproject_all(or a directcard_cacheUPDATE + REINDEX of the trgm index).Found during the 2026-09-02 card-data review. Related: #578 (one of its three compounding causes).
Filed from a Claude Code session
STATUS — PR Gambit-Inc/gambit#398 open,
verifygreen, changes requested.Blocker found in review:
routes/public_catalog.py:34androutes/api_explorer.py:24stillhold byte-identical copies of the pre-fix stripper, applied to the user's query and then
ILIKE'd against
card_cache.name_search. Today both sides are consistently corrupted so JPsearch works; fixing only the write side turns
q=ポケモンinto a guaranteed zero-row resulton the public catalog. Two one-line imports of the shared
_name_search.Also established in review, and it changes this issue's urgency: the 14,535 rows self-heal.
scrydex_canonical_ingest.py:201reassignsname_searchunconditionally on every resolve,and the daily
pull_allre-walk coverspokemon(EN+JP). Exposure is one sweep, ≤24h — thebackfill script is an accelerator, not a gate on the promote. Lucas pushed review fixes at
20:07 UTC.