info-canicule/CLAUDE.md
Florian 15635ed0e3
Some checks are pending
Deploy info-canicule / deploy (push) Waiting to run
feat(home): onglets Aujourd'hui / Demain pour la carte Vigilance
Permet au visiteur de basculer entre la carte du jour et celle de J+1
(toujours présente dans le bulletin MF, sauf entre minuit et la pub de
6h où l'onglet est désactivé avec explication). Réutilise les helpers
existants (currentEcheance, maxColorByDepartement) — pas de fetch
supplémentaire, J et J1 sont déjà dans le snapshot.

Bonus :
- dayLabel calculé depuis "maintenant Paris" plutôt qu'un sample
  d'alerte → fonctionne en jour calme tout-vert.
- CLAUDE.md : clarifie que MF officiel est canonique et Opendatasoft
  fallback (au lieu de la formulation "le jour où on bascule").

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 00:47:56 +02:00

8.1 KiB
Raw Blame History

CLAUDE.md — info-canicule

Objectif

Site d'utilité publique qui affiche en temps réel la Vigilance Météo France par département

  • les conseils officiels (canicule, orages, vent, pluie, neige, avalanches, vagues-submersion).

Hébergé sur le VPS Nocleus partagé (réseau shared-net, cache Valkey ACL info-canicule).

Stack

  • Astro 5 SSR (output: 'server', adapter @astrojs/node mode standalone)
  • TailwindCSS 3 (via @astrojs/tailwind) + @tailwindcss/typography
  • @astrojs/sitemap (statiques) + src/pages/sitemap-departements.xml.ts (les 96 dépts dynamiques)
  • @sentry/astro opt-in via SENTRY_DSN (cible GlitchTip, sample 0.1, source maps non uploadées). Si DSN absent, intégration omise.
  • ioredis pour le cache Valkey
  • TypeScript strict
  • Pas de DB Postgres (toutes les données vivent en cache Valkey, refresh à la demande)
  • Tests Playwright (tests/, pnpm test:e2e / test:e2e:local)
  • OG image générée au build par scripts/build-og-image.mjs (sharp, devDep)

Sources de données

Quatre flux indépendants, tous cachés en Valkey :

  • Vigilance (src/lib/vigilance.ts) — API officielle Météo France DPVigilance/v1/cartevigilance/encours (source canonique, publication directe, couvre métropole + côtes). Auth via METEOFRANCE_API_KEY. Fallback automatique sur Opendatasoft weatherref-france-vigilance-meteo-departement (Licence Ouverte 2.0, no-auth) si MF échoue, puis sur :last-good (30 j). Override possible via VIGILANCE_PROVIDER=opendatasoft|meteofrance pour debug.
  • Observations horaires SYNOP (src/lib/observations.ts) — API Météo France, station SYNOP la plus proche du centroïde du dept, pré-calculé dans src/data/stations-synop.json. Alimente l'onglet « 24 h » sur les pages dept.
  • Climato quotidienne (src/lib/climato.ts) — fichiers CSV gzippés data.gouv.fr/meteofrance/.../QUOT (latest-2025-2026 pour l'année courante, previous-1950-2024 pour l'historique long). 365 jours glissants par dept. Cold-fetch optimisé : on skippe previous-1950-2024 si latest suffit (cf. commit ac46637).
  • Normales 1991-2020 (src/lib/normales.ts) — JSON statique src/data/normales.json (généré par scripts/build-normales.mjs), indexé par dept × day-of-year (1..366), lissage 7 jours.

Schéma cache

Clé Valkey TTL (frais → hard) Contenu
vigilance:snapshot VIGILANCE_CACHE_TTL (défaut 900s) → ×6 VigilanceSnapshot (fetchedAt + productDatetime + alerts[])
vigilance:snapshot:fallback idem Snapshot Opendatasoft de secours si provider principal échoue
vigilance:snapshot:last-good 30 jours Dernier snapshot connu (filet de sécurité quand tous providers KO)
mf:hourly:<dept>:<hours> 30 min → ×6 HourlySeries (obs SYNOP sur fenêtre glissante)
climato:<dept> 24h → ×6 ClimatoSeries (365 jours)

Le prefix info-canicule: est injecté par ioredis (keyPrefix), donc la clé réelle côté Valkey est info-canicule:<clé> (l'ancien doc parlait d'un double prefix — c'était une erreur). Compatible avec l'ACL ~info-canicule:* côté VPS.

SWR : cacheOrFetch stocke {v, fu} (valeur + freshUntil). Au-delà de fu mais avant l'expiration Valkey (= ttl × 6), la valeur est servie stale et un refresh est déclenché en background (lock in-process pour éviter le stampede). Un blip MF de ~25 min reste invisible côté visiteur. Voir src/lib/cache.ts.

last-good : à chaque fetch réussi, le snapshot Vigilance est aussi persisté en :last-good (TTL 30j). Si MF + Opendatasoft échouent en même temps et que :snapshot/:fallback sont expirés, on sert :last-good plutôt que de planter la page. Le visiteur voit la date du bulletin dans l'UI (déjà affichée) et constate l'ancienneté.

Pages exposées

  • / — home + carte/grille des dépts
  • /departement/[code] — fiche dept avec onglets Vigilance / Observations SYNOP 24h / Climato 365j vs normales
  • /embed/ + /embed/dept/[code] — versions embarquables (iframe-friendly) pour intégration tierce
  • /conseils/ + /conseils/registre-canicule — fiches statiques
  • /api/health — JSON {status, cache, vigilance: {productDatetime, ageSeconds}}. Usage : UptimeRobot + cron HC.io de fraîcheur. Pas de CORS, Cache-Control: no-store, renvoie 503 si Valkey KO.
  • /sitemap-index.xml + /sitemap-departements.xml (les 96 dépts filtrés hors @astrojs/sitemap standard).

Cache HTTP (en plus du cache Valkey)

Home et pages dept renvoient Cache-Control: ... must-revalidate (cf. commit e72f25b) pour autoriser le revalidation des CDN/navigateurs tout en bornant la fraîcheur. À garder cohérent avec le TTL Valkey.

Pièges connus / à surveiller

  • product_datetime change 2× par jour (~06h et 16h Paris). Tant que Météo France n'a pas publié le suivant, le snapshot renvoie les valeurs J→J1 du bulletin courant. Inutile de poll plus vite que 15 min. Les deux bulletins contiennent J ET J1 — l'onglet "Demain" sur la home est donc toujours disponible quand on est dans la fenêtre du bulletin (sauf entre minuit et la pub de 6h, où currentEcheance renvoie J1 et il n'y a plus de J2 dispo).
  • Echéance "J" = aujourd'hui, "J1" = demain. Ne pas confondre avec un horizon en heures.
  • phenomenon_id : 1=vent, 2=pluie, 3=orages, 5=neige/verglas, 6=canicule, 8=avalanches, 9=vagues-submersion. Pas de 4 ni 7.
  • Andorre (domain_id = 99) est inclus dans le flux Vigilance mais n'est pas un département français. Mappé comme 99 — Andorre (zone Vigilance) côté front pour ne pas l'écarter silencieusement.
  • Corse : codes 2A et 2B, pas 20. Mais pour la climato previous-1950-2024, le fichier source est encore sous code 20 (split 2A/2B post-1976) — buildUrl() dans climato.ts gère ce cas.
  • SYNOP renvoie les températures en Kelvin. observations.ts les convertit via K2C(). Piège classique si on ajoute un nouveau champ température.
  • DROM : pas (encore) inclus dans le mapping departements.ts côté front. Vigilance Métropole only pour le MVP. Pour ajouter Outre-mer : étendre DEPARTEMENTS + tester si domain_id correspond aux codes 971-978.
  • Conseils par phénomène (advice.ts) : texte curated depuis sante.gouv.fr et meteofrance.fr. À relire / actualiser périodiquement (au moins 1× par an).
  • Normales 1991-2020 : fichier src/data/normales.json committé. Régénération via scripts/build-normales.mjs quand la décennie de référence Météo France change (~tous les 10 ans, prochain ~2031).
  • Cache miss au boot : si Valkey est down, cacheOrFetch log un warning mais re-fetch à chaque requête — pas de fallback persistant. Acceptable pour un service stateless, mais surveiller la latence Opendatasoft.

Déploiement

Pattern Reteno : push main → CI Forgejo SSH au VPS → git fetch && reset --hard && make env && docker compose up -d --build --wait.

Le .env.tmpl est commit, le .env réel est matérialisé par make env (pass-cli, vault Infra).

Météo France API — rotation API Key

  • Vault item : Infra/Météo France API, field api_key (hidden) + created_at + expires_at.
  • Le portail (portail-api.meteofrance.fr) propose une API Key longue durée (durée choisie à la création, max ~10 ans). Pas de flow OAuth2 client_credentials dispo gratuitement → on n'utilise QUE l'API Key, pas de refresh automatique.
  • Code : src/lib/meteofrance-auth.ts — header apikey: ${METEOFRANCE_API_KEY} (PAS Authorization: Bearer, qui est le format des tokens OAuth2 courts du portail).
  • Rotation : avant l'expiration notée dans le vault, regénérer côté portail, copier dans le vault, make env côté VPS, docker compose up -d --force-recreate app.
  • Si la clé expire / est révoquée : l'onglet « Observations 24 h » (SYNOP) sur les pages dept ne s'affiche plus (skip silencieux), pas d'impact sur Vigilance, Climato ni le reste du site. Erreur loggée côté serveur uniquement.
  • Si on veut être alerté : prévoir un cron HC.io qui curl /api/vigilance non, qui curl un endpoint test MF (à coder). Pour l'instant : se reposer sur la date expires_at du vault item.