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
+174
View File
@@ -0,0 +1,174 @@
<?php
/**
* Import de l'historique des populations légales communales — source officielle INSEE.
* Base « séries historiques » du recensement (Comparateur de territoires) :
* https://www.insee.fr/fr/statistiques/fichier/8582555/base-cc-serie-historique-2022_csv.zip
*
* Population municipale à 9 recensements : 1968, 1975, 1982, 1990, 1999, 2006, 2011,
* 2016, 2022 (colonnes D68_POP … D99_POP puis P06_POP … P22_POP). Cette base est au
* niveau commune ET arrondissement municipal : elle contient donc le VRAI historique
* par arrondissement pour Paris (75101…), Lyon (69381…) et Marseille (13201…), ainsi
* que les totaux communes (75056 / 69123 / 13055).
*
* Remplit la table pop_commune (comc, annee, pop). Lit le CSV dans le zip sans
* l'extension PHP « zip » (mini-lecteur ZIP + gzinflate).
*
* Usage : php import_pop_insee.php
*/
declare(strict_types=1);
require __DIR__ . '/api/db.php';
ini_set('memory_limit', '1024M');
const INSEE_SERIE_URL = 'https://www.insee.fr/fr/statistiques/fichier/8582555/base-cc-serie-historique-2022_csv.zip';
const INSEE_CSV_NAME = 'base-cc-serie-historique-2022.CSV';
// Colonne de population → année du recensement.
const POP_COLS = [
'P22_POP' => 2022, 'P16_POP' => 2016, 'P11_POP' => 2011, 'P06_POP' => 2006,
'D99_POP' => 1999, 'D90_POP' => 1990, 'D82_POP' => 1982, 'D75_POP' => 1975, 'D68_POP' => 1968,
];
function say(string $m): void { echo $m . "\n"; flush(); }
/**
* Lit une entrée d'un fichier ZIP (méthodes « stored » et « deflate ») sans ext/zip.
* Renvoie le contenu décompressé, ou null si absente.
*/
function zip_entry(string $path, string $name): ?string
{
$fh = fopen($path, 'rb');
if (!$fh) {
return null;
}
$size = filesize($path);
$look = min($size, 22 + 65536);
fseek($fh, $size - $look);
$buf = fread($fh, $look);
$eo = strrpos($buf, "PK\x05\x06");
if ($eo === false) { fclose($fh); return null; }
$eocd = unpack('Vsig/vdisk/vcddisk/ventries/vtotal/Vcdsize/Vcdoffset/vcomlen', substr($buf, $eo, 22));
fseek($fh, $eocd['cdoffset']);
$cd = fread($fh, $eocd['cdsize']);
$off = 0;
$found = null;
$n = strlen($cd);
while ($off + 46 <= $n && substr($cd, $off, 4) === "PK\x01\x02") {
$h = unpack('vver/vverneed/vflags/vmethod/vmtime/vmdate/Vcrc/Vcsize/Vusize/vnlen/velen/vclen/vdisk/viattr/Veattr/Vloffset', substr($cd, $off + 4, 42));
$fname = substr($cd, $off + 46, $h['nlen']);
if ($fname === $name) { $found = $h; break; }
$off += 46 + $h['nlen'] + $h['elen'] + $h['clen'];
}
if (!$found) { fclose($fh); return null; }
fseek($fh, $found['loffset']);
$lh = unpack('Vsig/vver/vflags/vmethod/vmtime/vmdate/Vcrc/Vcsize/Vusize/vnlen/velen', fread($fh, 30));
fseek($fh, $found['loffset'] + 30 + $lh['nlen'] + $lh['elen']);
$comp = fread($fh, $found['csize']);
fclose($fh);
if ($found['method'] === 0) {
return $comp;
}
if ($found['method'] === 8) {
return gzinflate($comp);
}
return null;
}
try {
$pdo = db();
$pdo->exec(
'CREATE TABLE IF NOT EXISTS pop_commune (
comc VARCHAR(5) NOT NULL,
annee SMALLINT NOT NULL,
pop INT NOT NULL DEFAULT 0,
PRIMARY KEY (comc, annee),
INDEX idx_comc (comc)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4'
);
$zipPath = __DIR__ . '/data/insee/serie_hist.zip';
if (!is_file($zipPath)) {
@mkdir(dirname($zipPath), 0777, true);
say('… Téléchargement de la base INSEE (~6,7 Mo) …');
$fp = fopen($zipPath, 'w');
$ch = curl_init(INSEE_SERIE_URL);
curl_setopt_array($ch, [
CURLOPT_FILE => $fp, CURLOPT_FOLLOWLOCATION => true, CURLOPT_TIMEOUT => 600,
CURLOPT_USERAGENT => 'ecoles-import/1.0',
]);
$ok = curl_exec($ch);
$err = curl_error($ch);
curl_close($ch);
fclose($fp);
if (!$ok || filesize($zipPath) < 100000) {
@unlink($zipPath);
throw new RuntimeException('Téléchargement échoué : ' . $err);
}
}
say('… Lecture du CSV …');
$csv = zip_entry($zipPath, INSEE_CSV_NAME);
if ($csv === null || $csv === '') {
throw new RuntimeException('CSV introuvable dans le zip : ' . INSEE_CSV_NAME);
}
$lines = preg_split('/\r\n|\n|\r/', $csv);
$header = str_getcsv(array_shift($lines), ';');
$header[0] = preg_replace('/^\xEF\xBB\xBF/', '', (string) $header[0]); // BOM
$iCode = array_search('CODGEO', $header, true);
if ($iCode === false) {
throw new RuntimeException('Colonne CODGEO introuvable.');
}
$colIdx = [];
foreach (POP_COLS as $col => $annee) {
$k = array_search($col, $header, true);
if ($k !== false) {
$colIdx[$k] = $annee;
}
}
if (!$colIdx) {
throw new RuntimeException('Aucune colonne de population trouvée.');
}
$ins = $pdo->prepare('REPLACE INTO pop_commune (comc, annee, pop) VALUES (?, ?, ?)');
$pdo->exec('TRUNCATE TABLE pop_commune');
$pdo->beginTransaction();
$n = 0;
foreach ($lines as $line) {
if ($line === '') {
continue;
}
$row = str_getcsv($line, ';');
$code = trim((string) ($row[$iCode] ?? ''));
if (!preg_match('/^[0-9AB]\w{4}$/', $code)) {
continue;
}
foreach ($colIdx as $k => $annee) {
$v = $row[$k] ?? '';
if ($v === '' || !is_numeric(str_replace([' ', ','], ['', '.'], $v))) {
continue;
}
$ins->execute([$code, $annee, (int) round((float) str_replace([' ', ','], ['', '.'], $v))]);
if (++$n % 10000 === 0) {
$pdo->commit();
$pdo->beginTransaction();
}
}
}
$pdo->commit();
$communes = (int) $pdo->query('SELECT COUNT(DISTINCT comc) FROM pop_commune')->fetchColumn();
$arr = (int) $pdo->query("SELECT COUNT(DISTINCT comc) FROM pop_commune WHERE comc BETWEEN '13201' AND '13216' OR comc BETWEEN '69381' AND '69389' OR comc LIKE '751__'")->fetchColumn();
say("✔ pop_commune : $n lignes ($communes codes dont $arr arrondissements PLM, INSEE séries historiques 1968→2022).");
say('🎉 Terminé.');
} catch (Throwable $e) {
if (isset($pdo) && $pdo->inTransaction()) {
$pdo->rollBack();
}
http_response_code(500);
say('!! ERREUR : ' . $e->getMessage());
exit(1);
}