Commit Graph

116 Commits

Author SHA1 Message Date
Gwadaking
9f73639f25 clean: supprimer les artifacts Vite du repo, gitignore public/assets 2026-04-08 03:00:02 -04:00
Gwadaking
1b50bd4b87 fix: import logo via Vite asset pour résoudre la résolution d'URL dev/prod 2026-04-08 02:58:56 -04:00
Gwadaking
003de64fc4 fix: logo dans frontend/public/assets pour que Vite le serve en dev et le copie au build 2026-04-08 02:56:53 -04:00
Gwadaking
cc33c63d46 add: logo top-left sur la carte, compressé à 20ko (110px)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 02:51:11 -04:00
Gwadaking
d60951cae8 fix: score basé sur l'observation la plus proche du spot, pas la dernière globale
impact_score stocke source_observation_id — la lecture trie par distance(obs→spot)
ASC pour garantir que Barbade n'écrase jamais Guadeloupe. Suppression du garde
"closest wins" au write, remplacé par un ORDER BY déterministe au read.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 02:38:48 -04:00
Gwadaking
51ca910e11 fix: l'observation la plus proche gagne — évite l'écrasement par zone distante
ImpactScoreService : ne pas écraser le score si la nouvelle observation est plus
loin que le score actuel (sauf après 6h de stale). Empêche Barbade d'écraser
la Guadeloupe avec 367km quand des sargasses sont à 8km.

SpotScoreController : horizons et bancs filtrés par proximité (<300km) du spot,
plus la dernière observation globale quelle que soit sa zone.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 02:33:31 -04:00
Gwadaking
a607fd3c99 fix: Veille/ambre uniquement si sargasses à <100km, "banc" au lieu de "patch"
- scoreLabel/etaLabel/SpotMarker conditionnent Veille à distance<100km
  pour éviter l'alerte ambre sur les spots sans sargasses proches
- "patch" → "banc de sargasses" dans le SpotPanel

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 02:25:51 -04:00
Gwadaking
0d47a7ee89 ux: refonte jauge — label dominant, score /100 en détail discret
- "Indice de risque" en petit header explicite au-dessus
- Le label (VEILLE / RISQUE / IMPACT) devient l'élément visuel principal (30px)
- Le nombre /100 rétrogradé en hint discret sous la barre
- Suppression de l'ambiguïté "est-ce une note ou un risque ?"

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 02:03:33 -04:00
Gwadaking
364bb5dfcc fix: label "Veille" pour scores low 15-30, marqueur ambre, ETA sous surveillance
- scoreLabel() : low <15 → "OK", low ≥15 → "Veille", cohérence avec valeur numérique
- etaLabel() : score ≥15 sans ETA → "Sargasses en surveillance" (eta--watch)
- SpotMarker : étoile ambre pour level=low + scoreValue≥15 vs étoile verte pure OK
- spotScores stocke {level, value} au lieu de level seul

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 01:58:11 -04:00
Gwadaking
5dfa000247 chore: add symfony/messenger + symfony/scheduler + phpunit infrastructure
- composer.json : symfony/messenger, symfony/scheduler, symfony/test-pack
- PHPStan fix : is_string() pour narrower mixed dans buildForecastGeometry
- phpunit.dist.xml + tests/bootstrap.php + bin/phpunit pour la suite de tests
- Suppression de src/Schedule.php (schedule vide généré par Flex, inutile)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 01:51:42 -04:00
Gwadaking
c89560aea2 feat: suivi par patch — décomposition MULTIPOLYGON avec distance et surface par patch
ST_Dump sur la dernière observation → 5 patches les plus proches dans 200km, exposés
dans /spots/{id}/score. SpotPanel affiche le nombre de patches et la distance+surface
de chacun sous forme de barres (échelle 0–100km).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 23:36:19 -04:00
Gwadaking
d4d2314f7b feat: explainability — décomposition des 4 composantes du score dans le SpotPanel
Expose breakdown {distance, density, velocity, trend} dans l'API /spots/{id}/score
(pour le score courant reconstruit depuis les champs stockés, et pour chaque horizon
avec confidence appliquée). Affiche les barres avec valeur/max dans le SpotPanel.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 23:33:07 -04:00
Gwadaking
779c07ced0 feat: courants océaniques OSCAR dans le modèle de dérive (v3)
- OceanCurrentClient : requête ERDDAP/OSCAR sans clé API, JSON direct
  Résolution 1/3°, snap au point de grille le plus proche
  Fallback gracieux (wind-only) si OSCAR indisponible
