Ingénierie des données · septembre 2026

Une chaîne de données ECG, des téléchargements publics à une base traçable

Douze distributions publiques d'ECG arrivent dans des formats sur lesquels leurs éditeurs ne se sont jamais accordés. J'ai construit la chaîne qui les prend telles qu'elles sont publiées et en fait une seule base Parquet, où chaque tracé remonte à son fichier source en une jointure. Elle tourne sur des corpus publics, et aucun modèle n'y est entraîné.

12 distributions · 110 876 enregistrements · 87 609 tracés distincts
Chiffres lus dans les fichiers de résultats au commit e066fde

Comment la chaîne tourne

Chaque étape écrit un fichier de résultats dans le dépôt, et chaque chiffre de cette page est lu dans l'un d'eux.

La chaîne en six étapes, dans l'ordre où elles tournent : provenance, ingestion, contrôle qualité, doublons, catalogue Parquet, et la requête qui remonte d'un tracé à son fichier source.Provenancechaque fichier comparéà l'empreinte de son éditeurIngestiondouze distributions,prises telles quellesContrôle qualitédérivations plates, écrêtées,absentes ou non finiesDoublonsdans une distributionet entre éditionsCatalogue Parquetonze tables,lues sur place par DuckDBRequêteune jointure, du tracéà son fichier source

Ce que les contrôles ont relevé

Chacun de ces défauts a été relevé par une étape de la chaîne et se compte dans un fichier de résultats.

Une unité, trois graphies

Les en-têtes déclarent la même unité de trois façons. Sur les 12 distributions, 7 écrivent mV, 3 écrivent mv, et 2 n'en déclarent aucune, ce qui laisse au lecteur le soin de la deviner. Le relevé des enregistrements garde la graphie déclarée par chaque en-tête, si bien que l'écart se voit avant qu'un seul signal soit lu.

Dérivations plates et écrêtées à Ningbo

Chaque enregistrement est jugé sur les dix premières secondes de ses douze dérivations standard, ou sur tout l'enregistrement quand il est plus court. À Ningbo, 1 583 enregistrements sur 34 905 échouent à au moins un contrôle. Parmi eux, 1 480 ont une dérivation plate, 20 une dérivation écrêtée à sa valeur extrême et 97 des échantillons non finis, un même enregistrement pouvant échouer à plusieurs contrôles. Toutes distributions confondues, 2 320 enregistrements échouent, et Ningbo en compte plus que les onze autres réunies. Les enregistrements restent dans la base avec leur verdict dans la table de qualité, et le lecteur filtre dessus.

Le même tracé deux fois dans CPSC-2018

Sur les 6 877 enregistrements de CPSC-2018, 505 tombent dans 250 groupes de tracés identiques, donc 255 enregistrements répètent un tracé que la distribution contient déjà. J'ai ouvert et comparé 30 groupes de répétitions, tirés avec une graine fixe dans CPSC-2018 et Georgia : tous sont identiques échantillon par échantillon, commentaires d'en-tête compris. Un découpage par patient ne garantit pas qu'un enregistrement et sa copie tombent du même côté, et CPSC-2018 ne publie de toute façon aucun identifiant de patient, donc chaque enregistrement a un groupe de signal sur lequel découper.

Deux PTB-XL qui ne concordent pas

PhysioNet publie PTB-XL avec 21 799 enregistrements. Le lot du PhysioNet Challenge 2021 le reprend avec 21 837, sous d'autres identifiants. Chaque original se retrouve dans le lot par son nom, et les 38 enregistrements du lot qui restent répètent un tracé que le lot contient déjà. La clé de la base est la distribution et non le nom du corpus, donc les deux distributions restent séparées et le lien entre elles est enregistré.

Un fichier dont l'empreinte diffère de celle publiée

