Linkr
Accueil Ressources Outils Documentation Blog Démo
EN
  • Découverte d'OMOP
  • La communauté OHDSI
  • Le modèle OMOP CDM
  • Les vocabulaires OMOP
  • L'écosystème d'outils OHDSI
  • Construire un ETL OMOP
  • SQL — Niveau débutant
  • SQL — Niveau intermédiaire
  • SQL — Niveau avancé
6/9
25 min Boris Delange · 22/05/2026

Construire un ETL OMOP

Une démarche progressive : aligner sémantiquement projet par projet, puis router chaque donnée vers sa table OMOP par le domain_id.

En résumé

Construire un ETL OMOP est souvent perçu comme un chantier de plusieurs mois. La démarche peut être découpée progressivement et intégrée au flux de travail habituel. La plus grande partie du travail est l’alignement sémantique — produire le fichier SOURCE_TO_CONCEPT_MAP.csv. Une fois cet alignement en place, le reste est largement mécanique : on route chaque ligne source vers la bonne table OMOP en fonction du domain_id du concept standard. Cet article décrit cette démarche, présente le module d’alignement de Linkr, puis vous fait construire pas à pas un mini-ETL MIMIC-IV → OMOP en exécutant le SQL dans votre navigateur.

L’ETL OMOP : une démarche progressive

Quand on découvre OMOP, on peut se représenter l’ETL comme un chantier de plusieurs années visant à standardiser l’intégralité du dictionnaire local avant de pouvoir exploiter quoi que ce soit. Cette vision est légitime — elle reflète d’ailleurs ce qu’ont effectivement vécu plusieurs équipes pionnières — mais elle n’est pas la seule.

Une autre approche, plus incrémentale, permet de démarrer un premier ETL OMOP utile en quelques semaines, puis d’enrichir progressivement la couverture au fil des projets. Cette démarche s’intègre dans le flux de travail habituel d’une équipe d’entrepôt de données, sans bouleverser les habitudes. Elle repose sur deux principes :

  1. Procéder projet par projet. Pour une étude donnée, vous avez besoin de quelques dizaines de variables. Vous n’alignez que celles-ci ; les autres concepts attendront un autre projet, qui réutilisera ce qui aura déjà été aligné.
  2. L’alignement sémantique d’abord, le reste suit mécaniquement. Une fois la table SOURCE_TO_CONCEPT_MAP produite, l’ETL devient surtout du routage : on regarde le domain_id de chaque concept standard, et il indique directement dans quelle table OMOP atterrit la donnée.

Une conséquence directe de cette approche : la base OMOP que vous produisez n’aura pas forcément toutes les tables du modèle. Vous remplirez celles qui correspondent aux concepts effectivement alignés pour le projet — typiquement PERSON, VISIT_OCCURRENCE, plus quelques tables d’événements (CONDITION_OCCURRENCE, MEASUREMENT, DRUG_EXPOSURE…). Les autres (DEVICE_EXPOSURE, SPECIMEN, NOTE…) resteront vides tant qu’un projet ne les requiert pas. Ce périmètre réduit est suffisant pour le projet en cours : les analyses ne s’appuieront que sur les tables effectivement peuplées, et c’est ce qui compte pour aboutir.

L’ETL complet reste l’objectif

Si les ressources de votre équipe le permettent, l’ETL complet reste l’objectif : aligner l’ensemble du dictionnaire local, peupler toutes les tables OMOP, viser dès la première version un haut niveau de couverture. Ce qui est présenté ici est une alternative pragmatique pour les équipes qui n’ont pas ces ressources d’emblée, et qui veulent tout de même commencer à produire — quitte à étendre projet après projet.

Cet article suit cette démarche progressive. On présente d’abord la partie sémantique (la plus exigeante, qui mobilise les experts du domaine), puis le routage par domain_id, et enfin on déroule un exemple immersif sur MIMIC-IV : vous exécuterez vous-même le SQL dans votre navigateur, étape par étape, pour bâtir une mini-base OMOP.

Pré-requis

Cet article suppose la lecture des articles précédents de la série OMOP : le modèle OMOP CDM, les vocabulaires OMOP et l’écosystème d’outils OHDSI. On y emploie sans les redéfinir les notions de concept_id, domain_id, standard_concept, Maps to et concept_id = 0.

