annuaire-sante-fhir


Nameannuaire-sante-fhir JSON
Version 0.0.3 PyPI version JSON
download
home_pageNone
SummaryClient Python type-safe pour l'API Annuaire Santé : FHIR vers JSON DB-ready avec résolution MOS automatique
upload_time2025-10-21 17:51:06
maintainerOlivier Roch Vilato
docs_urlNone
authorOlivier Roch Vilato
requires_python>=3.9
licenseNone
keywords fhir health annuaire-sante healthcare france mos annuaire sante esante pydantic fhir-to-json rpps finess medical-directory french-healthcare
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # Annuaire Santé FHIR - Client Python Moderne

[![PyPI version](https://badge.fury.io/py/annuaire-sante-fhir.svg)](https://badge.fury.io/py/annuaire-sante-fhir)
[![Python versions](https://img.shields.io/pypi/pyversions/annuaire-sante-fhir.svg)](https://pypi.org/project/annuaire-sante-fhir/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/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

```bash
pip install annuaire-sante-fhir
```

**Note**: Le nom du package sur PyPI est `annuaire-sante-fhir`, mais l'import dans votre code reste `annuairesante`:

```python
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](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é)

```bash
export ANNUAIRE_SANTE_API_KEY="votre-cle-api"
```

### Méthode 2 : Fichier .env

Créez un fichier `.env` à la racine de votre projet :

```env
ANNUAIRE_SANTE_API_KEY=votre-cle-api
```

### Méthode 3 : Paramètre direct

```python
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)

```python
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)

```python
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

```python
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é)

```python
{
  "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é)

```python
{
  "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 :

```python
# 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 :
```bash
python examples/update_mos_cache.py
```

## Résolution MOS

Tous les codes MOS (TRE_*, JDV_*) sont automatiquement résolus en libellés lisibles :

```python
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 :

```python
# 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](docs/API_PARAMETERS.md) pour la liste exhaustive.

**Exemples courants :**

```python
# 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

```python
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

```python
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

```python
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/`](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

```bash
# 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](CONTRIBUTING.md) pour les guidelines.

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "annuaire-sante-fhir",
    "maintainer": "Olivier Roch Vilato",
    "docs_url": null,
    "requires_python": ">=3.9",
    "maintainer_email": null,
    "keywords": "fhir, health, annuaire-sante, healthcare, france, mos, annuaire, sante, esante, pydantic, fhir-to-json, rpps, finess, medical-directory, french-healthcare",
    "author": "Olivier Roch Vilato",
    "author_email": null,
    "download_url": "https://files.pythonhosted.org/packages/a2/8f/27d0917646f3d5792f6f9b0f525723a7ae66acc7ab7e9c7557ef14ec3709/annuaire_sante_fhir-0.0.3.tar.gz",
    "platform": null,
    "description": "# Annuaire Sant\u00e9 FHIR - Client Python Moderne\n\n[![PyPI version](https://badge.fury.io/py/annuaire-sante-fhir.svg)](https://badge.fury.io/py/annuaire-sante-fhir)\n[![Python versions](https://img.shields.io/pypi/pyversions/annuaire-sante-fhir.svg)](https://pypi.org/project/annuaire-sante-fhir/)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n\nClient Python moderne et type-safe pour l'API FHIR de l'Annuaire Sant\u00e9 avec mod\u00e8les JSON propres, sans artefacts FHIR, pr\u00eats pour injection en base de donn\u00e9es.\n\n## Caract\u00e9ristiques\n\n- **Mod\u00e8les Pydantic v2** propres et DB-ready sans artefacts FHIR\n- **R\u00e9solution automatique des codes MOS** (Mod\u00e8le des Objets de Sant\u00e9)\n- **Transformateurs FHIR \u2192 JSON** pour chaque ressource\n- **Type hints complets** pour une excellente exp\u00e9rience d\u00e9veloppeur\n- **Support complet des profils**:\n  - FR Core v2.1.0 (Practitioner, Organization, PractitionerRole, HealthcareService)\n  - AS DP v1.1.0 (Annuaire Sant\u00e9 Donn\u00e9es Publiques)\n\n## Installation\n\n```bash\npip install annuaire-sante-fhir\n```\n\n**Note**: Le nom du package sur PyPI est `annuaire-sante-fhir`, mais l'import dans votre code reste `annuairesante`:\n\n```python\nfrom annuairesante import AnnuaireSanteClient, transform_practitioner\n```\n\n## Configuration de la cl\u00e9 API\n\nPour utiliser l'API Annuaire Sant\u00e9, vous devez obtenir une cl\u00e9 API :\n\n1. **Obtenir une cl\u00e9** : Rendez-vous sur [https://ansforge.github.io/annuaire-sante-fhir-documentation/pages/guide/version-2/getting-started/get-api-key.html](https://ansforge.github.io/annuaire-sante-fhir-documentation/pages/guide/version-2/getting-started/get-api-key.html) et cr\u00e9ez un compte pour obtenir votre cl\u00e9 API\n2. **Configurer la cl\u00e9** : Trois m\u00e9thodes disponibles\n\n### M\u00e9thode 1 : Variable d'environnement (recommand\u00e9)\n\n```bash\nexport ANNUAIRE_SANTE_API_KEY=\"votre-cle-api\"\n```\n\n### M\u00e9thode 2 : Fichier .env\n\nCr\u00e9ez un fichier `.env` \u00e0 la racine de votre projet :\n\n```env\nANNUAIRE_SANTE_API_KEY=votre-cle-api\n```\n\n### M\u00e9thode 3 : Param\u00e8tre direct\n\n```python\nfrom annuairesante import AnnuaireSanteClient\n\nclient = AnnuaireSanteClient(api_key=\"votre-cle-api\")\n```\n\n\u26a0\ufe0f **S\u00e9curit\u00e9** : Ne commitez jamais votre cl\u00e9 API dans Git ! Ajoutez `.env` \u00e0 votre `.gitignore`.\n\n## Utilisation rapide\n\n### Recherche simple (quelques r\u00e9sultats)\n\n```python\nfrom annuairesante import AnnuaireSanteClient, transform_practitioner\n\n# Initialiser le client\nclient = AnnuaireSanteClient()  # Utilise ANNUAIRE_SANTE_API_KEY\n\n# Rechercher des professionnels\nbundle = client.practitioner.search(family=\"MARTIN\", given=\"Jean\")\n\nprint(f\"Total trouv\u00e9: {bundle.total}\")\n\n# Parcourir les r\u00e9sultats\nfor resource in bundle.entries:\n    practitioner = transform_practitioner(resource)\n\n    print(f\"Nom: {practitioner.name.full_text}\")\n    print(f\"RPPS: {practitioner.identifiers.rpps}\")\n\n    # Exporter en JSON pour base de donn\u00e9es\n    db_ready_json = practitioner.model_dump(mode='json')\n```\n\n### Synchronisation de masse (pagination automatique)\n\n```python\nfrom annuairesante import AnnuaireSanteClient, transform_organization\n\nclient = AnnuaireSanteClient()\n\n# Synchroniser toutes les organisations d'une r\u00e9gion\ncount = 0\nfor org_fhir in client.organization.search_all(\n    **{\"address-postalcode\": \"69\"},  # D\u00e9partement du Rh\u00f4ne\n    active=True\n):\n    org = transform_organization(org_fhir)\n    database.save(org.model_dump(mode='json'))\n\n    count += 1\n    if count % 100 == 0:\n        print(f\"{count} organisations synchronis\u00e9es...\")\n\nprint(f\"Total: {count} organisations\")\n```\n\n### Synchronisation incr\u00e9mentale\n\n```python\nfrom annuairesante import AnnuaireSanteClient\nfrom datetime import datetime\n\nclient = AnnuaireSanteClient()\n\n# Synchroniser uniquement les modifications depuis la derni\u00e8re synchro\nlast_sync = \"2025-01-01T00:00:00Z\"\n\nfor practitioner in client.practitioner.search_all(\n    _lastUpdated=f\"ge{last_sync}\",  # ge = greater or equal\n    **{\"address-city\": \"Lyon\"}\n):\n    database.upsert(practitioner)  # Mise \u00e0 jour ou insertion\n\n# Sauvegarder la date de synchro\ndatabase.set_last_sync(datetime.utcnow().isoformat() + \"Z\")\n```\n\n## Mod\u00e8les disponibles\n\n### Practitioner (Professionnel de sant\u00e9)\n\n```python\n{\n  \"identifiers\": {\n    \"idnps\": \"810101205564\",       # Obligatoire\n    \"rpps\": \"10101205564\",         # Obligatoire\n    \"adeli\": None\n  },\n  \"name\": {\n    \"family\": \"VERDIER\",\n    \"given\": [\"Pauline\"],\n    \"prefix\": \"MME\",               # Civilit\u00e9 (JDV_J78)\n    \"suffix\": [\"DR\"],              # Titre exercice (JDV_J79)\n    \"full_text\": \"VERDIER Pauline\"\n  },\n  \"gender\": \"female\",\n  \"birth_date\": \"1985-03-15\",\n  \"contacts\": {\n    \"phones\": [\"+33612345678\"],\n    \"emails\": [\"contact@example.com\"],\n    \"mssante\": [{\n      \"email\": \"pauline.verdier@aura.mssante.fr\",\n      \"type\": \"PER\",               # ORG, APP, PER, CAB\n      \"digitization\": false,\n      \"liste_rouge\": false\n    }]\n  },\n  \"addresses\": [{\n    \"lines\": [\"2 RUE CLAUDE BERNARD\"],\n    \"city\": \"PARIS\",\n    \"postal_code\": \"75005\",\n    \"insee_code\": \"75105\"\n  }],\n  \"qualifications\": {              # Index\u00e9 par type puis nom de syst\u00e8me\n    \"profession\": {\n      \"ProfessionSante\": {\n        \"code\": \"21\",\n        \"display\": \"M\u00e9decin\"       # R\u00e9solu via MOS\n      },\n      \"CategorieProfessionnelle\": {\n        \"code\": \"C\",\n        \"display\": \"Civil\"\n      }\n    },\n    \"diplome\": {\n      \"DiplomeEtatFrancais\": {\n        \"code\": \"DE28\",\n        \"display\": \"DE Docteur en m\u00e9decine\"\n      }\n    }\n  },\n  \"smartcards\": [{\n    \"type\": \"CPS\",\n    \"number\": \"3100434368\",\n    \"period\": {\"start\": \"2024-02-21\", \"end\": \"2027-02-21\"},\n    \"is_valid\": true\n  }],\n  \"metadata\": {\n    \"id\": \"003-3014698-3057235\",\n    \"version_id\": \"1\",\n    \"last_updated\": \"2025-04-28T18:19:26.335+02:00\",\n    \"profiles\": [\"https://hl7.fr/ig/fhir/core/StructureDefinition/fr-core-practitioner\"],\n    \"data_trace\": {\n      \"systeme_information\": \"RPPS\"\n    }\n  },\n  \"active\": true\n}\n```\n\n### Organization (Structure de sant\u00e9)\n\n```python\n{\n  \"identifiers\": {\n    \"finess\": \"750010753\",         # FINEJ ou FINEG\n    \"idnst\": \"1750010753\",\n    \"siret\": \"12345678901234\"\n  },\n  \"name\": \"PHARMACIE BLONDEEL\",\n  \"aliases\": [\"LA GRANDE PHARMACIE DU 5\"],\n  \"types_by_category\": {            # Index\u00e9 par cat\u00e9gorie\n    \"categorieEtablissement\": {\n      \"code\": \"620\",\n      \"display\": \"Pharmacie d'officine\",  # R\u00e9solu via MOS\n      \"category\": \"categorieEtablissement\"\n    },\n    \"secteurActiviteRASS\": {\n      \"code\": \"SA33\",\n      \"display\": \"Secteur priv\u00e9 commercial\",\n      \"category\": \"secteurActiviteRASS\"\n    },\n    \"statutJuridiqueINSEE\": {\n      \"code\": \"101\",\n      \"display\": \"SELAS\",\n      \"category\": \"statutJuridiqueINSEE\"\n    }\n  },\n  \"primary_type\": {                 # Type sans cat\u00e9gorie sp\u00e9cifique\n    \"code\": \"620\",\n    \"display\": \"Pharmacie d'Officine\",\n    \"category\": null\n  },\n  \"pharmacy_licence\": \"75#000283\",\n  \"addresses\": [...]\n}\n```\n\n### Autres ressources\n\n- **PractitionerRole**: Situation d'exercice (genre, mode, fonction)\n- **HealthcareService**: Service/activit\u00e9 de sant\u00e9 (modalit\u00e9, type, forme)\n- **Device**: \u00c9quipement mat\u00e9riel lourd\n\n## Acc\u00e8s simplifi\u00e9 aux donn\u00e9es\n\nLes structures JSON sont optimis\u00e9es pour un acc\u00e8s direct sans boucles :\n\n```python\n# Practitioner - Acc\u00e8s aux qualifications\npract.qualifications[\"profession\"][\"ProfessionSante\"]\n# \u2192 {\"code\": \"21\", \"display\": \"Pharmacien\"}\n\n# Helpers disponibles\npract.get_profession()  # Code profession principal\npract.get_diploma()     # Dipl\u00f4me principal\npract.get_category()    # Cat\u00e9gorie professionnelle\n\n# Organization - Acc\u00e8s aux types\norg.types_by_category[\"secteurActiviteRASS\"]\n# \u2192 {\"code\": \"SA33\", \"display\": \"Pharmacie d'officine\"}\n\norg.primary_type        # Type principal (sans cat\u00e9gorie)\n\n# Helpers disponibles\norg.get_secteur_activite()\norg.get_statut_juridique()\n```\n\n**Note** : Les cl\u00e9s des dictionnaires sont des **noms lisibles** (\"ProfessionSante\", \"DiplomeEtatFrancais\") extraits des r\u00e9f\u00e9rentiels MOS. Si l'index MOS n'est pas encore construit avec le nouveau format, les codes de table sont utilis\u00e9s comme fallback (\"TRE-G15\", \"TRE-R48\").\n\nPour construire l'index avec les noms lisibles :\n```bash\npython examples/update_mos_cache.py\n```\n\n## R\u00e9solution MOS\n\nTous les codes MOS (TRE_*, JDV_*) sont automatiquement r\u00e9solus en libell\u00e9s lisibles :\n\n```python\nfrom annuairesante.mos import MOSResolver\n\nresolver = MOSResolver()\ndisplay = resolver.resolve(\n    \"https://mos.esante.gouv.fr/NOS/TRE_G15-ProfessionSante/FHIR/TRE-G15-ProfessionSante\",\n    \"21\"\n)\n# \u2192 \"M\u00e9decin\"\n```\n\n## API et ressources disponibles\n\n### Ressources FHIR support\u00e9es\n\n| Ressource | Description | Exemples |\n|-----------|-------------|----------|\n| **Practitioner** | Professionnels de sant\u00e9 | `client.practitioner.search(family=\"MARTIN\")` |\n| **Organization** | Structures de sant\u00e9 | `client.organization.search(type=\"620\")` |\n| **PractitionerRole** | Situations d'exercice | `client.practitioner_role.search(practitioner=\"003-123456\")` |\n| **HealthcareService** | Services/activit\u00e9s de sant\u00e9 | `client.healthcare_service.search(organization=\"001-01-174986\")` |\n| **Device** | \u00c9quipements mat\u00e9riels lourds | `client.device.search(type=\"05602\")` |\n\n### M\u00e9thodes disponibles\n\nPour chaque ressource, trois m\u00e9thodes sont disponibles :\n\n```python\n# 1. search() - Recherche avec r\u00e9sultat pagin\u00e9 (Bundle)\nbundle = client.practitioner.search(family=\"MARTIN\", _count=20)\nprint(f\"Page courante: {len(bundle.entries)} r\u00e9sultats\")\n# Note: bundle.total est toujours 0 (l'API ne fournit pas ce champ)\nfor entry in bundle.entries:\n    process(entry)\n\n# Pagination manuelle\nwhile bundle.has_next():\n    bundle = bundle.next()\n    for entry in bundle.entries:\n        process(entry)\n\n# 2. search_all() - G\u00e9n\u00e9rateur avec pagination automatique\nfor practitioner in client.practitioner.search_all(family=\"MARTIN\"):\n    database.save(practitioner)\n\n# 3. get() - R\u00e9cup\u00e9rer par ID\npractitioner = client.practitioner.get(\"003-123456\")\n```\n\n### Param\u00e8tres de recherche\n\nConsultez la [documentation compl\u00e8te des param\u00e8tres](docs/API_PARAMETERS.md) pour la liste exhaustive.\n\n**Exemples courants :**\n\n```python\n# Practitioner\nclient.practitioner.search(\n    family=\"MARTIN\",                    # Nom de famille\n    given=\"Jean\",                       # Pr\u00e9nom\n    **{\"qualification-code\": \"10\"},     # Code profession (10=M\u00e9decin dans TRE-G15)\n    **{\"mailbox-mss\": \"jean@mssante.fr\"}, # Bo\u00eete MSS\n    active=True,                        # Actif uniquement\n    _lastUpdated=\"ge2025-01-01\"         # Modifi\u00e9 depuis le 1er janvier\n)\n\n# Organization\nclient.organization.search(\n    name=\"hopital\",                     # Nom (recherche partielle)\n    identifier=\"750010753\",             # FINESS, SIRET, etc.\n    type=\"620\",                         # Type (620 = Pharmacie)\n    **{\"address-city\": \"Paris\"},        # Ville\n    **{\"address-postalcode\": \"75\"},     # Code postal / d\u00e9partement\n    active=True\n)\n\n# PractitionerRole\nclient.practitioner_role.search(\n    practitioner=\"003-123456\",          # ID du professionnel\n    organization=\"001-01-879996\",       # ID de l'organisation\n    role=\"204\",                         # Code fonction/activit\u00e9\n    active=True\n)\n```\n\n## Exemples complets\n\n### 1. Recherche et affichage\n\n```python\nfrom annuairesante import AnnuaireSanteClient, transform_practitioner\n\nclient = AnnuaireSanteClient()\n\n# Rechercher des m\u00e9decins \u00e0 Lyon\nbundle = client.practitioner.search(\n    **{\"qualification-code\": \"10\"},  # M\u00e9decin (code TRE-G15)\n    _count=10\n)\n\nprint(f\"M\u00e9decins (page courante): {len(bundle.entries)} r\u00e9sultats\")\n\nfor resource in bundle.entries:\n    practitioner = transform_practitioner(resource)\n\n    print(f\"\\n{practitioner.name.full_text}\")\n    print(f\"  RPPS: {practitioner.identifiers.rpps}\")\n\n    if practitioner.contacts.mssante:\n        print(f\"  MSSant\u00e9: {practitioner.contacts.mssante[0].email}\")\n```\n\n### 2. Synchronisation r\u00e9gionale compl\u00e8te\n\n```python\nfrom annuairesante import AnnuaireSanteClient, transform_organization\nimport json\n\nclient = AnnuaireSanteClient()\n\n# Exporter toutes les pharmacies du d\u00e9partement 69 en JSON Lines\nwith open(\"pharmacies_69.jsonl\", \"w\") as f:\n    for org_fhir in client.organization.search_all(\n        type=\"620\",                             # Pharmacie d'officine\n        **{\"address-postalcode\": \"69\"},         # Rh\u00f4ne\n        active=True,\n        _count=100                              # 100 par page\n    ):\n        org = transform_organization(org_fhir)\n        json.dump(org.model_dump(mode='json'), f, ensure_ascii=False)\n        f.write(\"\\n\")\n```\n\n### 3. Mise \u00e0 jour incr\u00e9mentale quotidienne\n\n```python\nfrom annuairesante import AnnuaireSanteClient\nfrom datetime import datetime\n\nclient = AnnuaireSanteClient()\n\n# Lire la derni\u00e8re date de synchro\nlast_sync = load_last_sync_date()  # Ex: \"2025-10-08T00:00:00Z\"\n\n# Synchroniser uniquement les modifications\nmodified_count = 0\nfor org in client.organization.search_all(\n    _lastUpdated=f\"ge{last_sync}\",\n    **{\"address-postalcode\": \"69\"}\n):\n    database.upsert(org)\n    modified_count += 1\n\n# Sauvegarder la nouvelle date\nsave_last_sync_date(datetime.utcnow().isoformat() + \"Z\")\nprint(f\"{modified_count} organisations mises \u00e0 jour\")\n```\n\nVoir aussi les exemples complets dans le dossier [`examples/`](examples/) :\n- `basic_usage.py` - Utilisation basique\n- `api_search.py` - Recherches avanc\u00e9es et pagination\n- `sync_region.py` - Synchronisation de masse d'une r\u00e9gion\n- `incremental_sync.py` - Synchronisation incr\u00e9mentale avec gestion d'\u00e9tat\n\n## D\u00e9veloppement\n\n```bash\n# Installer en mode d\u00e9veloppement\npip install -e \".[dev]\"\n\n# Tests\npytest\n\n# Formatage\nblack src/\nruff check src/\n\n# Type checking\nmypy src/\n```\n\n## Standards et conformit\u00e9\n\nCette biblioth\u00e8que impl\u00e9mente:\n\n- **FR Core v2.1.0**: Profils FHIR fran\u00e7ais (HL7 France)\n  - FR Core Practitioner\n  - FR Core Organization\n  - FR Core PractitionerRole\n  - FR Core HealthcareService\n  - FR Core Address, ContactPoint, HumanName\n\n- **AS DP v1.1.0**: Profils Annuaire Sant\u00e9 Donn\u00e9es Publiques\n  - AS DP Practitioner (extensions: smartcard, mailbox-mss-metadata)\n  - AS DP Organization (extensions: organization-types, pharmacy-licence)\n  - AS DP PractitionerRole\n  - AS DP HealthcareService Healthcare Activity (extension: authorization)\n  - AS DP Device (extension: authorization)\n\n## Licence\n\nMIT\n\n## Contributions\n\nLes contributions sont les bienvenues ! Consultez [CONTRIBUTING.md](CONTRIBUTING.md) pour les guidelines.\n",
    "bugtrack_url": null,
    "license": null,
    "summary": "Client Python type-safe pour l'API Annuaire Sant\u00e9 : FHIR vers JSON DB-ready avec r\u00e9solution MOS automatique",
    "version": "0.0.3",
    "project_urls": {
        "Changelog": "https://github.com/orochvilato/annuaire-sante-fhir/releases",
        "Documentation": "https://github.com/orochvilato/annuaire-sante-fhir#readme",
        "Homepage": "https://github.com/orochvilato/annuaire-sante-fhir",
        "Issues": "https://github.com/orochvilato/annuaire-sante-fhir/issues",
        "Repository": "https://github.com/orochvilato/annuaire-sante-fhir",
        "Source Code": "https://github.com/orochvilato/annuaire-sante-fhir"
    },
    "split_keywords": [
        "fhir",
        " health",
        " annuaire-sante",
        " healthcare",
        " france",
        " mos",
        " annuaire",
        " sante",
        " esante",
        " pydantic",
        " fhir-to-json",
        " rpps",
        " finess",
        " medical-directory",
        " french-healthcare"
    ],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "39a014b24682f3160898d796818368bedd312106a958792e34d3064a01ea66ee",
                "md5": "b2b4efbdaa83526c4a7b1dc6d4393d2d",
                "sha256": "79025c55d0b0cb10601c9e193cbe079190203f2355a148489be25518190faf41"
            },
            "downloads": -1,
            "filename": "annuaire_sante_fhir-0.0.3-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "b2b4efbdaa83526c4a7b1dc6d4393d2d",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.9",
            "size": 50159,
            "upload_time": "2025-10-21T17:51:04",
            "upload_time_iso_8601": "2025-10-21T17:51:04.786353Z",
            "url": "https://files.pythonhosted.org/packages/39/a0/14b24682f3160898d796818368bedd312106a958792e34d3064a01ea66ee/annuaire_sante_fhir-0.0.3-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "a28f27d0917646f3d5792f6f9b0f525723a7ae66acc7ab7e9c7557ef14ec3709",
                "md5": "a4ed094c6d74739796d958e5806211a8",
                "sha256": "cc6f7ece865a87dc6e45f48e955816ea2f007b3d341950a7d01a6aaed20ffa10"
            },
            "downloads": -1,
            "filename": "annuaire_sante_fhir-0.0.3.tar.gz",
            "has_sig": false,
            "md5_digest": "a4ed094c6d74739796d958e5806211a8",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.9",
            "size": 38018,
            "upload_time": "2025-10-21T17:51:06",
            "upload_time_iso_8601": "2025-10-21T17:51:06.390253Z",
            "url": "https://files.pythonhosted.org/packages/a2/8f/27d0917646f3d5792f6f9b0f525723a7ae66acc7ab7e9c7557ef14ec3709/annuaire_sante_fhir-0.0.3.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-10-21 17:51:06",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "orochvilato",
    "github_project": "annuaire-sante-fhir",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": false,
    "lcname": "annuaire-sante-fhir"
}
        
Elapsed time: 3.01076s