Files
Geniusia_v2/.kiro/specs/workflow-graph-implementation/design.md
2026-03-05 00:20:25 +01:00

44 KiB

Document de Design - Workflow Graph Implementation

Vue d'Ensemble

Ce document décrit le design détaillé de l'implémentation de l'architecture Workflow Graph pour RPA Vision V2. Le système transforme des captures d'écran brutes en workflows sémantiques appris à travers 5 couches d'abstraction progressive.

Philosophie : "Observer → Comprendre → Apprendre → Agir"

Le système ne travaille PAS avec des coordonnées de clics, mais avec une compréhension sémantique des interfaces : types d'éléments, rôles, contexte visuel et textuel.

Architecture en 5 Couches :

RawSession (Couche 0) → ScreenState (Couche 1) → UIElement Detection (Couche 2) 
→ State Embedding (Couche 3) → Workflow Graph (Couche 4)

Référence : Ce design s'appuie sur docs/reference/ARCHITECTURE_VISION_COMPLETE.md et docs/reference/ARCHITECTURE_ENRICHISSEMENTS.md.

Architecture

Architecture Globale

┌─────────────────────────────────────────────────────────────┐
│                    Couche 4 : Workflow Graph                │
│  WorkflowNode + WorkflowEdge + Learning States              │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────────┐  │
│  │ Graph Builder│  │ Node Matcher │  │ Action Executor  │  │
│  └──────────────┘  └──────────────┘  └──────────────────┘  │
└─────────────────────────────────────────────────────────────┘
                            ↕
┌─────────────────────────────────────────────────────────────┐
│                 Couche 3 : State Embedding                  │
│  Fusion Multi-Modale (Image + Text + UI + Context)         │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────────┐  │
│  │ Fusion Engine│  │ FAISS Index  │  │ Similarity Comp  │  │
│  └──────────────┘  └──────────────┘  └──────────────────┘  │
└─────────────────────────────────────────────────────────────┘
                            ↕
┌─────────────────────────────────────────────────────────────┐
│              Couche 2 : UIElement Detection                 │
│  Détection Sémantique (Type + Rôle + Embeddings)           │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────────┐  │
│  │ VLM Detector │  │ Classifier   │  │ Embedding Gen    │  │
│  └──────────────┘  └──────────────┘  └──────────────────┘  │
└─────────────────────────────────────────────────────────────┘
                            ↕
┌─────────────────────────────────────────────────────────────┐
│                 Couche 1 : ScreenState                      │
│  Analyse Multi-Modale (4 Niveaux)                          │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────────┐  │
│  │ Raw Capture  │  │ Perception   │  │ Semantic + Ctx   │  │
│  └──────────────┘  └──────────────┘  └──────────────────┘  │
└─────────────────────────────────────────────────────────────┘
                            ↕
┌─────────────────────────────────────────────────────────────┐
│                  Couche 0 : RawSession                      │
│  Capture Brute (Events + Screenshots)                      │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────────┐  │
│  │ Event Logger │  │ Screenshot   │  │ Session Manager  │  │
│  └──────────────┘  └──────────────┘  └──────────────────┘  │
└─────────────────────────────────────────────────────────────┘

Structure des Répertoires

geniusia2/
├── core/
│   ├── models/
│   │   ├── raw_session.py          # Couche 0 : RawSession
│   │   ├── screen_state.py         # Couche 1 : ScreenState
│   │   ├── ui_element.py           # Couche 2 : UIElement
│   │   ├── state_embedding.py      # Couche 3 : State Embedding
│   │   └── workflow_graph.py       # Couche 4 : Workflow Graph
│   ├── capture/
│   │   ├── event_capture.py        # Capture événements
│   │   └── screenshot_capture.py   # Capture screenshots
│   ├── detection/
│   │   ├── ui_detector.py          # Détection UI avec VLM
│   │   └── text_detector.py        # Détection texte
│   ├── embedding/
│   │   ├── fusion_engine.py        # Fusion multi-modale
│   │   ├── faiss_manager.py        # Gestion index FAISS
│   │   └── similarity.py           # Calculs de similarité
│   ├── graph/
│   │   ├── graph_builder.py        # Construction graphes
│   │   ├── node_matcher.py         # Matching nodes
│   │   ├── action_executor.py      # Exécution actions
│   │   └── learning_manager.py     # Gestion états apprentissage
│   └── persistence/
│       ├── json_serializer.py      # Sérialisation JSON
│       └── storage_manager.py      # Gestion stockage
└── data/
    ├── sessions/                   # RawSessions
    ├── screen_states/              # ScreenStates
    ├── embeddings/                 # Embeddings (.npy)
    ├── faiss_index/                # Index FAISS
    └── workflows/                  # Workflow Graphs

Composants et Interfaces

Couche 0 : RawSession

Responsabilité : Capture fidèle de tous les événements utilisateur avec screenshots.

Classe Principale : RawSession

