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>
This commit is contained in:
2026-06-03 21:35:41 +02:00
commit 5633969d98
40 changed files with 7242 additions and 0 deletions
+130
View File
@@ -0,0 +1,130 @@
<?php
/**
* Historique de la population municipale (INSEE) de la zone d'un établissement.
* GET ?uai=UAI → { zone: [{annee,pop},…], zone_estimated: bool, ville: [...]|null, ville_nom }
*
* « zone » = commune, ou arrondissement pour Paris/Lyon/Marseille (code INSEE comc de
* l'établissement). Pour un arrondissement, « ville » donne en plus la courbe historique
* de la ville entière. Sinon « ville » est null.
*
* Lyon/Marseille n'ont pas d'historique INSEE par arrondissement : « zone » est alors
* ESTIMÉE (zone_estimated=true) = population de la ville × part de l'arrondissement dans
* les IRIS du recensement 2022 (iris_pop).
*
* Source : pop_commune (cf. import_pop_hist.php), prolongée à 2022 via iris_pop.
*/
declare(strict_types=1);
require __DIR__ . '/db.php';
/**
* Ville entière pour un arrondissement Paris/Lyon/Marseille :
* [ codes pop_commune (historique), codes iris_pop (point 2022), nom ] — ou null.
* Paris n'a pas de code commune unique dans pop_commune : on somme les arrondissements.
*/
function plm_ville(string $comc): ?array
{
$i = (int) $comc;
$codes = static fn(int $a, int $b) => array_map('strval', range($a, $b));
if ($i >= 13201 && $i <= 13216) return [['13055'], $codes(13201, 13216), 'Marseille'];
if ($i >= 69381 && $i <= 69389) return [['69123'], $codes(69381, 69389), 'Lyon'];
if ($i >= 75101 && $i <= 75120) return [$codes(75101, 75120), $codes(75101, 75120), 'Paris'];
return null;
}
/** Somme des populations IRIS (recensement 2022) pour une liste de codes commune/arrondissement. */
function iris_sum(array $codes): float
{
if (!$codes) {
return 0.0;
}
$ph = implode(',', array_fill(0, count($codes), '?'));
$st = db()->prepare("SELECT SUM(pop) FROM iris_pop WHERE com IN ($ph)");
$st->execute(array_map('strval', $codes));
return (float) $st->fetchColumn();
}
/**
* Construit une série [{annee,pop}] : somme de pop_commune sur $histCodes (par année),
* prolongée à 2022 par la somme d'iris_pop sur $irisCodes.
*/
function pop_series(bool $hasHist, bool $hasIris, array $histCodes, array $irisCodes): array
{
$out = [];
$hasYear = [];
if ($hasHist && $histCodes) {
$ph = implode(',', array_fill(0, count($histCodes), '?'));
$st = db()->prepare("SELECT annee, SUM(pop) AS pop FROM pop_commune WHERE comc IN ($ph) GROUP BY annee ORDER BY annee");
$st->execute($histCodes);
foreach ($st as $r) {
$y = (int) $r['annee'];
$out[] = ['annee' => $y, 'pop' => (int) $r['pop']];
$hasYear[$y] = true;
}
}
if ($hasIris && $irisCodes && empty($hasYear[2022])) {
$ph = implode(',', array_fill(0, count($irisCodes), '?'));
$p = db()->prepare("SELECT ROUND(SUM(pop)) FROM iris_pop WHERE com IN ($ph)");
$p->execute($irisCodes);
$v = $p->fetchColumn();
if ($v !== null && $v !== false && (float) $v > 0) {
$out[] = ['annee' => 2022, 'pop' => (int) $v];
}
}
usort($out, static fn($a, $b) => $a['annee'] - $b['annee']);
return $out;
}
try {
$uai = trim((string) ($_GET['uai'] ?? ''));
if ($uai === '') {
json_error('Paramètre uai requis.');
}
$hasHist = (bool) db()->query("SHOW TABLES LIKE 'pop_commune'")->fetchColumn();
$hasIris = (bool) db()->query("SHOW TABLES LIKE 'iris_pop'")->fetchColumn();
if (!$hasHist && !$hasIris) {
json_out(['zone' => [], 'ville' => null, 'ville_nom' => null]);
}
$e = db()->prepare('SELECT comc FROM etablissements WHERE id = ?');
$e->execute([$uai]);
$comc = $e->fetchColumn();
if (!$comc) {
json_out(['zone' => [], 'ville' => null, 'ville_nom' => null]);
}
$comc = (string) $comc;
// Zone = commune ou arrondissement (code de l'établissement directement).
$zone = pop_series($hasHist, $hasIris, [$comc], [$comc]);
// Ville entière (seulement pour un arrondissement PLM).
$ville = null;
$villeNom = null;
$estimated = false;
if ($plm = plm_ville($comc)) {
[$histCodes, $irisCodes, $villeNom] = $plm;
$ville = pop_series($hasHist, $hasIris, $histCodes, $irisCodes);
// Si l'arrondissement n'a pas d'historique propre (Lyon/Marseille), on l'ESTIME :
// population de la ville × part de l'arrondissement dans les IRIS (recensement 2022).
if (count($zone) < 2 && $ville && $hasIris) {
$arrIris = iris_sum([$comc]);
$cityIris = iris_sum($irisCodes);
if ($arrIris > 0 && $cityIris > 0) {
$share = $arrIris / $cityIris;
$zone = array_map(
static fn(array $p) => ['annee' => $p['annee'], 'pop' => (int) round($p['pop'] * $share)],
$ville
);
$estimated = true;
}
}
}
json_out([
'zone' => $zone,
'zone_estimated' => $estimated,
'ville' => $ville,
'ville_nom' => $villeNom,
]);
} catch (Throwable $e) {
json_error('Erreur : ' . $e->getMessage(), 500);
}