Kazeia-central/kazeia_central/features/medical/conversations/service.py

81 lines
3.4 KiB
Python

"""Service Conversations (médical, cœur RGPD).
Rapatriement des conversations cliniques : `conversations_export` (RPC file-drop NDJSON
sur le device) → `adb pull` → archive **chiffrée** dans le store → **`export_purge`
immédiat** (l'export est PLAINTEXT MVP côté app §écarts, donc ne doit pas traîner sur le
stockage device). Puis lecture / export JSON / purge RGPD depuis l'archive centrale.
"""
from __future__ import annotations
import json
import os
import tempfile
from typing import Any
from kazeia_central.core.adb import Adb
from kazeia_central.core.provider import ProviderClient
from kazeia_central.core.store import Store
def collect(adb: Adb, store: Store, serial: str, *, profile_id: str | None = None,
since: int | None = None, until: int | None = None, now: int,
actor: str | None = None) -> dict[str, Any]:
"""Exporte (device) → pull → archive chiffrée (dédoublonnée) → purge device immédiate."""
c = ProviderClient(adb, serial)
res = c.conversations_export(profile_id=profile_id, since=since, until=until)
path = res.get("path")
exported = int(res.get("count") or 0)
archived = 0
if path and exported:
with tempfile.TemporaryDirectory() as td:
local = os.path.join(td, "export.ndjson")
adb.pull(path, local, serial=serial)
turns = []
with open(local, encoding="utf-8") as f:
for line in f:
line = line.strip()
if not line:
continue
obj = json.loads(line)
if "_meta" in obj: # ligne d'en-tête {"_meta": {...}}
continue
turns.append(obj)
archived = store.archive_turns(serial, turns, now=now)
# PLAINTEXT MVP : purger le fichier device tout de suite (pas de PII qui traîne).
purged = False
if path:
try:
c.export_purge(path)
purged = True
except Exception:
pass
store.audit("conversations_collect", actor=actor, target=serial,
detail={"exported": exported, "archived": archived, "purged": purged}, now=now)
return {"exported": exported, "archived": archived, "purged": purged}
def sessions(store: Store, serial: str) -> list[dict[str, Any]]:
return store.archived_sessions(serial)
def session_turns(store: Store, serial: str, session_id: str) -> list[dict[str, Any]]:
return store.session_turns(serial, session_id)
def export_session(store: Store, serial: str, session_id: str) -> dict[str, Any]:
"""Bundle JSON exportable d'une session (déchiffré)."""
turns = store.session_turns(serial, session_id)
return {"serial": serial, "session_id": session_id, "turn_count": len(turns),
"profile_id": turns[0]["profile_id"] if turns else None, "turns": turns}
def purge(store: Store, serial: str, *, session_id: str | None = None,
profile_id: str | None = None, before: int | None = None,
now: int, actor: str | None = None) -> dict[str, Any]:
n = store.purge_turns(serial, session_id=session_id, profile_id=profile_id, before=before)
store.audit("conversations_purge", actor=actor, target=serial,
detail={"session_id": session_id, "profile_id": profile_id, "before": before,
"deleted": n}, now=now)
return {"deleted": n}