Files
ecoles/README.md
T
Arnaud 5633969d98 Version initiale — carte des établissements scolaires
Carte Leaflet (annuaire Éducation nationale) + API PHP/MySQL :
fiche établissement (effectifs, IPS, population INSEE, DPE ADEME),
GED documents/photos, commentaires par groupe, gestion utilisateurs
et politique de mots de passe. Imports opendata (effectifs, IPS,
population, DPE, IRIS).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 21:35:41 +02:00

297 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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*
(<https://www.insee.fr/fr/statistiques/fichier/8582555/base-cc-serie-historique-2022_csv.zip>),
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) |