Skip to content

feat(image-evidence): make evidence-affecting helpers version-visible + CI totality - #690

Merged
WilfordGrimley merged 1 commit into
masterfrom
feat/extractor-ownership-visibility
Aug 5, 2026
Merged

feat(image-evidence): make evidence-affecting helpers version-visible + CI totality#690
WilfordGrimley merged 1 commit into
masterfrom
feat/extractor-ownership-visibility

Conversation

@WilfordGrimley

Copy link
Copy Markdown

Summary

  • Inventories every code path that helps determine a stored ImageEvidence field but carries no *_EXTRACTOR_VERSION of its own (issue Add illustration_id import and Stage D illustration deduction calculator #509's own follow-up): 8 module-private helpers in image_evidence.py (_crop_box_to_pixels, _compute_region_phash, _short_circuit_enabled_by_env, _contains_digit, _parse_is_lexicon_valid, _extract_legal_line, _parse_artist_is_contradicted, _confidently_digit_free) plus 9 externally-imported callables it actually calls from collector_line_artist.py/local_ocr.py/local_image_quality.py (recover_artist_from_card_text, compute_blur_variance, compute_entropy, is_image_truncated, parse_collector_line, parse_legal_line, preprocess_variants, run_tesseract, run_tesseract_text_and_words) - 17 entries total, each declared in a new EXTRACTOR_OWNERSHIP map naming every MANIFEST_EXTRACTOR_KEYS member whose stored field it helps determine.
  • Every one of the 17 is a component of an already-versioned extractor (judgement call made explicitly per entry) - none is genuinely its own extractor. No new MANIFEST_EXTRACTOR_KEYS member, no MANIFEST_EXTRACTOR_CURRENT_VERSIONS entry, no GOLDEN_EXPECTATIONS addition, and no stored ImageEvidence value changes as a result of this PR - all 17 were already feeding their currently-versioned extractor(s) before this landed. This is a declaration + CI-enforcement PR, not a re-extraction trigger.
  • New .github/scripts/check_extractor_ownership_totality.py, a sibling to the pre-existing check_extractor_manifest_sync.py: AST-derives the set of image_evidence.py's own module-private helpers plus every scoped-external import it actually calls, and fails CI if that derived set disagrees with EXTRACTOR_OWNERSHIP in either direction (undeclared new contributor, or a stale entry for a renamed/removed one). Also cross-checks every declared owning key against the real MANIFEST_EXTRACTOR_KEYS, imported directly from check_extractor_manifest_sync.py so the two scripts can never independently disagree about what counts as a real key.
  • Two deliberate, explicitly-documented exclusions from the sweep: local_fallback.py's own helpers and the OCR attempt-tier ladder (_collector_line_ocr_attempts, issue Stage D no-text bucket: OCR preprocessing/crop recovery (88.8% of skips are fixable, not a coverage ceiling) #259) - both being worked on in parallel by other branches per this PR's own brief. Each is already the direct mechanism of an existing versioned extractor, so the omission is a sequencing decision, not a coverage gap.
  • Written convention: new "Stage C extractor ownership" section in docs/features/catalog-completion-plan.md states the rule (own extractor vs. declared component, never left implicit), how the two rosters (manifest + ownership) stay in sync, and the two exclusions above. Pointer paragraph added to image_evidence.py's own module docstring; no behavioral change to that file.
  • Correction to this PR's own brief: local_art_edge.classify_art_edge_continuity (issue Extended art: the owner's two-sample-point art-edge classifier (evidence-only) #617's extended-art classifier) was named as a candidate for this sweep but is not actually reachable from compute_card_evidence today - local_art_edge.py is a declared-but-not-yet-live Stage D calculator (reads already-persisted ImageEvidence.art_crop_px, isn't called from Stage C's own extraction pass), confirmed by grep against the whole cardpicker package finding zero call sites outside its own tests. Documented in the new doc section rather than silently dropped.

Test plan

  • .github/scripts/tests/test_check_extractor_manifest_sync.py + .github/scripts/tests/test_check_extractor_ownership_totality.py, individually and together (python3 -m pytest both files in one invocation) - 39 passed, no cross-file state leakage between the two scripts' REPO_ROOT monkeypatches.
  • New test suite includes a case (test_undeclared_new_contributor_fails) proving a newly-added, newly-called module-private helper with no EXTRACTOR_OWNERSHIP entry fails CI, plus a matching case for an undeclared external call, a stale-entry case, a fake-manifest-key case, and the excluded-ladder boundary (a name called only from inside _collector_line_ocr_attempts is correctly NOT required).
  • check_extractor_ownership_totality.py and check_extractor_manifest_sync.py both run clean against the real repo (17 declared contributors / 11 manifest keys).
  • python3 .github/scripts/docs_lint.py --strict clean.
  • cardpicker/tests/test_image_evidence.py (111 tests) and cardpicker/tests/test_golden_set.py (29 tests), individually and together against real testcontainers Postgres/ES (issue Test suite is order-dependent: leaked fetch-failure window trips the envelope across files (8 failures on master) #679's order-dependence concern) - 111 / 29 / 140 all passed, confirming the docstring-only image_evidence.py change introduced no regression.
  • pre-commit run (ruff, isort, black, mypy, prettier, docs-lint's readme parity) green on every touched file.
  • CI - not watched by this session per dispatch convention.

CLAUDE.md task-end checks

  • Wiki maintenance: N/A - no user-visible or admin-visible change (CI tooling + docs only).
  • Extractable-primitives ledger: N/A - no primitive extracted or retired; this PR adds declaration/enforcement, not a module.
  • Constant-rename equivalence: N/A - no module-level constant renamed, extracted, moved, or retired; every name added is new.
  • Docs convention: edited docs/features/catalog-completion-plan.md in place (new dated section, matching that file's own established chronological-log convention) rather than a separate changelog file.

… + CI totality

Inventory + declare EXTRACTOR_OWNERSHIP for every module-private helper in
image_evidence.py and every externally-imported callable it actually calls
from collector_line_artist.py/local_ocr.py/local_image_quality.py that helps
determine a stored ImageEvidence field but carries no *_EXTRACTOR_VERSION of
its own (issue #509's own follow-up). All 17 entries are components of an
already-versioned extractor - none are new extractors, so no manifest key,
golden-set expectation, or stored value changes.

- .github/scripts/check_extractor_ownership_totality.py: AST-derived
  totality check, sibling to check_extractor_manifest_sync.py. Derives the
  set of module-private helpers + called scoped-external imports reachable
  from compute_card_evidence's own call graph and fails CI if it disagrees
  with EXTRACTOR_OWNERSHIP in either direction. Cross-checks every declared
  owning key against the real MANIFEST_EXTRACTOR_KEYS (imported directly
  from check_extractor_manifest_sync.py). Excludes local_fallback.py and
  _collector_line_ocr_attempts (the OCR tier ladder) - both out of scope,
  worked on in parallel per this PR's own brief.
- .github/scripts/tests/test_check_extractor_ownership_totality.py: fixture
  + real-repo tests, including a case proving a newly-added undeclared
  contributor fails CI.
- .github/workflows/docs-lint.yml: wires the new tether in as its own job,
  mirroring extractor-manifest-sync exactly.
- docs/features/catalog-completion-plan.md: "Stage C extractor ownership"
  section - the rule, why the two rosters stay in sync, the two explicit
  exclusions, and a correction to this PR's own brief (local_art_edge.py's
  extended-art classifier, cited as an example, is not actually reachable
  from compute_card_evidence today - it's a declared-not-yet-live Stage D
  calculator, not a Stage C extractor).
- image_evidence.py: pointer paragraph in the module docstring; no
  behavioral change.
@WilfordGrimley
WilfordGrimley merged commit 966b58a into master Aug 5, 2026
15 checks passed
WilfordGrimley added a commit that referenced this pull request Aug 5, 2026
…s to 2 (issue #677) (#698)

* perf(image-evidence): collapse Stage C OCR attempt ladder from 3 tiers to 2 (issue #677)

_collector_line_ocr_attempts drops tier 3 (a PSM-11 re-try of tier 1's own
variants) - measured, not guessed. Two fresh real-production probes
(450 forced-escalation cards combined) walked the old 3-tier ladder to
full completion and recorded, per card, the first tier at which a
candidate-validated genuine match appeared: tier 2 produced 2 genuine
matches across the sample, tier 3 produced zero - only more lexicon-valid
-but-uncorroborated noise, consistent with the 2026-07-23 preprocessing
probes' own ~99% hopeless-art-noise finding for this population.
Cross-checked against all 30 golden_set.GOLDEN_CARD_IDS cards: none ever
resolved uniquely at tier 3, so this collapse changes zero golden-set
expectations.

Worst case drops from 8 tesseract calls to 6 (down from #259-#677's own
4x pre-#259 cost to 3x).

collector_line_ocr/collector_line_tsv/artist_ocr extractor versions
bumped v2->v3 (all three read through the same escalation loop, per
EXTRACTOR_OWNERSHIP's own 'bump every listed key together' convention);
legal_line unchanged.

Investigated and explicitly did NOT ship the companion 'unify the
collector-line/legal-line OCR reads into one full-width pass' idea named
in this issue's own brief: a real-image accuracy probe found it regresses
46/150 (30.7%) of currently-correct collector-number reads (mostly proxy/
watermark text getting picked up by the set-code regex in place of the
real code, or the wider crop causing tesseract to drop the collector-
number line outright), with zero improvements. Reported as a legitimate
negative finding rather than shipped anyway.

Lifts the EXTRACTOR_OWNERSHIP EXCLUDED_HELPERS entry for the OCR ladder
(check_extractor_ownership_totality.py) that PR #690 left in place while
this branch was in flight - _collector_line_ocr_attempts and
preprocess_fallback_variants now carry real ownership entries.

Verification: full backend suite (3598 passed, 8 skipped, 0 failed),
ownership-totality + manifest-sync scripts clean with their own unit
tests green (40/40), docs_lint --strict clean, pre-commit clean on all
tracked files.

* fix(image-evidence): bump artist_ocr to v4 to avoid colliding with #685's v3

ARTIST_OCR_EXTRACTOR_VERSION was set to "artist-ocr-v3" on this branch for
issue #677's ladder-collapse dedup, but PR #685 (merged to master
separately) already claims "artist-ocr-v3" for an unrelated change
(wiring modern_artist_credit's crop fallback into live extraction). Two
different behaviours under one version string would defeat the staleness
filter MANIFEST_EXTRACTOR_CURRENT_VERSIONS exists to enforce.

Bumps artist_ocr straight to v4, updates the matching manifest entry in
run_image_evidence_cohort.py, and documents the collision in both the
version constant's own history comment and catalog-completion-plan.md's
#677 section. collector_line_ocr/collector_line_tsv stay at v3 (master
still has both at v2, no collision).

Also fixes golden_set.py's #677 re-verification comment, which cited
MPCAutofill/scripts/experiments/ocr_ladder_tier_attribution.py - a
worktree-only analysis script never committed to this repo, per the
established convention (see docs/reports/2026-07-23-ocr-preprocessing-
probe.md's own LIVE STATE) of not committing one-off probes. Points at
catalog-completion-plan.md's already-committed #677 section instead,
which documents the same 450-card probe's methodology and results.

* fix(docs): cite OCR ladder probe scripts as prose, not backticked paths

Both scripts are deliberately uncommitted (worktree-only analysis
tooling, same convention as the 2026-07-23 probe reports). Backticked
citations to their paths tripped docs_lint.py's path-reference rule
since MPCAutofill/scripts/ doesn't exist in the repo. Rephrase as
prose so the provenance stays legible without a machine-checkable
path reference to a file that was never meant to be committed.
WilfordGrimley added a commit that referenced this pull request Aug 5, 2026
…s to 2 (issue #677) (#698) (#702)

* perf(image-evidence): collapse Stage C OCR attempt ladder from 3 tiers to 2 (issue #677)

_collector_line_ocr_attempts drops tier 3 (a PSM-11 re-try of tier 1's own
variants) - measured, not guessed. Two fresh real-production probes
(450 forced-escalation cards combined) walked the old 3-tier ladder to
full completion and recorded, per card, the first tier at which a
candidate-validated genuine match appeared: tier 2 produced 2 genuine
matches across the sample, tier 3 produced zero - only more lexicon-valid
-but-uncorroborated noise, consistent with the 2026-07-23 preprocessing
probes' own ~99% hopeless-art-noise finding for this population.
Cross-checked against all 30 golden_set.GOLDEN_CARD_IDS cards: none ever
resolved uniquely at tier 3, so this collapse changes zero golden-set
expectations.

Worst case drops from 8 tesseract calls to 6 (down from #259-#677's own
4x pre-#259 cost to 3x).

collector_line_ocr/collector_line_tsv/artist_ocr extractor versions
bumped v2->v3 (all three read through the same escalation loop, per
EXTRACTOR_OWNERSHIP's own 'bump every listed key together' convention);
legal_line unchanged.

Investigated and explicitly did NOT ship the companion 'unify the
collector-line/legal-line OCR reads into one full-width pass' idea named
in this issue's own brief: a real-image accuracy probe found it regresses
46/150 (30.7%) of currently-correct collector-number reads (mostly proxy/
watermark text getting picked up by the set-code regex in place of the
real code, or the wider crop causing tesseract to drop the collector-
number line outright), with zero improvements. Reported as a legitimate
negative finding rather than shipped anyway.

Lifts the EXTRACTOR_OWNERSHIP EXCLUDED_HELPERS entry for the OCR ladder
(check_extractor_ownership_totality.py) that PR #690 left in place while
this branch was in flight - _collector_line_ocr_attempts and
preprocess_fallback_variants now carry real ownership entries.

Verification: full backend suite (3598 passed, 8 skipped, 0 failed),
ownership-totality + manifest-sync scripts clean with their own unit
tests green (40/40), docs_lint --strict clean, pre-commit clean on all
tracked files.

* fix(image-evidence): bump artist_ocr to v4 to avoid colliding with #685's v3

ARTIST_OCR_EXTRACTOR_VERSION was set to "artist-ocr-v3" on this branch for
issue #677's ladder-collapse dedup, but PR #685 (merged to master
separately) already claims "artist-ocr-v3" for an unrelated change
(wiring modern_artist_credit's crop fallback into live extraction). Two
different behaviours under one version string would defeat the staleness
filter MANIFEST_EXTRACTOR_CURRENT_VERSIONS exists to enforce.

Bumps artist_ocr straight to v4, updates the matching manifest entry in
run_image_evidence_cohort.py, and documents the collision in both the
version constant's own history comment and catalog-completion-plan.md's
still has both at v2, no collision).

Also fixes golden_set.py's #677 re-verification comment, which cited
MPCAutofill/scripts/experiments/ocr_ladder_tier_attribution.py - a
worktree-only analysis script never committed to this repo, per the
established convention (see docs/reports/2026-07-23-ocr-preprocessing-
probe.md's own LIVE STATE) of not committing one-off probes. Points at
catalog-completion-plan.md's already-committed #677 section instead,
which documents the same 450-card probe's methodology and results.

* fix(docs): cite OCR ladder probe scripts as prose, not backticked paths

Both scripts are deliberately uncommitted (worktree-only analysis
tooling, same convention as the 2026-07-23 probe reports). Backticked
citations to their paths tripped docs_lint.py's path-reference rule
since MPCAutofill/scripts/ doesn't exist in the repo. Rephrase as
prose so the provenance stays legible without a machine-checkable
path reference to a file that was never meant to be committed.

(cherry picked from commit c5eee1c)
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.

1 participant