CoolFace
Apppublic

emmixam/ai_workflow_discovery_agent

sourceHugging Faceupdated 7mo agoView on Hugging Face
0likes
time_validators.py217 linesDownload Raw Back to validators
1from __future__ import annotations2 3from schemas import TimeMetric, TimeMetricsReport4 5 6class TimeValidationError(ValueError):7    """Erreur de validation métier sur les métriques temporelles."""8 9 10def _assert_allowed_level(metric: TimeMetric) -> None:11    allowed = {"provided", "observed", "estimated", "missing"}12    if metric.confidence_level not in allowed:13        raise TimeValidationError(14            f"{metric.name}: confidence_level invalide ({metric.confidence_level})."15        )16 17 18def _assert_non_negative_numeric(metric: TimeMetric) -> None:19    if metric.confidence_level == "missing":20        return21 22    if not isinstance(metric.value, (int, float)):23        raise TimeValidationError(24            f"{metric.name}: value doit être numérique pour une métrique temporelle exploitable."25        )26 27    if metric.value < 0:28        raise TimeValidationError(29            f"{metric.name}: value négative interdite ({metric.value})."30        )31 32 33def _assert_missing_consistency(metric: TimeMetric) -> None:34    if metric.confidence_level == "missing":35        if metric.value is not None:36            raise TimeValidationError(37                f"{metric.name}: une métrique 'missing' ne doit pas avoir de valeur."38            )39    else:40        if metric.value is None:41            raise TimeValidationError(42                f"{metric.name}: une métrique non 'missing' doit avoir une valeur."43            )44 45 46def _assert_estimated_has_hypothesis(metric: TimeMetric) -> None:47    if metric.confidence_level == "estimated":48        if metric.hypothesis is None or not metric.hypothesis.strip():49            raise TimeValidationError(50                f"{metric.name}: une métrique 'estimated' doit documenter une hypothèse."51            )52 53 54def validate_time_metric_or_raise(metric: TimeMetric) -> None:55    if not isinstance(metric, TimeMetric):56        raise TimeValidationError(f"TimeMetric attendu, reçu: {type(metric)!r}")57 58    if not metric.name.strip():59        raise TimeValidationError("TimeMetric.name vide.")60    if not metric.unit.strip():61        raise TimeValidationError(f"{metric.name}: unit vide.")62    if not metric.source.strip():63        raise TimeValidationError(f"{metric.name}: source vide.")64 65    _assert_allowed_level(metric)66    _assert_missing_consistency(metric)67    _assert_estimated_has_hypothesis(metric)68    _assert_non_negative_numeric(metric)69 70 71def _assert_metric_units(report: TimeMetricsReport) -> None:72    expected_units = {73        "monthly_volume": {"cases/month", "items/month", "events/month"},74        "activity_duration_minutes": {"minutes", "minute"},75        "case_cycle_time_minutes": {"minutes", "minute"},76        "manual_time_minutes": {"minutes", "minute"},77        "waiting_time_minutes": {"minutes", "minute"},78    }79 80    metrics = {81        "monthly_volume": report.monthly_volume,82        "activity_duration_minutes": report.activity_duration_minutes,83        "case_cycle_time_minutes": report.case_cycle_time_minutes,84        "manual_time_minutes": report.manual_time_minutes,85        "waiting_time_minutes": report.waiting_time_minutes,86    }87 88    for key, metric in metrics.items():89        if metric.unit not in expected_units[key]:90            raise TimeValidationError(91                f"{metric.name}: unité inattendue pour {key} ({metric.unit})."92            )93 94 95def _assert_time_logic(report: TimeMetricsReport) -> None:96    activity = report.activity_duration_minutes97    manual = report.manual_time_minutes98    waiting = report.waiting_time_minutes99    cycle = report.case_cycle_time_minutes100 101    comparable = [activity, manual, waiting, cycle]102    if any(m.confidence_level == "missing" for m in comparable):103        return104 105    activity_value = float(activity.value)106    manual_value = float(manual.value)107    waiting_value = float(waiting.value)108    cycle_value = float(cycle.value)109 110    if manual_value > cycle_value:111        raise TimeValidationError(112            "manual_time_minutes ne peut pas dépasser case_cycle_time_minutes."113        )114 115    if activity_value > cycle_value:116        raise TimeValidationError(117            "activity_duration_minutes ne peut pas dépasser case_cycle_time_minutes."118        )119 120    if waiting_value > cycle_value:121        raise TimeValidationError(122            "waiting_time_minutes ne peut pas dépasser case_cycle_time_minutes."123        )124 125 126def _assert_roi_flags(report: TimeMetricsReport) -> None:127    critical = [128        report.monthly_volume,129        report.activity_duration_minutes,130        report.manual_time_minutes,131    ]132    levels = [m.confidence_level for m in critical]133 134    full_ok = all(level in {"provided", "observed"} for level in levels)135    partial_ok = all(level in {"provided", "observed", "estimated"} for level in levels) and not any(136        level == "missing" for level in levels137    )138 139    if report.can_compute_full_roi and not full_ok:140        raise TimeValidationError(141            "can_compute_full_roi=True incohérent avec les niveaux de confiance critiques."142        )143 144    if report.can_compute_partial_roi and not partial_ok:145        raise TimeValidationError(146            "can_compute_partial_roi=True incohérent avec les niveaux de confiance critiques."147        )148 149    if report.can_compute_full_roi and not report.can_compute_partial_roi:150        raise TimeValidationError(151            "Un ROI complet implique nécessairement qu'un ROI partiel soit possible."152        )153 154 155def validate_time_metrics_report_or_raise(report: TimeMetricsReport) -> None:156    if not isinstance(report, TimeMetricsReport):157        raise TimeValidationError(158            f"TimeMetricsReport attendu, reçu: {type(report)!r}"159        )160 161    metrics = [162        report.monthly_volume,163        report.activity_duration_minutes,164        report.case_cycle_time_minutes,165        report.manual_time_minutes,166        report.waiting_time_minutes,167    ]168 169    for metric in metrics:170        validate_time_metric_or_raise(metric)171 172    _assert_metric_units(report)173    _assert_time_logic(report)174    _assert_roi_flags(report)175 176 177def audit_time_metrics_report(report: TimeMetricsReport) -> list[str]:178    warnings: list[str] = []179 180    metrics = [181        report.monthly_volume,182        report.activity_duration_minutes,183        report.case_cycle_time_minutes,184        report.manual_time_minutes,185        report.waiting_time_minutes,186    ]187 188    estimated_metrics = [m.name for m in metrics if m.confidence_level == "estimated"]189    missing_metrics = [m.name for m in metrics if m.confidence_level == "missing"]190 191    if estimated_metrics:192        warnings.append(193            f"Métriques estimées présentes: {estimated_metrics}. Vérifier les hypothèses."194        )195 196    if missing_metrics:197        warnings.append(198            f"Métriques manquantes: {missing_metrics}. Le ROI peut être partiel ou bloqué."199        )200 201    if report.can_compute_partial_roi and not report.can_compute_full_roi:202        warnings.append("ROI partiel seulement: certaines métriques critiques sont estimées.")203 204    if not report.can_compute_partial_roi:205        warnings.append("ROI désactivé: données temporelles insuffisantes.")206 207    if (208        report.activity_duration_minutes.confidence_level != "missing"209        and report.waiting_time_minutes.confidence_level != "missing"210        and float(report.waiting_time_minutes.value) > float(report.activity_duration_minutes.value) * 3211    ):212        warnings.append(213            "Le temps d'attente dépasse largement le temps d'activité; possible goulot de validation/transfert."214        )215 216    return warnings217