Le problème de l'analytique en santé
Les organismes de santé disposent de données précieuses, mais les partager relève du parcours du combattant. Un hôpital souhaite étudier les taux de lésions rénales aiguës dans sa population aux soins intensifs. Une équipe de recherche externe possède l'expertise statistique. Les données existent — des centaines de milliers d'observations de laboratoire stockées sous forme de ressources FHIR dans Aidbox. Mais transmettre les dossiers bruts des patients contrevient à HIPAA.
La solution habituelle consiste en un pipeline ETL sur mesure : exporter les données, exécuter un script pour supprimer les identificateurs, espérer qu'aucun ne passe à travers les mailles, puis charger les données expurgées ailleurs. Cette approche est lente, sujette aux erreurs et difficile à auditer. Chaque nouvelle question de recherche nécessite un nouveau pipeline.
Et si la dépersonnalisation se produisait à l'intérieur de la base de données, déclarée aux côtés de la vue qui structure les données?
La dépersonnalisation comme fonctionnalité des ViewDefinitions
Aidbox prend désormais en charge la dépersonnalisation par colonne dans les ViewDefinitions. Vous annotez les colonnes avec une extension FHIR qui précise quelle transformation appliquer. Le compilateur SQL enveloppe chaque expression de colonne avec une fonction PostgreSQL — les données sensibles sont transformées avant d'atteindre la sortie.
{
"resourceType": "ViewDefinition",
"name": "deident_observations",
"status": "active",
"resource": "Observation",
"select": [{
"column": [
{
"name": "patient_id",
"path": "subject.getReferenceKey(Patient)",
"extension": [{
"url": "http://health-samurai.io/fhir/core/StructureDefinition/de-identification",
"extension": [
{"url": "method", "valueCode": "cryptoHash"},
{"url": "cryptoHashKey", "valueString": "my-secret-key"}
]
}]
},
{
"name": "status",
"path": "status"
}
]
}]
}
La colonne patient_id comporte une extension de dépersonnalisation. Elle produira un hachage HMAC-SHA256 au lieu de la vraie référence du patient — déterministe (le même patient obtient toujours le même hachage) et irréversible (on ne peut pas retrouver la valeur d'origine à partir du hachage). La colonne status ne comporte aucune extension et est transmise sans modification.
Le générateur de ViewDefinitions dans l'interface Aidbox rend cela visuel — cliquez sur l'icône de bouclier d'une colonne pour choisir une méthode et définir ses paramètres :

Méthodes disponibles
| Méthode | Ce qu'elle fait | Paramètres clés |
|---|---|---|
| redact | Remplacer par NULL | — |
| cryptoHash | Hachage HMAC-SHA256 (déterministe, unidirectionnel) | cryptoHashKey |
| dateshift | Décaler les dates de ±1 à 50 jours par ressource | dateShiftKey |
| birthDateSafeHarbor | Décalage de date + suppression automatique si âge >89 (Patient.birthDate uniquement) | dateShiftKey |
| encrypt | AES-128-CBC, sortie en base64 (réversible avec la clé) | encryptKey |
| substitute | Remplacer par une chaîne fixe | replaceWith |
| perturb | Ajouter un bruit aléatoire aux valeurs numériques | span, rangeType, roundTo |
| custom_function | Appeler votre propre fonction PostgreSQL | custom_function, custom_arg (facultatif) |
Deux méthodes sont particulièrement pertinentes pour le schéma que nous allons explorer :
cryptoHash produit la même sortie pour la même entrée — ce qui signifie que vous pouvez l'utiliser pour joindre des tables. Hachez l'identifiant du patient dans deux ViewDefinitions différentes avec la même clé, et les hachages correspondront. C'est le fondement de la réidentification.
dateshift décale toutes les dates au sein d'une même ressource du même décalage, préservant les relations temporelles. Un écart de 3 jours entre deux événements reste un écart de 3 jours. Le décalage est dérivé de HMAC(dateShiftKey, resource.id), donc il est déterministe par ressource mais imprévisible sans la clé.
Exemple pratique : sous-traiter une étude sur les lésions rénales aiguës
Parcourons un scénario réaliste à l'aide de données MIMIC-IV chargées dans Aidbox — 100 patients aux soins intensifs, plus de 800 000 observations.
L'objectif : Un hôpital souhaite qu'une équipe de recherche externe analyse les taux de lésions rénales aiguës (LRA) dans sa population aux soins intensifs. Les chercheurs ont besoin des résultats de laboratoire de créatinine, mais ne doivent pas voir les véritables identificateurs des patients ni les dates exactes.
L'approche : Créer deux tables matérialisées — un jeu de données dépersonnalisé pour les chercheurs, et une table de correspondance interne qui relie les identifiants hachés aux vrais patients.
Conception de la vue dépersonnalisée
Nous commençons par une ViewDefinition sur la ressource Observation, filtrée sur les résultats de laboratoire de créatinine. Quatre colonnes, chacune avec une stratégie de dépersonnalisation différente :
Identifiant du patient — la colonne qui nécessite le plus de protection. Nous appliquons cryptoHash pour transformer l'UUID réel en une chaîne hexadécimale HMAC-SHA256 irréversible :
// ---8<--- snip
{
"name": "patient_id",
"path": "subject.getReferenceKey(Patient)",
"extension": [{
"url": "..de-identification",
"extension": [
{"url": "method", "valueCode": "cryptoHash"},
{"url": "cryptoHashKey", "valueString": "aki-study-2026"}
]
}]
}
// ---8<--- snap
Valeur et unité de créatinine — les données cliniques dont le chercheur a réellement besoin. Aucune dépersonnalisation — les valeurs de laboratoire ne sont pas des identificateurs :
// ---8<--- snip
{
"name": "creatinine",
"path": "value.ofType(Quantity).value",
"type": "decimal"
},
{
"name": "unit",
"path": "value.ofType(Quantity).unit"
}
// ---8<--- snap
Date effective — décalée par un décalage déterministe par ressource à l'aide de dateshift. Un écart de 3 jours entre deux analyses reste un écart de 3 jours, mais la vraie date calendaire est dissimulée :
// ---8<--- snip
{
"name": "effective_date",
"path": "effective.ofType(dateTime)",
"extension": [{
"url": "..de-identification",
"extension": [
{"url": "method", "valueCode": "dateshift"},
{"url": "dateShiftKey", "valueString": "aki-study-2026"}
]
}]
}
// ---8<--- snap
La table de correspondance
La deuxième ViewDefinition sert à la réidentification — elle demeure interne. Deux colonnes sur la ressource Patient, toutes deux pointant vers id : l'une en clair, l'autre hachée avec la même clé que ci-dessus. Comme cryptoHash est déterministe, les identifiants hachés correspondront entre les deux tables.
Création dans l'interface
Le générateur de ViewDefinitions rend tout cela visuel. Cliquez sur l'icône de bouclier d'une colonne pour choisir une méthode de dépersonnalisation et définir ses paramètres :

Une fois enregistré, matérialisez en table :

Les ViewDefinitions comportant des extensions de dépersonnalisation ne peuvent être matérialisées qu'en tant que table — et non en view ou materialized-view. PostgreSQL stocke la définition SQL complète des vues dans les catalogues système (pg_views), ce qui exposerait vos clés cryptographiques à quiconque y a accès. Les tables ne stockent que les données transformées.
Ce que le chercheur voit
Le chercheur interroge la table dépersonnalisée. Aucun vrai identifiant de patient, aucune vraie date :
SELECT * FROM sof.deident_creatinine LIMIT 5;
patient_id | creatinine | unit | effective_date |
|---|---|---|---|
| db1d98d58...f1508c7b439aba | 1.1 | mg/dL | 2113-01-17T09:45:00-05:00 |
| db1d98d58...f1508c7b439aba | 1.4 | mg/dL | 2116-03-05T16:16:00-05:00 |
| f852c0e57...ca9c6e3db462a9 | 0.8 | mg/dL | 2153-03-08T02:17:00-04:00 |
| 59e60d080...01688175f47b16 | 1.7 | mg/dL | 2147-09-09T15:20:00-04:00 |
| 6283390bd...738cd02dac4c08 | 0.8 | mg/dL | 2140-10-03T15:20:00-04:00 |
Le patient_id est une chaîne hexadécimale — le chercheur peut la regrouper, compter les patients distincts, suivre les tendances dans le temps. Mais il ne peut pas l'inverser pour retrouver le vrai patient.
Calcul de la mesure
Les lésions rénales aiguës sont couramment identifiées par une élévation de la créatinine. Un critère de dépistage simplifié : une créatinine supérieure à 1,5 mg/dL indique une possible lésion rénale. Le chercheur exécute :
SELECT
CASE
WHEN creatinine <= 1.1 THEN 'Normal (≤1.1)'
WHEN creatinine <= 1.5 THEN 'Elevated (1.1–1.5)'
WHEN creatinine <= 3.0 THEN 'High (1.5–3.0)'
ELSE 'Critical (>3.0)'
END AS risk_category,
count(*) AS observation_count,
count(DISTINCT patient_id) AS patient_count,
round(avg(creatinine)::numeric, 2) AS avg_creatinine
FROM sof.deident_creatinine
GROUP BY 1
ORDER BY 1;
risk_category | observation_count | patient_count | avg_creatinine |
|---|---|---|---|
| Critical (>3.0) | 307 | 19 | 5.08 |
| Elevated (1.1–1.5) | 393 | 40 | 1.33 |
| High (1.5–3.0) | 575 | 30 | 2.11 |
| Normal (≤1.1) | 1728 | 84 | 0.77 |
19 patients avaient des mesures de créatinine supérieures à 3,0 mg/dL — un seuil critique évocateur d'une lésion rénale sévère. Le chercheur identifie ces patients par leurs identifiants hachés et transmet les résultats à l'hôpital.
Réidentification pour le suivi clinique
L'hôpital effectue une jointure des résultats de recherche avec la table de correspondance interne — faisant correspondre les identifiants hachés aux vrais UUID des patients :
SELECT
m.real_id,
max(o.creatinine) AS peak_creatinine,
count(*) AS high_readings
FROM sof.deident_creatinine o
JOIN sof.patient_id_map m ON o.patient_id = m.hashed_id
WHERE o.creatinine > 3.0
GROUP BY m.real_id
ORDER BY peak_creatinine DESC
LIMIT 20;
real_id | peak_creatinine | high_readings |
|---|---|---|
| af0e5009-d87d-52a8-ac8a-676e471c41f1 | 15.2 | 9 |
| 8e77dd0b-932d-5790-9ba6-5c6df8434457 | 11 | 80 |
| 1cf9e585-806c-513b-80af-4ca565a28231 | 9.1 | 62 |
| 568cb149-804c-59e8-bdf5-816e8151cd22 | 8.3 | 9 |
| df756e08-6ea8-5d69-b918-67911945f827 | 7.9 | 11 |
| 7ec7078a-0593-5a99-9862-ebbff47fd1c5 | 6.4 | 20 |
| dd2bf984-33c3-5874-8f68-84113327877e | 6.2 | 26 |
| 4f773083-7f4d-5378-b839-c24ca1e15434 | 5.3 | 11 |
| 72d56b49-a7ee-5b9a-a679-25d1c836d3c3 | 5.1 | 3 |
| ... | ... | ... |
| 842680b3-e421-58cc-8050-3b29668b438c | 3.2 | 1 |
L'hôpital sait maintenant exactement quels patients nécessitent une attention clinique — sans jamais avoir exposé leur identité à l'équipe de recherche.
Vue d'ensemble
L'idée clé : la même cryptoHashKey relie tout ensemble. Le chercheur travaille entièrement avec des identifiants hachés. L'hôpital détient la table de correspondance et la clé. La réidentification n'a lieu qu'en interne, uniquement quand c'est nécessaire.
ViewDefinitions Safe Harbor prédéfinies
Rédiger des règles de dépersonnalisation pour chaque type de ressource est fastidieux. Nous fournissons io.health-samurai.de-identification.r4 — un paquet FHIR disponible via le registre d'artefacts d'Aidbox, avec des ViewDefinitions prêtes à l'emploi pour 17 types de ressources R4 courants : Patient, Encounter, Observation, Condition, Claim, ExplanationOfBenefit, AllergyIntolerance, DiagnosticReport, MedicationRequest, MedicationDispense, MedicationAdministration, Immunization, Procedure, Specimen, DocumentReference, Practitioner et Location.
Chaque ViewDefinition applique les règles Safe Harbor :
- cryptoHash sur les identificateurs et les références — cohérent entre les tables pour les jointures
- dateshift sur les dates cliniques — préserve les relations temporelles
- birthDateSafeHarbor sur Patient.birthDate — décalage de date plus suppression automatique lorsque le patient a plus de 89 ans, comme l'exige le 45 CFR 164.514(b)(2)(i)(C)
- redact sur les noms, adresses et autres identificateurs directs
Tous les paramètres de clés cryptographiques sont vides par défaut — définissez vos propres clés avant de matérialiser. Installez le paquet via FAR, configurez les clés, matérialisez en tables, puis commencez à interroger.
Démarrage
- Activer le mode fhir-schema — définir
fhir.validation.fhir-schema-validation=true(requis depuis Aidbox 2604) - Installer
io.health-samurai.de-identification.r4via FAR, ou écrire vos propres ViewDefinitions - Définir les clés cryptographiques dans chaque ViewDefinition — les clés vides seront rejetées
- Matérialiser en tables —
$materializeavectype=tableou via le générateur de ViewDefinitions - Interroger avec SQL depuis la console SQL d'Aidbox, Grafana ou tout outil de visualisation de données
Pour la référence complète des méthodes et les détails des paramètres, consultez la documentation sur la dépersonnalisation. Pour essayer le scénario complet de cet article avec un simple docker compose up, consultez l'exemple de dépersonnalisation.




