# Architecture du module IA ## Vue d'ensemble ``` Photo du terminal │ ▼ ┌─────────────────────┐ │ preprocessing.py │ détection de l'écran LCD (contours) puis │ │ découpage en 3 lignes (prix / volume / prix_litre) └─────────┬────────────┘ ▼ ┌─────────────────────┐ │ recognizer.py │ CRNN fine-tuné : lit chaque ligne, renvoie │ (models/*.pt) │ {field, text, confidence} par valeur └─────────┬────────────┘ ▼ ┌─────────────────────┐ │ postprocess.py │ normalise les nombres (virgule/point), │ │ calcule la valeur manquante, vérifie │ │ montant = litres × prix └─────────┬────────────┘ ▼ ┌─────────────────────┐ ┌─────────────────┐ │ rules.py │◄──────│ quality.py │ flou / luminosité │ décide success/blocage │ └─────────────────┘ └─────────┬────────────┘ ▼ réponse API (main.py) ``` ## Pourquoi ce découpage en modules séparés Chaque étage a une responsabilité et une durée de vie différentes : - **preprocessing** et **quality** : de la vision par ordinateur classique (OpenCV), aucune dépendance au modèle de reconnaissance. Réutilisable même si on change de modèle IA demain. - **recognizer** : la seule brique qui dépend du modèle entraîné. Isolée pour pouvoir la remplacer (nouvelle version du CRNN, ou un autre modèle) sans toucher au reste. - **postprocess** : logique métier pure (aucune I/O, aucun modèle) — donc entièrement testable sans charger le CRNN (voir `tests/test_postprocess.py`, instantané). - **rules** : la décision finale (bloquer/valider) séparée du calcul, pour pouvoir ajuster les seuils (`config.py`/`config.yaml`) sans toucher à la logique de calcul. ## Différence avec le prototype précédent (OCR générique) L'ancienne version du projet (racine du dépôt, `src/`, `api/`) utilisait PaddleOCR/EasyOCR : des modèles pré-entraînés génériques, avec toute une mécanique de variantes d'image et de repli entre moteurs pour compenser leur manque de spécialisation. Ce module utilise à la place un modèle **entraîné sur nos propres données** (voir `train/`), ce qui simplifie le post-traitement : le CRNN sait déjà quelle ligne correspond à quel champ (par position), il n'y a donc plus besoin d'heuristique de proximité de libellé ni de deviner "quel nombre est le montant" par magnitude — la principale source d'erreur du prototype précédent. ## Fichiers clés | Fichier | Rôle | |---|---| | `app/preprocessing.py` | détection écran + découpage lignes | | `app/recognizer.py` | chargement CRNN + inférence | | `app/postprocess.py` | normalisation, calculs, cohérence | | `app/rules.py` | porte de décision succès/blocage | | `app/config.py` | seuils configurables | | `app/main.py` | API FastAPI | | `train/finetune_doctr.py` | entraînement du CRNN | | `docs/LIMITATIONS.md` | limites connues du modèle actuel |