- DriftSimulationService v3 : déplacement total = vent Stokes (3%) + courant OSCAR (100%)
  Physique correcte : les sargasses dérivent avec les courants de surface,
  pas uniquement sous l'effet du vent
  MODEL_VERSION 2.0.0 → 3.0.0
- Courant Caraïbe typique ~0.3–0.5 m/s (courant des Antilles NW) — contribution
  majeure sur 24-48h vs vent Stokes seul (~0.15-0.25 m/s)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 22:39:11 -04:00
Gwadaking
48e6e5060c feat: ingestion automatique toutes les 6h + indicateur fraîcheur UI
- Symfony Scheduler (MainSchedule) : cron 0,6,12,18h UTC
- IngestionScheduleMessage + IngestionScheduleHandler : pipeline complet
  ingestion → forecasts → impact scores pour toutes les zones Antilles
- messenger.yaml : transport scheduler_main + async Redis (rs971_async)
- entrypoint.sh : 2 workers en background (scheduler_main + async)
- GET /api/status : date/heure de la dernière observation
- SargassesMap : badge "Données du JJ/MM à HH:MM" en bas à gauche

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 22:31:58 -04:00
Gwadaking
55caca001c fix: scores horizons — même formule 4 composantes que le score now
Avant : horizons = (1 - dist/50km) × 100 × confidence — échelle incomparable
avec le score now qui utilise distance(40) + densité(30) + vitesse(20) + tendance(10).

Après : même décomposition pour les 4 horizons, en passant currentDistanceKm
pour calculer la vitesse d'approche réelle par rapport à la position courante.
La confiance s'applique sur le score total (incertitude du modèle).

