danielxdata commited on
Commit
6cd05a0
·
1 Parent(s): f3e22ea

Ajoute la doc de soutenance, corrige Vercel->Netlify, bouton stats

Browse files

- docs/DEFENSE_QA.md : reponses courtes aux questions techniques
probables pour la soutenance
- README/API/WORKFLOW : wording corrige (frontend heberge sur
Netlify, pas Vercel)
- web/index.html : bouton de navigation vers stats.html

Files changed (7) hide show
  1. README.md +2 -2
  2. app/main.py +2 -2
  3. docs/API.md +2 -2
  4. docs/DEFENSE_QA.md +88 -0
  5. docs/WORKFLOW.md +15 -13
  6. web/config.js +1 -1
  7. web/index.html +8 -2
README.md CHANGED
@@ -37,7 +37,7 @@ yely_ai_module/
37
  │ └── crnn_fuel_pump_best.pt # modèle sérialisé (livrable)
38
  ├── train/ # scripts d'entraînement (reproductibilité)
39
  ├── tests/ # tests unitaires (postprocess, règles)
40
- ├── web/ # interface web de démo (déploiement Vercel)
41
  ├── docs/ # documentation par module + limites identifiées
42
  ├── Dockerfile # déploiement Hugging Face Spaces
43
  └── requirements.txt
@@ -115,5 +115,5 @@ limitant est la diversité du jeu de données, pas les hyperparamètres.
115
 
116
  ## Déploiement
117
 
118
- Voir `docs/WORKFLOW.md` §6 : frontend (`web/`) sur Vercel, API sur
119
  Hugging Face Spaces (ce dépôt, via le `Dockerfile` à la racine).
 
37
  │ └── crnn_fuel_pump_best.pt # modèle sérialisé (livrable)
38
  ├── train/ # scripts d'entraînement (reproductibilité)
39
  ├── tests/ # tests unitaires (postprocess, règles)
40
+ ├── web/ # interface web de démo (déploiement Netlify)
41
  ├── docs/ # documentation par module + limites identifiées
42
  ├── Dockerfile # déploiement Hugging Face Spaces
43
  └── requirements.txt
 
115
 
116
  ## Déploiement
117
 
118
+ Voir `docs/WORKFLOW.md` §6 : frontend (`web/`) sur Netlify, API sur
119
  Hugging Face Spaces (ce dépôt, via le `Dockerfile` à la racine).
app/main.py CHANGED
@@ -57,10 +57,10 @@ logger.addHandler(_file_handler)
57
 
58
  app = FastAPI(title="YELY — Module IA pompiste (CRNN)")
59
 
60
- # Le frontend (Vercel) et l'API (Hugging Face Spaces) sont sur des domaines
61
  # différents : sans CORS, le navigateur bloquerait la lecture de la réponse
62
  # même si la requête aboutit côté serveur. ALLOWED_ORIGINS est une liste
63
- # d'origines séparées par des virgules (ex. "https://yely-demo.vercel.app").
64
  _allowed_origins = os.environ.get("ALLOWED_ORIGINS", "*")
65
 
66
  app.add_middleware(
 
57
 
58
  app = FastAPI(title="YELY — Module IA pompiste (CRNN)")
59
 
60
+ # Le frontend (Netlify) et l'API (Hugging Face Spaces) sont sur des domaines
61
  # différents : sans CORS, le navigateur bloquerait la lecture de la réponse
62
  # même si la requête aboutit côté serveur. ALLOWED_ORIGINS est une liste
63
+ # d'origines séparées par des virgules (ex. "https://yely-demo.netlify.app").
64
  _allowed_origins = os.environ.get("ALLOWED_ORIGINS", "*")
65
 
66
  app.add_middleware(
docs/API.md CHANGED
@@ -42,9 +42,9 @@ chaque rechargement de code redémarre le worker et donc le modèle.
42
 
43
  `ALLOWED_ORIGINS` (variable d'environnement, origines séparées par des
44
  virgules) contrôle quels domaines peuvent appeler l'API depuis un
45
- navigateur — nécessaire car le frontend (Vercel) et l'API (Hugging Face
46
  Spaces) sont sur des domaines différents. Par défaut `"*"` (permissif,
47
- adapté à une démo) ; à restreindre au domaine Vercel réel en production.
48
 
49
  ## Journalisation
50
 
 
42
 
43
  `ALLOWED_ORIGINS` (variable d'environnement, origines séparées par des
44
  virgules) contrôle quels domaines peuvent appeler l'API depuis un
45
+ navigateur — nécessaire car le frontend (Netlify) et l'API (Hugging Face
46
  Spaces) sont sur des domaines différents. Par défaut `"*"` (permissif,
47
+ adapté à une démo) ; à restreindre au domaine Netlify réel en production.
48
 
