Skip to main content

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

PyPI version Python versions License: MIT

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 :

  1. 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
  2. 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 basique
  • api_search.py - Recherches avancées et pagination
  • sync_region.py - Synchronisation de masse d'une région
  • incremental_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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

annuaire_sante_fhir-0.0.3.tar.gz (38.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

annuaire_sante_fhir-0.0.3-py3-none-any.whl (50.2 kB view details)

Uploaded Python 3

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

Hashes for annuaire_sante_fhir-0.0.3.tar.gz
Algorithm Hash digest
SHA256 cc6f7ece865a87dc6e45f48e955816ea2f007b3d341950a7d01a6aaed20ffa10
MD5 a4ed094c6d74739796d958e5806211a8
BLAKE2b-256 a28f27d0917646f3d5792f6f9b0f525723a7ae66acc7ab7e9c7557ef14ec3709

See more details on using hashes here.

File details

Details for the file annuaire_sante_fhir-0.0.3-py3-none-any.whl.

File metadata

File hashes

Hashes for annuaire_sante_fhir-0.0.3-py3-none-any.whl
Algorithm Hash digest
SHA256 79025c55d0b0cb10601c9e193cbe079190203f2355a148489be25518190faf41
MD5 b2b4efbdaa83526c4a7b1dc6d4393d2d
BLAKE2b-256 39a014b24682f3160898d796818368bedd312106a958792e34d3064a01ea66ee

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page