Appearance
1. Backend /scan-frame — endpoint for client-side-warped crops
Published on 2026-05-12
First page using the new YYYY-MM-DD-NNN-slug.md + dated/numbered frontmatter convention. Older pages keep their unprefixed names (separate backfill task).
Context
The poc-tcg-collector Path #5 « Frame » plan (see Path #5 — alternatives considered) splits card detection (client-side, OpenCV on Android) from embedding (server-side, DINOv2 + pgvector). When the client has successfully isolated the card and warped it into a clean crop via a perspective transform, there is no need for the server to redo the bounds detection — it can embed the crop as-is.
This is what POST /scan-frame does, shipped in tcg-collector-backend#148 (merged 2026-05-12).
Key difference with /scan-lab
/scan-lab accepts an opt-in ?crop=1 flag (off by default) that triggers a server-side bounds detection via detectCardBounds before embedding. That detection is useful when the client uploads a raw photo, but it is counter-productive when the client uploads an already-perfect crop: the heuristic eats into the card instead of leaving it intact, and the resulting vector no longer matches what was indexed.
/scan-frame never runs a server-side crop. The contract is explicit: "the client has already warped, just embed it".
/scan-lab | /scan-frame | |
|---|---|---|
| Server-side crop | opt-in (?crop=1) | never |
| Embed pipeline | DINOv2 → pgvector ANN | DINOv2 → pgvector ANN |
| Mode-TCG re-rank | yes | yes |
?tcg= filter | yes | yes |
| JSON response | same shape | same shape (1:1) |
| Auth | public (POST allowlisted) | public (POST allowlisted) |
Why the same response shape
The mobile app fan-outs in parallel over 4 cells (Lab / Compat / Tormknd / Combined) since 2026-05-10. poc-tcg-collector PR 4b (upcoming) adds a 5th "Frame" cell that consumes the exact same JSON shape. Keeping a 1:1 contract between /scan-lab and /scan-frame makes A/B-testing trivial: same catalog, same indexing, same DB — the only variable is "client crop" vs "raw photo".
Implementation notes
- New file
src/api/src/scan/routeFrame.ts(~150 lines). The full pipeline reusesqueryLab,projectCardDetails,toPgVector,LabMatchRowfromrouteLab.ts— now exported and shared. The pgvector SQL is not duplicated, so any future divergence will be contained to the handler envelope. - Wired in
src/api/src/index.tsnext toscanLabRoutes, gated by the sameembeddingsConfigcheck (the endpoint shows up only when the embedding sidecar is online). /scan-frameadded toPUBLIC_POST_PATHSinsrc/api/src/auth.ts— explicit security review point, same as/scanand/scan-lab.- 186/186 existing tests pass,
tsc --noEmitclean.
Known limitations
- No dedicated e2e test in this PR — the available local fixtures are unwarped photos (designed for
/scan-lab), so feeding them to/scan-framewould yield artificially low similarities. A real e2e test needs already-warped crops, which will fall out of the mobile OpenCV pipeline oncepoc-tcg-collectorPR 4b lands. - No CHANGELOG.md update in the PR — per backend repo convention (
CLAUDE.md), feature branches do not touch CHANGELOG; the entry lands on a separatedocs/…branch.
Further reading
- Path #5 « Frame » — alternatives considered — the original architectural decision
- Scan paths comparison — Lab / Compat / Tormknd
tcg-collector-backend#148— PR source