Référentiel Établissements — Construction

A1Connect est la source de vérité du référentiel des établissements (lycées + supérieur), alimenté depuis ONISEP chaque mois et complété par le dataset MESR des campus connectés. Dema1n et Inspire gardent chacun une copie locale rafraîchie sur notification.

[ONISEP]  →(mensuel)→  [A1C]  →(webhook POST)→  [Dema1n]  copie locale
[MESR]    →(mensuel)→         →(webhook POST)→  [Inspire] copie locale

Pour consommer les données déjà construites (auth, endpoints, format), voir api.md.

Récapitulatif des syncs

Donnée Source Déclenchement Endpoint manuel
Structures (lycées + supérieur) ONISEP Idéo Cron mensuel POST /admin/etablissements/sync
Campus connectés (sup., hors UAI) MESR — dataset fr-esr-campus-connectes Cron mensuel (même cron) POST /admin/etablissements/sync/campus-connecte
Ministère (tutelle) ONISEP Idéo Manuel POST /admin/etablissements/sync/ministere
Téléphone ONISEP Idéo Manuel POST /admin/etablissements/sync/telephone
Formations ONISEP Idéo (2 datasets dédiés) Auto après chaque sync mensuel + manuel POST /admin/etablissements/sync/formations
Code INSEE commune geo.api.gouv.fr Auto (calcul des flags) + manuel POST /admin/etablissements/sync/insee
ZRR collectivites-locales.gouv.fr (Excel FRR) Auto (calcul des flags) + manuel POST /admin/etablissements/sync/zrr
QPV SIGVille ANCT Auto (calcul des flags) + manuel POST /admin/etablissements/sync/qpv
IPS / IEL data.education.gouv.fr Auto (calcul des flags) + manuel POST /admin/etablissements/sync/ips-iel
isPrioritaire Calculé (QPV ou ZRR ou IEL ou IPS renseigné ou MVLS partenaire) Manuel (bouton admin) POST /admin/etablissements/sync/prioritaire
Sélectif Liste statique SELECTIF_UAIS Manuel POST /admin/etablissements/sync/selectif
Cordées de la Réussite Scraping portail ONISEP Manuel POST /admin/etablissements/sync/cordees
isMVLSPartenaire Aucune — édition admin uniquement — PATCH /admin/etablissements/:id

Source de données — ONISEP Idéo

ONISEP est la seule source. Elle couvre lycées + supérieur sans primaire ni maternelle, déjà filtrés pour l'orientation scolaire.

Pourquoi ONISEP et pas RAMSESE : RAMSESE ne couvre pas le supérieur et inclut primaire/maternelle (~30K établissements inutiles). ONISEP couvre les deux en un seul outil.

Dataset ID Volume
Structures secondaire 5fa5816ac6a6e ~15 283
Structures supérieur 5fa586da5c4b6 ~9 024

Auth : Bearer token JWT 24h + Application-ID statique. OnisepAuthService gère le cache mémoire du token (TTL 23h, renouvellement automatique). Env vars : ONISEP_APP_ID, ONISEP_EMAIL, ONISEP_PASSWORD.

Deux champs ONISEP sont aussi resynchronisables seuls, sans repasser par l'upsert complet : ministere (POST /admin/etablissements/sync/ministere) et telephone (POST /admin/etablissements/sync/telephone).


Source de données — Campus connectés (MESR)

Deuxième source, indépendante d'ONISEP : dataset fr-esr-campus-connectes (MESR, via OpenDataSoft, CampusConnecteAdapter). Couvre des structures du supérieur sans code UAI — upsert par idPaysage (clé MESR) plutôt que par codeUai, avec estCampusConnecte: true posé à la création.

Auth : clé API en query string, publique (exposée côté front sur services.dgesip.fr, pas un secret) — CAMPUS_CONNECTE_API_KEY dans campus-connecte.adapter.ts.

