Projet Surf - App de prévision surf et conditions côtières 100% européenne
D'un moteur de scoring déterministe à une architecture multi-services souveraine, sans compte ni collecte de données personnelles
Contexte
Cette App est une plateforme de prévision des conditions de surf et côtières que j'ai conçue et développé seul, couvrant la France, l'Espagne et le Portugal : environ 320 spots de surf calibrés individuellement et plus de 845 ports de plaisance.
J'ai construit un moteur de scoring déterministe, spot par spot, avec des briefs de session adaptés au niveau du surfeur, sans création de compte et sans collecte de données personnelles, sur une stack technique entièrement européenne.
Quelques résultats particulièrement significatifs :
Ce que ce projet démontre
Il m'a permis de porter seul un projet multi-services de bout en bout : moteur de scoring Python, API Java, deux applications Angular, orchestration Docker et pipeline de données assisté par IA. Au-delà de la diversité technique, c'est un projet de décisions d'architecture assumées : protection de la propriété intellectuelle par construction plutôt que par convention, choix délibéré d'un scoring déterministe plutôt qu'une boîte noire IA, et une approche privacy-first pensée dès la conception plutôt qu'ajoutée après coup.
C'est aussi un projet de rigueur sur la donnée : ne jamais inventer une valeur manquante, tracer la provenance de chaque champ, et concevoir des pipelines idempotents capables de se remettre d'un incident sans perte d'information.
L'architecture finale s'organise en cinq dépôts distincts (moteur Python, API Java, application Angular/Ionic, backoffice Angular, orchestration Docker) et repose sur un principe non négociable : les paramètres de calibration de chaque spot ne quittent jamais le moteur de scoring, ni vers la base de données, ni vers l'API, ni vers le frontend.
🎯 Mes objectifs
- Remplacer les verdicts IA opaques par un moteur de scoring déterministe et explicable.
- Protéger la propriété intellectuelle des paramètres de calibration de chaque spot.
- Couvrir 320 spots sur trois pays avec un niveau de qualité de donnée homogène.
- Concevoir une architecture privacy-first : aucun compte, aucune donnée personnelle collectée.
- Bâtir une stack 100% européenne : hébergement, modèle d'IA, sources de données.
- Adapter automatiquement les briefs de session au niveau du surfeur.
- Financer la plateforme par un système publicitaire géo-ciblé, sans exploitation de données utilisateur.
- Industrialiser un pipeline de vérification de données idempotent et traçable.
Les grandes étapes du projet
Conception de l'architecture et choix structurants
Avant d'écrire la moindre ligne de code, j'ai posé les décisions d'architecture qui allaient conditionner tout le projet.
J'ai découpé le projet en cinq dépôts spécialisés plutôt qu'un monolithe : un moteur de scoring en Python/FastAPI, une couche API en Java/Spring Boot, une application mobile Angular/Ionic, un backoffice Angular, et un dépôt d'orchestration Docker Compose. Ce découpage n'est pas qu'organisationnel : il matérialise une frontière de sécurité.
Décision fondatrice : les paramètres de calibration (orientation du spot, direction de vent optimale, fenêtres de houle, seuils de score) restent exclusivement dans les fichiers JSON internes au moteur FastAPI. Ils ne transitent jamais vers PostgreSQL, Spring Boot, ou une réponse HTTP, quelle qu'elle soit.
J'ai également tranché tôt sur le modèle produit : pas de compte utilisateur, aucune donnée personnelle collectée, et une stack reposant uniquement sur des fournisseurs européens (hébergement Scaleway Paris, IA Mistral, données Open-Meteo, SHOM et Puertos del Estado).
Modélisation des spots : le schéma Spot JSON V2
Chaque spot de surf est décrit par un fichier JSON structuré, avec des blocs geometry et conditions qui portent l'ensemble de la logique de calibration.
Ces fichiers sont versionnés dans Git et embarqués directement dans l'image Docker du moteur : Spring Boot n'effectue aucune lecture de fichier JSON, il fonctionne à 100% sur base de données. Seul FastAPI a légitimement accès aux paramètres de calibration.
- 319 spots seedés en base PostgreSQL, avec un identifiant public découplé des identifiants OpenStreetMap ;
- une colonne
cityrenseignée sur 317 spots sur 319 ; - un principe strict : aucune donnée n'est inventée, un champ sans source confirmée reste à
null, avec une traçabilité de la provenance viaverification.sources.
Construction du moteur de scoring déterministe
C'est le cœur différenciant du projet : un système de notation à deux étages plutôt qu'une estimation par modèle de langage.
Cette moyenne pondérée est ensuite multipliée par trois portes multiplicatives : direction de houle, vitesse de vent absolue, hauteur de houle absolue. Un spot hors fenêtre de direction ou avec un vent trop fort voit son score s'effondrer, même si les autres critères sont favorables.
J'ai conçu les endpoints critiques pour qu'ils soient autonomes : POST /engine/briefing charge lui-même les SpotParams du spot concerné. Spring Boot ne transmet que {spot_id, level, lang}, jamais de paramètre de calibration.
Ce pattern d'endpoints autonomes n'est pas qu'une commodité technique : il empêche structurellement toute exposition accidentelle des paramètres propriétaires, même par erreur de développement côté Spring Boot.
Le même moteur a ensuite été étendu à la prévision portuaire (GET /engine/port-forecast), en réutilisant les services Open-Meteo et marée existants sans toucher à la logique de scoring surf.
Couche API et persistance avec Spring Boot
La couche API expose les données publiques (spots, ports, prévisions, briefs) sans jamais exposer la logique de calibration.
- recherche de spots et de ports symétrique, avec une réponse structurée
{results, hasMore, total}plutôt qu'un tableau plat ; - cache Redis multi-niveaux : verdicts 6h, météo 6h, prévisions 1h, briefs 2h, prévisions portuaires 1h ;
J'ai également résolu un problème de routing lié aux identifiants OpenStreetMap contenant des caractères « / », qui cassaient le @PathVariable de Spring Boot : ajout d'un champ osmId dédié pour découpler les slugs publics des identifiants bruts.
Application mobile Angular / Ionic
Le brief de session affiche les données au format que j'estimais le plus lisible pour un surfeur : un texte neutre généré par Mistral, deux panneaux d'histogrammes de houle (0h–12h / 13h–24h, tous les 2h), des panneaux de tendance de vent avec une rose des vents en SVG, et les heures de marée haute/basse.
- prévisions à 7 jours avec hauteur de houle en donnée principale, température de l'eau, vent, période et marées en détail ;
- recherche débouncée à 300ms sur les pages surf, spots et ports, affichant nom, ville et région ;
Backoffice administrateur
Pour piloter la monétisation, j'ai développé un backoffice Angular dédié à la gestion des campagnes publicitaires.
Le système de ciblage repose sur trois niveaux de portée (global, pays, région, spot) combinés à des portées météo (ville, région, département), avec un CRUD complet et une gestion des priorités.
Ce système d'annonces géo-ciblées est la seule source de revenu envisagée pour le projet : pas de vente de données, pas d'abonnement, uniquement de la publicité contextuelle vendue en direct, avec Google Ads en solution de repli.
Pipeline de données V2 : recherche multi-source et IA
Calibrer 320 spots à la main n'était pas réaliste. J'ai construit un pipeline en plusieurs étapes documentées (11 documents de spécification) : recherche web multi-source, normalisation par LLM, validation, puis génération d'un contexte enrichi (aiContext) pour chaque spot.
- 167 spots vérifiés en France, 81 en Espagne, 84 à 85 au Portugal ;
aiContextgénéré via l'API Anthropic (Claude Sonnet 5), de manière idempotente avec sauvegarde incrémentale spot par spot ;- scripts de calibration idempotents, avec mode
--dry-run, sauvegarde automatique avant toute modification, et préfixedefault:pour tracer les valeurs calculées plutôt que sourcées.
Fiabilisation et résolution d'incidents
Comme sur tout projet multi-services, une part importante du travail a consisté à diagnostiquer et corriger des comportements inattendus, souvent aux frontières entre services.
- fenêtre de marée Stormglass devant démarrer 3h en avance
- échecs de désérialisation Redis sur les DTOs ;
- alignement des fuseaux horaires UTC/local renvoyé par Open-Meteo ;
- limite de 10 entrées pour les configurations plus larges (offre free trial pour commencer);
- routing Spring Boot cassé par des identifiants OSM contenant des « / ».
Ces incidents ont été traités méthodiquement : reproduire, isoler le service en cause, corriger à la source plutôt que de contourner, puis documenter la règle pour éviter la récidive.
Infrastructure et déploiement
L'ensemble des services tourne en local via Docker Compose, avec un pipeline GitHub Actions qui construit et publie les images vers le registre de conteneurs Scaleway.
Le choix de Scaleway Paris n'est pas anodin : il s'inscrit dans la logique de souveraineté numérique du projet, aux côtés de Mistral AI pour la génération des briefs et d'Open-Meteo/SHOM/Puertos del Estado pour les données météo et marée. L'objectif est qu'aucune donnée du projet ne transite par une infrastructure non-européenne.
État actuel et suite du projet
L'application fonctionne aujourd'hui de bout en bout en local, avec les cinq dépôts intégrés via Docker Compose.
Plusieurs chantiers sont volontairement mis de côté à ce stade de développement :
- provisioning des services managés Scaleway (PostgreSQL, Redis, Object Storage) ;
- build iOS/Android via Capacitor et soumission aux stores ;