French public-data MCP: cross-ref health, demographics, business, geo & real-estate.
French public-data MCP server that cross-references and reconciles French public registries across health, demographics, business, and geo/real-estate. It combines data from INSEE, SIRENE, DREES, RPPS/Annuaire Santé, Ameli/Annuaire Santé, CNAM centers, DVF, Sit@del, PLU, IGN, geo.api.gouv.fr, and DINUM sources, producing outputs tied to site and group context and tracking source freshness.
🛠️ Key Features
Reconciles 13 public French data registries (health, demographics, geo, real-estate)
Detects “closed” SIRETs invisible to DREES
Distinguishes site vs group and cross-references site-level needs
Crosses healthcare supply with neighborhood demographics
Evaluates real-estate potential: DVF €/m², building permits, and AU zones from PLU
Exposes the “freshness” of each source
🚀 Use Cases
Linking health service availability to local demographic data at the neighborhood level
Investigating SIREN/SIRET relationships, including edge cases where DREES misses closed SIRETs
Supporting site-level real-estate potential analysis using DVF and planning data
⚡ Developer Benefits
TypeScript MCP implementation
Tooling aligned with Model Context Protocol (MCP)
Topic coverage across INSEE, SIRENE, FINESS, RPPS/Annuaire Santé, geo, and open-data ecosystems
Documented support for geocoding and PostGIS-style geo workflows (as indicated by topics)
⚠️ Limitations
Scope is limited to the set of cross-reconciled French public registries described in the README excerpt
The catalog details provided do not enumerate individual tool names beyond a total tool count (36)
Recherche de communes françaises par nom, code postal ou code INSEE. Idéal pour autocomplétion. Source : geo.api.gouv.fr (DINUM/Etalab).
Un (au moins) parmi `nom`, `codePostal`, `code` est requis. Alias acceptés : `q`/`query`/`search` → `nom`, `codepostal`/`postal_code` → `codePostal`, `code_insee`/`insee` → `code`.
Parameters5
nom
string
optional
Recherche par nom (autocomplétion). Ex: "Villeneuve d'Ascq", "Lyon".
codePostal
string
optional
Code postal exact (5 chiffres). Ex: "59650".
code
string
optional
Code INSEE exact (5 caractères). Ex: "59009".
limit
number
optional
Nombre max de résultats (1-30, défaut 10).
boostPopulation
boolean
optional
Trier par population décroissante. Recommandé pour les noms ambigus (ex: 'Charleville').
Raw schema
{
"type": "object",
"properties": {
"nom": {
"type": "string",
"description": "Recherche par nom (autocomplétion). Ex: \"Villeneuve d'Ascq\", \"Lyon\"."
},
"codePostal": {
"type": "string",
"description": "Code postal exact (5 chiffres). Ex: \"59650\"."
},
"code": {
"type": "string",
"description": "Code INSEE exact (5 caractères). Ex: \"59009\"."
},
"limit": {
"type": "number",
"description": "Nombre max de résultats (1-30, défaut 10).",
"default": 10
},
"boostPopulation": {
"type": "boolean",
"description": "Trier par population décroissante. Recommandé pour les noms ambigus (ex: 'Charleville').",
"default": true
}
}
}
get_commune_by_code
Récupère une commune par son code INSEE. Retourne un objet `LookupResult` discriminé par `found`. `found: true` → champs commune à plat (nom, codesPostaux, centre…). `found: false` → `{ found: false, key, lookupStatus: 'not_found', message }` orientant vers `autocomplete_commune` pour disambiguer.
Alias acceptés : `code_insee`/`codeInsee`/`insee` → `code`.
Géocode une adresse française en coordonnées GPS. Source : IGN Géoplateforme (data.geopf.fr). Précision au numéro de rue.
Le champ `score` (0-1) qualifie la fiabilité du match : >= 0.8 fiable, < 0.5 = match douteux (souvent un fallback rue/commune sans rapport avec l'adresse demandée). Le champ booléen `confidence_low` vaut `true` dans ce cas : ne PAS utiliser `point` pour une décision quand `confidence_low: true`. Le champ `type` indique aussi la granularité (housenumber > street > locality > municipality).
Parameters3
adresse
string
required
Adresse complète à géocoder.
codePostal
string
optional
Optionnel — limiter le résultat à un code postal pour désambiguïser.
codeCommune
string
optional
Optionnel — limiter au code INSEE de commune.
Raw schema
{
"type": "object",
"properties": {
"adresse": {
"type": "string",
"description": "Adresse complète à géocoder."
},
"codePostal": {
"type": "string",
"description": "Optionnel — limiter le résultat à un code postal pour désambiguïser."
},
"codeCommune": {
"type": "string",
"description": "Optionnel — limiter au code INSEE de commune."
}
},
"required": [
"adresse"
]
}
reverse_geocode
Géocodage inverse : à partir de coordonnées GPS, retrouve l'adresse la plus proche. Source : IGN Géoplateforme. Couverture France métropolitaine + DOM uniquement : des coordonnées hors zone (ex. New York) ou en pleine mer renvoient `null` (pas une erreur — c'est l'absence de résultat, pas une panne).
Population d'une COMMUNE (code INSEE 5 car.), d'un DÉPARTEMENT (2-3 car.) OU d'un IRIS infracommunal (9 car.) — granularité auto-détectée par la longueur du `code`. Retourne un `LookupResult` discriminé par `found`.
- IRIS (9 car., ex `751103701` = commune `75110` + IRIS `3701`) : population totale du quartier au Recensement 2022 (champ `population`, comptes bruts), + `libelle`, `code_commune`, `type_iris` (H/A/D/Z). Source : INSEE RP 2022 (table ingérée, géo 01/01/2024). Maille la plus fine (quartier) pour les villes ; en zone peu dense la commune = 1 IRIS (`type_iris` Z, code `COM+0000`). Pour le profil démographique détaillé d'un îlot ou d'un bassin (âge, CSP, familles, revenu), utiliser `profil_iris`.
- Commune (5 car., ex `75056` Paris, `13055` Marseille, `2A004` Ajaccio) : PMUN/PCAP/PTOT. Source INSEE Melodi (DS_POPULATIONS_REFERENCE). PMUN = base légale DREES. Commune fusionnée → `found: false` + orientation `autocomplete_commune`. INSEE n'expose PAS les arrondissements PLM (75101-75120, 13201-13216, 69381-69389) → passer la commune-mère ou le département.
- Département (2-3 car., ex `75`, `59`, `2A`, `971`) : Mayotte (`976`) ABSENTE de Melodi → `lookupNotFound`.
Alias acceptés : `code_insee`/`codeInsee`/`insee`, `code_dept`/`dept`/`departement`/`code_departement`, `code_iris`/`iris` → `code`.
Parameters1
code
string
required
Code INSEE — 5 caractères = commune (ex "75056"), 2-3 caractères = département (ex "75", "971", "2A"). Granularité auto-détectée par la longueur.
Raw schema
{
"type": "object",
"properties": {
"code": {
"type": "string",
"description": "Code INSEE — 5 caractères = commune (ex \"75056\"), 2-3 caractères = département (ex \"75\", \"971\", \"2A\"). Granularité auto-détectée par la longueur."
}
},
"required": [
"code"
]
}
profil_iris
Profil démographique au grain QUARTIER (IRIS) — la « demande » d'un territoire (âge, CSP, familles, revenu), à croiser avec l'offre de soins pour l'aide à l'implantation. Source : INSEE RP 2022 + FILOSOFI 2021 (tables ingérées, géo 01/01/2024). Retourne un `LookupResult` discriminé par `found`.
Entrée : EXACTEMENT un de `point` (`lat`+`lon`) OU `code_iris` (9 car.). `rayon_km` optionnel (0 < r ≤ 10) → DEUX modes :
- SANS `rayon_km` → profil de l'ÎLOT seul (~2000 hab) sous le point / du code. `mode: "ilot"`, `revenu_median` = médiane réelle de l'îlot.
- AVEC `rayon_km` → AGRÉGAT du BASSIN = îlots dont le CENTROÏDE est dans le disque (chaque îlot compté 1 fois). `mode: "bassin"`, `population_bassin`, `nb_iris_agreges`, et `revenu_median_pondere` = PROXY (moyenne pondérée population des médianes des îlots couverts — PAS une vraie médiane de bassin) + `couverture` {`revenu_pct_population`, `iris_revenu_manquants`} car FILOSOFI ne couvre que les communes ≥5000 hab.
Les parts `age` (part_65_plus/75_plus) et `csp` (cadres, prof_interm, employés, ouvriers, agriculteurs, artisans_comm, retraités, autres) sont des ratios sur comptes bruts (Σ/Σ). Pour une simple population de commune/dept, utiliser `population`. `not_found` motivé si code absent ou point hors métropole / en mer.
Parameters4
lat
number
optional
Latitude du point (mode point).
lon
number
optional
Longitude du point (mode point).
code_iris
string
optional
Code IRIS 9 caractères (ex `751103701`) — alternatif au point.
rayon_km
number
optional
Rayon du bassin en km (0 < r ≤ 10). Absent = profil de l'îlot seul.
Raw schema
{
"type": "object",
"properties": {
"lat": {
"type": "number",
"description": "Latitude du point (mode point)."
},
"lon": {
"type": "number",
"description": "Longitude du point (mode point)."
},
"code_iris": {
"type": "string",
"description": "Code IRIS 9 caractères (ex `751103701`) — alternatif au point."
},
"rayon_km": {
"type": "number",
"description": "Rayon du bassin en km (0 < r ≤ 10). Absent = profil de l'îlot seul."
}
}
}
entreprises_in_radius
Recherche d'entreprises françaises avec filtres NAF, code postal, département ou rayon géographique. Couvre tous secteurs (santé via NAF 8690B, 4773Z, 8710A, 8621Z, etc.). Source : DINUM Recherche Entreprises (SIRENE + RNE). Renvoie CA, dirigeants, tranches d'effectif et dates de création.
Deux modes EXCLUSIFs (endpoints DINUM distincts) : (1) proximité — `lat`+`lon`+`radiusKm` (optionnellement + `naf`), résolu nativement via `/near_point` ; (2) administratif — `q` (texte libre) et/ou `naf` + `codePostal`/`departement`, via `/search`. La recherche de proximité ne supporte PAS `q` ni `codePostal`/`departement` (combinaison rejetée avec une erreur explicite : choisir un seul mode). `radiusKm` borné à 50 km.
**Réduction de payload (V0.13)** : `includeDirigeants: false` strip la liste des dirigeants RNE de chaque entreprise du résultat — utile en énumération volume (Geo Intel) où les dirigeants ne sont pas exploités et où les groupes type Biogroup peuvent en lister 20+ par entité (gonflement inutile du payload). Défaut `true` pour préserver le contrat V0.12 (backward-compat strict).
Inclure la liste des dirigeants RNE dans chaque entreprise (défaut true). `false` strip `dirigeants: []` côté handler — utile en énumération volume où les dirigeants ne sont pas exploités (économie de tokens, groupes type Biogroup peuvent lister 20+ dirigeants par entité).
Raw schema
{
"type": "object",
"properties": {
"naf": {
"type": "string",
"description": "Code NAF principal (ex: '8690B' = labos, '4773Z' = pharmacies, '8710A' = EHPAD, '8621Z' = MG)."
},
"q": {
"type": "string",
"description": "Recherche textuelle libre (raison sociale, dirigeant…)."
},
"lon": {
"type": "number",
"description": "Longitude du centre du cercle de recherche."
},
"lat": {
"type": "number",
"description": "Latitude du centre du cercle de recherche."
},
"radiusKm": {
"type": "number",
"description": "Rayon en km (1-50)."
},
"codePostal": {
"type": "string",
"description": "Filtre alternatif : code postal exact."
},
"departement": {
"type": "string",
"description": "Filtre alternatif : code département."
},
"perPage": {
"type": "number",
"description": "Résultats par page (1-25, défaut 10).",
"default": 10
},
"page": {
"type": "number",
"description": "Page (1-indexed).",
"default": 1
},
"includeDirigeants": {
"type": "boolean",
"description": "Inclure la liste des dirigeants RNE dans chaque entreprise (défaut true). `false` strip `dirigeants: []` côté handler — utile en énumération volume où les dirigeants ne sont pas exploités (économie de tokens, groupes type Biogroup peuvent lister 20+ dirigeants par entité).",
"default": true
}
}
}
entreprise_by_siren
Récupère le détail d'une entreprise française par son SIREN (9 chiffres) : raison sociale, NAF, finances historiques, dirigeants, établissements. Source : DINUM Recherche Entreprises.
**Format de retour** : objet `LookupResult` discriminé par `found`.
- `found: true` → l'entreprise est retournée à plat (champs `siren`, `nomComplet`, `etablissements`, `enrichmentStatus`, …)
- `found: false` → `{ found: false, key, lookupStatus: 'not_found' | 'ambiguous', message }`. `not_found` : SIREN non indexé par DINUM (souvent diffusion partielle INSEE — l'entreprise peut quand même exister dans SIRENE). `ambiguous` : régression API à signaler.
⚠️ Quand `found: true`, la liste `etablissements` peut être tronquée. Le champ `nombreEtablissements` (compté SIRENE) reflète le total réel. **Lire `enrichmentStatus`** pour savoir si la liste est complète :
- `success` : `etablissements` contient tous les sites
- `partial` : sites manquants (multi-département ou NAF différent du siège) — voir `enrichmentWarning`
- `failed` : l'enrichissement a échoué (rate limit, panne API) — seul le siège est listé
- `not_attempted` : entreprise monosite ou data SIRENE manquante
Pour énumération exhaustive multi-département, utiliser `entreprises_in_radius` par zone géographique. Coût : 1 ou 2 appels API DINUM par invocation (rate limit ~1 req/s effectif).
Retourne la fraîcheur des dumps de données ingérés côté serveur : FINESS / ANS (flux quotidien, ingéré le 1er et le 15 du mois), Annuaire Santé Ameli (hebdomadaire), RPPS / Annuaire Santé ANS (mensuel), Centres de Santé CNAM (hebdomadaire), IRIS INSEE (annuel), permis de construire Sit@del / SDES (mensuel, cron le 10). Pour chaque source : `last_data_change_at` + `data_age_days` (dernier run ayant RÉELLEMENT changé la donnée servie, et son âge en jours — C'EST LE CHAMP À LIRE), `last_success_at` + `staleness_days` (dernier run réussi, y compris un run court-circuité « fichier amont identique » — ne mesure PAS l'âge de la donnée), `last_success_row_count`, `last_attempt_at`, `last_attempt_status`, `cadence_hint` (cadence attendue).
Usage typique : avant un audit territorial ou une analyse temporelle, le caller appelle ce tool pour savoir si les données sont à jour. Juger sur `data_age_days`, JAMAIS sur `staleness_days` seul : en 2026 la source FINESS s'est tarie 4 mois pendant que `staleness_days` restait à quelques jours (runs « fichier identique » comptés comme succès). Règle d'alerte : `data_age_days > expected_max_age_days` (seuil par source, exposé dans chaque ligne — ne pas le recopier) ; `data_age_days: null` = jamais ingéré.
Les sources LIVE (DINUM Recherche Entreprises, INSEE SIRENE V3.11, ANS FHIR live) ne sont PAS listées ici puisqu'elles n'ont pas de cycle d'ingestion — leur fraîcheur est celle des API amont (live, ~secondes).
Cache serveur : 5 minutes. Coût : 1 SELECT sur `ingest_log` au pire (sinon hit cache).
Compare la raison sociale FINESS DREES vs RPPS / Annuaire Santé ANS pour un même num_finess. Primitive brute SANS interprétation métier — retourne juste les deux libellés + un statut de comparaison. Le caller décide quoi faire de la divergence.
Utilité : RPPS reflète souvent plus rapidement les rebrandings post-M&A que FINESS DREES (ex: un site racheté reste 'DIAGNOVIE' chez DREES alors qu'il est déjà 'BIOGROUP NORD' chez l'ANS). Ce tool expose la divergence factuelle ; il NE DIT PAS qui a racheté qui (ça repose sur de la connaissance d'enseignes commerciales non publique).
**Statut renvoyé** (champ `statut` présent uniquement sur la branche `found: true`) :
- `exact_match` : FINESS et ≥1 RPPS sont strictement égaux après normalisation
- `divergent_after_normalization` : aucune RPPS ne matche FINESS — vraie divergence
- `rpps_absent` : aucune RPPS n'a déclaré ce FINESS (pivot impossible)
Format : objet `LookupResult` discriminé par `found`. Quand `num_finess` est absent de FINESS DREES, le tool retourne `{found: false, lookupStatus: 'not_found', message, ...}` — il n'y a PAS de champ `statut` dans ce cas.
Compare l'adresse d'un centre de santé côté CNAM (Annuaire santé Ameli) vs FINESS DREES pour un même num_finess. Primitive brute SANS interprétation métier — retourne les deux adresses, un `score_dice` (0..1, informatif ; `null` si non comparable car `finess_absent`) et un `statut`. Le caller décide quoi faire de la divergence.
Utilité : signaler un déménagement propagé par une source mais pas (encore) par l'autre (ex: CNAM '5 RUE DE L'ARQUEBUSE AUTUN' vs FINESS '15 BD BERNARD GIBERSTEIN AUTUN' pour le même FINESS). Équivalent côté centre de santé de `compare_raison_sociale_finess_vs_rpps`.
**Statut** (présent uniquement sur `found: true`) :
- `match` : adresses strictement égales après normalisation
- `match_after_abbreviation_normalization` : égales après expansion des abréviations de voie FR (R/RUE, BD/BOULEVARD, AV/AVENUE…) — MÊME adresse, simple abréviation DREES vs CNAM, PAS un déménagement
- `divergent_after_normalization` : adresses réellement différentes (déménagement non synchronisé entre sources)
- `finess_absent` : le CDS existe côté CNAM mais le num_finess est absent de FINESS DREES (latence sync bimensuelle)
Format : objet `LookupResult` discriminé par `found`. Si le num_finess n'est PAS un centre de santé CNAM, le tool retourne `{found: false, lookupStatus: 'not_found', message}` (utiliser `etablissement_by_finess` pour un établissement non-CDS).
Reconstitue la timeline complète d'un établissement de santé (ouvertures, fermetures, changements de NAF/enseigne) en croisant FINESS DREES ↔ resolver SIRET (RPPS + DINUM) ↔ SIRENE INSEE V3.11. Lit les `periodesEtablissement` complètes pour chaque SIRET candidat.
**V0.7.0** : SIRET candidats élargis via le resolver — inclut désormais les SIRET fermés du SIREN parent qui matchent l'adresse FINESS (invisibles côté RPPS seul). Permet de tracer la fermeture exacte d'un site même quand FINESS le liste encore actif.
Usage typique :
- Tracer l'historique d'un site après une fusion-acquisition
- Identifier la date de fermeture exacte d'un SIRET encore listé actif côté FINESS
- Comprendre une cascade de rebrandings via les changements de `enseigne1Etablissement` au fil des périodes
Format : objet `LookupResult`. Quand `found: true`, retourne `finess` (vue DREES synthétique) + `siret_timelines` (1 entrée par SIRET candidat avec `periodes` chronologiques).
Coût : 1 RPC FINESS + 1 SELECT rpps + N appels DINUM + N appels INSEE en parallèle (N ≤ 5 typiquement). Pas de cache.
Croise FINESS DREES ↔ SIRENE INSEE V3.11 et calcule un score de cohérence (Sørensen-Dice sur bigrammes) pour chaque SIRET candidat. Utile pour confirmer/infirmer un appariement num_finess ↔ SIRET avant prospection ou cross-check qualité.
Logique :
1. Récupère FINESS (raison sociale + adresse libellée)
2. Récupère SIRET candidats via la table RPPS
3. Pour chaque SIRET, lookup SIRENE puis calcule 3 sous-scores :
- `nom` : Dice sur raison sociale (FINESS vs SIRENE.uniteLegale)
- `adresse` : Dice sur adresse complète
- `telephone` : binaire 0/1 (toujours 0 actuellement : SIRENE n'expose pas le tel)
4. Score global = pondération (nom 0.5, adresse 0.4, tel 0.1)
5. Verdict brut : `match` (≥0.8) / `partial` (0.5..0.8) / `mismatch` (<0.5)
Algorithme PUBLIC (Sørensen-Dice est dans la littérature depuis 1948). Aucune valeur ajoutée Unilabs ici — c'est une primitive ouverte. La connaissance propriétaire (mapping enseignes ↔ SELAS) reste côté Geo Intel.
Format : objet `LookupResult`. Quand `found: true`, retourne `{ num_finess, candidates, skipped }` :
- `candidates` : tableau trié par `score_global` décroissant (meilleur match en premier)
- `skipped` : SIRET candidats qu'on n'a PAS pu réconcilier (lookup SIRENE rejected ou not_found) avec la `reason`. Permet au caller de distinguer 'aucun SIRET candidat trouvé' (`found: false` LookupResult.not_found) de 'N SIRETs candidats mais tous rejetés par SIRENE' (`candidates: []` + `skipped: [...]`).
Vérifie si un établissement de santé FINESS est encore en activité en croisant FINESS DREES ↔ RPPS (pivot SIRET) ↔ DINUM (liste complète des SIRET du SIREN, incluant les fermés). Détecte les SIRET fermés encore listés actifs côté FINESS (DREES a 1-2 mois de retard).
**V0.16 — fix succession M&A** : quand un site a changé d'exploitant (rachat), l'ancien SIRET fermé et le repreneur actif coexistent à la même adresse. Le resolver privilégie désormais le SIRET ACTIF co-localisé avec le FINESS (distance géodésique ≤ 100 m, recalibré V0.16.1 — le géocodage DREES place le point FINESS à plusieurs dizaines de mètres de l'adresse réelle) — avant, le verdict pouvait être `ferme` à tort, le best_match étant choisi sur la seule ressemblance d'adresse. Parmi les co-localisés, seul l'actif de la bande la plus proche prime : un voisin actif d'une autre adresse ne bascule pas le verdict. Un site RÉELLEMENT fermé reste `ferme` (aucun SIRET actif co-localisé).
Logique :
1. Lookup FINESS pour récupérer raison sociale + adresse + téléphone DREES
2. SIRET candidats via le resolver : pivot RPPS, puis fallback géo DINUM /near_point (récupère TOUS les SIRET autour de l'adresse FINESS, actifs ET fermés — capte le repreneur invisible côté RPPS)
3. `best_match` = le SIRET ACTIF co-localisé avec le FINESS s'il en existe un ; sinon le meilleur candidat (possiblement fermé). La co-localisation est une distance géo, pas un score textuel.
4. **2 verdicts distincts** :
- `verdict_site` (`actif` / `ferme` / `indetermine`) : basé sur `best_match.actif`. C'est le verdict qui compte pour un audit territorial.
- `verdict_groupe` (`actif` / `ferme` / `indetermine`) : basé sur l'état admin de l'UL parente (champ `actif` DINUM). Une UL active peut très bien avoir un site fermé.
**Format de retour** : objet `LookupResult` discriminé par `found`. Quand `found: true`, le payload contient `finess` (vue DREES), `candidates` (liste enrichie — chaque candidat porte `distance_finess_m`), `best_match`, `sirens_explored`, `verdict_site`, `verdict_groupe`, `succession` (`{ detected, exploitants_precedents }` — les SIRET fermés co-localisés avec le repreneur ; fait brut, le tool ne qualifie PAS de « rachat »), `explication`. Quand `num_finess` est absent de FINESS DREES, le tool retourne `{found: false, lookupStatus: 'not_found', message, ...}`.
Coût : 1 RPC FINESS + 1 SELECT rpps + N appels DINUM (N = nombre de SIREN distincts, typiquement 1). DINUM gère son propre fallback INSEE V3.11 pour les SIREN diffusion partielle.
Récupère le détail d'un établissement par son SIRET (14 chiffres) via l'API SIRENE INSEE V3.11 : raison sociale de l'unité légale, enseigne commerciale, NAF de l'établissement, dates de création/fermeture, statut administratif actif/fermé, adresse complète, tranche d'effectif. Source : SIRENE INSEE V3.11 (api.insee.fr).
**Format de retour** : objet `LookupResult` discriminé par `found`.
- `found: true` → établissement à plat (`siret`, `siren`, `actif`, `dateFermeture`, `enseigne`, `adresse`, …)
- `found: false` → `{ found: false, key, lookupStatus: 'not_found', message }`. Cas typiques : clé `INSEE_SIRENE_API_KEY` non configurée côté serveur (message explicite), SIRET inexistant SIRENE, diffusion partielle INSEE.
⚠️ Différence avec `entreprise_by_siren` : ce tool renvoie UN établissement précis (un site), alors que `entreprise_by_siren` renvoie l'unité légale + sa liste d'établissements. Pour détecter un SIRET fermé encore listé actif côté FINESS, lire `actif: false` + `dateFermeture`.
**Pas de coords** : l'endpoint INSEE `/siret/<siret>` ne renvoie pas les coordonnées GPS. Pour géolocaliser, croiser avec `geocode_adresse` côté caller ou utiliser `entreprises_in_radius`.
Rate limit INSEE : 30 req/min (retry-after géré côté serveur).
Recherche d'établissements de santé FINESS dans un rayon géographique (PostGIS ST_DWithin). Filtrable par familles. 24 valeurs disponibles : mco, ssr, sld, had, psychiatrie, dialyse, ambulatoire, labo, imagerie, pharmacie, msp_cpts, ehpad, residence_autonomie, senior_accompagnement, ssiad, aide_domicile, handicap_enfants, handicap_adultes, addictologie, enfance_protection, pmi, hebergement_social, prevention_sante, groupement. Source : FINESS / ANS (flux quotidien, ingéré le 1ᵉʳ et le 15 ; établissements EN SERVICE uniquement). Chaque résultat porte `geo_precision: "adresse"` dès que `coords` est présent (point WGS84 ANS ou point BAN de l'adresse, jamais un centroïde ; un établissement sans `coords` n'a pas de point connu et est invisible des recherches par rayon) et `siret_ans` (SIRET déclaré par l'ANS, fait brut non vérifié SIRENE — pour le verdict : reconcilier_finess_sirene / verifier_site_actif). Note : champ `email` toujours `null` (non exposé par FINESS public). Lentille : un filtre `familles` compte les établissements par leur catégorie FINESS *principale*. Les activités hébergées dans un site d'une autre catégorie (ex. plateau de biologie d'un hôpital sous `famille=labo`) ne sont pas comptées — voir le champ `perimetre` de la réponse. La famille `imagerie` renvoie le plus souvent 0 résultat (FINESS ne répertorie pas les cabinets d'imagerie).
Parameters6
lon
number
required
Longitude du centre (WGS84).
lat
number
required
Latitude du centre (WGS84).
radius_km
number
optional
Rayon en km (0.1-50, défaut 5).
familles
array
optional
Familles FINESS à inclure (24 valeurs disponibles, voir enum). Si omis, toutes catégories.
limit
number
optional
Nombre max de résultats (1-500, défaut 100).
include_freshness
boolean
optional
Si true, ajoute un champ `data_freshness` au payload (dans `query_metadata` si présent, sinon à la racine) listant, par source (FINESS, Ameli, RPPS, CDS, IRIS), la dernière ingestion réussie (`last_success_at`, `staleness_days`) ET la dernière fois que la donnée a réellement changé (`last_data_change_at`, `data_age_days` — un run court-circuité « fichier amont identique » compte comme succès mais ne rajeunit pas la donnée ; c'est `data_age_days` qui dit l'âge réel de ce qui est servi). Opt-in pour ne pas alourdir les payloads par défaut. Cache 5min côté serveur — coût négligeable.
Raw schema
{
"type": "object",
"properties": {
"lon": {
"type": "number",
"description": "Longitude du centre (WGS84)."
},
"lat": {
"type": "number",
"description": "Latitude du centre (WGS84)."
},
"radius_km": {
"type": "number",
"description": "Rayon en km (0.1-50, défaut 5).",
"minimum": 0.1,
"maximum": 50,
"default": 5
},
"familles": {
"type": "array",
"description": "Familles FINESS à inclure (24 valeurs disponibles, voir enum). Si omis, toutes catégories.",
"items": {
"type": "string",
"enum": [
"mco",
"ssr",
"sld",
"had",
"psychiatrie",
"dialyse",
"ambulatoire",
"labo",
"imagerie",
"pharmacie",
"msp_cpts",
"ehpad",
"residence_autonomie",
"senior_accompagnement",
"ssiad",
"aide_domicile",
"handicap_enfants",
"handicap_adultes",
"addictologie",
"enfance_protection",
"pmi",
"hebergement_social",
"prevention_sante",
"groupement"
]
}
},
"limit": {
"type": "number",
"description": "Nombre max de résultats (1-500, défaut 100).",
"minimum": 1,
"maximum": 500,
"default": 100
},
"include_freshness": {
"type": "boolean",
"default": false,
"description": "Si true, ajoute un champ `data_freshness` au payload (dans `query_metadata` si présent, sinon à la racine) listant, par source (FINESS, Ameli, RPPS, CDS, IRIS), la dernière ingestion réussie (`last_success_at`, `staleness_days`) ET la dernière fois que la donnée a réellement changé (`last_data_change_at`, `data_age_days` — un run court-circuité « fichier amont identique » compte comme succès mais ne rajeunit pas la donnée ; c'est `data_age_days` qui dit l'âge réel de ce qui est servi). Opt-in pour ne pas alourdir les payloads par défaut. Cache 5min côté serveur — coût négligeable."
}
},
"required": [
"lon",
"lat"
]
}
etablissements_finess_by_categorie
Liste des établissements FINESS par famille, avec filtre département ou commune optionnel. Pas de rayon — pour énumération exhaustive d'une zone administrative. 24 familles disponibles : mco, ssr, sld, had, psychiatrie, dialyse, ambulatoire, labo, imagerie, pharmacie, msp_cpts, ehpad, residence_autonomie, senior_accompagnement, ssiad, aide_domicile, handicap_enfants, handicap_adultes, addictologie, enfance_protection, pmi, hebergement_social, prevention_sante, groupement.
V0.19.0 : accepte `nom_commune` (string) comme alternative à `code_insee` (résolu via geo.api.gouv.fr). XOR strict — passer SOIT `departement` SOIT `code_insee` SOIT `nom_commune` (combinable avec `departement` qui agit alors comme hint de désambiguïsation pour homonymes type "Saint-Martin"). Aucun param zone = France entière (acceptée).
Source : FINESS / ANS (flux quotidien, ingéré le 1ᵉʳ et le 15 ; établissements EN SERVICE uniquement). Chaque résultat porte `geo_precision: "adresse"` dès que `coords` est présent (point WGS84 ANS ou point BAN de l'adresse, jamais un centroïde ; un établissement sans `coords` n'a pas de point connu et est invisible des recherches par rayon) et `siret_ans` (SIRET déclaré par l'ANS, fait brut non vérifié SIRENE — pour le verdict : reconcilier_finess_sirene / verifier_site_actif). Note : champ `email` toujours `null` (non exposé par FINESS public). Lentille : un filtre `familles` compte les établissements par leur catégorie FINESS *principale*. Les activités hébergées dans un site d'une autre catégorie (ex. plateau de biologie d'un hôpital sous `famille=labo`) ne sont pas comptées — voir le champ `perimetre` de la réponse. La famille `imagerie` renvoie le plus souvent 0 résultat (FINESS ne répertorie pas les cabinets d'imagerie).
Parameters6
categorie
string
required
Famille FINESS recherchée (24 valeurs disponibles, voir enum).
departement
string
optional
Code département INSEE (ex: '75', '2A', '2B', '971'). Métropole 2 caractères (Corse '2A'/'2B', pas '20'), DOM/TOM 3 caractères. Optionnel. Combinable avec `nom_commune` comme hint resolver (filtre les homonymes), sinon XOR strict avec `code_insee` et `nom_commune`.
code_insee
string
optional
Code INSEE de commune (5 caractères). Optionnel. XOR strict avec `departement` et `nom_commune`.
nom_commune
string
optional
Nom officiel de commune (alternative à `code_insee`, V0.19). Ex: "Lille", "Saint-Étienne". Le serveur résout en interne via geo.api.gouv.fr. Si ambigu (ex "Saint-Martin" → 5 villes), retourne une erreur structurée avec candidates. Combinable avec `departement` comme hint de désambiguïsation. Abréviations type "St-Martin" non reconnues — utiliser le nom officiel complet.
limit
number
optional
Nombre max de résultats (1-500, défaut 100).
include_freshness
boolean
optional
Si true, ajoute un champ `data_freshness` au payload (dans `query_metadata` si présent, sinon à la racine) listant, par source (FINESS, Ameli, RPPS, CDS, IRIS), la dernière ingestion réussie (`last_success_at`, `staleness_days`) ET la dernière fois que la donnée a réellement changé (`last_data_change_at`, `data_age_days` — un run court-circuité « fichier amont identique » compte comme succès mais ne rajeunit pas la donnée ; c'est `data_age_days` qui dit l'âge réel de ce qui est servi). Opt-in pour ne pas alourdir les payloads par défaut. Cache 5min côté serveur — coût négligeable.
Raw schema
{
"type": "object",
"properties": {
"categorie": {
"type": "string",
"description": "Famille FINESS recherchée (24 valeurs disponibles, voir enum).",
"enum": [
"mco",
"ssr",
"sld",
"had",
"psychiatrie",
"dialyse",
"ambulatoire",
"labo",
"imagerie",
"pharmacie",
"msp_cpts",
"ehpad",
"residence_autonomie",
"senior_accompagnement",
"ssiad",
"aide_domicile",
"handicap_enfants",
"handicap_adultes",
"addictologie",
"enfance_protection",
"pmi",
"hebergement_social",
"prevention_sante",
"groupement"
]
},
"departement": {
"type": "string",
"description": "Code département INSEE (ex: '75', '2A', '2B', '971'). Métropole 2 caractères (Corse '2A'/'2B', pas '20'), DOM/TOM 3 caractères. Optionnel. Combinable avec `nom_commune` comme hint resolver (filtre les homonymes), sinon XOR strict avec `code_insee` et `nom_commune`."
},
"code_insee": {
"type": "string",
"description": "Code INSEE de commune (5 caractères). Optionnel. XOR strict avec `departement` et `nom_commune`."
},
"nom_commune": {
"type": "string",
"description": "Nom officiel de commune (alternative à `code_insee`, V0.19). Ex: \"Lille\", \"Saint-Étienne\". Le serveur résout en interne via geo.api.gouv.fr. Si ambigu (ex \"Saint-Martin\" → 5 villes), retourne une erreur structurée avec candidates. Combinable avec `departement` comme hint de désambiguïsation. Abréviations type \"St-Martin\" non reconnues — utiliser le nom officiel complet."
},
"limit": {
"type": "number",
"description": "Nombre max de résultats (1-500, défaut 100).",
"minimum": 1,
"maximum": 500,
"default": 100
},
"include_freshness": {
"type": "boolean",
"default": false,
"description": "Si true, ajoute un champ `data_freshness` au payload (dans `query_metadata` si présent, sinon à la racine) listant, par source (FINESS, Ameli, RPPS, CDS, IRIS), la dernière ingestion réussie (`last_success_at`, `staleness_days`) ET la dernière fois que la donnée a réellement changé (`last_data_change_at`, `data_age_days` — un run court-circuité « fichier amont identique » compte comme succès mais ne rajeunit pas la donnée ; c'est `data_age_days` qui dit l'âge réel de ce qui est servi). Opt-in pour ne pas alourdir les payloads par défaut. Cache 5min côté serveur — coût négligeable."
}
},
"required": [
"categorie"
]
}
etablissement_by_finess
Récupère le détail complet d'un établissement de santé par son numéro FINESS (9 chiffres) : raison sociale, catégorie + famille, adresse complète (voie + CP + ville + code INSEE + département), coordonnées GPS, téléphone. Retourne un objet `LookupResult` discriminé par `found`. `found: true` → champs FINESS à plat. `found: false` → `{ found: false, key, lookupStatus: 'not_found', message }` (numéro inexistant, établissement fermé, ou trop récent pour le flux). Source : FINESS / ANS (flux quotidien, ingéré le 1ᵉʳ et le 15 ; établissements EN SERVICE uniquement). Chaque résultat porte `geo_precision: "adresse"` dès que `coords` est présent (point WGS84 ANS ou point BAN de l'adresse, jamais un centroïde ; un établissement sans `coords` n'a pas de point connu et est invisible des recherches par rayon) et `siret_ans` (SIRET déclaré par l'ANS, fait brut non vérifié SIRENE — pour le verdict : reconcilier_finess_sirene / verifier_site_actif). Note : champ `email` toujours `null` (non exposé par FINESS public).
Parameters2
num_finess
string
required
Numéro FINESS exact (9 chiffres).
include_freshness
boolean
optional
Si true, ajoute un champ `data_freshness` au payload (dans `query_metadata` si présent, sinon à la racine) listant, par source (FINESS, Ameli, RPPS, CDS, IRIS), la dernière ingestion réussie (`last_success_at`, `staleness_days`) ET la dernière fois que la donnée a réellement changé (`last_data_change_at`, `data_age_days` — un run court-circuité « fichier amont identique » compte comme succès mais ne rajeunit pas la donnée ; c'est `data_age_days` qui dit l'âge réel de ce qui est servi). Opt-in pour ne pas alourdir les payloads par défaut. Cache 5min côté serveur — coût négligeable.
Raw schema
{
"type": "object",
"properties": {
"num_finess": {
"type": "string",
"description": "Numéro FINESS exact (9 chiffres)."
},
"include_freshness": {
"type": "boolean",
"default": false,
"description": "Si true, ajoute un champ `data_freshness` au payload (dans `query_metadata` si présent, sinon à la racine) listant, par source (FINESS, Ameli, RPPS, CDS, IRIS), la dernière ingestion réussie (`last_success_at`, `staleness_days`) ET la dernière fois que la donnée a réellement changé (`last_data_change_at`, `data_age_days` — un run court-circuité « fichier amont identique » compte comme succès mais ne rajeunit pas la donnée ; c'est `data_age_days` qui dit l'âge réel de ce qui est servi). Opt-in pour ne pas alourdir les payloads par défaut. Cache 5min côté serveur — coût négligeable."
}
},
"required": [
"num_finess"
]
}
etablissement_finess_by_nom
Cherche un établissement de santé par son NOM (« Institut Gustave Roussy », « Hôpital Foch », « Clinique Pasteur ») et rend ses fiches FINESS avec coordonnées exactes. À APPELER AVANT tout géocodage dès que le lieu cité est un hôpital, une clinique, un centre, un institut, un EHPAD… : l'annuaire d'adresses (BAN / `geocode_adresse`) ne connaît que des rues et renverrait une « rue Gustave » n'importe où en France. Passer le nom PROPRE de l'établissement (le sigle « IGR » n'est pas dans FINESS) et, si connue, la commune (`nom_commune` ou `code_insee` — Paris/Lyon/Marseille acceptés en ville entière) ou le `departement`.
Sortie : `statut` = `unique` (une fiche) | `ambigu` (plusieurs : homonymes OU plusieurs fiches d'un même campus — l'IGR en a 4 : CLCC, site EFS, service de santé au travail, site de Chevilly-Larue) | `aucun`. `candidats[]` (≥ seuil de similarité 0,8, triés : similarité, puis hôpitaux/cliniques avant pharmacies/CMP/EFS homonymes, puis libellé le plus court) avec `coords`, `geo_precision`, `categorie.famille`, `similarite`. `commune_prouvee` = la commune (commune-mère pour Paris/Lyon/Marseille) quand TOUS les candidats y sont — c'est le fait à utiliser pour ancrer une analyse territoriale ; `null` avec `raison_commune: 'communes_divergentes'` sinon (« Clinique Pasteur » sans commune = 16 fiches dans 9 villes → demander la commune, ne JAMAIS prendre le premier). Le point à utiliser = premier candidat avec `coords` non null (2,4 % des fiches n'ont pas de point). `meilleure_similarite` sous 0,8 sur `aucun` : le nom FINESS diffère (abréviations administratives : « HOP EUROPEEN G POMPIDOU ») → relancer avec la partie distinctive (« Pompidou ») + la commune ; `meilleure_similarite: null` = AUCUNE ligne dans le territoire demandé → vérifier la commune/le département avant de conclure que le nom n'existe pas. `tronque: true` (autant de lignes que `limit`) → la commune n'est PAS prouvée (`raison_commune: 'tronque'`) : monter `limit` ou préciser le territoire. `lignes_rejetees` > 0 = lignes RPC illisibles écartées. Source : FINESS / ANS (flux quotidien, ingéré le 1ᵉʳ et le 15 ; établissements EN SERVICE uniquement). Chaque résultat porte `geo_precision: "adresse"` dès que `coords` est présent (point WGS84 ANS ou point BAN de l'adresse, jamais un centroïde ; un établissement sans `coords` n'a pas de point connu et est invisible des recherches par rayon) et `siret_ans` (SIRET déclaré par l'ANS, fait brut non vérifié SIRENE — pour le verdict : reconcilier_finess_sirene / verifier_site_actif). Note : champ `email` toujours `null` (non exposé par FINESS public).
Parameters6
nom
string
required
Nom de l'établissement tel qu'on le dit (3 caractères min). Accents, casse et ponctuation indifférents. Sans le nom de la commune (le mettre dans `nom_commune`).
nom_commune
string
optional
Nom officiel de la commune où se trouve l'établissement (résolu via geo.api.gouv.fr). Ex : "Villejuif", "Paris". Combinable avec `departement` comme hint de désambiguïsation. XOR avec `code_insee`.
code_insee
string
optional
Code INSEE de la commune (5 caractères). Paris/Lyon/Marseille : 75056 / 69123 / 13055 acceptés (ville entière) comme un arrondissement. XOR avec `nom_commune` et `departement`.
departement
string
optional
Code département (ex : '94', '2A', '971'). Seul = filtre département ; avec `nom_commune` = hint de désambiguïsation.
limit
number
optional
Candidats bruts lus avant seuil et tri (1-200, défaut 50). Monter si le nom est très partagé (« Saint Antoine » : 42 fiches).
include_freshness
boolean
optional
Si true, ajoute un champ `data_freshness` au payload (dans `query_metadata` si présent, sinon à la racine) listant, par source (FINESS, Ameli, RPPS, CDS, IRIS), la dernière ingestion réussie (`last_success_at`, `staleness_days`) ET la dernière fois que la donnée a réellement changé (`last_data_change_at`, `data_age_days` — un run court-circuité « fichier amont identique » compte comme succès mais ne rajeunit pas la donnée ; c'est `data_age_days` qui dit l'âge réel de ce qui est servi). Opt-in pour ne pas alourdir les payloads par défaut. Cache 5min côté serveur — coût négligeable.
Raw schema
{
"type": "object",
"properties": {
"nom": {
"type": "string",
"description": "Nom de l'établissement tel qu'on le dit (3 caractères min). Accents, casse et ponctuation indifférents. Sans le nom de la commune (le mettre dans `nom_commune`)."
},
"nom_commune": {
"type": "string",
"description": "Nom officiel de la commune où se trouve l'établissement (résolu via geo.api.gouv.fr). Ex : \"Villejuif\", \"Paris\". Combinable avec `departement` comme hint de désambiguïsation. XOR avec `code_insee`."
},
"code_insee": {
"type": "string",
"description": "Code INSEE de la commune (5 caractères). Paris/Lyon/Marseille : 75056 / 69123 / 13055 acceptés (ville entière) comme un arrondissement. XOR avec `nom_commune` et `departement`."
},
"departement": {
"type": "string",
"description": "Code département (ex : '94', '2A', '971'). Seul = filtre département ; avec `nom_commune` = hint de désambiguïsation."
},
"limit": {
"type": "number",
"description": "Candidats bruts lus avant seuil et tri (1-200, défaut 50). Monter si le nom est très partagé (« Saint Antoine » : 42 fiches).",
"minimum": 1,
"maximum": 200,
"default": 50
},
"include_freshness": {
"type": "boolean",
"default": false,
"description": "Si true, ajoute un champ `data_freshness` au payload (dans `query_metadata` si présent, sinon à la racine) listant, par source (FINESS, Ameli, RPPS, CDS, IRIS), la dernière ingestion réussie (`last_success_at`, `staleness_days`) ET la dernière fois que la donnée a réellement changé (`last_data_change_at`, `data_age_days` — un run court-circuité « fichier amont identique » compte comme succès mais ne rajeunit pas la donnée ; c'est `data_age_days` qui dit l'âge réel de ce qui est servi). Opt-in pour ne pas alourdir les payloads par défaut. Cache 5min côté serveur — coût négligeable."
}
},
"required": [
"nom"
]
}
centres_sante_in_radius
Recherche des Centres de Santé (CDS) dans un rayon géographique (PostGIS ST_DWithin). Source : Annuaire santé Ameli, Assurance Maladie (mention obligatoire L.1461-2 CSP — sync hebdomadaire CNAM). Différenciateur métier vs `etablissements_finess_in_radius` filtré famille=124 : expose **carte_vitale**, **APCV**, **spécialités exercées sur place** (Annexe A nomenclature CNAM, ~70 codes).
CDS = structures de soins ambulatoires non lucratives encadrées L.6323-1 CSP (associations, mutuelles, communes, hôpitaux). Volume ~3K en France. Filtres :
- `specialite_codes` : array Annexe A (ex: ['01'] médecine générale, ['53'] dentaire). Match any-of — retourne les CDS qui exercent AU MOINS UNE des spécialités demandées.
- `accepte_carte_vitale` : true / false / omis. Quasi-totalité accepte CV en pratique → filtre surtout utile en `false` pour audits.
- `type_etab_codes` : ['124'] CDS standard, ['125'] CDS dentaire (deprecated CNAM, en voie d'extinction).
Coords = centroïde commune (~3 km moyenne) — pour précision adresse, pivoter via `etab_finess` retourné avec `etablissement_by_finess`. PAS d'horaires/tarifs/secteur 1/2 (retirés du nouvel annuaire CNAM post-2025).
Alias acceptés : `radius`/`radius_meters` → `radius_km`, `latitude`/`longitude` → `lat`/`lon`.
Parameters8
lon
number
required
Longitude du centre (WGS84). Ex: 2.317 (Paris).
lat
number
required
Latitude du centre (WGS84). Ex: 48.872 (Paris).
radius_km
number
optional
Rayon en km (0.1-50, défaut 5).
specialite_codes
array
optional
Codes spécialité CNAM Annexe A (ex: ['01'] médecine générale, ['53'] chirurgien-dentiste). Match any-of. Vide = pas de filtre spécialité.
accepte_carte_vitale
boolean
optional
Filtre par acceptation carte Vitale. true = uniquement CDS qui acceptent CV, false = uniquement ceux qui ne l'acceptent pas. Omis = pas de filtre.
type_etab_codes
array
optional
Codes type établissement Annexe B : ['124'] CDS standard (défaut implicite), ['125'] CDS dentaire deprecated. Vide = tous types.
limit
number
optional
Nombre max de résultats (1-500, défaut 100).
include_freshness
boolean
optional
Si true, ajoute un champ `data_freshness` au payload (dans `query_metadata` si présent, sinon à la racine) listant, par source (FINESS, Ameli, RPPS, CDS, IRIS), la dernière ingestion réussie (`last_success_at`, `staleness_days`) ET la dernière fois que la donnée a réellement changé (`last_data_change_at`, `data_age_days` — un run court-circuité « fichier amont identique » compte comme succès mais ne rajeunit pas la donnée ; c'est `data_age_days` qui dit l'âge réel de ce qui est servi). Opt-in pour ne pas alourdir les payloads par défaut. Cache 5min côté serveur — coût négligeable.
Raw schema
{
"type": "object",
"properties": {
"lon": {
"type": "number",
"description": "Longitude du centre (WGS84). Ex: 2.317 (Paris)."
},
"lat": {
"type": "number",
"description": "Latitude du centre (WGS84). Ex: 48.872 (Paris)."
},
"radius_km": {
"type": "number",
"description": "Rayon en km (0.1-50, défaut 5).",
"minimum": 0.1,
"maximum": 50,
"default": 5
},
"specialite_codes": {
"type": "array",
"items": {
"type": "string"
},
"description": "Codes spécialité CNAM Annexe A (ex: ['01'] médecine générale, ['53'] chirurgien-dentiste). Match any-of. Vide = pas de filtre spécialité."
},
"accepte_carte_vitale": {
"type": "boolean",
"description": "Filtre par acceptation carte Vitale. true = uniquement CDS qui acceptent CV, false = uniquement ceux qui ne l'acceptent pas. Omis = pas de filtre."
},
"type_etab_codes": {
"type": "array",
"items": {
"type": "string"
},
"description": "Codes type établissement Annexe B : ['124'] CDS standard (défaut implicite), ['125'] CDS dentaire deprecated. Vide = tous types."
},
"limit": {
"type": "number",
"description": "Nombre max de résultats (1-500, défaut 100).",
"minimum": 1,
"maximum": 500,
"default": 100
},
"include_freshness": {
"type": "boolean",
"default": false,
"description": "Si true, ajoute un champ `data_freshness` au payload (dans `query_metadata` si présent, sinon à la racine) listant, par source (FINESS, Ameli, RPPS, CDS, IRIS), la dernière ingestion réussie (`last_success_at`, `staleness_days`) ET la dernière fois que la donnée a réellement changé (`last_data_change_at`, `data_age_days` — un run court-circuité « fichier amont identique » compte comme succès mais ne rajeunit pas la donnée ; c'est `data_age_days` qui dit l'âge réel de ce qui est servi). Opt-in pour ne pas alourdir les payloads par défaut. Cache 5min côté serveur — coût négligeable."
}
},
"required": [
"lon",
"lat"
]
}
centres_sante_by_finess
Récupère le détail d'un Centre de Santé (CDS) par son numéro FINESS. Différenciateur métier vs `etablissement_by_finess` : expose **carte_vitale**, **APCV**, et **spécialités exercées sur place** (Annexe A CNAM). Retourne un `LookupResult` discriminé par `found`.
`found: true` → payload CDS complet (raison sociale, accepte_carte_vitale/apcv, specialites.codes/libelles alignés, type_etab 124/125, adresse, coords centroïde commune, telephone). `found: false` → `{found: false, key, lookupStatus: 'not_found', message}` quand le numéro FINESS pointe vers une structure non-CDS (hôpital, EHPAD, labo) ou un CDS très récent (CNAM latence ~1 sem).
Source : Annuaire santé Ameli, Assurance Maladie (sync hebdomadaire CNAM, mention obligatoire L.1461-2 CSP). Pour les structures non-CDS, utiliser `etablissement_by_finess`.
Alias acceptés : `numFiness`/`finess`/`etab_finess` → `num_finess`.
Parameters2
num_finess
string
required
Numéro FINESS exact 9 chiffres. Ex: '750000123'.
include_freshness
boolean
optional
Si true, ajoute un champ `data_freshness` au payload (dans `query_metadata` si présent, sinon à la racine) listant, par source (FINESS, Ameli, RPPS, CDS, IRIS), la dernière ingestion réussie (`last_success_at`, `staleness_days`) ET la dernière fois que la donnée a réellement changé (`last_data_change_at`, `data_age_days` — un run court-circuité « fichier amont identique » compte comme succès mais ne rajeunit pas la donnée ; c'est `data_age_days` qui dit l'âge réel de ce qui est servi). Opt-in pour ne pas alourdir les payloads par défaut. Cache 5min côté serveur — coût négligeable.
Raw schema
{
"type": "object",
"properties": {
"num_finess": {
"type": "string",
"description": "Numéro FINESS exact 9 chiffres. Ex: '750000123'."
},
"include_freshness": {
"type": "boolean",
"default": false,
"description": "Si true, ajoute un champ `data_freshness` au payload (dans `query_metadata` si présent, sinon à la racine) listant, par source (FINESS, Ameli, RPPS, CDS, IRIS), la dernière ingestion réussie (`last_success_at`, `staleness_days`) ET la dernière fois que la donnée a réellement changé (`last_data_change_at`, `data_age_days` — un run court-circuité « fichier amont identique » compte comme succès mais ne rajeunit pas la donnée ; c'est `data_age_days` qui dit l'âge réel de ce qui est servi). Opt-in pour ne pas alourdir les payloads par défaut. Cache 5min côté serveur — coût négligeable."
}
},
"required": [
"num_finess"
]
}
professionnels_in_radius
Recherche de professionnels de santé libéraux conventionnés dans un rayon géographique. Précision géo HYBRIDE depuis le géocodage BAN (Chantier C) : ~77 % des PS sont géolocalisés à l'adresse précise (rue/bâtiment, `distance_km` exacte au m près), ~23 % restent au centroïde commune (~3 km, repli pour adresses non géocodables — DROM, Monaco, CEDEX, lieux-dits). Lire `geo_precision` PAR résultat — ne pas présumer une précision uniforme. Codes type_ps Ameli présents en base (3) : '1' médecins, '2' auxiliaires médicaux (fourre-tout : IDE, kinés, sages-femmes, podologues, orthophonistes, orthoptistes, IPA), '5' chirurgiens-dentistes. Pour cibler une profession précise (ex: IDE seuls, kinés seuls, podologues seuls), passer par `specialite_codes` plutôt que `type_ps_codes` qui ratisse plus large. Liste exhaustive des codes spécialité disponibles via le tool `lister_nomenclature(referentiel:'ameli_specialites')`. Multi-sites : par défaut un PS exerçant sur N adresses apparaît N fois — utiliser `dedupe_by_ps=true` pour regrouper par praticien et lister les sites en sous-objet. Distance retournée en km vol d'oiseau (haversine PostGIS) — pour distance routière, croiser avec un service externe (OSRM, ORS). Chaque PS géolocalisé porte `geo_precision` ∈ {`"adresse"`, `"centroide_commune"`} : `"adresse"` = coords BAN précises, `distance_km` exacte, classement individuel fiable ; `"centroide_commune"` = ~3 km, `distance_km` IDENTIQUE pour tous les PS d'une même commune (non discriminante intra-commune — filtre de zone uniquement, pas de classement/choix d'un PS individuel). **Param `precise_only`** (défaut false) : à true, exclut les PS au centroïde commune et ne renvoie que les ~77 % géocodés à l'adresse BAN (`distance_km` exacte) — recommandé pour les rayons courts (<3 km) et le classement intra-commune. PÉRIMÈTRE : libéraux conventionnés UNIQUEMENT. HORS PÉRIMÈTRE : médecins exclusivement hospitaliers/salariés, biologistes médicaux salariés en LBM, anatomopathologistes hospitaliers, médecins du travail, médecine légale. Pour effectifs tous statuts, voir Annuaire Santé ANS (RPPS, esante.gouv.fr) — non couvert par ce serveur. Source : Annuaire santé Ameli (Assurance Maladie), MAJ hebdomadaire. Réutilisation soumise à l'art. L.1461-2 CSP — citer la source et la date de sync.
Parameters9
lon
number
required
Longitude du centre (WGS84).
lat
number
required
Latitude du centre (WGS84).
radius_km
number
optional
Rayon en km (0.1-50, défaut 5).
specialite_codes
array
optional
Liste de codes spécialité Ameli (ex: ['01'] MG, ['03'] cardio). Si omis, toutes spécialités.
type_ps_codes
array
optional
Liste de codes type PS Ameli (3 valeurs présentes en base : '1' médecins, '2' auxiliaires médicaux fourre-tout — IDE/kinés/sages-femmes/podologues/orthophonistes/orthoptistes/IPA, '5' chirurgiens-dentistes). Pour cibler une seule profession, préférer `specialite_codes`. Si omis, tous types.
limit
number
optional
Nombre max de résultats (1-500, défaut 100). Appliqué AVANT déduplication.
dedupe_by_ps
boolean
optional
Regrouper les entrées par praticien (nom + prénom + code spécialité) et lister chaque adresse d'exercice dans `sites[]`. Défaut false (comportement V0.4 historique : un PS multi-sites = N entrées).
precise_only
boolean
optional
Si true, exclut les PS au centroïde commune et ne renvoie que ceux géocodés à l'adresse BAN, à `distance_km` exacte (cf. description du tool pour la sémantique complète). Défaut false.
include_freshness
boolean
optional
Si true, ajoute un champ `data_freshness` au payload (dans `query_metadata` si présent, sinon à la racine) listant, par source (FINESS, Ameli, RPPS, CDS, IRIS), la dernière ingestion réussie (`last_success_at`, `staleness_days`) ET la dernière fois que la donnée a réellement changé (`last_data_change_at`, `data_age_days` — un run court-circuité « fichier amont identique » compte comme succès mais ne rajeunit pas la donnée ; c'est `data_age_days` qui dit l'âge réel de ce qui est servi). Opt-in pour ne pas alourdir les payloads par défaut. Cache 5min côté serveur — coût négligeable.
Raw schema
{
"type": "object",
"properties": {
"lon": {
"type": "number",
"description": "Longitude du centre (WGS84)."
},
"lat": {
"type": "number",
"description": "Latitude du centre (WGS84)."
},
"radius_km": {
"type": "number",
"description": "Rayon en km (0.1-50, défaut 5).",
"minimum": 0.1,
"maximum": 50,
"default": 5
},
"specialite_codes": {
"type": "array",
"description": "Liste de codes spécialité Ameli (ex: ['01'] MG, ['03'] cardio). Si omis, toutes spécialités.",
"items": {
"type": "string"
}
},
"type_ps_codes": {
"type": "array",
"description": "Liste de codes type PS Ameli (3 valeurs présentes en base : '1' médecins, '2' auxiliaires médicaux fourre-tout — IDE/kinés/sages-femmes/podologues/orthophonistes/orthoptistes/IPA, '5' chirurgiens-dentistes). Pour cibler une seule profession, préférer `specialite_codes`. Si omis, tous types.",
"items": {
"type": "string"
}
},
"limit": {
"type": "number",
"description": "Nombre max de résultats (1-500, défaut 100). Appliqué AVANT déduplication.",
"minimum": 1,
"maximum": 500,
"default": 100
},
"dedupe_by_ps": {
"type": "boolean",
"description": "Regrouper les entrées par praticien (nom + prénom + code spécialité) et lister chaque adresse d'exercice dans `sites[]`. Défaut false (comportement V0.4 historique : un PS multi-sites = N entrées).",
"default": false
},
"precise_only": {
"type": "boolean",
"description": "Si true, exclut les PS au centroïde commune et ne renvoie que ceux géocodés à l'adresse BAN, à `distance_km` exacte (cf. description du tool pour la sémantique complète). Défaut false.",
"default": false
},
"include_freshness": {
"type": "boolean",
"default": false,
"description": "Si true, ajoute un champ `data_freshness` au payload (dans `query_metadata` si présent, sinon à la racine) listant, par source (FINESS, Ameli, RPPS, CDS, IRIS), la dernière ingestion réussie (`last_success_at`, `staleness_days`) ET la dernière fois que la donnée a réellement changé (`last_data_change_at`, `data_age_days` — un run court-circuité « fichier amont identique » compte comme succès mais ne rajeunit pas la donnée ; c'est `data_age_days` qui dit l'âge réel de ce qui est servi). Opt-in pour ne pas alourdir les payloads par défaut. Cache 5min côté serveur — coût négligeable."
}
},
"required": [
"lon",
"lat"
]
}
professionnels_par_specialite_dept
Liste des professionnels de santé libéraux conventionnés d'un département, avec filtres optionnels par spécialité ou type de PS. Pour énumération administrative — pas de rayon. Codes type_ps Ameli présents en base (3) : '1' médecins, '2' auxiliaires médicaux (fourre-tout : IDE, kinés, sages-femmes, podologues, orthophonistes, orthoptistes, IPA), '5' chirurgiens-dentistes. Pour cibler une profession précise (ex: IDE seuls), passer par `specialite_code` plutôt que `type_ps_code` qui ratisse plus large. Liste exhaustive des codes spécialité disponibles via le tool `lister_nomenclature(referentiel:'ameli_specialites')`. Pagination : utiliser `offset` pour récupérer les pages suivantes quand `truncated=true`. Multi-sites : utiliser `dedupe_by_ps=true` pour regrouper par praticien. PÉRIMÈTRE : libéraux conventionnés UNIQUEMENT. HORS PÉRIMÈTRE : médecins exclusivement hospitaliers/salariés, biologistes médicaux salariés en LBM, anatomopathologistes hospitaliers, médecins du travail, médecine légale. Pour effectifs tous statuts, voir Annuaire Santé ANS (RPPS, esante.gouv.fr) — non couvert par ce serveur. Source : Annuaire santé Ameli (Assurance Maladie), MAJ hebdomadaire. Réutilisation soumise à l'art. L.1461-2 CSP — citer la source et la date de sync.
Parameters7
departement
string
required
Code département INSEE : 2 caractères métropole/Corse ('01'-'95', '2A'/'2B'), 3 caractères DOM ('971'-'978').
specialite_code
string
optional
Code spécialité Ameli (ex: '01' MG, '24' IDE, '26' kiné, '03' cardio). Optionnel. Liste complète via `lister_nomenclature(referentiel:'ameli_specialites')`.
type_ps_code
string
optional
Code type PS Ameli ('1' médecins, '2' auxiliaires médicaux, '5' chirurgiens-dentistes). Optionnel — préférer `specialite_code` pour un ciblage précis. Liste complète via `lister_nomenclature(referentiel:'ameli_types_ps')`.
limit
number
optional
Nombre max de résultats (1-500, défaut 100). Appliqué AVANT déduplication.
offset
number
optional
Décalage de pagination (≥ 0, défaut 0). Combiner avec `limit` pour énumérer un département à fort effectif. Re-paginer tant que `truncated=true`.
dedupe_by_ps
boolean
optional
Regrouper les entrées par praticien (nom + prénom + code spécialité) et lister chaque adresse d'exercice dans `sites[]`. Défaut false.
include_freshness
boolean
optional
Si true, ajoute un champ `data_freshness` au payload (dans `query_metadata` si présent, sinon à la racine) listant, par source (FINESS, Ameli, RPPS, CDS, IRIS), la dernière ingestion réussie (`last_success_at`, `staleness_days`) ET la dernière fois que la donnée a réellement changé (`last_data_change_at`, `data_age_days` — un run court-circuité « fichier amont identique » compte comme succès mais ne rajeunit pas la donnée ; c'est `data_age_days` qui dit l'âge réel de ce qui est servi). Opt-in pour ne pas alourdir les payloads par défaut. Cache 5min côté serveur — coût négligeable.
Raw schema
{
"type": "object",
"properties": {
"departement": {
"type": "string",
"description": "Code département INSEE : 2 caractères métropole/Corse ('01'-'95', '2A'/'2B'), 3 caractères DOM ('971'-'978')."
},
"specialite_code": {
"type": "string",
"description": "Code spécialité Ameli (ex: '01' MG, '24' IDE, '26' kiné, '03' cardio). Optionnel. Liste complète via `lister_nomenclature(referentiel:'ameli_specialites')`."
},
"type_ps_code": {
"type": "string",
"description": "Code type PS Ameli ('1' médecins, '2' auxiliaires médicaux, '5' chirurgiens-dentistes). Optionnel — préférer `specialite_code` pour un ciblage précis. Liste complète via `lister_nomenclature(referentiel:'ameli_types_ps')`."
},
"limit": {
"type": "number",
"description": "Nombre max de résultats (1-500, défaut 100). Appliqué AVANT déduplication.",
"minimum": 1,
"maximum": 500,
"default": 100
},
"offset": {
"type": "number",
"description": "Décalage de pagination (≥ 0, défaut 0). Combiner avec `limit` pour énumérer un département à fort effectif. Re-paginer tant que `truncated=true`.",
"minimum": 0,
"default": 0
},
"dedupe_by_ps": {
"type": "boolean",
"description": "Regrouper les entrées par praticien (nom + prénom + code spécialité) et lister chaque adresse d'exercice dans `sites[]`. Défaut false.",
"default": false
},
"include_freshness": {
"type": "boolean",
"default": false,
"description": "Si true, ajoute un champ `data_freshness` au payload (dans `query_metadata` si présent, sinon à la racine) listant, par source (FINESS, Ameli, RPPS, CDS, IRIS), la dernière ingestion réussie (`last_success_at`, `staleness_days`) ET la dernière fois que la donnée a réellement changé (`last_data_change_at`, `data_age_days` — un run court-circuité « fichier amont identique » compte comme succès mais ne rajeunit pas la donnée ; c'est `data_age_days` qui dit l'âge réel de ce qui est servi). Opt-in pour ne pas alourdir les payloads par défaut. Cache 5min côté serveur — coût négligeable."
}
},
"required": [
"departement"
]
}
lister_nomenclature
Découverte des nomenclatures de codes du serveur (tool unique paramétré par `referentiel`) — à appeler avant de filtrer un autre tool plutôt que deviner les codes. ⚠️ Les 3 nomenclatures sont DISTINCTES : un même nombre y désigne des choses différentes (ex '10' = Médecin côté ANS, Neurochirurgien côté Ameli). Ne JAMAIS passer un code d'un référentiel à un paramètre d'un autre — le filtre renverrait vide sans erreur.
`referentiel` :
- `ameli_specialites` — codes `specialite_code` Ameli (libéraux conventionnés Assurance Maladie / CNAM) : libellé natif, `type_ps_code` de rattachement, count, `libelle_clarifie` (désambigüise les libellés partagés, ex "Médecin généraliste" = 01/22/23 ; "Psychiatre" = 33/75), `is_libelle_partage`. Pour filtrer `professionnels_in_radius` / `professionnels_par_specialite_dept` (param `specialite_code(s)`).
- `ameli_types_ps` — codes `type_ps` Ameli : `libelle_source`, `libelle_clarifie` (résout l'ambiguïté du code "2" fourre-tout), count, et `specialites_presentes` (spécialités regroupées). Payload léger via `include_specialites: false` (→ `nb_specialites`).
- `rpps_savoir_faire` — spécialités médicales `savoir_faire_code` RPPS / Annuaire Santé ANS (ex 'SM04' Cardiologie). Pour filtrer `densite_sante` (cible professionnels) / `professionnels_rpps_*`. Filtre par `profession_code` (défaut '10' Médecin ; string vide ou 'null' = tous savoir_faire).
Paginé : `limit` (défaut 50), réponse expose `total` et `truncated`. PÉRIMÈTRE : libéraux conventionnés UNIQUEMENT. HORS PÉRIMÈTRE : médecins exclusivement hospitaliers/salariés, biologistes médicaux salariés en LBM, anatomopathologistes hospitaliers, médecins du travail, médecine légale. Pour effectifs tous statuts, voir Annuaire Santé ANS (RPPS, esante.gouv.fr) — non couvert par ce serveur. Source : Annuaire santé Ameli (Assurance Maladie), MAJ hebdomadaire. Réutilisation soumise à l'art. L.1461-2 CSP — citer la source et la date de sync.
Nombre max de résultats (défaut 50, max 1000). Triés par fréquence décroissante. La réponse expose `total` (effectif réel) et `truncated` — re-appeler avec un `limit` supérieur pour la liste complète.
include_freshness
boolean
optional
Si true, ajoute un champ `data_freshness` au payload (dans `query_metadata` si présent, sinon à la racine) listant, par source (FINESS, Ameli, RPPS, CDS, IRIS), la dernière ingestion réussie (`last_success_at`, `staleness_days`) ET la dernière fois que la donnée a réellement changé (`last_data_change_at`, `data_age_days` — un run court-circuité « fichier amont identique » compte comme succès mais ne rajeunit pas la donnée ; c'est `data_age_days` qui dit l'âge réel de ce qui est servi). Opt-in pour ne pas alourdir les payloads par défaut. Cache 5min côté serveur — coût négligeable.
include_specialites
boolean
optional
Référentiel `ameli_types_ps` UNIQUEMENT : inclure le sous-tableau `specialites_presentes` détaillé (défaut true). `false` → remplacé par `nb_specialites` (compteur), ~6K tokens économisés.
profession_code
string
optional
Référentiel `rpps_savoir_faire` UNIQUEMENT : code profession ANS (TRE_R94). Défaut '10' (Médecin). String vide ou 'null' = tous savoir_faire, toutes professions.
Raw schema
{
"type": "object",
"properties": {
"referentiel": {
"type": "string",
"enum": [
"ameli_specialites",
"ameli_types_ps",
"rpps_savoir_faire"
],
"description": "Nomenclature à lister. `ameli_specialites` / `ameli_types_ps` = Ameli (libéraux conventionnés) ; `rpps_savoir_faire` = spécialités médicales ANS/RPPS (nomenclature DISTINCTE)."
},
"limit": {
"type": "number",
"description": "Nombre max de résultats (défaut 50, max 1000). Triés par fréquence décroissante. La réponse expose `total` (effectif réel) et `truncated` — re-appeler avec un `limit` supérieur pour la liste complète."
},
"include_freshness": {
"type": "boolean",
"default": false,
"description": "Si true, ajoute un champ `data_freshness` au payload (dans `query_metadata` si présent, sinon à la racine) listant, par source (FINESS, Ameli, RPPS, CDS, IRIS), la dernière ingestion réussie (`last_success_at`, `staleness_days`) ET la dernière fois que la donnée a réellement changé (`last_data_change_at`, `data_age_days` — un run court-circuité « fichier amont identique » compte comme succès mais ne rajeunit pas la donnée ; c'est `data_age_days` qui dit l'âge réel de ce qui est servi). Opt-in pour ne pas alourdir les payloads par défaut. Cache 5min côté serveur — coût négligeable."
},
"include_specialites": {
"type": "boolean",
"description": "Référentiel `ameli_types_ps` UNIQUEMENT : inclure le sous-tableau `specialites_presentes` détaillé (défaut true). `false` → remplacé par `nb_specialites` (compteur), ~6K tokens économisés.",
"default": true
},
"profession_code": {
"type": "string",
"description": "Référentiel `rpps_savoir_faire` UNIQUEMENT : code profession ANS (TRE_R94). Défaut '10' (Médecin). String vide ou 'null' = tous savoir_faire, toutes professions."
}
},
"required": [
"referentiel"
]
}
professionnels_rpps_in_radius
Trouve les PS dans un rayon via RPPS (Annuaire Santé ANS — **tous statuts** : libéraux + salariés + mixtes + remplaçants ; vs `professionnels_in_radius` Ameli = libéraux conventionnés seuls).
**Param critique `precise_only`** — Défaut `false` (mode hybride). À `true` : ne renvoie que les PS géolocalisés précisément (`distance_km` exacte au m près) — recommandé pour rayons courts (<3 km), classement intra-commune, "PS à <500 m d'une adresse".
Chaque résultat porte `geo_precision` ∈ :
- `"adresse"` — coords BAN rue/lieu-dit/bâtiment, `distance_km` exacte.
- `"etablissement_finess"` — coords du site FINESS (via `num_finess`), `distance_km` exacte au site.
- `"centroide_commune"` — centroïde commune (~3 km), `distance_km` IDENTIQUE pour tous les PS de la commune — ne PAS l'utiliser pour classer individuellement, seulement comme filtre de zone.
Couverture actuelle : ~68,5 % précis, ~31,5 % `centroide_commune` résiduel. Mode hybride = précis (granularité adresse) + centroïde (granularité commune) fusionnés et triés globalement par `distance_km`.
Filtres : `profession_codes` (ex: `["10"]` Médecin, `["60"]` Infirmier), `savoir_faire_codes` (spécialité fine DES/DESC), `mode_exercice_codes`. Codes mode_exercice ANS : L libéral, S salarié, M mixte, R remplaçant, B bénévole, A autre. Catégorie par défaut : Civil (C, ~97 % — libéraux, salariés privés, hospitaliers contractuels). Opt-in : `include_agents_publics: true` ajoute Agents publics (M, ~0,3 % — PH titulaires, ARS, CNAM, Éducation nationale, PMI, militaires SSA) ; `include_etudiants: true` ajoute Étudiants (E, ~2,5 % — internes, externes, élèves IDE/SF). Réf : https://mos.esante.gouv.fr/NOS/TRE_R09-CategorieProfessionnelle/. ATTENTION nomenclatures : les codes ANS (`profession_code`, `savoir_faire_code`) sont une nomenclature DISTINCTE des codes Ameli (`specialite_code`, `type_ps_code`) — un même nombre désigne des choses différentes (ex: '10' = Médecin côté ANS, Neurochirurgien côté Ameli). Ne JAMAIS passer un code Ameli à un paramètre ANS : le filtre renverrait vide sans erreur. Découvrir les codes ANS via `lister_nomenclature(referentiel:'rpps_savoir_faire')`. Source : Annuaire Santé, Agence du Numérique en Santé (ANS) — Licence Ouverte v2.0
Parameters10
center
object
required
Centre du cercle de recherche (coordonnées WGS84).
radius_km
number
required
Rayon en km (0.1-50).
profession_codes
array
optional
Codes profession ANS (ex: ['10'] Médecin, ['60'] Infirmier). Si omis, toutes professions.
savoir_faire_codes
array
optional
Codes savoir-faire ANS (spécialités fines DES/DESC). Si omis, tous savoir-faire.
mode_exercice_codes
array
optional
Codes mode d'exercice ANS (libéral / salarié / mixte). Si omis, tous modes.
include_etudiants
boolean
optional
include_agents_publics
boolean
optional
limit
number
optional
Nombre max de résultats retournés (défaut serveur 100).
precise_only
boolean
optional
Si true, exclut les PS au centroïde commune et ne renvoie que ceux à `distance_km` exacte (cf. description du tool pour la sémantique complète et le seuil d'usage recommandé). Défaut false.
include_freshness
boolean
optional
Si true, ajoute un champ `data_freshness` au payload (dans `query_metadata` si présent, sinon à la racine) listant, par source (FINESS, Ameli, RPPS, CDS, IRIS), la dernière ingestion réussie (`last_success_at`, `staleness_days`) ET la dernière fois que la donnée a réellement changé (`last_data_change_at`, `data_age_days` — un run court-circuité « fichier amont identique » compte comme succès mais ne rajeunit pas la donnée ; c'est `data_age_days` qui dit l'âge réel de ce qui est servi). Opt-in pour ne pas alourdir les payloads par défaut. Cache 5min côté serveur — coût négligeable.
Raw schema
{
"type": "object",
"properties": {
"center": {
"type": "object",
"description": "Centre du cercle de recherche (coordonnées WGS84).",
"properties": {
"lat": {
"type": "number",
"description": "Latitude (WGS84)."
},
"lon": {
"type": "number",
"description": "Longitude (WGS84)."
}
},
"required": [
"lat",
"lon"
]
},
"radius_km": {
"type": "number",
"description": "Rayon en km (0.1-50).",
"minimum": 0.1,
"maximum": 50
},
"profession_codes": {
"type": "array",
"description": "Codes profession ANS (ex: ['10'] Médecin, ['60'] Infirmier). Si omis, toutes professions.",
"items": {
"type": "string"
}
},
"savoir_faire_codes": {
"type": "array",
"description": "Codes savoir-faire ANS (spécialités fines DES/DESC). Si omis, tous savoir-faire.",
"items": {
"type": "string"
}
},
"mode_exercice_codes": {
"type": "array",
"description": "Codes mode d'exercice ANS (libéral / salarié / mixte). Si omis, tous modes.",
"items": {
"type": "string"
}
},
"include_etudiants": {
"type": "boolean"
},
"include_agents_publics": {
"type": "boolean"
},
"limit": {
"type": "number",
"description": "Nombre max de résultats retournés (défaut serveur 100)."
},
"precise_only": {
"type": "boolean",
"description": "Si true, exclut les PS au centroïde commune et ne renvoie que ceux à `distance_km` exacte (cf. description du tool pour la sémantique complète et le seuil d'usage recommandé). Défaut false.",
"default": false
},
"include_freshness": {
"type": "boolean",
"default": false,
"description": "Si true, ajoute un champ `data_freshness` au payload (dans `query_metadata` si présent, sinon à la racine) listant, par source (FINESS, Ameli, RPPS, CDS, IRIS), la dernière ingestion réussie (`last_success_at`, `staleness_days`) ET la dernière fois que la donnée a réellement changé (`last_data_change_at`, `data_age_days` — un run court-circuité « fichier amont identique » compte comme succès mais ne rajeunit pas la donnée ; c'est `data_age_days` qui dit l'âge réel de ce qui est servi). Opt-in pour ne pas alourdir les payloads par défaut. Cache 5min côté serveur — coût négligeable."
}
},
"required": [
"center",
"radius_km"
]
}
professionnels_rpps_par_dept
Liste tous les PS d'un département via RPPS (libéraux + salariés). Pour les libéraux conventionnés uniquement, préférer `professionnels_par_specialite_dept` (Ameli). Re-paginer via `offset` tant que `truncated=true`.
Chaque résultat géolocalisé porte `geo_precision` ∈ {`"adresse"`, `"etablissement_finess"`, `"centroide_commune"`} — lire ce champ pour évaluer la fiabilité des `coords` (précise BAN/FINESS au m près vs centroïde commune ~3 km, non discriminant intra-commune).
Filtres optionnels : `profession_code`, `savoir_faire_code`, `mode_exercice_code`. Catégorie par défaut : Civil (C, ~97 % — libéraux, salariés privés, hospitaliers contractuels). Opt-in : `include_agents_publics: true` ajoute Agents publics (M, ~0,3 % — PH titulaires, ARS, CNAM, Éducation nationale, PMI, militaires SSA) ; `include_etudiants: true` ajoute Étudiants (E, ~2,5 % — internes, externes, élèves IDE/SF). Réf : https://mos.esante.gouv.fr/NOS/TRE_R09-CategorieProfessionnelle/. ATTENTION nomenclatures : les codes ANS (`profession_code`, `savoir_faire_code`) sont une nomenclature DISTINCTE des codes Ameli (`specialite_code`, `type_ps_code`) — un même nombre désigne des choses différentes (ex: '10' = Médecin côté ANS, Neurochirurgien côté Ameli). Ne JAMAIS passer un code Ameli à un paramètre ANS : le filtre renverrait vide sans erreur. Découvrir les codes ANS via `lister_nomenclature(referentiel:'rpps_savoir_faire')`. Source : Annuaire Santé, Agence du Numérique en Santé (ANS) — Licence Ouverte v2.0
Parameters9
departement
string
required
Code département INSEE (ex: '75', '2A', '2B', '971'). Métropole 2 caractères (Corse '2A'/'2B', pas '20'), DOM/TOM 3 caractères.
profession_code
string
optional
Code profession ANS (ex: '10' Médecin, '60' Infirmier). Optionnel.
savoir_faire_code
string
optional
Code savoir-faire ANS (spécialité fine DES/DESC). Optionnel.
mode_exercice_code
string
optional
Code mode d'exercice ANS (libéral / salarié / mixte). Optionnel.
include_etudiants
boolean
optional
include_agents_publics
boolean
optional
limit
number
optional
Nombre max de résultats par page (défaut serveur 100).
offset
number
optional
Offset pour pagination (défaut 0). Re-paginer tant que `truncated=true`.
include_freshness
boolean
optional
Si true, ajoute un champ `data_freshness` au payload (dans `query_metadata` si présent, sinon à la racine) listant, par source (FINESS, Ameli, RPPS, CDS, IRIS), la dernière ingestion réussie (`last_success_at`, `staleness_days`) ET la dernière fois que la donnée a réellement changé (`last_data_change_at`, `data_age_days` — un run court-circuité « fichier amont identique » compte comme succès mais ne rajeunit pas la donnée ; c'est `data_age_days` qui dit l'âge réel de ce qui est servi). Opt-in pour ne pas alourdir les payloads par défaut. Cache 5min côté serveur — coût négligeable.
Raw schema
{
"type": "object",
"properties": {
"departement": {
"type": "string",
"description": "Code département INSEE (ex: '75', '2A', '2B', '971'). Métropole 2 caractères (Corse '2A'/'2B', pas '20'), DOM/TOM 3 caractères."
},
"profession_code": {
"type": "string",
"description": "Code profession ANS (ex: '10' Médecin, '60' Infirmier). Optionnel."
},
"savoir_faire_code": {
"type": "string",
"description": "Code savoir-faire ANS (spécialité fine DES/DESC). Optionnel."
},
"mode_exercice_code": {
"type": "string",
"description": "Code mode d'exercice ANS (libéral / salarié / mixte). Optionnel."
},
"include_etudiants": {
"type": "boolean"
},
"include_agents_publics": {
"type": "boolean"
},
"limit": {
"type": "number",
"description": "Nombre max de résultats par page (défaut serveur 100)."
},
"offset": {
"type": "number",
"description": "Offset pour pagination (défaut 0). Re-paginer tant que `truncated=true`."
},
"include_freshness": {
"type": "boolean",
"default": false,
"description": "Si true, ajoute un champ `data_freshness` au payload (dans `query_metadata` si présent, sinon à la racine) listant, par source (FINESS, Ameli, RPPS, CDS, IRIS), la dernière ingestion réussie (`last_success_at`, `staleness_days`) ET la dernière fois que la donnée a réellement changé (`last_data_change_at`, `data_age_days` — un run court-circuité « fichier amont identique » compte comme succès mais ne rajeunit pas la donnée ; c'est `data_age_days` qui dit l'âge réel de ce qui est servi). Opt-in pour ne pas alourdir les payloads par défaut. Cache 5min côté serveur — coût négligeable."
}
},
"required": [
"departement"
]
}
rpps_dans_etablissement
Liste les PS rattachés à un établissement FINESS (`num_finess` 9 chiffres). Pivot RPPS↔FINESS — répond à "qui travaille dans ce labo / hôpital / clinique ?". Le `mode_exercice` distingue les libéraux exerçant sur place (vacations) des salariés. Couverture : RPPS expose ce lien quand le PS l'a déclaré ; salariés CH/CHU/cliniques bien couverts.
Sortie compacte : `coords` et `distance_km` sont `null` (le tool est par établissement, pas spatial — pour la géoloc, pivoter via `etablissement_by_finess` sur le `num_finess`). Catégorie par défaut : Civil (C, ~97 % — libéraux, salariés privés, hospitaliers contractuels). Opt-in : `include_agents_publics: true` ajoute Agents publics (M, ~0,3 % — PH titulaires, ARS, CNAM, Éducation nationale, PMI, militaires SSA) ; `include_etudiants: true` ajoute Étudiants (E, ~2,5 % — internes, externes, élèves IDE/SF). Réf : https://mos.esante.gouv.fr/NOS/TRE_R09-CategorieProfessionnelle/. Source : Annuaire Santé, Agence du Numérique en Santé (ANS) — Licence Ouverte v2.0
Parameters5
num_finess
string
required
include_etudiants
boolean
optional
include_agents_publics
boolean
optional
limit
number
optional
include_freshness
boolean
optional
Si true, ajoute un champ `data_freshness` au payload (dans `query_metadata` si présent, sinon à la racine) listant, par source (FINESS, Ameli, RPPS, CDS, IRIS), la dernière ingestion réussie (`last_success_at`, `staleness_days`) ET la dernière fois que la donnée a réellement changé (`last_data_change_at`, `data_age_days` — un run court-circuité « fichier amont identique » compte comme succès mais ne rajeunit pas la donnée ; c'est `data_age_days` qui dit l'âge réel de ce qui est servi). Opt-in pour ne pas alourdir les payloads par défaut. Cache 5min côté serveur — coût négligeable.
Raw schema
{
"type": "object",
"properties": {
"num_finess": {
"type": "string",
"pattern": "^\\d{9}$"
},
"include_etudiants": {
"type": "boolean"
},
"include_agents_publics": {
"type": "boolean"
},
"limit": {
"type": "number"
},
"include_freshness": {
"type": "boolean",
"default": false,
"description": "Si true, ajoute un champ `data_freshness` au payload (dans `query_metadata` si présent, sinon à la racine) listant, par source (FINESS, Ameli, RPPS, CDS, IRIS), la dernière ingestion réussie (`last_success_at`, `staleness_days`) ET la dernière fois que la donnée a réellement changé (`last_data_change_at`, `data_age_days` — un run court-circuité « fichier amont identique » compte comme succès mais ne rajeunit pas la donnée ; c'est `data_age_days` qui dit l'âge réel de ce qui est servi). Opt-in pour ne pas alourdir les payloads par défaut. Cache 5min côté serveur — coût négligeable."
}
},
"required": [
"num_finess"
]
}
densite_sante
Densité de santé pour 100 000 habitants — `cible: professionnels` (RPPS) OU `cible: etablissements` (FINESS). Niveau **département** (`code_dept`) OU **commune** (`code_insee` / `nom_commune`). Exactement un scope des trois requis. Croise le count (RPPS ou FINESS) et INSEE Melodi (population municipale PMUN, recensement 2023).
**cible='professionnels'** (RPPS) — méthodo DREES par défaut : médecins (`profession_code='10'`) en activité régulière (mode_exercice L, S, M), hors étudiants. Filtres : `profession_code` (60 infirmier, 21 pharmacien, 50 sage-femme…), `savoir_faire_code` (ex 'SM04' Cardiologie — 'SM02' = Anesthésie-réanimation ; voir `lister_nomenclature` referentiel rpps_savoir_faire), `mode_exercice_codes` (['L'] = libéraux seuls).
**cible='etablissements'** (FINESS) — `famille` OBLIGATOIRE : labo, pharmacie, ehpad, mco, ssr, psychiatrie, dialyse, imagerie, had, msp_cpts, handicap_enfants, handicap_adultes, addictologie, pmi, prevention_sante, etc. Sans famille le ratio mélangerait labos/hôpitaux/EHPAD → non-sens.
**Sémantique conditionnelle de `code_dept`** : seul = scope de calcul (dept entier) ; combiné avec `nom_commune` = hint de résolution UNIQUEMENT (filtre les homonymes), le calcul reste sur la commune résolue.
Paris/Marseille/Lyon : densité par `code_insee` INDISPONIBLE (RPPS/FINESS rattachés aux arrondissements, INSEE n'expose la population qu'à la commune entière) → RangeError ; utiliser `code_dept` (75, 13, 69).
`compare_national: true` ajoute la densité France entière (DOM inclus) + écart en % (positif = sur-doté, négatif = sous-doté).
Alias : `dept`/`departement` → `code_dept`, `codeInsee`/`insee` → `code_insee`. Ne renvoie AUCUNE interprétation métier (pas de seuil "désert médical" auto). Catégorie par défaut : Civil (C, ~97 % — libéraux, salariés privés, hospitaliers contractuels). Opt-in : `include_agents_publics: true` ajoute Agents publics (M, ~0,3 % — PH titulaires, ARS, CNAM, Éducation nationale, PMI, militaires SSA) ; `include_etudiants: true` ajoute Étudiants (E, ~2,5 % — internes, externes, élèves IDE/SF). Réf : https://mos.esante.gouv.fr/NOS/TRE_R09-CategorieProfessionnelle/. ATTENTION nomenclatures : les codes ANS (`profession_code`, `savoir_faire_code`) sont une nomenclature DISTINCTE des codes Ameli (`specialite_code`, `type_ps_code`) — un même nombre désigne des choses différentes (ex: '10' = Médecin côté ANS, Neurochirurgien côté Ameli). Ne JAMAIS passer un code Ameli à un paramètre ANS : le filtre renverrait vide sans erreur. Découvrir les codes ANS via `lister_nomenclature(referentiel:'rpps_savoir_faire')`. Source : Annuaire Santé, Agence du Numérique en Santé (ANS) — Licence Ouverte v2.0
Code INSEE du département 2-3 caractères. Ex: "75" Paris, "59" Nord, "2A" Corse-du-Sud, "971" Guadeloupe. Sémantique conditionnelle : seul = scope dept entier ; combiné avec `nom_commune` = hint resolver pour désambiguer les homonymes. XOR avec `code_insee`.
code_insee
string
optional
Code INSEE de la commune 5 caractères. Ex: "59009" Villeneuve-d'Ascq, "33063" Bordeaux, "2A004" Ajaccio. Paris/Lyon/Marseille NON supporté au niveau commune (densité indisponible — voir description) : utiliser code_dept. XOR avec `code_dept` et `nom_commune`.
nom_commune
string
optional
Nom officiel de commune (alternative à `code_insee`). Ex: "Lille", "Villeneuve-d'Ascq". Le serveur résout en interne via geo.api.gouv.fr. Combinable avec `code_dept` comme hint de désambiguïsation pour homonymes (ex "Saint-Martin" + dept "65"). XOR avec `code_insee`.
cible='professionnels' UNIQUEMENT : code spécialité (savoir_faire). Pertinent surtout pour profession_code=10 (médecin). Ex : 'SM04' Cardiologie, 'SM15' Dermatologie et vénéréologie, 'SM02' Anesthésie-réanimation, 'SM26' Médecine générale. Voir lister_nomenclature(referentiel:'rpps_savoir_faire') pour la liste exhaustive.
mode_exercice_codes
array
optional
cible='professionnels' UNIQUEMENT : codes mode_exercice ANS à inclure. Default ['L','S','M'] (libéral + salarié + mixte = activité régulière DREES). Passer ['L'] pour libéraux seuls. Codes mode_exercice ANS : L libéral, S salarié, M mixte, R remplaçant, B bénévole, A autre.
compare_national
boolean
optional
Ajoute le calcul France entière + écart relatif en % (recommandé pour qualifier 'sous-doté'/'sur-doté').
include_etudiants
boolean
optional
include_agents_publics
boolean
optional
Raw schema
{
"type": "object",
"properties": {
"cible": {
"type": "string",
"enum": [
"professionnels",
"etablissements"
],
"description": "`professionnels` = densité de PS (RPPS, filtres profession_code/savoir_faire_code/mode_exercice_codes) ; `etablissements` = densité d'établissements (FINESS, `famille` obligatoire)."
},
"code_dept": {
"type": "string",
"description": "Code INSEE du département 2-3 caractères. Ex: \"75\" Paris, \"59\" Nord, \"2A\" Corse-du-Sud, \"971\" Guadeloupe. Sémantique conditionnelle : seul = scope dept entier ; combiné avec `nom_commune` = hint resolver pour désambiguer les homonymes. XOR avec `code_insee`."
},
"code_insee": {
"type": "string",
"description": "Code INSEE de la commune 5 caractères. Ex: \"59009\" Villeneuve-d'Ascq, \"33063\" Bordeaux, \"2A004\" Ajaccio. Paris/Lyon/Marseille NON supporté au niveau commune (densité indisponible — voir description) : utiliser code_dept. XOR avec `code_dept` et `nom_commune`."
},
"nom_commune": {
"type": "string",
"description": "Nom officiel de commune (alternative à `code_insee`). Ex: \"Lille\", \"Villeneuve-d'Ascq\". Le serveur résout en interne via geo.api.gouv.fr. Combinable avec `code_dept` comme hint de désambiguïsation pour homonymes (ex \"Saint-Martin\" + dept \"65\"). XOR avec `code_insee`."
},
"famille": {
"type": "string",
"description": "cible='etablissements' UNIQUEMENT (obligatoire) : famille FINESS à compter (labo, pharmacie, ehpad, mco, ssr, psychiatrie, dialyse, imagerie, had, msp_cpts, handicap_enfants, handicap_adultes, addictologie, pmi, prevention_sante, etc.)."
},
"profession_code": {
"type": "string",
"description": "cible='professionnels' UNIQUEMENT : code profession ANS (TRE_R94). Default '10' (Médecin). Ex : '60' Infirmier, '21' Pharmacien, '50' Sage-femme, '40' Chirurgien-dentiste, '70' Masseur-kinésithérapeute."
},
"savoir_faire_code": {
"type": "string",
"description": "cible='professionnels' UNIQUEMENT : code spécialité (savoir_faire). Pertinent surtout pour profession_code=10 (médecin). Ex : 'SM04' Cardiologie, 'SM15' Dermatologie et vénéréologie, 'SM02' Anesthésie-réanimation, 'SM26' Médecine générale. Voir lister_nomenclature(referentiel:'rpps_savoir_faire') pour la liste exhaustive."
},
"mode_exercice_codes": {
"type": "array",
"items": {
"type": "string"
},
"description": "cible='professionnels' UNIQUEMENT : codes mode_exercice ANS à inclure. Default ['L','S','M'] (libéral + salarié + mixte = activité régulière DREES). Passer ['L'] pour libéraux seuls. Codes mode_exercice ANS : L libéral, S salarié, M mixte, R remplaçant, B bénévole, A autre."
},
"compare_national": {
"type": "boolean",
"description": "Ajoute le calcul France entière + écart relatif en % (recommandé pour qualifier 'sous-doté'/'sur-doté').",
"default": false
},
"include_etudiants": {
"type": "boolean"
},
"include_agents_publics": {
"type": "boolean"
}
},
"required": [
"cible"
]
}
panorama_sante_territoire
Panorama santé d'une commune française en 1 appel (V0.9). Agrège en parallèle : population (INSEE Melodi), densités médecins + infirmiers + pharmaciens avec comparaison nationale (méthodo DREES), nombre d'établissements FINESS par famille (default ["labo","pharmacie","ehpad","mco","msp_cpts"]), et un bloc DEMANDE (V0.22.0 — profil démographique de la commune agrégé depuis ses IRIS : âge, CSP, familles, revenu pondéré, à CROISER avec l'OFFRE ci-dessus pour l'aide à l'implantation ; `demande: null` si commune hors couverture IRIS (DOM non ingéré) — pour le détail au quartier ou un bassin par rayon, utiliser `profil_iris`).
Remplace 7-10 appels MCP individuels par 1 seul. Ne renvoie AUCUNE interprétation métier (pas de qualification automatique 'désert médical') — le caller LLM applique sa grille.
V0.19.0 : accepte `nom_commune` (string) comme alternative à `code_insee`. `departement` (V0.19) = hint resolver UNIQUEMENT (panorama ne calcule pas par dept ; un `departement` seul lève une erreur explicite).
**Granularité mixte** : les densités professionnels et la population sont calculées au niveau **commune** ; le décompte FINESS est agrégé au niveau **département** dérivé du code INSEE (limitation V0.9 — pas de RPC count_finess_by_commune encore). Le champ `niveauEtablissements` du résultat indique `"departement"` (succès), `"indisponible"` (dept indérivable, ex code DOM tronqué) — utiliser cette information pour ne pas confondre ratios commune et dept.
Paris/Marseille/Lyon NON supporté : le panorama par commune dépend de la densité par commune, indisponible pour ces villes (INSEE n'expose la population qu'à la commune entière, les praticiens RPPS aux arrondissements). Un code PLM (commune-mère 75056 ou arrondissement) lève une RangeError. Pour ces villes, interroger les tools individuels au niveau `code_dept` (75/69/13).
Alias acceptés : `codeInsee`/`insee`/`code` → `code_insee`.
Sources : RPPS / Annuaire Santé ANS (mensuel), FINESS DREES (bimensuel), INSEE Melodi (PMUN 2023).
Parameters4
code_insee
string
optional
Code INSEE de la commune 5 caractères. Ex: "59009" Villeneuve-d'Ascq, "33063" Bordeaux, "2A004" Ajaccio. Paris/Lyon/Marseille NON supporté (voir description). XOR avec `nom_commune`.
nom_commune
string
optional
Nom officiel de commune (alternative à `code_insee`, V0.19). Ex: "Lille", "Saint-Étienne". Combinable avec `departement` comme hint de désambiguïsation pour homonymes (ex "Saint-Martin" + dept "65"). Abréviations type "St-Martin" non reconnues.
departement
string
optional
Code département INSEE (V0.19, hint resolver UNIQUEMENT). À utiliser EN COMBINAISON avec `nom_commune` pour désambiguer les homonymes. Seul, lève une erreur (panorama = calcul commune uniquement, utiliser `code_insee` ou `nom_commune`).
finess_familles
array
optional
Familles FINESS à inclure dans le décompte établissements. Default ["labo","pharmacie","ehpad","mco","msp_cpts"]. Passer [] pour omettre le décompte FINESS (renvoie uniquement population + densités PS).
Raw schema
{
"type": "object",
"properties": {
"code_insee": {
"type": "string",
"description": "Code INSEE de la commune 5 caractères. Ex: \"59009\" Villeneuve-d'Ascq, \"33063\" Bordeaux, \"2A004\" Ajaccio. Paris/Lyon/Marseille NON supporté (voir description). XOR avec `nom_commune`."
},
"nom_commune": {
"type": "string",
"description": "Nom officiel de commune (alternative à `code_insee`, V0.19). Ex: \"Lille\", \"Saint-Étienne\". Combinable avec `departement` comme hint de désambiguïsation pour homonymes (ex \"Saint-Martin\" + dept \"65\"). Abréviations type \"St-Martin\" non reconnues."
},
"departement": {
"type": "string",
"description": "Code département INSEE (V0.19, hint resolver UNIQUEMENT). À utiliser EN COMBINAISON avec `nom_commune` pour désambiguer les homonymes. Seul, lève une erreur (panorama = calcul commune uniquement, utiliser `code_insee` ou `nom_commune`)."
},
"finess_familles": {
"type": "array",
"items": {
"type": "string"
},
"description": "Familles FINESS à inclure dans le décompte établissements. Default [\"labo\",\"pharmacie\",\"ehpad\",\"mco\",\"msp_cpts\"]. Passer [] pour omettre le décompte FINESS (renvoie uniquement population + densités PS)."
}
}
}
inspect_site
Vue 360 d'un établissement de santé en 1 appel (V0.10). Pendant naturel de `panorama_sante_territoire` côté **site** : agrège en parallèle (a) identification FINESS DREES (raison sociale, adresse, téléphone), (b) statut administratif SIRENE via le resolver SIRET (verdicts site + groupe, best_match, SIREN explorés, dinum_errors, explication LLM-friendly), (c) professionnels rattachés via num_finess (sample borné + flag `truncated` si le site a plus de PS — PAS un count total), (d) historique INSEE (timeline périodes administratives par SIRET candidat).
Remplace 3 appels MCP individuels (`verifier_site_actif` + `rpps_dans_etablissement` + `historique_etablissement`) par 1 seul. Utile pour : prospection (qualifier un site avant outreach), audit territorial (cross-check rapide d'un FINESS suspect), enrichissement CRM en batch.
**Format de retour** : objet `LookupResult`. Quand `found: true`, payload avec 4 sections (finess, statut_site, professionnels, historique). La section `historique` peut être `available: false` quand le FINESS existe mais qu'aucun SIRET candidat n'a été identifié (RPPS vide + DINUM 0 match) — dans ce cas le `message` reprend celui de `historique_etablissement`. Quand `num_finess` est absent de FINESS DREES, retourne `{found: false, lookupStatus: 'not_found', message}`.
Coût : 3 sous-appels parallèles. Cache PostgreSQL absorbe la duplication FINESS-RPC ; le pivot RPPS→DINUM est exécuté en double (verifier + historique partagent la cascade), surcoût p95 ≤ 600 ms — acceptable pour un agrégateur. Pour les besoins ciblés (juste le verdict, juste l'historique), préférer les tools individuels. Payload lourd (~7K tokens) : passer `historique_detail: false` pour un retour allégé (résumé au lieu des timelines SIRENE complètes) en usage batch.
Alias acceptés : `numFiness`/`finess`/`id` → `num_finess`.
Parameters3
num_finess
string
required
Numéro FINESS exact 9 chiffres. Ex: '590048997'.
rpps_limit
integer
optional
Nombre max de PS dans `professionnels.sample`. `professionnels.count` = taille du sample (≤ cette borne), pas le total du site ; `truncated: true` signale qu'il y a davantage de PS. Borné [1, 50]. Défaut 10.
historique_detail
boolean
optional
Inclure les timelines SIRENE détaillées dans `historique.siret_timelines` (défaut true). `false` = payload allégé (~7K tokens en moins) : `historique` ne porte qu'un `resume` (counts) + un pointeur vers `historique_etablissement`.
Raw schema
{
"type": "object",
"properties": {
"num_finess": {
"type": "string",
"description": "Numéro FINESS exact 9 chiffres. Ex: '590048997'."
},
"rpps_limit": {
"type": "integer",
"description": "Nombre max de PS dans `professionnels.sample`. `professionnels.count` = taille du sample (≤ cette borne), pas le total du site ; `truncated: true` signale qu'il y a davantage de PS. Borné [1, 50]. Défaut 10."
},
"historique_detail": {
"type": "boolean",
"description": "Inclure les timelines SIRENE détaillées dans `historique.siret_timelines` (défaut true). `false` = payload allégé (~7K tokens en moins) : `historique` ne porte qu'un `resume` (counts) + un pointeur vers `historique_etablissement`.",
"default": true
}
},
"required": [
"num_finess"
]
}
rpps_search_by_name
Trouve un PS par identité (matching trigram tolérant aux accents/typos). Usage : "Dr Martin à Paris" → `nom: "Martin", departement: "75"`. Nom obligatoire ; `prenom` et `departement` affinent.
Tri par `match_score` ∈ [0..1] décroissant (score trigram pg_trgm). Un score <0.5 = homonymie partielle à confirmer côté caller. Sans `departement`, des homonymes exacts ("Pierre Martin") ont TOUS le même score ~1.0 et ne sont pas départagés — toujours filtrer par dept ou prénom sur un nom commun.
`truncated: true` = d'autres résultats existent (restreindre, ne pas parcourir).
Chaque résultat géolocalisé porte `geo_precision` ∈ {`"adresse"`, `"etablissement_finess"`, `"centroide_commune"`} — lire ce champ pour évaluer la fiabilité des `coords` (précise BAN/FINESS au m près vs centroïde commune ~3 km, non discriminant intra-commune).
Catégorie par défaut : Civil (C, ~97 % — libéraux, salariés privés, hospitaliers contractuels). Opt-in : `include_agents_publics: true` ajoute Agents publics (M, ~0,3 % — PH titulaires, ARS, CNAM, Éducation nationale, PMI, militaires SSA) ; `include_etudiants: true` ajoute Étudiants (E, ~2,5 % — internes, externes, élèves IDE/SF). Réf : https://mos.esante.gouv.fr/NOS/TRE_R09-CategorieProfessionnelle/.
Source : Annuaire Santé, Agence du Numérique en Santé (ANS) — Licence Ouverte v2.0
Parameters7
nom
string
required
Nom de famille (non vide).
prenom
string
optional
Prénom du PS.
departement
string
optional
Code département INSEE (ex: '75', '2A', '2B', '971'). Métropole 2 caractères (Corse '2A'/'2B', pas '20'), DOM/COM 3 caractères.
include_etudiants
boolean
optional
include_agents_publics
boolean
optional
limit
number
optional
Nombre max de résultats (1-500, défaut 100).
include_freshness
boolean
optional
Si true, ajoute un champ `data_freshness` au payload (dans `query_metadata` si présent, sinon à la racine) listant, par source (FINESS, Ameli, RPPS, CDS, IRIS), la dernière ingestion réussie (`last_success_at`, `staleness_days`) ET la dernière fois que la donnée a réellement changé (`last_data_change_at`, `data_age_days` — un run court-circuité « fichier amont identique » compte comme succès mais ne rajeunit pas la donnée ; c'est `data_age_days` qui dit l'âge réel de ce qui est servi). Opt-in pour ne pas alourdir les payloads par défaut. Cache 5min côté serveur — coût négligeable.
Raw schema
{
"type": "object",
"properties": {
"nom": {
"type": "string",
"description": "Nom de famille (non vide)."
},
"prenom": {
"type": "string",
"description": "Prénom du PS."
},
"departement": {
"type": "string",
"description": "Code département INSEE (ex: '75', '2A', '2B', '971'). Métropole 2 caractères (Corse '2A'/'2B', pas '20'), DOM/COM 3 caractères."
},
"include_etudiants": {
"type": "boolean"
},
"include_agents_publics": {
"type": "boolean"
},
"limit": {
"type": "number",
"description": "Nombre max de résultats (1-500, défaut 100).",
"minimum": 1,
"maximum": 500,
"default": 100
},
"include_freshness": {
"type": "boolean",
"default": false,
"description": "Si true, ajoute un champ `data_freshness` au payload (dans `query_metadata` si présent, sinon à la racine) listant, par source (FINESS, Ameli, RPPS, CDS, IRIS), la dernière ingestion réussie (`last_success_at`, `staleness_days`) ET la dernière fois que la donnée a réellement changé (`last_data_change_at`, `data_age_days` — un run court-circuité « fichier amont identique » compte comme succès mais ne rajeunit pas la donnée ; c'est `data_age_days` qui dit l'âge réel de ce qui est servi). Opt-in pour ne pas alourdir les payloads par défaut. Cache 5min côté serveur — coût négligeable."
}
},
"required": [
"nom"
]
}
professionnel_by_rpps
Récupère la fiche complète d'un PS par identifiant national (`rpps_id` / IDNPS, 11 ou 12 chiffres — IDs émis depuis 2020 ont un préfixe `"81"` = 12 chars ; anciens IDs = 11 chars). Renvoie N entrées quand le PS exerce sur plusieurs sites (1 par site, chacun avec sa propre `geo_precision` — un même PS peut donc cumuler un site précis FINESS et un site au centroïde commune).
Chaque résultat géolocalisé porte `geo_precision` ∈ {`"adresse"`, `"etablissement_finess"`, `"centroide_commune"`} — lire ce champ pour évaluer la fiabilité des `coords` (précise BAN/FINESS au m près vs centroïde commune ~3 km, non discriminant intra-commune).
Fallback automatique sur l'API FHIR ANS live (`gateway.api.esante.gouv.fr/fhir/v2`) si non trouvé en base locale (snapshot mensuel J-30 max). Le champ `source` distingue `db` (base locale) de `ans_fhir` (live). `include_freshness` n'affecte que `source: "db"`. Source : Annuaire Santé, Agence du Numérique en Santé (ANS) — Licence Ouverte v2.0
Parameters2
rpps_id
string
required
include_freshness
boolean
optional
Si true, ajoute un champ `data_freshness` au payload (dans `query_metadata` si présent, sinon à la racine) listant, par source (FINESS, Ameli, RPPS, CDS, IRIS), la dernière ingestion réussie (`last_success_at`, `staleness_days`) ET la dernière fois que la donnée a réellement changé (`last_data_change_at`, `data_age_days` — un run court-circuité « fichier amont identique » compte comme succès mais ne rajeunit pas la donnée ; c'est `data_age_days` qui dit l'âge réel de ce qui est servi). Opt-in pour ne pas alourdir les payloads par défaut. Cache 5min côté serveur — coût négligeable.
Raw schema
{
"type": "object",
"properties": {
"rpps_id": {
"type": "string",
"pattern": "^\\s*\\d{11,12}\\s*$"
},
"include_freshness": {
"type": "boolean",
"default": false,
"description": "Si true, ajoute un champ `data_freshness` au payload (dans `query_metadata` si présent, sinon à la racine) listant, par source (FINESS, Ameli, RPPS, CDS, IRIS), la dernière ingestion réussie (`last_success_at`, `staleness_days`) ET la dernière fois que la donnée a réellement changé (`last_data_change_at`, `data_age_days` — un run court-circuité « fichier amont identique » compte comme succès mais ne rajeunit pas la donnée ; c'est `data_age_days` qui dit l'âge réel de ce qui est servi). Opt-in pour ne pas alourdir les payloads par défaut. Cache 5min côté serveur — coût négligeable."
}
},
"required": [
"rpps_id"
]
}
finess_sirene_coverage_in_radius
Compare la couverture du référentiel FINESS DREES (sites physiques agréés LBM/pharmacie/etc.) au référentiel SIRENE DINUM (SIRET physiques actifs au NAF cible) dans un rayon géographique. Métrique : ratio sites FINESS / SIRET SIRENE. Utile pour détecter une sur-déclaration FINESS (sites encore listés mais SIRET fermés) ou une sous-déclaration DREES (sites SIRENE non agréés FINESS). Inclut une méthodologie explicite + caveats. V0.13.2 : si `familles` n'est pas passé, le scope FINESS est auto-dérivé du NAF cible (garantit un ratio cohérent — sinon `finess_sites` mélangerait toutes les familles co-localisées dans le rayon). Le matching FINESS↔SIRET est gaté par activité NAF↔famille (cas Hôpital Franco-Britannique : IFSI et labo au 4 rue Kléber ne sont plus confondus). Source : FINESS DREES + DINUM Recherche Entreprises + SIRENE INSEE.
Familles FINESS à inclure côté DREES. V0.13.2 : si omis, auto-dérivé du NAF cible via la table naf-finess-mapping (ex: naf=8690B → familles=[labo] ; naf=8610Z → multi-familles hospitalières). Passer explicitement si vous voulez restreindre davantage le scope. Valeurs : mco, ssr, sld, had, psychiatrie, dialyse, ambulatoire, labo, imagerie, pharmacie, msp_cpts, ehpad, residence_autonomie, senior_accompagnement, ssiad, aide_domicile, handicap_enfants, handicap_adultes, addictologie, enfance_protection, pmi, hebergement_social, prevention_sante, groupement.
max_unites_legales
number
optional
Nombre maximum d'unités légales DINUM à déplier (1-25, défaut 10). Au-delà : truncated_unites_legales=true.
Raw schema
{
"type": "object",
"properties": {
"lon": {
"type": "number",
"description": "Longitude WGS84 du centre de la zone."
},
"lat": {
"type": "number",
"description": "Latitude WGS84 du centre de la zone."
},
"radius_km": {
"type": "number",
"minimum": 0.1,
"maximum": 50,
"default": 5,
"description": "Rayon de la zone en km (0.1-50, défaut 5)."
},
"naf": {
"type": "string",
"description": "Code NAF SIRENE à comparer (ex: '8690B' labos d'analyses médicales, '4773Z' pharmacies, '8621Z' médecine générale)."
},
"familles": {
"type": "array",
"items": {
"type": "string",
"enum": [
"mco",
"ssr",
"sld",
"had",
"psychiatrie",
"dialyse",
"ambulatoire",
"labo",
"imagerie",
"pharmacie",
"msp_cpts",
"ehpad",
"residence_autonomie",
"senior_accompagnement",
"ssiad",
"aide_domicile",
"handicap_enfants",
"handicap_adultes",
"addictologie",
"enfance_protection",
"pmi",
"hebergement_social",
"prevention_sante",
"groupement"
]
},
"description": "Familles FINESS à inclure côté DREES. V0.13.2 : si omis, auto-dérivé du NAF cible via la table naf-finess-mapping (ex: naf=8690B → familles=[labo] ; naf=8610Z → multi-familles hospitalières). Passer explicitement si vous voulez restreindre davantage le scope. Valeurs : mco, ssr, sld, had, psychiatrie, dialyse, ambulatoire, labo, imagerie, pharmacie, msp_cpts, ehpad, residence_autonomie, senior_accompagnement, ssiad, aide_domicile, handicap_enfants, handicap_adultes, addictologie, enfance_protection, pmi, hebergement_social, prevention_sante, groupement."
},
"max_unites_legales": {
"type": "number",
"minimum": 1,
"maximum": 25,
"default": 10,
"description": "Nombre maximum d'unités légales DINUM à déplier (1-25, défaut 10). Au-delà : truncated_unites_legales=true."
}
},
"required": [
"lon",
"lat",
"naf"
]
}
panorama_implantation_complet
Étude d'implantation labo en 1 appel (V0.23). Géocode l'adresse cible puis agrège EN PARALLÈLE 7 sections : `territoire` (densités PS commune vs national + établissements), `demande` (profil démographique du BASSIN — rayon — via profil_iris : âge, CSP, revenu pondéré), `concurrents` (labos FINESS), `pourvoyeurs` (MCO/EHPAD/SSR/dialyse — drivers écosystémiques), `prescripteurs` (médecins RPPS + IDEL Ameli), `cds` (centres de santé), `referentiels` (qualité couverture FINESS↔SIRENE).
Remplace ~15 appels MCP individuels par 1. Renvoie des RÉSUMÉS (count / top-N / moyenne), JAMAIS de listes brutes. AUCUNE interprétation métier (pas de 'désert médical' ni de verdict GO/NO-GO) — le caller LLM applique sa grille.
DÉGRADATION (lis `couverture` — 1 drapeau par section) : `"ok"` | `"partiel:<raison>"` | `"indisponible:<raison>"`. Si une source est down, SA section est flaggée et le RESTE est renvoyé — comble alors le trou via l'outil unitaire correspondant (etablissements_finess_in_radius, professionnels_rpps_in_radius, densite_sante, centres_sante_in_radius…). Échec d'ANCRAGE (géocodage KO / adresse douteuse / code INSEE indérivable) = rejet total (RangeError).
Pièges internalisés : Paris/Lyon/Marseille basculés sur le département (`meta.plm_mode=true`) ; `prescripteurs` expose `precis_count` (PS géolocalisés à l'adresse, pas au centroïde commune) ; `cds` sans distance individuelle (centroïde commune).
WORKFLOW : appelle CET outil pour DÉMARRER une étude, puis creuse les sections `partiel`/`indisponible` via les unitaires, puis `enrichir_concurrents` sur le top 3 de `concurrents.top`.
Sources : IGN (géocodage), FINESS DREES, RPPS/ANS, Ameli/CNAM, INSEE/FILOSOFI, SIRENE/DINUM.
Parameters4
adresse
string
optional
Adresse cible, géocodée en interne via IGN. Ex: "12 rue Nationale, Lille". XOR avec `point`.
point
object
optional
Coordonnées { lat, lon } si déjà connues (skip géocodage). Fournir `code_insee` avec.
code_insee
string
optional
Code INSEE commune (avec `point`, quand le géocodage est déjà fait).
rayon_km
number
optional
Rayon du bassin de l'étude (km). Défaut 5.
Raw schema
{
"type": "object",
"properties": {
"adresse": {
"type": "string",
"description": "Adresse cible, géocodée en interne via IGN. Ex: \"12 rue Nationale, Lille\". XOR avec `point`."
},
"point": {
"type": "object",
"description": "Coordonnées { lat, lon } si déjà connues (skip géocodage). Fournir `code_insee` avec.",
"properties": {
"lat": {
"type": "number"
},
"lon": {
"type": "number"
}
}
},
"code_insee": {
"type": "string",
"description": "Code INSEE commune (avec `point`, quand le géocodage est déjà fait)."
},
"rayon_km": {
"type": "number",
"description": "Rayon du bassin de l'étude (km). Défaut 5."
}
}
}
enrichir_concurrents
Enquête approfondie sur le top concurrents (V0.23). Pour chaque FINESS : statut actif + taille d'équipe + historique récent (inspect_site), signal M&A — rebranding en cours — (compare raison sociale FINESS vs RPPS), groupe parent (entreprise_by_siren : Biogroup/Cerballiance/… + `est_grand_groupe`).
Cap dur `max=3` (inspect_site ~7 K tokens/appel — JAMAIS 10+). Drapeau `couverture` PAR concurrent (`"ok"` | `"partiel:<raison>"`) : un concurrent qui échoue n'annule pas les autres.
Typiquement appelé sur `concurrents.top[0..2].finess` renvoyés par panorama_implantation_complet.
Sources : FINESS/ANS, RPPS/ANS, SIRENE/DINUM.
Parameters2
finess
array
required
Numéros FINESS à enquêter (typiquement le top 3 concurrents par distance).
max
number
optional
Cap dur du nombre de concurrents enquêtés. Défaut 3.
Raw schema
{
"type": "object",
"properties": {
"finess": {
"type": "array",
"items": {
"type": "string"
},
"description": "Numéros FINESS à enquêter (typiquement le top 3 concurrents par distance)."
},
"max": {
"type": "number",
"description": "Cap dur du nombre de concurrents enquêtés. Défaut 3."
}
},
"required": [
"finess"
]
}
dynamique_immobiliere
Dynamique immobilière et potentiel de croissance d'une zone (point + rayon). Combine 3 sources officielles : permis de construire (Sit@del/SDES, maille COMMUNE — logements autorisés/commencés récents → habitants attendus), zones AU du PLU (Géoportail de l'Urbanisme/IGN — futurs quartiers réservés, géolocalisés), ventes de terrains à bâtir (DGFiP DVF, géolocalisées). Sortie en 2 registres : 'note' = VOLUME (logements autorisés/commencés, nombre et immédiateté des zones AU) destiné au scoring de potentiel ; 'info' = quartiers concernés (nommés), habitants attendus, prix indicatifs (contexte, hors score). En ville dense les permis-commune sont grossiers → s'appuyer sur zones AU + terrains (géolocalisés). Point côtier/isolé sans commune au géocodage inverse → `couverture.permis`='indisponible:commune_introuvable' et `meta.code_commune`=null, MAIS zones AU + terrains restent servis (calcul par rayon) — l'outil ne plante jamais pour ça. Paris/Lyon/Marseille : Sit@del ne descend pas à l'arrondissement → les permis servis sont ceux de la VILLE ENTIÈRE (`meta.code_commune_permis`, ex 75056), `couverture.permis`='partiel:ville_entiere_plm' et le `signal` ne s'appuie alors que sur les zones AU. Commune absente de Sit@del, ou sans aucune année pleine → `couverture.permis`='indisponible:no_data' (les zéros ne sont PAS une donnée ; `info.permis.annee_en_cours` peut malgré tout être servi pour une commune nouvelle). Les totaux permis ne somment que des ANNÉES PLEINES (`info.permis.annees_pleines`) ; l'année en cours, incomplète (le SDES publie avec ~2 mois de retard), est servie à part dans `info.permis.annee_en_cours` avec ses `mois_couverts` — ne JAMAIS la comparer telle quelle à une année pleine ni l'ajouter aux totaux. Moins de 5 années pleines en base → 'partiel:fenetre_courte' ; une année de la fenêtre à moins de 12 mois publiés → 'partiel:annees_incompletes' (total SERVI mais non comparable, non scoré dans les deux cas). 'geojson' = polygones des zones AU pour la carte. Sources : SDES, IGN/GPU, DGFiP.
Parameters3
lat
number
required
Latitude du centre (WGS84).
lon
number
required
Longitude du centre (WGS84).
rayon_km
number
optional
Rayon en km (0.1-10, défaut 3).
Raw schema
{
"type": "object",
"properties": {
"lat": {
"type": "number",
"description": "Latitude du centre (WGS84)."
},
"lon": {
"type": "number",
"description": "Longitude du centre (WGS84)."
},
"rayon_km": {
"type": "number",
"description": "Rayon en km (0.1-10, défaut 3).",
"minimum": 0.1,
"maximum": 10,
"default": 3
}
},
"required": [
"lat",
"lon"
]
}
cout_foncier
Coût du foncier d'une zone (point + rayon) : prix médian au m² RÉSIDENTIEL bâti — maisons + appartements UNIQUEMENT, PAS les locaux commerciaux/professionnels (+ quartiles p25/p75), volume de ventes, période couverte. Source DGFiP DVF (ventes réelles géolocalisées). Pour un local pro (labo, cabinet), ce prix résidentiel est un PROXY indicatif, pas le prix d'un local commercial. INFORMATION pour le business case d'implantation — NE PAS intégrer à une note d'attractivité : le coût d'installation est distinct du potentiel de marché.
Parameters3
lat
number
required
Latitude du centre (WGS84).
lon
number
required
Longitude du centre (WGS84).
rayon_km
number
optional
Rayon en km (0.1-10, défaut 3).
Raw schema
{
"type": "object",
"properties": {
"lat": {
"type": "number",
"description": "Latitude du centre (WGS84)."
},
"lon": {
"type": "number",
"description": "Longitude du centre (WGS84)."
},
"rayon_km": {
"type": "number",
"description": "Rayon en km (0.1-10, défaut 3).",
"minimum": 0.1,
"maximum": 10,
"default": 3
}
},
"required": [
"lat",
"lon"
]
}
MCP TypeScript qui croise et réconcilie 13 référentiels publics français (INSEE SIRENE, IRIS & Melodi, FINESS DREES, RPPS / Annuaire Santé ANS, Annuaire Santé Ameli, Centres de Santé CNAM, DVF / DGFiP, Sit@del / SDES, PLU via apicarto, IGN Géoplateforme, geo.api.gouv.fr & Recherche Entreprises DINUM). Détecte les SIRET fermés invisibles côté DREES, distingue site vs groupe, croise l'offre de soins avec la démographie au quartier, évalue le potentiel immobilier d'un site (prix DVF €/m², permis de construire, zones AU du PLU), expose la fraîcheur de chaque source.
Le wrapper forwarde stdio → endpoint HTTPS distant. Aucune DB locale à provisionner. Override possible : FRANCE_DATA_MCP_URL=https://mon-miroir.example/mcp.
Les APIs officielles (INSEE, FINESS DREES, RPPS ANS, Annuaire Ameli, Centres de Santé CNAM, IGN, DINUM) existent mais sont éclatées, sous-documentées et pleines de pièges : rate limits, formats CSV propriétaires, latence DREES de 1-2 mois, diffusion partielle INSEE, mappings inconsistants Ameli ↔ RPPS.
france-data-mcp est le premier MCP qui croise factuellement ces sources pour répondre à des questions concrètes — cartographie d'offre de soins, étude de marché territoriale, journalisme local, civic-tech.
Périmètre — 13 sources publiques croisées
🗺️ Territoire (2) : geo.api.gouv.fr (DINUM, communes), IGN Géoplateforme (géocodage)
🏥 Santé (3) : FINESS / ANS (~105 K établissements en service, DOM inclus — flux JSON quotidien qui remplace le CSV DREES arrêté en juillet 2026), Annuaire Santé Ameli (~466 K libéraux), RPPS / ANS (~2,28 M PS actifs)
🩺 Centres de Santé (1) : Annuaire santé CNAM (~3 K structures L.6323-1 CSP, sync hebdomadaire)
📊 Démographie infracommunale (2) : INSEE IRIS (~48,6 K quartiers — RP 2022, FILOSOFI 2021 revenu, contours IGN) + INSEE Melodi (population de référence)
~2,2 M PS actifs (libéraux + salariés privés + hospitaliers contractuels + agents publics). Par défaut : Civils uniquement.
🩺 Centres de Santé — Annuaire CNAM (2)
centres_sante_in_radius · centres_sante_by_finess
Structures de soins ambulatoires non lucratives (L.6323-1 CSP, ~3 K). Différenciateur vs FINESS famille=124 : expose carte Vitale, APCV et spécialités exercées sur place (Annexe A CNAM, ~70 codes). Coords = centroïde commune. Sync hebdomadaire.
📊 Démographie & densités — INSEE Melodi + IRIS (3)
Population de référence INSEE croisée avec RPPS / FINESS — méthodologie DREES (ratios pour 100 k hab.). Maille IRIS (quartier, ~48,6K zones) pour la DEMANDE, à croiser avec l'offre de soins.
population (IRIS 9 car., commune 5 car. ou département 2-3 car. — granularité auto-détectée par la longueur du code) · densite_sante (cible: professionnels RPPS ouetablissements FINESS — labos, pharmacies, EHPAD, hôpitaux ; + comparaison nationale matview <50 ms) · profil_iris (pointoucode_iris, rayon_km?) — profil démo d'un îlot ou d'un bassin (âge, CSP, familles, revenu) au RP 2022 + FILOSOFI 2021
🔎 Découverte des nomenclatures (1)
lister_nomenclature (referentiel: ameli_specialites | ameli_types_ps | rpps_savoir_faire) — codes spécialité/type_ps Ameli et savoir_faire RPPS dans un seul tool (remplace les 3 anciens lister_*).
🧭 Agrégateurs & études composites (4)
panorama_sante_territoire (V0.9) — 1 call : population + densités médecins/infirmiers/pharmaciens vs national + count FINESS par famille (labo, pharmacie, EHPAD, MCO, MSP/CPTS) + bloc demande IRIS (profil démo commune : âge, CSP, familles, revenu). Granularité explicite (niveau: commune, niveauEtablissements: departement | indisponible).
inspect_site (V0.10) — vue 360 d'un établissement en 1 call : identification FINESS + statut administratif SIRENE (resolver SIRET) + PS rattachés + historique INSEE.
panorama_implantation_complet (V0.23) — étude d'implantation labo en 1 call : 7 sections (territoire, demande IRIS du bassin, concurrents, pourvoyeurs MCO/EHPAD/SSR, prescripteurs RPPS+IDEL, centres de santé, qualité référentiels). Résumés, jamais de listes brutes ; dégradation par section.
enrichir_concurrents (V0.23) — enquête sur le top concurrents (statut actif + équipe + signal M&A + groupe parent), cap dur max=3.
🏗️ Immobilier — potentiel d'un site (2)
dynamique_immobiliere (V0.26) — composite en 1 call : permis de construire (Sit@del / SDES, table ingérée mensuellement) + zones AU du PLU (apicarto / IGN, live) + ventes de terrains DVF. Sortie 2 registres : note (volume → scoring) / info (quartiers AU + prix → contexte) ; geojson = polygones des zones AU. · cout_foncier (V0.26) — prix médian €/m² DVF (P25/P75, n_ventes, période), info seule.
Source DVF / DGFiP (cache paresseux PostGIS, anon lit / service écrit). Zones AU = live ; permis Sit@del = table ingérée (cron mensuel, le 10). Pensé pour les rapports d'implantation.
Dernière version (V0.31.0) : etablissement_finess_by_nom — un établissement de santé par son nom (« Institut Gustave Roussy » → fiches, point exact, commune prouvée, jamais un premier résultat servi seul) ; permis de construire Sit@del lus en base (10,9 s au lieu de 45 s) avec l'année en cours servie à part des 5 années pleines et le « 0 logement » de Paris/Lyon/Marseille corrigé. 13 référentiels / 37 outils.
Contribuer
Ouvrir une issue pour discuter avant d'envoyer une PR.
Licence
MIT — voir LICENSE. Les données restent sous leurs licences respectives :