Initial commit
This commit is contained in:
238
raw_session.py
Normal file
238
raw_session.py
Normal file
@@ -0,0 +1,238 @@
|
||||
# raw_session.py
|
||||
"""
|
||||
Modèle RawSession v1 pour l'agent_v0.
|
||||
|
||||
Ce module définit la structure des données RawSession et
|
||||
la sérialisation en JSON au format rawsession_v1.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
import uuid
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from config import AGENT_VERSION, SESSIONS_ROOT
|
||||
|
||||
|
||||
@dataclass
|
||||
class ScreenshotMeta:
|
||||
screenshot_id: str
|
||||
relative_path: str
|
||||
captured_at: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class Event:
|
||||
t: float
|
||||
type: str
|
||||
window: Dict[str, Any]
|
||||
screenshot_id: Optional[str] = None
|
||||
# champs optionnels selon type
|
||||
button: Optional[str] = None
|
||||
pos: Optional[List[int]] = None
|
||||
keys: Optional[List[str]] = None
|
||||
idle_ms: Optional[int] = None
|
||||
scroll_delta: Optional[List[int]] = None # [dx, dy] pour la molette
|
||||
|
||||
|
||||
@dataclass
|
||||
class RawSession:
|
||||
schema_version: str
|
||||
session_id: str
|
||||
agent_version: str
|
||||
environment: Dict[str, Any]
|
||||
user: Dict[str, Any]
|
||||
context: Dict[str, Any]
|
||||
started_at: str
|
||||
ended_at: Optional[str] = None
|
||||
|
||||
events: List[Event] = field(default_factory=list)
|
||||
screenshots: List[ScreenshotMeta] = field(default_factory=list)
|
||||
|
||||
_start_time_monotonic: float = field(default_factory=time.monotonic, repr=False)
|
||||
|
||||
@classmethod
|
||||
def create(
|
||||
cls,
|
||||
user_id: str,
|
||||
user_label: str = "",
|
||||
customer: str = "",
|
||||
training_label: str = "",
|
||||
notes: str = "",
|
||||
platform: str = "unknown",
|
||||
hostname: str = "unknown",
|
||||
screen_resolution: Optional[List[int]] = None,
|
||||
display_scale: float = 1.0,
|
||||
) -> "RawSession":
|
||||
"""Fabrique une nouvelle session avec les champs de base remplis."""
|
||||
session_id = f"sess_{time.strftime('%Y%m%dT%H%M%S')}_{uuid.uuid4().hex[:8]}"
|
||||
started_at = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
|
||||
|
||||
environment = {
|
||||
"platform": platform,
|
||||
"hostname": hostname,
|
||||
"screen": {
|
||||
"primary_resolution": screen_resolution or [0, 0],
|
||||
"display_scale": display_scale,
|
||||
},
|
||||
}
|
||||
user = {
|
||||
"id": user_id,
|
||||
"label": user_label or user_id,
|
||||
}
|
||||
context = {
|
||||
"customer": customer,
|
||||
"training_label": training_label,
|
||||
"notes": notes,
|
||||
}
|
||||
|
||||
return cls(
|
||||
schema_version="rawsession_v1",
|
||||
session_id=session_id,
|
||||
agent_version=AGENT_VERSION,
|
||||
environment=environment,
|
||||
user=user,
|
||||
context=context,
|
||||
started_at=started_at,
|
||||
)
|
||||
|
||||
# --- API temps relatif ---
|
||||
|
||||
def now_relative(self) -> float:
|
||||
"""Temps relatif (en secondes) depuis le début de la session."""
|
||||
return time.monotonic() - self._start_time_monotonic
|
||||
|
||||
# --- API screenshots ---
|
||||
|
||||
def add_screenshot(
|
||||
self,
|
||||
screenshot_id: str,
|
||||
relative_path: str,
|
||||
captured_at: Optional[str] = None,
|
||||
) -> None:
|
||||
"""Enregistre les métadonnées d'un screenshot."""
|
||||
captured_at = captured_at or time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
|
||||
self.screenshots.append(
|
||||
ScreenshotMeta(
|
||||
screenshot_id=screenshot_id,
|
||||
relative_path=relative_path,
|
||||
captured_at=captured_at,
|
||||
)
|
||||
)
|
||||
|
||||
# --- API événements ---
|
||||
|
||||
def add_mouse_click_event(
|
||||
self,
|
||||
button: str,
|
||||
pos: List[int],
|
||||
window_title: str,
|
||||
app_name: str,
|
||||
screenshot_id: Optional[str],
|
||||
) -> None:
|
||||
self.events.append(
|
||||
Event(
|
||||
t=self.now_relative(),
|
||||
type="mouse_click",
|
||||
button=button,
|
||||
pos=pos,
|
||||
window={"title": window_title, "app_name": app_name},
|
||||
screenshot_id=screenshot_id,
|
||||
)
|
||||
)
|
||||
|
||||
def add_key_combo_event(
|
||||
self,
|
||||
keys: List[str],
|
||||
window_title: str,
|
||||
app_name: str,
|
||||
screenshot_id: Optional[str],
|
||||
) -> None:
|
||||
self.events.append(
|
||||
Event(
|
||||
t=self.now_relative(),
|
||||
type="key_combo",
|
||||
keys=keys,
|
||||
window={"title": window_title, "app_name": app_name},
|
||||
screenshot_id=screenshot_id,
|
||||
)
|
||||
)
|
||||
|
||||
def add_hover_idle_event(
|
||||
self,
|
||||
pos: List[int],
|
||||
idle_ms: int,
|
||||
window_title: str,
|
||||
app_name: str,
|
||||
screenshot_id: Optional[str],
|
||||
) -> None:
|
||||
self.events.append(
|
||||
Event(
|
||||
t=self.now_relative(),
|
||||
type="hover_idle",
|
||||
pos=pos,
|
||||
idle_ms=idle_ms,
|
||||
window={"title": window_title, "app_name": app_name},
|
||||
screenshot_id=screenshot_id,
|
||||
)
|
||||
)
|
||||
|
||||
def add_scroll_event(
|
||||
self,
|
||||
pos: List[int],
|
||||
delta: List[int],
|
||||
window_title: str,
|
||||
app_name: str,
|
||||
screenshot_id: Optional[str],
|
||||
) -> None:
|
||||
"""delta = [dx, dy]"""
|
||||
self.events.append(
|
||||
Event(
|
||||
t=self.now_relative(),
|
||||
type="scroll",
|
||||
pos=pos,
|
||||
scroll_delta=delta,
|
||||
window={"title": window_title, "app_name": app_name},
|
||||
screenshot_id=screenshot_id,
|
||||
)
|
||||
)
|
||||
|
||||
def close(self) -> None:
|
||||
"""Marque la fin de la session."""
|
||||
self.ended_at = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
|
||||
|
||||
# --- Sérialisation ---
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
return {
|
||||
"schema_version": self.schema_version,
|
||||
"session_id": self.session_id,
|
||||
"agent_version": self.agent_version,
|
||||
"environment": self.environment,
|
||||
"user": self.user,
|
||||
"context": self.context,
|
||||
"started_at": self.started_at,
|
||||
"ended_at": self.ended_at,
|
||||
"events": [e.__dict__ for e in self.events],
|
||||
"screenshots": [s.__dict__ for s in self.screenshots],
|
||||
}
|
||||
|
||||
def save_json(self, base_dir: Optional[str] = None) -> str:
|
||||
"""
|
||||
Sauvegarde le JSON rawsession_v1 dans un sous-dossier de SESSIONS_ROOT.
|
||||
|
||||
Retourne le chemin du fichier JSON.
|
||||
"""
|
||||
base_dir = base_dir or SESSIONS_ROOT
|
||||
session_dir = os.path.join(base_dir, self.session_id)
|
||||
os.makedirs(session_dir, exist_ok=True)
|
||||
json_path = os.path.join(session_dir, f"{self.session_id}.json")
|
||||
|
||||
with open(json_path, "w", encoding="utf-8") as f:
|
||||
json.dump(self.to_dict(), f, ensure_ascii=False, indent=2)
|
||||
|
||||
return json_path
|
||||
Reference in New Issue
Block a user