L’alignement sémantique, cœur du travail

L’alignement sémantique consiste à relier chaque code local (un itemid MIMIC, un identifiant de laboratoire interne, un libellé propre à un établissement) à un concept standard OMOP : un identifiant LOINC pour une mesure biologique, un SNOMED CT pour un diagnostic ou une observation, un RxNorm pour un médicament. Le résultat tient dans un fichier CSV : la fameuse SOURCE_TO_CONCEPT_MAP.

Cette étape est la plus chronophage de tout l’ETL. Elle s’appuie sur les experts du domaine : biologistes pour les paramètres de laboratoire, pharmaciens pour les médicaments, cliniciens (réanimateurs, internistes, urgentistes…) pour les diagnostics, observations et procédures. La qualité de l’alignement détermine la qualité de tout ce qui suivra — c’est là qu’il faut prendre le temps nécessaire.

Le module d’alignement de Linkr

Pour accompagner ce travail, Linkr propose un module dédié — l’Alignement de concepts. La création d’un projet d’alignement se fait en quelques clics, en important un fichier source (CSV, Parquet…) listant les concepts à aligner. Lors de cet import, Linkr propose une étape de paramétrage où vous choisissez les colonnes du fichier qui correspondent au code local, au libellé, à la catégorie, à la fréquence et aux métadonnées JSON :

linkr-v2-b1800b.frama.io

Paramètres d'import

Configurez le parsing du fichier et associez les colonnes aux champs de concepts.

Détection auto
UTF-8
0
Oui

Associez les colonnes du fichier aux champs concept. Au minimum le nom ou le code du concept est requis.

— Aucune —
category
itemid
— Aucune —
label
— Aucune —
— Aucune —
— Aucune —
#itemidlabelfluidcategory
150808Free CalciumBloodBlood Gas
250826Tidal VolumeBloodBlood Gas
350813LactateBloodBlood Gas
452029% Ionized CalciumBloodBlood Gas
550801Alveolar-arterial GradientBloodBlood Gas
650810Hematocrit, CalculatedBloodBlood Gas
750820pHBloodBlood Gas
850912CreatinineBloodChemistry
951221HematocritBloodHematology
1051301White Blood CellsBloodHematology
Affichage de 10 sur 1 622 lignes
Les paramètres d'import sur le fichier d_labitems.csv de MIMIC-IV : vocabulaire, code, libellé, fréquences, métadonnées.

Une fois le projet créé, l’éditeur d’alignements s’ouvre en deux panneaux : à gauche les concepts source, à droite les candidats standards :

linkr-v2-b1800b.frama.io
Rechercher un nom, code ou ID…
VocabNom du conceptCode conceptPatientsLignes
ICCAcreatinine serumL00211,8429,614
ICCAlactate bloodL01021,2034,871
ICCAhemoglobinL03011,6887,203
ICCAplatelet countL04119743,120
ICCAWBC countL05128812,954
Jeux de conceptsRechercherSuggestions
creatinine serum
NomCodeVocab.Domaine
Creatinine [Mass/volume] in Serum or Plasma2160-0LOINCMeasurement
Creatinine [Mass/volume] in Blood38483-4LOINCMeasurement
Creatinine [Moles/volume] in Serum or Plasma14682-9LOINCMeasurement
L'éditeur en cours d'utilisation : un concept source sélectionné à gauche, ses candidats LOINC à droite.

L’alignement assisté par IA agentique

L’alignement à la main reste long. Pour gagner du temps sur la première passe, Linkr s’intègre avec Claude Code via un skill dédié concept-mapping. Un agent traite les concepts source par lots, propose pour chacun les meilleurs candidats standards, et produit un score combiné :

linkr-v2-b1800b.frama.io
Jeux de conceptsRechercherSuggestions
ScoreMéthodesVocabNom du conceptCodeStd
92%
95%88%91%
LOINCCreatinine [Mass/volume] in Serum or Plasma2160-0S
81%
83%79%82%
LOINCCreatinine [Mass/volume] in Blood38483-4S
76%
80%71%77%
LOINCCreatinine [Moles/volume] in Serum or Plasma14682-9S
68%
72%64%
SNOMEDSerum creatinine measurement113075003S
54%
58%51%
LOINCCreatinine renal clearance2164-2S
8 / 24 résultats⚙
Le panneau Suggestions : score combiné, points colorés par méthode (Syntaxique, Sémantique, Statistique, IA), gestion des poids via le bouton « Gérer les suggestions ».

