#!/usr/bin/env node // Calcule les normales mensuelles TN / TX par département sur la période WMO 1991-2020 // à partir des données quotidiennes Météo France (Q__previous-1950-2024_RR-T-Vent.csv.gz). // // Output : src/data/normales.json // { "01": [{ month:1, tn:-1.5, tx:5.2, tnStd:3.1, txStd:4.2, n:1234 }, ..., 12], "02": [...], ... } // // Lancé manuellement (très long : 96 × ~4 MB download + parse). // À relancer une fois par an, ou jamais — les normales 1991-2020 restent la référence WMO standard. import { gunzipSync } from 'node:zlib'; import { writeFileSync, mkdirSync } from 'node:fs'; import { dirname, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; const __dirname = dirname(fileURLToPath(import.meta.url)); const OUT = resolve(__dirname, '../src/data/normales.json'); const BASE = 'https://object.files.data.gouv.fr/meteofrance/data/synchro_ftp/BASE/QUOT'; const PERIOD_START = 1991; const PERIOD_END = 2020; const DEPTS = [ '01','02','03','04','05','06','07','08','09','10', '11','12','13','14','15','16','17','18','19','21', '22','23','24','25','26','27','28','29','2A','2B', '30','31','32','33','34','35','36','37','38','39', '40','41','42','43','44','45','46','47','48','49', '50','51','52','53','54','55','56','57','58','59', '60','61','62','63','64','65','66','67','68','69', '70','71','72','73','74','75','76','77','78','79', '80','81','82','83','84','85','86','87','88','89', '90','91','92','93','94','95', ]; async function fetchAndParse(dept) { // Météo France utilise "20" pour la Corse historique (avant split 2A/2B en 1976). // On affecte la même normale à 2A et 2B (climat sensiblement homogène à l'échelle mensuelle). const fileCode = (dept === '2A' || dept === '2B') ? '20' : dept; const url = `${BASE}/Q_${fileCode}_previous-1950-2024_RR-T-Vent.csv.gz`; const res = await fetch(url); if (!res.ok) throw new Error(`HTTP ${res.status}`); const buf = Buffer.from(await res.arrayBuffer()); const text = gunzipSync(buf).toString('utf-8'); const lines = text.split('\n'); if (lines.length < 2) return null; const header = lines[0].split(';'); const idxDate = header.indexOf('AAAAMMJJ'); const idxTN = header.indexOf('TN'); const idxTX = header.indexOf('TX'); if (idxDate === -1) return null; const acc = Array.from({ length: 12 }, () => ({ tnSum: 0, tnSquares: 0, tnN: 0, txSum: 0, txSquares: 0, txN: 0, })); for (let i = 1; i < lines.length; i++) { const line = lines[i]; if (!line) continue; const cols = line.split(';'); const raw = cols[idxDate]; if (!raw || raw.length !== 8) continue; const year = parseInt(raw.slice(0, 4), 10); if (year < PERIOD_START || year > PERIOD_END) continue; const m = parseInt(raw.slice(4, 6), 10) - 1; if (m < 0 || m > 11) continue; if (idxTN !== -1 && cols[idxTN]) { const v = parseFloat(cols[idxTN].replace(',', '.')); if (Number.isFinite(v)) { acc[m].tnSum += v; acc[m].tnSquares += v * v; acc[m].tnN++; } } if (idxTX !== -1 && cols[idxTX]) { const v = parseFloat(cols[idxTX].replace(',', '.')); if (Number.isFinite(v)) { acc[m].txSum += v; acc[m].txSquares += v * v; acc[m].txN++; } } } return acc.map((a, m) => { const tnMean = a.tnN > 0 ? a.tnSum / a.tnN : null; const txMean = a.txN > 0 ? a.txSum / a.txN : null; const tnVar = a.tnN > 1 && tnMean !== null ? (a.tnSquares - a.tnN * tnMean * tnMean) / (a.tnN - 1) : 0; const txVar = a.txN > 1 && txMean !== null ? (a.txSquares - a.txN * txMean * txMean) / (a.txN - 1) : 0; return { month: m + 1, tn: tnMean !== null ? +tnMean.toFixed(2) : null, tx: txMean !== null ? +txMean.toFixed(2) : null, tnStd: +Math.sqrt(Math.max(0, tnVar)).toFixed(2), txStd: +Math.sqrt(Math.max(0, txVar)).toFixed(2), n: Math.min(a.tnN, a.txN), }; }); } const start = Date.now(); const all = {}; let done = 0; for (const dept of DEPTS) { const t0 = Date.now(); process.stdout.write(`[${++done}/${DEPTS.length}] ${dept}... `); try { const r = await fetchAndParse(dept); all[dept] = r; console.log(`ok ${(Date.now() - t0) / 1000 | 0}s`); } catch (e) { console.log(`FAIL ${e.message}`); all[dept] = null; } } mkdirSync(dirname(OUT), { recursive: true }); writeFileSync(OUT, JSON.stringify(all)); const dur = ((Date.now() - start) / 1000 / 60).toFixed(1); const size = (JSON.stringify(all).length / 1024).toFixed(1); console.log(`\nWrote ${OUT} (${size} KB) in ${dur} min`);