fix(climato): remettre TX en moyenne + fenêtres calendaires jusqu'à aujourd'hui
All checks were successful
Deploy info-canicule / deploy (push) Successful in 1m33s
All checks were successful
Deploy info-canicule / deploy (push) Successful in 1m33s
TX revient en moyenne inter-stations (comme TN/TM) ; la version MAX était une mauvaise piste. Les vues 7j/30j/1an sont désormais construites sur une plage calendaire fixe se terminant à aujourd'hui (heure Paris) : les jours sans données source (lag publication ~1-4j) apparaissent comme points nuls plutôt que de décaler la fenêtre vers le passé. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
759cda8ea9
commit
a1c6002756
2 changed files with 22 additions and 12 deletions
|
|
@ -37,7 +37,7 @@ function buildUrl(dept: string, period: string): string | null {
|
||||||
return `${BASE}/Q_${dept}_${period}_RR-T-Vent.csv.gz`;
|
return `${BASE}/Q_${dept}_${period}_RR-T-Vent.csv.gz`;
|
||||||
}
|
}
|
||||||
|
|
||||||
type Agg = { tnSum: number; tnN: number; txMax: number; txN: number; tmSum: number; tmN: number; rrSum: number; rrN: number; stations: number };
|
type Agg = { tnSum: number; tnN: number; txSum: number; txN: number; tmSum: number; tmN: number; rrSum: number; rrN: number; stations: number };
|
||||||
|
|
||||||
function parseCsvInto(text: string, byDate: Map<string, Agg>): void {
|
function parseCsvInto(text: string, byDate: Map<string, Agg>): void {
|
||||||
const lines = text.split(/\r?\n/);
|
const lines = text.split(/\r?\n/);
|
||||||
|
|
@ -60,21 +60,17 @@ function parseCsvInto(text: string, byDate: Map<string, Agg>): void {
|
||||||
const date = `${raw.slice(0, 4)}-${raw.slice(4, 6)}-${raw.slice(6, 8)}`;
|
const date = `${raw.slice(0, 4)}-${raw.slice(4, 6)}-${raw.slice(6, 8)}`;
|
||||||
let agg = byDate.get(date);
|
let agg = byDate.get(date);
|
||||||
if (!agg) {
|
if (!agg) {
|
||||||
agg = { tnSum: 0, tnN: 0, txMax: -Infinity, txN: 0, tmSum: 0, tmN: 0, rrSum: 0, rrN: 0, stations: 0 };
|
agg = { tnSum: 0, tnN: 0, txSum: 0, txN: 0, tmSum: 0, tmN: 0, rrSum: 0, rrN: 0, stations: 0 };
|
||||||
byDate.set(date, agg);
|
byDate.set(date, agg);
|
||||||
}
|
}
|
||||||
agg.stations++;
|
agg.stations++;
|
||||||
const addAvg = (raw: string | undefined, sum: 'tnSum' | 'tmSum' | 'rrSum', n: 'tnN' | 'tmN' | 'rrN') => {
|
const addAvg = (raw: string | undefined, sum: 'tnSum' | 'txSum' | 'tmSum' | 'rrSum', n: 'tnN' | 'txN' | 'tmN' | 'rrN') => {
|
||||||
if (!raw) return;
|
if (!raw) return;
|
||||||
const v = parseFloat(raw.replace(',', '.'));
|
const v = parseFloat(raw.replace(',', '.'));
|
||||||
if (Number.isFinite(v)) { agg![sum] += v; agg![n]++; }
|
if (Number.isFinite(v)) { agg![sum] += v; agg![n]++; }
|
||||||
};
|
};
|
||||||
// TX: max across stations (not average) — hottest reading in the dept for canicule detection
|
|
||||||
if (idx.tx !== -1 && cols[idx.tx]) {
|
|
||||||
const v = parseFloat(cols[idx.tx].replace(',', '.'));
|
|
||||||
if (Number.isFinite(v)) { if (v > agg.txMax) agg.txMax = v; agg.txN++; }
|
|
||||||
}
|
|
||||||
if (idx.tn !== -1) addAvg(cols[idx.tn], 'tnSum', 'tnN');
|
if (idx.tn !== -1) addAvg(cols[idx.tn], 'tnSum', 'tnN');
|
||||||
|
if (idx.tx !== -1) addAvg(cols[idx.tx], 'txSum', 'txN');
|
||||||
if (idx.tm !== -1) addAvg(cols[idx.tm], 'tmSum', 'tmN');
|
if (idx.tm !== -1) addAvg(cols[idx.tm], 'tmSum', 'tmN');
|
||||||
if (idx.rr !== -1) addAvg(cols[idx.rr], 'rrSum', 'rrN');
|
if (idx.rr !== -1) addAvg(cols[idx.rr], 'rrSum', 'rrN');
|
||||||
}
|
}
|
||||||
|
|
@ -93,7 +89,7 @@ function aggregateDays(byDate: Map<string, Agg>): DayObservation[] {
|
||||||
.map(([date, agg]) => ({
|
.map(([date, agg]) => ({
|
||||||
date,
|
date,
|
||||||
tn: agg.tnN > 0 ? +(agg.tnSum / agg.tnN).toFixed(1) : null,
|
tn: agg.tnN > 0 ? +(agg.tnSum / agg.tnN).toFixed(1) : null,
|
||||||
tx: agg.txN > 0 ? +agg.txMax.toFixed(1) : null,
|
tx: agg.txN > 0 ? +(agg.txSum / agg.txN).toFixed(1) : null,
|
||||||
tm: agg.tmN > 0 ? +(agg.tmSum / agg.tmN).toFixed(1) : null,
|
tm: agg.tmN > 0 ? +(agg.tmSum / agg.tmN).toFixed(1) : null,
|
||||||
rr: agg.rrN > 0 ? +(agg.rrSum / agg.rrN).toFixed(1) : null,
|
rr: agg.rrN > 0 ? +(agg.rrSum / agg.rrN).toFixed(1) : null,
|
||||||
stations: agg.stations,
|
stations: agg.stations,
|
||||||
|
|
|
||||||
|
|
@ -54,9 +54,23 @@ let normales30: Array<{ tx: number | null; tn: number | null }> = [];
|
||||||
let normales365: Array<{ tx: number | null; tn: number | null }> = [];
|
let normales365: Array<{ tx: number | null; tn: number | null }> = [];
|
||||||
let normaleHourly: { tx: number | null; tn: number | null } | null = null;
|
let normaleHourly: { tx: number | null; tn: number | null } | null = null;
|
||||||
|
|
||||||
const last7 = climato?.days?.slice(-7) ?? [];
|
// Build calendar-based windows ending at today (Paris time), filling missing days with nulls.
|
||||||
const last30 = climato?.days?.slice(-30) ?? [];
|
// Source data has a ~1-4 day publication lag; the chart must still end at today.
|
||||||
const last365 = climato?.days?.slice(-365) ?? [];
|
const _todayParis = new Intl.DateTimeFormat('sv', { timeZone: 'Europe/Paris' }).format(new Date());
|
||||||
|
const _climaByDate = new Map((climato?.days ?? []).map((d) => [d.date, d]));
|
||||||
|
function _buildRange(n: number) {
|
||||||
|
const result = [];
|
||||||
|
for (let i = n - 1; i >= 0; i--) {
|
||||||
|
const d = new Date(_todayParis + 'T12:00:00Z'); // noon UTC to avoid DST drift
|
||||||
|
d.setUTCDate(d.getUTCDate() - i);
|
||||||
|
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 });
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
const last7 = climato ? _buildRange(7) : [];
|
||||||
|
const last30 = climato ? _buildRange(30) : [];
|
||||||
|
const last365 = climato ? _buildRange(365) : [];
|
||||||
|
|
||||||
if (!drom) {
|
if (!drom) {
|
||||||
if (climato?.days?.length) {
|
if (climato?.days?.length) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue