info-canicule/src/components/AnomalyBadge.astro
Florian c2b489f9b9
Some checks are pending
Deploy info-canicule / deploy (push) Waiting to run
feat: normales saisonnières 1991-2020 + AnomalyBadge
- scripts/build-normales.mjs : agrégation TN/TX mensuelles par dept sur
  la période WMO 1991-2020 depuis les fichiers Q_<DEPT>_previous-1950-2024.
  Output src/data/normales.json (78 KB, committé). Run annuel max.
- Corse : Météo France utilise le code historique "20" (avant split 2A/2B
  en 1976), donc 2A et 2B partagent la même normale issue de Q_20_*.
- src/lib/normales.ts : computeAnomaly() qui moyenne TX/TN des 7 derniers
  jours, compare à la normale du mois, calcule l'écart en °C et en σ,
  catégorise (normal / warm / cool / anomaly_warm / anomaly_cool /
  extreme_warm / extreme_cool / unknown).
- src/components/AnomalyBadge.astro : badge coloré (vert/jaune/orange/rouge)
  visible sur /departement/[code] juste au-dessus du graphe T°.
  Différencie "il fait chaud" de "il fait anormalement chaud pour ce mois".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 21:34:04 +02:00

80 lines
2.5 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
import type { Anomaly } from '../lib/normales';
interface Props {
anomaly: Anomaly;
}
const { anomaly } = Astro.props;
const CATEGORY_LABEL: Record<Anomaly['txCategory'], { label: string; cls: string; icon: string }> = {
normal: {
label: 'Températures dans la normale saisonnière',
cls: 'border-slate-200 bg-slate-50 text-slate-700',
icon: '🌡️',
},
warm: {
label: 'Légèrement au-dessus de la normale',
cls: 'border-yellow-200 bg-yellow-50 text-yellow-900',
icon: '↗',
},
cool: {
label: 'Légèrement en dessous de la normale',
cls: 'border-blue-200 bg-blue-50 text-blue-900',
icon: '↘',
},
anomaly_warm: {
label: 'Anormalement chaud',
cls: 'border-orange-300 bg-orange-50 text-orange-900',
icon: '🔥',
},
anomaly_cool: {
label: 'Anormalement frais',
cls: 'border-blue-300 bg-blue-100 text-blue-900',
icon: '❄',
},
extreme_warm: {
label: 'Extrêmement chaud (déviation extrême)',
cls: 'border-red-300 bg-red-50 text-red-900 font-semibold',
icon: '🚨',
},
extreme_cool: {
label: 'Extrêmement frais (déviation extrême)',
cls: 'border-blue-400 bg-blue-200 text-blue-900 font-semibold',
icon: '🚨',
},
unknown: {
label: 'Normale non disponible pour ce mois',
cls: 'border-slate-200 bg-slate-50 text-slate-500',
icon: '?',
},
};
const cat = CATEGORY_LABEL[anomaly.txCategory];
const signDiff = (anomaly.diffTx ?? 0) > 0 ? '+' : '';
---
<div class:list={['rounded-lg border p-4', cat.cls]}>
<div class="flex items-center gap-2 text-sm font-semibold">
<span aria-hidden="true">{cat.icon}</span>
<span>{cat.label}</span>
</div>
{anomaly.diffTx !== null && anomaly.normaleTx !== null && (
<p class="mt-2 text-sm">
<strong>T° max moyenne {anomaly.windowDays} derniers jours :</strong>{' '}
<span class="font-mono">{anomaly.meanTx}°C</span>
<span class="text-slate-500"> · normale du mois (1991-2020) : <span class="font-mono">{anomaly.normaleTx}°C</span></span>
</p>
<p class="mt-1 text-sm">
<strong>Écart :</strong>{' '}
<span class="font-mono">{signDiff}{anomaly.diffTx}°C</span>
{anomaly.sigmaTx !== null && (
<span class="text-xs text-slate-500"> ({anomaly.sigmaTx > 0 ? '+' : ''}{anomaly.sigmaTx}σ)</span>
)}
</p>
)}
<p class="mt-2 text-xs text-slate-500">
Comparaison sur la période de référence WMO 1991-2020. σ = écart-type, mesure de l'amplitude
historique du mois ; au-delà de 2σ l'événement est statistiquement rare.
</p>
</div>