fix(climato): combler le lag de publication via SYNOP 48h
All checks were successful
Deploy info-canicule / deploy (push) Successful in 1m33s
All checks were successful
Deploy info-canicule / deploy (push) Successful in 1m33s
Fetcher 48h d'obs horaires (au lieu de 24h) pour avoir le jour J-1 complet. Dériver TX/TN journaliers (max/min des températures horaires) et les injecter dans les fenêtres 7j/30j/1an pour les jours absents de la climato CSV. Le tab "24h" du graphique reste filtré sur les 24 dernières heures. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
a1c6002756
commit
e247005d34
1 changed files with 30 additions and 3 deletions
|
|
@ -30,7 +30,7 @@ const [snapshotR, climatoR, hourlyR] = drom
|
||||||
: ([
|
: ([
|
||||||
getVigilanceSnapshot(),
|
getVigilanceSnapshot(),
|
||||||
getClimatoForDepartement(dept.code),
|
getClimatoForDepartement(dept.code),
|
||||||
getHourlyForDepartement(dept.code, 24),
|
getHourlyForDepartement(dept.code, 48),
|
||||||
] as const);
|
] as const);
|
||||||
|
|
||||||
const settled = await Promise.allSettled([snapshotR, climatoR, hourlyR]);
|
const settled = await Promise.allSettled([snapshotR, climatoR, hourlyR]);
|
||||||
|
|
@ -58,13 +58,35 @@ let normaleHourly: { tx: number | null; tn: number | null } | null = null;
|
||||||
// Source data has a ~1-4 day publication lag; the chart must still end at today.
|
// Source data has a ~1-4 day publication lag; the chart must still end at today.
|
||||||
const _todayParis = new Intl.DateTimeFormat('sv', { timeZone: 'Europe/Paris' }).format(new Date());
|
const _todayParis = new Intl.DateTimeFormat('sv', { timeZone: 'Europe/Paris' }).format(new Date());
|
||||||
const _climaByDate = new Map((climato?.days ?? []).map((d) => [d.date, d]));
|
const _climaByDate = new Map((climato?.days ?? []).map((d) => [d.date, d]));
|
||||||
|
|
||||||
|
// Derive daily TX/TN from the 48h SYNOP window to fill the climato publication gap (typically 1-2 days).
|
||||||
|
// TX = max hourly t, TN = min hourly t — approximation acceptable for gap-filling.
|
||||||
|
const _synopDaily = new Map<string, { tx: number | null; tn: number | null }>();
|
||||||
|
if (hourly) {
|
||||||
|
const _byDate = new Map<string, number[]>();
|
||||||
|
for (const obs of hourly.observations) {
|
||||||
|
if (obs.t === null) continue;
|
||||||
|
const d = new Intl.DateTimeFormat('sv', { timeZone: 'Europe/Paris' }).format(new Date(obs.time));
|
||||||
|
if (!_byDate.has(d)) _byDate.set(d, []);
|
||||||
|
_byDate.get(d)!.push(obs.t);
|
||||||
|
}
|
||||||
|
for (const [date, temps] of _byDate) {
|
||||||
|
_synopDaily.set(date, { tx: +Math.max(...temps).toFixed(1), tn: +Math.min(...temps).toFixed(1) });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function _buildRange(n: number) {
|
function _buildRange(n: number) {
|
||||||
const result = [];
|
const result = [];
|
||||||
for (let i = n - 1; i >= 0; i--) {
|
for (let i = n - 1; i >= 0; i--) {
|
||||||
const d = new Date(_todayParis + 'T12:00:00Z'); // noon UTC to avoid DST drift
|
const d = new Date(_todayParis + 'T12:00:00Z'); // noon UTC to avoid DST drift
|
||||||
d.setUTCDate(d.getUTCDate() - i);
|
d.setUTCDate(d.getUTCDate() - i);
|
||||||
const dateStr = new Intl.DateTimeFormat('sv', { timeZone: 'Europe/Paris' }).format(d);
|
const dateStr = new Intl.DateTimeFormat('sv', { timeZone: 'Europe/Paris' }).format(d);
|
||||||
result.push(_climaByDate.get(dateStr) ?? { date: dateStr, tn: null, tx: null, tm: null, rr: null, stations: 0 });
|
if (_climaByDate.has(dateStr)) {
|
||||||
|
result.push(_climaByDate.get(dateStr)!);
|
||||||
|
} else {
|
||||||
|
const synop = _synopDaily.get(dateStr);
|
||||||
|
result.push({ date: dateStr, tn: synop?.tn ?? null, tx: synop?.tx ?? null, tm: null, rr: null, stations: 0 });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
@ -88,6 +110,11 @@ if (!drom) {
|
||||||
if (normaleHourly) normaleHourly = { tx: normaleHourly.tx, tn: normaleHourly.tn };
|
if (normaleHourly) normaleHourly = { tx: normaleHourly.tx, tn: normaleHourly.tn };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pass only the last 24h slice to the chart (we fetched 48h for synop gap-filling above).
|
||||||
|
const hourlyFor24h = hourly
|
||||||
|
? { ...hourly, observations: hourly.observations.filter((o) => new Date(o.time).getTime() >= Date.now() - 24 * 3600 * 1000) }
|
||||||
|
: null;
|
||||||
|
|
||||||
const stationLabel = hourly ? `${hourly.stationName} (${hourly.distKm} km)` : null;
|
const stationLabel = hourly ? `${hourly.stationName} (${hourly.distKm} km)` : null;
|
||||||
|
|
||||||
const ech = snapshot ? currentEcheance(snapshot) : 'J';
|
const ech = snapshot ? currentEcheance(snapshot) : 'J';
|
||||||
|
|
@ -361,7 +388,7 @@ const topPhen = topAlert ? PHENOMENA[topAlert.phenomenonId] : null;
|
||||||
<div class="ic-card" style="padding: clamp(16px, 3vw, 28px);">
|
<div class="ic-card" style="padding: clamp(16px, 3vw, 28px);">
|
||||||
<h2 style="font-size: clamp(1.2rem, 1rem + 0.8vw, 1.6rem); margin-bottom: 20px;">Températures récentes</h2>
|
<h2 style="font-size: clamp(1.2rem, 1rem + 0.8vw, 1.6rem); margin-bottom: 20px;">Températures récentes</h2>
|
||||||
<TemperatureChartInteractive
|
<TemperatureChartInteractive
|
||||||
hourly={hourly}
|
hourly={hourlyFor24h}
|
||||||
days7={last7}
|
days7={last7}
|
||||||
days30={last30}
|
days30={last30}
|
||||||
days365={last365}
|
days365={last365}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue