Files
2026-03-05 00:20:25 +01:00

36 KiB

Document de Design

Vue d'Ensemble

Ce document décrit l'architecture et la conception détaillée du système de détection d'éléments d'UI et de fusion multi-modale pour GeniusIA v2. Le système transforme l'approche actuelle de comparaison plein écran en une plateforme RPA robuste capable de détecter et d'interagir avec des éléments d'interface individuels via une analyse vision multi-modale.

Objectifs Principaux

  1. Détecter et classifier les éléments d'UI individuels (boutons, champs, listes, etc.)
  2. Créer des embeddings multi-modaux robustes combinant vision, texte et contexte
  3. Permettre une correspondance de workflow au niveau élément plutôt qu'au niveau écran complet
  4. Maintenir la compatibilité arrière avec le système existant
  5. Implémenter progressivement sans casser les workflows existants

Architecture

Composants Principaux

┌─────────────────────────────────────────────────────────────┐
│                     Orchestrator                             │
│                  (Boucle Cognitive)                          │
└────────────────────┬────────────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────────────┐
│              UIElementDetector (NOUVEAU)                     │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐      │
│  │   Region     │→ │  Character   │→ │  Classifier  │      │
│  │  Proposer    │  │   -izer      │  │              │      │
│  └──────────────┘  └──────────────┘  └──────────────┘      │
└────────────────────┬────────────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────────────┐
│          MultiModalEmbeddingManager (NOUVEAU)                │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐      │
│  │   Image      │  │     Text     │  │   Context    │      │
│  │  Embedder    │  │   Embedder   │  │   Embedder   │      │
│  └──────────────┘  └──────────────┘  └──────────────┘      │
│                    ┌──────────────┐                          │
│                    │    Fusion    │                          │
│                    │    Engine    │                          │
│                    └──────────────┘                          │
└────────────────────┬────────────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────────────┐
│              EnrichedScreenState (NOUVEAU)                   │
│  ┌──────────────────────────────────────────────────┐       │
│  │  ui_elements: List[UIElement]                    │       │
│  │  state_embedding: StateEmbedding                 │       │
│  │  perception: PerceptionData                      │       │
│  └──────────────────────────────────────────────────┘       │
└────────────────────┬────────────────────────────────────────┘
                     │
                     ▼
┌─────────────────────────────────────────────────────────────┐
│              WorkflowMatcher (AMÉLIORÉ)                      │
│  - Matching au niveau élément                                │
│  - Matching au niveau état global                            │
│  - Mode legacy pour compatibilité                            │
└─────────────────────────────────────────────────────────────┘

Flux de Données

  1. Capture d'écran → Screenshot brut
  2. UIElementDetector → Liste de UIElement
  3. MultiModalEmbeddingManager → State embedding unifié
  4. EnrichedScreenState → Structure complète avec éléments + embedding
  5. WorkflowMatcher → Correspondance de workflow robuste
  6. Orchestrator → Décision d'action basée sur éléments sémantiques

Composants et Interfaces

1. UIElement (Structure de Données)

@dataclass
class UIElement:
    """Représente un élément d'interface utilisateur détecté."""
    
    # Identification
    element_id: str  # Hash stable: hash(app_name + center_bbox + label_normalized)
    type: UIElementType  # button, text_input, checkbox, tab, etc.
    role: str  # validate_invoice, search_field, primary_action, etc.
    
    # Position
    bbox: Tuple[int, int, int, int]  # (x1, y1, x2, y2)
    
    # Contenu
    label: str  # Texte visible de l'élément
    
    # Embeddings visuels
    visual: VisualData
    # - screenshot_path: str
    # - embedding_provider: str
    # - embedding_vector_id: str
    
    # Embeddings textuels
    text: TextData
    # - raw: str
    # - normalized: str
    # - embedding_provider: str
    # - embedding_vector_id: str
    
    # Propriétés
    properties: ElementProperties
    # - is_clickable: bool
    # - is_focusable: bool
    # - is_dangerous: bool
    
    # Contexte
    context: ElementContext
    # - app_name: str
    # - window_title: str
    # - workflow_hint: Optional[str]
    
    # Métadonnées
    tags: List[str]
    confidence: float  # Score de confiance de la détection

