Spaces:
Sleeping
Sleeping
| """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, | |
| } | |