Résultat : si la sargasse s'éloigne à H+6, le score baisse. Si elle approche,
le score monte — mais atténué par la confiance du modèle (0.85 à H+6).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 21:51:56 -04:00
Gwadaking
0014870c3b fix: pondérer le score horizon par la confiance
Sans pondération, H+6 pouvait afficher 67 quand "now" était 34 car les
deux formules n'ont pas la même échelle. Multiplier par confidence (0.85
à H+6, 0.45 à H+48) rend les scores cohérents entre horizons : un risque
ne peut pas paraître croître si c'est juste l'incertitude qui augmente.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 21:49:09 -04:00
Gwadaking
163c2c3a56 fix: nettoyer assets/ avant npm build — plus de bundles orphelins
rm -rf assets/ + index.html avant chaque build React pour ne garder
que les fichiers du dernier build. index.php et sw.js Symfony sont préservés.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 21:37:14 -04:00
Gwadaking
3136b5e7fc fix: couleur forecasts → même orange sargasses que observations
Les prévisions utilisent la même couleur (#f4a020/#c47a10) que les
observations confirmées, avec fill à 20% (vs 50%) et contour tirets.
Lecture immédiate : "sargasses futures" sans rupture visuelle arbitraire.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 21:01:46 -04:00
Gwadaking
cdc8fe90ac fix: forecasts fill opacity 0.45→0.12 — zones prédictives non opaques
Les prévisions sont des estimations approximatives : réduire le fill à 0.12
laisse voir la carte (et la terre) en dessous, communiquant visuellement
"zone probable" vs observation confirmée. Contour tirets [6,4] plus lisible.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 20:43:08 -04:00
Gwadaking
a14782b067 fix: 3 bugs introduits lors du refactoring DriftSimulation/--force
1. DriftSimulationService : suppression ST_Buffer(::geography) — produisait des
   géométries invalides/dégénérées sur grand MULTIPOLYGON → ST_Distance retournait
   652 029km. Remplacé par ST_Translate + ST_SimplifyPreserveTopology uniquement.

2. ImpactScoreService : filtre MAX_RELEVANT_M (300km) — une observation distante
   ne remplace plus le score d'un spot proche. Les scores existants sont préservés
   si l'observation est hors zone pertinente.

3. ComputeForecastsCommand --force : suppression du DELETE FROM impact_score.
   On ne purge plus les scores existants ; computeForObservation ajoute les
   nouveaux scores, les anciens restent valides pour les spots éloignés.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 20:25:49 -04:00
Gwadaking
1429eaa71c fix: DriftSimulationService v2 — ST_Translate au lieu de sample+ConcaveHull
L'ancienne approche (ST_GeneratePoints × 40 + ST_ConcaveHull) avait un défaut
majeur : les points aléatoires pouvaient ne pas couvrir les patches locaux
(ex: le patch à 3.61km de Barbados), donnant des distances absurdes à H+6 (654km).

Nouvelle approche :
- ST_Translate(geometry, dx_deg, dy_deg) : translation directe du MULTIPOLYGON
  source → chaque patch individuel se déplace correctement
- ST_Buffer(... DIFFUSION_M × √horizon) : diffusion turbulente via dilatation
  (H+6 ≈ +3.7km, H+48 ≈ +10.4km) — formes qui s'élargissent sans perdre la structure
- Déterministe : pas d'aléatoire, distances ST_Distance fiables

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 20:14:22 -04:00
Gwadaking
fb25142bad feat: diffusion turbulente + recalcul forcé — polygones qui évoluent dans le temps
- DriftSimulationService : ajout diffusion turbulente 600m/h (marche aléatoire
  indépendante par point) → les patches se dispersent et déforment au fil des horizons
  au lieu d'une simple translation rigide avec les alizés constants
- SAMPLE_POINTS 30→40, CONCAVITY 0.8→0.7, MODEL_VERSION 1.0→1.1
- ComputeForecastsCommand : option --force pour supprimer et recalculer les
  forecasts existants (utile après changement de modèle)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 20:03:51 -04:00
Gwadaking
492ed02b6f feat: ForecastController avec filtres bbox+horizon — polygones par horizon sur la carte 2026-04-03 19:38:22 -04:00
Gwadaking
878174404e fix: calculer ETA depuis horizons API — évite dépendance aux données stale en base 2026-04-03 19:35:21 -04:00
Gwadaking
22a3ce23a4 fix: corriger null paint et line-dasharray invalide dans cartoonizeStyle 2026-04-03 19:27:15 -04:00
Gwadaking
8bf6e20ddf fix: corriger chemin DEPLOY_DIR et docker run dans le hook 2026-04-03 19:14:19 -04:00
Gwadaking
744f76f85e feat: ETA côtier — transformer la map en outil de décision actionnable
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 18:42:21 -04:00
Gwadaking
da7d62fa6a refactor: simplifier Dockerfile et hook — code via volume, pas image
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 17:13:09 -04:00
Gwadaking
00996cb163 fix: monter PHPStan à niveau 8 — corriger 2 bugs file_get_contents/json_encode
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 17:08:45 -04:00
Gwadaking
0d323b871b add trend UX + PHPStan level 6 clean (0 errors)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 16:06:13 -04:00
Gwadaking
997e2d186c fix: GeometryCollection incompatible avec colonne MultiPolygon
ST_Collect peut retourner une GeometryCollection selon les sous-types.
Correction : ST_CollectionExtract(..., 3) extrait uniquement les polygones,
ST_Multi garantit le type MultiPolygon, ST_GeomFromText fournit un fallback
vide typé correctement.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-02 17:06:42 -04:00
Gwadaking
6752b6df0b fix: durcir les filtres AFAI — SWIR guard, NDWI marge côtière, filtre PostGIS 1km²
- S2 : ajout garde SWIR (B11 > 0.03 = artifact atmo/cirrus), NIR abaissé
  à 0.06, NDWI marge côtière (> 0.05), AFAI threshold 0.020
- S3 : même logique, NIR 0.06, NDWI 0.05, MCI 0.020
- PostGIS : suppression des polygones < 1 km² après vectorisation
  (résidus ponctuels = bruit ou artefact atmosphérique en Caraïbe)
- Auto-suppression de l'observation si plus aucun polygone valide
- PROCESSING_VERSION 1.2.0

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-02 17:04:20 -04:00
Gwadaking
edca50200e fix: corriger les faux positifs AFAI — seuils, masque gdal, filtre bruit
Problème : le threshold AFAI 0.005 (S2) / MCI 0.008 (S3) trop bas
classifie la majorité des pixels océan comme sargasse → gros rectangle
orange sur des zones sans sargasses.

Corrections :
- S2 AFAI threshold 0.005 → 0.015 (littérature : Hu 2009, Wang & Hu 2016)
- S3 MCI threshold 0.008 → 0.015
- Ajout protection sun glint : si NIR > 0.08 → pixel rejeté (S2 + S3)
- Division NDWI sécurisée (dénominateur = 0 → return 0)
- gdal_polygonize avec -mask (gdal_calc.py) : seuls les pixels DN=1
  sont vectorisés, le reste n'existe pas dans le GeoJSON
- Filtre MIN_RING_POINTS=8 : supprime les polygones < ~2px (bruit)
- PROCESSING_VERSION 1.0.0 → 1.1.0 pour traçabilité

⚠️  Les observations en base avec processing_version='1.0.0' sont
faussées — les supprimer avant de relancer l'ingestion.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-02 16:52:28 -04:00
Gwadaking
8a60c783b1 fix: supprimer le pattern dots, réduire opacité observations
Les dots couvraient tout le tile Sentinel (pas seulement les pixels
sargasses), saturant la vue. Fill opacity réduit à 0.5 pour laisser
respirer la carte sous-jacente.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-02 16:42:33 -04:00
Gwadaking
6bf6762dac fix: ne pas injecter afai:null dans les propriétés GeoJSON
MapLibre considère null comme "propriété présente" → le filtre !has
ne prenait pas le relais → tous les polygones étaient masqués.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-02 16:36:57 -04:00
Gwadaking
3f1e49bb3b fix: masquer les zones d'observation sans sargasses (afaiIndex = 0)
Passe afaiIndex en propriété GeoJSON et applique un filtre MapLibre
sur les layers observation : seules les features avec afai > 0 sont
affichées. Fallback : si la propriété est absente, affichage normal.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-02 16:21:15 -04:00
Gwadaking
7169dffc67 feat: refonte visuelle cartoon — fond OpenFreeMap, marqueurs SVG, légende comic, SpotPanel clair
- Fond cartoon : OpenFreeMap bright transformé (îles #7ec850, océan #3ab5e6, bordure #4a7c2f)
- Overlay vagues SVG subtil sur toute la carte (opacity 6%)
- Marqueurs spots SVG expressifs : ★ (ok), ! (risque), ✕ (impact) — animation bounce sur impact
- Sargasses #f4a020 avec texture dots MapLibre + contour épais 3.5px
- Légende comic : fond #fffbf0, bordure 3px, ombre dure 4px 4px 0 #333, Fredoka One
- SpotPanel blanc cassé avec ombre dure cartoon, gauge coloré pastel (vert/jaune/rouge)
- Topbar #1a1a2e avec boutons bleus ombre dure + hover translate(-2px,-2px)
- Timeline : step actif avec ombre dure cartoon
- Police Fredoka One chargée via Google Fonts

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-02 15:25:17 -04:00
Gwadaking
1078a3459b fix: supprimer le width 1126px du #root qui bridait la map en desktop
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-02 15:05:13 -04:00
Gwadaking
6e6df9fd89 build: bundle frontend assets
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-02 14:42:46 -04:00
Gwadaking
6fe6252a25 feat: refonte UX front — topbar, navigation île, marqueurs colorés, gauge score, fade layers
- Topbar glassmorphism : logo + boutons île (Guadeloupe/Martinique/Barbade/St-Martin/Dominique) + timeline intégrée
- flyTo() animé par île, vue initiale centrée sur Guadeloupe
- Marqueurs spots colorés par niveau d'impact (vert/jaune/rouge) chargés progressivement
- Score remplacé par un gauge : valeur en grand + barre de progression animée
- Légende flottante bas-gauche (observations / prévisions / niveaux)
- Transition fade 350ms sur les layers MapLibre au changement d'horizon temporel
- NavigationControl déplacé en bas à droite

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-02 00:06:40 -04:00
Gwadaking
d4c513e5e5 build: bundle frontend assets 2026-04-01 23:39:52 -04:00
Gwadaking
e8e82c4b5a feat: display current date in timeline slider 2026-04-01 23:31:34 -04:00
Gwadaking
438a1fc392 fix: add NDWI water mask to evalscripts (S2 + S3) to exclude land pixels 2026-04-01 23:21:19 -04:00
Gwadaking
f0f28dca47 fix: json_encode geometry array before persisting SargassumObservation 2026-04-01 23:08:06 -04:00
Gwadaking
1d03698888 fix: remove unsupported sortby from CDSE catalog request 2026-04-01 23:06:29 -04:00
Gwadaking
bb6ca7b563 fix: use spot.latitude/longitude for feedback (geometry no longer serialized) 2026-04-01 22:58:19 -04:00
Gwadaking
1485339aec fix: expose lat/lng from CoastalPoint, fix getCoords in map 2026-04-01 22:37:14 -04:00
Gwadaking
15079ea7b4 fix: enable application/json format in API Platform 2026-04-01 22:14:55 -04:00
Gwadaking
25244c308a fix: add SERVER_NAME=:80 for FrankenPHP, remove redundant traefik.docker.network label 2026-04-01 21:59:02 -04:00
Gwadaking
6cbd190ba0 fix: disable transaction on initial migration (CONCURRENTLY indexes) 2026-04-01 21:07:12 -04:00