feat(gui): câbler l'envoi de la télémétrie d'usage en fin de run
Le module usage_telemetry est maintenant réellement branché : la GUI V6 envoie les statistiques au portail après chaque run (les stats web restaient vides sans cela). - processing_runner : RunSummary porte une liste DocResult (ordinal, page_count via page_count_for, status, duration_ms, extension) — peuplée dans la boucle. Aucun nom/chemin de fichier. - usage_telemetry : report_run_summary(summary, base_url, license_ref, machine_id, session, ...) construit le payload depuis le RunSummary et l'envoie (non bloquant). N'envoie RIEN sans license_ref. Spool JSONL si échec réseau. - tab_usage : _finish() déclenche l'envoi en thread daemon (jamais bloquant pour l'UI ni le run). - app : fournit le reporter à UsageTab avec le contexte licence (base_url du LicenseClient, license_ref via local_status, machine_id, app_version). Tests : RunSummary.documents peuplé (0 chemin) ; report_run_summary (payload correct, réseau KO → spool sans crash, pas d'envoi sans licence) ; _finish appelle le reporter. 252 tests unit OK (0 régression), self-test OK. V5/moteur/app_aivanov intacts, 0 dépendance. Aucun build/push sans GO Dom. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -123,6 +123,62 @@ class UsageTelemetryClient:
|
||||
|
||||
# --- file locale JSONL (rejeu best-effort des échecs) -----------------------
|
||||
|
||||
def documents_from_summary(summary: Any) -> list[dict]:
|
||||
"""Extrait la liste de documents (RGPD-safe) d'un ``RunSummary``.
|
||||
|
||||
Ne lit que les attributs autorisés ; aucun nom/chemin n'est récupéré.
|
||||
"""
|
||||
docs: list[dict] = []
|
||||
for item in getattr(summary, "documents", None) or []:
|
||||
docs.append(
|
||||
{
|
||||
"ordinal": getattr(item, "ordinal", 0),
|
||||
"page_count": getattr(item, "page_count", None),
|
||||
"status": getattr(item, "status", "success"),
|
||||
"duration_ms": getattr(item, "duration_ms", None),
|
||||
"extension": getattr(item, "extension", None),
|
||||
}
|
||||
)
|
||||
return docs
|
||||
|
||||
|
||||
def report_run_summary(
|
||||
summary: Any,
|
||||
*,
|
||||
base_url: str,
|
||||
license_ref: Optional[str],
|
||||
machine_id: Optional[str],
|
||||
session: Any,
|
||||
app_name: str = "gui_v6",
|
||||
app_version: Optional[str] = None,
|
||||
run_id: Optional[str] = None,
|
||||
spool_path: Any = None,
|
||||
logger: Optional[Callable[[str], None]] = None,
|
||||
) -> bool:
|
||||
"""Construit le payload depuis un ``RunSummary`` et l'envoie (non bloquant).
|
||||
|
||||
N'envoie RIEN si ``license_ref`` est absent. En cas d'échec réseau, spoole le
|
||||
payload (si ``spool_path``) pour un rejeu ultérieur. Ne lève jamais.
|
||||
"""
|
||||
log = logger or (lambda _msg: None)
|
||||
if not license_ref:
|
||||
log("télémétrie ignorée : aucune licence locale valide")
|
||||
return False
|
||||
payload = build_usage_payload(
|
||||
run_id=run_id or new_run_id(),
|
||||
app_name=app_name,
|
||||
app_version=app_version,
|
||||
license_ref=license_ref,
|
||||
machine_id=machine_id,
|
||||
documents=documents_from_summary(summary),
|
||||
)
|
||||
client = UsageTelemetryClient(base_url, session=session, logger=log)
|
||||
ok = client.report(payload)
|
||||
if not ok and spool_path is not None:
|
||||
spool_payload(spool_path, payload)
|
||||
return ok
|
||||
|
||||
|
||||
def spool_payload(path: Any, payload: dict) -> None:
|
||||
"""Ajoute un payload à la file JSONL locale (ne lève pas)."""
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user