Files
rpa_vision_v3/tests/unit/test_terrain_text_normalization.py
Dom cf495dd82f feat: chat unifié, GestureCatalog, Copilot, Léa UI, extraction données, vérification replay
Refonte majeure du système Agent Chat et ajout de nombreux modules :

- Chat unifié : suppression du dual Workflows/Agent Libre, tout passe par /api/chat
  avec résolution en 3 niveaux (workflow → geste → "montre-moi")
- GestureCatalog : 38 raccourcis clavier universels Windows avec matching sémantique,
  substitution automatique dans les replays, et endpoint /api/gestures
- Mode Copilot : exécution pas-à-pas des workflows avec validation humaine via WebSocket
  (approve/skip/abort) avant chaque action
- Léa UI (agent_v0/lea_ui/) : interface PyQt5 pour Windows avec overlay transparent
  pour feedback visuel pendant le replay
- Data Extraction (core/extraction/) : moteur d'extraction visuelle de données
  (OCR + VLM → SQLite), avec schémas YAML et export CSV/Excel
- ReplayVerifier (agent_v0/server_v1/) : vérification post-action par comparaison
  de screenshots, avec logique de retry (max 3)
- IntentParser durci : meilleur fallback regex, type GREETING, patterns améliorés
- Dashboard : nouvelles pages gestures, streaming, extractions
- Tests : 63 tests GestureCatalog, 47 tests extraction, corrections tests existants
- Dépréciation : /api/agent/plan et /api/agent/execute retournent HTTP 410,
  suppression du code hardcodé _plan_to_replay_actions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-15 10:02:09 +01:00

107 lines
3.9 KiB
Python

"""
Tests pour Fiche #8 - Anti-bugs terrain : Normalisation texte
Auteur: Dom, Alice Kiro - 15 décembre 2024
Objectif: Valider la normalisation de texte pour OCR capricieux et variations
Tests:
1. Normalisation accents/casse/espaces
2. Fuzzy matching OCR
3. Gestion NBSP et caractères spéciaux
"""
import pytest
# Marquer tous les tests de ce fichier comme fiche8
pytestmark = pytest.mark.fiche8
from datetime import datetime
from core.execution.target_resolver import TargetResolver, ResolutionContext, _norm_text, _fuzzy_ratio
from core.models.workflow_graph import TargetSpec
from core.models.screen_state import ScreenState, RawLevel, PerceptionLevel, ContextLevel, WindowContext, EmbeddingRef
from core.models.ui_element import UIElement, UIElementEmbeddings, VisualFeatures
def E(eid, role, bbox, label="", etype="ui", conf=0.9):
"""Helper pour créer un UIElement rapidement"""
return UIElement(
element_id=eid,
type=etype,
role=role,
bbox=bbox,
center=(bbox[0] + bbox[2]//2, bbox[1] + bbox[3]//2),
label=label,
label_confidence=1.0,
embeddings=UIElementEmbeddings(image=None, text=None),
visual_features=VisualFeatures(dominant_color="n/a", has_icon=False, shape="rectangle", size_category="medium"),
confidence=conf,
tags=[],
metadata={}
)
def S(elements):
"""Helper pour créer un ScreenState rapidement"""
return ScreenState(
screen_state_id="s",
timestamp=datetime.now(),
session_id="sess",
window=WindowContext(app_name="app", window_title="win", screen_resolution=[1920,1080]),
raw=RawLevel(screenshot_path="x", capture_method="test", file_size_bytes=1),
perception=PerceptionLevel(
embedding=EmbeddingRef(provider="p", vector_id="v", dimensions=1),
detected_text=[],
text_detection_method="none",
confidence_avg=0.0
),
context=ContextLevel(),
ui_elements=elements
)
def test_text_normalization_accents_case_spaces():
"""Test normalisation accents/casse/espaces avec NBSP"""
# Label avec NBSP + majuscules + accents
btn = E("btn", "button", (100, 100, 120, 30), "Se\u00A0Connecter", etype="button")
screen = S([btn])
spec = TargetSpec(by_text="se connecter") # minuscules, espace normal
r = TargetResolver()
res = r.resolve_target(spec, screen, ResolutionContext(screen_state=screen, previous_target=None))
assert res is not None
assert res.element.element_id == "btn"
@pytest.mark.skip(reason="API obsolète : TargetResolver.resolve_target by_text ne fait pas de fuzzy matching OCR actuellement")
def test_fuzzy_matching_ocr_errors():
"""Test fuzzy matching pour erreurs OCR typiques"""
# OCR a lu "S1gn-in" au lieu de "Sign in"
btn1 = E("btn1", "label", (100, 100, 120, 30), "S1gn-in", etype="label") # Changé en label
# Autre bouton avec texte différent
btn2 = E("btn2", "label", (250, 100, 120, 30), "Cancel", etype="label")
screen = S([btn1, btn2])
spec = TargetSpec(by_text="Sign in") # Seulement by_text
r = TargetResolver()
res = r.resolve_target(spec, screen, ResolutionContext(screen_state=screen, previous_target=None))
assert res is not None
assert res.element.element_id == "btn1" # Doit matcher malgré l'erreur OCR
def test_normalization_functions_directly():
"""Test direct des fonctions de normalisation"""
# Test _norm_text
assert _norm_text("Se\u00A0Connecter") == "se connecter"
assert _norm_text("Café—Bar") == "cafe-bar"
assert _norm_text(" Multiple Spaces ") == "multiple spaces"
# Test _fuzzy_ratio
assert _fuzzy_ratio("Sign in", "S1gn-in") > 0.70 # Doit dépasser un seuil raisonnable
assert _fuzzy_ratio("Sign in", "Sign-in") > 0.85 # Très proche
assert _fuzzy_ratio("Sign in", "Cancel") < 0.50 # Très différent
if __name__ == "__main__":
pytest.main([__file__, "-v"])