- A1: scripts/backup-postgres.sh — pg_dump quotidien compressé → Backblaze B2 (rclone), rétention 30j - A2: StatusController retourne HTTP 503 + healthy/alertReason si dernière observation > 6h - A4: Dockerfile installe tippecanoe, GenerateTilesCommand génère 5 tilesets (obs + h6/12/24/48), Caddyfile sert /tiles/* sans fallback SPA, SargassesMap.jsx passe en sources vector tiles statiques - chore: backend/public/assets/ ajouté au .gitignore (build artifacts) - chore: setup vitest frontend + ImpactScoreServiceTest (session précédente) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
8.2 KiB
8.2 KiB
ROADMAP — Sargasse-Sentry
Checklist permanente d'avancement projet. Mettre à jour au fil des implémentations.
Légende
- À faire
- [~] En cours
- Terminé
Phase 0 — Fondations
Infrastructure & environnement
- Initialisation dépôt Git (bare repo VPS + hook post-receive)
- Configuration Docker (FrankenPHP + PostgreSQL/PostGIS + Redis)
- Configuration Traefik (domaine radarsargasses971.com, SSL Let's Encrypt)
- Variables d'environnement (.env + clés VAPID documentées)
- Initialisation projet Symfony (
symfony new backend) - Installation API Platform
- Initialisation projet React (
npm create vite@latest frontend) - Configuration MapLibre GL JS + react-map-gl
Base de données
- Extension PostGIS activée (dans la migration)
- Migration :
SargassumObservation - Migration :
SargassumForecast - Migration :
DataIngestionJob - Migration :
CoastalPoint - Migration :
ImpactScore - Migration :
UserFeedback - Migration :
PushSubscription - Index spatiaux (GIST) + index BTREE définis
Phase 1 — MVP (Antilles, observations uniquement)
Pipeline d'ingestion
- Cron VPS (
docker exectoutes les 3h) →app:ingest-sentinel - Appel API Sentinel Hub (bandes B04, B08, B11 via evalscript)
- Création
DataIngestionJob(pending → success/failed) - Retry automatique + logging erreurs
- Filtrage nuages via Catalog API (seuil 60%, configurable)
- Calcul AFAI dans evalscript Sentinel Hub (formule complète)
- Rasterisation → raster UINT8 binaire (evalscript cloud-side)
- Vectorisation GDAL (
gdal_polygonize.py) + simplification PostGIS - Persistance
SargassumObservation - Génération vector tiles (Tippecanoe →
.pbf) — différé Phase 2
API Backend
GET /api/spots— liste CoastalPoints (API Platform)GET /api/spots/{id}— détail (API Platform)GET /api/spots/{id}/score— score + niveau + trendGET /api/observations?bbox=&date=&source=— PostGIS ST_IntersectsGET /api/observations/{id}— détail avec geometry GeoJSONGET /api/forecasts— API PlatformPOST /api/feedback— feedback anonyme (IP hachée)
Frontend React
- Carte de base (MapLibre GL JS + fond CARTO dark)
- Affichage observations GeoJSON (bbox dynamique au mouvement)
- Marqueurs CoastalPoints avec sélection
- SpotPanel : Spot Mode complet (score, niveau, distance, trend)
- Bouton feedback terrain (confirmation/infirmation anonyme)
- Build Vite → backend/public/ (SPA servie depuis la racine par Caddy)
Données initiales
- Seed
CoastalPointzone Antilles — 29 points (plages, ports, surf, pêche)
Phase 2 — Dérive & Score
Pipeline — simulation de dérive
- Vent horaire via Open-Meteo (gratuit, sans clé, JSON)
- Échantillonnage PostGIS ST_GeneratePoints (30 points internes)
- Déplacement horaire itératif (modèle Stokes 3% vent)
- Reconstruction ST_ConcaveHull (PostGIS 3.4)
- Génération horizons H+6, H+12, H+24, H+48 avec confiance décroissante
- Persistance
SargassumForecast - Génération vector tiles prédictions (Tippecanoe) — différé
Calcul ImpactScore
- Score par
CoastalPoint: distance(40) + densité(30) + vitesse(20) + tendance(10) - Persistance
ImpactScore - Mise en cache Redis (TTL 3h) via ImpactScoreService
API Backend
GET /api/spots/{id}/score— score courant + horizons H+6/12/24/48GET /api/spots/{id}/score?at={datetime}— score à un instantGET /api/forecasts?bbox=&horizon=— prédictions par zone + horizon (API Platform)
Frontend React
- Slider temporel (Now → +6h → +12h → +24h → +48h)
- Spot Mode complet par horizon (OK / Risque / Impact + confiance)
- Mise à jour dynamique polygones + score sur slider
- Couche forecasts (violet pointillé) vs observations (orange plein)
- Gradients radiaux densité — différé
Phase 3 — Feedback & Alertes
Boucle d'apprentissage
- Bouton "Je confirme présence de sargasses"
POST /api/feedback— persistanceUserFeedbackanonyme- Intégration feedback dans calcul score (bonus +8 pts si présence confirmée < 6h dans rayon 20 km)
Alertes push
- Commande
app:generate-vapid-keys(génération clés VAPID) GET /api/push/vapid-public-key— clé publique VAPIDPOST /api/push/subscribe— enregistrement abonnement pushDELETE /api/push/subscribe— désinscriptionPushNotificationService— envoi WebPush VAPID, nettoyage abonnements expirés- Déclenchement alertes si score ≥ 70 (HIGH_SCORE_THRESHOLD)
- Service worker (
sw.js) — réception push + ouverture app au clic usePushSubscriptionhook React — subscribe/unsubscribe lifecycle- Bouton "Activer les alertes" dans SpotPanel
Transversal (continu)
- Rate limiting endpoints publics (api_read 120/min, api_feedback 10/min, api_push 5/min)
- Fallback Sentinel-2 → Sentinel-3 OLCI (mode
autodans IngestSentinelCommand) - Routing SPA corrigé (Caddyfile
try_files+ build versbackend/public/) - Vue mobile (SpotPanel bottom-sheet + TimelineSlider compact)
- Post-receive hook complet (composer + npm build + migrations + cache + docker up)
- Monitoring pipeline (alertes si ingestion échoue > N fois)
- Logs structurés (Monolog JSON handler)
- Configuration Cloudflare free tier (cache tiles statiques)
- Tests fonctionnels pipeline (ingestion → score)
Phase A — Blindage opérationnel (priorité absolue)
Pré-requis à tout le reste. Un crash VPS sans backup = perte totale des données.
- A1 Backup PostgreSQL automatisé —
pg_dumpquotidien compressé vers stockage externe (Backblaze B2), rétention 30 jours - A2 Monitoring pipeline — healthcheck externe (UptimeRobot) sur
/api/status, alerte email si ingestion absente > 6h - A3 CI/CD via Gitea Actions —
phpstan + phpunit + vitestbloquants sur chaque push ; deploy déclenché par action SSH uniquement si CI ✅ (Gitea installé sur VPS, runner à configurer) - A4 Vector tiles Tippecanoe — génération
.pbfaprès chaque ingestion, served statiquement par Caddy ; remplacement des sources GeoJSON par sourcesvectorcôté frontend
Phase B — Crédibilité produit
Transforme le prototype en produit présentable à un partenaire.
- B1 Analytics — Plausible (self-hosted sur VPS) ou Umami ; métriques sessions, pages vues, îles consultées
- B2 Onboarding — modale premier lancement : légende polygones orange/violet, explication score, CTA "Sélectionnez un point côtier"
- B3 Légende interactive carte — compléter le composant
Legendexistant (observations vs prédictions, niveaux de risque) - B4 CSP header — Content-Security-Policy adapté MapLibre GL JS + Web Workers + blob URLs
- B5 HSTS —
Strict-Transport-Securitydans le Caddyfile
Phase C — Valeur ajoutée produit
- C1 Vue historique — date picker sur le slider temporel, chargement des observations/scores passés (données déjà en base)
- C2 Tests d'intégration pipeline — scénario
ingestion GeoJSON synthétique → dérive → score; couvre le type de bug ST_Distance/detected_at - C3 TypeScript frontend — activation progressive sur les nouveaux fichiers, typage des interfaces API (score, horizons, patches) en priorité
- C4 Refactoring
SargassesMap.jsx— découpage du composant monolithique (320 lignes) en sous-composants testables
Phase D — Expansion & B2B
À démarrer seulement après A entièrement livré et B partiellement livré.
- D1 Documenter le coût marginal d'ajout d'une zone — temps + quota Sentinel Hub + coût stockage tiles, pour chiffrer l'expansion (Yucatán, Brésil, Açores…)
- D2 API publique documentée avec clé — endpoint
/api/v1/score+ quota + documentation OpenAPI ; ouvre la porte au B2B (offices de tourisme, collectivités, assureurs maritimes) - D3 Expansion géographique — première zone hors Caraïbes (candidat : côtes du Yucatán, Mexico)