commit e44916d44421b0ab0bc94ffcaa7f6f6e10cc9a00 Author: Gwadaking Date: Tue Mar 31 23:24:51 2026 -0400 Initial commit : docs (specs, data-model, architecture, roadmap) diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..2763052 --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,15 @@ +{ + "hooks": { + "PreToolUse": [ + { + "matcher": "Bash", + "hooks": [ + { + "type": "command", + "command": "bun ./validate-commands.js" + } + ] + } + ] + } +} \ No newline at end of file diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..81570a5 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,7 @@ +{ + "permissions": { + "allow": [ + "Bash(git commit:*)" + ] + } +} diff --git a/.claude/validate-commands.js b/.claude/validate-commands.js new file mode 100644 index 0000000..0f891cc --- /dev/null +++ b/.claude/validate-commands.js @@ -0,0 +1,426 @@ +#!/usr/bin/env bun + +/** + * Claude Code "Before Tools" Hook - Command Validation Script + * + * This script validates commands before execution to prevent harmful operations. + * It receives command data via stdin and returns exit code 0 (allow) or 1 (block). + * + * Usage: Called automatically by Claude Code PreToolUse hook + * Manual test: echo '{"tool_name":"Bash","tool_input":{"command":"rm -rf /"}}' | bun validate-command.js + */ + +// Comprehensive dangerous command patterns database +const SECURITY_RULES = { + // Critical system destruction commands + CRITICAL_COMMANDS: [ + "del", + "format", + "mkfs", + "shred", + "dd", + "fdisk", + "parted", + "gparted", + "cfdisk", + ], + + // Privilege escalation and system access + PRIVILEGE_COMMANDS: [ + "sudo", + "su", + "passwd", + "chpasswd", + "usermod", + "chmod", + "chown", + "chgrp", + "setuid", + "setgid", + ], + + // Network and remote access tools + NETWORK_COMMANDS: [ + "nc", + "netcat", + "nmap", + "telnet", + "ssh-keygen", + "iptables", + "ufw", + "firewall-cmd", + "ipfw", + ], + + // System service and process manipulation + SYSTEM_COMMANDS: [ + "systemctl", + "service", + "kill", + "killall", + "pkill", + "mount", + "umount", + "swapon", + "swapoff", + ], + + // Dangerous regex patterns + DANGEROUS_PATTERNS: [ + // File system destruction - block rm -rf with absolute paths + /rm\s+.*-rf\s*\/\s*$/i, // rm -rf ending at root directory + /rm\s+.*-rf\s*\/\w+/i, // rm -rf with any absolute path + /rm\s+.*-rf\s*\/etc/i, // rm -rf in /etc + /rm\s+.*-rf\s*\/usr/i, // rm -rf in /usr + /rm\s+.*-rf\s*\/bin/i, // rm -rf in /bin + /rm\s+.*-rf\s*\/sys/i, // rm -rf in /sys + /rm\s+.*-rf\s*\/proc/i, // rm -rf in /proc + /rm\s+.*-rf\s*\/boot/i, // rm -rf in /boot + /rm\s+.*-rf\s*\/home\/[^\/]*\s*$/i, // rm -rf entire home directory + /rm\s+.*-rf\s*\.\.+\//i, // rm -rf with parent directory traversal + /rm\s+.*-rf\s*\*.*\*/i, // rm -rf with multiple wildcards + /rm\s+.*-rf\s*\$\w+/i, // rm -rf with variables (could be dangerous) + />\s*\/dev\/(sda|hda|nvme)/i, + /dd\s+.*of=\/dev\//i, + /shred\s+.*\/dev\//i, + /mkfs\.\w+\s+\/dev\//i, + + // Fork bomb and resource exhaustion + /:\(\)\{\s*:\|:&\s*\};:/, + /while\s+true\s*;\s*do.*done/i, + /for\s*\(\(\s*;\s*;\s*\)\)/i, + + // Command injection and chaining + /;\s*(rm|dd|mkfs|format)/i, + /&&\s*(rm|dd|mkfs|format)/i, + /\|\|\s*(rm|dd|mkfs|format)/i, + + // Remote code execution + /\|\s*(sh|bash|zsh|fish)$/i, + /(wget|curl)\s+.*\|\s*(sh|bash)/i, + /(wget|curl)\s+.*-O-.*\|\s*(sh|bash)/i, + + // Command substitution with dangerous commands + /`.*rm.*`/i, + /\$\(.*rm.*\)/i, + /`.*dd.*`/i, + /\$\(.*dd.*\)/i, + + // Sensitive file access + /cat\s+\/etc\/(passwd|shadow|sudoers)/i, + />\s*\/etc\/(passwd|shadow|sudoers)/i, + /echo\s+.*>>\s*\/etc\/(passwd|shadow|sudoers)/i, + + // Network exfiltration + /\|\s*nc\s+\S+\s+\d+/i, + /curl\s+.*-d.*\$\(/i, + /wget\s+.*--post-data.*\$\(/i, + + // Log manipulation + />\s*\/var\/log\//i, + /rm\s+\/var\/log\//i, + /echo\s+.*>\s*~?\/?\.bash_history/i, + + // Backdoor creation + /nc\s+.*-l.*-e/i, + /nc\s+.*-e.*-l/i, + /ncat\s+.*--exec/i, + /ssh-keygen.*authorized_keys/i, + + // Crypto mining and malicious downloads + /(wget|curl).*\.(sh|py|pl|exe|bin).*\|\s*(sh|bash|python)/i, + /(xmrig|ccminer|cgminer|bfgminer)/i, + + // Hardware direct access + /cat\s+\/dev\/(mem|kmem)/i, + /echo\s+.*>\s*\/dev\/(mem|kmem)/i, + + // Kernel module manipulation + /(insmod|rmmod|modprobe)\s+/i, + + // Cron job manipulation + /crontab\s+-e/i, + /echo\s+.*>>\s*\/var\/spool\/cron/i, + + // Environment variable exposure + /env\s*\|\s*grep.*PASSWORD/i, + /printenv.*PASSWORD/i, + ], + + // Paths that should never be written to + PROTECTED_PATHS: [ + "/etc/", + "/usr/", + "/bin/", + "/sbin/", + "/boot/", + "/sys/", + "/proc/", + "/dev/", + "/root/", + ], +}; + +// Allowlist of safe commands (when used appropriately) +const SAFE_COMMANDS = [ + "ls", + "dir", + "pwd", + "whoami", + "date", + "echo", + "cat", + "head", + "tail", + "grep", + "find", + "wc", + "sort", + "uniq", + "cut", + "awk", + "sed", + "git", + "npm", + "pnpm", + "node", + "bun", + "python", + "pip", + "cd", + "cp", + "mv", + "mkdir", + "touch", + "ln", +]; + +class CommandValidator { + constructor() { + this.logFile = "/Users/melvynx/.claude/security.log"; + } + + /** + * Main validation function + */ + validate(command, toolName = "Unknown") { + const result = { + isValid: true, + severity: "LOW", + violations: [], + sanitizedCommand: command, + }; + + if (!command || typeof command !== "string") { + result.isValid = false; + result.violations.push("Invalid command format"); + return result; + } + + // Normalize command for analysis + const normalizedCmd = command.trim().toLowerCase(); + const cmdParts = normalizedCmd.split(/\s+/); + const mainCommand = cmdParts[0]; + + // Check against critical commands + if (SECURITY_RULES.CRITICAL_COMMANDS.includes(mainCommand)) { + result.isValid = false; + result.severity = "CRITICAL"; + result.violations.push(`Critical dangerous command: ${mainCommand}`); + } + + // Check privilege escalation commands + if (SECURITY_RULES.PRIVILEGE_COMMANDS.includes(mainCommand)) { + result.isValid = false; + result.severity = "HIGH"; + result.violations.push(`Privilege escalation command: ${mainCommand}`); + } + + // Check network commands + if (SECURITY_RULES.NETWORK_COMMANDS.includes(mainCommand)) { + result.isValid = false; + result.severity = "HIGH"; + result.violations.push(`Network/remote access command: ${mainCommand}`); + } + + // Check system commands + if (SECURITY_RULES.SYSTEM_COMMANDS.includes(mainCommand)) { + result.isValid = false; + result.severity = "HIGH"; + result.violations.push(`System manipulation command: ${mainCommand}`); + } + + // Check dangerous patterns + for (const pattern of SECURITY_RULES.DANGEROUS_PATTERNS) { + if (pattern.test(command)) { + result.isValid = false; + result.severity = "CRITICAL"; + result.violations.push(`Dangerous pattern detected: ${pattern.source}`); + } + } + + // Check for protected path access (but allow common redirections like /dev/null) + for (const path of SECURITY_RULES.PROTECTED_PATHS) { + if (command.includes(path)) { + // Allow common safe redirections + if ( + path === "/dev/" && + (command.includes("/dev/null") || + command.includes("/dev/stderr") || + command.includes("/dev/stdout")) + ) { + continue; + } + result.isValid = false; + result.severity = "HIGH"; + result.violations.push(`Access to protected path: ${path}`); + } + } + + // Additional safety checks + if (command.length > 2000) { + result.isValid = false; + result.severity = "MEDIUM"; + result.violations.push("Command too long (potential buffer overflow)"); + } + + // Check for binary/encoded content + if (/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\xFF]/.test(command)) { + result.isValid = false; + result.severity = "HIGH"; + result.violations.push("Binary or encoded content detected"); + } + + return result; + } + + /** + * Log security events + */ + async logSecurityEvent(command, toolName, result, sessionId = null) { + const timestamp = new Date().toISOString(); + const logEntry = { + timestamp, + sessionId, + toolName, + command: command.substring(0, 500), // Truncate for logs + blocked: !result.isValid, + severity: result.severity, + violations: result.violations, + source: "claude-code-hook", + }; + + try { + // Write to log file + const logLine = JSON.stringify(logEntry) + "\n"; + await Bun.write(this.logFile, logLine, { createPath: true, flag: "a" }); + + // Also output to stderr for immediate visibility + console.error( + `[SECURITY] ${ + result.isValid ? "ALLOWED" : "BLOCKED" + }: ${command.substring(0, 100)}`, + ); + } catch (error) { + console.error("Failed to write security log:", error); + } + } + + /** + * Check if command matches any allowed patterns from settings + */ + isExplicitlyAllowed(command, allowedPatterns = []) { + for (const pattern of allowedPatterns) { + // Convert Claude Code permission pattern to regex + // e.g., "Bash(git *)" becomes /^git\s+.*$/ + if (pattern.startsWith("Bash(") && pattern.endsWith(")")) { + const cmdPattern = pattern.slice(5, -1); // Remove "Bash(" and ")" + const regex = new RegExp( + "^" + cmdPattern.replace(/\*/g, ".*") + "$", + "i", + ); + if (regex.test(command)) { + return true; + } + } + } + return false; + } +} + +/** + * Main execution function + */ +async function main() { + const validator = new CommandValidator(); + + try { + // Read hook input from stdin + const stdin = process.stdin; + const chunks = []; + + for await (const chunk of stdin) { + chunks.push(chunk); + } + + const input = Buffer.concat(chunks).toString(); + + if (!input.trim()) { + console.error("No input received from stdin"); + process.exit(1); + } + + // Parse Claude Code hook JSON format + let hookData; + try { + hookData = JSON.parse(input); + } catch (error) { + console.error("Invalid JSON input:", error.message); + process.exit(1); + } + + const toolName = hookData.tool_name || "Unknown"; + const toolInput = hookData.tool_input || {}; + const sessionId = hookData.session_id || null; + + // Only validate Bash commands for now + if (toolName !== "Bash") { + console.log(`Skipping validation for tool: ${toolName}`); + process.exit(0); + } + + const command = toolInput.command; + if (!command) { + console.error("No command found in tool input"); + process.exit(1); + } + + // Validate the command + const result = validator.validate(command, toolName); + + // Log the security event + await validator.logSecurityEvent(command, toolName, result, sessionId); + + // Output result and exit with appropriate code + if (result.isValid) { + console.log("Command validation passed"); + process.exit(0); // Allow execution + } else { + console.error( + `Command validation failed: ${result.violations.join(", ")}`, + ); + console.error(`Severity: ${result.severity}`); + process.exit(2); // Block execution (Claude Code requires exit code 2) + } + } catch (error) { + console.error("Validation script error:", error); + // Fail safe - block execution on any script error + process.exit(2); + } +} + +// Execute main function +main().catch((error) => { + console.error("Fatal error:", error); + process.exit(2); +}); \ No newline at end of file diff --git a/claude.md b/claude.md new file mode 100644 index 0000000..1eab872 --- /dev/null +++ b/claude.md @@ -0,0 +1,202 @@ +# 🧠 Claude Project Context — Sargasse-Sentry + +## 🎯 Objectif du Projet + +Construire une plateforme de renseignement maritime capable de transformer des donnĂ©es satellites complexes en une information simple, fiable et immĂ©diatement exploitable par des utilisateurs non techniques. + +Le projet doit privilĂ©gier : +- la robustesse +- la lisibilitĂ© +- la performance +- la maintenabilitĂ© long terme + +Toute dĂ©cision technique doit ĂȘtre orientĂ©e vers ces objectifs. + +--- + +## ⚙ Stack Technique (NON NÉGOCIABLE) + +- Backend : Symfony 7.4 LTS (PHP 8.4+) +- Database : PostgreSQL + PostGIS +- Queue : Symfony Messenger + Redis +- Cartographie : Mapbox GL JS +- Cache : Redis + HTTP Cache +- Traitement : PHP prioritaire, Python uniquement si nĂ©cessaire + +--- + +## đŸ§± Principes d’Architecture + +### 1. SĂ©paration stricte des responsabilitĂ©s + +- Observation ≠ PrĂ©diction ≠ Score +- Ne jamais mĂ©langer ces concepts dans une mĂȘme entitĂ© ou table + +--- + +### 2. Aucune logique mĂ©tier dans les contrĂŽleurs + +- Utiliser des services +- Utiliser des handlers Messenger pour les traitements lourds + +--- + +### 3. Pipeline asynchrone obligatoire + +- Toute ingestion ou traitement doit passer par Messenger +- Aucun traitement bloquant en requĂȘte HTTP + +--- + +### 4. DonnĂ©es immuables + +- Une observation ne doit jamais ĂȘtre modifiĂ©e +- Une prĂ©diction est versionnĂ©e (modelVersion) + +--- + +### 5. Optimisation gĂ©ospatiale native + +- Utiliser PostGIS pour : + - distance + - intersection + - simplification +- Ne jamais recalculer cĂŽtĂ© PHP si PostGIS peut le faire + +--- + +## 📊 Conventions de DonnĂ©es + +### GĂ©omĂ©trie + +- SRID : 4326 obligatoire +- Utiliser MULTIPOLYGON mĂȘme pour un seul polygone +- Simplifier avant stockage + +--- + +### Dates + +- Toujours en UTC +- Utiliser DateTimeImmutable + +--- + +### Identifiants + +- UUID pour toutes les entitĂ©s critiques + +--- + +## 🧠 ModĂ©lisation + +Claude DOIT crĂ©er des entitĂ©s distinctes : + +- SargassumObservation +- SargassumForecast +- CoastalPoint +- ImpactScore +- DataIngestionJob + +Aucune fusion ou simplification n’est autorisĂ©e. + +--- + +## 🌊 Pipeline + +Claude DOIT implĂ©menter : + +1. Ingestion Sentinel +2. Calcul AFAI +3. Vectorisation +4. Simulation de dĂ©rive par points +5. GĂ©nĂ©ration de prĂ©dictions multiples + +--- + +## ⚠ Contraintes critiques + +### 1. Performance + +- Interdiction d’envoyer du GeoJSON brut en production +- Utilisation obligatoire de vector tiles + +--- + +### 2. Cache + +- Toute donnĂ©e calculĂ©e doit ĂȘtre cachĂ©e +- Aucun recalcul inutile + +--- + +### 3. RĂ©silience + +- Retry automatique sur ingestion +- Gestion des erreurs obligatoire + +--- + +## 🎯 UX Constraints + +Claude doit considĂ©rer que : + +- L’utilisateur ne comprend pas les donnĂ©es satellites +- L’information doit ĂȘtre lisible en moins de 3 secondes + +Donc : + +- Toujours fournir un score simple +- Toujours fournir une tendance +- Toujours fournir un horizon temporel + +--- + +## 🔁 ÉvolutivitĂ© + +Le systĂšme doit ĂȘtre conçu pour : + +- ajouter de nouvelles sources de donnĂ©es +- amĂ©liorer le modĂšle de dĂ©rive +- intĂ©grer du machine learning + +Sans refonte complĂšte. + +--- + +## đŸš« Interdictions + +- Pas de logique mĂ©tier dans les contrĂŽleurs +- Pas de calcul gĂ©ospatial lourd en PHP si PostGIS peut le faire +- Pas de dĂ©pendance inutile +- Pas de complexitĂ© prĂ©maturĂ©e (microservices non nĂ©cessaires au MVP) + +--- + +## ✅ Attentes vis-Ă -vis de Claude + +Claude doit : + +- gĂ©nĂ©rer du code propre, structurĂ©, testable +- respecter strictement les conventions +- documenter les choix techniques +- proposer des amĂ©liorations si pertinentes + +Claude ne doit PAS : + +- simplifier les modĂšles de donnĂ©es +- ignorer les contraintes de performance +- court-circuiter Messenger + +--- + +## 🧭 Philosophie + +Ce projet n’est pas une simple application cartographique. + +C’est un systĂšme de renseignement environnemental. + +Chaque dĂ©cision doit renforcer : +- la prĂ©cision +- la fiabilitĂ© +- la comprĂ©hension utilisateur diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 0000000..6fc5f96 --- /dev/null +++ b/docs/architecture.md @@ -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 | 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 +``` diff --git a/docs/data-model.md b/docs/data-model.md new file mode 100644 index 0000000..4eca331 --- /dev/null +++ b/docs/data-model.md @@ -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 0–100 | % couverture nuageuse | +| `confidence` | float 0–1 | 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 0–1 | | +| `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 0–100 | | +| `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) +``` diff --git a/docs/roadmap.md b/docs/roadmap.md new file mode 100644 index 0000000..0b798f3 --- /dev/null +++ b/docs/roadmap.md @@ -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) diff --git a/docs/specs.md b/docs/specs.md new file mode 100644 index 0000000..a74fc7a --- /dev/null +++ b/docs/specs.md @@ -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 | +|---|---| +| 0–30 | OK | +| 30–70 | Risque | +| 70–100 | 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