2. EnrichedScreenState (Structure de Données)

@dataclass
class EnrichedScreenState:
    """ScreenState enrichi avec éléments d'UI et embedding multi-modal."""
    
    # Identification
    screen_state_id: str
    timestamp: datetime
    session_id: str
    
    # Fenêtre
    window: WindowInfo
    # - app_name: str
    # - window_title: str
    # - screen_resolution: Tuple[int, int]
    
    # Données brutes
    raw: RawData
    # - screenshot_path: str
    
    # Perception
    perception: PerceptionData
    # - detected_text: List[str]
    # - ocr_results: Optional[Dict]
    
    # Éléments d'UI (NOUVEAU)
    ui_elements: List[UIElement]
    
    # Embedding d'état unifié (NOUVEAU)
    state_embedding: StateEmbedding
    # - provider: str
    # - vector_id: str
    # - components: EmbeddingComponents
    #   - image_embedding: ComponentInfo
    #   - text_embedding: ComponentInfo
    #   - title_embedding: ComponentInfo
    #   - ui_embedding: ComponentInfo
    #   - context_embedding: ComponentInfo
    
    # Contexte workflow
    context: ContextData
    # - current_workflow_candidate: Optional[str]
    # - tags: List[str]

3. UIElementDetector (Nouveau Composant)

class UIElementDetector:
    """Détecte et caractérise les éléments d'UI dans un screenshot."""
    
    def __init__(
        self,
        vlm_model: VisionLanguageModel,
        embedder: ImageEmbedder,
        text_embedder: TextEmbedder,
        config: DetectorConfig
    ):
        self.vlm = vlm_model
        self.image_embedder = embedder
        self.text_embedder = text_embedder
        self.config = config
        
        # Composants du pipeline
        self.region_proposer = RegionProposer(vlm_model, config)
        self.characterizer = ElementCharacterizer(embedder, text_embedder)
        self.classifier = ElementClassifier(config)
    
    def detect_elements(
        self,
        screenshot: np.ndarray,
        window_info: WindowInfo
    ) -> List[UIElement]:
        """
        Pipeline complet de détection d'éléments.
        
        Étapes:
        1. Proposer des régions d'intérêt
        2. Caractériser chaque région
        3. Classifier type et rôle
        """
        # Étape 1: Régions d'intérêt
        regions = self.region_proposer.propose_regions(screenshot, window_info)
        
        # Étape 2: Caractérisation
        characterized = []
        for region in regions:
            char_element = self.characterizer.characterize(
                screenshot, region, window_info
            )
            if char_element:
                characterized.append(char_element)
        
        # Étape 3: Classification
        elements = []
        for char_elem in characterized:
            element = self.classifier.classify(char_elem, window_info)
            elements.append(element)
        
        return elements

4. RegionProposer (Sous-composant)

class RegionProposer:
    """Propose des régions d'intérêt candidates pour les éléments d'UI."""
    
    def propose_regions(
        self,
        screenshot: np.ndarray,
        window_info: WindowInfo
    ) -> List[BoundingBox]:
        """
        Propose des régions en combinant plusieurs méthodes:
        - Détection de zones de texte
        - Repérage de rectangles autour de texte
        - Requête VLM pour zones cliquables (optionnel, coûteux)
        """
        regions = []
        
        # Méthode 1: Zones de texte (rapide)
        if self.config.use_text_detection:
            text_regions = self._detect_text_regions(screenshot)
            regions.extend(text_regions)
        
        # Méthode 2: Rectangles propres (heuristique)
        if self.config.use_rectangle_detection:
            rect_regions = self._detect_rectangles(screenshot)
            regions.extend(rect_regions)
        
        # Méthode 3: VLM (précis mais lent, à utiliser avec parcimonie)
        if self.config.use_vlm_detection and self._should_use_vlm(window_info):
            vlm_regions = self._query_vlm_for_regions(screenshot)
            regions.extend(vlm_regions)
        
        # Fusion et nettoyage
        regions = self._merge_overlapping_regions(regions)
        regions = self._filter_invalid_regions(regions)
        
        return regions
    
    def _should_use_vlm(self, window_info: WindowInfo) -> bool:
        """Décide si on doit utiliser le VLM (coûteux)."""
        # Utiliser VLM seulement pour nouveaux écrans ou écrans importants
        return (
            self.config.vlm_on_new_screens and
            self._is_new_screen(window_info)
        )

5. MultiModalEmbeddingManager (Nouveau Composant)

class MultiModalEmbeddingManager:
    """Gère la création d'embeddings multi-modaux pour les états d'écran."""
    
    def __init__(
        self,
        image_embedder: ImageEmbedder,
        text_embedder: TextEmbedder,
        config: EmbeddingConfig
    ):
        self.image_embedder = image_embedder
        self.text_embedder = text_embedder
        self.config = config
        
        # Poids de fusion (configurables)
        self.weights = {
            'image': config.weight_image,      # 0.5 par défaut
            'text': config.weight_text,        # 0.3 par défaut
            'title': config.weight_title,      # 0.1 par défaut
            'ui': config.weight_ui,            # 0.1 par défaut
            'context': config.weight_context   # 0.1 par défaut (à terme)
        }
    
    def create_state_embedding(
        self,
        screenshot: np.ndarray,
        detected_text: List[str],
        window_title: str,
        ui_elements: List[UIElement],
        context: Optional[Dict] = None
    ) -> StateEmbedding:
        """
        Crée un embedding d'état unifié en fusionnant toutes les modalités.
        """
        # Composante 1: Image globale
        image_emb = self.image_embedder.embed(screenshot)
        image_emb_norm = self._normalize(image_emb)
        
        # Composante 2: Texte concaténé
        text_concat = " ".join(detected_text)
        text_emb = self.text_embedder.embed(text_concat)
        text_emb_norm = self._normalize(text_emb)
        
        # Composante 3: Titre de fenêtre
        title_emb = self.text_embedder.embed(window_title)
        title_emb_norm = self._normalize(title_emb)
        
        # Composante 4: UI éléments (moyenne des embeddings importants)
        ui_emb = self._compute_ui_embedding(ui_elements)
        ui_emb_norm = self._normalize(ui_emb)
        
        # Composante 5: Contexte (à implémenter plus tard)
        context_emb = self._compute_context_embedding(context)
        context_emb_norm = self._normalize(context_emb)
        
        # Fusion pondérée
        state_emb = (
            self.weights['image'] * image_emb_norm +
            self.weights['text'] * text_emb_norm +
            self.weights['title'] * title_emb_norm +
            self.weights['ui'] * ui_emb_norm +
            self.weights['context'] * context_emb_norm
        )
        
        # Normalisation finale
        state_emb_final = self._normalize(state_emb)
        
        # Créer l'objet StateEmbedding avec composantes
        return StateEmbedding(
            provider="multimodal_fusion_v1",
            vector=state_emb_final,
            components={
                'image': ComponentInfo(provider="openclip", vector=image_emb),
                'text': ComponentInfo(provider="clip_text", vector=text_emb),
                'title': ComponentInfo(provider="clip_text", vector=title_emb),
                'ui': ComponentInfo(provider="openclip", vector=ui_emb),
                'context': ComponentInfo(provider="numeric", vector=context_emb)
            }
        )
    
    def _compute_ui_embedding(self, ui_elements: List[UIElement]) -> np.ndarray:
        """Calcule un embedding représentatif des éléments d'UI."""
        if not ui_elements:
            return np.zeros(self.config.embedding_dim)
        
        # Filtrer les éléments importants (boutons primaires, etc.)
        important_elements = [
            elem for elem in ui_elements
            if 'primary_action' in elem.tags or elem.properties.is_clickable
        ]
        
        if not important_elements:
            important_elements = ui_elements[:5]  # Top 5 si aucun important
        
        # Moyenne des embeddings
        embeddings = [self._load_embedding(elem.visual.embedding_vector_id)
                     for elem in important_elements]
        return np.mean(embeddings, axis=0)

6. WorkflowMatcher Amélioré

class EnhancedWorkflowMatcher:
    """Matcher amélioré supportant correspondance au niveau élément."""
    
    def __init__(
        self,
        legacy_matcher: WorkflowMatcher,
        config: MatcherConfig
    ):
        self.legacy_matcher = legacy_matcher
        self.config = config
    
    def match_workflow_step(
        self,
        current_state: EnrichedScreenState,
        workflow: Workflow
    ) -> MatchResult:
        """
        Trouve la meilleure correspondance de workflow.
        
        Stratégie:
        1. Si le workflow a des descripteurs d'éléments → matching élément
        2. Sinon → matching legacy (plein écran)
        """
        if self._has_element_descriptors(workflow):
            return self._match_by_elements(current_state, workflow)
        else:
            return self._match_legacy(current_state, workflow)
    
    def _match_by_elements(
        self,
        current_state: EnrichedScreenState,
        workflow: Workflow
    ) -> MatchResult:
        """Matching basé sur les éléments d'UI."""
        best_match = None
        best_score = 0.0
        
        for step in workflow.steps:
            # Comparer state_embedding
            state_sim = self._compare_state_embeddings(
                current_state.state_embedding,
                step.expected_state_embedding
            )
            
            # Comparer éléments requis
            element_sim = self._compare_required_elements(
                current_state.ui_elements,
                step.required_elements
            )
            
            # Score combiné
            score = (
                self.config.weight_state * state_sim +
                self.config.weight_elements * element_sim
            )
            
            if score > best_score:
                best_score = score
                best_match = step
        
        return MatchResult(
            matched_step=best_match,
            confidence=best_score,
            method="element_based"
        )
    
    def _compare_required_elements(
        self,
        detected: List[UIElement],
        required: List[ElementDescriptor]
    ) -> float:
        """Compare les éléments détectés aux éléments requis."""
        if not required:
            return 1.0
        
        matches = 0
        for req_elem in required:
            # Chercher un élément correspondant
            for det_elem in detected:
                if self._elements_match(det_elem, req_elem):
                    matches += 1
                    break
        
        return matches / len(required)
    
    def _elements_match(
        self,
        detected: UIElement,
        required: ElementDescriptor
    ) -> bool:
        """Vérifie si un élément détecté correspond à un descripteur."""
        # Vérifier type
        if required.type and detected.type != required.type:
            return False
        
        # Vérifier rôle
        if required.role and detected.role != required.role:
            return False
        
        # Vérifier similarité sémantique (embedding)
        if required.embedding:
            sim = cosine_similarity(
                detected.text.embedding_vector,
                required.embedding
            )
            if sim < self.config.min_semantic_similarity:
                return False
        
        # Vérifier position relative (optionnel)
        if required.position_hint:
            if not self._position_matches(detected.bbox, required.position_hint):
                return False
        
        return True

Modèles de Données

Formats JSON

UIElement JSON (v1)

{
  "schema_version": "uielement_v1",
  "element_id": "el_btn_valider_001",
  "type": "button",
  "role": "validate_invoice",
  "bbox": [1600, 900, 1800, 940],
  "label": "Valider la facture",
  "confidence": 0.95,
  "detection_method": "heuristic_rectangle",
  "visual": {
    "screenshot_path": "data/screens/elements/el_btn_valider_001.png",
    "embedding": {
      "provider": "openclip_ViT-B-32",
      "vector_id": "data/embeddings/elements/el_btn_valider_001.npy"
    }
  },
  "text": {
    "raw": "Valider la facture",
    "normalized": "valider facture",
    "embedding": {
      "provider": "clip_text",
      "vector_id": "data/embeddings/elements/el_btn_valider_001_text.npy"
    }
  },
  "properties": {
    "is_clickable": true,
    "is_focusable": true,
    "is_dangerous": false
  },
  "context": {
    "app_name": "logiciel_facturation",
    "window_title": "Facture n° 2025-00123 - DUPONT Jean",
    "workflow_hint": "WF_validation_facture"
  },
  "tags": ["primary_action", "billing"]
}

Notes importantes :

  • schema_version : Permet la compatibilité future et la migration de schémas
  • detection_method : Trace comment l'élément a été détecté (heuristic_rectangle, vlm_query, manual, etc.)
  • embedding : Peut être null en mode ultralight pour permettre une détection progressive
  • confidence : Score de confiance de la détection (0.0 à 1.0)

EnrichedScreenState JSON (v1)

{
  "schema_version": "screenstate_v1",
  "mode": "complete",
  "screen_state_id": "screen_2025-11-21T10-15-32.123Z",
  "timestamp": "2025-11-21T10:15:32.123Z",
  "session_id": "session_abc123",
  
  "environment": {
    "platform": "windows",
    "display_scale": 1.0
  },
  
  "window": {
    "app_name": "logiciel_facturation",
    "window_title": "Factures - Clinique Demo",
    "screen_resolution": [1920, 1080]
  },
  
  "raw": {
    "screenshot_path": "data/screens/2025-11-21/10-15-32_factures.png"
  },
  
  "perception": {
    "detected_text": [
      "Factures",
      "Patient",
      "Montant",
      "Statut",
      "À valider",
      "Validée"
    ]
  },
  
  "ui_elements": [
    {
      "schema_version": "uielement_v1",
      "element_id": "el_btn_valider",
      "type": "button",
      "role": "validate_invoice",
      "bbox": [1600, 900, 1800, 940],
      "label": "Valider la facture",
      "confidence": 0.95
    }
  ],
  
  "state_embedding": {
    "provider": "multimodal_fusion_v1",
    "vector_id": "data/embeddings/screens/state/screen_2025-11-21T10-15-32.123Z.npy",
    "components": {
      "image_embedding": {
        "provider": "openclip_ViT-B-32",
        "vector_id": "data/embeddings/screens/image/screen_2025-11-21T10-15-32.123Z.npy"
      },
      "text_embedding": {
        "provider": "clip_text",
        "vector_id": "data/embeddings/screens/text/screen_2025-11-21T10-15-32.123Z.npy"
      },
      "title_embedding": {
        "provider": "clip_text",
        "vector_id": "data/embeddings/screens/title/screen_2025-11-21T10-15-32.123Z.npy"
      },
      "ui_embedding": {
        "provider": "openclip_ViT-B-32",
        "vector_id": "data/embeddings/screens/ui/screen_2025-11-21T10-15-32.123Z.npy"
      },
      "context_embedding": {
        "provider": "numeric_context_v1",
        "vector_id": "data/embeddings/screens/context/screen_2025-11-21T10-15-32.123Z.npy"
      }
    }
  },
  
  "context": {
    "current_workflow_candidate": "WF_validation_facture",
    "tags": ["facturation"]
  },
  
  "processing_metadata": {
    "detection_time_ms": 1234,
    "embedding_time_ms": 567,
    "num_elements_detected": 1,
    "vlm_used": false
  }
}

Notes importantes :

  • schema_version : Permet la compatibilité future et la migration de schémas
  • mode : Indique le niveau de traitement ("light", "enriched", "complete")
  • environment : Informations sur la plateforme et l'échelle d'affichage (crucial pour gérer les différences Windows/Linux/Mac et les DPI)
  • state_embedding.components : Peut être null en mode light
  • processing_metadata : Métriques de performance pour monitoring et optimisation (optionnel)

Exemples de modes :

Mode Light :

{
  "schema_version": "screenstate_v1",
  "mode": "light",
  "ui_elements": [],
  "state_embedding": {
    "provider": "openclip_ViT-B-32",
    "vector_id": "data/embeddings/screens/image/screen_xxx.npy",
    "components": null
  }
}

Mode Enriched :

{
  "schema_version": "screenstate_v1",
  "mode": "enriched",
  "ui_elements": [/* éléments détectés */],
  "state_embedding": {
    "provider": "openclip_ViT-B-32",
    "vector_id": "data/embeddings/screens/image/screen_xxx.npy",
    "components": null
  }
}

Mode Complete :

{
  "schema_version": "screenstate_v1",
  "mode": "complete",
  "ui_elements": [/* éléments détectés */],
  "state_embedding": {
    "provider": "multimodal_fusion_v1",
    "vector_id": "data/embeddings/screens/state/screen_xxx.npy",
    "components": {/* toutes les composantes */}
  }
}

Gestion des Erreurs

Stratégies de Fallback

  1. Détection d'éléments échoue → Continuer avec liste vide, utiliser matching legacy
  2. VLM indisponible → Utiliser heuristiques simples uniquement
  3. Embedding échoue → Utiliser embedding par défaut (zéros) avec flag d'avertissement
  4. Élément requis manquant → Passer en mode assisté, demander aide utilisateur

Logging et Debug

  • Chaque étape du pipeline doit logger son état
  • Les échecs de détection doivent être enregistrés avec screenshots
  • Les scores de matching doivent être tracés pour analyse
  • Mode debug avec visualisation des bounding boxes détectées

Stratégie de Test

Tests Unitaires

  1. RegionProposer: Tester détection de zones sur images synthétiques
  2. ElementCharacterizer: Vérifier extraction d'embeddings
  3. ElementClassifier: Valider classification de types connus
  4. MultiModalEmbeddingManager: Tester fusion avec poids variés
  5. EnhancedWorkflowMatcher: Vérifier matching élément vs legacy

Tests d'Intégration

  1. Pipeline complet: Screenshot → UIElements → StateEmbedding
  2. Compatibilité: Anciens workflows fonctionnent toujours
  3. Performance: Temps de traitement < 2s par écran
  4. Robustesse: Variations visuelles (thème, taille) ne cassent pas matching

Tests de Propriétés (Property-Based Testing)

À définir dans la section Correctness Properties ci-dessous.

Propriétés de Correction

Une propriété est une caractéristique ou un comportement qui devrait être vrai pour toutes les exécutions valides d'un système - essentiellement, une déclaration formelle sur ce que le système devrait faire. Les propriétés servent de pont entre les spécifications lisibles par l'humain et les garanties de correction vérifiables par machine.

Propriété 1: Complétude de Détection d'Éléments

Pour tout screenshot contenant des éléments interactifs, le système doit détecter tous les éléments interactifs présents et générer un UIElement pour chacun avec tous les champs requis (element_id, type, role, bbox, label, visual, text, properties, context, tags).

Valide: Exigences 1.1, 1.2, 1.3, 1.4, 1.5, 11.2

Propriété 2: Validité des Types d'Éléments

Pour tout UIElement détecté, le champ type doit être l'un des types valides suivants : button, text_input, dropdown, tab, checkbox, radio_button, link, ou generic_interactive.

Valide: Exigences 2.1, 2.4

Propriété 3: Présence du Score de Confiance

Pour tout UIElement créé, un score de confiance entre 0.0 et 1.0 doit être assigné et stocké dans le descripteur.

Valide: Exigences 2.3, 2.4

Propriété 4: Extraction de Description Sémantique

Pour tout UIElement analysé, une description sémantique non-vide doit être générée par le VLM et stockée dans le descripteur.

Valide: Exigences 3.1, 3.2

Propriété 5: Unicité des Identifiants d'Éléments

Pour tout ensemble d'UIElement détectés dans un même screenshot, si deux éléments ont des positions ou labels différents, alors leurs element_id doivent être différents.

Valide: Exigences 3.5, 11.1

Propriété 6: Stabilité des Identifiants

Pour tout UIElement créé deux fois avec les mêmes paramètres (app_name, centre_bbox, label_normalized), l'element_id généré doit être identique.

Valide: Exigences 11.1

Propriété 7: Structure Complète du State Embedding

Pour tout state_embedding généré, il doit contenir toutes les composantes suivantes : image_embedding, text_embedding, title_embedding, ui_embedding, et context_embedding, chacune avec un provider et un vector_id valides.

Valide: Exigences 4.1, 4.5, 12.3, 14.1

Propriété 8: Extraction de Texte Systématique

Pour tout screenshot traité, le système doit extraire le contenu textuel (liste potentiellement vide si aucun texte) et le stocker dans le champ perception.detected_text du ScreenState.

Valide: Exigences 4.2, 7.1

Propriété 9: Présence du Titre de Fenêtre

Pour tout ScreenState créé, le champ window.window_title doit être présent et non-null.

Valide: Exigences 4.3

Propriété 10: Inclusion du Contexte Workflow

Pour tout ScreenState où le contexte workflow est disponible, ce contexte doit être inclus dans le state_embedding et stocké dans le champ context.

Valide: Exigences 4.4, 8.1

Propriété 11: Normalisation des Embeddings

Pour tout state_embedding final généré, la norme L2 du vecteur doit être égale à 1.0 (±0.001 pour tolérance numérique).

Valide: Exigences 4.5, 14.4

Propriété 12: Dimension Fixe des Embeddings

Pour tout state_embedding généré, la dimension du vecteur doit être constante et égale à la dimension configurée (ex: 512).

Valide: Exigences 5.1

Propriété 13: Robustesse aux Variations Visuelles Légères

Pour tout screenshot S et sa variation légère S' (changement de couleur, taille ±10%, position ±20px), la similarité cosinus entre state_embedding(S) et state_embedding(S') doit être ≥ 0.85.

Valide: Exigences 5.3

Propriété 14: Discrimination Sémantique

Pour tout screenshot S1 et screenshot S2 sémantiquement différents (écrans différents, contenus différents), la similarité cosinus entre state_embedding(S1) et state_embedding(S2) doit être ≤ 0.70.

Valide: Exigences 5.4

Propriété 15: Association des Empreintes

Pour tout state_embedding stocké, il doit être associé à au moins un workflow_step ou un ScreenState avec un screen_state_id valide.

Valide: Exigences 5.5

Propriété 16: Vérification des Éléments Requis

Pour tout workflow avec N éléments requis, si le ScreenState actuel contient moins de N éléments correspondants, alors le score de matching doit être < 0.5 (échec).

Valide: Exigences 6.3

Propriété 17: Bonus des Éléments Optionnels

Pour tout workflow avec des éléments optionnels, le score de matching avec tous les éléments optionnels présents doit être strictement supérieur au score sans les éléments optionnels.

Valide: Exigences 6.4

Propriété 18: Feedback Détaillé sur Échec

Pour tout échec de matching de workflow, le système doit retourner un MatchResult contenant une liste non-vide de différences détectées (éléments manquants, types incorrects, etc.).

Valide: Exigences 6.5

Propriété 19: Préservation des Informations Spatiales du Texte

Pour tout texte détecté dans un screenshot, des coordonnées spatiales (bbox ou position) doivent être stockées avec le texte.

Valide: Exigences 7.2

Propriété 20: Association Texte-Élément

Pour tout UIElement contenant du texte visible, ce texte doit être présent dans le champ text.raw du descripteur d'élément.

Valide: Exigences 7.3

Propriété 21: Cohérence du Texte Extrait

Pour tout texte extrait d'un screenshot, il doit apparaître à la fois dans perception.detected_text du ScreenState ET dans les descripteurs text.raw des UIElement correspondants.

Valide: Exigences 7.5

Propriété 22: Sensibilité au Contexte

Pour tout ScreenState avec contexte C1 et ScreenState avec contexte C2 (où C1 ≠ C2), les state_embedding doivent différer (similarité cosinus < 0.95).

Valide: Exigences 8.2

Propriété 23: Robustesse en Absence de Contexte

Pour tout screenshot sans contexte workflow disponible, le système doit quand même générer un state_embedding valide avec dimension correcte et norme unitaire.

Valide: Exigences 8.4

Propriété 24: Capture du Contexte d'Enregistrement

Pour tout workflow_step stocké, si un contexte était présent pendant l'enregistrement, ce contexte doit être sauvegardé dans le champ context du step.

Valide: Exigences 8.5

Propriété 25: Compatibilité Arrière des Workflows Legacy

Pour tout workflow existant créé avant l'implémentation du système d'éléments, le système doit continuer à le traiter avec le matcher legacy et produire un résultat de matching valide.

Valide: Exigences 9.1, 9.2

