Aller au contenu

Décisions d'architecture (ADR)

Fiches courtes : Contexte → Décision → Conséquences → Source. Une décision majeure par fiche. Index :

#DécisionStatut
ADR-001Résoudre vers Mondo (CC0) comme pivotadopté
ADR-002Contract-first (OpenAPI source de vérité)adopté
ADR-003Snapshot dénormalisé > FK (audit)adopté
ADR-004Codec JSONB asyncpgadopté
ADR-005Identifiant vs contenu (licence)adopté
ADR-006Émission par précédence + abstentionadopté
ADR-007Voie 1 (CC0) vs voie 1.5 (UMLS licenciée)adopté
ADR-008Repli NCIt CC0adopté
ADR-009Curation onco en 2 tempsadopté
ADR-010Exécuteur de build = runner hostadopté
ADR-011Activation in-place (bind mount)adopté
ADR-012meta.json déclaré + backfilladopté
ADR-013Pseudonymisation à l’exportadopté
ADR-014Build Mondo manueladopté
ADR-015SPHN : SNOMED, identifiants seuls ; TNM/BBMRI hors périmètreadopté
ADR-016Exigence de granularité SCTIDouvert

Résoudre vers Mondo (CC0) comme pivot.

  • Contexte : il faut une ontologie maladie riche, multilingue-friendly et redistribuable.
  • Décision : Mondo comme pivot (CC0, riche en xrefs) ; SNOMED atteint via un pont.
  • Conséquences : open-core possible ; mais trou-pivot (granularité) → ADR-006/016.
  • Source : notes/15, 33, 44 → Pont terminologique & émission (Mondo→SNOMED).

Contract-first (OpenAPI = source de vérité).

  • Contexte : 2 langages (Python API, TS frontend) doivent rester synchrones.
  • Décision : openapi.yaml est la vérité ; types TS + schémas Pydantic en dérivent.
  • Conséquences : drift cassé au compile-time TS ; éditer le contrat d’abord.
  • Source : repo/CLAUDE.md → Architecture.

Snapshot dénormalisé > FK pour l’audit.

  • Contexte : l’audit doit survivre aux changements (renommage user, suppression).
  • Décision : figer des libellés au moment de l’action (validated_by, actor_label) plutôt que des FK.
  • Conséquences : audit stable et lisible ; léger surcoût de stockage.
  • Source : repo/CLAUDE.md.

Codec JSONB asyncpg (encoder=json.dumps).

  • Contexte : champs JSONB (settings, candidates, snapshots).
  • Décision : codec posé → passer les dict directement, PAS de json.dumps() + cast ::jsonb (sinon double-encodage, JSONB stocké comme string).
  • Conséquences : au décodage, certains JSONB reviennent en strjson.loads défensif.
  • Source : repo/CLAUDE.md.

Identifiant vs contenu (licence).

  • Contexte : SNOMED/UMLS licenciés ; Mondo/NCIt CC0.
  • Décision : ne redistribuer que des identifiants ; jamais le contenu licencié ; libellés SNOMED lus à la volée, jamais persistés ; artefacts licenciés BYO RO.
  • Conséquences : snapshots label-free ; dashboard = comptes ; séparation physique.
  • Source : notes/50, D22 → Modèle de licence (open-core).

Émission par précédence + abstention.

  • Contexte : émission aveugle = 33–39 % de faux ; justesse plafonnée par le pivot.
  • Décision : paliers exact > confirmed > candidate > approximate > ncit > uncoded ; auto-émission seulement sur ancre confiante ; sinon dégradation / abstention.
  • Conséquences : moins de codes mais fiables ; charge de confirmation humaine.
  • Source : notes/47-49 → Pont terminologique & émission (Mondo→SNOMED).

Voie 1 (CC0) vs voie 1.5 (UMLS licenciée).

  • Contexte : SNOMED atteignable directement (xref Mondo CC0) ou via UMLS (licencié).
  • Décision : séparer par tier ; voie 1.5 produit des codes candidate PENDING (cardinalité = signal de confiance), confirmés par un humain.
  • Conséquences : carte UMLS BYO gitignorée ; provenance par code.
  • Source : notes/50.

Repli NCIt (CC0) quand SNOMED absent.

  • Contexte : certains concepts n’ont pas de SNOMED émis.
  • Décision : émettre un code NCIt (CC0) plutôt que rien — code d’interop alternatif.
  • Conséquences : meilleure couverture sans code SNOMED douteux.
  • Source : notes/historique émission → Pont terminologique & émission (Mondo→SNOMED).

Curation onco en 2 temps (pending_oncology).

  • Contexte : une tumeur = ancre + axes ; valider l’ancre ne suffit pas.
  • Décision : 1ᵉʳ accept → pending_oncology (file dédiée) ; 2ᵉ accept (axes) → validated.
  • Conséquences : file onco séparée ; tests doivent éviter les mentions onco si non voulu.
  • Source : notes/41 → Moteur compositionnel & oncologie (MC).

Exécuteur de build = runner host.

  • Contexte : l’API monte l’index RO et n’embarque pas le builder (kb_builders ⊂ MC, host).
  • Décision : l’UI enfile une requête ; un runner host (timer systemd) la consomme. Pas de conteneur builder dédié.
  • Conséquences : daemon out-of-band + accès DB host ; SNOMED vérifié réel ; UMLS = +restart.
  • Source : notes/54 → Gestion des ontologies, Opérations & infra.

Activation in-place (os.replace), car l’index est un bind mount.

  • Contexte : un swap par rename de dossier n’est pas vu par le conteneur (bind mount).
  • Décision : build dans un dossier temp → os.replace fichier par fichier (atomique, même FS) ; pas de restart conteneur.
  • Conséquences : activation sans downtime ; brève fenêtre d’incohérence inter-fichiers acceptable (op admin).
  • Source : notes/54.

meta.json déclaré + backfill (vs scan live).

  • Contexte : le dashboard dérivait/scannait (couverture, version) à chaque lecture.
  • Décision : les builders émettent les champs déclarés ; un backfill les pose sans reprocess ; le dashboard lit le déclaré avec fallback.
  • Conséquences : plus rapide, traçable (provenance/sha) ; rétrocompatible.
  • Source : notes/52 → Gestion des ontologies.

Pseudonymisation déterministe à l’export.

  • Contexte : les livrables peuvent quitter l’institution (nLPD).
  • Décision : curator_<sha256(user_id)[:8]> à la sortie (pas de colonne en base).
  • Conséquences : 0 PII dans les livrables biobanque/SPHN ; audit_training reste nominatif (interne).
  • Source : Jalon 6 → Imports & exports (interop).

Build Mondo manuel (KB linker = xmen GPU).

  • Contexte : le KB du linker est un index xmen/FAISS (rebuild GPU + alias DE séparés), pas l’index compositionnel MC.
  • Décision : Mondo détecté dans l’UI mais non déclenchable (un one-click régresserait le multilingue / serait lourd) ; build = procédure host documentée.
  • Conséquences : asymétrie assumée avec SNOMED/UMLS (triggerables) ; Phase 3 si besoin.
  • Source : notes/54.

SPHN : SNOMED, identifiants seuls ; TNM/BBMRI hors périmètre.

  • Contexte : SPHN attend historiquement de l’ICD-O-3 ; Messier produit du SNOMED.
  • Décision : émettre du SNOMED (seule option légale), identifiants seuls ; TNM (staging) et specimen BBMRI hors périmètre (linking ≠ staging).
  • Conséquences : binding site/histologie ICD-O-3-vs-SNOMED = décision externe SPHN / CHU partenaire.
  • Source : notes/51, 53 → Imports & exports (interop).

Exigence de granularité SCTID (ouvert).

  • Contexte : le pivot Mondo plafonne la justesse exacte ; le pilote doit choisir.
  • Décision (à prendre) : SCTID niveau maladie (granularité Mondo, atteignable) vs SCTID clinique exact (nécessite un linker SNOMED-direct, piste O3).
  • Conséquences : conditionne l’ouverture de O3 (56 % des maladies rencontrées sans pivot).
  • Source : notes/48 → Pont terminologique & émission (Mondo→SNOMED).