49
  ## Journalisation
50
 
docs/DEFENSE_QA.md ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Questions techniques probables, reponses courtes (soutenance)
2
+
3
+ Aide-memoire pour repondre vite en jury. Chaque reponse tient en 2-3 phrases ; les details complets sont dans les fichiers cites.
4
+
5
+ ## Architecture generale
6
+
7
+ **Pourquoi un CRNN plutot que l'OCR generique (PaddleOCR/EasyOCR) ?**
8
+ L'OCR generique ne "sait" pas ce qu'il lit (il devine si un nombre est un prix ou un volume par sa magnitude), et n'apprend jamais de ses erreurs. Le CRNN est entraine sur nos propres photos de pompes : il apprend a lire ce type d'ecran precisement, et sa precision peut s'ameliorer avec plus de donnees. Voir `docs/ARCHITECTURE.md`.
9
+
10
+ **Le pipeline en une phrase ?**
11
+ Photo -> detection ecran (OpenCV) -> decoupage en 3 lignes (prix/volume/prix_litre) -> lecture CRNN ligne par ligne -> normalisation numerique -> calcul de coherence -> moteur de regles (bloque ou valide) -> reponse JSON.
12
+
13
+ **Pourquoi trois modules separes (preprocessing, recognizer, postprocess, rules) au lieu d'un seul script ?**
14
+ Chaque etape est testee independamment (`tests/`), et une amelioration future du modele de reconnaissance ne touche pas au moteur de regles ni au pretraitement. Voir `docs/ARCHITECTURE.md`.
15
+
16
+ ## Pretraitement (detection ecran + decoupage)
17
+
18
+ **Comment l'ecran est-il detecte dans la photo ?**
19
+ Contours (Canny) + fermeture morphologique pour relier les bords, puis on garde le plus grand rectangle dont la forme ressemble a un ecran (ratio largeur/hauteur entre 1 et 8). Heuristique, pas un modele entraine. Voir `docs/PREPROCESSING.md`.
20
+
21
+ **Comment l'ecran est-il decoupe en 3 lignes ?**
22
+ Binarisation (CLAHE + Otsu), puis projection horizontale : on compte les pixels "texte" par ligne de pixels, on repere les bandes actives. Si la projection ne trouve pas exactement 3 bandes, repli sur un decoupage proportionnel (3 tiers egaux).
23
+
24
+ **Quelle est la limite connue de cette methode ?**
25
+ 86% des images du jeu d'annotation tombent sur le repli proportionnel, pas la detection precise -> c'est la cause n°2 identifiee du plafond a 70.2% (`docs/LIMITATIONS.md`).
26
+
27
+ **Pourquoi CLAHE avant la binarisation ?**
28
+ Les ecrans LCD ont un eclairage inegal (reflets, angle de prise de vue) ; CLAHE renforce le contraste localement plutot que globalement, ce qui rend le seuillage Otsu plus fiable.
29
+
30
+ ## Modele et entrainement
31
+
32
+ **Quelle precision atteint le modele actuellement ?**
33
+ 70.2% de precision exacte par ligne en validation (epoch 32/60), contre 55.3% pour la version initiale. Voir `train/models/v2/history.json`.
34
+
35
+ **Qu'est-ce qui limitait le modele a 55% au depart ?**
36
+ Un bug de redimensionnement : a l'entrainement, les images etaient etirees sans preserver le ratio d'aspect, alors qu'a l'inference le ratio etait preserve. Le modele apprenait donc sur des chiffres deformes differemment de ce qu'il voit en production. Corrige (resize + padding identiques aux deux etapes).
37
+
38
+ **Qu'est-ce qui limite encore la precision aujourd'hui ?**
39
+ Le manque de diversite du dataset : 310 echantillons mais seulement 30 valeurs distinctes (probablement des rafales de photos des memes tickets). Le modele memorise plus qu'il ne generalise. Voir `docs/LIMITATIONS.md`.
40
+
41
+ **Y a-t-il un risque d'overfitting ?**
42
+ Le signal est visible dans les logs (perte d'entrainement ~0.10 contre perte de validation qui plafonne ~0.93-0.95). Mitige par la selection du checkpoint sur la meilleure perte de validation (pas la derniere epoch) et par l'arret avant la fin des 60 epochs prevues.
43
+
44
+ **Comment le train/val split evite-t-il la fuite de donnees ?**
45
+ `split_lines.py` et `prepare_doctr_dataset.py` nettoient desormais le dossier de sortie avant de regenerer le split a chaque execution -- un bug precedent laissait d'anciens fichiers en place, ce qui faisait apparaitre une meme image dans train ET val.
46
+
47
+ **Pourquoi ne pas viser directement 90%+ ?**
48
+ Le facteur limitant est la quantite/diversite de donnees annotees, pas les hyperparametres. Collecter plus de transactions distinctes est le levier le plus efficace, mais aussi le plus long -- priorise pour une iteration future plutot que pour cette version.
49
+
50
+ ## Moteur de regles metier
51
+
52
+ **Quelle est la regle la plus importante ?**
53
+ Le prix du litre configure cote YELY fait toujours autorite sur celui lu a l'ecran (`fuel_price` fourni par l'appelant prime). Le prix lu a l'ecran n'est utilise qu'en repli, si aucun prix n'est fourni.
54
+
55
+ **Dans quel ordre les blocages sont-ils evalues ?**
56
+ 1) qualite image non valide (floue/sombre/surexposee) -> 2) aucune donnee detectee -> 3) litres ET montant manquants, ou prix manquant -> 4) incoherence montant/litres/prix -> 5) confiance sous le seuil -> sinon succes. Le premier echec fixe le message. Voir `app/rules.py`.
57
+
58
+ **Comment le score de confiance est-il calcule ?**
59
+ `confidence_score = confiance OCR moyenne x 0.6 + score qualite image x 0.4`, plafonne a 1.0. Voir `app/rules.py::_compute_confidence_score`.
60
+
61
+ **Pourquoi verifier la luminosite avant le flou dans le controle qualite ?**
62
+ La variance du Laplacien (mesure de flou) depend du contraste, qui s'effondre deja dans une image sombre meme si elle est nette. Verifier le flou en premier classait a tort des photos sombres comme "floues". Corrige cette session.
63
+
64
+ ## Monitoring et apprentissage continu
65
+
66
+ **Comment le systeme s'ameliore-t-il avec l'usage ?**
67
+ Boucle de feedback : `POST /feedback` permet a un pompiste de corriger une lecture erronee (photo deja sauvegardee cote serveur) ; `monitoring/feedback.py` convertit ces corrections en nouvelles entrees d'annotation, relues manuellement (`annotate.py --review-pending`) avant un reentrainement. Voir `docs/MONITORING.md`.
68
+
69
+ **Pourquoi une relecture humaine avant reentrainement, pas un cycle automatique ?**
70
+ La detection d'ecran automatique n'est pas fiable a 100% -- injecter silencieusement des crops mal cadres degraderait le dataset plutot que de l'ameliorer. Un cout humain court est prefere a un risque de regression silencieuse.
71
+
72
+ **Comment suit-on les performances en production ?**
73
+ `GET /metrics` agrege `logs/api.log` a la volee (taux de succes, confiance moyenne, causes de blocage). `GET /failures` liste les dernieres transactions bloquees avec leur photo, pour investigation immediate.
74
+
75
+ ## Deploiement
76
+
77
+ **Pourquoi Hugging Face Spaces et pas Vercel/Netlify pour l'API ?**
78
+ L'API embarque PyTorch + doctr + OpenCV (bien au-dela des limites de taille des plateformes serverless) et l'inference prend 60-100s sur CPU (au-dela des timeouts serverless). HF Spaces (Docker) n'a pas ces limites. Voir `docs/WORKFLOW.md` §6.
79
+
80
+ **Ou est heberge le frontend ?**
81
+ Netlify (fichiers statiques, `web/`), separement de l'API sur Hugging Face -- d'ou le CORS configure dans `app/main.py`.
82
+
83
+ ## Limites assumees (a dire spontanement si demande)
84
+
85
+ - 70.2% de precision exacte par ligne, en dessous du seuil de 90% vise initialement.
86
+ - Dataset petit et peu diversifie en valeurs (30 valeurs distinctes / 310 echantillons).
87
+ - Decoupage en lignes imprecis sur 86% des images (repli proportionnel).
88
+ - Le CRNN reste separe du reste du pipeline (regles, qualite, API) -- une amelioration future du modele ne casse rien d'autre.
docs/WORKFLOW.md CHANGED
@@ -130,21 +130,23 @@ Contenu minimal :
130
 
