Spaces:
Sleeping
Sleeping
File size: 3,871 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 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 | """Porte de décision métier unifiée.
Combine la sortie du parsing (`app.postprocess.process`) et de la qualité
image (`app.quality.estimate_image_quality`) pour décider si une lecture
peut être validée (règles 3/4/5/7 du cahier des charges) : un paiement ne
doit jamais être validé si la photo est floue/illisible, si litres et
montant sont incohérents, ou si la confiance du modèle est trop faible.
"""
from typing import Any, Dict, Optional
from .config import RuleConfig, load_config
def _compute_confidence_score(parsed: Dict[str, Any], quality: Dict[str, Any]) -> float:
ocr_confidence = parsed.get("ocr_confidence")
if ocr_confidence is None:
ocr_confidence = min(1.0, len(parsed.get("raw_numbers", [])) / 3.0)
quality_score = quality.get("quality_score", 0.0)
return round(min(1.0, ocr_confidence * 0.6 + quality_score * 0.4), 2)
def evaluate(parsed: Dict[str, Any], quality: Dict[str, Any],
fuel_price: Optional[float] = None,
cfg: Optional[RuleConfig] = None) -> Dict[str, Any]:
"""Retourne {'success': bool, 'message': str, 'confidence_score': float}.
Ordre des vérifications (le premier échec l'emporte) :
1. image non "valid" (floue/sombre/surexposée/peu exploitable) -> bloqué
2. aucune donnée numérique détectée -> bloqué
3. litres ET montant manquants, ou prix manquant -> bloqué
4. is_consistent est False ou None (quand vérifiable) -> bloqué
5. confidence_score sous le seuil -> bloqué
6. sinon -> succès
"""
if cfg is None:
cfg = load_config()
confidence_score = _compute_confidence_score(parsed, quality)
image_quality = quality.get("image_quality")
if image_quality != "valid":
messages = {
"blurry": "Photo floue, veuillez reprendre la photo.",
"dark": "Image trop sombre, veuillez reprendre la photo.",
"bright": "Image surexposée (reflet), veuillez reprendre la photo.",
"unclear": "Image peu exploitable, veuillez reprendre la photo.",
}
return {
"success": False,
"message": messages.get(image_quality, "Image non exploitable, veuillez reprendre la photo."),
"confidence_score": confidence_score,
}
if not parsed.get("raw_numbers"):
return {
"success": False,
"message": "Aucune donnée détectée sur l'écran, veuillez reprendre la photo.",
"confidence_score": confidence_score,
}
liters = parsed.get("detected_liters")
amount = parsed.get("detected_amount")
price = parsed.get("fuel_price")
if liters is None and amount is None:
return {
"success": False,
"message": "Données insuffisantes (litres et montant non lisibles).",
"confidence_score": confidence_score,
}
if price is None:
return {
"success": False,
"message": "Prix du litre manquant, impossible de vérifier la cohérence.",
"confidence_score": confidence_score,
}
if liters is not None and amount is not None:
is_consistent = parsed.get("is_consistent")
if is_consistent is not True:
return {
"success": False,
"message": "Incohérence détectée entre montant, litres et prix.",
"confidence_score": confidence_score,
}
if confidence_score < cfg.business_confidence_threshold:
return {
"success": False,
"message": "Confiance de lecture insuffisante, vérification manuelle requise.",
"confidence_score": confidence_score,
}
return {
"success": True,
"message": "Données vérifiées avec succès.",
"confidence_score": confidence_score,
}
|