Client Python type-safe pour l'API Annuaire Santé : FHIR vers JSON DB-ready avec résolution MOS automatique
Project description
Annuaire Santé FHIR - Client Python Moderne
Client Python moderne et type-safe pour l'API FHIR de l'Annuaire Santé avec modèles JSON propres, sans artefacts FHIR, prêts pour injection en base de données.
Caractéristiques
- Modèles Pydantic v2 propres et DB-ready sans artefacts FHIR
- Résolution automatique des codes MOS (Modèle des Objets de Santé)
- Transformateurs FHIR → JSON pour chaque ressource
- Type hints complets pour une excellente expérience développeur
- Support complet des profils:
- FR Core v2.1.0 (Practitioner, Organization, PractitionerRole, HealthcareService)
- AS DP v1.1.0 (Annuaire Santé Données Publiques)
Installation
pip install annuaire-sante-fhir
Note: Le nom du package sur PyPI est annuaire-sante-fhir, mais l'import dans votre code reste annuairesante:
from annuairesante import AnnuaireSanteClient, transform_practitioner
Configuration de la clé API
Pour utiliser l'API Annuaire Santé, vous devez obtenir une clé API :
- Obtenir une clé : Rendez-vous sur https://ansforge.github.io/annuaire-sante-fhir-documentation/pages/guide/version-2/getting-started/get-api-key.html et créez un compte pour obtenir votre clé API
- Configurer la clé : Trois méthodes disponibles
Méthode 1 : Variable d'environnement (recommandé)
export ANNUAIRE_SANTE_API_KEY="votre-cle-api"
Méthode 2 : Fichier .env
Créez un fichier .env à la racine de votre projet :
ANNUAIRE_SANTE_API_KEY=votre-cle-api
Méthode 3 : Paramètre direct
from annuairesante import AnnuaireSanteClient
client = AnnuaireSanteClient(api_key="votre-cle-api")
⚠️ Sécurité : Ne commitez jamais votre clé API dans Git ! Ajoutez .env à votre .gitignore.
Utilisation rapide
Recherche simple (quelques résultats)
from annuairesante import AnnuaireSanteClient, transform_practitioner
# Initialiser le client
client = AnnuaireSanteClient() # Utilise ANNUAIRE_SANTE_API_KEY
# Rechercher des professionnels
bundle = client.practitioner.search(family="MARTIN", given="Jean")
print(f"Total trouvé: {bundle.total}")
# Parcourir les résultats
for resource in bundle.entries:
practitioner = transform_practitioner(resource)
print(f"Nom: {practitioner.name.full_text}")
print(f"RPPS: {practitioner.identifiers.rpps}")
# Exporter en JSON pour base de données
db_ready_json = practitioner.model_dump(mode='json')
Synchronisation de masse (pagination automatique)
from annuairesante import AnnuaireSanteClient, transform_organization
client = AnnuaireSanteClient()
# Synchroniser toutes les organisations d'une région
count = 0
for org_fhir in client.organization.search_all(
**{"address-postalcode": "69"}, # Département du Rhône
active=True
):
org = transform_organization(org_fhir)
database.save(org.model_dump(mode='json'))
count += 1
if count % 100 == 0:
print(f"{count} organisations synchronisées...")
print(f"Total: {count} organisations")
Synchronisation incrémentale
from annuairesante import AnnuaireSanteClient
from datetime import datetime
client = AnnuaireSanteClient()
# Synchroniser uniquement les modifications depuis la dernière synchro
last_sync = "2025-01-01T00:00:00Z"
for practitioner in client.practitioner.search_all(
_lastUpdated=f"ge{last_sync}", # ge = greater or equal
**{"address-city": "Lyon"}
):
database.upsert(practitioner) # Mise à jour ou insertion
# Sauvegarder la date de synchro
database.set_last_sync(datetime.utcnow().isoformat() + "Z")
Modèles disponibles
Practitioner (Professionnel de santé)
{
"identifiers": {
"idnps": "810101205564", # Obligatoire
"rpps": "10101205564", # Obligatoire
"adeli": None
},
"name": {
"family": "VERDIER",
"given": ["Pauline"],
"prefix": "MME", # Civilité (JDV_J78)
"suffix": ["DR"], # Titre exercice (JDV_J79)
"full_text": "VERDIER Pauline"
},
"gender": "female",
"birth_date": "1985-03-15",
"contacts": {
"phones": ["+33612345678"],
"emails": ["contact@example.com"],
"mssante": [{
"email": "pauline.verdier@aura.mssante.fr",
"type": "PER", # ORG, APP, PER, CAB
"digitization": false,
"liste_rouge": false
}]
},
"addresses": [{
"lines": ["2 RUE CLAUDE BERNARD"],
"city": "PARIS",
"postal_code": "75005",
"insee_code": "75105"
}],
"qualifications": { # Indexé par type puis nom de système
"profession": {
"ProfessionSante": {
"code": "21",
"display": "Médecin" # Résolu via MOS
},
"CategorieProfessionnelle": {
"code": "C",
"display": "Civil"
}
},
"diplome": {
"DiplomeEtatFrancais": {
"code": "DE28",
"display": "DE Docteur en médecine"
}
}
},
"smartcards": [{
"type": "CPS",
"number": "3100434368",
"period": {"start": "2024-02-21", "end": "2027-02-21"},
"is_valid": true
}],
"metadata": {
"id": "003-3014698-3057235",
"version_id": "1",
"last_updated": "2025-04-28T18:19:26.335+02:00",
"profiles": ["https://hl7.fr/ig/fhir/core/StructureDefinition/fr-core-practitioner"],
"data_trace": {
"systeme_information": "RPPS"
}
},
"active": true
}
Organization (Structure de santé)
{
"identifiers": {
"finess": "750010753", # FINEJ ou FINEG
"idnst": "1750010753",
"siret": "12345678901234"
},
"name": "PHARMACIE BLONDEEL",
"aliases": ["LA GRANDE PHARMACIE DU 5"],
"types_by_category": { # Indexé par catégorie
"categorieEtablissement": {
"code": "620",
"display": "Pharmacie d'officine", # Résolu via MOS
"category": "categorieEtablissement"
},
"secteurActiviteRASS": {
"code": "SA33",
"display": "Secteur privé commercial",
"category": "secteurActiviteRASS"
},
"statutJuridiqueINSEE": {
"code": "101",
"display": "SELAS",
"category": "statutJuridiqueINSEE"
}
},
"primary_type": { # Type sans catégorie spécifique
"code": "620",
"display": "Pharmacie d'Officine",
"category": null
},
"pharmacy_licence": "75#000283",
"addresses": [...]
}
Autres ressources
- PractitionerRole: Situation d'exercice (genre, mode, fonction)
- HealthcareService: Service/activité de santé (modalité, type, forme)
- Device: Équipement matériel lourd
Accès simplifié aux données
Les structures JSON sont optimisées pour un accès direct sans boucles :
# Practitioner - Accès aux qualifications
pract.qualifications["profession"]["ProfessionSante"]
# → {"code": "21", "display": "Pharmacien"}
# Helpers disponibles
pract.get_profession() # Code profession principal
pract.get_diploma() # Diplôme principal
pract.get_category() # Catégorie professionnelle
# Organization - Accès aux types
org.types_by_category["secteurActiviteRASS"]
# → {"code": "SA33", "display": "Pharmacie d'officine"}
org.primary_type # Type principal (sans catégorie)
# Helpers disponibles
org.get_secteur_activite()
org.get_statut_juridique()
Note : Les clés des dictionnaires sont des noms lisibles ("ProfessionSante", "DiplomeEtatFrancais") extraits des référentiels MOS. Si l'index MOS n'est pas encore construit avec le nouveau format, les codes de table sont utilisés comme fallback ("TRE-G15", "TRE-R48").
Pour construire l'index avec les noms lisibles :
python examples/update_mos_cache.py
Résolution MOS
Tous les codes MOS (TRE_, JDV_) sont automatiquement résolus en libellés lisibles :
from annuairesante.mos import MOSResolver
resolver = MOSResolver()
display = resolver.resolve(
"https://mos.esante.gouv.fr/NOS/TRE_G15-ProfessionSante/FHIR/TRE-G15-ProfessionSante",
"21"
)
# → "Médecin"
API et ressources disponibles
Ressources FHIR supportées
| Ressource | Description | Exemples |
|---|---|---|
| Practitioner | Professionnels de santé | client.practitioner.search(family="MARTIN") |
| Organization | Structures de santé | client.organization.search(type="620") |
| PractitionerRole | Situations d'exercice | client.practitioner_role.search(practitioner="003-123456") |
| HealthcareService | Services/activités de santé | client.healthcare_service.search(organization="001-01-174986") |
| Device | Équipements matériels lourds | client.device.search(type="05602") |
Méthodes disponibles
Pour chaque ressource, trois méthodes sont disponibles :
# 1. search() - Recherche avec résultat paginé (Bundle)
bundle = client.practitioner.search(family="MARTIN", _count=20)
print(f"Page courante: {len(bundle.entries)} résultats")
# Note: bundle.total est toujours 0 (l'API ne fournit pas ce champ)
for entry in bundle.entries:
process(entry)
# Pagination manuelle
while bundle.has_next():
bundle = bundle.next()
for entry in bundle.entries:
process(entry)
# 2. search_all() - Générateur avec pagination automatique
for practitioner in client.practitioner.search_all(family="MARTIN"):
database.save(practitioner)
# 3. get() - Récupérer par ID
practitioner = client.practitioner.get("003-123456")
Paramètres de recherche
Consultez la documentation complète des paramètres pour la liste exhaustive.
Exemples courants :
# Practitioner
client.practitioner.search(
family="MARTIN", # Nom de famille
given="Jean", # Prénom
**{"qualification-code": "10"}, # Code profession (10=Médecin dans TRE-G15)
**{"mailbox-mss": "jean@mssante.fr"}, # Boîte MSS
active=True, # Actif uniquement
_lastUpdated="ge2025-01-01" # Modifié depuis le 1er janvier
)
# Organization
client.organization.search(
name="hopital", # Nom (recherche partielle)
identifier="750010753", # FINESS, SIRET, etc.
type="620", # Type (620 = Pharmacie)
**{"address-city": "Paris"}, # Ville
**{"address-postalcode": "75"}, # Code postal / département
active=True
)
# PractitionerRole
client.practitioner_role.search(
practitioner="003-123456", # ID du professionnel
organization="001-01-879996", # ID de l'organisation
role="204", # Code fonction/activité
active=True
)
Exemples complets
1. Recherche et affichage
from annuairesante import AnnuaireSanteClient, transform_practitioner
client = AnnuaireSanteClient()
# Rechercher des médecins à Lyon
bundle = client.practitioner.search(
**{"qualification-code": "10"}, # Médecin (code TRE-G15)
_count=10
)
print(f"Médecins (page courante): {len(bundle.entries)} résultats")
for resource in bundle.entries:
practitioner = transform_practitioner(resource)
print(f"\n{practitioner.name.full_text}")
print(f" RPPS: {practitioner.identifiers.rpps}")
if practitioner.contacts.mssante:
print(f" MSSanté: {practitioner.contacts.mssante[0].email}")
2. Synchronisation régionale complète
from annuairesante import AnnuaireSanteClient, transform_organization
import json
client = AnnuaireSanteClient()
# Exporter toutes les pharmacies du département 69 en JSON Lines
with open("pharmacies_69.jsonl", "w") as f:
for org_fhir in client.organization.search_all(
type="620", # Pharmacie d'officine
**{"address-postalcode": "69"}, # Rhône
active=True,
_count=100 # 100 par page
):
org = transform_organization(org_fhir)
json.dump(org.model_dump(mode='json'), f, ensure_ascii=False)
f.write("\n")
3. Mise à jour incrémentale quotidienne
from annuairesante import AnnuaireSanteClient
from datetime import datetime
client = AnnuaireSanteClient()
# Lire la dernière date de synchro
last_sync = load_last_sync_date() # Ex: "2025-10-08T00:00:00Z"
# Synchroniser uniquement les modifications
modified_count = 0
for org in client.organization.search_all(
_lastUpdated=f"ge{last_sync}",
**{"address-postalcode": "69"}
):
database.upsert(org)
modified_count += 1
# Sauvegarder la nouvelle date
save_last_sync_date(datetime.utcnow().isoformat() + "Z")
print(f"{modified_count} organisations mises à jour")
Voir aussi les exemples complets dans le dossier examples/ :
basic_usage.py- Utilisation basiqueapi_search.py- Recherches avancées et paginationsync_region.py- Synchronisation de masse d'une régionincremental_sync.py- Synchronisation incrémentale avec gestion d'état
Développement
# Installer en mode développement
pip install -e ".[dev]"
# Tests
pytest
# Formatage
black src/
ruff check src/
# Type checking
mypy src/
Standards et conformité
Cette bibliothèque implémente:
-
FR Core v2.1.0: Profils FHIR français (HL7 France)
- FR Core Practitioner
- FR Core Organization
- FR Core PractitionerRole
- FR Core HealthcareService
- FR Core Address, ContactPoint, HumanName
-
AS DP v1.1.0: Profils Annuaire Santé Données Publiques
- AS DP Practitioner (extensions: smartcard, mailbox-mss-metadata)
- AS DP Organization (extensions: organization-types, pharmacy-licence)
- AS DP PractitionerRole
- AS DP HealthcareService Healthcare Activity (extension: authorization)
- AS DP Device (extension: authorization)
Licence
MIT
Contributions
Les contributions sont les bienvenues ! Consultez CONTRIBUTING.md pour les guidelines.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file annuaire_sante_fhir-0.0.3.tar.gz.
File metadata
- Download URL: annuaire_sante_fhir-0.0.3.tar.gz
- Upload date:
- Size: 38.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cc6f7ece865a87dc6e45f48e955816ea2f007b3d341950a7d01a6aaed20ffa10
|
|
| MD5 |
a4ed094c6d74739796d958e5806211a8
|
|
| BLAKE2b-256 |
a28f27d0917646f3d5792f6f9b0f525723a7ae66acc7ab7e9c7557ef14ec3709
|
File details
Details for the file annuaire_sante_fhir-0.0.3-py3-none-any.whl.
File metadata
- Download URL: annuaire_sante_fhir-0.0.3-py3-none-any.whl
- Upload date:
- Size: 50.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
79025c55d0b0cb10601c9e193cbe079190203f2355a148489be25518190faf41
|
|
| MD5 |
b2b4efbdaa83526c4a7b1dc6d4393d2d
|
|
| BLAKE2b-256 |
39a014b24682f3160898d796818368bedd312106a958792e34d3064a01ea66ee
|