Quatre familles de méthodes contribuent à ce score :

  • Syntaxique — similarité de chaînes entre le libellé local et les libellés standards (et leurs synonymes).
  • Sémantique — distance entre embeddings biomédicaux (BioLORD-2023-M), calculée hors ligne sur l’ensemble du vocabulaire OMOP.
  • Statistique (à venir) — comparaison des distributions de valeurs source/cible pour les concepts numériques.
  • Agentique — un agent LLM examine les meilleurs candidats, applique du raisonnement clinique (unités cohérentes ? bonne granularité ? bon domaine ?) et propose une équivalence (exactMatch, closeMatch, broadMatch, narrowMatch) avec une justification écrite.

Chaque suggestion reste à valider ou corriger explicitement dans l’éditeur — l’IA prépare le terrain, la décision finale revient toujours à l’évaluateur. La page Suggestions détaille tout ce mécanisme.

Métadonnées et traçabilité

Un alignement de concepts n’est pas qu’une table de correspondance. C’est aussi un dossier de traçabilité : qui a aligné quoi, quand, sur la base de quel raisonnement, avec quelle confiance, et quels commentaires ont été échangés au cours de la revue. Ces métadonnées sont précieuses pour l’équipe qui exploitera ensuite les données. Linkr les conserve avec chaque alignement et peut les exposer côté concept source — distributions, statistiques, propriétés, graphiques temporels :

linkr-v2-b1800b.frama.io

Frequence_cardiaque

ICCA · M0042 · 12 450 lignes · 1 842 patients

Statistiques

Min0
P2565
Médiane78
P7592
Max245
Moyenne82,3
Écart-type18,7

Boxplot

Distribution

Distribution par sexe

Femme(50%)
Homme(50%)

Évolution par année

201920202021202220232024

Unités hospitalières

Réanimation80%
Soins intensifs20%

Propriétés

Unitébpm
FréquenceHoraire
Complétude98 %
SourceMonitorage
La vue des métadonnées : statistiques, boxplot, histogramme, distributions et propriétés du concept source.

La structure JSON attendue est documentée sur la page Éditeur d’alignements. Elle accepte statistics (min/médiane/max/moyenne…), distributions (graphiques en barres, secteurs ou courbes), properties (paires clé/valeur libres) et metadata (métadonnées scalaires). Le tout sert à l’évaluateur pour valider un alignement en connaissance de cause.

Côté traçabilité, chaque alignement porte sa fiche de détail : libellés source et cible, statistiques, équivalence SKOS, statut, méthode, dates, et la liste des avis posés par les relecteurs. C’est cette fiche qui constitue le dossier complet d’un alignement, consultable à tout moment depuis la page Évaluation.

linkr-v2-b1800b.frama.io
TA_systolique_invasive→Invasive systolic arterial pressure
ExactApprouvé· John Doe· 14/05/2026 14:32
1 / 7

Concept source

Vocabulaire sourceICCA
Nom concept sourceTA_systolique_invasive
Code concept sourceM0118
Patients1 842
Lignes24 380

Statistiques

Min60
P25108
Médiane122
P75138
Max220

Boxplot (mmHg)

Concept cible

Vocabulaire cibleSNOMED
ID concept cible4353843
Nom concept cibleInvasive systolic arterial pressure
Code concept cible251071003
DomaineMeasurement
Classe de conceptObservable Entity
StandardS

Métadonnées de l'alignement

ÉquivalenceExact
StatusApprouvé
Score de correspondance—
Aligné parJohn Doe
Créé le14/05/2026 14:32
Dernière modification14/05/2026 14:32

Avis (1)

Jane SmithApprouvé

14/05/2026 15:08

La fiche de détail d'un alignement : concept source à gauche (libellé, code, patients, lignes, statistiques), concept cible et métadonnées d'évaluation à droite (équivalence, statut, méthode, dates, avis posés).

Réseaux multicentriques

