Spaces:
Sleeping
Sleeping
| # Post-traitement et moteur de règles (`app/postprocess.py`, `app/rules.py`) | |
| ## `postprocess.py` — du texte brut aux valeurs métier | |
| ### `_norm_number(s)` | |
| Normalise un nombre lu par le CRNN en `float` : retire les espaces, | |
| gère la virgule décimale française ("14,28" → 14.28) et les séparateurs de | |
| milliers quand virgule ET point sont présents ("17.500,00" → 17500.00). | |
| ### `process(recognized_fields, fuel_price=None, cfg=None)` | |
| Point d'entrée principal. Contrairement à l'ancien pipeline OCR générique | |
| (`src/postprocess.py` à la racine du dépôt), **pas d'heuristique de | |
| classification** ici : le CRNN a déjà étiqueté chaque ligne par position | |
| (`field: "prix"|"volume"|"prix_litre"`), donc on normalise et calcule | |
| directement. | |
| **Règle la plus importante du fichier** : | |
| ```python | |
| price_val = fuel_price if fuel_price is not None else screen_price_val | |
| ``` | |
| Le prix du litre **configuré côté YELY** (paramètre `fuel_price` de l'appelant) | |
| fait toujours autorité sur celui lu à l'écran. C'est la règle métier n°1 du | |
| cahier des charges ("le prix du litre doit être configurable depuis le | |
| système YELY"), et ça corrige un bug réel rencontré en session : sur | |
| certaines pompes, le libellé "Prix" désigne en fait le **montant total**, | |
| pas le prix unitaire — un ancien pipeline qui faisait confiance à la valeur | |
| lue à l'écran pour le prix unitaire pouvait donc confondre montant et prix, | |
| et bloquer un paiement pourtant cohérent. | |
| ### `evaluate_consistency(liters, amount, price, tol, cfg)` | |
| Implémente la règle centrale du §5.5 : `montant = litres × prix`, avec une | |
| tolérance (`cfg.consistency_tolerance`, 2% par défaut — pour absorber les | |
| arrondis d'affichage pompe). Calcule aussi la valeur manquante quand une | |
| seule des deux (litres ou montant) est lisible (§5.4/§6.4/§6.5). | |
| ## `rules.py` — décider succès ou blocage | |
| `evaluate(parsed, quality, fuel_price, cfg)` applique les vérifications du | |
| §5.6/§10 **dans un ordre précis, la première qui échoue l'emporte** : | |
| 1. Qualité image non "valid" → bloqué (photo floue/sombre/surexposée) | |
| 2. Aucune donnée numérique détectée → bloqué | |
| 3. Litres ET montant manquants, ou prix manquant → bloqué | |
| 4. Incohérence détectée (`is_consistent` pas `True`) → bloqué | |
| 5. Score de confiance sous le seuil (`cfg.business_confidence_threshold`) → bloqué | |
| 6. Sinon → succès | |
| Cet ordre est délibéré : par exemple, une image floue doit toujours | |
| produire le message "photo floue" même si, par coïncidence, des chiffres | |
| ont quand même été lus — le pompiste doit reprendre la photo, pas être | |
| induit en erreur par un résultat qui a l'air valide. | |
| ### Score de confiance | |
| ```python | |
| confidence_score = ocr_confidence * 0.6 + quality_score * 0.4 | |
| ``` | |
| Combine la confiance moyenne du CRNN sur les champs lus et le score de | |
| qualité image. Simple et explicable (utile pour justifier une décision de | |
| blocage au pompiste), mais **pas appris** — une piste d'amélioration futur | |
| serait d'entraîner un petit modèle de calibration sur des données réelles | |
| de succès/échec, plutôt qu'une pondération fixe choisie à la main. | |
| ## Pourquoi ces deux fichiers sont séparés | |
| `postprocess.py` ne fait aucune I/O et ne dépend d'aucun modèle : il se | |
| teste en quelques millisecondes (`tests/test_postprocess.py`, ~11 tests, | |
| aucun chargement du CRNN). `rules.py` encapsule uniquement la **décision** | |
| (les seuils métier) — on peut ajuster `config.yaml` sans toucher au calcul, | |
| et inversement changer une formule de calcul sans re-tester la logique de | |
| blocage. | |