Commit Graph

47 Commits

Author SHA1 Message Date
Gwadaking
8114103117 fix: icônes PNG + fetch handler pour déclencher le prompt PWA
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 17:29:10 -04:00
Gwadaking
ad0dc92f89 add PWA installable + recalibrer scoring échelle Guadeloupe
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 17:12:20 -04:00
Gwadaking
5c860547f8 fix: corriger le masquage du bas de l'app par la barre Android
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 17:06:06 -04:00
Gwadaking
6a6f42e55b fix: verrouiller la carte sur l'archipel guadeloupéen
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 16:33:10 -04:00
Gwadaking
2db067023f focus: recentrer l'app sur l'archipel guadeloupéen uniquement
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 16:26:20 -04:00
Gwadaking
fd476f50c2 feat: système de signalement bug/idée + modal changelog avec limitations
- AppReport entity + migration (type, message, fingerprint SHA-256, created_at)
- POST /api/report avec rate-limit 5/heure par fingerprint IP
- ReportModal : formulaire type radio (bug/idée) + textarea 1 000 chars max
- ChangelogModal : fonctionnalités en prod, roadmap, 5 limitations honnêtes
- Deux boutons flottants bas-gauche (? info + ✉ signalement)
- Légende remontée pour éviter le chevauchement

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 12:52:32 -04:00
Gwadaking
83690b94ab perf: spots/overview — 100+ requêtes individuelles → 1 requête cachée 30 min
Avant : SargassesMap faisait 1 appel /api/spots (API Platform) puis
N appels /api/spots/{id}/score (un par spot, ~100+ pour la Guadeloupe).

Après : un seul GET /api/spots/overview (LATERAL JOIN spots+scores)
avec Cache-Control: public, max-age=1800, stale-while-revalidate=300.

Les marqueurs colorés s'affichent immédiatement depuis la réponse unique ;
/api/spots/{id}/score n'est appelé qu'au clic sur un spot (SpotPanel).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 12:43:38 -04:00
Gwadaking
14d2acc39b feat: différencier visuellement Sentinel-2 (fiable) vs Sentinel-3 (bruit)
S3 OLCI n'a pas de bande SWIR : impossible de filtrer sun glint résiduel
et aérosols sahariens → faux positifs massifs à 300 m de résolution.