Dans un réseau de recherche où plusieurs hôpitaux collaborent sur des projets multicentriques, chaque établissement peut prendre en charge l’alignement d’une partie des concepts et contribuer son travail à un registre commun. C’est le principe du portail Linkr (Linkr portal) : un portail statique (déployable sur GitLab Pages, GitHub Pages ou tout hébergement statique) qui regroupe plusieurs workspaces d’alignement et projets, partagés entre équipes. La Vue d’ensemble attribue les source_concept_id custom (plage ≥ 2 milliards) par hôpital, en plages disjointes, pour éviter les collisions lors de la centralisation.

Outils OHDSI — l'alternative classique

La communauté OHDSI propose son propre outil pour cette étape : Usagi, une application de bureau Java. Vous chargez votre liste de codes locaux, Usagi propose des concepts standards par similarité textuelle, et vous validez à la main. Le format de sortie est compatible avec SOURCE_TO_CONCEPT_MAP. Usagi est stable, éprouvé, gratuit, hors ligne — la voie historique, qui reste tout à fait valable.

Le routage par domain_id

Une fois SOURCE_TO_CONCEPT_MAP produite, le reste de l’ETL devient surtout mécanique. Pour chaque ligne de donnée source, on regarde :

  1. Quel est le concept standard OMOP associé (via la jointure SOURCE_TO_CONCEPT_MAP) ?
  2. Quel est son domain_id ?

Et le domain_id détermine directement la table OMOP de destination :

domain_idTable OMOP cibleExemples
ConditionCONDITION_OCCURRENCEDiabète, sepsis, hypertension
DrugDRUG_EXPOSUREAspirine, héparine, furosémide
ProcedurePROCEDURE_OCCURRENCEColoscopie, intubation, cathétérisme
MeasurementMEASUREMENTCréatinine, fréquence cardiaque, hémoglobine
DeviceDEVICE_EXPOSURECathéter, sonde, ventilateur
ObservationOBSERVATIONStatut, mode d’assurance, langue
VisitVISIT_OCCURRENCE / VISIT_DETAILHospitalisation, urgences, séjour ICU

Tout ce qui ne tombe dans aucune de ces tables atterrit par convention dans OBSERVATION — la table générique d’accueil prévue par OMOP pour ces cas. La logique d’implémentation devient alors la même pour toutes les tables d’événements : extraire les lignes source, joindre sur SOURCE_TO_CONCEPT_MAP, filtrer par domain_id du target, insérer dans la table cible avec les _concept_id standards, les _source_concept_id custom et les _source_value pour la traçabilité.

Construire un ETL MIMIC-IV → OMOP, pas à pas

Place à la pratique. On va construire ensemble une mini-base OMOP à partir de MIMIC-IV, en exécutant le SQL dans votre navigateur (via DuckDB-Wasm). Les données source sont celles de la démo publique MIMIC-IV — 100 patients, leurs séjours, leurs mesures, leurs prescriptions.

Avant d’attaquer le SQL, voici les cinq étapes de la démarche :

A

Extraire les concepts source

Inventorier ce qui est à aligner : une ligne par concept, avec ses fréquences et un info_json de métadonnées (distributions, unités, histogrammes…).

B

Récupérer la table d’alignement

L’alignement (manuel ou assisté par IA dans Linkr) produit le fichier source_to_concept_map.csv qui mappe chaque code source vers son concept standard.

C

Charger les vocabulaires de référence

Importer CONCEPT, CONCEPT_RELATIONSHIP et compagnie depuis ATHENA, plus le SOURCE_TO_CONCEPT_MAP produit à l’étape B.

D

Peupler les tables OMOP

D’abord les tables d’ancrage (PERSON, VISIT_OCCURRENCE, VISIT_DETAIL), puis une CTE de routage par domain_id qui peuple MEASUREMENT, CONDITION_OCCURRENCE et OBSERVATION. DRUG_EXPOSURE est traitée séparément.

E

Contrôler la qualité

Lancer ACHILLES et le Data Quality Dashboard, repérer les anomalies, corriger, recommencer.

Étape A — Extraire les concepts source

La toute première étape de tout ETL OMOP n’est pas un INSERT : c’est l’inventaire de ce qu’il y a à aligner. Pour notre périmètre (démographie, types de visite, parcours, diagnostics, mesures et médicaments), on extrait depuis les tables MIMIC les concepts source distincts, avec leurs fréquences (nombre de lignes, nombre de patients distincts) et quelques métadonnées (unités, catégories…) qui aideront ensuite l’évaluateur. La logique d’extraction tient en une requête SQL par catégorie :

Voici un extrait représentatif du SQL d’extraction pour les paramètres biologiques de labevents. La requête calcule en une passe les statistiques descriptives, l’histogramme, la distribution par sexe, la fréquence typique des mesures et la distribution temporelle — toutes assemblées dans un objet JSON unique qui suit le format attendu par Linkr (les autres catégories — chartevents, diagnoses_icd, transfers, admissions, prescriptions — suivent un patron très proche, ajusté selon que le concept est numérique ou catégoriel) :

-- Paramètres biologiques (labevents) avec info_json riche
WITH lab_stats AS (
  SELECT
    le.itemid,
    COUNT(*)                                       AS record_count,
    COUNT(DISTINCT le.subject_id)                  AS patient_count,
    MIN(le.valuenum)                               AS v_min,
    MAX(le.valuenum)                               AS v_max,
    AVG(le.valuenum)                               AS v_mean,
    STDDEV(le.valuenum)                            AS v_sd,
    QUANTILE_CONT(le.valuenum, 0.05)               AS p5,
    QUANTILE_CONT(le.valuenum, 0.25)               AS p25,
    QUANTILE_CONT(le.valuenum, 0.50)               AS p50,
    QUANTILE_CONT(le.valuenum, 0.75)               AS p75,
    QUANTILE_CONT(le.valuenum, 0.95)               AS p95
  FROM labevents le
  WHERE le.itemid IN (50971, 50983, 50912, 50882, 51265, 51222, 51301, 50931)
  GROUP BY le.itemid
)
SELECT
  'MIMIC_LABITEMS'                                 AS vocabulary,
  'Biologie'                                       AS category,
  CAST(le.itemid AS VARCHAR)                       AS concept_code,
  ANY_VALUE(dli.label)                             AS concept_name,
  ANY_VALUE(s.record_count)                        AS record_count,
  ANY_VALUE(s.patient_count)                       AS patient_count,
  -- Compose the JSON metadata expected by Linkr's mapping editor
  json_object(
    'full_name',  'Lab test / ' || ANY_VALUE(dli.label),
    'data_source', 'MIMIC-IV',
    'data_types',  'numeric',
    'unit',        ANY_VALUE(le.valueuom),
    'numeric_data', json_object(
      'min',    ANY_VALUE(s.v_min),
      'p5',     ANY_VALUE(s.p5),
      'p25',    ANY_VALUE(s.p25),
      'median', ANY_VALUE(s.p50),
      'mean',   ANY_VALUE(s.v_mean),
      'p75',    ANY_VALUE(s.p75),
      'p95',    ANY_VALUE(s.p95),
      'max',    ANY_VALUE(s.v_max),
      'sd',     ANY_VALUE(s.v_sd)
    )
    -- + histogram, categorical_data (sex), temporal_distribution
    -- (assemblés via des sous-requêtes du même genre)
  )                                                AS info_json
FROM labevents le
JOIN d_labitems dli ON dli.itemid = le.itemid
JOIN lab_stats s    ON s.itemid   = le.itemid
WHERE le.itemid IN (50971, 50983, 50912, 50882, 51265, 51222, 51301, 50931)
GROUP BY le.itemid;

La colonne info_json qui en résulte suit la structure documentée dans la page Éditeur d’alignements, que Linkr lit pour afficher la fiche détaillée du concept source. Pour un signe vital comme la fréquence cardiaque, le résultat ressemble à :

{
  "full_name": "Vital sign / Routine Vital Signs / Heart Rate",
  "data_source": "MIMIC-IV",
  "data_types": "numeric",
  "unit": "bpm",
  "missing_rate": 0.0,
  "numeric_data": {
    "min": 0.0, "p5": 62.0, "p25": 78.0, "median": 90.0, "mean": 91.12,
    "p75": 104.0, "p95": 122.0, "max": 200.0, "sd": 18.69
  },
  "histogram": [
    { "x": 10.0, "count": 3 },    { "x": 30.0, "count": 12 },
    { "x": 50.0, "count": 454 },  { "x": 70.0, "count": 3313 },
    { "x": 90.0, "count": 5555 }, { "x": 110.0, "count": 3685 },
    { "x": 130.0, "count": 764 }, { "x": 150.0, "count": 117 }
  ],
  "categorical_data": [
    { "category": "Female", "count": 6284, "percentage": 45.2 },
    { "category": "Male",   "count": 7629, "percentage": 54.8 }
  ],
  "temporal_distribution": {
    "start_date": "2110-04-11", "end_date": "2201-12-13",
    "by_year": [ { "year": 2110, "percentage": 6.5 }, … ]
  },
  "measurement_frequency": "every 1 hours"
}

Le résultat final est un CSV avec une ligne par concept source — pour notre périmètre, 46 concepts au total (2 sexes + 7 types d’admission + 8 unités + 10 diagnostics + 8 vitaux + 8 paramètres bio + 3 médicaments).

Télécharger source-concepts.csv

Pour aller plus loin, ouvrez la démo Linkr et :

  1. Créez un nouveau projet d’alignement (voir la doc Projets d’alignement).
  2. Importez le fichier source-concepts.csv ci-dessus, en mappant les colonnes selon les paramètres d’import présentés plus haut.
  3. Procédez à l’alignement concept par concept dans l’éditeur — manuel, ou avec l’assistance IA via la page Suggestions.

Pour les besoins de la suite, on saute cette étape et on récupère directement la table d’alignement déjà produite.

Étape B — Récupérer la table d’alignement

On suppose ici l’alignement bouclé : voici le fichier final, prêt à être chargé dans la base OMOP.

Télécharger source_to_concept_map.csv

46 lignes, 9 colonnes au total (le détail complet est dans la référence de la table source_to_concept_map). Trois lignes représentatives, dans l’ordre des colonnes du schéma :

source_codesource_concept_idsource_vocabulary_idsource_code_descriptiontarget_concept_idtarget_vocabulary_idvalid_start_datevalid_end_dateinvalid_reason
F2000000001MIMIC_GENDERFemale8532Gender1970-01-012099-12-31NULL
2200452000000028MIMIC_CHARTEVENTSHeart Rate3027018LOINC1970-01-012099-12-31NULL
Aspirin2000000044MIMIC_DRUGAspirin1112807RxNorm1970-01-012099-12-31NULL

Notez les source_concept_id dans la plage custom OMOP (≥ 2 milliards) et les target_concept_id qui pointent vers les concepts standards.

Étape C — Charger les vocabulaires de référence dans la base

Avant d’écrire les INSERT métier, la base OMOP cible doit contenir le dictionnaire des concepts standards — typiquement les tables CONCEPT, CONCEPT_RELATIONSHIP, CONCEPT_SYNONYM, CONCEPT_ANCESTOR, VOCABULARY, DOMAIN. On les récupère depuis ATHENA (compte gratuit nécessaire) en cochant les vocabulaires utiles à notre périmètre (SNOMED CT, LOINC, RxNorm, ICD10CM, UCUM…). Le téléchargement renvoie un ZIP de fichiers CSV à charger via un COPY ou un INSERT INTO ... SELECT FROM read_csv(...).

Une fois ces vocabulaires en place, on charge également notre fichier source_to_concept_map.csv dans la table éponyme, et on ajoute les concepts custom (les source_concept_id ≥ 2 milliards de notre STCM) dans la table CONCEPT — c’est ce qui permettra aux _source_concept_id de nos lignes d’événements de pointer vers des entrées valides de la base.

Pré-requis du tutoriel interactif

Pour simplifier la démo qui suit, le composant interactif ne charge pas la table CONCEPT entière : il prépare uniquement la table source_to_concept_map avec nos 46 alignements et crée les tables OMOP cibles vides. Sur une vraie base OMOP, l’étape C est obligatoire — sans elle, ni ACHILLES ni le DQD ne peuvent fonctionner correctement, et les requêtes utilisant le vocabulaire pour explorer les hiérarchies (par exemple CONCEPT_ANCESTOR) ne renverront aucun résultat.

Étape D — Peupler les tables OMOP

