380 lines
14 KiB
Python
380 lines
14 KiB
Python
"""
|
|
Modèles de données pour RPA Vision V2
|
|
Contient les dataclasses pour TaskProfile, Action et Detection
|
|
"""
|
|
|
|
from dataclasses import dataclass, field, asdict
|
|
from datetime import datetime
|
|
from typing import List, Dict, Any, Tuple, Optional
|
|
import json
|
|
import numpy as np
|
|
|
|
|
|
@dataclass
|
|
class Detection:
|
|
"""
|
|
Représente une détection d'élément UI par un modèle de vision
|
|
|
|
Attributes:
|
|
label: Nom/label de l'élément détecté
|
|
confidence: Score de confiance de la détection (0-1)
|
|
bbox: Bounding box (x, y, width, height) en pixels
|
|
embedding: Embedding visuel 512-d de l'élément
|
|
model_source: Modèle ayant effectué la détection ("owl-v2", "dino", "yolo")
|
|
roi_image: Image de la région d'intérêt (optionnel)
|
|
metadata: Métadonnées additionnelles du modèle
|
|
"""
|
|
label: str
|
|
confidence: float
|
|
bbox: Tuple[int, int, int, int]
|
|
embedding: np.ndarray
|
|
model_source: str
|
|
roi_image: Optional[np.ndarray] = None
|
|
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
|
|
def to_dict(self) -> Dict[str, Any]:
|
|
"""
|
|
Convertit la détection en dictionnaire pour sérialisation
|
|
Note: Les arrays numpy ne sont pas sérialisés directement
|
|
"""
|
|
return {
|
|
"label": self.label,
|
|
"confidence": float(self.confidence),
|
|
"bbox": list(self.bbox),
|
|
"model_source": self.model_source,
|
|
"metadata": self.metadata,
|
|
# embedding et roi_image sont exclus car non JSON-sérialisables
|
|
}
|
|
|
|
@classmethod
|
|
def from_dict(cls, data: Dict[str, Any], embedding: Optional[np.ndarray] = None) -> 'Detection':
|
|
"""
|
|
Crée une instance Detection depuis un dictionnaire
|
|
|
|
Args:
|
|
data: Dictionnaire contenant les données de détection
|
|
embedding: Embedding numpy (doit être fourni séparément)
|
|
"""
|
|
return cls(
|
|
label=data["label"],
|
|
confidence=data["confidence"],
|
|
bbox=tuple(data["bbox"]),
|
|
embedding=embedding if embedding is not None else np.array([]),
|
|
model_source=data["model_source"],
|
|
metadata=data.get("metadata", {})
|
|
)
|
|
|
|
|
|
@dataclass
|
|
class Action:
|
|
"""
|
|
Représente une action UI effectuée ou suggérée
|
|
|
|
Attributes:
|
|
action_type: Type d'action ("click", "type", "scroll", "wait")
|
|
target_element: Nom de l'élément cible
|
|
bbox: Bounding box de l'élément cible
|
|
confidence: Score de confiance pour cette action
|
|
embedding: Embedding visuel de l'élément cible
|
|
timestamp: Horodatage de l'action
|
|
window_title: Titre de la fenêtre où l'action est effectuée
|
|
parameters: Paramètres additionnels (ex: texte à taper, direction de scroll)
|
|
result: Résultat de l'exécution ("success", "failed", "pending")
|
|
"""
|
|
action_type: str
|
|
target_element: str
|
|
bbox: Tuple[int, int, int, int]
|
|
confidence: float
|
|
embedding: np.ndarray
|
|
timestamp: datetime
|
|
window_title: str
|
|
parameters: Dict[str, Any] = field(default_factory=dict)
|
|
result: str = "pending"
|
|
|
|
def to_dict(self) -> Dict[str, Any]:
|
|
"""
|
|
Convertit l'action en dictionnaire pour sérialisation
|
|
"""
|
|
return {
|
|
"action_type": self.action_type,
|
|
"target_element": self.target_element,
|
|
"bbox": list(self.bbox),
|
|
"confidence": float(self.confidence),
|
|
"timestamp": self.timestamp.isoformat(),
|
|
"window_title": self.window_title,
|
|
"parameters": self.parameters,
|
|
"result": self.result,
|
|
}
|
|
|
|
@classmethod
|
|
def from_dict(cls, data: Dict[str, Any], embedding: Optional[np.ndarray] = None) -> 'Action':
|
|
"""
|
|
Crée une instance Action depuis un dictionnaire
|
|
|
|
Args:
|
|
data: Dictionnaire contenant les données d'action
|
|
embedding: Embedding numpy (doit être fourni séparément)
|
|
"""
|
|
return cls(
|
|
action_type=data["action_type"],
|
|
target_element=data["target_element"],
|
|
bbox=tuple(data["bbox"]),
|
|
confidence=data["confidence"],
|
|
embedding=embedding if embedding is not None else np.array([]),
|
|
timestamp=datetime.fromisoformat(data["timestamp"]),
|
|
window_title=data["window_title"],
|
|
parameters=data.get("parameters", {}),
|
|
result=data.get("result", "pending"),
|
|
)
|
|
|
|
def get_inverse_action(self) -> Optional['Action']:
|
|
"""
|
|
Retourne l'action inverse pour rollback (si applicable)
|
|
"""
|
|
# Cette méthode sera implémentée plus tard dans input_utils
|
|
# Pour l'instant, retourne None
|
|
return None
|
|
|
|
|
|
@dataclass
|
|
class TaskProfile:
|
|
"""
|
|
Profil d'une tâche apprise par le système
|
|
|
|
Attributes:
|
|
task_id: Identifiant unique de la tâche
|
|
task_name: Nom descriptif de la tâche
|
|
mode: Mode opérationnel actuel ("shadow", "assist", "auto")
|
|
observation_count: Nombre d'observations de cette tâche
|
|
concordance_rate: Taux de concordance (0-1)
|
|
confidence_score: Score de confiance global (0-1)
|
|
correction_count: Nombre de corrections reçues
|
|
last_execution: Horodatage de la dernière exécution
|
|
window_whitelist: Liste des fenêtres autorisées pour cette tâche
|
|
action_sequence: Séquence d'actions composant la tâche
|
|
embeddings: Liste des embeddings visuels associés
|
|
metadata: Métadonnées additionnelles
|
|
execution_history: Historique des exécutions récentes
|
|
"""
|
|
task_id: str
|
|
task_name: str
|
|
mode: str = "shadow"
|
|
observation_count: int = 0
|
|
concordance_rate: float = 0.0
|
|
confidence_score: float = 0.0
|
|
correction_count: int = 0
|
|
last_execution: Optional[datetime] = None
|
|
window_whitelist: List[str] = field(default_factory=list)
|
|
action_sequence: List[Action] = field(default_factory=list)
|
|
embeddings: List[np.ndarray] = field(default_factory=list)
|
|
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
execution_history: List[Dict[str, Any]] = field(default_factory=list)
|
|
|
|
def to_json(self) -> str:
|
|
"""
|
|
Sérialise le profil de tâche en JSON
|
|
Note: Les embeddings numpy ne sont pas inclus dans le JSON
|
|
"""
|
|
data = {
|
|
"task_id": self.task_id,
|
|
"task_name": self.task_name,
|
|
"mode": self.mode,
|
|
"observation_count": self.observation_count,
|
|
"concordance_rate": float(self.concordance_rate),
|
|
"confidence_score": float(self.confidence_score),
|
|
"correction_count": self.correction_count,
|
|
"last_execution": self.last_execution.isoformat() if self.last_execution else None,
|
|
"window_whitelist": self.window_whitelist,
|
|
"action_sequence": [action.to_dict() for action in self.action_sequence],
|
|
"metadata": self.metadata,
|
|
"execution_history": self.execution_history,
|
|
}
|
|
return json.dumps(data, indent=2, ensure_ascii=False)
|
|
|
|
@classmethod
|
|
def from_json(cls, json_str: str, embeddings: Optional[List[np.ndarray]] = None) -> 'TaskProfile':
|
|
"""
|
|
Crée une instance TaskProfile depuis une chaîne JSON
|
|
|
|
Args:
|
|
json_str: Chaîne JSON contenant les données du profil
|
|
embeddings: Liste d'embeddings numpy (doivent être fournis séparément)
|
|
"""
|
|
data = json.loads(json_str)
|
|
|
|
# Reconstruire les actions
|
|
actions = [Action.from_dict(action_data) for action_data in data.get("action_sequence", [])]
|
|
|
|
return cls(
|
|
task_id=data["task_id"],
|
|
task_name=data["task_name"],
|
|
mode=data.get("mode", "shadow"),
|
|
observation_count=data.get("observation_count", 0),
|
|
concordance_rate=data.get("concordance_rate", 0.0),
|
|
confidence_score=data.get("confidence_score", 0.0),
|
|
correction_count=data.get("correction_count", 0),
|
|
last_execution=datetime.fromisoformat(data["last_execution"]) if data.get("last_execution") else None,
|
|
window_whitelist=data.get("window_whitelist", []),
|
|
action_sequence=actions,
|
|
embeddings=embeddings if embeddings is not None else [],
|
|
metadata=data.get("metadata", {}),
|
|
execution_history=data.get("execution_history", []),
|
|
)
|
|
|
|
def get_historical_performance(self) -> float:
|
|
"""
|
|
Calcule la performance historique basée sur les exécutions récentes
|
|
|
|
Returns:
|
|
Score de performance (0-1)
|
|
"""
|
|
if not self.execution_history:
|
|
return 0.0
|
|
|
|
# Calculer le taux de succès sur les exécutions récentes
|
|
recent_executions = self.execution_history[-10:] # 10 dernières exécutions
|
|
success_count = sum(1 for exec in recent_executions if exec.get("result") == "success")
|
|
|
|
return success_count / len(recent_executions) if recent_executions else 0.0
|
|
|
|
def add_execution(self, result: str, confidence: float, latency_ms: float):
|
|
"""
|
|
Ajoute une exécution à l'historique
|
|
|
|
Args:
|
|
result: Résultat de l'exécution ("success", "failed")
|
|
confidence: Score de confiance de l'exécution
|
|
latency_ms: Latence en millisecondes
|
|
"""
|
|
execution = {
|
|
"timestamp": datetime.now().isoformat(),
|
|
"result": result,
|
|
"confidence": confidence,
|
|
"latency_ms": latency_ms,
|
|
}
|
|
self.execution_history.append(execution)
|
|
self.last_execution = datetime.now()
|
|
|
|
# Limiter l'historique aux 50 dernières exécutions
|
|
if len(self.execution_history) > 50:
|
|
self.execution_history = self.execution_history[-50:]
|
|
|
|
def update_concordance_rate(self, success: bool):
|
|
"""
|
|
Met à jour le taux de concordance basé sur le résultat d'une exécution
|
|
|
|
Args:
|
|
success: True si l'exécution a réussi, False sinon
|
|
"""
|
|
# Utiliser une moyenne mobile pour le taux de concordance
|
|
window_size = 10 # Fenêtre de 10 exécutions
|
|
recent_executions = self.execution_history[-window_size:]
|
|
|
|
if recent_executions:
|
|
success_count = sum(1 for exec in recent_executions if exec.get("result") == "success")
|
|
self.concordance_rate = success_count / len(recent_executions)
|
|
|
|
def should_transition_to_auto(self, min_observations: int = 20, min_concordance: float = 0.95) -> bool:
|
|
"""
|
|
Vérifie si la tâche remplit les critères pour passer en mode Autopilot
|
|
|
|
Args:
|
|
min_observations: Nombre minimum d'observations requises
|
|
min_concordance: Taux de concordance minimum requis
|
|
|
|
Returns:
|
|
True si les critères sont remplis
|
|
"""
|
|
return (self.observation_count >= min_observations and
|
|
self.concordance_rate >= min_concordance)
|
|
|
|
def should_rollback_to_assist(self, min_confidence: float = 0.90) -> bool:
|
|
"""
|
|
Vérifie si la tâche doit être rétrogradée au mode Assisté
|
|
|
|
Args:
|
|
min_confidence: Score de confiance minimum requis
|
|
|
|
Returns:
|
|
True si la confiance est trop faible
|
|
"""
|
|
return self.mode == "auto" and self.confidence_score < min_confidence
|
|
|
|
|
|
if __name__ == "__main__":
|
|
# Tests basiques des modèles
|
|
print("Test des modèles de données RPA Vision V2")
|
|
print("=" * 50)
|
|
|
|
# Test Detection
|
|
print("\n1. Test Detection:")
|
|
detection = Detection(
|
|
label="valider_button",
|
|
confidence=0.93,
|
|
bbox=(450, 320, 120, 40),
|
|
embedding=np.random.rand(512),
|
|
model_source="owl-v2",
|
|
metadata={"class": "button"}
|
|
)
|
|
print(f" Label: {detection.label}")
|
|
print(f" Confidence: {detection.confidence}")
|
|
print(f" BBox: {detection.bbox}")
|
|
print(f" Model: {detection.model_source}")
|
|
det_dict = detection.to_dict()
|
|
print(f" Dict keys: {list(det_dict.keys())}")
|
|
|
|
# Test Action
|
|
print("\n2. Test Action:")
|
|
action = Action(
|
|
action_type="click",
|
|
target_element="valider_button",
|
|
bbox=(450, 320, 120, 40),
|
|
confidence=0.95,
|
|
embedding=np.random.rand(512),
|
|
timestamp=datetime.now(),
|
|
window_title="Dolibarr - Facturation",
|
|
parameters={"button": "left"},
|
|
result="success"
|
|
)
|
|
print(f" Type: {action.action_type}")
|
|
print(f" Target: {action.target_element}")
|
|
print(f" Window: {action.window_title}")
|
|
print(f" Result: {action.result}")
|
|
action_dict = action.to_dict()
|
|
print(f" Dict keys: {list(action_dict.keys())}")
|
|
|
|
# Test TaskProfile
|
|
print("\n3. Test TaskProfile:")
|
|
task = TaskProfile(
|
|
task_id="ouvrir_facture_001",
|
|
task_name="Ouvrir Facture",
|
|
mode="assist",
|
|
observation_count=15,
|
|
concordance_rate=0.87,
|
|
confidence_score=0.92,
|
|
window_whitelist=["Dolibarr - Facturation"],
|
|
action_sequence=[action]
|
|
)
|
|
print(f" Task ID: {task.task_id}")
|
|
print(f" Mode: {task.mode}")
|
|
print(f" Observations: {task.observation_count}")
|
|
print(f" Concordance: {task.concordance_rate:.2%}")
|
|
print(f" Confidence: {task.confidence_score:.2%}")
|
|
|
|
# Test sérialisation JSON
|
|
print("\n4. Test sérialisation JSON:")
|
|
json_str = task.to_json()
|
|
print(f" JSON length: {len(json_str)} chars")
|
|
|
|
# Test désérialisation
|
|
task_restored = TaskProfile.from_json(json_str)
|
|
print(f" Restored task ID: {task_restored.task_id}")
|
|
print(f" Restored mode: {task_restored.mode}")
|
|
|
|
# Test méthodes de transition
|
|
print("\n5. Test méthodes de transition:")
|
|
print(f" Should transition to auto: {task.should_transition_to_auto()}")
|
|
print(f" Should rollback to assist: {task.should_rollback_to_assist()}")
|
|
|
|
print("\n✓ Tous les tests basiques réussis!")
|