5633969d98
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>
297 lines
18 KiB
Markdown
297 lines
18 KiB
Markdown
# 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) |
|