feat(validator): R1 MVP P0 — OcrRoiChecker + orchestrator (flag OFF default)
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>
This commit is contained in:
53
core/validation/result.py
Normal file
53
core/validation/result.py
Normal file
@@ -0,0 +1,53 @@
|
||||
"""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,
|
||||
}
|
||||
Reference in New Issue
Block a user