# Carte des établissements scolaires (France) Carte interactive OpenStreetMap / Leaflet des ~68 500 établissements de l'annuaire de l'Éducation nationale, avec filtres côté client. Source des données : [data.education.gouv.fr — annuaire-education](https://data.education.gouv.fr/explore/dataset/fr-en-annuaire-education/). ## Lancer L'application nécessite **PHP + MySQL** (la carte et la GED sont servies par une API PHP qui lit la base ; pas de fichier `file://`). ### 1. Initialiser la base (une seule fois) MySQL doit tourner (Laragon le démarre). Puis : ```sh php setup.php # crée la base « ecoles », les tables, importe les établissements php setup.php --reset # réimporte les établissements depuis data/etablissements.json ``` > Identifiants par défaut (Laragon : `root` / mot de passe vide) dans `api/db.php`. ### 2. Servir l'application **Avec Laragon** : place le projet dans `www/`, puis ouvre `http://ecoles.test/` (ou `http://localhost/ecoles/`). **Sans Laragon**, serveur PHP intégré : ```sh php -S 127.0.0.1:8000 ``` puis ouvre http://127.0.0.1:8000/. ## Fiche établissement Un clic sur un point → **« Voir la fiche complète »** ouvre une fiche plein écran (overlay superposé à la carte, donc le retour ne recharge pas la carte) avec quatre onglets : - **Informations** — toutes les données de l'établissement + bouton « Localiser sur la carte », les effectifs, et le **fil de commentaires** de l'établissement (voir *Groupes & commentaires*) - **Documents** — arborescence de dossiers + fichiers / liens / photos **validés** - **Photothèque** — galerie des photos validées - **Proposer un élément** — tout internaute peut soumettre un fichier, une photo ou un lien Les éléments sont stockés **sur disque** dans `uploads/`, leurs métadonnées en base (table `items`, type `file` / `photo` / `link`), indexés par UAI et rangés dans l'arborescence globale (`folders`). Chaque document/photo de l'onglet *Documents* affiche, en plus de son libellé, sa **date de dépôt** et une **description** (texte explicatif). La description est saisie par le proposant au dépôt et **modifiable** ensuite par les **valideurs** (bouton ✏️ visible sur la fiche quand on est connecté avec un droit de validation sur l'établissement). Chaque document a son **fil de commentaires** dépliable (bouton 💬), soumis aux **mêmes règles** que les commentaires d'établissement (visibilité par groupe, dépôt réservé aux membres d'un groupe, suppression par l'auteur ou le super-admin) — voir *Groupes & commentaires*. Les commentaires d'un document et ceux de l'établissement restent distincts (`comments.item_id`). ## Droits & modération Trois niveaux : | Rôle | Droits | |---|---| | **Internaute** (anonyme) | Proposer des éléments (statut « en attente ») | | **Commentateur** | Compte sans droit de modération : participe aux **commentaires** des fiches via ses groupes | | **Admin de périmètre** | Valider/rejeter les éléments des communes de son périmètre (commune + département) ou de ses groupes valideurs ; commente aussi | | **Super-admin (plateforme)** | Tout valider, gérer l'arborescence, créer les utilisateurs et les groupes, importer l'opendata | - Seuls les éléments **validés** sont visibles du public. - Espace d'administration : **`admin.html`** (lien en bas de la barre latérale). - Super-admin par défaut créé par `setup.php` : **`admin` / `admin`** — *à changer*. - L'import opendata (bouton dans l'admin) télécharge la dernière version depuis data.education.gouv.fr et met la base à jour (accès Internet requis côté serveur). ## Groupes d'utilisateurs & commentaires Le super-admin organise les utilisateurs en **groupes** (section *Groupes d'utilisateurs* de l'admin) : - Un groupe marqué **valideur** donne à ses membres le droit de **valider les documents** des établissements de **ses communes** (même modèle de périmètre que les admins de périmètre, mais porté au niveau du groupe). Un utilisateur peut appartenir à plusieurs groupes ; ses droits de validation sont l'union de ceux de ses groupes valideurs. - Tables : `user_groups` (nom + `can_validate`), `group_members` (appartenance n..n), `group_scopes` (communes du groupe valideur). Le contrôle est centralisé dans `can_validate_uai()` (`api/db.php`), qui accepte super-admin, périmètre personnel *(legacy)* **ou** groupe valideur. API : `api/groups.php` (super-admin). **Commentaires de fiche** — affichés directement dans l'onglet *Informations* : - Un commentaire est **horodaté** (date + heure), porte un **texte** et le **nom** de son auteur (table `comments`). - Il n'est **visible qu'entre membres d'un même groupe** que l'auteur ; le super-admin voit tout. Déposer un commentaire nécessite d'**appartenir à au moins un groupe**. - L'**auteur** (et le super-admin) peuvent supprimer un commentaire ; pas d'édition (horodatage figé). API : `api/comments.php`. - La connexion se fait **directement dans la carte commentaires** (mini-formulaire), la session étant partagée avec l'espace d'administration. - Pour les participants qui ne doivent **pas** modérer, le super-admin crée des comptes de rôle **Commentateur** (section *Utilisateurs* de l'admin) : ils se connectent et commentent via leurs groupes, sans accès à la file de validation. ## Sécurité des mots de passe - **Hachage** : bcrypt (`password_hash` / `PASSWORD_DEFAULT`), vérifié par `password_verify`. Aucun mot de passe n'est stocké ni transmis en clair. - **Politique de complexité** (`password_policy_check`, `api/db.php`) : **≥ 8 caractères** et **au moins 3 types** parmi minuscule / majuscule / chiffre / caractère spécial. Appliquée à la création, à la réinitialisation et au changement. Côté navigateur, une **jauge de robustesse** (`pwpolicy.js`) guide la saisie. - **Changement forcé** : les comptes créés par un admin (et `admin`/`admin` par défaut) sont marqués `must_change` → l'utilisateur doit choisir un nouveau mot de passe à sa première connexion (`account.html?forced=1`). - **Self-service** : tout utilisateur connecté peut changer son mot de passe via **`account.html`** (saisie du mot de passe actuel). - **Mot de passe oublié** : depuis l'écran de connexion → **`forgot.html`** (identifiant ou e-mail). Un lien à **usage unique** (valable 1 h) est envoyé par e-mail et consommé sur **`reset.html`**. Table `password_resets` (jeton stocké **haché** en SHA-256). - **Anti-bruteforce** : après **5 échecs** de connexion, le compte est **verrouillé 15 min** (`users.failed_attempts` / `locked_until`). - **Envoi d'e-mails** — transport configurable dans `api/db.php` (`MAIL_TRANSPORT`) : - `log` (par défaut) : écrit l'e-mail (dont le lien) dans `private/mail_outbox.log` (dossier protégé par `.htaccess`) — pratique en local sans serveur de mail ; - `mail` : fonction PHP `mail()` ; - `smtp` : client SMTP intégré (renseigner `SMTP_HOST/PORT/USER/PASS/SECURE`). Pensez à ajuster `APP_BASE_URL` pour que les liens de réinitialisation soient corrects. ## Couche IRIS & population - Fond clair (CartoDB Positron) par défaut ; sélecteur de couches en haut à droite. - Couche **Contours IRIS** (WFS Géoplateforme `STATISTICALUNITS.IRIS:contours_iris`), chargée par zone visible (zoom ≥ 11). - **IRIS cliquables** : un clic sur une zone affiche son nom, sa commune, son type, et la **population du recensement INSEE 2022** (de l'IRIS et de la commune). - Populations importées via `php import_pop.php` dans la table `iris_pop` (population communale = somme des IRIS de la commune). - Bouton **« 📍 Me localiser »** + centrage automatique sur la position de l'internaute (nécessite HTTPS ou localhost). - **Coloration par densité** : chaque IRIS est colorié selon sa densité (population ÷ surface), avec une légende ; un clic affiche population de l'IRIS, densité, et population de la commune. ## Effectifs des écoles (1er degré) - Dataset `fr-en-ecoles-effectifs-nb_classes` importé via `php import_effectifs.php` dans la table `effectifs` (uai, année → élèves, classes ; ~809 000 lignes, 55 360 écoles). - La fiche d'une **école / collège / lycée** affiche, dans l'onglet *Informations*, ses **derniers effectifs** (élèves ; classes pour le 1er degré) et un **graphique d'évolution** sur tout l'historique connu. (1er degré depuis 2009 ; collèges/lycées via leurs datasets dédiés.) - Une carte voisine montre les **effectifs cumulés des écoles publiques de la commune** (somme des élèves des établissements **publics** de la commune, par année) pour situer l'établissement dans son contexte local. API : `api/effectifs.php?action=commune&uai=…`. - **À côté**, un graphique de l'**IPS** (indice de position sociale) de l'établissement. Trois datasets importés via `php import_ips.php` dans la table `ips` (clé `uai` + année) : `fr-en-ips-ecoles-ap2022` (écoles, rentrées 2022→2024, **+ référence départementale**), `fr-en-ips-colleges-ap2022` (collèges) et `fr-en-ips-lycees-ap2022` (lycées, IPS ensemble GT+Pro). Pour les écoles, la courbe de l'établissement est tracée avec la **référence départementale** (même échelle) ; collèges et lycées n'ont qu'une rentrée (2022-2023) pour l'instant, donc un point. API : `api/ips.php?uai=…`. Un IPS élevé reflète un milieu social plus favorisé (référence nationale ≈ 100). `php import_ips.php --niveau=ecoles|colleges|lycees` permet d'importer un seul niveau. - À côté de cette courbe, un graphique d'**évolution de la population** (INSEE) sur les recensements successifs (**1968 → 2022**, 9 dates) au niveau de la **commune** — ou de l'**arrondissement** pour Paris/Lyon/Marseille (déduit du code INSEE `comc`). Pour un arrondissement, une **2ᵉ courbe « ville entière »** donne en plus l'évolution de la ville complète. Source : INSEE, *base communale séries historiques du recensement* (), qui contient le **vrai historique par arrondissement** (Paris 75101…, Lyon 69381…, Marseille 13201…) ainsi que les totaux communes, importée via `php import_pop_insee.php` dans la table `pop_commune`. API : `api/population.php?uai=…` → `{zone, zone_estimated, ville, ville_nom}`. - L'importeur lit le CSV dans le zip sans l'extension PHP `zip` (mini-lecteur ZIP + `gzinflate`). `zone_estimated` n'est utilisé qu'en **repli** (zone sans historique propre) : population de la ville × part de l'arrondissement dans les IRIS 2022. - `import_pop_hist.php` reste une **alternative** (CSV Opendatasoft, s'arrête ~2014, sans arrondissements). ## Performance énergétique (DPE — ADEME) - Dataset `dpe01tertiaire` (DPE des **bâtiments tertiaires** depuis juillet 2021, ~532 000 diagnostics), importé via l'API data-fair de l'ADEME. - ⚠ Ce jeu de données **n'a pas d'identifiant école (UAI)** : les bâtiments sont repérés par adresse BAN + géolocalisation. Le rattachement école↔DPE est donc **géographique** (plus proche bâtiment de la même commune, sous **5 m** par défaut — soit le même bâtiment / la même adresse), comme le rattachement école→IRIS. La distance affichée mesure la fiabilité du lien ; `--radius=N` permet d'élargir. - Import en deux temps : ```sh php import_dpe.php # remplit la table « dpe » (long ; --dep=75 pour tester) php import_comc.php # (si pas déjà fait) code INSEE commune des écoles php match_dpe.php # rattache les DPE aux écoles (table « etab_dpe » ; --radius=N) ``` - La fiche d'un établissement affiche, dans l'onglet *Informations*, le ou les **DPE rattachés** : étiquettes **Énergie** et **GES** (A→G, palette officielle), consommation, émissions, surface, dates et adresse du bâtiment. - **Coloration de la carte** : un sélecteur dans la légende (en bas à droite) permet de colorer les marqueurs par **type d'établissement** (défaut) ou par **étiquette DPE** (A→G ; gris = aucun DPE rattaché). Les étiquettes du sous-ensemble affiché sont chargées à la demande via `api/dpe.php?action=latest`. - **Import depuis l'admin** : un super-admin peut lancer l'import + le rattachement depuis `admin.html` (section *Performance énergétique (DPE)*), avec un département optionnel et un rayon réglable. Opération longue ⇒ pour le dataset complet, préférez les scripts CLI ci-dessus. - API : `api/dpe.php?uai=…` (liste triée par distance) et `?action=latest` (batch, meilleur match par UAI). Logique d'import/rattachement partagée dans `api/dpe_lib.php`. ## Coloration des IRIS (2 modes) Quand la couche IRIS est active, un sélecteur (en bas à gauche) permet de choisir : 1. **Densité de population** — population 2022 ÷ surface de l'IRIS (palette jaune→rouge). 2. **Taux d'écoliers publics** — **à l'échelle de l'IRIS** : élèves des écoles publiques (1er degré, dernière rentrée) **situées dans l'IRIS** ÷ population de l'IRIS, en palette **verte** (vert pâle = faible taux → vert foncé = fort taux). Chaque école est rattachée à l'IRIS qui la contient (point-dans-polygone, côté navigateur). Chaque mode a sa propre légende. Données : `api/iris.php` (population), `api/effectifs.php?action=latest` (effectifs des écoles). Le rattachement école→IRIS et le calcul du taux se font côté client sur la zone visible. ## Filtres disponibles - Recherche texte (nom d'établissement / commune), insensible aux accents - Type d'établissement (École, Collège, Lycée, Médico-social, EREA…) - Statut (Public / Privé) - État (OUVERT / À FERMER) - Région → Académie → Département → Circonscription (sélecteurs en cascade) - Options proposées : Restauration, Internat, ULIS, SEGPA (logique ET ; coché = établissements proposant l'option) > Note : la colonne `apprentissage` existe dans la source mais n'y est jamais renseignée (0 établissement), elle n'est donc pas proposée comme filtre. Les points sont regroupés (clustering) pour rester fluides à l'échelle nationale et colorés par type d'établissement (voir la légende). ## Mettre à jour les données 1. Télécharger l'export à jour (champs réduits) : ```sh curl "https://data.education.gouv.fr/api/explore/v2.1/catalog/datasets/fr-en-annuaire-education/exports/json?select=identifiant_de_l_etablissement,nom_etablissement,type_etablissement,statut_public_prive,libelle_region,libelle_academie,libelle_departement,code_departement,nom_commune,code_postal,adresse_1,telephone,mail,web,etat,nom_circonscription,restauration,hebergement,ulis,segpa,apprentissage,latitude,longitude" -o raw_export.json ``` 2. Recompacter vers `data/etablissements.json` : ```sh php build_data.php ``` ## Fichiers | Fichier | Rôle | |---|---| | `index.html` | Page carte (sidebar + overlay fiche) | | `admin.html` · `admin.js` | Espace d'administration (connexion, modération, arbre, admins, import) | | `style.css` | Mise en forme (carte, fiche, galeries, admin, pages d'auth) | | `app.js` | Chargement (API), filtres en cascade, clustering, popups | | `account.html` · `forgot.html` · `reset.html` | Parcours mot de passe (changer / oublié / réinitialiser) | | `pwpolicy.js` | Politique de mot de passe côté client (validation + jauge) | | `fiche.js` | Fiche plein écran : infos, documents (arbre), photothèque, proposition | | `setup.php` | Crée base + tables, super-admin, arbre d'exemple, importe les établissements | | `api/db.php` | PDO MySQL + JSON + session/auth + import partagé | | `api/auth.php` | Connexion / déconnexion / utilisateur courant | | `api/etablissements.php` | GET : tous les établissements (sert la carte) | | `api/items.php` | Éléments (fichiers/photos/liens) : liste, soumission, validation, suppression | | `api/folders.php` | Arborescence globale (CRUD, super-admin) | | `api/users.php` | Admins de périmètre + leurs communes (super-admin) | | `api/groups.php` | Groupes d'utilisateurs : membres, communes, droit de validation (super-admin) | | `api/comments.php` | Commentaires de fiche (horodatés, visibles par groupe) | | `api/communes.php` | Autocomplétion des communes (affectation de périmètre) | | `api/import.php` | Import de la dernière version de l'opendata (super-admin) | | `api/iris.php` | Population INSEE 2022 d'un IRIS et de sa commune (+ densité par batch) | | `api/effectifs.php` | Historique des effectifs d'un établissement + `?action=latest` (batch) + `?action=commune` (cumul commune) | | `api/ips.php` | Historique de l'IPS (indice de position sociale) d'une école | | `api/population.php` | Historique de la population municipale (INSEE) de la commune | | `api/dpe.php` | DPE rattachés à un établissement (`?uai=`) + `?action=latest` (batch, meilleur match) | | `api/dpe_lib.php` | Logique partagée DPE (création tables, import data-fair, rattachement géo) | | `api/import_dpe.php` | Import DPE + rattachement déclenché depuis l'admin (super-admin) | | `import_dpe.php` | Importe les DPE tertiaires de l'ADEME dans `dpe` (CLI ; appelle `dpe_lib`) | | `match_dpe.php` | Rattache les DPE aux écoles par proximité géographique (CLI ; table `etab_dpe`) | | `import_pop.php` | Importe les populations INSEE 2022 dans `iris_pop` (voir en-tête du script) | | `import_effectifs.php` | Importe les effectifs des écoles (1er degré) dans `effectifs` | | `import_ips.php` | Importe l'IPS (indice de position sociale) des écoles, collèges et lycées dans `ips` | | `import_pop_insee.php` | Importe l'historique des populations INSEE (base séries historiques, communes **et arrondissements**, 1968→2022) dans `pop_commune` | | `import_pop_hist.php` | Variante : historique des populations via CSV Opendatasoft (~2014) → `pop_commune` | | `import_effectifs_2d.php` | Importe les effectifs des collèges et lycées dans `effectifs` | | `import_comc.php` | Renseigne le code INSEE commune (`comc`) des établissements | | `uploads/files/` · `uploads/photos/` | Fichiers et photos stockés sur disque | | `build_data.php` | Convertit `raw_export.json` → `data/etablissements.json` (source du premier import) | | `data/etablissements.json` | Jeu de données importé en base par `setup.php` | | `raw_export.json` | Export brut temporaire (supprimable après build) |