Appearance
Architecture du scan de cartes
Vue d'ensemble du pipeline de reconnaissance de cartes côté backend, qui héberge quoi, et comment ajouter un nouveau TCG (Trading Card Game) sans dépendre de personne.
Snapshot daté du 2026-05-07.
Pour le raccordement R1/D2 fusionné le 3 novembre 2026, voir Déploiement, activation et retour arrière. Le présent document décrit l'architecture historique de mai, pas l'état de déploiement actuel.
TL;DR
- L'app React Native scanne via
POST /scan-compatqui tape surtcg-vision, un sidecar Docker hébergé sur notre VPS. tcg-visionest buildé à partir du repo open-sourceSKN/tgc-app(Tormknd), mais tourne 100% chez nous. Aucun appel sortant runtime vers Tormknd.- Le seul élément qu'on n'a pas généré nous-mêmes est
faiss_index.bin(~30 370 embeddings DINOv2 pré-calculés). Il est baké dans notre image au build. - On a aussi un second pipeline parallèle entièrement à nous (
pgvector+embedCatalog.ts+/scancôté API Fastify) qui dort actuellement, tablecard_embeddingsà 0 ligne. - Magic the Gathering (Scryfall, ~85 k printings, ingéré le 2026-05-07) tourne sur le pipeline pgvector parce que
tcg-visionn'a pas d'index FAISS pour MTG. Voirdocs/INGEST.md§Magic the Gathering. - Pour ajouter encore un TCG, deux options : demander à Tormknd de rebuild son index, OU activer notre pgvector et embedder nous-mêmes (recette MTG = précédent reproductible).
Schéma global
┌──── Phone (RN app) ─────────────────────────────────────┐
│ ScannerScreen → backendScan.ts │
│ POST {photo} → http://<vps>:8000/scan-compat │
└────────────────────────┬────────────────────────────────┘
│
▼
┌──── Notre VPS — docker compose ─────────────────────────┐
│ │
│ ┌─ tcg-vision (port 8000) ──────────────────┐ │
│ │ Code : SKN/tgc-app pinned commit │ │
│ │ ML : DINOv2 small (HuggingFace) │ ← actif │
│ │ Index : faiss_index.bin (~30 370 cartes) │ │
│ │ Routes: /scan-compat, /scan │ │
│ └─────────────┬─────────────────────────────┘ │
│ │ enrichit avec /cards/:id (PR #97) │
│ ▼ │
│ ┌─ api Fastify (port 8080) ─────────────────┐ │
│ │ /cards/:id, /sets, /cards/dump… │ │
│ │ /scan (pHash + branche pgvector dormante)│ ← prêt │
│ └─────────────┬─────────────────────────────┘ │
│ ▼ │
│ ┌─ postgres (pgvector/pg16) ────────────────┐ │
│ │ cards (23 k PKM + 3 k OP + 85 k MTG) │ │
│ │ card_images (rows + chemin disque) │ │
│ │ card_embeddings (table HNSW, 0 lignes) │ │
│ └───────────────────────────────────────────┘ │
│ │
│ ┌─ embeddings (DINOv2 sidecar, interne)──┐ │
│ │ Sert à embedCatalog.ts pour peupler │ │
│ │ card_embeddings (jamais lancé encore) │ │
│ └────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘Qui héberge quoi
| Composant | Code source | Données ML | Hosting runtime |
|---|---|---|---|
tcg-vision container | Cloné de SKN/tgc-app au docker build | faiss_index.bin baké dans l'image (généré offline par Tormknd) | Notre VPS |
| Poids DINOv2 | facebook/dinov2-small (HuggingFace) | Téléchargés au premier boot, cachés dans le volume tcg-vision-torch-cache | Notre VPS |
api Fastify, postgres, nginx, embeddings, ingest | snag-tcg/tcg-collector-backend (notre repo) | cards / card_images / card_embeddings ingérés par nous | Notre VPS |
Conclusion runtime : zéro appel sortant pendant un scan. Tout le traitement se fait sur notre VPS.
Conclusion build-time : tcg-vision.Dockerfile clone le repo de Tormknd à un commit pinned (TCG_APP_COMMIT). Si son repo disparaît demain, l'image existante continue de tourner ; on perd juste la possibilité de la rebuild.
La vraie dépendance — faiss_index.bin
C'est le seul artefact qu'on ne sait pas régénérer aujourd'hui sans un travail conséquent.
- Contenu : ~30 370 vecteurs DINOv2 (Pokémon ~26 000, One Piece ~4 000, Lorcana quelques centaines)
- Comment c'est fait : Tormknd a embeddé ses propres dossiers d'images (
pokemon_cards_fr_high_res/,onepiece_cards_en/, etc.) avec DINOv2-small et a sauvegardé l'index FAISS résultant. - Couverture des sets récents : limitée à ce que Tormknd avait quand il a buildé. Les sets sortis après la date de génération ne sont pas dans l'index → scan retourne "no-match".
- Si on veut Magic : il faudrait que Tormknd ajoute Magic dans son repo et rebuild, OU on construit nous-mêmes.
Le second pipeline — entièrement à nous, dormant
L'archi pgvector + DINOv2 existe déjà côté backend mais n'est pas encore activée :
| Élément | État | Rôle |
|---|---|---|
Migration 0004_card_embeddings.sql | ✅ Appliquée | Table card_embeddings (card_id, embedding vector(384), model) + index HNSW cosinus |
Service embeddings: (compose) | ✅ Up & healthy | DINOv2 sidecar Python (~250 ms/inférence CPU) |
Script src/ingest/embedCatalog.ts | ✅ Existe, jamais lancé | Walk toutes les cartes avec image → embed → upsert dans card_embeddings |
Route API /scan branche embedding (src/api/src/scan/embeddings.ts) | ✅ Coded, dormant | Query embedding <=> $1 (pgvector ANN cosinus) |
Table card_embeddings | 🟠 0 lignes | Pas peuplée |
Pour activer ce path :
bash
cd tcg-collector-backend
docker compose --env-file .env -f docker/compose.yml run --rm \
--entrypoint "node dist/embedCatalog.js" ingest
# ~80 minutes pour ~25 000 cartes (Pokémon + One Piece existants)À la fin, card_embeddings contient toutes les cartes ingérées, et l'API peut servir /scan avec une qualité comparable à tcg-vision — sans aucune dépendance externe.
Comment ajouter un TCG (recette générale)
L'architecture multi-TCG est en place depuis la migration 0005_tcg_classifier.sql (colonnes sets.tcg / cards.tcg). Pour un nouveau TCG :
- Choisir une source de données — voir
poc-tcg-collector/doc/COMPETITOR_DATA_SOURCES.md(Scryfall pour MTG, YGOPRODeck pour Yu-Gi-Oh!, OPTCG API pour One Piece, Lorcast pour Lorcana, etc.) - Écrire le client + l'orchestrateur — calquer sur
src/ingest/optcgapi.ts+src/ingest/onepiece.ts: ~150 lignes - Ajouter au dispatch dans
src/ingest/src/index.ts— nouvelle brancheif (tcg === 'magic')qui appelle l'orchestrateur - Lancer l'ingest —
INGEST_TCG=<name> docker compose ... run --rm ingest - Gérer l'index ML :
- Option A — demander à Tormknd : il ajoute le TCG à son
tgc-app, rebuild son index, on bumpTCG_APP_COMMITet rebuild notre image. Pas de code de notre côté. - Option B — pgvector : lancer
embedCatalog.tsaprès l'ingest pour peuplercard_embeddings, et utiliser/scan(côté API) au lieu de/scan-compat(côté tcg-vision).
- Option A — demander à Tormknd : il ajoute le TCG à son
- Tester côté app RN — un scan d'une carte du nouveau TCG doit retourner nom + image + métadonnées via
/scan-compat(ou/scanselon le path retenu). Voirtcg-collector-backend/docs/INGEST.md§One Piece pour le précédent.
Endpoints aujourd'hui
| Endpoint | Container | Path scan | Source des matches | Statut |
|---|---|---|---|---|
:8000/scan-compat | tcg-vision | DINOv2 + FAISS (Tormknd) | faiss_index.bin | Actif, pointé par l'app RN |
:8000/scan | tcg-vision | DINOv2 + FAISS (Tormknd) | idem | Actif, pas utilisé par l'app |
:8080/scan | api Fastify | pHash (signal faible) + branche embedding pgvector | card_images (pHash) + card_embeddings (vide) | Coded, qualité dégradée tant que pgvector vide |
Stratégie de transition recommandée quand on voudra retirer la dépendance Tormknd :
- Lancer
embedCatalog.tspour remplircard_embeddings(~80 min) - A/B comparer la qualité
/scan(api) vs/scan-compat(tcg-vision) sur quelques scans - Si égalité ou mieux → bump
BACKEND_SCAN_URLcôtépoc-tcg-collectorvers:8080/scan - À ce moment-là le container
tcg-visionpeut être stoppé (et son volume torch-cache libéré, ~85 MB)
Voir aussi
tcg-collector-backend/docs/scanner/TCG_VISION.md— doc technique du sidecartcg-collector-backend/docs/INGEST.md— runbook d'ingest backend (pipeline, sources par TCG, multi-langues, roadmap Option B)tcg-collector-backend/docs/scanner/SCANNER_BACKEND_INGEST.md— pipeline complet ingest → scan côté apppoc-tcg-collector/doc/COMPETITOR_DATA_SOURCES.md— inventaire des sources data par TCG