Place à l’ETL proprement dit. Les données MIMIC raw et la table d’alignement sont déjà chargées dans une base DuckDB qui tourne dans votre navigateur. Les tables OMOP cibles (PERSON, VISIT_OCCURRENCE, VISIT_DETAIL, MEASUREMENT, CONDITION_OCCURRENCE, OBSERVATION, DRUG_EXPOSURE) sont créées vides au schéma v5.4.

1

Tables d’ancrage

Trois tables sont peuplées en premier — chacune avec son propre INSERT :

  • PERSON depuis patients (gender, year of birth dérivé de anchor_year - anchor_age).
  • VISIT_OCCURRENCE depuis admissions (un séjour = un hadm_id).
  • VISIT_DETAIL depuis transfers (un passage en unité de soins = un transfer_id).
2

Routage par domain_id

Sur MIMIC, les vocabulaires sources sont cloisonnés par domaine (MIMIC_CHARTEVENTS → Measurement, MIMIC_DRUG → Drug, MIMIC_ICD10 → Condition…) — on pourrait théoriquement écrire un INSERT par vocabulaire en se reposant sur ce cloisonnement.

Cependant, pour rester généraliste et coller à l’approche décrite plus haut, on prépare une CTE matérialisée mapped_events qui unifie les sources d’événements (diagnostics, signes vitaux, biologies) et résout le domain_id du concept cible via CONCEPT. On filtre ensuite cette CTE par target_domain_id pour dispatcher vers la bonne table OMOP :

  • target_domain_id = 'Measurement' → MEASUREMENT
  • target_domain_id = 'Condition' → CONDITION_OCCURRENCE
  • Tout le reste → OBSERVATION (table de repli par défaut)

PROCEDURE_OCCURRENCE, DEVICE_EXPOSURE, SPECIMEN, NOTE… suivent exactement la même logique — il suffit d’ajouter une branche WHERE target_domain_id = '…' ; on les laisse de côté ici, on n’a pas de concept correspondant dans notre périmètre.

DRUG_EXPOSURE est traitée à part : ses sources et ses spécificités (dose, route, unité) ne se prêtent pas au même routeur générique.

Cas d'un vocabulaire source hétérogène

Sur des bases moins propres — typiquement un vocabulaire interne d’hôpital qui mélange dans un même tableau de codes des biologies, des observations cliniques, des scores, voire des médicaments — le cloisonnement par vocabulaire ne marche plus. La CTE mapped_events décrite ici, qui résout le domain_id via CONCEPT, devient alors indispensable : c’est la seule manière de router correctement chaque ligne. C’est précisément pour cette raison qu’on adopte cette approche dès le départ — au prix d’une jointure supplémentaire — plutôt qu’un INSERT par vocabulaire.

Chaque table OMOP a sa propre section ci-dessous, avec sa requête INSERT INTO prête à l’emploi suivie d’un SELECT qui affiche un aperçu du résultat. Pour exécuter une requête, cliquez sur Exécuter (ou utilisez le raccourci ⌘ / Ctrl + Entrée). Le bouton Réinitialiser la requête au-dessus de chaque éditeur remet la version d’origine en un clic. Lancez les requêtes dans l’ordre : mapped_events doit exister avant les INSERT de routage, et PERSON / VISIT_OCCURRENCE doivent exister avant DRUG_EXPOSURE.

Le pattern est toujours le même : JOIN sur source_to_concept_map, INSERT dans la table cible, COALESCE(..., 0) pour les codes non alignés afin de ne pas perdre les lignes, et les colonnes _source_value et _source_concept_id conservent la traçabilité vers les données d’origine.

DRUG_EXPOSURE — version pédagogique simplifiée

L’INSERT dans DRUG_EXPOSURE ci-dessus est volontairement minimaliste : on extrait la quantité de form_val_disp par regex, on remplit route_source_value, dose_unit_source_value et drug_source_value, et c’est tout. Une vraie implémentation calcule la dose réellement reçue à partir de amount_value/amount_unit (quantité unitaire), numerator_value/denominator_value (concentration des solutions), de la durée d’administration et de la fréquence — et s’appuie sur DRUG_STRENGTH côté vocabulaire pour résoudre les présentations des spécialités. Les formules sont détaillées dans la documentation OMOP « drug dose ». À calculer dans un vrai projet, on ne le fait pas ici pour ne pas alourdir le tutoriel.

