Initial commit : docs (specs, data-model, architecture, roadmap)

This commit is contained in:
Gwadaking
2026-03-31 23:24:51 -04:00
commit e44916d444
8 changed files with 1247 additions and 0 deletions

150
docs/architecture.md Normal file
View File

@@ -0,0 +1,150 @@
# ARCHITECTURE — Sargasse-Sentry
## Stack technique
| Couche | Technologie | Rôle |
|---|---|---|
| Backend | Symfony (PHP) | API JSON, crons, pipeline |
| Frontend | React SPA | Interface utilisateur |
| Maps | MapLibre GL JS + react-map-gl | Rendu cartographique |
| Base de données | PostgreSQL + PostGIS | Données géospatiales |
| Vector tiles | Tippecanoe (génération) | Tiles statiques pré-générées |
| Cache | Redis | Scores, résultats API |
| Reverse proxy | Traefik | Routage multi-tenant, SSL |
| Runtime PHP | FrankenPHP | Remplace php-fpm + nginx |
| Conteneurisation | Docker | Isolation, déploiement |
---
## Infrastructure
### Hébergement
VPS personnel, architecture **multi-tenant Docker + Traefik + FrankenPHP** (configuration existante partagée entre projets).
```
Internet
└── Traefik (reverse proxy, SSL Let's Encrypt)
├── sargasse-sentry.tld → container Symfony/FrankenPHP
├── [autres projets]
└── ...
Containers Sargasse-Sentry :
├── frankenphp (Symfony API + worker cron)
├── postgres (PostgreSQL + PostGIS)
└── redis (cache)
```
### CDN (optionnel, sans coût)
**Cloudflare free tier** peut être activé devant Traefik pour mettre en cache les vector tiles statiques et les réponses HTTP publiques. Aucun coût.
---
## Architecture applicative
### Backend — Symfony API
Symfony n'expose que du JSON (pas de Twig). Deux responsabilités :
1. **API REST** — endpoints consommés par le frontend React
2. **Pipeline** — crons + workers de traitement satellite
### Frontend — React SPA
Application React statique servie par FrankenPHP (ou Traefik directement). Consomme l'API Symfony et affiche les vector tiles via MapLibre GL JS.
---
## API Endpoints
### Spots & scores
```
GET /api/spots Liste des CoastalPoints
GET /api/spots/{id} Détail d'un CoastalPoint
GET /api/spots/{id}/score ImpactScore courant + horizons
GET /api/spots/{id}/score?at={datetime} Score à un instant donné
```
### Observations & prédictions
```
GET /api/observations?bbox={bbox}&date={date} Observations dans une zone
GET /api/observations/{id} Détail observation
GET /api/forecasts?observationId={id} Prédictions d'une observation
GET /api/forecasts?bbox={bbox}&horizon={h} Prédictions par zone + horizon
```
### Feedback
```
POST /api/feedback Soumettre un UserFeedback anonyme
```
### Tiles (statiques, hors API)
```
GET /tiles/observations/{z}/{x}/{y}.pbf
GET /tiles/forecasts/{horizon}/{z}/{x}/{y}.pbf
```
---
## Vector Tiles
### Stratégie : Tippecanoe + tiles statiques
- Les observations et prédictions sont converties en tiles `.pbf` après chaque ingestion (via Tippecanoe)
- Les tiles sont stockées sur le filesystem du VPS et servies statiquement
- Pas de rendu dynamique à la volée (Tegola écarté — complexité non justifiée au MVP)
- Cloudflare free tier peut mettre ces tiles en cache si activé
### Interdits
- GeoJSON brut exposé en production sur des géométries complexes
---
## Caching
| Données | Stratégie | TTL suggéré |
|---|---|---|
| Scores par spot | Redis | 3h (durée du cycle d'ingestion) |
| Résultats API observations | Redis | 3h |
| Vector tiles statiques | HTTP Cache + Cloudflare | Long (invalidation à chaque ingestion) |
| Endpoints publics | HTTP Cache | 1530 min |
Règle : **1 requête = 1 zone + 1 timestamp**, pas de recalcul à la volée.
---
## Sécurité
- Rate limiting sur tous les endpoints publics (Symfony RateLimiter)
- Aucune donnée personnelle collectée (UserFeedback = fingerprint anonyme)
- Retry automatique pipeline avec backoff
- Logs structurés sur chaque DataIngestionJob
- Secrets (clés API Sentinel Hub, NOAA) via variables d'environnement Docker
---
## Schéma de flux de données
```
Sentinel Hub API
└── Cron Symfony (3h)
└── DataIngestionJob
├── Calcul AFAI → SargassumObservation
│ └── Tippecanoe → tiles statiques /tiles/observations/
└── Simulation dérive → SargassumForecast (×4 horizons)
├── Calcul ImpactScore par CoastalPoint → Redis
└── Tippecanoe → tiles statiques /tiles/forecasts/
NOAA GRIB API
└── (consommé pendant la simulation de dérive)
React SPA
├── MapLibre GL JS → /tiles/...pbf
└── Symfony API → /api/spots/{id}/score
```

141
docs/data-model.md Normal file
View File

@@ -0,0 +1,141 @@
# DATA MODEL — Sargasse-Sentry
## Principe fondamental
Séparer strictement :
- **Observation** — ce qui a été détecté (réel)
- **Forecast** — ce qui est simulé (prédiction)
- **Job** — traçabilité du pipeline
- **Score** — agrégat calculé pour un point côtier
- **Feedback** — retour terrain anonyme
---
## Entité : SargassumObservation
Représente une détection satellite réelle.
| Champ | Type | Notes |
|---|---|---|
| `id` | UUID | PK |
| `geometry` | MULTIPOLYGON (SRID 4326) | Polygones détectés |
| `bbox` | GEOMETRY BOX | Bounding box — requêtes spatiales légères |
| `detectedAt` | datetime UTC | Immutable |
| `source` | string | `Sentinel-2` / `Sentinel-3` |
| `tileId` | string | Identifiant tuile Sentinel |
| `cloudCoverage` | float 0100 | % couverture nuageuse |
| `confidence` | float 01 | Indice de confiance détection |
| `afaiMean` | float | Valeur AFAI moyenne |
| `afaiStd` | float | Écart-type AFAI |
| `coverageArea` | float (km²) | Surface totale détectée |
| `processingVersion` | string | Version de l'algorithme AFAI utilisé |
| `createdAt` | datetime | |
**Index :**
- `GIST (geometry)`
- `GIST (bbox)`
- `BTREE (detectedAt)`
- `BTREE (source)`
---
## Entité : SargassumForecast
Projection issue du modèle de dérive.
| Champ | Type | Notes |
|---|---|---|
| `id` | UUID | PK |
| `sourceObservation` | FK → SargassumObservation | |
| `geometry` | MULTIPOLYGON (SRID 4326) | Polygone prédit |
| `validAt` | datetime UTC | Moment auquel la prédiction est valide |
| `computedAt` | datetime | Moment du calcul |
| `modelVersion` | string | Version du modèle de dérive |
| `timeHorizon` | int (heures) | `6 / 12 / 24 / 48` |
| `confidence` | float 01 | |
| `driftVectorAvg` | GEOMETRY(POINT, 4326) | Vecteur de dérive moyen (PostGIS natif) |
| `createdAt` | datetime | |
**Index :**
- `GIST (geometry)`
- `BTREE (validAt)`
- `BTREE (timeHorizon)`
---
## Entité : DataIngestionJob
Traçabilité du pipeline — robustesse obligatoire.
| Champ | Type | Notes |
|---|---|---|
| `id` | UUID | PK |
| `source` | string | `Sentinel-2` / `Sentinel-3` |
| `tileId` | string | |
| `requestedAt` | datetime | |
| `processedAt` | datetime (nullable) | |
| `status` | enum | `pending / success / failed` |
| `retryCount` | int | |
| `errorMessage` | string (nullable) | |
---
## Entité : CoastalPoint
Points d'intérêt utilisateur.
| Champ | Type | Notes |
|---|---|---|
| `id` | UUID | PK |
| `name` | string | |
| `geometry` | POINT (SRID 4326) | |
| `type` | enum | `beach / port / surf / fishing` |
| `region` | string | |
---
## Entité : ImpactScore
Score calculé pour un point côtier à un instant donné.
| Champ | Type | Notes |
|---|---|---|
| `id` | UUID | PK |
| `coastalPoint` | FK → CoastalPoint | |
| `timestamp` | datetime | |
| `score` | int 0100 | |
| `level` | enum | `low / medium / high` |
| `distanceToNearestSargassum` | float (km) | |
| `densityEstimate` | float | |
| `trend` | enum | `increasing / stable / decreasing` |
---
## Entité : UserFeedback
Retour terrain anonyme — alimente la boucle d'apprentissage.
| Champ | Type | Notes |
|---|---|---|
| `id` | UUID | PK |
| `location` | POINT (SRID 4326) | Localisation signalée |
| `coastalPoint` | FK → CoastalPoint (nullable) | Si associé à un point connu |
| `timestamp` | datetime | |
| `hasSeaweed` | bool | Présence confirmée ou infirmée |
| `density` | enum (nullable) | `low / medium / high` |
| `source` | string | Fingerprint anonyme (IP hachée) — pas de compte |
---
## Schéma des relations
```
SargassumObservation
└── SargassumForecast (N)
CoastalPoint
└── ImpactScore (N)
└── UserFeedback (N, nullable)
DataIngestionJob (indépendant, audit trail)
```

136
docs/roadmap.md Normal file
View File

@@ -0,0 +1,136 @@
# ROADMAP — Sargasse-Sentry
> Checklist permanente d'avancement projet.
> Mettre à jour au fil des implémentations.
---
## Légende
- [ ] À faire
- [~] En cours
- [x] Terminé
---
## Phase 0 — Fondations
### Infrastructure & environnement
- [ ] Initialisation dépôt Git
- [ ] Configuration Docker (FrankenPHP + PostgreSQL/PostGIS + Redis)
- [ ] Configuration Traefik (domaine, SSL)
- [ ] Variables d'environnement (Sentinel Hub API key, NOAA, DB, Redis)
- [ ] Initialisation projet Symfony
- [ ] Installation API Platform (ou controllers JSON manuels)
- [ ] Initialisation projet React
- [ ] Configuration MapLibre GL JS + react-map-gl
### Base de données
- [ ] Extension PostGIS activée
- [ ] Migration : `SargassumObservation`
- [ ] Migration : `SargassumForecast`
- [ ] Migration : `DataIngestionJob`
- [ ] Migration : `CoastalPoint`
- [ ] Migration : `ImpactScore`
- [ ] Migration : `UserFeedback`
- [ ] Index spatiaux (GIST) + index BTREE définis
---
## Phase 1 — MVP (Antilles, observations uniquement)
### Pipeline d'ingestion
- [ ] Cron Symfony toutes les 3h
- [ ] Appel API Sentinel Hub (bandes B04, B08, B11)
- [ ] Création `DataIngestionJob` (pending → success/failed)
- [ ] Retry automatique + logging erreurs
- [ ] Filtrage nuages (seuil configurable, défaut 60%)
- [ ] Calcul AFAI (formule complète avec ratio longueurs d'onde)
- [ ] Rasterisation → raster binaire sargasse/non
- [ ] Vectorisation (raster → polygones, Douglas-Peucker, suppression bruit)
- [ ] Persistance `SargassumObservation`
- [ ] Génération vector tiles (Tippecanoe → `.pbf`)
### API Backend
- [ ] `GET /api/spots` — liste CoastalPoints
- [ ] `GET /api/spots/{id}` — détail
- [ ] `GET /api/observations?bbox=&date=` — observations par zone
### Frontend React
- [ ] Carte de base (MapLibre GL JS)
- [ ] Affichage vector tiles observations
- [ ] Sélection d'un CoastalPoint (Spot Mode)
- [ ] Affichage polygones observés
### Données initiales
- [ ] Seed `CoastalPoint` zone Antilles (plages, ports, spots surf, zones pêche)
---
## Phase 2 — Dérive & Score
### Pipeline — simulation de dérive
- [ ] Accès NOAA GRIB (vent + courant)
- [ ] Échantillonnage polygone (centroids + random sampling)
- [ ] Application vecteur dérive par point
- [ ] Reconstruction polygone (convex hull / alpha shape)
- [ ] Génération horizons H+6, H+12, H+24, H+48
- [ ] Persistance `SargassumForecast`
- [ ] Génération vector tiles prédictions (Tippecanoe)
### Calcul ImpactScore
- [ ] Calcul score par `CoastalPoint` (distance, densité, vitesse d'approche, tendance)
- [ ] Persistance `ImpactScore`
- [ ] Mise en cache Redis (TTL 3h)
### API Backend
- [ ] `GET /api/spots/{id}/score` — score courant + horizons
- [ ] `GET /api/spots/{id}/score?at={datetime}` — score à un instant
- [ ] `GET /api/forecasts?observationId=` — prédictions par observation
- [ ] `GET /api/forecasts?bbox=&horizon=` — prédictions par zone + horizon
### Frontend React
- [ ] Spot Mode complet (OK / Risque / Impact par horizon)
- [ ] Slider temporel (Now → +6h → +12h → +24h → +48h)
- [ ] Mise à jour dynamique polygones + score sur slider
- [ ] Gradients radiaux densité
- [ ] Animation légère sur polygones de prédiction
- [ ] Vue mobile optimisée
---
## Phase 3 — Feedback & Alertes
### Boucle d'apprentissage
- [ ] Bouton "Je confirme présence de sargasses"
- [ ] `POST /api/feedback` — persistance `UserFeedback` anonyme
- [ ] Intégration feedback dans calcul score (pondération)
### Alertes push
- [ ] Définir modalité d'inscription légère (push token navigateur ou email)
- [ ] Système d'abonnement par `CoastalPoint`
- [ ] Worker Symfony — envoi alertes si score dépasse seuil
- [ ] Interface d'inscription (friction minimale)
---
## Transversal (continu)
- [ ] Rate limiting endpoints publics
- [ ] Monitoring pipeline (alertes si ingestion échoue > N fois)
- [ ] Logs structurés
- [ ] Fallback Sentinel-2 → Sentinel-3
- [ ] Configuration Cloudflare free tier (cache tiles statiques)
- [ ] Tests fonctionnels pipeline (ingestion → score)

170
docs/specs.md Normal file
View File

@@ -0,0 +1,170 @@
# SPECS — Sargasse-Sentry
## Vision Produit
Sargasse-Sentry est une plateforme de renseignement maritime autonome transformant des données satellites brutes en information directement exploitable pour la prise de décision terrain.
Le produit ne doit pas être perçu comme une carte, mais comme un **outil d'aide à la décision en environnement incertain**, avec un focus sur :
- anticipation
- lisibilité immédiate
- confiance dans la donnée
### Utilisateurs cibles
| Profil | Usage |
|---|---|
| Marins-pêcheurs | Décision de sortie |
| Surfeurs | Qualité du spot |
| Baigneurs | Sécurité / nuisance |
### Contraintes produit
- Gratuit
- Sans inscription obligatoire (sauf alertes push, voir Roadmap V3)
- Mobile-first
- Compréhensible en moins de 3 secondes
---
## UX — Logique Produit
### Mode principal : Spot Mode
L'utilisateur sélectionne un point côtier → affichage :
| Horizon | Indicateur |
|---|---|
| Aujourd'hui | OK / Risque / Impact |
| +24h | OK / Risque / Impact |
| +48h | OK / Risque / Impact |
### Timeline interactive
Slider temporel : `Now → +6h → +12h → +24h → +48h`
Met à jour en temps réel :
- les polygones sur la carte
- le score d'impact
### Impact Score
Calcul basé sur :
```
score = f(distance côte, densité, vitesse d'approche, évolution tendancielle)
```
| Score | Niveau |
|---|---|
| 030 | OK |
| 3070 | Risque |
| 70100 | Impact |
### Visualisation
- Gradients radiaux pour la densité
- Contour épais pour la lisibilité
- Animation légère **uniquement** sur les prédictions (pas les observations)
### Boucle d'apprentissage (différenciateur)
Bouton : **"Je confirme présence de sargasses"**
Stocke un `UserFeedback` anonyme (localisation, timestamp, confirmation, densité estimée).
---
## Pipeline de Données
### Étape 1 — Ingestion
- Cron Symfony toutes les 3h
- Appel API Sentinel Hub
- Récupération bandes B04 (RED), B08 (NIR), B11 (SWIR1)
- Création d'un `DataIngestionJob`
### Étape 2 — Pré-traitement
- Filtrage nuages : rejet si `cloudCoverage > seuil`
- Seuil configurable par région (défaut 60% — peut être ajusté pour zones tropicales à forte nébulosité)
### Étape 3 — Calcul AFAI
Formule complète :
```
AFAI = R_NIR - R_RED - (R_SWIR1 - R_RED) × (λ_NIR - λ_RED) / (λ_SWIR1 - λ_RED)
```
- `R_*` : réflectance de surface des bandes Sentinel
- `λ_*` : longueurs d'onde centrales (constantes fixes par capteur)
Résultat : raster binaire (sargasse / non-sargasse)
### Étape 4 — Vectorisation
- Raster → polygones
- Simplification Douglas-Peucker
- Suppression du bruit (polygones < seuil surface minimal)
### Étape 5 — Simulation de dérive (CRITIQUE)
Ne pas appliquer de translation globale.
Algorithme :
1. **Échantillonnage** du polygone : génération de points internes (centroids + random sampling)
2. **Pour chaque point** : récupération vent + courant (GRIB NOAA) → application du vecteur de dérive
3. **Reconstruction** du polygone : convex hull ou alpha shape
4. **Génération des horizons** : H+6, H+12, H+24, H+48
### Sources de données
| Source | Usage | Notes |
|---|---|---|
| Sentinel-2 | Observations haute résolution | Faible fréquence |
| Sentinel-3 | Fallback | Fréquence élevée |
| NOAA GRIB | Vent + courant | Gratuit, accès HTTP direct |
---
## Performance
### Caching
- **Redis** : résultats API, scores d'impact
- **HTTP Cache** : endpoints publics, tiles statiques
- **Cloudflare free tier** (optionnel, sans coût) : CDN pour les vector tiles statiques
### Stratégie
- 1 requête = 1 zone + 1 timestamp
- Pas de recalcul à la volée
- Scores pré-calculés à chaque ingestion
---
## Sécurité & Résilience
- Retry automatique sur ingestion échouée
- Fallback Sentinel-2 → Sentinel-3 si indisponible
- Logs détaillés sur chaque `DataIngestionJob`
- Monitoring des erreurs pipeline
- Rate limiting sur les endpoints publics
---
## Roadmap
### MVP
- Ingestion Sentinel-2
- Calcul AFAI + vectorisation
- Affichage polygones (observations uniquement)
- Zone géographique : Antilles
### V2
- Dérive H+6 / H+12 / H+24 / H+48
- Score d'impact par `CoastalPoint`
- Interface Spot Mode complète
### V3
- Alertes push (nécessite inscription légère : push token ou email — à concevoir en minimisant la friction)
- Boucle feedback utilisateur (`UserFeedback`)
- Amélioration du modèle de dérive via données terrain