Skip to content

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-compat qui tape sur tcg-vision, un sidecar Docker hébergé sur notre VPS.
  • tcg-vision est buildé à partir du repo open-source SKN/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 + /scan côté API Fastify) qui dort actuellement, table card_embeddings à 0 ligne.
  • Magic the Gathering (Scryfall, ~85 k printings, ingéré le 2026-05-07) tourne sur le pipeline pgvector parce que tcg-vision n'a pas d'index FAISS pour MTG. Voir docs/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 ​

ComposantCode sourceDonnées MLHosting runtime
tcg-vision containerCloné de SKN/tgc-app au docker buildfaiss_index.bin baké dans l'image (généré offline par Tormknd)Notre VPS
Poids DINOv2facebook/dinov2-small (HuggingFace)Téléchargés au premier boot, cachés dans le volume tcg-vision-torch-cacheNotre VPS
api Fastify, postgres, nginx, embeddings, ingestsnag-tcg/tcg-collector-backend (notre repo)cards / card_images / card_embeddings ingérés par nousNotre 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ÉtatRôle
Migration 0004_card_embeddings.sql✅ AppliquéeTable card_embeddings (card_id, embedding vector(384), model) + index HNSW cosinus
Service embeddings: (compose)✅ Up & healthyDINOv2 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, dormantQuery embedding <=> $1 (pgvector ANN cosinus)
Table card_embeddings🟠 0 lignesPas 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 :

  1. 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.)
  2. Écrire le client + l'orchestrateur — calquer sur src/ingest/optcgapi.ts + src/ingest/onepiece.ts : ~150 lignes
  3. Ajouter au dispatch dans src/ingest/src/index.ts — nouvelle branche if (tcg === 'magic') qui appelle l'orchestrateur
  4. Lancer l'ingest — INGEST_TCG=<name> docker compose ... run --rm ingest
  5. Gérer l'index ML :
    • Option A — demander à Tormknd : il ajoute le TCG à son tgc-app, rebuild son index, on bump TCG_APP_COMMIT et rebuild notre image. Pas de code de notre côté.
    • Option B — pgvector : lancer embedCatalog.ts après l'ingest pour peupler card_embeddings, et utiliser /scan (côté API) au lieu de /scan-compat (côté tcg-vision).
  6. Tester côté app RN — un scan d'une carte du nouveau TCG doit retourner nom + image + métadonnées via /scan-compat (ou /scan selon le path retenu). Voir tcg-collector-backend/docs/INGEST.md §One Piece pour le précédent.

Endpoints aujourd'hui ​

EndpointContainerPath scanSource des matchesStatut
:8000/scan-compattcg-visionDINOv2 + FAISS (Tormknd)faiss_index.binActif, pointé par l'app RN
:8000/scantcg-visionDINOv2 + FAISS (Tormknd)idemActif, pas utilisé par l'app
:8080/scanapi FastifypHash (signal faible) + branche embedding pgvectorcard_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 :

  1. Lancer embedCatalog.ts pour remplir card_embeddings (~80 min)
  2. A/B comparer la qualité /scan (api) vs /scan-compat (tcg-vision) sur quelques scans
  3. Si égalité ou mieux → bump BACKEND_SCAN_URL côté poc-tcg-collector vers :8080/scan
  4. À ce moment-là le container tcg-vision peut être stoppé (et son volume torch-cache libéré, ~85 MB)

Voir aussi ​

  • tcg-collector-backend/docs/scanner/TCG_VISION.md — doc technique du sidecar
  • tcg-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é app
  • poc-tcg-collector/doc/COMPETITOR_DATA_SOURCES.md — inventaire des sources data par TCG