Skip to content

fix: pre-filter TCGdex results by card number before candidate cap - #317

Open
lamiskin wants to merge 1 commit into
Git-Romer:mainfrom
lamiskin:fix/scanner-number-prefilter
Open

fix: pre-filter TCGdex results by card number before candidate cap#317
lamiskin wants to merge 1 commit into
Git-Romer:mainfrom
lamiskin:fix/scanner-number-prefilter

Conversation

@lamiskin

Copy link
Copy Markdown

Problem

The scanner rarely returns the correct card as match #1 for anything but low-numbered printings. On my install it never matched correctly until I traced it.

backend/api/recognize.py:249 caps each TCGdex name search at the first 8 results:

for c in tcgdex_cards[:8]:

TCGdex returns search results sorted ascending by localId, so this keeps only the 8 lowest-numbered printings of that Pokémon and discards the rest. The recognized card number is then used at :317 only to sort the survivors — never to select from the full result set.

Example, Snorlax (64 EN results, target #63):

10, 11, 15, 26, 27, 30, 33, 33, | 33, 37, 49, 049, 50, 051, 055, 057, 58, 063, 063, 64, 076, ...
└──── the 8 kept ──────────────┘   └──── discarded, including the target at positions 18/19 ────┘

The correct card (me03-063) exists in TCGdex and is thrown away before ranking runs.

Why this contradicts #96

PR #96 ("smarter card scanner — number ranking + visual verification") describes:

Step 3: Number ranking (NEW) — Sort results so cards matching the recognized number appear first.

That works only when the target survives the Step 2 cap. Its spec table also says:

Scenario Visual verification runs?
Unique number match (1 card has that number) No (already confident)

In practice number_match_count was almost always 0 for high-numbered cards, so number_match_clear never became true and every scan paid a second Gemini call — with the second call being shown five wrong candidates and correctly answering 0. The PR notes "minimal extra cost" for step 4 on the assumption it's the exception; currently it's the default.

Fix

Float printings matching the recognized number to the front of the result list before the cap. When no number is recognized, or nothing matches, the original order is returned unchanged — so the worst case is identical to current behaviour. No extra API calls.

Verification

Candidate list before/after, against live TCGdex data:

Card # Results In top 8 before After
Snorlax me03-063 63 64
Gengar me03-050 50 58
Rare Candy sv1-191 191 23
Exeggcute base2-52 52 39
Talonflame me03-014 14 18
Cacnea sv1-005 5 18

Live scans through /api/cards/recognize on the patched build — all five English cards returned the exact correct printing at position 1:

Snorlax POR 063     19.8s   1. me03-063  Snorlax #063
Gengar POR 050      15.8s   1. me03-050  Gengar #050
Exeggcute Jungle 52  5.0s   1. base2-52  Exeggcute #52
Talonflame POR 014   7.0s   1. me03-014  Talonflame #014
Alcremie 071         7.8s   1. swsh9-071 Alcremie #071

New log line, and verification correctly skipped where the match is unique:

Number pre-filter: 1 of 58 results match #50     ← Gengar, unique → 2nd Gemini call skipped
Number pre-filter: 2 of 64 results match #63     ← Snorlax, ambiguous → verification picked candidate 1 (correct)
Number pre-filter: 1 of 39 results match #52     ← Exeggcute, unique → skipped

Scope / known limitations

This is a candidate-selection fix, not a matching-quality fix:

  • It does nothing when no number is recognized. A Japanese Jungle card I tested prints No. 039 (the National Pokédex number — Jungle-era JP cards carry no collector number), which re.match(r"(\d+)", ...) cannot parse since it's anchored at string start. That card still doesn't match.
  • I deliberately did not "fix" that parsing. Reading No. 039 as a card number would float SV2a-039 — an unrelated Scarlet & Violet card — to position 1, turning a no-match into a confidently wrong match. That needs separate discussion.
  • set_hint is extracted by the prompt and never read by any code path. Using it would be more decisive than number matching alone (it would have resolved the me03-063 vs A2a-063 tie with no second Gemini call), but that's a design change and out of scope here.

The scanner caps each TCGdex name search at the first 8 results
(recognize.py:249). TCGdex returns those results sorted ascending by
localId, so the cap keeps only the lowest-numbered printings of a
Pokemon and discards everything else.

Number ranking (Git-Romer#96, step 3) then sorts a list the target card has
already been removed from, so it can only ever succeed for cards that
happen to be numbered low enough to survive the slice.

Float printings whose number matches the recognized number to the front
of the result list before the cap is applied. When no number is
recognized, or nothing matches, the original order is returned unchanged.

Verified against live TCGdex data and live scans:

  card                  #    results  in top 8 before  after
  Snorlax (me03-063)   63         64            no      yes
  Gengar  (me03-050)   50         58            no      yes
  Rare Candy (sv1-191) 191        23            no      yes
  Exeggcute (base2-52) 52         39            no      yes

Also restores the "unique number match skips visual verification" row of
the Git-Romer#96 spec table: previously number_match_count was almost always 0 for
high-numbered cards, so number_match_clear never became true and every
scan paid a second Gemini call.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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