Étape E — Valider la qualité de la base produite

Une fois les INSERT bouclés, la base OMOP existe. Mais elle peut contenir des incohérences silencieuses : dates aberrantes, concepts non standards là où on attend un standard, champs obligatoires nuls. Avant toute utilisation pour la recherche, il faut mesurer la qualité.

Deux outils OHDSI sont conçus pour ça :

  • ACHILLES — un package R qui produit un rapport synthétique de caractérisation : nombre de patients, distributions d’âge, prévalences des conditions, fréquences des médicaments, distributions des mesures biologiques. Excellent pour prendre la mesure de ce que contient la base — et repérer rapidement les anomalies grossières.
  • Data Quality Dashboard (DQD) — environ 4 000 contrôles automatisés répartis en trois axes : conformité du schéma, complétude des champs obligatoires, plausibilité des valeurs. Le rapport HTML interactif permet de prioriser les corrections.

À l’usage, le DQD remonte presque toujours des centaines d’anomalies sur une base fraîchement produite. La plupart sont mineures (concepts non standards, dates manquantes sur des événements optionnels…), quelques-unes pointent de vrais bugs dans l’ETL. C’est un cycle itératif : on lance, on corrige, on relance, jusqu’à un niveau de qualité jugé suffisant pour la diffusion.

Tables vides et DQD

Le DQD gère explicitement le cas des tables non peuplées — c’est précisément le scénario d’un ETL projet par projet. Si une table existe mais reste vide, tous les contrôles de niveau champ et concept la concernant sont marqués notApplicable (les détails sont dans la documentation officielle des statuts). Ces contrôles ne comptent pas comme des anomalies. Vous pouvez en plus exclure proactivement certaines tables via le paramètre tablesToExclude d’executeDqChecks() — par défaut, le DQD exclut déjà les tables de vocabulaire (CONCEPT, VOCABULARY, etc.).

Et après ?

Une fois votre première base OMOP en place, le projet suivant réutilisera les alignements existants et n’ajoutera que quelques nouveaux concepts. Au fil des projets, votre couverture du dictionnaire local devient conséquente — sans avoir eu à tout aligner en une seule fois.

  • L'ETL OMOP n'a pas besoin d'être réalisé d'un seul bloc : on procède projet par projet, en alignant uniquement les concepts dont le projet a réellement besoin.
  • L'alignement sémantique est la partie la plus chronophage. Elle mobilise les experts du domaine (biologistes, pharmaciens, cliniciens) et produit la table SOURCE_TO_CONCEPT_MAP.
  • L'IA agentique (Claude Code intégré à Linkr, scores syntaxiques + sémantiques + LLM avec équivalences justifiées) accélère considérablement la première passe — chaque suggestion reste validée explicitement par un évaluateur.
  • Une fois l'alignement en place, le routage suit le domain_id du concept cible : Condition → CONDITION_OCCURRENCE, Drug → DRUG_EXPOSURE, Measurement → MEASUREMENT, etc. Tout ce qui ne tombe pas ailleurs atterrit dans OBSERVATION, la table de repli par défaut prévue par OMOP.
  • Toutes les tables d'événements suivent la même structure-type : extraction + JOIN sur STCM + INSERT avec les colonnes _concept_id (standard), _source_concept_id (custom, plage ≥ 2 milliards) et _source_value (libellé d'origine, pour traçabilité).
  • Les réseaux multicentriques centralisent les alignements via linkr-portal : un portail statique partagé entre établissements, avec plages d'IDs custom disjointes pour éviter les collisions.
  • La validation finale par ACHILLES et le Data Quality Dashboard est un cycle itératif : on lance, on corrige, on relance — jusqu'à un niveau de qualité diffusable.
Article suivant : SQL OMOP — Niveau débutant
PrécédentL'écosystème d'outils OHDSISuivantSQL — Niveau débutant

Produit

  • Accueil
  • Démo

Ressources

  • Documentation
  • Ressources
  • Outils
  • Blog

Communauté

  • Code source Framagit
  • Code source Github

À propos

  • InterHop.org
  • Contact

2021–2026 InterHop — CC BY-NC-SA 4.0 (site) · GPLv3 (logiciel)