* docs(convoy): seed scanner identify upgrade epic and sub-convoys Baseline scan_attempts telemetry and three-phase plan for faster, more accurate card identification without touching scanner chrome. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(scanner): tighten Layer-1 identify hot path (Phase 1) Cut verify hold-still gates, OCR collector numbers on Layer 1, request structured Gemini JSON, and skip automatic L2 refine when L1 opens the printing picker. Includes convoy UX/architecture briefs and unit tests. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
5.6 KiB
| name | classification | success_metric | skip | status | created | depends_on | umbrella | model_policy | |||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| scan-visual-catalog-search | feature | After catalog embeddings exist and the new kNN path is on, ≥50% of legitimate card scans (scan_attempts excluding not_a_card) auto-match a printing with no picker — the original add-real-ocr-layer 70% target, measured honestly. Gemini L2 share of attempts falls vs the Phase 1 post-ship baseline. |
|
open | 2026-08-14 |
|
scanner-identify-upgrade |
|
Convoy: scan-visual-catalog-search
Phase 3 of scanner-identify-upgrade. Identify cards by what they
look like, not by reading the name. This is how Manabox / Delver-style
scanners get printing-accurate matches on foil and alt-art.
Blocked on improve-scan-card-detection: embeddings of unwarped
phone crops will not match catalog image_urls reliably.
Why
Name-only pg_trgm cannot distinguish printings. Tesseract fails on
foil and stylized type even when it emits 8–20 characters (76.7% L1
escalate with non-empty text). Gemini can read a name but still
returns a picker when set/number are missing, and costs 5/min.
The catalog already stores cards.image_url for every imported
printing. Precompute an embedding per row; at scan time embed the
warped crop and take top-k cosine. OCR / Gemini become hints and
unknown-card fallback.
Scope
In scope
- Migration:
pgvector(confirm Neon availability) +cards.embedding(or a side tablecard_embeddings) + ANN index. Updatedocs/SCHEMA_MAP.md. - Offline embed job: new dated script or
npm runtask that readsimage_url, writes vectors. Idempotent. Rate-limit the embedding provider. Do not edit historicalscripts/add-*.js. - Architect picks the embedder (one):
- Gateway embedding model (same
AI_GATEWAY_API_KEY, server-only). - In-browser MobileCLIP-S2 / SigLIP ONNX for the query crop, with catalog vectors baked or fetched — only if weight license + size are acceptable.
- Gateway embedding model (same
- Identify path: new Layer-0 (or replace L1) — kNN then
auto-match / disambiguate / escalate to existing L2. Log
scan_attempts.layer = 0(or Architect-ratified value). - Similarity thresholds analogous to 0.85 / 0.60, tuned on a held-out set of scan captures if any exist in Blob.
- Auth + rate-limit on any new route. No client-side API keys
(
forbidden-client-side-llm-keysmust stay green).
Out of scope
- Replacing Gemini entirely on day one — keep L2 for catalog misses and low similarity.
- Training a custom card CNN.
- Python GPU service.
- Changing scanner chrome / cart.
- Auto-approving the 27 pending
card_submissions.
Roles invoked
role-architect— embedder, schema, layer numbering, thresholds, brief split (migration / backfill / route). Security-sensitive: escalate to Sonnet if schema + new route land together.role-implementer.- Audit:
role-reviewer+role-security-auditor(required).
Todos
- Architect: confirm
pgvectoron prod Neon tier - Brief 1 — migration + SCHEMA_MAP
- Brief 2 — catalog backfill job (idempotent)
- Brief 3 — identify kNN route + client escalate order
- Threshold bake-off on real crops
- Re-measure auto-match % excluding
not_a_card
Likely file ownership
| Area | Files |
|---|---|
| Schema | migrations/*_card-embeddings.js, docs/SCHEMA_MAP.md |
| Backfill | new scripts/ job (dated) or lib/card-embed-backfill.js |
| Query | new lib/card-visual-match.js, pages/api/scan/identify-by-image.js or fold into existing identify |
| Client | lib/scanner-card-identify.js (tryLayer1TextIdentify sibling) |
Do not rewrite lib/scanner-card-detection.js here.
Multitask dispatch
Brief 1 first. Brief 2 after 1. Brief 3 after 1 (can overlap 2 if the route degrades to escalate-when-empty-index).
/multitask role-reviewer + role-security-auditor
Group id: audit-scan-visual-catalog-search-<pr>.
CI impact
| Workflow / job | Behavior |
|---|---|
schema-map-fresh |
Fires — migration + SCHEMA_MAP |
ci.yml migrate |
Must apply pgvector on CT 102 CI Postgres — Architect must verify the extension is available there or gate the migration |
forbidden-client-side-llm-keys |
Blocking |
Operator action
- Confirm Neon
pgvector(or Neon’s equivalent) on the prod project. - Budget: one embedding per catalog image, plus one per live scan if the query embed is server-side. Architect publishes a cost note before Brief 2 runs against prod images.
- No new browser secrets.
Conductor notes
This is the accuracy leap. Do not start it to "try CLIP" before Phase 2
crops are rectified — that wastes the backfill. If Architect finds
pgvector unavailable on CI Postgres, stop and write a fallback
(external index vs skip-CI-extension plan) rather than shipping an
untestable migration.