Spaces:
Sleeping
Sleeping
File size: 3,584 Bytes
b510add | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 | # 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.
|