fix(replay): cure régression b584bbabc — fallback recorded_coords aveugle
Trois changements complémentaires dans la cascade de résolution serveur, finis ce soir 7 mai pour la démo GHT 8 mai. Restaure le comportement strict d'avril 2026 (workflow qui passait 20 fois d'affilée sans incident). 1. resolve_engine.py — _validate_resolution_quality (lignes 2255-2289) : Le commitb584bbabcdu 1er mai 2026 ("fix(stream): démo UHCD") avait transformé le rejet strict (resolved=False, method="rejected_drift_*") en fallback aveugle (resolved=True, method="fallback_recorded_coords", coords du record). Symptôme observé : Léa cliquait sur "Dossier en cours" du menu au lieu de "Synthèse Urgences" du tab — le VLM Quick Find Ollama hallucinait à (0.526, 0.918), drift dépassé, fallback ratait. Restauré : resolved=False explicite, le client passe en pause supervisée comme prévu (philosophie échec = apprentissage). 2. resolve_engine.py — exemption high-confidence élargie : L'exemption drift>0.20 IGNORÉ ne couvrait que template_matching ≥ 0.95 (commit35b27ae49du 2 mai). Étendue à hybrid_text_direct ≥ 0.80 : un OCR direct qui trouve le texte cible exact à score 0.80+ est aussi sûr qu'un template à 0.95 — la position est sémantiquement vraie, le drift reflète juste un changement de layout (résolution écran, refonte UI, scroll), pas une erreur de résolution. 3. resolve_engine.py + api_stream.py — pré-check OCR sémantique : Nouvelle fonction _validate_text_at_position (singleton EasyOCR fr+en, crop 200px autour de la coord résolue, fuzzy match 60% des tokens ≥3 caractères de l'expected_text). Câblée dans api_stream.py juste après _validate_resolution_quality. Si le by_text attendu n'est PAS présent dans la zone autour de la coord résolue → resolved=False method="rejected_text_mismatch" → pause supervisée. Pattern Verification-Aware Planning (state of the art 2026 — voir recommandations agent archéologue + agent SOTA review) : le serveur ne renvoie une coord que s'il est sémantiquement sûr du résultat. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -4503,6 +4503,44 @@ async def resolve_target(request: ResolveTargetRequest):
|
||||
request.fallback_y_pct,
|
||||
)
|
||||
|
||||
# Pré-check sémantique post-cascade : OCR sur une zone autour de la
|
||||
# coordonnée résolue pour vérifier que le by_text attendu y est bien
|
||||
# présent. Attrape les cas où la cascade rend des coords plausibles
|
||||
# mais pointant sur un autre élément (ex : clic sur "Dossier en cours"
|
||||
# du menu au lieu de "Synthèse Urgences" du tab plus bas).
|
||||
if result and result.get("resolved"):
|
||||
_by_text = (request.target_spec.get("by_text") or "").strip()
|
||||
if _by_text:
|
||||
from agent_v0.server_v1.resolve_engine import _validate_text_at_position
|
||||
_is_valid, _observed, _ocr_ms = _validate_text_at_position(
|
||||
tmp_path,
|
||||
float(result.get("x_pct", 0) or 0),
|
||||
float(result.get("y_pct", 0) or 0),
|
||||
_by_text,
|
||||
effective_w,
|
||||
effective_h,
|
||||
)
|
||||
if not _is_valid:
|
||||
logger.warning(
|
||||
"[REPLAY] Pre-check OCR REJET : '%s' attendu @ (%.4f, %.4f) "
|
||||
"via %s mais OCR voit '%s' (%.0fms)",
|
||||
_by_text[:40],
|
||||
float(result.get("x_pct", 0) or 0),
|
||||
float(result.get("y_pct", 0) or 0),
|
||||
result.get("method", "?"),
|
||||
_observed[:80],
|
||||
_ocr_ms,
|
||||
)
|
||||
result = {
|
||||
"resolved": False,
|
||||
"method": "rejected_text_mismatch",
|
||||
"reason": f"expected='{_by_text[:40]}' observed='{_observed[:60]}'",
|
||||
"original_method": result.get("method"),
|
||||
"original_score": result.get("score"),
|
||||
"x_pct": None,
|
||||
"y_pct": None,
|
||||
}
|
||||
|
||||
# [REPLAY] log structuré de sortie résolution (après validation)
|
||||
logger.info(
|
||||
f"[REPLAY] RESOLVE_EXIT session={request.session_id} "
|
||||
|
||||
Reference in New Issue
Block a user