← Retour au blog

ML et reglementation: données de santé

Lucian BLETAN

Les données de santé combinent forte valeur et fortes contraintes. Pour innover sans déraper, il faut appliquer la minimisation, pseudonymiser avec une clé séparée, cloisonner les zones, tracer finement les usages, et documenter via une DPIA claire. L’objectif n’est pas de ralentir, mais de sécuriser ce qui compte et de prouver sa conformité avec des contrôles simples.

prérequis

  • référentiel interne privacy et sécurité (rôles, finalités, responsabilités).
  • base légale et finalité documentées pour chaque traitement.
  • inventaire des champs sensibles et quasi identifiants.
  • environnement de datalab fermé pour la mise au point des features.

aperçu rapide

  • minimiser avant tout: ne garder que les champs utiles à la finalité.
  • pseudonymiser avec HMAC ou tokenisation; stocker la clé ailleurs.
  • isoler les zones: protégée, pseudonymisée, agrégée; pas de copies sauvages.
  • appliquer RLS/CLS et vues masquées côté entrepôt.
  • tester k anonymat et seuils de publication avant tout export.
  • documenter et tracer: DPIA, journaux d’accès, rétention, preuves.

carte mentale des principes

santé et ml

minimisation

champs utiles seulement

fenetre temporelle courte

pseudonymisation

hmac ou token

cle separee

cloisonnement

zone protegee

zone pseudo

zone agregat

controles

rls cls

k anonymat

journaux

dpiA

risques

mesures

suivi

tutoriel pas-à-pas

étape 1: cartographier finalités et données

  • décrire qui, quoi, pourquoi, et pendant combien de temps.
  • classer les champs: identifiants directs, quasi identifiants, métriques.
scope:
  finalite: "prediction de risques post operatoires"
  base_legale: "interet legitime de recherche interne"
  duree_conservation_jours: 365
  champs:
    identifiants: ["patient_id", "nom", "email", "telephone"]
    quasi_identifiants: ["date_naissance", "code_postal", "sexe"]
    variables_cliniques: ["acte_code", "duree_sejour", "score_asa"]

étape 2: pseudonymiser correctement

  • générer un identifiant pseudonyme via HMAC avec une clé stockée dans un coffre distinct.
  • ne jamais stocker la clé dans le même système ni dans le code.
# pseudonymisation deterministe via HMAC (exemple)
import hmac, hashlib
def pseudo_id(patient_id: str, key: bytes) -> str:
    return hmac.new(key, patient_id.encode(), hashlib.sha256).hexdigest()[:24]

étape 3: cloisonner en zones et couper les liens

  • zone protégée: accès restreint, identifiants directs, clé de pseudonymisation.
  • zone pseudonymisée: identifiants remplacés, quasi identifiants normalisés.
  • zone agrégée: vues et exports avec seuils, aucun identifiant.

pseudonymiser

features et modeles

resultats

exports valides

zone protegee identifiants

zone pseudo ids

zone entrainement

zone agregats bi

partage controle

étape 4: appliquer RLS et masquage de colonnes

  • restreindre les lignes par rôle et masquer les colonnes sensibles par défaut.
  • exposer des vues prêtes pour l’analyse sans champs risqués.
-- exemple RLS: seuls les rôles autorisés voient la zone pseudo
ALTER TABLE sante.patients_pseudo ENABLE ROW LEVEL SECURITY;

CREATE POLICY rls_pseudo_ok
ON sante.patients_pseudo
FOR SELECT
TO role_recherche
USING (true);

-- vue masquée pour analyses
CREATE OR REPLACE VIEW sante.v_patients_features AS
SELECT
  pseudo_id,
  date_part('year', age(current_date, date_naissance))::int AS age,
  substr(code_postal, 1, 2) AS dep,
  sexe,
  acte_code,
  duree_sejour,
  score_asa
FROM sante.patients_pseudo;

étape 5: tester la réidentification et fixer des seuils

  • vérifier le k anonymat sur les coupes usuelles avant publication.
  • refuser tout tableau où n < seuil (ex: 20).
-- controle de k-anonymat minimal
WITH g AS (
  SELECT dep, date_trunc('week', ts)::date AS semaine, COUNT(*) AS n
  FROM actes
  GROUP BY 1,2
)
SELECT dep, semaine FROM g WHERE n < 20;

