Files
rpa_vision_v3/core/validation/result.py
Dom 1b4e64960b 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>
2026-05-24 17:52:06 +02:00

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,
}