Propriété 26: Routage Correct des Workflows

Pour tout workflow avec descripteurs d'éléments (has_element_descriptors = true), le système doit utiliser le matcher amélioré ; pour tout workflow sans descripteurs, le système doit utiliser le matcher legacy.

Valide: Exigences 9.2, 9.3

Propriété 27: Performance de Détection

Pour tout screenshot typique (1920x1080, 5-20 éléments), le temps de détection d'éléments doit être ≤ 2 secondes.

Valide: Exigences 10.1

Propriété 28: Performance de Génération d'Embedding

Pour tout state_embedding généré, le temps de calcul doit être ≤ 1 seconde.

Valide: Exigences 10.2

Propriété 29: Performance de Comparaison

Pour tout appel à la fonction de comparaison de state_embedding, le temps d'exécution doit être ≤ 100ms.

Valide: Exigences 10.3

Propriété 30: Efficacité du Cache VLM

Pour tout screenshot traité deux fois consécutivement, le temps de traitement de la deuxième extraction de texte VLM doit être ≤ 10% du temps de la première (cache hit).

Valide: Exigences 10.4

Propriété 31: Scalabilité des Requêtes

Pour tout base de données d'éléments contenant N éléments (où N ≤ 100,000), le temps de requête pour trouver un élément par element_id doit être ≤ 50ms.

Valide: Exigences 10.5

Propriété 32: Complétude de la Structure UIElement

Pour tout UIElement stocké, tous les champs suivants doivent être présents et non-null : element_id, type, role, bbox, label, visual (avec screenshot_path, provider, vector_id), text (avec raw, normalized, provider, vector_id), properties, context, tags.

Valide: Exigences 11.2, 11.3, 11.4

Propriété 33: Round-Trip de Sérialisation UIElement

Pour tout UIElement sérialisé en JSON puis désérialisé, l'objet résultant doit être égal à l'objet original (tous les champs identiques).

Valide: Exigences 11.5

Propriété 34: Complétude de la Structure ScreenState

Pour tout ScreenState créé, tous les champs suivants doivent être présents : screen_state_id, timestamp, session_id, window, raw, perception, ui_elements, state_embedding, context.

Valide: Exigences 12.1

Propriété 35: Stockage des Éléments Détectés

Pour tout ScreenState où des UIElement ont été détectés, ces éléments doivent être présents dans le champ ui_elements (liste non-vide).

Valide: Exigences 12.2

Propriété 36: Round-Trip de Sérialisation ScreenState

Pour tout ScreenState sérialisé en JSON puis désérialisé, tous les embeddings doivent pouvoir être reconstruits à partir des vector_id et être identiques aux embeddings originaux (distance L2 < 0.001).

Valide: Exigences 12.4, 12.5

Propriété 37: Complétude de la Caractérisation

Pour tout élément caractérisé par le pipeline, les champs suivants doivent être extraits : crop image, embedding image, texte, embedding texte, bbox.

Valide: Exigences 13.3

Propriété 38: Complétude de la Classification

Pour tout élément classifié, les champs type et role doivent être présents et non-vides.

Valide: Exigences 13.4

Propriété 39: Robustesse du Pipeline

Pour tout screenshot contenant N éléments dont 1 élément problématique (causant une erreur), le pipeline doit quand même produire N-1 UIElement valides pour les autres éléments.

Valide: Exigences 13.5

Propriété 40: Normalisation des Composantes

Pour tout state_embedding en cours de génération, chaque composante individuelle (image_embedding, text_embedding, title_embedding, ui_embedding, context_embedding) doit avoir une norme L2 = 1.0 avant fusion.

Valide: Exigences 14.2

Propriété 41: Stockage des Composantes

Pour tout state_embedding généré, toutes les composantes individuelles doivent être stockées séparément avec leurs provider et vector_id respectifs.

Valide: Exigences 14.5

Propriété 42: Compatibilité de Lecture Multi-Mode

Pour tout fichier de données créé en mode "light", "enrichi" ou "complet", le système doit pouvoir le lire quel que soit le mode actuel, sans erreur de parsing.

Valide: Exigences 15.5