Champs alimentés : nom, adresse, academie, region, universiteRattachement, telephone, latitude, longitude. Pas de codePostal fourni ⇒ pas de résolution INSEE ⇒ pas de ZRR (ni IPS/IEL, qui matchent aussi par codeUai, absent ici). QPV reste calculable via SIGVille (lat/long directement).


Synchronisation

Cron

Un seul cron NestJS : @Cron('0 3 1 * *') dans EtablissementSyncService (1er du mois à 3h), qui déclenche successivement le sync ONISEP puis le sync campus connectés. Pas de CronJob K8s nécessaire — le scheduling est géré par l'application.

Le sync ONISEP se termine dès que l'upsert est fini. Le calcul des flags tourne ensuite en arrière-plan (non-bloquant). État accessible en temps réel via GET /api/v1/etablissements/sync/status.

Post-sync (ONISEP uniquement) : email de rapport Brevo à tech@article-1.eu (succès ou échec). Pas de message RabbitMQ à ce jour.

Flow complet

1. Pull ONISEP — 2 datasets (~26 appels paginés)

2. Upsert ~24K records par codeUai (idempotent, relançable)

3. Résolution codes INSEE
   → si index absent : chargement automatique depuis geo.api.gouv.fr (~35K communes)
   → chaque établissement : codePostal + ville → codeInseeCommune

4. Calcul des flags (en parallèle)
   ├─ ZRR  → Excel FRR (collectivites-locales.gouv.fr) + codeInseeCommune
   ├─ QPV  → SIGVille API X,Y (latitude/longitude) — 50 requêtes concurrentes
   └─ IPS + IEL → datasets data.education.gouv.fr (match par UAI)

5. Webhook POST vers Dema1n + Inspire (non actif — à décommenter dans notifyPlatforms()
   quand les plateformes auront leur endpoint POST /etablissements/sync)

6. (cron uniquement) Sync campus connectés MESR — déclenché juste après, indépendant d'ONISEP

Tout est idempotent. En cas d'erreur mi-parcours, relancer POST /api/v1/etablissements/sync suffit.

Chaque flag est aussi relançable seul (utile pour ne pas refaire tout syncAll) : POST /admin/etablissements/sync/zrr, /sync/qpv, /sync/ips-iel, /sync/selectif, /sync/prioritaire.


Calcul des flags — ZRR / QPV / IPS / IEL

Quatre indicateurs de vulnérabilité territoriale et socio-économique, calculés chacun par un service dédié (EtablissementZrrService, EtablissementQpvService, EtablissementIpsIelService, EtablissementPrioritaireService) orchestrés par EtablissementFlagsService. Description des champs exposés : voir api.md.

Sources

Flag Source Détail
ZRR collectivites-locales.gouv.fr — fichier Excel FRR Colonne Code_insee, filtrée sur la colonne Classement... (!== 'Non classée') — le fichier liste toutes les communes de France, pas seulement celles classées. URL dans la constante FRR_EXCEL_URL de etablissement-flags.constants.ts — à mettre à jour si le fichier est republié après une révision législative (~3–5 ans).
QPV SIGVille ANCT — wsa.sig.ville.gouv.fr/api/xy.json Géoréférencement inverse : POST {type_quartier: 'QP', x: longitude, y: latitude} → OUI/NON. Auth HTTP Basic : SIGVILLE_USERNAME / SIGVILLE_PASSWORD.
IPS data.education.gouv.fr — dataset fr-en-ips_lycees Champ ips_ensemble_gt_pro. Mis à jour à chaque rentrée scolaire. Fournit aussi code_insee_de_la_commune, renseigné en bonus sur les ~4 300 lycées couverts.
IEL dataeducation.opendatasoft.com — dataset fr-en-indice_eloignement_lycee_ap2020 Champ indice_eloignement. Mis à jour à chaque rentrée scolaire.

ZRR — source et signification de null

Source : liste officielle des communes classées France Ruralités Revitalisation (FRR, ex-ZRR) publiée par collectivites-locales.gouv.fr — elle couvre la quasi-totalité des communes de France, classées ou non.