@dataclass
class RawSession:
    schema_version: str = "rawsession_v1"
    session_id: str
    agent_version: str
    environment: Dict[str, Any]
    user: Dict[str, str]
    context: Dict[str, str]
    started_at: datetime
    ended_at: Optional[datetime]
    events: List[Event]
    screenshots: List[Screenshot]
    
    def add_event(self, event: Event) -> None:
        """Ajouter un événement à la session"""
        
    def add_screenshot(self, screenshot: Screenshot) -> None:
        """Ajouter un screenshot à la session"""
        
    def to_json(self) -> Dict[str, Any]:
        """Sérialiser en JSON"""
        
    @classmethod
    def from_json(cls, data: Dict[str, Any]) -> 'RawSession':
        """Désérialiser depuis JSON"""

Classe Event :

@dataclass
class Event:
    t: float  # Timestamp relatif en secondes
    type: str  # mouse_click, key_press, etc.
    window: WindowContext
    screenshot_id: Optional[str]
    # Champs spécifiques selon type
    data: Dict[str, Any]

Couche 1 : ScreenState

Responsabilité : Représentation structurée d'un écran à 4 niveaux d'abstraction.

Classe Principale : ScreenState

@dataclass
class ScreenState:
    screen_state_id: str
    timestamp: datetime
    session_id: str
    window: WindowContext
    
    # Niveau 1 : Raw
    raw: RawLevel
    
    # Niveau 2 : Perception
    perception: PerceptionLevel
    
    # Niveau 3 : Sémantique UI
    ui_elements: List[UIElement]
    
    # Niveau 4 : Contexte Métier
    context: ContextLevel
    
    metadata: Dict[str, Any]
    
    def to_json(self) -> Dict[str, Any]:
        """Sérialiser en JSON"""
        
    @classmethod
    def from_json(cls, data: Dict[str, Any]) -> 'ScreenState':
        """Désérialiser depuis JSON"""

Niveaux :

@dataclass
class RawLevel:
    screenshot_path: str
    capture_method: str
    file_size_bytes: int

@dataclass
class PerceptionLevel:
    embedding: EmbeddingRef
    detected_text: List[str]
    text_detection_method: str
    confidence_avg: float

@dataclass
class ContextLevel:
    current_workflow_candidate: Optional[str]
    workflow_step: Optional[int]
    user_id: str
    tags: List[str]
    business_variables: Dict[str, Any]

Couche 2 : UIElement Detection

Responsabilité : Détection sémantique des éléments UI avec types, rôles et embeddings.

Classe Principale : UIElement

@dataclass
class UIElement:
    element_id: str
    type: str  # button, text_input, checkbox, etc.
    role: str  # primary_action, cancel, form_input, etc.
    bbox: Tuple[int, int, int, int]
    center: Tuple[int, int]
    
    label: str
    label_confidence: float
    
    embeddings: UIElementEmbeddings
    visual_features: VisualFeatures
    
    tags: List[str]
    confidence: float
    
    metadata: Dict[str, Any]
    
    def to_json(self) -> Dict[str, Any]:
        """Sérialiser en JSON"""

Embeddings Duaux :

@dataclass
class UIElementEmbeddings:
    image: EmbeddingRef  # Embedding de l'image croppée
    text: EmbeddingRef   # Embedding du texte détecté

Détecteur UI :

class UIDetector:
    def __init__(self, vlm_model: str, clip_model: str):
        self.vlm = VLMClient(vlm_model)
        self.clip = CLIPEmbedder(clip_model)
    
    def detect_elements(self, screenshot: np.ndarray, 
                       window_context: WindowContext) -> List[UIElement]:
        """Détecter tous les éléments UI dans un screenshot"""
        # 1. Proposer régions d'intérêt avec VLM
        # 2. Caractériser chaque élément (crop, OCR, embeddings)
        # 3. Classifier type et rôle
        # 4. Retourner liste d'UIElements

Couche 3 : State Embedding

