199 lines
6.5 KiB
Python
199 lines
6.5 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
SessionManager - Segmente les actions en sessions pour détecter les workflows
|
|
"""
|
|
|
|
from datetime import datetime, timedelta
|
|
from typing import List, Dict, Any, Optional, Callable
|
|
from dataclasses import dataclass, field
|
|
|
|
|
|
@dataclass
|
|
class Session:
|
|
"""Représente une session d'actions utilisateur."""
|
|
session_id: str
|
|
start_time: datetime
|
|
end_time: Optional[datetime] = None
|
|
actions: List[Dict[str, Any]] = field(default_factory=list)
|
|
window: Optional[str] = None
|
|
|
|
@property
|
|
def duration(self) -> timedelta:
|
|
"""Durée de la session."""
|
|
if self.end_time:
|
|
return self.end_time - self.start_time
|
|
return datetime.now() - self.start_time
|
|
|
|
@property
|
|
def action_count(self) -> int:
|
|
"""Nombre d'actions dans la session."""
|
|
return len(self.actions)
|
|
|
|
|
|
class SessionManager:
|
|
"""
|
|
Gestionnaire de sessions pour segmenter les actions en sessions.
|
|
Une session = groupe d'actions dans une fenêtre de temps.
|
|
"""
|
|
|
|
def __init__(self, logger, config: Dict[str, Any]):
|
|
"""
|
|
Initialise le gestionnaire de sessions.
|
|
|
|
Args:
|
|
logger: Logger pour journalisation
|
|
config: Configuration globale
|
|
"""
|
|
self.logger = logger
|
|
self.config = config
|
|
|
|
# Configuration
|
|
self.session_timeout = config.get("workflow", {}).get(
|
|
"session_timeout", 300 # 5 minutes par défaut
|
|
)
|
|
|
|
# Session courante
|
|
self.current_session: Optional[Session] = None
|
|
|
|
# Historique des sessions
|
|
self.sessions: List[Session] = []
|
|
|
|
# Callback pour session complétée
|
|
self.on_session_completed: Optional[Callable] = None
|
|
|
|
if self.logger:
|
|
self.logger.log_action({
|
|
"action": "session_manager_initialized",
|
|
"session_timeout": self.session_timeout
|
|
})
|
|
|
|
def add_action(self, action: Dict[str, Any]):
|
|
"""
|
|
Ajoute une action à la session courante.
|
|
Crée une nouvelle session si nécessaire.
|
|
|
|
Args:
|
|
action: Action à ajouter
|
|
"""
|
|
# Vérifier si on doit créer une nouvelle session
|
|
if self.should_start_new_session(action):
|
|
self.finalize_current_session()
|
|
self.start_new_session(action)
|
|
|
|
# Ajouter l'action à la session courante
|
|
if self.current_session:
|
|
self.current_session.actions.append(action)
|
|
|
|
def should_start_new_session(self, action: Dict[str, Any]) -> bool:
|
|
"""
|
|
Détermine si une nouvelle session doit être créée.
|
|
|
|
Args:
|
|
action: Action à évaluer
|
|
|
|
Returns:
|
|
True si nouvelle session nécessaire
|
|
"""
|
|
# Pas de session courante
|
|
if not self.current_session:
|
|
return True
|
|
|
|
# Vérifier le timeout
|
|
if self.current_session.actions:
|
|
last_action = self.current_session.actions[-1]
|
|
last_time = last_action.get("timestamp")
|
|
|
|
if last_time:
|
|
if isinstance(last_time, str):
|
|
last_time = datetime.fromisoformat(last_time)
|
|
|
|
current_time = action.get("timestamp")
|
|
if isinstance(current_time, str):
|
|
current_time = datetime.fromisoformat(current_time)
|
|
elif not current_time:
|
|
current_time = datetime.now()
|
|
|
|
time_gap = (current_time - last_time).total_seconds()
|
|
|
|
if time_gap > self.session_timeout:
|
|
return True
|
|
|
|
# Changement de fenêtre majeur
|
|
current_window = action.get("window")
|
|
if current_window and self.current_session.window:
|
|
if current_window != self.current_session.window:
|
|
return True
|
|
|
|
return False
|
|
|
|
def start_new_session(self, first_action: Dict[str, Any]):
|
|
"""
|
|
Démarre une nouvelle session.
|
|
|
|
Args:
|
|
first_action: Première action de la session
|
|
"""
|
|
session_id = f"session_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
|
|
|
|
timestamp = first_action.get("timestamp")
|
|
if isinstance(timestamp, str):
|
|
timestamp = datetime.fromisoformat(timestamp)
|
|
elif not timestamp:
|
|
timestamp = datetime.now()
|
|
|
|
self.current_session = Session(
|
|
session_id=session_id,
|
|
start_time=timestamp,
|
|
window=first_action.get("window")
|
|
)
|
|
|
|
if self.logger:
|
|
self.logger.log_action({
|
|
"action": "session_started",
|
|
"session_id": session_id,
|
|
"window": first_action.get("window")
|
|
})
|
|
|
|
def finalize_current_session(self):
|
|
"""Finalise la session courante."""
|
|
if not self.current_session:
|
|
return
|
|
|
|
# Marquer la fin
|
|
self.current_session.end_time = datetime.now()
|
|
|
|
# Ajouter à l'historique
|
|
self.sessions.append(self.current_session)
|
|
|
|
# Callback (l'Orchestrator loggera avec plus de détails)
|
|
if self.on_session_completed:
|
|
self.on_session_completed(self.current_session)
|
|
|
|
# Réinitialiser
|
|
self.current_session = None
|
|
|
|
def force_finalize_session(self):
|
|
"""Force la finalisation de la session courante."""
|
|
self.finalize_current_session()
|
|
|
|
def get_recent_sessions(self, n: int = 10) -> List[Session]:
|
|
"""
|
|
Retourne les N sessions les plus récentes.
|
|
|
|
Args:
|
|
n: Nombre de sessions à retourner
|
|
|
|
Returns:
|
|
Liste des sessions récentes
|
|
"""
|
|
return self.sessions[-n:] if len(self.sessions) >= n else self.sessions
|
|
|
|
def get_stats(self) -> Dict[str, Any]:
|
|
"""Retourne les statistiques des sessions."""
|
|
return {
|
|
"total_sessions": len(self.sessions),
|
|
"current_session_actions": self.current_session.action_count if self.current_session else 0,
|
|
"avg_session_duration": sum(s.duration.total_seconds() for s in self.sessions) / len(self.sessions) if self.sessions else 0,
|
|
"avg_actions_per_session": sum(s.action_count for s in self.sessions) / len(self.sessions) if self.sessions else 0
|
|
}
|