Spaces:
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 :
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 :
- Qualité image non "valid" → bloqué (photo floue/sombre/surexposée)
- Aucune donnée numérique détectée → bloqué
- Litres ET montant manquants, ou prix manquant → bloqué
- Incohérence détectée (
is_consistentpasTrue) → bloqué - Score de confiance sous le seuil (
cfg.business_confidence_threshold) → bloqué - 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
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.