131
  ## 6. Déploiement
132
 
133
- **Frontend (`web/`) → Vercel** : parfaitement adapté (statique ou
134
- Next.js), pas de souci particulier.
135
-
136
- **API IA → PAS Vercel.** Point d'attention important avant d'aller plus
137
- loin : Vercel (Serverless Functions) limite la taille du build (~250 Mo
138
- décompressés) et le temps d'exécution par requête. Cette API embarque
139
- PyTorch + doctr + OpenCV, qui dépassent déjà largement cette taille à eux
140
- seuls, et l'inférence CRNN prend actuellement 60-100+ secondes par image
141
- (bien au-delà des temps d'exécution autorisés par Vercel, même sur les
142
- plans payants). Déployer tel quel sur Vercel échouera au build ou au
 
143
  timeout, pas juste "sera lent".
144
 
145
  **Décision retenue : Hugging Face Spaces (Docker SDK)** pour l'API.
146
  Gratuit, pensé pour les démos ML, pas de limite stricte de temps
147
- d'exécution comme Vercel, `Dockerfile` déjà préparé (`yely_ai_module/Dockerfile`).
 
148
 
149
  Étapes de déploiement (à faire manuellement, action externe) :
150
  1. Créer un Space sur huggingface.co → SDK "Docker" → visibilité au choix.
