Kazeia-central/kazeia_central/features/fleet/service.py

69 lines
2.6 KiB
Python

"""Opérations de flotte (fan-out) pour Kazeia-central.
§9 du contrat : toute action parc se fait par **fan-out séquentiel par device**, et
les commandes adb **échouent silencieusement par tablette** → on capture le résultat
OU l'erreur de CHAQUE device, jamais un échec global qui masquerait les autres. Le
rapport renvoyé est une liste `{serial, ok, result|error}` exploitable telle quelle.
Séquentiel (pas parallèle) volontairement : conforme au contrat, et un `adb` partagé
sature vite ; pour un parc de quelques tablettes le coût est négligeable.
"""
from __future__ import annotations
from typing import Any, Callable
from ...core.adb import Adb
from ...core.provider import ProviderClient
def fan_out(adb: Adb, fn: Callable[[str], Any], *,
serials: list[str] | None = None) -> list[dict[str, Any]]:
"""Exécute `fn(serial)` sur chaque tablette prête (ou la sous-liste `serials`),
en isolant les échecs. Renvoie un rapport par device."""
ready = adb.ready_devices()
if serials is not None:
wanted = set(serials)
ready = [d for d in ready if d.serial in wanted]
report: list[dict[str, Any]] = []
for d in ready:
try:
report.append({"serial": d.serial, "ok": True, "result": fn(d.serial)})
except Exception as e: # adb/provider peut échouer pour CE device seulement
report.append({"serial": d.serial, "ok": False, "error": str(e)})
return report
def device_overview(adb: Adb, serial: str) -> dict[str, Any]:
"""Résumé santé d'une tablette pour la vue parc. Chaque sous-requête est isolée :
une section indisponible ne fait pas tomber tout le résumé (renvoi partiel)."""
c = ProviderClient(adb, serial)
def safe(f, default=None):
try:
return f()
except Exception:
return default
state = safe(lambda: c.state().model_dump())
updates = safe(lambda: c.updates().model_dump())
rag = safe(lambda: c.rag_status().model_dump())
profiles = safe(lambda: c.profiles(), [])
sessions = safe(lambda: c.sessions(), [])
crashes = safe(lambda: c.crashes(), [])
g = lambda d, k: (d or {}).get(k)
return {
"serial": serial,
"online": state is not None,
"uptime_s": g(state, "uptime_seconds"),
"mem_available_mb": g(state, "mem_available_mb"),
"update_phase": g(updates, "phase"),
"app_update_available": g(updates, "app_update_available"),
"rag_ready": g(rag, "ready"),
"rag_docs": g(rag, "doc_count"),
"profiles": len(profiles),
"sessions": len(sessions),
"crashes": len(crashes),
}