Skip to content

Scan Evaluation Protocol ​

This page defines how we evaluate scanner speed and accuracy across the mobile app, backend endpoints, and future sealed/slab flows. The goal is not "the model felt good once"; the goal is a repeatable protocol that tells us which path is fast, which path is correct, and which UX fallback made the user successful.

Scope ​

Current mobile fan-out paths:

ModeInputEndpointPurpose
labRaw camera JPEG/scan-labMain pgvector/DINOv2 path over the backend catalog
compatRaw camera JPEG/scan-compatIn-stack tcg-vision FAISS comparison path
tormkndRaw camera JPEGTormknd /scanUpstream reference path
combinedRaw camera JPEG/scanHistorical multi-signal path: pHash + OCR + embedding
frameClient-warped crop/scan-frameExperimental Path #5: phone-side OpenCV crop before backend embedding

Future modes must fit this same protocol before they become product candidates: slabs, sealed products, graded labels, and any OCR-first hybrid path.

Definitions ​

  • Top-1 accuracy: the first displayed match is exactly the expected item.
  • Top-3 recoverability: the expected item appears in the endpoint's alternatives or debug topK when available.
  • User-success accuracy: the user can finish the flow correctly through top match, alternatives, or correction UI. This is the product target; it can approach 100% even when raw top-1 cannot.
  • Client latency: wall-clock time observed by the phone around upload/prep/network.
  • Server latency: backend-reported ms inside the response.
  • First useful result: first endpoint returning ok: true with a non-null match.
  • Blocking path: an endpoint that delays the scanner state after another endpoint already produced a useful result.

Metrics Per Capture ​

Every captured photo should record:

MetricSourceWhy it matters
Capture timestampScan Debug entryGroups the five endpoint rows
Active TCG filterScan Debug entryExplains forced ?tcg= behavior
Endpoint modeScan Debug entryMakes A/B comparison possible
Client elapsed timeclientTotalMs or clientMsWhat the user actually waited for
Server elapsed timeresponse msSeparates backend cost from phone/network cost
Frame prep timingsclientPrepMs, clientDetectMs, clientWarpMsIsolates Path #5 phone-side cost
Result statematch, no-match, failure reasonSeparates accuracy from availability
Confidence/similarityresponse fieldsHelps tune thresholds and ranking
Archived case pathbackend Scan Debug case archiveLets reviewers inspect the exact input, crop, top-K, and backend JSON later
User verdictfuture correction UIRequired for real accuracy learning

Dataset Tiers ​

Start small, but keep the tiers explicit.

TierSizeContentsUse
Smoke5-10 imagesKnown easy cards, one real phone capture eachValidate a build before asking for more phone tests
Lab50 imagesEasy cards, visually similar cards, glare, tilt, poor backgroundCompare endpoint quality and latency
Regression200+ imagesEvery historical wrong match and timeoutPrevent known failures from coming back
Product500+ imagesCards, slabs, sealed, multiple languages, multiple devicesRelease gate for scanner UX

Each dataset row needs:

  • image_id
  • expected item id
  • item type: card, slab, sealed
  • TCG
  • language
  • set / product family
  • variant notes: holo, reverse, promo, graded company, grade, sealed product type
  • capture context: device, lighting, background, distance, tilt
  • expected acceptable alternatives, if any

Phone Test Runbook ​

  1. Pull latest poc-tcg-collector and backend repos.
  2. Confirm exposed endpoints respond with a known image before opening the app.
  3. Build/install the Android app on the development machine.
  4. Clear Scan Debug history.
  5. Scan each physical item three times:
    • normal centered capture
    • slight tilt
    • lower-light or glare capture
  6. For every bad result, keep the Scan Debug group expanded and confirm the case was archived.
  7. Save a screenshot only when the UI layout itself is the bug; the backend case archive is the source of truth for recognition review.
  8. Record whether the user could still finish correctly through alternatives or correction.

Scan Debug Case Archive ​

The app can upload Scan Debug cases to the backend lab archive when the local upload config is enabled. The archive stores the input image, the backend result JSON, top-K candidates, latency fields, and the private crop artifact when debug preprocessing returned one.

Treat this archive like a machine-learning intake queue, not a training set. A case becomes useful only after a human labels the expected item, marks whether the displayed match was correct, and classifies the failure mode. Labelled cases can then be copied into the appropriate benchmark folder, such as sealed-eval, when rights are clear.

After a phone test session, run the backend review command:

bash
cd tcg-collector-backend/src/api
npm run scan-debug:review -- \
  --markdown /tmp/scan-debug-review.md \
  --json /tmp/scan-debug-review.json

The command checks every archived case folder, verifies that input.jpg and metadata.json exist, detects optional crop.jpg, counts ambiguous responses and missing labels, and writes a Markdown report for human review.

The internal Cardex backend app also has a protected scan debug section. It reads the same archive through GET /scan-debug-cases, then fetches input.jpg and crop.jpg through bearer-auth artifact URLs. Use this screen for fast visual review after an associate test batch: compare the uploaded input, the crop, the scanner match, the reason, and the top-K IDs before adding the human verdict.

Backend Smoke Command ​

Use this only as a backend availability smoke test. It does not replace phone testing because it does not exercise camera capture, Android networking, or client-side Frame prep.

bash
img=/home/kael/skn-tcg/scan-debug.jpeg
curl -sS -m 12 -w "lab http=%{http_code} total=%{time_total}s\n" \
  -F "photo=@$img;type=image/jpeg" http://127.0.0.1:8080/scan-lab
curl -sS -m 12 -w "frame http=%{http_code} total=%{time_total}s\n" \
  -F "photo=@$img;type=image/jpeg" http://127.0.0.1:8080/scan-frame

For Tormknd, the multipart field is file, not photo.

Decision Rules ​

  • Keep all five endpoints while we are comparing scanner behavior.
  • Do not promote a path because it wins one screenshot; promote it when it wins the dataset tier.
  • If an endpoint is slow but uniquely correct, keep it as a secondary/correction candidate.
  • If an endpoint is fast but often wrong, keep it visible in lab mode only until ranking is fixed.
  • If frame times out often and the four raw-photo endpoints already identify correctly, make Frame opt-in or keep it debug-only.
  • Measure user-success separately from raw top-1. The product can be excellent if the UI makes correction fast and learns from it.

Current Baseline Note ​

On 2026-05-13, a VPS smoke against scan-debug.jpeg returned roughly:

Endpointcurl total
/scan-frame0.62 s
/scan-lab0.83 s
/scan combined0.84 s
Tormknd /scan1.26 s
/scan-compat1.42 s

This suggests that the visible Path #5 slowdown should first be investigated in the Android client-side OpenCV prep path, not in /scan-frame itself. Phone captures remain the source of truth.