@@ -163,9 +165,9 @@ d'exécution comme Vercel, `Dockerfile` déjà préparé (`yely_ai_module/Docker
163
  3. Pousser le contenu de `yely_ai_module/` (avec `models/crnn_fuel_pump_best.pt`
164
  inclus) vers le dépôt git du Space.
165
  4. Noter l'URL publique du Space (`https://<user>-<space>.hf.space`) — c'est
166
- l'URL que le frontend Vercel appellera pour `/analyze`.
167
  5. Ajouter `CORSMiddleware` dans `app/main.py` pour autoriser le domaine
168
- Vercel du frontend (sinon le navigateur bloquera les réponses).
169
  6. (Optionnel, payant) Activer le **stockage persistant** du Space et
170
  définir la variable d'environnement `YELY_DATA_DIR` sur son point de
171
  montage — sans quoi `photos/`, `logs/` et `monitoring/feedback.jsonl`
 
130
 
131
  ## 6. Déploiement
132
 
133
+ **Frontend (`web/`) → Netlify** : parfaitement adapté (fichiers statiques,
134
+ pas de build nécessaire), pas de souci particulier.
135
+
136
+ **API IA → PAS d'hébergement serverless classique (Vercel, Netlify
137
+ Functions...).** Point d'attention important avant d'aller plus loin : ce
138
+ type d'hébergement limite la taille du build (souvent ~250 Mo décompressés)
139
+ et le temps d'exécution par requête. Cette API embarque PyTorch + doctr +
140
+ OpenCV, qui dépassent déjà largement cette taille à eux seuls, et
141
+ l'inférence CRNN prend actuellement 60-100+ secondes par image (bien
142
+ au-delà des temps d'exécution autorisés, même sur les plans payants).
143
+ Déployer tel quel sur ce type de plateforme échouera au build ou au
144
  timeout, pas juste "sera lent".
145
 
146
  **Décision retenue : Hugging Face Spaces (Docker SDK)** pour l'API.
147
  Gratuit, pensé pour les démos ML, pas de limite stricte de temps
148
+ d'exécution comme un hébergement serverless, `Dockerfile` déjà préparé
149
+ (`yely_ai_module/Dockerfile`).
150
 
