Initial commit

This commit is contained in:
Dom
2026-03-05 00:20:25 +01:00
commit dcd4de9945
1954 changed files with 669380 additions and 0 deletions

379
geniusia2/core/models.py Normal file
View File

@@ -0,0 +1,379 @@
"""
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!")