zrr vaut : - true — la commune est classée FRR. - false — la commune est dans la liste FRR mais "Non classée". - null — la commune n'a pas été trouvée dans la liste FRR (pas encore rattachée à un code INSEE, ou absente du fichier).

isPrioritaire

Pas une source externe : flag calculé (QPV ou ZRR ou IEL ou IPS renseigné ou MVLS partenaire), donc dépendant des flags ci-dessus et de isMVLSPartenaire. Reset puis ré-application complète à chaque appel — pas de sync automatique, déclenchement manuel uniquement (bouton "Prioritaire" du menu Actions en admin) : POST /admin/etablissements/sync/prioritaire.

Résolution code INSEE

ZRR et QPV requièrent le codeInseeCommune (code INSEE 5 chars), différent du codePostal ONISEP.

Résolution via un index mémoire construit depuis geo.api.gouv.fr/communes (Map<codePostal, {code, nom}[]>, ~35K communes). syncAll le charge automatiquement en début de traitement s'il n'est pas déjà en mémoire. Aucun appel réseau par établissement — tout se fait en mémoire. Statut : GET /admin/etablissements/communes-index/status ; rechargement forcé : POST /admin/etablissements/communes-index/refresh.

Suivi de la résolution : GET /admin/etablissements/sync/insee/remaining (non résolus), GET /admin/etablissements/sync/insee/not-found (sentinel posé), POST /admin/etablissements/sync/insee (relance la résolution sur les non-résolus).

Si ambiguïté (plusieurs communes pour un code postal) : match sur le nom de ville (exact, puis préfixe pour les arrondissements Paris/Lyon/Marseille). Si non résolu, fallback sur la première entrée.

Sentinel NOT_FOUND

codeInseeCommune a trois états :

Valeur Signification
null Pas encore résolu
"01234" Résolu
"NOT_FOUND" Résolution tentée, aucune commune trouvée

Le sentinel distingue "pas encore traité" de "traité sans résultat" — évite de retenter à chaque sync. Arrive pour des codes postaux CEDEX ou des établissements à l'étranger. Si le code postal est corrigé dans ONISEP, relançable via POST /v1/admin/etablissements/sync/insee/not-found.


Formations

Source : deux datasets ONISEP dédiés, distincts des structures (OnisepAdapter.fetchFormationsIndex).

Dataset ID
Formations lycée 605340ddc19a9
Formations supérieur 605344579a7d7

Deux champs sont stockés à partir de ces datasets : - formations — intitulé détaillé (formation_for_libelle, ex: "BTS gestion de la PME") - formationsTypes — catégorie brute (for_type, ex: "brevet de technicien supérieur")

Sync indépendante du sync principal, non-bloquante : POST /admin/etablissements/sync/formations (EtablissementSyncService.syncFormations), aussi déclenchée automatiquement en arrière-plan après chaque sync mensuel.


Cordée de la Réussite & établissements sélectifs

Cordées — seule donnée du référentiel obtenue par scraping (pas de dataset opendata dédié) : CordeePortailAdapter parcourt portail.onisep.fr/cordeesdelareussite.onisep.fr académie par académie. Déclenché via POST /admin/etablissements/sync/cordees.

Établissements sélectifs — liste statique SELECTIF_UAIS, pas de source externe — reset puis ré-application complète à chaque sync.


Notes et limites

Fallback établissement libre — ONISEP ne couvre pas les CFA hors contrat, privés hors contrat, établissements étrangers. Le champ texte libre etablissementNom dans inspire-v2 reste nécessaire.

codeAcademie et etatEtablissement — non fournis par ONISEP, restent null dans A1C.

sigle — champ sigle du dataset ONISEP (secondaire et supérieur), souvent vide côté secondaire. Réécrit à chaque sync mensuel comme les autres champs ONISEP ; correction manuelle possible en admin mais non protégée d'un futur resync.

isMVLSPartenaire — seul champ du référentiel sans aucune source de sync : purement éditable en admin (PATCH /admin/etablissements/:id), jamais touché par un resync.