Skip to content

Add modern-bare-name artist-credit recognizer (issue #368) - #390

Merged
WilfordGrimley merged 2 commits into
masterfrom
modern-artist-credit-recognizer
Jul 23, 2026
Merged

Add modern-bare-name artist-credit recognizer (issue #368)#390
WilfordGrimley merged 2 commits into
masterfrom
modern-artist-credit-recognizer

Conversation

@WilfordGrimley

Copy link
Copy Markdown

Description

Issue #368, owner-endorsed approach "2-B": ImageEvidence.artist_ocr_raw_text is already captured for 215,049 cards (98.5% of evidence), but artist_ocr_name extraction succeeds on only 13,588 of them, because the existing extractor (local_fallback.extract_artist_name, PROTECTED CORE, untouched by this PR) only recognizes old-border "Illus. <name>" credit lines. Modern (post-2003) frames instead print a bare name beside a brush-glyph icon tesseract reliably mangles — a shape that anchor was never built to catch.

New module cardpicker/modern_artist_credit.py — a from-scratch, in-house recognizer (PROVENANCE noted in its own header: entirely original code, no external patterns copied) that re-parses the already-stored raw OCR text: extracts every short (≤4-word) token-window per line as a name candidate, then validates each against the real CanonicalArtist lexicon (~2,523 names) via a tolerant difflib-ratio fuzzy match. Two conservative guards, both found and fixed by sampling real production rows before finalizing thresholds:

  • Margin-over-runner-up (not just an absolute ratio floor) — mirrors local_fallback.SYMBOL_MARGIN's own established convention for this exact risk shape.
  • Running-prose-neighbour guardartist_ocr_raw_text is OCR'd from a wide bottom-of-card crop band that often also catches a fragment of flavor text. Sampling surfaced a real false positive: card evidence id 517's raw text contained French flavor text ("...ensuite, plus rien." = "...then, nothing more.") right next to the actual credit line, and "rien." happens to also be a real (if obscure) artist's stage name in the lexicon. Fixed by rejecting any candidate immediately preceded by a closed-class grammatical connector word (plus, and, the, avec, ...) — verified this rejects zero true positives across ~6,500 sampled rows while fixing the one false positive found.

Handles the flagship garble case from the task brief directly: card 83867's raw text OCR'd "Kalk Kopinski" for the real "Karl Kopinski" (edit-distance 2) — correctly recovered.

New management command backfill_modern_artist_names — house pattern (dry-run default, --write to persist, PilotRunLedger self-recording, chunked .iterator(), capped audit sample). Re-parses stored artist_ocr_raw_text only — no image fetch, no OCR call anywhere in either module. Never overwrites a non-blank artist_ocr_name (enforced by the eligibility queryset itself, plus a defence-in-depth re-check immediately before any write).

Downstream — nothing new wired, on purpose: local_calculate_verdicts.calculate_join_key_verdict's existing "ARTIST-OCR corroboration" step already reads evidence.artist_ocr_name in production. Every name this backfill fills flows into printing identification automatically on that calculator's next pass over the affected cards.

Docs: docs/features/catalog-completion-plan.md's OCR-group section (which documents the original artist_ocr_name/Illus.-anchor gap) updated in place with a short paragraph pointing at this closure, per house doc convention.

Verification (production dry-run, read-only)

Ran backfill_modern_artist_names (no --write) directly against the live production database (read-only reads + its own single PilotRunLedger row — the only write, exactly as intended):

considered=201,461   would_fill=130,427 (64.7%)   no_match=71,034 (35.3%)   filled=0
elapsed=3,308s (~55 min)

Confirmed zero ImageEvidence.artist_ocr_name rows changed (13,588 non-blank before and after).

20-name confidence sample from the real run (ratio / margin-over-runner-up):

Candidate Matched Ratio Margin
STEVEN BELLEDIN Steven Belledin 1.0 0.400
CALEB MEURER Caleb Meurer 1.0 0.333
DAN MURAYAMA SCOTT Dan Murayama Scott 1.0 0.529
MIKE BIEREK Mike Bierek 1.0 0.300
LINDSEY LooK Lindsey Look 1.0 0.417
Lius LASAHIDO Lius Lasahido 1.0 0.385
Marco Bucci Marco Bucci 1.0 0.304
WARREN MAHY Warren Mahy 1.0 0.500
JAIME JONES Jaime Jones 1.0 0.250
Ros ALEXANDER Rob Alexander 0.923 0.340
NINO Is Nino Is 1.0 0.385
CALEB MEURER Caleb Meurer 1.0 0.333
MILA PESIC Mila Pesic 1.0 0.400
MARK ZUG Mark Zug 1.0 0.333
NICHOLAS GREGORY Nicholas Gregory 1.0 0.333
DAREK ZABROCKI Darek Zabrocki 1.0 0.267
RON SPENCER Ron Spencer 1.0 0.238
PAOLO PUGGIONI Paolo Puggioni 1.0 0.407
MAGALI VILLENEUVE Magali Villeneuve 1.0 0.529

Every sampled ratio ≥0.92, every margin ≥0.24 — comfortably clear of the conservative floors (MIN_RATIO_SINGLE_WORD=0.92, MIN_RATIO_MULTI_WORD=0.85, MIN_MATCH_MARGIN=0.06).

Full backend test suite (pytest cardpicker/, host venv against testcontainers Postgres/ES, never production): 1,700 passed / 4 skipped, plus 47 new tests for this change (31 in the two new test modules) — all passing. 6 pre-existing snapshot failures in test_views.py (TestGetSampleCards/TestNewCardsFirstPages/TestNewCardsPage/TestPostExploreSearchResults) reproduced identically on a clean master checkout with none of this PR's files present (verified via git stash) — confirmed pre-existing/environmental, not caused by this change.

Checklist

  • I have installed pre-commit and installed the hooks with pre-commit install before creating any commits.
  • I have updated any related tests for code I modified or added new tests where appropriate.
  • I have manually tested my changes as follows:
    • Unit tests for the pure recognizer against realistic raw-text fixtures, including the Kopinski garble case and the "rien." false-positive case, plus lexicon-match threshold/no-overwrite/dry-run/write/ledger-row tests for the DB-touching layer and command (47 new tests, all passing).
    • Full cardpicker/ test suite run twice, confirming the 6 failing tests are pre-existing on master independent of this change.
    • Dry-run against the live production database (read-only reads, one PilotRunLedger row written) — see counts/sample above.
  • I have updated any relevant documentation or created new documentation where appropriate.

Closes #368

#368)

New cardpicker.modern_artist_credit module: a from-scratch, in-house
recognizer that re-parses already-stored ImageEvidence.artist_ocr_raw_text
against the CanonicalArtist lexicon (tolerant of OCR garble via a
ratio-plus-margin threshold) to recover modern bare-name credit lines the
existing Illus.-anchored extractor (PROTECTED CORE, untouched) can't
reach. backfill_modern_artist_names management command (dry-run default,
--write to persist, never overwrites a non-blank name) fills the gap;
no image fetch or OCR involved, and no new wiring needed downstream since
local_calculate_verdicts already consumes artist_ocr_name in production.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…pshots

CardFactory/CanonicalArtistFactory calls in
test_backfill_modern_artist_names.py were permanently advancing
factory_boy's process-global Sequence counters, shifting the ambient
cumulative count test_views.py's snapshot tests hardcode (e.g.
"Artist 102") when run later in the same suite. Adds the repo's
established _preserve_shared_factory_sequences autouse fixture (see
test_printing_consensus.py and ~30 other test files using the same
pattern); verified via a throwaway probe that the fixture now leaves
CardFactory/CanonicalArtistFactory's sequence counters untouched
across the file's own tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@WilfordGrimley
WilfordGrimley merged commit 7e0d1f1 into master Jul 23, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

artist_ocr's Illus.-anchor-only pattern misses modern bare-name credit lines (protected core, scoping only)

1 participant