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.