étape 6: DPIA concise et vivante

  • une page claire: risques, mesures, responsables, preuves.
  • mettre à jour à chaque changement de périmètre.
dpia:
  risques:
    - "reidentification sur petites agregations"
    - "fuites via exports non traces"
  mesures:
    - "seuil k>=20 et suppression des valeurs rares"
    - "logs d acces et approbation export"
  responsables: ["@privacy", "@data-platform"]
  preuves: ["tests_k_anonymat.md", "captures_dashboard.pdf"]

étape 7: journaliser et gouverner les exports

  • tracer qui exporte quoi, quand, et sous quelles conditions.
  • limiter les colonnes exportables via des vues d’export.
CREATE TABLE IF NOT EXISTS sante.export_log (
  export_id UUID PRIMARY KEY,
  created_at TIMESTAMP NOT NULL,
  actor TEXT NOT NULL,
  view_name TEXT NOT NULL,
  rows BIGINT,
  purpose TEXT
);

CREATE OR REPLACE VIEW sante.export_agregats AS
SELECT dep, semaine, COUNT(*) AS n, ROUND(AVG(duree_sejour),1) AS duree_moy
FROM sante.v_patients_features
GROUP BY dep, semaine
HAVING COUNT(*) >= 20;

exemples complets

cas 1: pipeline minimal de pseudonymisation

from dataclasses import dataclass
import os, hmac, hashlib

KEY = os.environ.get("PSEUDO_KEY", "change-me").encode()

@dataclass
class Patient:
    patient_id: str
    date_naissance: str
    code_postal: str
    sexe: str

def pseudo_id(pid: str) -> str:
    return hmac.new(KEY, pid.encode(), hashlib.sha256).hexdigest()[:24]

def to_pseudo(p: Patient):
    return {
        "pseudo_id": pseudo_id(p.patient_id),
        "date_naissance": p.date_naissance,
        "code_postal": p.code_postal,
        "sexe": p.sexe
    }

explications: identifiant stable, clé hors code idéalement via coffre. Les champs directs ne sortent pas de la zone protégée.

cas 2: agrégation sûre pour la BI

-- agrégations hebdo avec seuils et suppression des valeurs rares
WITH base AS (
  SELECT dep,
         date_trunc('week', admission_ts)::date AS semaine,
         duree_sejour
  FROM sante.v_patients_features
),
filtre AS (
  SELECT dep, semaine, COUNT(*) AS n
  FROM base
  GROUP BY 1,2
)
SELECT b.dep, b.semaine, COUNT(*) AS n, ROUND(AVG(b.duree_sejour),1) AS duree_moy
FROM base b
JOIN filtre f USING(dep, semaine)
WHERE f.n >= 20
GROUP BY b.dep, b.semaine;

explications: jointure avec un filtre de seuil pour empêcher les petits groupes d’apparaître.

erreurs courantes et solutions

  • pseudonymisation réversible par simple hash -> utiliser HMAC salé et clé séparée.
  • copies sauvages dans des répertoires partagés -> datalab fermé, journaux d’accès, politiques d’export.
  • quasi identifiants laissés bruts -> normaliser, tronquer, arrondir, regrouper.
  • vues sans seuils -> appliquer HAVING COUNT(*) >= seuils.
  • clé stockée dans le code -> utiliser un coffre et rotation périodique.

faq

  • Quelle différence entre anonymisation et pseudonymisation ? L’anonymisation supprime toute possibilité de retour; la pseudonymisation remplace l’identifiant mais permet un lien via une clé séparée. La plupart des cas ML utilisent la pseudonymisation.

  • Faut il chiffrer si je pseudonymise déjà ? Oui pour les zones protégées et en transit. La pseudonymisation ne remplace pas le chiffrement ni les contrôles d’accès.

  • Comment choisir le seuil k pour les agrégats ? Dépend du contexte et du risque de croisement externe. Beaucoup d’équipes retiennent k entre 10 et 30; documentez votre choix dans la DPIA.

  • Puis je entraîner des modèles sur la zone pseudonymisée ? Oui, si la finalité est documentée et que l’accès est restreint. Les résultats publiés doivent respecter les seuils et la minimisation.

  • Comment auditer simplement ? Conservez des journaux d’accès, des traces d’export, les scripts de tests k anonymat et les versions des vues. Échantillonnez des audits mensuels.