Spaces:
Sleeping
Sleeping
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
- README.md +2 -2
- app/main.py +2 -2
- docs/API.md +2 -2
- docs/DEFENSE_QA.md +88 -0
- docs/WORKFLOW.md +15 -13
- web/config.js +1 -1
- 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
|
| 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
|
| 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 (
|
| 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.
|
| 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 (
|
| 46 |
Spaces) sont sur des domaines différents. Par défaut `"*"` (permissif,
|
| 47 |
-
adapté à une démo) ; à restreindre au domaine
|
| 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/`) →
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
**API IA → PAS
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
|
|
|
| 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
|
|
|
|
| 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
|
| 167 |
5. Ajouter `CORSMiddleware` dans `app/main.py` pour autoriser le domaine
|
| 168 |
-
|
| 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">
|
|
|
|
|
|
|
|
|
|
| 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>
|