151
  Étapes de déploiement (à faire manuellement, action externe) :
152
  1. Créer un Space sur huggingface.co → SDK "Docker" → visibilité au choix.
 
165
  3. Pousser le contenu de `yely_ai_module/` (avec `models/crnn_fuel_pump_best.pt`
166
  inclus) vers le dépôt git du Space.
167
  4. Noter l'URL publique du Space (`https://<user>-<space>.hf.space`) — c'est
168
+ l'URL que le frontend Netlify appellera pour `/analyze`.
169
  5. Ajouter `CORSMiddleware` dans `app/main.py` pour autoriser le domaine
170
+ Netlify du frontend (sinon le navigateur bloquera les réponses).
171
  6. (Optionnel, payant) Activer le **stockage persistant** du Space et
172
  définir la variable d'environnement `YELY_DATA_DIR` sur son point de
173
  montage — sans quoi `photos/`, `logs/` et `monitoring/feedback.jsonl`
web/config.js CHANGED
@@ -6,4 +6,4 @@
6
  // sur un sous-domaine dédié : https://<user>-<space-en-minuscules-tirets>.hf.space
7
  // Vérifier l'URL exacte en ouvrant le Space et en regardant l'adresse dans
8
  // la barre du navigateur une fois l'app chargée (ou l'onglet réseau).
9
- window.YELY_API_URL = "https://danielxdata-yely-ai-module.hf.space/analyze";
 
6
  // sur un sous-domaine dédié : https://<user>-<space-en-minuscules-tirets>.hf.space
7
  // Vérifier l'URL exacte en ouvrant le Space et en regardant l'adresse dans
8
  // la barre du navigateur une fois l'app chargée (ou l'onglet réseau).
9
+ window.YELY_API_URL = "https://danielxdata-yely-ai-module.hf.space/analyze";
web/index.html CHANGED
@@ -32,9 +32,12 @@
32
  padding: 2rem 1rem;
33
  }
34
  .app { width: 100%; max-width: 480px; }
35
- .brand { display: flex; align-items: center; gap: 0.6rem; margin-bottom: 0.25rem; }
 
36
  .brand .dot { width: 10px; height: 10px; border-radius: 50%; background: var(--accent); }
37
  .brand h1 { font-size: 1.3rem; margin: 0; }
 
 
38
  .subtitle { color: var(--muted); font-size: 0.9rem; margin: 0 0 1.5rem; }
39
 
40
  .card {
@@ -112,7 +115,10 @@
112
  </head>
113
  <body>
114
  <div class="app">
115
- <div class="brand"><span class="dot"></span><h1>YELY | Vérification pompiste</h1></div>
 
 
 
116
  <p class="subtitle">Démo du module IA : photo du terminal -> lecture automatique -> vérification de cohérence.</p>
117
 
118
  <div id="banner" class="banner"></div>
 
32
  padding: 2rem 1rem;
33
  }
34
  .app { width: 100%; max-width: 480px; }
35
+ .brand { display: flex; align-items: center; justify-content: space-between; gap: 0.6rem; margin-bottom: 0.25rem; }
36
+ .brand-left { display: flex; align-items: center; gap: 0.6rem; }
37
  .brand .dot { width: 10px; height: 10px; border-radius: 50%; background: var(--accent); }
38
  .brand h1 { font-size: 1.3rem; margin: 0; }
39
+ .brand a { color: var(--muted); font-size: 0.85rem; text-decoration: none; border: 1px solid var(--border); padding: 0.4rem 0.7rem; border-radius: 8px; }
40
+ .brand a:hover { border-color: var(--accent); color: var(--text); }
41
  .subtitle { color: var(--muted); font-size: 0.9rem; margin: 0 0 1.5rem; }
42
 
43
  .card {
 
115
  </head>
116
  <body>
117
  <div class="app">
118
+ <div class="brand">
119
+ <div class="brand-left"><span class="dot"></span><h1>YELY, verification pompiste</h1></div>
120
+ <a href="stats.html">Statistiques</a>
121
+ </div>
122
  <p class="subtitle">Démo du module IA : photo du terminal -> lecture automatique -> vérification de cohérence.</p>
123
 
124
  <div id="banner" class="banner"></div>