"""Router flotte : découverte tablettes, labels patients, télémétrie par device (lecture provider) et opérations parc (fan-out OTA).""" from __future__ import annotations from fastapi import APIRouter from pydantic import BaseModel from ...core.context import Ctx from .service import device_overview, fan_out class _Label(BaseModel): label: str def make_router(ctx: Ctx) -> APIRouter: r = APIRouter(prefix="/api", tags=["fleet"]) g, now = ctx.guard, ctx.now_ms # ---- parc + labels ---------------------------------------------------- @r.get("/devices") def devices(): devs = [d.__dict__ for d in g(ctx.adb.devices)] if ctx.store.is_unlocked: # enrichit du label patient labels = ctx.store.device_labels() for d in devs: d["label"] = labels.get(d["serial"]) return devs @r.put("/devices/{serial}/label") def set_label(serial: str, body: _Label): g(lambda: ctx.store.set_device_label(serial, body.label, now=now())) ctx.store.audit("set_label", actor=ctx.operator, target=serial, now=now()) # pas de PII en clair return {"serial": serial, "label": body.label} # ---- télémétrie par tablette (lecture provider) ----------------------- @r.get("/devices/{serial}/state") def state(serial: str): return g(lambda: ctx.client(serial).state()).model_dump() @r.get("/devices/{serial}/turns") def turns(serial: str): return [t.model_dump() for t in g(lambda: ctx.client(serial).turns())] @r.get("/devices/{serial}/crashes") def crashes(serial: str): return [c.model_dump() for c in g(lambda: ctx.client(serial).crashes())] @r.get("/devices/{serial}/models") def models(serial: str): return [x.model_dump() for x in g(lambda: ctx.client(serial).models())] @r.get("/devices/{serial}/rag/status") def rag_status(serial: str): return g(lambda: ctx.client(serial).rag_status()).model_dump() @r.get("/devices/{serial}/rag/index") def rag_index(serial: str): return [d.model_dump() for d in g(lambda: ctx.client(serial).rag_index())] @r.get("/devices/{serial}/updates") def updates(serial: str): return g(lambda: ctx.client(serial).updates()).model_dump() @r.get("/devices/{serial}/profiles") def profiles(serial: str): return [p.model_dump() for p in g(lambda: ctx.client(serial).profiles())] @r.get("/devices/{serial}/conversations") def sessions(serial: str, profile_id: str | None = None): return [s.model_dump() for s in g(lambda: ctx.client(serial).sessions(profile_id))] # ---- opérations parc (fan-out, rapport par device §9) ----------------- @r.get("/fleet/overview") def fleet_overview(): report = fan_out(ctx.adb, lambda s: device_overview(ctx.adb, s)) labels = ctx.store.device_labels() if ctx.store.is_unlocked else {} for x in report: x["label"] = labels.get(x["serial"]) return report @r.post("/fleet/update-check") def fleet_update_check(): return fan_out(ctx.adb, lambda s: ctx.client(s).update_check()) @r.post("/fleet/update-install") def fleet_update_install(): report = fan_out(ctx.adb, lambda s: ctx.client(s).update_install()) ctx.store.audit("fleet_update_install", actor=ctx.operator, detail={"devices": [x["serial"] for x in report if x["ok"]]}, now=now()) return report return r