Package core/validation/ minimal : - result.py : Verdict, FailureCategory, ValidationResult - pixel_diff_checker.py : wrapper de ReplayVerifier.verify_action - ocr_roi_checker.py : ROI 80px autour du clic, détecte WRONG_APPLICATION via SUSPECT_TOKENS (edge/https/explorateur de fichiers/…) - orchestrator.py : Validator dispatch action_type → checkers + agrégation Wiring api_stream.py:3646 derrière RPA_VALIDATOR_V2_ENABLED (OFF par défaut). Si verdict ≠ COMPLETE, override report.success=False et expose failure_category dans result_entry. Zero régression flag OFF. Tests : - tests/unit/test_validator_v2.py : 13 tests (Checkers + Validator + sérialisation) - tests/integration/test_validator_step10.py : 2 tests reproduisant le bug replay_sess_4c38dbb8 / act_raw_6c1432b3 (clic Enregistrer fait basculer vers Explorateur de fichiers) — Validator retourne WRONG_APPLICATION Activation pour test live : RPA_VALIDATOR_V2_ENABLED=true Cf. docs/recherche/SPEC_VALIDATOR_MATRICE.md, AXE_B2_DEEP_VALIDATOR.md. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
54 lines
1.8 KiB
Python
54 lines
1.8 KiB
Python
"""Dataclasses du Validator — Verdict, FailureCategory, ValidationResult.
|
|
|
|
Cf. SPEC_VALIDATOR_MATRICE.md §1 et AXE_B2_DEEP_VALIDATOR.md §3.1.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass, field
|
|
from enum import Enum
|
|
from typing import Any, Dict, Optional
|
|
|
|
|
|
class Verdict(str, Enum):
|
|
"""Trois verdicts possibles (calque Skyvern complete/terminate/continue)."""
|
|
COMPLETE = "complete" # l'action a eu l'effet voulu
|
|
CONTINUE = "continue" # effet pas encore visible → recheck/wait
|
|
TERMINATE = "terminate" # échec irrécupérable → pause supervisée
|
|
|
|
|
|
class FailureCategory(str, Enum):
|
|
"""Classification des échecs (restreinte au contexte rpa_vision_v3)."""
|
|
WRONG_TARGET = "wrong_target"
|
|
WRONG_APPLICATION = "wrong_application" # bug step 10 (clic hors-app)
|
|
NO_VISUAL_CHANGE = "no_visual_change"
|
|
UNEXPECTED_DIALOG = "unexpected_dialog"
|
|
OCR_TEXT_MISSING = "ocr_text_missing"
|
|
SCHEMA_INVALID = "schema_invalid"
|
|
UI_LOADING = "ui_loading"
|
|
UNKNOWN = "unknown"
|
|
|
|
|
|
@dataclass
|
|
class ValidationResult:
|
|
"""Résultat d'un check. Toujours sérialisable JSON."""
|
|
verdict: Verdict
|
|
confidence: float
|
|
check_used: str
|
|
elapsed_ms: float
|
|
reasoning: str = ""
|
|
failure_category: Optional[FailureCategory] = None
|
|
raw_evidence: Dict[str, Any] = field(default_factory=dict)
|
|
|
|
def to_dict(self) -> Dict[str, Any]:
|
|
return {
|
|
"verdict": self.verdict.value,
|
|
"confidence": round(self.confidence, 3),
|
|
"check_used": self.check_used,
|
|
"elapsed_ms": round(self.elapsed_ms, 1),
|
|
"reasoning": self.reasoning,
|
|
"failure_category": (
|
|
self.failure_category.value if self.failure_category else None
|
|
),
|
|
"raw_evidence": self.raw_evidence,
|
|
}
|