Appearance
1. Backend /scan-frame — endpoint pour crops pré-warpés
Publié le 2026-05-12
Première page utilisant la nouvelle convention YYYY-MM-DD-NNN-slug.md + frontmatter daté/numéroté. Les anciennes pages gardent leur nom non-préfixé (rétro-fit séparé).
Contexte
Le chemin Path #5 « Frame » de poc-tcg-collector (voir Path #5 — alternatives considérées) sépare la détection de carte (côté client, OpenCV sur Android) de l'embedding (côté backend, DINOv2 + pgvector). Quand le client a réussi à isoler la carte et à la dérouler en un crop net via une transformation perspective, on n'a plus besoin que le serveur refasse une détection de bordure — il suffit de l'embedder tel quel.
C'est exactement ce que fait POST /scan-frame, livré dans tcg-collector-backend#148 (mergé le 2026-05-12).
Différence clé avec /scan-lab
/scan-lab accepte un flag ?crop=1 (désactivé par défaut) qui déclenche une détection de bordure serveur via detectCardBounds avant d'embedder. Cette détection est utile quand le client envoie une photo brute, mais elle est contre-productive quand le client envoie un crop déjà parfait : la heuristique mord dans la carte au lieu de la laisser intacte, et le vecteur produit ne ressemble plus à ce qu'on a au moment de l'indexation.
/scan-frame ne fait jamais de détection serveur. La contrat est explicite : « le client a déjà warpé, embed-le tel quel ».
/scan-lab | /scan-frame | |
|---|---|---|
| Crop serveur | optionnel (?crop=1) | jamais |
| Pipeline embed | DINOv2 → pgvector ANN | DINOv2 → pgvector ANN |
| Re-rank TCG dominant | oui | oui |
?tcg= filter | oui | oui |
| Réponse JSON | même format | même format (1:1) |
| Auth | publique (POST allowlisté) | publique (POST allowlisté) |
Pourquoi le même format de réponse
Le mobile fait un fan-out parallèle sur 4 cellules (Lab / Compat / Tormknd / Combined) depuis 2026-05-10. PR 4b sur poc-tcg-collector (à venir) ajoute une 5ème cellule « Frame » qui consomme exactement la même forme JSON. Garder un contrat 1:1 entre /scan-lab et /scan-frame rend l'A/B test trivial : on compare la similarity du même catalogue, même indexation, même DB — la seule variable est « crop client » vs « photo brute ».
Détails d'implémentation
- Nouveau fichier
src/api/src/scan/routeFrame.ts(~150 lignes). Pipeline complet réutilisé depuisrouteLab.ts:queryLab,projectCardDetails,toPgVector,LabMatchRowsont maintenant exportés et partagés. Le SQL pgvector n'est donc pas dupliqué — la divergence (si elle vient un jour) sera contenue dans le handler envelope. - Wiré dans
src/api/src/index.tsà côté descanLabRoutes, derrière la même gateembeddingsConfig(l'endpoint n'apparaît que si le sidecar d'embedding est branché). /scan-frameajouté àPUBLIC_POST_PATHSdanssrc/api/src/auth.ts— explicit security review point, comme/scanet/scan-lab.- 186/186 tests existants verts,
tsc --noEmitclean.
Limites connues
- Pas de test e2e dédié dans ce PR — les fixtures locales sont des photos non-warpées (conçues pour
/scan-lab), donc les passer à/scan-framedonnerait des sims artificiellement basses. Un vrai test e2e nécessite des fixtures de crops déjà warpés, qui tomberont du pipeline OpenCV mobile une fois PR 4b mergée. - Pas de CHANGELOG.md dans le PR — par convention du repo backend (
CLAUDE.md), les branches feat ne modifient pas CHANGELOG ; l'entrée arrive sur une branchedocs/…séparée.
Pour aller plus loin
- Path #5 « Frame » — alternatives considérées — la décision architecturale d'origine
- Comparaison des paths de scan — Lab / Compat / Tormknd
tcg-collector-backend#148— code source du PR