Responsabilité : Fusion multi-modale en vecteur unique (fingerprint d'écran).

Classe Principale : StateEmbedding

@dataclass
class StateEmbedding:
    embedding_id: str
    vector_id: str  # Chemin vers .npy
    dimensions: int
    fusion_method: str  # "weighted" ou "concat_projection"
    
    components: Dict[str, EmbeddingComponent]
    metadata: Dict[str, Any]
    
    def get_vector(self) -> np.ndarray:
        """Charger le vecteur depuis le fichier"""
        
    def compute_similarity(self, other: 'StateEmbedding') -> float:
        """Calculer similarité cosinus avec autre embedding"""

Moteur de Fusion :

class FusionEngine:
    def __init__(self, method: str = "weighted", 
                 weights: Optional[Dict[str, float]] = None):
        self.method = method
        self.weights = weights or {
            "image": 0.5,
            "text": 0.3,
            "title": 0.1,
            "ui": 0.1
        }
    
    def fuse(self, 
             img_emb: np.ndarray,
             text_emb: np.ndarray,
             title_emb: np.ndarray,
             ui_emb: np.ndarray) -> np.ndarray:
        """Fusionner tous les embeddings en un seul vecteur"""
        if self.method == "weighted":
            return self._weighted_fusion(img_emb, text_emb, title_emb, ui_emb)
        elif self.method == "concat_projection":
            return self._concat_projection(img_emb, text_emb, title_emb, ui_emb)
    
    def _weighted_fusion(self, img_emb, text_emb, title_emb, ui_emb) -> np.ndarray:
        """Fusion pondérée simple"""
        fused = (
            self.weights["image"] * normalize(img_emb) +
            self.weights["text"] * normalize(text_emb) +
            self.weights["title"] * normalize(title_emb) +
            self.weights["ui"] * normalize(ui_emb)
        )
        return normalize(fused)

Gestionnaire FAISS :

class FAISSManager:
    def __init__(self, index_path: str, dimension: int = 512):
        self.index_path = index_path
        self.dimension = dimension
        self.index = self._load_or_create_index()
        self.metadata_store: Dict[int, Dict[str, Any]] = {}
    
    def add_embedding(self, embedding: np.ndarray, 
                     metadata: Dict[str, Any]) -> int:
        """Ajouter un embedding à l'index"""
        idx = self.index.ntotal
        self.index.add(embedding.reshape(1, -1))
        self.metadata_store[idx] = metadata
        return idx
    
    def search_similar(self, query: np.ndarray, 
                      k: int = 5) -> List[SearchResult]:
        """Chercher les k plus proches voisins"""
        distances, indices = self.index.search(query.reshape(1, -1), k)
        results = []
        for dist, idx in zip(distances[0], indices[0]):
            results.append(SearchResult(
                id=int(idx),
                distance=float(dist),
                similarity=1.0 - float(dist),  # Cosine similarity
                metadata=self.metadata_store.get(int(idx), {})
            ))
        return results

Couche 4 : Workflow Graph

Responsabilité : Modélisation des workflows en graphes avec apprentissage progressif.

Classe WorkflowNode :

@dataclass
class WorkflowNode:
    node_id: str
    label: str
    description: str
    screen_template: ScreenTemplate
    metadata: Dict[str, Any]
    
    def matches(self, screen_state: ScreenState, 
                state_embedding: StateEmbedding) -> Tuple[bool, float]:
        """Vérifier si un ScreenState correspond à ce node"""
        # 1. Vérifier contraintes fenêtre
        # 2. Vérifier texte requis
        # 3. Vérifier éléments UI requis
        # 4. Vérifier similarité embedding
        # Retourner (match, confidence)

@dataclass
class ScreenTemplate:
    window: WindowConstraints
    required_text_any: List[str]
    required_ui_elements: List[UIElementConstraint]
    embedding_prototype: EmbeddingPrototype
    optional_elements: List[UIElementConstraint]

@dataclass
class EmbeddingPrototype:
    provider: str
    vector_id: str
    min_cosine_similarity: float
    sample_count: int

Classe WorkflowEdge :

@dataclass
class WorkflowEdge:
    edge_id: str
    from_node: str
    to_node: str
    action: Action
    constraints: EdgeConstraints
    post_conditions: PostConditions
    stats: EdgeStats
    metadata: Dict[str, Any]
    
    def can_execute(self, current_state: ScreenState) -> Tuple[bool, str]:
        """Vérifier si l'edge peut être exécuté"""
        # Vérifier pre-conditions
        
    def execute(self, executor: ActionExecutor) -> ExecutionResult:
        """Exécuter l'action de cet edge"""

@dataclass
class Action:
    type: str  # mouse_click, key_press, text_input, compound
    target: TargetSpec
    parameters: Dict[str, Any]

@dataclass
class TargetSpec:
    role: str  # Rôle sémantique de l'élément cible
    selection_policy: str  # first, last, by_similarity
    fallback_strategy: str  # visual_similarity, position

Classe Workflow :

@dataclass
class Workflow:
    workflow_id: str
    name: str
    description: str
    version: int
    
    learning_state: str  # OBSERVATION, COACHING, AUTO_CANDIDATE, AUTO_CONFIRMÉ
    
    created_at: datetime
    updated_at: datetime
    
    entry_nodes: List[str]
    end_nodes: List[str]
    
    nodes: List[WorkflowNode]
    edges: List[WorkflowEdge]
    
    safety_rules: SafetyRules
    stats: WorkflowStats
    learning: LearningConfig
    metadata: Dict[str, Any]
    
    def get_node(self, node_id: str) -> Optional[WorkflowNode]:
        """Récupérer un node par ID"""
        
    def get_outgoing_edges(self, node_id: str) -> List[WorkflowEdge]:
        """Récupérer tous les edges sortants d'un node"""
        
    def to_json(self) -> Dict[str, Any]:
        """Sérialiser en JSON"""

Graph Builder

Responsabilité : Construire automatiquement des Workflow Graphs depuis des RawSessions.

class GraphBuilder:
    def __init__(self, 
                 faiss_manager: FAISSManager,
                 fusion_engine: FusionEngine,
                 ui_detector: UIDetector):
        self.faiss = faiss_manager
        self.fusion = fusion_engine
        self.ui_detector = ui_detector
    
    def build_from_session(self, session: RawSession) -> Optional[Workflow]:
        """Construire un workflow depuis une session"""
        # 1. Créer ScreenStates pour tous les screenshots
        screen_states = self._create_screen_states(session)
        
        # 2. Calculer State Embeddings
        embeddings = self._compute_embeddings(screen_states)
        
        # 3. Détecter patterns répétés
        patterns = self._detect_patterns(screen_states, embeddings)
        
        # 4. Construire nodes et edges
        if patterns:
            return self._build_workflow(patterns)
        return None
    
    def _detect_patterns(self, 
                        screen_states: List[ScreenState],
                        embeddings: List[StateEmbedding]) -> List[Pattern]:
        """Détecter séquences répétées"""
        # Utiliser clustering sur embeddings
        # Identifier transitions récurrentes
        # Retourner patterns détectés

Node Matcher

Responsabilité : Matcher un ScreenState actuel contre les WorkflowNodes existants.

class NodeMatcher:
    def __init__(self, 
                 faiss_manager: FAISSManager,
                 fusion_engine: FusionEngine):
        self.faiss = faiss_manager
        self.fusion = fusion_engine
    
    def match(self, 
             screen_state: ScreenState,
             workflow: Workflow) -> Optional[NodeMatch]:
        """Trouver le node correspondant au ScreenState actuel"""
        # 1. Calculer State Embedding
        state_emb = self._compute_state_embedding(screen_state)
        
        # 2. Chercher dans FAISS les prototypes similaires
        candidates = self.faiss.search_similar(state_emb.get_vector(), k=5)
        
        # 3. Filtrer par workflow_id
        candidates = [c for c in candidates 
                     if c.metadata.get('workflow_id') == workflow.workflow_id]
        
        # 4. Valider contraintes pour chaque candidat
        for candidate in candidates:
            node = workflow.get_node(candidate.metadata['node_id'])
            if node:
                matches, confidence = node.matches(screen_state, state_emb)
                if matches:
                    return NodeMatch(
                        node=node,
                        confidence=confidence,
                        embedding_similarity=candidate.similarity
                    )
        
        return None

@dataclass
class NodeMatch:
    node: WorkflowNode
    confidence: float
    embedding_similarity: float

Action Executor

Responsabilité : Exécuter les actions définies dans WorkflowEdges.

class ActionExecutor:
    def __init__(self, input_controller):
        self.input = input_controller
    
    def execute_edge(self, 
                    edge: WorkflowEdge,
                    current_state: ScreenState) -> ExecutionResult:
        """Exécuter l'action d'un edge"""
        # 1. Vérifier pre-conditions
        can_execute, reason = edge.can_execute(current_state)
        if not can_execute:
            return ExecutionResult(success=False, reason=reason)
        
        # 2. Trouver élément cible par rôle
        target_element = self._find_target_element(
            edge.action.target, 
            current_state
        )
        if not target_element:
            return ExecutionResult(success=False, 
                                 reason="Target element not found")
        
        # 3. Exécuter action
        if edge.action.type == "mouse_click":
            self._execute_click(target_element, edge.action.parameters)
        elif edge.action.type == "text_input":
            self._execute_text_input(target_element, edge.action.parameters)
        elif edge.action.type == "compound":
            self._execute_compound(edge.action, current_state)
        
        # 4. Attendre post-conditions
        success = self._wait_for_postconditions(edge.post_conditions)
        
        return ExecutionResult(success=success)
    
    def _find_target_element(self, 
                            target: TargetSpec,
                            state: ScreenState) -> Optional[UIElement]:
        """Trouver élément UI par rôle sémantique"""
        candidates = [el for el in state.ui_elements 
                     if el.role == target.role]
        
        if not candidates:
            return None
        
        if target.selection_policy == "first":
            return candidates[0]
        elif target.selection_policy == "last":
            return candidates[-1]
        elif target.selection_policy == "by_similarity":
            # Utiliser embedding similarity
            return self._select_by_similarity(candidates, target)
        
        return candidates[0]

@dataclass
class ExecutionResult:
    success: bool
    reason: Optional[str] = None
    execution_time_ms: Optional[float] = None

Learning Manager

Responsabilité : Gérer les états d'apprentissage et transitions.

class LearningManager:
    def __init__(self):
        self.workflows: Dict[str, Workflow] = {}
    
    def update_workflow_stats(self, 
                             workflow_id: str,
                             execution_result: ExecutionResult) -> None:
        """Mettre à jour statistiques après exécution"""
        workflow = self.workflows[workflow_id]
        workflow.stats.total_executions += 1
        
        if execution_result.success:
            workflow.stats.success_count += 1
        else:
            workflow.stats.failure_count += 1
        
        # Vérifier si transition d'état nécessaire
        self._check_state_transition(workflow)
    
    def _check_state_transition(self, workflow: Workflow) -> None:
        """Vérifier et effectuer transitions d'état si nécessaire"""
        current_state = workflow.learning_state
        
        if current_state == "OBSERVATION":
            if self._can_transition_to_coaching(workflow):
                self._transition_to(workflow, "COACHING")
        
        elif current_state == "COACHING":
            if self._can_transition_to_auto_candidate(workflow):
                self._transition_to(workflow, "AUTO_CANDIDATE")
        
        elif current_state == "AUTO_CANDIDATE":
            if self._can_transition_to_auto_confirmed(workflow):
                # Nécessite validation utilisateur
                self._request_user_approval(workflow)
        
        elif current_state == "AUTO_CONFIRMÉ":
            if self._should_rollback(workflow):
                self._transition_to(workflow, "COACHING")
    
    def _can_transition_to_coaching(self, workflow: Workflow) -> bool:
        """Vérifier critères pour OBSERVATION → COACHING"""
        return (workflow.stats.observed_runs >= 5 and
                workflow.stats.avg_similarity >= 0.90)
    
    def _can_transition_to_auto_candidate(self, workflow: Workflow) -> bool:
        """Vérifier critères pour COACHING → AUTO_CANDIDATE"""
        return (workflow.stats.assist_runs >= 10 and
                workflow.stats.success_rate >= 0.90)
    
    def _can_transition_to_auto_confirmed(self, workflow: Workflow) -> bool:
        """Vérifier critères pour AUTO_CANDIDATE → AUTO_CONFIRMÉ"""
        return (workflow.stats.auto_candidate_runs >= 20 and
                workflow.stats.success_rate >= 0.95)
    
    def _should_rollback(self, workflow: Workflow) -> bool:
        """Vérifier si rollback nécessaire"""
        return workflow.stats.recent_confidence < 0.90

Modèles de Données

Format JSON Complet

RawSession :

{
  "schema_version": "rawsession_v1",
  "session_id": "sess_2025-11-22T10-15-00_user1",
  "agent_version": "0.2.0",
  "environment": {
    "platform": "linux",
    "hostname": "dev-machine",
    "screen": {"primary_resolution": [1920, 1080]}
  },
  "user": {"id": "user1", "label": "Developer User"},
  "context": {"customer": "Demo", "training_label": "workflow_test"},
  "started_at": "2025-11-22T10:15:00Z",
  "ended_at": "2025-11-22T10:30:00Z",
  "events": [
    {
      "t": 0.523,
      "type": "mouse_click",
      "button": "left",
      "pos": [800, 400],
      "window": {"title": "App", "app_name": "app.exe"},
      "screenshot_id": "shot_0001"
    }
  ],
  "screenshots": [
    {
      "screenshot_id": "shot_0001",
      "relative_path": "shots/shot_0001.png",
      "captured_at": "2025-11-22T10:15:00.523Z"
    }
  ]
}

ScreenState :

{
  "screen_state_id": "screen_2025-11-22T10-15-32.123Z",
  "timestamp": "2025-11-22T10:15:32.123Z",
  "session_id": "sess_2025-11-22T10-15-00_user1",
  "window": {
    "app_name": "app",
    "window_title": "Main Window",
    "screen_resolution": [1920, 1080]
  },
  "raw": {
    "screenshot_path": "data/screens/2025-11-22/10-15-32.png",
    "capture_method": "mss",
    "file_size_bytes": 245678
  },
  "perception": {
    "embedding": {
      "provider": "openclip_ViT-B-32",
      "vector_id": "data/embeddings/screens/screen_2025-11-22T10-15-32.123Z.npy",
      "dimensions": 512
    },
    "detected_text": ["Button", "Input", "Submit"],
    "text_detection_method": "qwen_vl",
    "confidence_avg": 0.92
  },
  "ui_elements": [
    {
      "element_id": "el_btn_001",
      "type": "button",
      "role": "primary_action",
      "bbox": [100, 200, 200, 240],
      "center": [150, 220],
      "label": "Submit",
      "label_confidence": 0.96,
      "embeddings": {
        "image": {
          "provider": "openclip_ViT-B-32",
          "vector_id": "data/embeddings/elements/el_btn_001_img.npy",
          "dimensions": 512
        },
        "text": {
          "provider": "openclip_ViT-B-32",
          "vector_id": "data/embeddings/elements/el_btn_001_txt.npy",
          "dimensions": 512
        }
      },
      "visual_features": {
        "dominant_color": "#4CAF50",
        "has_icon": false,
        "shape": "rectangle",
        "size_category": "medium"
      },
      "tags": ["action", "primary"],
      "confidence": 0.94
    }
  ],
  "context": {
    "current_workflow_candidate": null,
    "workflow_step": null,
    "user_id": "user1",
    "tags": ["demo"],
    "business_variables": {}
  },
  "metadata": {
    "processing_time_ms": 245,
    "ui_elements_count": 5
  }
}

Workflow Graph :

{
  "workflow_id": "WF_demo_workflow",
  "name": "Demo Workflow",
  "description": "Simple demo workflow for testing",
  "version": 1,
  "learning_state": "OBSERVATION",
  "created_at": "2025-11-22T10:45:00Z",
  "updated_at": "2025-11-22T10:45:00Z",
  "entry_nodes": ["N1_start"],
  "end_nodes": ["N3_end"],
  "nodes": [
    {
      "node_id": "N1_start",
      "label": "Start Screen",
      "description": "Initial screen with form",
      "screen_template": {
        "window": {
          "app_name_any_of": ["app"],
          "title_contains_any_of": ["Main"]
        },
        "required_text_any": ["Submit", "Input"],
        "required_ui_elements": [
          {
            "role": "primary_action",
            "type_any_of": ["button"],
            "min_count": 1
          }
        ],
        "embedding_prototype": {
          "provider": "openclip_ViT-B-32",
          "vector_id": "data/embeddings/workflows/WF_demo/N1_prototype.npy",
          "min_cosine_similarity": 0.85,
          "sample_count": 5
        }
      },
      "metadata": {
        "created_at": "2025-11-22T10:45:00Z",
        "observation_count": 5
      }
    }
  ],
  "edges": [
    {
      "edge_id": "E1_submit",
      "from_node": "N1_start",
      "to_node": "N2_processing",
      "action": {
        "type": "mouse_click",
        "target": {
          "role": "primary_action",
          "selection_policy": "first",
          "fallback_strategy": "visual_similarity"
        },
        "parameters": {
          "click_offset": [0, 0],
          "wait_after_ms": 500
        }
      },
      "constraints": {
        "max_delay_seconds": 5,
        "pre_conditions": ["element:primary_action_visible"],
        "post_conditions": ["window_title_changed"]
      },
      "post_conditions": {
        "expected_node": "N2_processing",
        "min_similarity": 0.85,
        "timeout_seconds": 5
      },
      "stats": {
        "manual_executions": 5,
        "assist_executions": 0,
        "auto_executions": 0,
        "success_count": 5,
        "failure_count": 0,
        "avg_execution_time_ms": 1200
      }
    }
  ],
  "safety_rules": {
    "forbidden_text_clicks": ["Delete", "Remove"],
    "forbidden_roles": ["delete_action"],
    "require_confirmation_for": ["irreversible_action"]
  },
  "stats": {
    "observed_runs": 5,
    "assist_runs": 0,
    "auto_candidate_runs": 0,
    "auto_confirmed_runs": 0,
    "success_rate_overall": 1.0,
    "avg_duration_seconds": 30.5
  },
  "learning": {
    "state": "OBSERVATION",
    "thresholds": {
      "min_observed_runs_for_coaching": 5,
      "min_assist_runs_for_auto_candidate": 10,
      "min_auto_candidate_runs_for_auto_confirmed": 20
    }
  }
}

Gestion des Erreurs

Échecs de Matching

Scénario : Aucun node ne correspond au ScreenState actuel.

Gestion :

  1. Logger le ScreenState non matché avec screenshot
  2. Calculer similarité avec tous les nodes existants
  3. Si similarité proche (0.75-0.84), suggérer mise à jour du node
  4. Si similarité faible (<0.75), suggérer création d'un nouveau node
  5. Notifier l'utilisateur et mettre en pause l'exécution

Échecs de Détection UI

Scénario : Élément cible non trouvé par rôle.

Gestion :

  1. Logger l'échec avec contexte (node, edge, rôle recherché)
  2. Essayer stratégie de fallback (visual similarity)
  3. Si fallback échoue, essayer position approximative
  4. Si tout échoue, notifier utilisateur et demander correction
  5. Mettre à jour le template du node avec feedback

Violations de Post-Conditions

Scénario : Post-conditions non satisfaites après exécution.

Gestion :

  1. Logger la violation avec détails (attendu vs réel)
  2. Attendre timeout configuré
  3. Si toujours pas satisfait, marquer exécution comme échec
  4. Incrémenter compteur d'échecs pour cet edge
  5. Si échecs répétés (>3), marquer edge comme problématique

Changements d'UI Détectés

Scénario : Similarité d'embedding chute significativement.

Gestion :

  1. Détecter changement (similarité < 0.70 vs prototype)
  2. Capturer nouveau screenshot pour analyse
  3. Mettre en pause l'exécution automatique
  4. Notifier utilisateur du changement détecté
  5. Proposer ré-apprentissage du node affecté

Stratégie de Test

Tests Unitaires

Composants à Tester :

  1. RawSession :

    • Sérialisation/désérialisation JSON
    • Ajout d'événements et screenshots
    • Validation de schéma
  2. ScreenState :

    • Création des 4 niveaux
    • Sérialisation/désérialisation JSON
    • Validation de structure
  3. UIElement :

    • Détection de types et rôles
    • Génération d'embeddings duaux
    • Calcul de features visuelles
  4. StateEmbedding :

    • Fusion pondérée
    • Fusion par concaténation
    • Calcul de similarité cosinus
  5. FAISSManager :

    • Ajout d'embeddings
    • Recherche de similarité
    • Sauvegarde/chargement d'index
  6. WorkflowNode :

    • Matching avec ScreenState
    • Validation de contraintes
    • Calcul de confiance
  7. WorkflowEdge :

    • Validation de pre-conditions
    • Exécution d'actions
    • Vérification de post-conditions
  8. LearningManager :

    • Transitions d'états
    • Calcul de métriques
    • Détection de rollback

Framework : pytest avec fixtures

Tests d'Intégration

Scénarios :

  1. Pipeline Complet RawSession → Workflow :

    • Capturer session
    • Créer ScreenStates
    • Détecter UI elements
    • Calculer embeddings
    • Construire workflow graph
    • Vérifier structure du graphe
  2. Matching et Exécution :

    • Charger workflow existant
    • Matcher ScreenState actuel
    • Trouver edge sortant
    • Exécuter action
    • Vérifier transition
  3. Apprentissage Progressif :

    • Simuler 5 observations → COACHING
    • Simuler 10 assistances → AUTO_CANDIDATE
    • Simuler 20 exécutions → AUTO_CONFIRMÉ
    • Vérifier transitions
  4. Gestion d'Erreurs :

    • Simuler échec de matching
    • Simuler élément non trouvé
    • Simuler violation post-conditions
    • Vérifier récupération

Tests de Performance

Métriques Cibles :

Opération Temps Cible Méthode de Test
Compute State Embedding < 100ms pytest-benchmark
FAISS Search < 50ms pytest-benchmark
UI Detection < 200ms pytest-benchmark
Action Execution < 50ms pytest-benchmark
End-to-End Processing < 400ms pytest-benchmark

Outils : pytest-benchmark, cProfile

Tests de Bout en Bout

Workflow de Test :

  1. Apprentissage Complet :

    • Capturer 5 sessions d'un workflow simple
    • Vérifier construction automatique du graphe
    • Vérifier transition OBSERVATION → COACHING
    • Exécuter en mode assisté 10 fois
    • Vérifier transition COACHING → AUTO_CANDIDATE
  2. Robustesse UI :

    • Apprendre workflow sur UI version 1
    • Modifier légèrement UI (couleurs, positions)
    • Vérifier que matching fonctionne toujours
    • Modifier significativement UI
    • Vérifier détection de changement
  3. Multi-Workflows :

    • Apprendre 3 workflows différents
    • Vérifier isolation des workflows
    • Vérifier matching correct pour chaque workflow
    • Vérifier pas de confusion entre workflows

Correctness Properties

A property is a characteristic or behavior that should hold true across all valid executions of a system-essentially, a formal statement about what the system should do. Properties serve as the bridge between human-readable specifications and machine-verifiable correctness guarantees.

Property 1: RawSession Serialization Round Trip

For any valid RawSession object, serializing to JSON then deserializing should produce an equivalent RawSession with all events and screenshots preserved.

Validates: Requirements 1.4, 1.5

Property 2: ScreenState Multi-Level Consistency

For any ScreenState, all 4 levels (Raw, Perception, Sémantique UI, Contexte) should reference the same screenshot and timestamp.

Validates: Requirements 2.1, 2.2, 2.3, 2.4, 2.5

Property 3: UIElement Detection Confidence Bounds

For any detected UIElement, the confidence score should be between 0.0 and 1.0, and elements with confidence below threshold should not be included in results.

Validates: Requirements 3.6

Property 4: State Embedding Normalization

For any State Embedding vector, the L2 norm should be 1.0 (normalized vector).

Validates: Requirements 4.6

Property 5: State Embedding Similarity Symmetry

For any two State Embeddings A and B, similarity(A, B) should equal similarity(B, A).

Validates: Requirements 4.7

Property 6: State Embedding Similarity Bounds

For any two State Embeddings, the cosine similarity should be between -1.0 and 1.0.

Validates: Requirements 4.7

Property 7: WorkflowNode Matching Consistency

For any WorkflowNode and ScreenState, if the node matches with confidence C1, then matching again immediately should return the same confidence C1 (deterministic).

Validates: Requirements 9.1, 9.2, 9.3, 9.4, 9.5, 9.6

Property 8: WorkflowEdge Pre-Condition Validation

For any WorkflowEdge, if pre-conditions are not satisfied, execution should not proceed and should return failure.

Validates: Requirements 10.5

Property 9: Learning State Monotonic Progression

For any Workflow in learning state S, transitioning to state S' should only happen if S' is the next state in the progression (OBSERVATION → COACHING → AUTO_CANDIDATE → AUTO_CONFIRMÉ), except for rollback to COACHING.

Validates: Requirements 8.1, 8.2, 8.3, 8.4

Property 10: Learning State Rollback Condition

For any Workflow in AUTO_CONFIRMÉ state, if confidence drops below 0.90, the system should rollback to COACHING state.

Validates: Requirements 8.6

Property 11: FAISS Index Consistency

For any embedding added to FAISS index with metadata M, searching for that exact embedding should return it as the top result with metadata M.

Validates: Requirements 4.8, 12.3, 12.6

Property 12: Workflow Graph Structural Validity

For any Workflow Graph, all edges should reference existing nodes (no dangling references).

Validates: Requirements 7.2

Property 13: UIElement Role Uniqueness Per Type

For any ScreenState, if multiple UIElements have the same role, they should have different element_ids.

Validates: Requirements 3.3

Property 14: Embedding Prototype Sample Count

For any WorkflowNode with embedding prototype, the sample_count should be at least 1 and the prototype vector should exist.

Validates: Requirements 5.4

Property 15: Action Execution Timeout

For any WorkflowEdge execution, if post-conditions are not satisfied within timeout_seconds, the execution should be marked as failed.

Validates: Requirements 10.6

Property 16: Pattern Detection Minimum Repetitions

For any detected workflow pattern, it should have been observed at least 3 times before being proposed as a Workflow Graph.

Validates: Requirements 11.7

Property 17: State Embedding Component Weights Sum

For any weighted fusion configuration, the sum of all component weights (image + text + title + ui) should equal 1.0.

Validates: Requirements 4.5

Property 18: Workflow JSON Serialization Round Trip

For any valid Workflow Graph, serializing to JSON then deserializing should produce an equivalent Workflow with all nodes and edges preserved.

Validates: Requirements 7.6, 12.4, 12.5

Property 19: Performance Constraint - State Embedding

For any ScreenState, computing the State Embedding should complete in less than 100ms.

Validates: Requirements 15.1

Property 20: Performance Constraint - End-to-End

For any ScreenState processing (detection + embedding + matching), the total time should be less than 400ms.

Validates: Requirements 15.5

Considérations de Sécurité

Validation des Données

  • Tous les JSON chargés doivent être validés contre leur schéma
  • Les embeddings chargés doivent avoir les dimensions attendues
  • Les workflow_ids et node_ids doivent être validés (format, unicité)

Isolation des Workflows

  • Chaque workflow doit avoir son propre espace dans FAISS
  • Les embeddings de différents workflows ne doivent pas interférer
  • Les métadonnées doivent inclure workflow_id pour filtrage

Safety Rules

  • Les actions interdites (forbidden_text_clicks, forbidden_roles) doivent être bloquées
  • Les actions irréversibles doivent demander confirmation
  • Les rollbacks doivent être possibles pour les 3 dernières actions

Logging et Audit

  • Toutes les transitions d'état doivent être loggées
  • Tous les échecs d'exécution doivent être loggées avec contexte
  • Les changements d'UI détectés doivent être loggées avec screenshots

Optimisation des Performances

Embeddings

  • Utiliser batch processing pour calculer plusieurs embeddings
  • Mettre en cache les embeddings de prototypes
  • Utiliser quantification FP16 pour modèles CLIP

FAISS

  • Utiliser index IVF pour grands ensembles (>10k embeddings)
  • Optimiser périodiquement l'index (compactage)
  • Utiliser GPU si disponible pour recherche

UI Detection

  • Limiter la résolution des screenshots (max 1920x1080)
  • Utiliser ROI detection pour réduire zone de traitement
  • Mettre en cache les résultats de détection pour frames similaires

Workflow Matching

  • Pré-filtrer les candidats par window context
  • Utiliser early stopping si confiance très élevée (>0.95)
  • Mettre en cache le dernier node matché

Considérations de Déploiement

Dépendances

  • Python 3.9+
  • PyTorch 2.0+
  • OpenCLIP
  • FAISS (CPU ou GPU)
  • Transformers (Hugging Face)
  • NumPy, Pillow
  • pytest, pytest-benchmark

Structure de Données

data/
├── sessions/
│   └── YYYY-MM-DD/
│       └── sess_*.json
├── screen_states/
│   └── YYYY-MM-DD/
│       └── screen_*.json
├── embeddings/
│   ├── screens/
│   │   └── *.npy
│   ├── elements/
│   │   └── *.npy
│   └── states/
│       └── *.npy
├── faiss_index/
│   ├── index.faiss
│   └── metadata.json
└── workflows/
    └── WF_*/
        ├── workflow.json
        └── prototypes/
            └── *.npy

Configuration

CONFIG = {
    "models": {
        "clip": "ViT-B-32",
        "vlm": "qwen2.5-vl:3b"
    },
    "embedding": {
        "dimension": 512,
        "fusion_method": "weighted",
        "weights": {
            "image": 0.5,
            "text": 0.3,
            "title": 0.1,
            "ui": 0.1
        }
    },
    "matching": {
        "min_similarity": 0.85,
        "faiss_k": 5
    },
    "learning": {
        "observation_threshold": 5,
        "coaching_threshold": 10,
        "auto_candidate_threshold": 20,
        "min_success_rate": 0.90,
        "rollback_confidence": 0.90
    },
    "performance": {
        "max_embedding_time_ms": 100,
        "max_detection_time_ms": 200,
        "max_total_time_ms": 400
    }
}

Plan d'Implémentation

Phase 1 : Fondations (Semaines 1-2)

Objectif : Structures de données et sérialisation

  • Implémenter classes de base (RawSession, ScreenState, UIElement, etc.)
  • Implémenter sérialisation/désérialisation JSON
  • Tests unitaires sur structures
  • Validation de schémas

Livrables :

  • geniusia2/core/models/*.py
  • Tests unitaires complets

Phase 2 : Embeddings et FAISS (Semaines 3-4)

Objectif : Système d'embeddings fonctionnel

  • Implémenter FusionEngine
  • Implémenter FAISSManager
  • Implémenter calculs de similarité
  • Tests de performance

Livrables :

  • geniusia2/core/embedding/*.py
  • Benchmarks de performance

Phase 3 : UI Detection (Semaines 5-6)

Objectif : Détection sémantique d'éléments UI

  • Intégrer VLM pour détection
  • Implémenter classification type/rôle
  • Générer embeddings duaux
  • Tests avec screenshots réels

Livrables :

  • geniusia2/core/detection/*.py
  • Dataset de test avec screenshots

Phase 4 : Workflow Graph (Semaines 7-9)

Objectif : Construction et matching de graphes

  • Implémenter WorkflowNode et WorkflowEdge
  • Implémenter GraphBuilder
  • Implémenter NodeMatcher
  • Tests d'intégration

Livrables :

  • geniusia2/core/graph/*.py
  • Tests d'intégration complets

Phase 5 : Exécution et Apprentissage (Semaines 10-12)

Objectif : Exécution d'actions et états d'apprentissage

  • Implémenter ActionExecutor
  • Implémenter LearningManager
  • Implémenter transitions d'états
  • Tests end-to-end

Livrables :

  • geniusia2/core/graph/action_executor.py
  • geniusia2/core/graph/learning_manager.py
  • Tests end-to-end

Phase 6 : Optimisation et Production (Semaines 13-14)

Objectif : Optimisation et déploiement

  • Optimiser performances (caching, batching)
  • Ajouter monitoring et logging
  • Documentation complète
  • Tests de charge

Livrables :

  • Système optimisé
  • Documentation utilisateur
  • Guide de déploiement