À côté de chacun des 222 301 fichiers vers lesquels elle pointe, la base garde l'empreinte déclarée par son éditeur. Sur les 224 882 fichiers dont l'empreinte est recalculée et comparée au manifeste de leur éditeur, 224 881 concordent avec l'empreinte publiée. Celui qui diffère est un fichier de signal de Ningbo. Il se lit en douze dérivations et passe tous les contrôles de qualité, et seule la comparaison au manifeste le révèle. Le fichier de résultats garde côte à côte l'empreinte publiée et l'empreinte observée.

Comment elle est testée et reconstruite

Le dépôt contient 303 tests. L'intégration continue en lance 277 à chaque push sur main et à chaque pull request, après ruff, le contrôle de format et mypy. Les 26 autres ont besoin des corpus sur disque et tournent sur la machine où se trouvent les téléchargements.

Les passes qui lisent les signaux tournent une à la fois, chacune sous un plafond de mémoire et un verrou. Le criblage des doublons — profilé phase par phase sur une paire d'éditions — occupe au plus 272 Mo de mémoire résidente. Le plafond est posé sur ce qu'une passe retient, mesuré ainsi, et non sur le compteur du cgroup, qui compte aussi le cache des fichiers en cours de lecture.

Ni les corpus ni le Parquet ne sont dans le dépôt. Les corpus se téléchargent sur PhysioNet, les scripts du dépôt en reconstruisent le Parquet, et chaque table de codes publiée est comparée, avant d'être lue, à l'empreinte relevée à son téléchargement. À partir de la table d'étiquettes reconstruite, les 240 cases du tableau des diagnostics retenus pour le score du PhysioNet Challenge 2021 sont recalculées et concordent avec les effectifs publiés par les organisateurs.

Rejouer une requête

Une fois la livraison reconstruite, cette requête DuckDB remonte d'un enregistrement au fichier dont il vient. C'est la requête de traçage du dépôt, écrite directement sur les fichiers Parquet, et le test du dépôt compte ses jointures et échoue si elle en gagne une.

SELECT r.record_id, r.source_id, f.relative_path, f.sha256_declared
FROM read_parquet('results/delivery/record.parquet') AS r
JOIN read_parquet('results/delivery/source_file.parquet') AS f
  ON f.file_id = r.signal_file_id
WHERE r.record_id = 'challenge-2021/chapman_shaoxing:JS02202';

Ce qu'elle rend, tel qu'enregistré dans results/database_report.json :

record_id        challenge-2021/chapman_shaoxing:JS02202
source_id        challenge-2021/chapman_shaoxing
relative_path    g3/JS02202.mat
sha256_declared  cd1e882875df52351103b3e9611133a5efad0fedfd93437b82ca53900a42b580

La dernière colonne est l'empreinte publiée par PhysioNet, lue dans le fichier de sommes de contrôle livré avec le téléchargement, et non une empreinte calculée ici.

La même chaîne dans un hôpital

Cette chaîne tourne sur des corpus publics, et elle n'a jamais tourné dans un hôpital. Quatre choses séparent les deux. Elle lit des fichiers publiés une fois, quand un hôpital envoie des tracés en continu depuis ses chariots et son logiciel de gestion des ECG, souvent sous des formats constructeur que ce dépôt n'a jamais lus ; ce flux se monte avec le service informatique de l'hôpital et le constructeur. Sa livraison est faite de fichiers ouverts sur des données ouvertes, sans contrôle d'accès, alors qu'à l'hôpital, qui lit quoi se décide avec le délégué à la protection des données, dans l'analyse d'impact qu'impose un entrepôt de données de santé. En France, des données de santé confiées à un tiers doivent l'être à un hébergeur certifié HDS, donc la base irait chez un tel prestataire ou dans l'infrastructure de l'hôpital, et non sur la machine où elle tourne aujourd'hui. Enfin, rien ici ne surveille les passes une fois lancées ; la supervision et l'astreinte qui va avec relèvent de l'équipe d'exploitation de l'hôpital. Le travail sur les données montré plus haut se transposerait aux exports d'un hôpital, à la place de ceux de PhysioNet.

Où vérifier les chiffres

Le dépôt contient le code, les tests et les fichiers de résultats dont chaque chiffre ci-dessus est tiré, au commit cité en tête de page.