# 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 | 15–30 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
```