- Tuiles : ajout des propriétés source/confidence/coverage_km2
- Carte : styling data-driven par source
    S2 → orange vif (#f4720a), opacité 65 % — signal confirmé
    S3 → or pâle (#d4b830), opacité 20 % — signal incertain
- Légende : entrées distinctes S2 / S3

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 12:33:11 -04:00
Gwadaking
79f834e1f0 fix: monter la carte une seule fois après chargement du style
Le style basculait de positron (URL) vers cartoonizeStyle() ~500 ms
après le premier rendu. Ce setStyle() déclenchait une réinscription
des sources/layers (react-map-gl v8 / maplibre-gl v5) qui échouait
silencieusement — les bancs de sargasses devenaient invisibles.

La carte est désormais montée UNE SEULE FOIS quand le style est prêt
(avec timeout 4 s → fallback positron). Plus de transition post-mount,
plus de race condition.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 02:48:08 -04:00
Gwadaking
9712cb54db feat: phase A — backup B2, healthcheck /api/status, vector tiles Tippecanoe
- 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>
2026-04-10 03:40:32 -04:00
Gwadaking
cda9a7a8ff security: appliquer les findings de l'audit OWASP (H1→B3)
H1 — trusted_proxies RFC-1918 dans framework.yaml : rate limiting
     opérationnel derrière Traefik (IP client réelle, pas IP Traefik)

H2 — En-têtes HTTP dans Caddyfile : X-Frame-Options DENY,
     X-Content-Type-Options nosniff, Referrer-Policy, Permissions-Policy,
     suppression header Server

H3 — API Platform docs désactivés en when@prod (Swagger UI, ReDoc)

M1 — Rate limiter sur DELETE /api/push/subscribe (manquant)
M2 — Validation FILTER_VALIDATE_URL sur endpoint push avant stockage
M3 — APP_ENV=prod dans backend/.env (était dev — risque si .env.local absent)
M4 — Limite 4096 octets sur le body JSON (FeedbackController + PushController)
M5 — Service Worker : open redirect corrigé (targetUrl validé contre l'origine)

B1 — robots.txt créé (bloque /api/ et /bundles/)
B3 — --time-limit=3600 sur les workers Messenger (rotation + libération mémoire)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 00:49:15 -04:00
Gwadaking
0d9c85e71b feat: icône radar SVG (topbar + favicon) et titre Radar Sargasses Caraïbes
Remplace le logo PNG par un SVG radar inline (cercles concentriques +
bras rotatif + blip) en bleu #2563eb/#60a5fa sur fond #1a1a2e.
Même SVG utilisé pour favicon.svg et le picto dans la topbar.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 00:39:00 -04:00
Gwadaking
50a9e49468 fix: ordre géographique N→S (St-Martin, Guadeloupe, Dominique, Martinique, Ste-Lucie, Barbade) 2026-04-09 01:07:27 -04:00
Gwadaking
81cc9736d8 feat: Dominique et Sainte-Lucie dans les filtres navigation 2026-04-09 01:06:45 -04:00
Gwadaking
fb00ec8fa2 fix: topbar 2 lignes sur mobile (boutons îles toujours visibles), logo 44px 2026-04-09 01:02:25 -04:00
Gwadaking
38e810bd79 fix: supprimer stat Distance sargasses (redondante avec la liste des bancs) 2026-04-09 01:00:25 -04:00
Gwadaking
8823d33eb5 fix: clic carte ferme le panel, stopPropagation sur marker, max-height panel 2026-04-09 00:35:41 -04:00
Gwadaking
e5e835298e feat: logo dans la topbar à la place de l'emoji+texte 2026-04-08 03:09:55 -04:00
Gwadaking
90bee1bfe9 fix: rebuild propre — 1 seul bundle, plugin clean-assets avant chaque build 2026-04-08 03:08: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
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
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
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
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
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
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
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
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
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
e8e82c4b5a feat: display current date in timeline slider 2026-04-01 23:31:34 -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
2cef3725e9 feat: Phase 3 complete — push alerts, feedback scoring, S3 fallback, SPA routing, mobile CSS
- ImpactScoreService: implement getFeedbackBonus() (ST_Distance query on UserFeedback within 20km/6h)
- ImpactScoreService: trigger PushNotificationService when score >= 70
- PushNotificationService: send VAPID WebPush to spot subscribers, clean expired subs
- PushController: GET vapid-public-key, POST/DELETE subscribe with rate limiting
- SentinelHubClient: add optional $collection param, add Sentinel-3 OLCI FAI evalscript (MCI)
- IngestionService: add $collection param + HighCloudCoverageException for fallback logic
- IngestionService: add ingestWithFallback() — tries S2, falls back to S3 on high cloud
- IngestSentinelCommand: --source=auto (default) triggers ingestWithFallback
- FeedbackController: rate limiting via apiFeedbackLimiter
- Migration: push_subscription table
- rate_limiter.yaml: api_read(120/min), api_feedback(10/min), api_push(5/min)
- sw.js: service worker handling push events + notificationclick
- usePushSubscription hook: subscribe/unsubscribe lifecycle with VAPID
- SpotPanel: PushButton component integrated
- SpotPanel.css + TimelineSlider.css: mobile responsive (bottom-sheet on small screens)
- Caddyfile: SPA served at / with try_files fallback, sw.js served from root scope
- vite.config.js: build outDir → backend/public (not /spa)
- deploy/post-receive.sh: full deploy script (composer, npm build, migrations, cache, docker up)
- docs/roadmap.md: all Phase 3 + transversal items marked done

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 03:43:56 -04:00
Gwadaking
94bb6f5e8c Phase 2 : dérive, ImpactScore, slider temporel
Backend :
- OpenMeteoClient : vent horaire 72h (gratuit, sans clé)
- DriftSimulationService : échantillonnage ST_GeneratePoints,
  déplacement itératif Stokes 3%, reconstruction ST_ConcaveHull,
  4 horizons H+6/12/24/48, confiance décroissante
- ImpactScoreService : score 4 composantes (distance/densité/
  vitesse/tendance), cache Redis TTL 3h, invalidation à chaque calcul
- ComputeForecastsCommand : traite les observations sans forecast
- SpotScoreController : score courant + horizons depuis forecasts

Frontend :
- TimelineSlider : navigation Now/+6h/+12h/+24h/+48h
- SargassesMap : couche observations (orange) vs forecasts (violet)
  rechargée à chaque changement d'étape
- SpotPanel : affichage score par horizon actif + indicateur confiance
- Build → backend/public/spa/

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 03:23:21 -04:00
Gwadaking
0c1deb93cb Phase 1 : seed CoastalPoints + frontend React complet
Backend :
- SeedCoastalPointsCommand : 29 points Antilles (idempotent)
  Martinique, Guadeloupe, Sainte-Lucie, Barbade, Saint-Martin

Frontend :
- api/client.js : wrapper fetch pour spots, observations, feedback
- SargassesMap : MapLibre GL JS, marqueurs spots, couche observations
  GeoJSON avec rechargement bbox dynamique à chaque mouvement carte
- SpotPanel : Spot Mode (score/niveau/distance/trend), bouton feedback
  terrain anonyme, gestion état loading/error/null
- App.jsx : composition map + panel, état selectedSpot
- vite.config.js : proxy /api → Symfony dev, build → backend/public/spa

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 03:17:43 -04:00
Gwadaking
f685c96ed6 Phase 0 : scaffolding complet
- Structure monorepo (backend Symfony, frontend React/Vite)
- Docker : FrankenPHP + PostgreSQL/PostGIS + Redis + Traefik labels
- Caddyfile, Dockerfile, .env.example, .gitignore
- MapLibre GL JS + react-map-gl installés
- Roadmap mise à jour

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 02:23:10 -04:00