fix(vigilance): écheance dynamique selon l'heure courante
Some checks are pending
Deploy info-canicule / deploy (push) Waiting to run
Some checks are pending
Deploy info-canicule / deploy (push) Waiting to run
Le bulletin MF est publié 2× par jour (~6h et ~16h Paris). Pendant la nuit (de minuit au prochain bulletin ~6h), on est techniquement dans le J+1 du bulletin courant, qui correspond pour l'utilisateur à "aujourd'hui réel". Avant : on affichait toujours J → la nuit, la carte montrait les alertes "d'hier" alors que l'utilisateur cherche "celles d'aujourd'hui réel". Symptôme : Florian voit du orange sur vigilance.meteofrance.fr (qui prend la bonne écheance) mais pas chez nous (qui restait collé sur J). Fix : - currentEcheance(snapshot) compare now au end_time du J : si dépassé, J1. - index.astro + departement.astro utilisent cette écheance pour "aujourd'hui". Page dept : "demain" = J1 si on est sur J, vide sinon. - Labels rendus dynamiques : "Niveau par département — mardi 26 mai" au lieu de "(aujourd'hui)" générique. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
27441cdbb8
commit
0b418bd031
3 changed files with 69 additions and 12 deletions
|
|
@ -190,6 +190,27 @@ export async function getVigilanceSnapshot(): Promise<VigilanceSnapshot> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Détermine quelle echeance du bulletin couvre l'instant présent.
|
||||||
|
*
|
||||||
|
* Le bulletin Météo France publié à 16h Paris reste valide jusqu'au prochain
|
||||||
|
* bulletin (~6h Paris le lendemain). Pendant la nuit (de minuit à 6h), on se
|
||||||
|
* retrouve techniquement dans le J+1 du bulletin, qui correspond pour
|
||||||
|
* l'utilisateur à "aujourd'hui réel".
|
||||||
|
*
|
||||||
|
* Logique : si l'instant présent est avant la fin du J, on utilise J ; sinon J1.
|
||||||
|
*/
|
||||||
|
export function currentEcheance(snapshot: VigilanceSnapshot): 'J' | 'J1' {
|
||||||
|
const now = new Date().toISOString();
|
||||||
|
const jEnd = snapshot.alerts
|
||||||
|
.filter((a) => a.echeance === 'J')
|
||||||
|
.map((a) => a.endTime)
|
||||||
|
.sort()
|
||||||
|
.slice(-1)[0];
|
||||||
|
if (jEnd && now < jEnd) return 'J';
|
||||||
|
return 'J1';
|
||||||
|
}
|
||||||
|
|
||||||
export function maxColorByDepartement(snapshot: VigilanceSnapshot, echeance: 'J' | 'J1' = 'J'): Map<string, ColorId> {
|
export function maxColorByDepartement(snapshot: VigilanceSnapshot, echeance: 'J' | 'J1' = 'J'): Map<string, ColorId> {
|
||||||
const map = new Map<string, ColorId>();
|
const map = new Map<string, ColorId>();
|
||||||
for (const a of snapshot.alerts) {
|
for (const a of snapshot.alerts) {
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
import Base from '../../layouts/Base.astro';
|
import Base from '../../layouts/Base.astro';
|
||||||
import VigilanceChip from '../../components/VigilanceChip.astro';
|
import VigilanceChip from '../../components/VigilanceChip.astro';
|
||||||
import VigilanceLegend from '../../components/VigilanceLegend.astro';
|
import VigilanceLegend from '../../components/VigilanceLegend.astro';
|
||||||
import { getVigilanceSnapshot, alertsForDepartement } from '../../lib/vigilance';
|
import { getVigilanceSnapshot, alertsForDepartement, currentEcheance } from '../../lib/vigilance';
|
||||||
import { getDepartement, isDrom } from '../../lib/departements';
|
import { getDepartement, isDrom } from '../../lib/departements';
|
||||||
import { PHENOMENA, COLOR_LABEL } from '../../lib/phenomena';
|
import { PHENOMENA, COLOR_LABEL } from '../../lib/phenomena';
|
||||||
import { ADVICE, EMERGENCY_NUMBERS } from '../../lib/advice';
|
import { ADVICE, EMERGENCY_NUMBERS } from '../../lib/advice';
|
||||||
|
|
@ -73,10 +73,19 @@ if (!drom) {
|
||||||
|
|
||||||
const stationLabel = hourly ? `${hourly.stationName} (${hourly.distKm} km)` : null;
|
const stationLabel = hourly ? `${hourly.stationName} (${hourly.distKm} km)` : null;
|
||||||
|
|
||||||
const today = snapshot ? alertsForDepartement(snapshot, dept.code, 'J') : [];
|
// "Aujourd'hui" = écheance courante (J ou J1 selon l'heure)
|
||||||
const tomorrow = snapshot ? alertsForDepartement(snapshot, dept.code, 'J1') : [];
|
// "Demain" = J1 si on est encore sur J, sinon pas dispo (bulletin n'a pas J2)
|
||||||
|
const ech = snapshot ? currentEcheance(snapshot) : 'J';
|
||||||
|
const today = snapshot ? alertsForDepartement(snapshot, dept.code, ech) : [];
|
||||||
|
const tomorrow = (snapshot && ech === 'J') ? alertsForDepartement(snapshot, dept.code, 'J1') : [];
|
||||||
const highest = today[0];
|
const highest = today[0];
|
||||||
const adviceFor = highest && ADVICE[highest.phenomenonId];
|
const adviceFor = highest && ADVICE[highest.phenomenonId];
|
||||||
|
|
||||||
|
const labelDate = (iso: string) => new Date(iso).toLocaleDateString('fr-FR', {
|
||||||
|
weekday: 'long', day: 'numeric', month: 'long', timeZone: 'Europe/Paris',
|
||||||
|
});
|
||||||
|
const todayLabel = today[0] ? labelDate(today[0].beginTime) : '';
|
||||||
|
const tomorrowLabel = tomorrow[0] ? labelDate(tomorrow[0].beginTime) : '';
|
||||||
---
|
---
|
||||||
|
|
||||||
<Base
|
<Base
|
||||||
|
|
@ -117,7 +126,9 @@ const adviceFor = highest && ADVICE[highest.phenomenonId];
|
||||||
{
|
{
|
||||||
!drom && !error && today.length === 0 && (
|
!drom && !error && today.length === 0 && (
|
||||||
<div class="rounded border border-green-200 bg-green-50 p-4">
|
<div class="rounded border border-green-200 bg-green-50 p-4">
|
||||||
<p class="font-semibold text-green-800">Aucune vigilance particulière aujourd'hui.</p>
|
<p class="font-semibold text-green-800">
|
||||||
|
Aucune vigilance particulière {todayLabel ? `pour ${todayLabel}` : "aujourd'hui"}.
|
||||||
|
</p>
|
||||||
<p class="text-sm text-green-700">Le département est en niveau vert pour tous les phénomènes.</p>
|
<p class="text-sm text-green-700">Le département est en niveau vert pour tous les phénomènes.</p>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
@ -126,7 +137,9 @@ const adviceFor = highest && ADVICE[highest.phenomenonId];
|
||||||
{
|
{
|
||||||
!drom && !error && today.length > 0 && (
|
!drom && !error && today.length > 0 && (
|
||||||
<>
|
<>
|
||||||
<h2 class="mb-3 text-xl font-semibold">Alertes en cours</h2>
|
<h2 class="mb-3 text-xl font-semibold capitalize">
|
||||||
|
Alertes en cours {todayLabel ? `— ${todayLabel}` : ''}
|
||||||
|
</h2>
|
||||||
<ul class="space-y-3">
|
<ul class="space-y-3">
|
||||||
{today.map((a) => {
|
{today.map((a) => {
|
||||||
const phen = PHENOMENA[a.phenomenonId];
|
const phen = PHENOMENA[a.phenomenonId];
|
||||||
|
|
@ -162,7 +175,9 @@ const adviceFor = highest && ADVICE[highest.phenomenonId];
|
||||||
{
|
{
|
||||||
!drom && !error && tomorrow.length > 0 && (
|
!drom && !error && tomorrow.length > 0 && (
|
||||||
<div class="mt-8">
|
<div class="mt-8">
|
||||||
<h2 class="mb-3 text-xl font-semibold">Prévision pour demain</h2>
|
<h2 class="mb-3 text-xl font-semibold capitalize">
|
||||||
|
Prévision {tomorrowLabel ? `— ${tomorrowLabel}` : 'pour demain'}
|
||||||
|
</h2>
|
||||||
<ul class="space-y-2">
|
<ul class="space-y-2">
|
||||||
{tomorrow.map((a) => (
|
{tomorrow.map((a) => (
|
||||||
<li class="flex flex-wrap items-center justify-between gap-3 rounded border border-slate-200 px-3 py-2">
|
<li class="flex flex-wrap items-center justify-between gap-3 rounded border border-slate-200 px-3 py-2">
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import FranceMap from '../components/FranceMap.astro';
|
||||||
import DepartementGrid from '../components/DepartementGrid.astro';
|
import DepartementGrid from '../components/DepartementGrid.astro';
|
||||||
import VigilanceLegend from '../components/VigilanceLegend.astro';
|
import VigilanceLegend from '../components/VigilanceLegend.astro';
|
||||||
import VigilanceChip from '../components/VigilanceChip.astro';
|
import VigilanceChip from '../components/VigilanceChip.astro';
|
||||||
import { getVigilanceSnapshot, maxColorByDepartement, activeAlerts } from '../lib/vigilance';
|
import { getVigilanceSnapshot, maxColorByDepartement, activeAlerts, currentEcheance } from '../lib/vigilance';
|
||||||
import { getDepartement } from '../lib/departements';
|
import { getDepartement } from '../lib/departements';
|
||||||
import type { VigilanceAlert } from '../lib/vigilance';
|
import type { VigilanceAlert } from '../lib/vigilance';
|
||||||
|
|
||||||
|
|
@ -18,14 +18,18 @@ try {
|
||||||
error = (e as Error).message;
|
error = (e as Error).message;
|
||||||
}
|
}
|
||||||
|
|
||||||
const colorsByDept = snapshot ? maxColorByDepartement(snapshot, 'J') : new Map();
|
// Determine l'écheance "aujourd'hui réel" — peut être J ou J1 selon l'heure
|
||||||
const alertsToday = snapshot ? activeAlerts(snapshot, 2) : [];
|
// (entre minuit et la publication du bulletin suivant ~6h, J du bulletin = hier).
|
||||||
|
const ech = snapshot ? currentEcheance(snapshot) : 'J';
|
||||||
|
const colorsByDept = snapshot ? maxColorByDepartement(snapshot, ech) : new Map();
|
||||||
|
const alertsToday = snapshot
|
||||||
|
? snapshot.alerts.filter((a) => a.echeance === ech && a.colorId >= 2)
|
||||||
|
: [];
|
||||||
|
|
||||||
// Group all today's alerts (any level) by department, for tooltip + active alerts list.
|
|
||||||
const alertsByDept = new Map<string, VigilanceAlert[]>();
|
const alertsByDept = new Map<string, VigilanceAlert[]>();
|
||||||
if (snapshot) {
|
if (snapshot) {
|
||||||
for (const a of snapshot.alerts) {
|
for (const a of snapshot.alerts) {
|
||||||
if (a.echeance !== 'J') continue;
|
if (a.echeance !== ech) continue;
|
||||||
if (a.colorId < 2) continue;
|
if (a.colorId < 2) continue;
|
||||||
const list = alertsByDept.get(a.departement) ?? [];
|
const list = alertsByDept.get(a.departement) ?? [];
|
||||||
list.push(a);
|
list.push(a);
|
||||||
|
|
@ -53,6 +57,16 @@ const productDate = snapshot?.productDatetime
|
||||||
timeZone: 'Europe/Paris',
|
timeZone: 'Europe/Paris',
|
||||||
})
|
})
|
||||||
: 'inconnu';
|
: 'inconnu';
|
||||||
|
|
||||||
|
// Date de validité de l'écheance affichée (pour clarifier au visiteur)
|
||||||
|
const dayLabel = (() => {
|
||||||
|
if (!snapshot) return '';
|
||||||
|
const sample = snapshot.alerts.find((a) => a.echeance === ech);
|
||||||
|
if (!sample) return '';
|
||||||
|
return new Date(sample.beginTime).toLocaleDateString('fr-FR', {
|
||||||
|
weekday: 'long', day: 'numeric', month: 'long', timeZone: 'Europe/Paris',
|
||||||
|
});
|
||||||
|
})();
|
||||||
---
|
---
|
||||||
|
|
||||||
<Base>
|
<Base>
|
||||||
|
|
@ -63,7 +77,14 @@ const productDate = snapshot?.productDatetime
|
||||||
</h1>
|
</h1>
|
||||||
<p class="mt-2 max-w-2xl text-slate-600">
|
<p class="mt-2 max-w-2xl text-slate-600">
|
||||||
Carte des alertes Vigilance par département, conseils officiels et numéros d'urgence.
|
Carte des alertes Vigilance par département, conseils officiels et numéros d'urgence.
|
||||||
Bulletin Météo France du <strong>{productDate}</strong> (heure de Paris).
|
{dayLabel && (
|
||||||
|
<>
|
||||||
|
Affichage pour <strong class="capitalize">{dayLabel}</strong>.
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
<p class="mt-1 text-xs text-slate-500">
|
||||||
|
Bulletin Météo France émis le {productDate} (heure de Paris).
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue