Objectif pedagogique : Comprendre l'interoperabilite en sante a travers un systeme distribue realiste de 11 conteneurs Docker communiquant via des API REST et le standard FHIR R5, avec gestion des terminologies medicales (ICD-11 & LOINC) et alias locaux par structure.
- Vue d'ensemble
- Architecture du systeme
- Guide Enseignant — Mise en route
- Guide Etudiant — Explorer et comprendre
- Standards et terminologies utilises
- Terminologies et alias locaux
- Structure des fichiers
- Reference API
- Scenarios cliniques de demonstration
- Aller plus loin
Ce projet simule un echange d'informations de sante (Health Information Exchange — HIE) entre :
- 2 hopitaux ayant chacun leur propre systeme d'information (SIH) et leur base de donnees
- 4 registres nationaux (identite, etablissements, professionnels de sante, index central)
- 1 serveur de terminologies (ICD-11 & LOINC) servant de reference pour les codes medicaux
- 1 portail patient permettant a un citoyen de consulter son dossier medical unifie
Chaque composant tourne dans son propre conteneur Docker avec sa propre adresse IP, exactement comme dans un systeme de sante reel ou les hopitaux, registres et portails sont des systemes independants qui communiquent par le reseau.
Dans la realite, quand un patient consulte dans plusieurs hopitaux :
- Chaque hopital a sa propre base de donnees (pas de base partagee)
- Pour reunir le dossier complet, il faut interroger chaque hopital via des API standardisees
- Les registres nationaux servent de "pages jaunes" pour localiser les donnees du patient
- Le standard FHIR garantit que tous les systemes parlent le meme langage
- Les terminologies (ICD-11, LOINC) permettent de coder les diagnostics et analyses avec un langage universel, meme si chaque hopital utilise ses propres abbreviations (alias locaux)
Ce projet reproduit exactement ce fonctionnement.
┌─────────────────────────┐
│ Portail Patient │ :3003
│ (React + FastAPI) │
└────────────┬─────────────┘
│
┌─────────────────────────────┼────────────────────────────┐
│ │ │
┌───────▼────────┐ ┌──────────▼──────────┐ ┌─────────▼────────┐
│ Registre NNI │ │ Registre Central │ │Registre Etab. │
│ :8001 │ │ :8004 │◄────►│ :8002 │
│ (Identite) │ │ (Index patients) │ │ (MFL) │
└────────────────┘ └──────────┬───────────┘ └─────────┬────────┘
│ │
┌─────────────┼──────────────┐ ┌────────▼────────┐
│ │ │ Registre RHS │
┌───────▼───────┐ ┌───────▼───────┐ :8003 │
│ SIH Struct.A │ │ SIH Struct.B │ (HWR) │
│ :3001/:8010 │ │ :3002/:8020 │ │
│ (Webapp) │ │ (Webapp) │─────────────┘
└───────┬───────┘ └───────┬───────┘
│ │
└──────────┬─────────────────┘
┌─────────▼──────────┐
│ Terminology Server │ :8005
│ ICD-11 & LOINC │
│ (~10 000 codes) │
└─────────────────────┘
| Conteneur | Role | Port hote | IP interne | Type |
|---|---|---|---|---|
nni-registry |
Registre National d'Identite (NNI) | 8001 | 172.28.0.10 | Backend API |
facility-registry |
Master Facility List (MFL) | 8002 | 172.28.0.11 | Backend API |
hwr-registry |
Registre des Ressources Humaines en Sante | 8003 | 172.28.0.12 | Backend API |
central-registry |
Index Central des Patients | 8004 | 172.28.0.13 | Backend API |
terminology-server |
Serveur de terminologies ICD-11 & LOINC | 8005 | 172.28.0.14 | Backend API |
sih-a-backend |
SIH Hopital National de Nouakchott | 8010 | 172.28.0.20 | Backend API |
sih-a-frontend |
Interface web Hopital National | 3001 | 172.28.0.21 | Frontend React |
sih-b-backend |
SIH Hopital Cheikh Zayed | 8020 | 172.28.0.30 | Backend API |
sih-b-frontend |
Interface web Hopital Cheikh Zayed | 3002 | 172.28.0.31 | Frontend React |
portal-backend |
Aggregateur du Portail Patient | 8040 | 172.28.0.40 | Backend API |
portal-frontend |
Interface web Portail Patient | 3003 | 172.28.0.41 | Frontend React |
- Docker et Docker Compose installes (docker.com)
- 8 Go de RAM minimum disponibles pour Docker
- Ports 3001-3003 et 8001-8005, 8010, 8020, 8040 libres sur la machine
cd multi_services
docker compose up --build -dLe premier build prend 5 a 10 minutes (telechargement des images Python et Node, installation des dependances, compilation React). Les lancements suivants sont quasi-instantanes grace au cache.
# Voir l'etat des 11 conteneurs (tous doivent etre "healthy" ou "Up")
docker compose psResultat attendu : 11 conteneurs, tous en etat Up ou Up (healthy).
# 1. Registre NNI — verifier une identite
curl -s -X POST http://localhost:8001/api/verify \
-H "Content-Type: application/json" \
-d '{"nni":"1234567890"}' | python3 -m json.tool
# 2. Registre des etablissements — lister les hopitaux
curl -s http://localhost:8002/api/facilities | python3 -m json.tool
# 3. Registre central — localiser un patient
curl -s http://localhost:8004/api/locate/1234567890 | python3 -m json.tool
# 4. SIH Structure A — recuperer le dossier FHIR
curl -s "http://localhost:8010/api/fhir/patient-record?patient_nni=1234567890" | python3 -m json.tool
# 5. Portail Patient — dossier unifie complet
curl -s http://localhost:8040/api/dossier/1234567890 | python3 -m json.tool
# 6. Terminology Server — rechercher un code ICD-11
curl -s "http://localhost:8005/api/terminology/icd11/search?q=diabete&limit=5" | python3 -m json.tool
# 7. Terminology Server — rechercher un code LOINC
curl -s "http://localhost:8005/api/terminology/loinc/search?q=glucose&limit=5" | python3 -m json.tool
# 8. SIH Structure A — lister les alias locaux
curl -s http://localhost:8010/api/aliases | python3 -m json.tool# Arreter tous les conteneurs
docker compose down
# Arreter ET supprimer les volumes (remet les bases de donnees a zero)
docker compose down -vChaque backend expose une documentation interactive a /docs :
| Service | URL Swagger |
|---|---|
| Registre NNI | http://localhost:8001/docs |
| Registre Etablissements | http://localhost:8002/docs |
| Registre RHS | http://localhost:8003/docs |
| Registre Central | http://localhost:8004/docs |
| Terminology Server | http://localhost:8005/docs |
| SIH Structure A | http://localhost:8010/docs |
| SIH Structure B | http://localhost:8020/docs |
| Portail Patient | http://localhost:8040/docs |
- Montrer le schema d'architecture (section 2) avant de lancer le systeme
- Commencer par le portail patient (port 3003) — c'est le plus visuel et le plus impressionnant
- Ouvrir deux onglets SIH cote a cote (3001 et 3002) pour montrer que les donnees sont differentes
- Utiliser l'onglet Journal Inter-Services dans les SIH pour voir les appels en temps reel
- Montrer les terminologies : ouvrir l'onglet "Terminologies" dans un SIH, puis ouvrir le meme onglet dans l'autre SIH et comparer les alias pour le meme code (ex: BA00 = "HTA" vs "Maladie hypertensive")
- Utiliser la Loupe de Terminologie : dans le detail d'un sejour, basculer entre les modes alias / standard / code pour montrer la difference entre affichage local et code FHIR
- Faire les exercices de la section 4 en TD
cd multi_services
docker compose up --build -dAttendez que tous les conteneurs soient demarres (environ 2 minutes apres le build) :
docker compose ps- Ouvrez http://localhost:3003 dans votre navigateur
- Cliquez sur Fatima Mint Ahmed (bouton de demonstration)
- Observez ce qui se passe :
Etape 1 : Le portail envoie le NNI "1234567890" au Registre NNI
→ Le registre confirme l'identite de Fatima
Etape 2 : Le portail demande au Registre Central "ou Fatima a-t-elle des dossiers ?"
→ Reponse : Hopital National (MFL-NKC-001) + Hopital Cheikh Zayed (MFL-NKC-002)
Etape 3 : Le portail interroge CHAQUE hopital pour recuperer son dossier FHIR
→ Hopital A repond : Bundle FHIR (admission urgence, appendicite, analyses)
→ Hopital B repond : Bundle FHIR (transfert, appendicectomie, suivi post-op)
Etape 4 : Le portail fusionne les deux Bundle en un Dossier Patient Unifie
Point cle : Aucun hopital n'a acces a la base de donnees de l'autre. Les donnees sont echangees uniquement par des appels HTTP au format FHIR.
Ouvrez deux onglets :
- http://localhost:3001 — Hopital National de Nouakchott (Structure A)
- http://localhost:3002 — Hopital Cheikh Zayed (Structure B)
Remarquez :
- Les deux interfaces sont identiques (meme code) mais affichent des donnees differentes
- Chaque hopital ne voit que ses propres patients
- La Structure A a 4 patients, la Structure B en a 3
- Dans le SIH de la Structure A (port 3001), allez dans Journal Inter-Services
- Puis allez dans Dossier Patient Unifie et recherchez le NNI
1234567890 - Revenez dans le Journal Inter-Services : vous verrez les appels HTTP qui viennent d'etre faits
Chaque ligne montre :
- Direction :
IN(appel recu) ouOUT(appel envoye) - Methode : GET ou POST
- Service cible : quel registre ou hopital a ete contacte
- Code HTTP : 200 = succes, 404 = non trouve
- Duree : temps de reponse en millisecondes
Ouvrez un terminal et reproduisez le parcours du portail pas a pas :
# Etape 1 : Qui est le NNI 1234567890 ?
curl -s -X POST http://localhost:8001/api/verify \
-H "Content-Type: application/json" \
-d '{"nni":"1234567890"}' | python3 -m json.toolObservez la reponse : l'identite ET une ressource FHIR Patient sont retournees.
# Etape 2 : Dans quels hopitaux Fatima a-t-elle des dossiers ?
curl -s http://localhost:8004/api/locate/1234567890 | python3 -m json.toolObservez : le registre central retourne une liste d'etablissements avec leurs URLs FHIR.
# Etape 3a : Recuperer le dossier FHIR de l'hopital A
curl -s "http://localhost:8010/api/fhir/patient-record?patient_nni=1234567890" \
| python3 -m json.tool | head -80
# Etape 3b : Recuperer le dossier FHIR de l'hopital B
curl -s "http://localhost:8020/api/fhir/patient-record?patient_nni=1234567890" \
| python3 -m json.tool | head -80Comparez les deux reponses : ce sont des FHIR Bundle avec des ressources differentes.
Ouvrez http://localhost:8001/docs dans votre navigateur. C'est la documentation interactive du Registre NNI. Vous pouvez :
- Cliquer sur un endpoint (ex:
POST /api/verify) - Cliquer sur Try it out
- Entrer un NNI dans le corps de la requete
- Cliquer sur Execute
- Voir la reponse en direct
Faites de meme pour chaque registre (ports 8002, 8003, 8004) et chaque SIH (8010, 8020).
Quand un hopital admet un patient, il effectue cette sequence :
SIH ──POST /api/verify──────────► Registre NNI
(envoie le NNI) (verifie l'identite)
◄── repond : identite + FHIR Patient
SIH ──POST /api/register─────────► Registre Central
(NNI + UID etablissement) (enregistre la localisation)
◄── repond : OK
SIH ──INSERT local DB (cree le patient + admission localement)
Quand un hopital ou le portail veut le dossier complet d'un patient :
Demandeur ──GET /api/locate/{nni}──► Registre Central
◄── repond : liste des hopitaux
Pour CHAQUE hopital dans la liste :
Demandeur ──GET /api/fhir/patient-record──► SIH cible
◄── repond : Bundle FHIR
Demandeur ── fusionne tous les Bundle en un dossier unifie
C'est le principe du profil IHE XCA (Cross-Community Access) utilise dans les systemes de sante reels comme le DMP en France ou le eHealth en Belgique.
Quand un clinicien recherche un code dans le formulaire de consultation :
Navigateur ──recherche "diabete"──────────► SIH Backend
SIH Backend ──GET /api/terminology/icd11/search──► Terminology Server
◄── repond : liste de codes
SIH Backend ──SELECT FROM terminology_aliases──► SQLite locale
◄── alias locaux pour ces codes
SIH Backend ──fusionne (codes + alias)──────────► Navigateur
Le clinicien voit les codes standards enrichis de ses alias locaux. Quand il selectionne un code, c'est le code standard (ex: 5A11) qui est enregistre dans la consultation, pas l'alias ("Diabete T2").
Le portail est un aggregateur sans base de donnees. Il ne stocke rien. A chaque requete, il interroge les registres et hopitaux en temps reel :
Navigateur ──GET /api/dossier/{nni}──► Portail Backend
Portail ──POST /api/verify────────► Registre NNI
Portail ──GET /api/locate/{nni}───► Registre Central
Portail ──GET /fhir/patient-record─► SIH A
Portail ──GET /fhir/patient-record─► SIH B
Portail ── fusionne + retourne au navigateur
FHIR est LE standard international pour l'echange de donnees de sante. Ce projet utilise la version R5 (la plus recente) avec ces ressources :
| Ressource FHIR | Description | Exemple dans le projet |
|---|---|---|
| Patient | Identite du patient | Fatima Mint Ahmed, NNI 1234567890 |
| Encounter | Sejour / admission | Urgence le 01/04, transfert le 01/04 |
| Condition | Diagnostic / pathologie | Appendicite (SNOMED: 74400008) |
| Observation | Resultat d'examen | WBC = 15.2 10^9/L (LOINC: 6690-2) |
| Organization | Etablissement de sante | Hopital National (MFL-NKC-001) |
| Practitioner | Professionnel de sante | Dr. Ahmed Mohamed (LIC001) |
| Bundle | Conteneur de ressources | Collection de toutes les ressources d'un patient |
{
"resourceType": "Patient",
"id": "abc-123",
"identifier": [{
"system": "http://registre-nni.mr/nni",
"value": "1234567890",
"type": {
"coding": [{
"system": "http://terminology.hl7.org/CodeSystem/v2-0203",
"code": "NIIP",
"display": "National Individual Identification"
}]
}
}],
"name": [{ "use": "official", "family": "Mint Ahmed", "given": ["Fatima"] }],
"gender": "female",
"birthDate": "1985-03-15"
}{
"resourceType": "Condition",
"subject": { "reference": "Patient?identifier=http://example.com/nni|1234567890" },
"code": {
"coding": [
{ "system": "http://snomed.info/sct", "code": "74400008", "display": "Appendicitis" },
{ "system": "http://hl7.org/fhir/sid/icd-11", "code": "DC11", "display": "Appendicite" }
]
},
"clinicalStatus": {
"coding": [{ "system": "http://terminology.hl7.org/CodeSystem/condition-clinical", "code": "active" }]
},
"onsetDateTime": "2026-04-01T00:00:00"
}A retenir : Une meme pathologie est codee avec deux systemes (SNOMED CT + ICD-11) dans le champ
coding[]. C'est une bonne pratique FHIR qui garantit l'interoperabilite meme si les systemes cibles ne supportent qu'un seul des deux referentiels.
Le referentiel clinique le plus complet au monde (~350 000 concepts). Utilise pour decrire avec precision les diagnostics, symptomes et actes.
| Code | Libelle | Usage dans le projet |
|---|---|---|
| 74400008 | Appendicitis | Diagnostic de Fatima |
| 44054006 | Type 2 diabetes mellitus | Diagnostic de Mohamed |
| 61462000 | Malaria | Diagnostic d'Aicha |
| 38341003 | Essential hypertension | Diagnostic de Mariam |
| 263102004 | Fracture | Diagnostic d'Ousmane |
| 309343006 | Physician | Profession (Dr. Ahmed) |
| 106292003 | Nurse | Profession (Fatima infirmiere) |
Le referentiel de l'OMS pour la classification des maladies, utilise mondialement pour les statistiques de sante et la facturation.
| Code | Libelle | Equivalent SNOMED |
|---|---|---|
| DC11 | Appendicite | 74400008 |
| 5A11 | Diabete sucre de type 2 | 44054006 |
| 1F40 | Paludisme a P. falciparum | 61462000 |
| BA00 | Hypertension arterielle essentielle | 38341003 |
| NA10 | Fracture de l'avant-bras | 263102004 |
Le referentiel international pour les analyses de laboratoire et les observations cliniques.
| Code | Libelle | Valeur demo | Normale | Interpretation |
|---|---|---|---|---|
| 6690-2 | Leucocytes (WBC) | 15.2 x10^9/L | 4.0–10.0 | ELEVE (infection) |
| 1988-5 | Proteine C-reactive (CRP) | 85 mg/L | 0–5 | ELEVE (inflammation) |
| 2345-7 | Glucose sanguin | 1.85 g/L | 0.7–1.1 | ELEVE (diabete) |
| Profil | Description | Simulation dans le projet |
|---|---|---|
| PIX/PDQ | Patient Identity Cross-referencing | Registre NNI + Registre Central |
| XCA | Cross-Community Access | Requete federee du dossier unifie |
| MHD | Mobile access to Health Documents | Endpoints FHIR des SIH |
| mCSD | Mobile Care Services Discovery | Registre des etablissements |
| HPD | Healthcare Provider Directory | Registre RHS |
Dans un hopital, un medecin dira "HTA" pour designer l'hypertension arterielle essentielle. Dans un autre, on parlera de "Maladie hypertensive". Pourtant, il s'agit du meme diagnostic, identifie par le code BA00 dans la classification internationale ICD-11.
Pour que les systemes d'information se comprennent, il faut un langage commun : les terminologies medicales internationales. Mais pour que les cliniciens soient a l'aise, il faut que l'interface affiche le vocabulaire local de leur etablissement.
┌─────────────────────────────────────────────────────────────────┐
│ Terminology Server (:8005) │
│ Charge ~10 000 codes en memoire depuis des fichiers JSON │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ ICD-11 │ │ LOINC │ │
│ │ ~9 600 codes │ │ ~5 000 codes │ │
│ │ 20 chapitres │ │ 10 categories │ │
│ │ Diagnostics │ │ Analyses labo │ │
│ └──────────────────┘ └──────────────────┘ │
└───────────────────────────┬─────────────────────────────────────┘
│ HTTP (recherche, lookup)
┌─────────────┼─────────────────┐
│ │
┌─────────▼──────────┐ ┌─────────▼──────────┐
│ SIH Structure A │ │ SIH Structure B │
│ │ │ │
│ Alias locaux : │ │ Alias locaux : │
│ BA00 → "HTA" │ │ BA00 → "Maladie │
│ 6690-2 → "GB" │ │ hypertens." │
│ 1988-5 → "CRP" │ │ 6690-2 → "Numera- │
│ │ │ tion leuco." │
└────────────────────┘ └────────────────────┘
│ │
└───────── FHIR echange ────────┘
(toujours le code standard BA00,
jamais l'alias local)
Le Terminology Server est un microservice stateless qui charge les catalogues ICD-11 et LOINC en memoire au demarrage depuis des fichiers JSON statiques. Il offre :
- Recherche fuzzy : accent-insensitive, par code ou par libelle (francais)
- Classement par pertinence : correspondance exacte > debut de code > debut de libelle > contient
- Filtrage par chapitre ICD-11 ou categorie LOINC
- Lookup direct : obtenir le detail d'un code par son identifiant
Le serveur ne contient aucune donnee patient. C'est un catalogue de reference, equivalent au role d'un "FHIR Terminology Server" dans une architecture de sante reelle.
Chaque SIH (Structure A et Structure B) possede sa propre table d'alias dans sa base SQLite :
| Champ | Description |
|---|---|
system |
"icd11" ou "loinc" |
code |
Code standard (ex: BA00, 6690-2) |
standard_display |
Libelle officiel du catalogue |
alias |
Nom local choisi par la structure |
Exemples pre-charges :
| Code | Standard | Structure A (jargon) | Structure B (formel) |
|---|---|---|---|
| BA00 | Hypertension arterielle essentielle | HTA | Maladie hypertensive |
| 5A11 | Diabete sucre de type 2 | Diabete T2 | Diabete non insulinodependant |
| 6690-2 | Leucocytes (GB) | GB | Numeration leucocytaire |
| 2160-0 | Creatinine serique | Creat | Dosage de la creatinine serique |
| 8310-5 | Temperature corporelle | T° | Temperature |
Point cle : Les alias ne voyagent jamais dans les echanges FHIR. Seul le code standard (BA00, 6690-2) est transmis entre structures. L'alias est purement un confort d'affichage local.
L'application SIH impose un mode strict pour la selection des codes de terminologie :
- Pas de saisie libre : le clinicien doit rechercher et selectionner un code dans le catalogue officiel
- Un composant TerminologyPicker offre une recherche avec autocompletion
- Le code standard est enregistre dans la donnee clinique (consultation, condition, observation)
- L'alias local (s'il existe) est affiche dans l'interface
Dans les vues de detail des sejours et du dossier unifie, un selecteur de mode d'affichage permet de basculer entre trois vues :
| Mode | Affichage | Interet pedagogique |
|---|---|---|
| Alias | Nom local de la structure | Ce que voit le clinicien au quotidien |
| Standard | Libelle officiel du catalogue | Le nom normalise international |
| Code | Code technique (BA00, 6690-2) | Ce qui voyage reellement dans le FHIR |
C'est un outil pedagogique puissant : il permet de montrer visuellement que derriere l'abbreviation "HTA" se cache le code BA00 qui est le meme pour toutes les structures.
multi_services/
├── README.md ← ce fichier
├── docker-compose.yml ← orchestration des 11 conteneurs
│
├── shared/
│ └── fhir_utils.py ← constructeurs de ressources FHIR (partage)
│
├── terminology_server/ ← Serveur de Terminologies
│ ├── main.py ← API : recherche, lookup, chapitres/categories
│ ├── generate_data.py ← Generateur des catalogues ICD-11 & LOINC (JSON)
│ ├── data/
│ │ ├── icd11.json ← ~9 600 codes ICD-11 (20 chapitres)
│ │ └── loinc.json ← ~5 000 codes LOINC (10 categories)
│ ├── requirements.txt
│ └── Dockerfile
│
├── nni_registry/ ← Registre National d'Identite
│ ├── main.py ← API : POST /api/verify, GET /api/fhir/Patient
│ ├── database.py ← Modele : Identity(nni, nom, prenom, ddn, sexe)
│ ├── seed_data.py ← 6 identites de demonstration
│ ├── requirements.txt
│ └── Dockerfile
│
├── facility_registry/ ← Registre des Etablissements (MFL)
│ ├── main.py ← API : GET/POST /api/facilities, FHIR Organization
│ ├── database.py ← Modele : Facility(uid, nom, type, fhir_endpoint)
│ ├── seed_data.py ← 2 structures hospitalieres
│ ├── requirements.txt
│ └── Dockerfile
│
├── hwr_registry/ ← Registre des Ressources Humaines en Sante
│ ├── main.py ← API : GET/POST /api/workers, FHIR Practitioner
│ ├── database.py ← Modele : Worker(licence, nom, profession, facility)
│ ├── seed_data.py ← 6 professionnels (3 par structure)
│ ├── requirements.txt
│ └── Dockerfile
│
├── central_registry/ ← Index Central des Patients
│ ├── main.py ← API : POST /api/register, GET /api/locate/{nni}
│ ├── database.py ← Modele : PatientLocation(nni, facility_uid)
│ ├── seed_data.py ← 7 associations patient-etablissement
│ ├── requirements.txt
│ └── Dockerfile
│
├── sih_app/ ← Application SIH (partagee par les 2 structures)
│ ├── backend/
│ │ ├── main.py ← API : admissions, FHIR, dossier unifie, terminologie, alias
│ │ ├── database.py ← Patient, Admission, Consultation, Condition, Observation, TerminologyAlias
│ │ ├── interop_client.py ← Client HTTP inter-services (httpx + logging)
│ │ ├── seed_data.py ← Donnees + alias conditionnels selon STRUCTURE_UID
│ │ ├── requirements.txt
│ │ └── Dockerfile
│ └── frontend/
│ ├── src/
│ │ ├── App.jsx ← Application principale avec navigation
│ │ ├── api.js ← Wrapper fetch avec interception reseau
│ │ ├── NetworkContext.jsx ← Contexte React pour les logs reseau
│ │ └── components/
│ │ ├── PatientAdmit.jsx ← Formulaire d'admission (appels inter-services)
│ │ ├── AdmissionsManager.jsx ← Sejours, consultations + Loupe de terminologie
│ │ ├── TerminologyManager.jsx← Navigateur ICD-11/LOINC + gestion des alias
│ │ ├── TerminologyPicker.jsx ← Selecteur strict de codes (autocompletion)
│ │ ├── DossierUnifie.jsx ← Vue federee multi-structures + Loupe terminologie
│ │ ├── InteropPanel.jsx ← Journal des appels inter-services
│ │ └── NetworkPanel.jsx ← Inspecteur reseau (dev tools)
│ ├── nginx.conf.template ← Proxy nginx (BACKEND_HOST dynamique)
│ ├── package.json
│ └── Dockerfile
│
└── patient_portal/ ← Portail Patient
├── backend/
│ ├── main.py ← Aggregateur : NNI → Central → SIH → fusion
│ ├── requirements.txt
│ └── Dockerfile
└── frontend/
├── src/
│ ├── App.jsx ← Application portail
│ ├── api.js ← Client API
│ └── components/
│ ├── NNISearch.jsx ← Recherche par NNI
│ ├── ArchitectureDiagram.jsx← Schema anime de l'architecture
│ ├── UnifiedDossier.jsx ← Dossier unifie avec timeline
│ └── CallTrace.jsx ← Cascade des appels inter-services
├── nginx.conf
├── package.json
└── Dockerfile
| Methode | Endpoint | Description |
|---|---|---|
| GET | /api/terminology/icd11/search?q={terme}&chapter={num}&limit={n} |
Recherche ICD-11 par code ou libelle |
| GET | /api/terminology/loinc/search?q={terme}&category={cat}&limit={n} |
Recherche LOINC par code ou libelle |
| GET | /api/terminology/icd11/{code} |
Detail d'un code ICD-11 |
| GET | /api/terminology/loinc/{code} |
Detail d'un code LOINC |
| GET | /api/terminology/icd11/chapters |
Liste des chapitres ICD-11 avec nombre de codes |
| GET | /api/terminology/loinc/categories |
Liste des categories LOINC avec nombre de codes |
| GET | /api/terminology/stats |
Statistiques des deux systemes de terminologie |
| GET | /api/logs |
Journal des requetes recues |
| Methode | Endpoint | Description | Corps requete |
|---|---|---|---|
| POST | /api/verify |
Verifier une identite nationale | {"nni": "1234567890"} |
| GET | /api/fhir/Patient?identifier={nni} |
Recherche FHIR Patient | — |
| GET | /api/identities |
Lister toutes les identites | — |
| GET | /api/logs |
Journal des requetes | — |
| Methode | Endpoint | Description | Corps requete |
|---|---|---|---|
| GET | /api/facilities |
Lister les etablissements | — |
| GET | /api/facilities/{uid} |
Detail + FHIR Organization | — |
| POST | /api/facilities/register |
Enregistrer un etablissement | {"uid", "name", ...} |
| GET | /api/fhir/Organization |
Bundle FHIR Organization | — |
| Methode | Endpoint | Description | Corps requete |
|---|---|---|---|
| GET | /api/workers |
Lister (filtre: ?facility_uid=) |
— |
| GET | /api/workers/{license} |
Detail + FHIR Practitioner | — |
| POST | /api/workers |
Enregistrer un professionnel | {"license_number", ...} |
| GET | /api/fhir/Practitioner |
Bundle FHIR Practitioner | — |
| Methode | Endpoint | Description | Corps requete |
|---|---|---|---|
| POST | /api/register |
Enregistrer patient ↔ etablissement | {"patient_nni", "facility_uid"} |
| GET | /api/locate/{nni} |
Localiser un patient | — |
| GET | /api/entries |
Lister toutes les associations | — |
| Methode | Endpoint | Description |
|---|---|---|
| POST | /api/patients/admit |
Admettre un patient (workflow inter-services) |
| GET | /api/patients |
Lister les patients locaux |
| GET | /api/admissions |
Lister les admissions |
| GET | /api/admissions/{id} |
Detail d'une admission (consultations, observations) |
| POST | /api/consultations |
Creer une consultation (avec codes ICD-11 en mode strict) |
| POST | /api/conditions |
Enregistrer un diagnostic (SNOMED + ICD-11) |
| POST | /api/observations |
Enregistrer une observation (code LOINC en mode strict) |
| GET | /api/dossier-unifie/{nni} |
Dossier Patient Unifie (requete federee) |
| GET | /api/fhir/patient-record?patient_nni={nni} |
Bundle FHIR du patient local |
| GET | /api/fhir/Patient?identifier={nni} |
Recherche FHIR Patient |
| GET | /api/terminology/{system}/search?q=... |
Recherche terminologie (proxy + alias locaux) |
| GET | /api/terminology/{system}/chapters |
Chapitres/categories (proxy terminology server) |
| GET | /api/aliases?system={icd11|loinc} |
Lister les alias locaux de cette structure |
| POST | /api/aliases |
Creer/modifier un alias local |
| PUT | /api/aliases/{id} |
Modifier un alias existant |
| DELETE | /api/aliases/{id} |
Supprimer un alias (revient au nom standard) |
| GET | /api/aliases/resolve?system=...&codes=... |
Resolution par lot (codes → alias locaux) |
| GET | /api/interop-logs |
Journal des appels inter-services |
| GET | /api/structure-info |
Informations sur la structure |
| GET | /api/workers |
Professionnels de sante (via registre RHS) |
| Methode | Endpoint | Description |
|---|---|---|
| GET | /api/dossier/{nni} |
Dossier unifie complet + trace des appels |
| GET | /api/architecture |
Graphe JSON de l'architecture pour le diagramme |
| NNI | Nom | Structures | Scenario |
|---|---|---|---|
1234567890 |
Fatima Mint Ahmed | A + B | Appendicite → transfert → chirurgie |
MR444555666 |
Mariam Diallo | A + B | Hypertension (A) + cardiologie (B) |
MR123456789 |
Mohamed Ould Sidi | A | Diabete type 2 |
MR987654321 |
Aicha Mint Cheikh | A | Paludisme |
MR111222333 |
Ousmane Ba | B | Fracture du poignet |
MR777888999 |
Ibrahim Ould Moulaye | aucune | Identite seule (pas encore admis) |
Ce scenario illustre un transfert inter-hospitalier realiste :
Jour 1 — Hopital National (Structure A)
- Fatima arrive aux urgences avec une douleur abdominale aigue
- Dr. Ahmed Mohamed (medecin generaliste) l'examine
- Analyses de laboratoire :
- WBC = 15.2 x10^9/L (leucocytes eleves → infection)
- CRP = 85 mg/L (inflammation severe)
- Diagnostic : suspicion d'appendicite aigue
- Decision : transfert vers l'Hopital Cheikh Zayed pour chirurgie
Jour 1 — Hopital Cheikh Zayed (Structure B) 6. Fatima est recue en chirurgie comme transfert 7. Dr. Moussa Diallo (chirurgien) confirme l'appendicite par imagerie 8. Appendicectomie laparoscopique realisee avec succes
Jour 2 — Suivi post-operatoire 9. Dr. Moussa note une evolution favorable 10. Condition mise a jour : appendicite → resolue
Consultation du portail 11. Fatima se connecte au portail patient (port 3003) avec son NNI 12. Le portail interroge les deux hopitaux et affiche : - 2 admissions (urgence + transfert) - 3 consultations (initiale + specialiste + suivi) - 2 analyses de laboratoire - 1 diagnostic (appendicite, resolue)
Exercice 1 — Tracer le parcours de Fatima
- Ouvrez le portail (port 3003) et recherchez Fatima (NNI
1234567890) - Identifiez combien de
BundleFHIR ont ete retournes et par quels hopitaux - Comptez les ressources FHIR par type (Patient, Encounter, Condition, Observation)
- Retrouvez les codes SNOMED et ICD-11 du diagnostic
Exercice 2 — Comparer les dossiers locaux
- Ouvrez le SIH Structure A (port 3001), onglet "Dossier Patient Unifie"
- Recherchez Fatima — observez la trace des appels inter-services
- Faites la meme chose depuis le SIH Structure B (port 3002)
- Question : les traces sont-elles identiques ? Pourquoi ?
Exercice 3 — Admettre un nouveau patient
- Ouvrez le SIH Structure A (port 3001), onglet "Patients & Admissions"
- Admettez Ibrahim (NNI
MR777888999) — c'est son premier sejour - Observez la trace : quels registres ont ete contactes ?
- Verifiez dans le Registre Central (port 8004) que l'association a ete creee :
curl -s http://localhost:8004/api/locate/MR777888999 | python3 -m json.tool
Exercice 4 — Explorer les API directement
- Ouvrez le Swagger du registre NNI (http://localhost:8001/docs)
- Utilisez
POST /api/verifypour verifier l'identite de Mariam (NNIMR444555666) - Notez le
resourceTypedans la reponse — c'est une ressource FHIR de quel type ? - Utilisez
GET /api/fhir/Patient?identifier=MR444555666— quelle est la difference ?
Exercice 5 — Comprendre l'absence de base partagee
- Listez les patients de la Structure A :
curl -s http://localhost:8010/api/patients - Listez les patients de la Structure B :
curl -s http://localhost:8020/api/patients - Mohamed (MR123456789) n'apparait que dans une structure — laquelle ?
- Que retourne le portail pour Mohamed ? Combien de structures ?
curl -s http://localhost:8040/api/dossier/MR123456789 | python3 -c \ "import sys,json; d=json.load(sys.stdin); print(f'Structures: {d[\"total_structures\"]}, Ressources: {d[\"total_resources\"]}')"
Exercice 6 — Explorer les terminologies medicales
- Ouvrez le SIH Structure A (port 3001), onglet Terminologies > ICD-11 & LOINC
- Recherchez "diabete" dans ICD-11 — combien de codes correspondent ?
- Recherchez "glucose" dans LOINC — observez les variantes (serique, urinaire, etc.)
- Notez le code ICD-11 du diabete type 2 et le code LOINC du glucose sanguin
- Verifiez directement via l'API :
# Recherche ICD-11 curl -s "http://localhost:8005/api/terminology/icd11/search?q=diabete+type+2&limit=3" \ | python3 -m json.tool # Recherche LOINC curl -s "http://localhost:8005/api/terminology/loinc/search?q=glucose&limit=5" \ | python3 -m json.tool
- Question : pourquoi un meme concept ("diabete") peut-il avoir plusieurs codes ICD-11 ? (Indice : regardez les suffixes — 5A11, 5A11.0Z, 5A11.1Z...)
Exercice 7 — Comparer les alias entre structures
- Ouvrez deux onglets : Structure A (port 3001) et Structure B (port 3002)
- Dans chacun, allez dans Terminologies et cherchez le code
BA00 - Observez l'alias local :
- Structure A : HTA (jargon medical abrege)
- Structure B : Maladie hypertensive (appellation formelle)
- Comparez les alias LOINC : cherchez
6690-2(leucocytes)- Structure A : GB (globules blancs — abrege)
- Structure B : Numeration leucocytaire (nom complet de l'analyse)
- Verifiez par l'API :
# Alias de Structure A curl -s http://localhost:8010/api/aliases?system=icd11 | python3 -c \ "import sys,json; [print(f\"{a['code']:8} → {a['alias']}\") for a in json.load(sys.stdin)[:5]]" # Alias de Structure B curl -s http://localhost:8020/api/aliases?system=icd11 | python3 -c \ "import sys,json; [print(f\"{a['code']:8} → {a['alias']}\") for a in json.load(sys.stdin)[:5]]"
- Question : quand Structure A envoie un diagnostic a Structure B via FHIR, est-ce "HTA" ou "BA00" qui est transmis ? Pourquoi ?
Exercice 8 — Creer une consultation avec terminologie stricte
- Dans le SIH Structure A, ouvrez un sejour existant
- Ajoutez une nouvelle consultation en utilisant le selecteur ICD-11 :
- Recherchez "paludisme" et selectionnez un code (pas de saisie libre !)
- Observez que le code standard est enregistre, pas l'alias
- Ajoutez une observation en utilisant le selecteur LOINC :
- Recherchez "hemoglobine" et selectionnez le code LOINC
- Entrez une valeur et une unite
- Basculez la Loupe de terminologie entre les modes Alias / Standard / Code
- Question : quel est l'interet du mode strict par rapport a la saisie libre ? (Indice : pensez a l'interoperabilite et aux statistiques de sante)
Exercice 9 — Creer un alias personnalise
- Dans le SIH Structure A, allez dans Terminologies
- Recherchez un code qui n'a pas encore d'alias (ex: recherchez "fracture")
- Ajoutez un alias de votre choix (ex: "Fx" pour le code NA10)
- Retournez dans un sejour qui contient ce code et activez la Loupe de terminologie
- Verifiez que votre alias apparait en mode "Alias"
- Ouvrez le SIH Structure B — votre alias y est-il visible ? Pourquoi ?
| Concept | Explication | Concretisation dans le projet |
|---|---|---|
| Interoperabilite | Capacite de systemes differents a echanger des donnees | Les 11 conteneurs communiquent via HTTP + FHIR |
| Requete federee | Interroger plusieurs sources et fusionner les resultats | GET /api/dossier-unifie/{nni} |
| Registre national | Service centralise de reference (identite, etablissements) | NNI, MFL, RHS, Index Central |
| Serveur de terminologie | Catalogue centralise des codes medicaux | Terminology Server (ICD-11 + LOINC) |
| Alias local | Vocabulaire specifique a un etablissement | HTA (Struct.A) vs Maladie hypertensive (Struct.B) pour BA00 |
| Mode strict | Selection contrainte dans un referentiel officiel | TerminologyPicker — pas de saisie libre |
| FHIR Bundle | Conteneur de ressources FHIR | Reponse de /api/fhir/patient-record |
| CodeableConcept | Concept code avec coding[] multi-systeme | SNOMED + ICD-11 dans une meme Condition |
| Double codage | Coder un diagnostic dans 2 referentiels | SNOMED CT + ICD-11 pour chaque Condition |
| Idempotence | Une operation repetee a le meme effet | POST /api/register ne cree pas de doublon |
| Stateless aggregator | Service sans base de donnees propre | Le portail patient et le terminology server ne stockent rien |
| Service mesh | Reseau de microservices communicant entre eux | Docker network health-net (172.28.0.0/16) |
| Composant | Technologie | Role |
|---|---|---|
| Backends API | FastAPI (Python 3.11) | Framework web asynchrone |
| ORM | SQLAlchemy + SQLite | Acces base de donnees |
| FHIR | fhir.resources (v7.1) | Construction de ressources FHIR R5 |
| HTTP inter-services | httpx | Client HTTP asynchrone |
| Frontends | React 18 + Tailwind CSS | Interfaces web |
| Proxy | nginx (envsubst) | Reverse proxy dynamique |
| Orchestration | Docker Compose | Gestion des 10 conteneurs |
| Reseau | Docker bridge network | Isolation et routage |
- Specification FHIR R5 — Documentation officielle
- SNOMED CT Browser — Explorer les codes SNOMED
- ICD-11 Browser — Explorer les codes ICD-11
- LOINC Search — Explorer les codes d'analyses
- Profils IHE — Profils d'interoperabilite en sante
- FastAPI Documentation — Framework Python utilise
- Docker Compose Documentation — Orchestration conteneurs