Démo en ligne — https://typhoon-rose.vercel.app : frontend statique et
backend FastAPI servis par le même projet Vercel (fonction Python
api/index.py derrière les rewrites du domaine, donc même origine et aucun
CORS). Ce qui a été rejoué sur cette URL est listé dans
Déploiement → Vérifié live.
Service de données climatiques par bâtiment pour les assureurs français. Une adresse → un JSON canonique joignant aléas réglementaires (Géorisques, point-in-polygon WFS) et vulnérabilité BDNB (139 champs verbatim), avec provenance et niveau de résolution sur chaque fait.
Pas de score, pas de narration IA, pas de recommandations : l'assureur applique son propre modèle actuariel (voir
constitution.md§2).
| Étape 1 — Diagnostic | Étape 2 — Scénarios |
|---|---|
![]() |
![]() |
À gauche : 13 aléas Géorisques, fiche BDNB, photo Panoramax sur carte 3D. À droite : classes TRI officielles (moyen ~100 ans, extrême ~500 ans…), timeline horaire et Play — l'eau monte sur le bâtiment.
- Recherche d'adresse (géocodage BAN) → carte 3D Mapbox avec bâtiments BDNB
- 13 aléas Géorisques (ICPE, inondation, sismique, PPR, radon, argile,
cavités, feux, avalanches, canalisations, vent cyclonique, SSP, mouvements
de terrain) avec badge de résolution :
per-building(vérifié par test géométrique WFS) /commune-level(décret) /commune-level-estimate - Fiche BDNB du bâtiment (usage, hauteur, emprise, matériaux, fiabilité adresse)
- Photo terrain Panoramax orientée sur le point
- Vigilances crues (Vigicrues) par bassin versant
- Historique CatNat et watchlist assureur (localStorage)
- Classes TRI officielles (Directive Inondation) : fréquent ~10 ans, moyen ~100 ans, extrême ~500 ans, faible — profondeur d'eau réglementaire au point, jamais inventée
- Timeline horaire + bouton Play : l'eau monte sur le bâtiment (extrusion Mapbox), le scénario pilote le pic, la pluie prévue n'en pilote que la forme
- Journée sèche → rampe divergée documentée, libellé « montée : hypothèse »
- Absence ≠ panne : « hors TRI », « dans un TRI sans classe au point » et « service indisponible » sont trois messages distincts
- Aléa feu : cône d'exposition orienté vent réel sur la carte
- Mode VFX optionnel (rendu cinématique non contractuel, toujours
partial) - Rapport PDF exportable (jsPDF) avec graphiques et provenance
| Route | Description |
|---|---|
POST /diagnostic/adresse |
Adresse → DiagnosticRecord canonique |
POST /diagnostic/batch + GET /diagnostic/batch/{id} |
Batch = N × pipeline unitaire |
GET /diagnostic/adresse/rapport-pdf |
Rapport PDF |
GET /diagnostic/zone/building(s) |
Fiches BDNB autour du point |
GET /api/flood-alea |
Classes TRI au point (Géorisques WFS, point-in-polygon) |
GET /api/hydro, /api/hydro/extend |
Tracé hydrographique (Sandre) |
GET /api/meteo |
Pluie horaire + débit GloFAS (Open-Meteo) |
GET /api/photo |
Photo Panoramax + crédit |
GET /api/report, /api/report/stream |
Rapport narratif (Mistral, template si clé absente) |
GET /api/geocode/search |
Autocomplétion BAN |
GET /health, /health/detailed |
Sonde de vie |
# Backend (Python 3.11+)
cd backend
python -m venv venv && venv/Scripts/activate # ou source venv/bin/activate
pip install -r requirements.txt
uvicorn app.main:app --reload --port 8000
# Frontend (Node 18+)
cd frontend
npm install
npm run dev # http://localhost:5173Copier .env.example vers .env à la racine du dépôt (le frontend lit
les variables VITE_* depuis là via envDir: '..') et renseigner :
| Variable | Usage |
|---|---|
VITE_MAPBOX_TOKEN |
Jeton public Mapbox (étape 1 & 2) |
VITE_SUPABASE_URL / VITE_SUPABASE_PUBLISHABLE_KEY |
Auth |
VITE_WINDY_API_KEY |
Overlay météo |
MISTRAL_API_KEY |
Prose du rapport (sinon rendu template) |
CDSAPI_KEY / CDSAPI_URL |
Copernicus CDS |
PARTNER_API_KEYS |
Endpoints fermés si absent (mode dev) |
| Adresse | Rôle | Résultat vérifié |
|---|---|---|
| Quai de la Rapée, 75012 Paris | Démo principale — eau sur le bâtiment | available: true · Moyen 0–1 m · Faible 2–3 m · bassin Seine |
| 10 Quai de la Charente, 75019 Paris | Absence honnête — dans un TRI sans classe au point | in_tri: true, 6/13 aléas, fiche BDNB 699 m² |
| Adresse intérieure hors TRI (ex. Brou, 28300) | Contraste « hors TRI ≠ jamais inondé » | available: false, in_tri: false |
Déroulé suggéré : Rapée (diagnostic → étape 2 → Moyen → scrub → Play) → Charente (rigueur : message distinct, aucun chiffre inventé) → hors TRI → export PDF.
# backend (depuis backend/)
python -m pytest tests/ -q # 214 tests hors-ligne, < 60 s
ruff check app tests
# frontend (depuis frontend/)
npm test # 143 tests vitest
npm run build # tsc -b && vite buildCaptures d'écran (rejouables) :
cd frontend && node scripts/demo-shots.mjs # nécessite backend + vite en localLa suite est hors-ligne (fixtures GML capturées, sources mockées) et doit rester verte avant tout commit. Les tests live Géorisques sont un tiers séparé.
backend/
app/api/routes/ diagnostic, flood, hydro, photo, report, geocoding, health
app/connectors/ bdnb, georisques (REST + WFS per-building), flood_alea
(classes TRI), hydro (Sandre), meteo (Open-Meteo),
copernicus, geocoding (BAN), site_photo (Panoramax)
app/services/ canonical (pipeline unitaire), batch, budget, limits,
report_agent (Mistral + cache)
app/schemas/ diagnostic_record (contrat sérialisé, refuse les
champs interdits), flood, report
app/core/ config, logging, api_key, rate_limit
frontend/
src/routes/ Zone (/zone), ReportPage, Dashboard, Portfolio, WatchlistPage…
src/components/ UnifiedMap (Mapbox 3D), ScenarioPanel, RiskConsole,
FloodConsole, RiskPanel, VfxDisclaimer…
src/zone/ moteur scénario : floodAlea (client TRI), floodSim
(scenarioDepthAt), damageModel, impactModel, hazardSim,
windSim, exposure, hydroRoute, hydroLayer, pdf-export
src/typhoon/ chrome applicatif, auth, navigation
- Chaque objet aléa porte
source+resolution+ attribution LO 2.0 - Interdits dans le contrat sérialisé : bandes/scores D03, recommandations IA, tout score composite — le schéma backend les refuse
- Batch = N ×
build_diagnostic_record, aucune seconde implémentation bulk - RGA toujours
commune-level-estimatetant qu'aucune source vectorielle n'est câblée (amendement requis pour changer) - Quotas upstream protégés côté client (BDNB 120 req/min, Géoplateforme 50 req/s)
- Clés API jamais journalisées
- Le WFS Géorisques exige un
AsyncClientdédié (keep-alive lié au backend ; mélanger REST/WFS provoque des 404 en cascade) - GML 3.2 uniquement ; ordre d'axes (lat, lon) quand
srsNameURN EPSG::4326 ; lesrsNamepeut vivre sur l'Envelope/MultiSurfacesans se répéter sur chaquePolygon— l'hériter, sinon point-in-polygon ne matche jamais - Une réponse WFS vide est ambiguë (hoquet MapServer) : rejouer une fois
- Arrondissements Paris/Lyon/Marseille : retranslater vers la commune parente
- Géocodeur : 429 + Retry-After à 50 req/s/IP — helper
_get_with_429_retry
Option A — Tout sur Vercel (un seul projet, recommandé) : le frontend est servi en statique, le backend FastAPI tourne en fonction serverless derrière des rewrites — une seule URL, pas de CORS à gérer.
Le vercel.json à la racine fait tout (le package.json racine préexistant
n'est pas utilisé par Vercel : le build installe et compile frontend/) :
- build :
cd frontend && npm install && npm run build→ servi depuisfrontend/dist - rewrites
/api/*,/diagnostic/*,/health(+/health/*) →api/index.py(fonction Python qui montebackend/app/main.py) — la géocodification vit sous/api/geocode/*, donc déjà couverte - rewrite
/vigicrues/*→https://www.vigicrues.gouv.fr/*: Vigicrues n'envoie pas d'en-têtes CORS, le front passe donc par un proxy — en dev c'est leserver.proxyde Vite, en production c'est cette règle Vercel (sans elle,/vigicrues/...tombe dans le fallback SPA et renvoie du HTML : « Vigilance crues indisponible ») - le frontend appelle le backend en même origine (URLs relatives
${API}/api/...) dès que la page n'est pas servie depuis un hôte local : aucune URL à configurer, aucun CORS. UnVITE_API_BASEqui pointe vershttp://127.0.0.1:8000est ignoré en production (il désignerait la machine du visiteur, d'où « backend inaccessible ? ») ⚠️ api/index.pydoit lierapp(ouapplication/handler) au niveau racine du module, horstry/if. Sinon le build échoue sur « Could not find a top-level "app"… », et en configuration moderne Vercel écarte silencieusement la fonction de la découverte avec un message trompeur (« patternapi/index.pydoesn't match any Serverless Functions »). Le diagnostic de démarrage peut donc rester, mais l'appel_handlerdoit remonter par une affectation non indentée (app = _handler)
-
Sur vercel.com : Add New → Project → importer le dépôt. Root Directory : laisser VIDE (la racine du dépôt).
-
Framework Preset : Other.
-
Variables d'environnement (production) :
Variable Valeur VITE_API_BASEabsent (recommandé). Override possible si le backend est ailleurs ; une valeur loopback ( http://127.0.0.1:8000) est sans effet en productionVITE_MAPBOX_TOKENjeton Mapbox VITE_SUPABASE_URL/VITE_SUPABASE_PUBLISHABLE_KEYauth VITE_WINDY_API_KEYoptionnel MISTRAL_API_KEY/MISTRAL_MODELprose du rapport (backend) CORS_ALLOWED_ORIGINSl'URL Vercel finale (ex. https://typhoon.vercel.app) — inutile en option A car même origine, mais sans risque -
Déployer. Vérifier (adresse TRI de référence) :
BASE=https://<projet>.vercel.app curl $BASE/health curl "$BASE/api/flood-alea?lat=48.848&lon=2.370" | head -c 200 curl -X POST "$BASE/diagnostic/adresse" -H 'Content-Type: application/json' \ -d '{"adresse":"Quai de la Rapée, 75012 Paris"}' | head -c 200
(Le réveil d'une fonction froide prend 5 à 10 s, et un diagnostic complet peut atteindre ~15 s à froid : normal sur le plan gratuit — appeler
/healthune fois avant une démo.)
Instance vérifiée le 2026-09-15 : https://typhoon-rose.vercel.app.
Configuration : Root Directory vide, Framework Preset Other — tout le reste
vient du vercel.json.
| Contrôle | Résultat |
|---|---|
/ et /zone |
200 — application servie (fallback SPA) |
/health, /health/detailed |
200 {"status":"ok"} |
/api/flood-alea?lat=48.848&lon=2.370 |
200 — in_tri: true, classes TRI réelles |
/api/meteo, /api/hydro, /api/photo |
200 |
/api/geocode/search?q=rapée |
200 — géocodage réel |
POST /diagnostic/adresse (Quai de la Rapée) |
200 en ~9–16 s |
POST /api/report |
200, fallback_used: false, prose française |
POST /api/report/stream |
200 en ~1,6 s — header, summary, mitigations, confidence, appendix, patch, done |
| Jeton Mapbox injecté dans le bundle | oui |
Comportements propres au serverless (à connaître avant une démo) :
- Le cache de rapports ne persiste pas (système de fichiers de la fonction) :
cache_hitest toujoursfalse, chaque rapport consomme un appel Mistral et la prose varie légèrement entre deux requêtes identiques. En local, le cache rejoue le rapport à l'identique — c'est la différence attendue. - Sans
VITE_SUPABASE_*, l'auth reste en mode démo (MOCK_USER) : la connexion fonctionne sans comptes réels. - Sans
MISTRAL_MODEL, le défaut estmistral-large-latest; si la clé n'a pas accès à ce modèle (403tier_not_allowed), mettreministral-8b-latest. - Un
VITE_API_BASEloopback resté dans le dashboard est sans effet : la résolution ignore un override local sur un hôte déployé (config.ts). ⚠️ Clé Windy liée au domaine : l'API Windy n'autorise une clé que sur les domaines déclarés dans le tableau de bord (POST /api/map-forecast/v2/authrépond403 key is used from unauthorized domain). Le domaine de production (et tout domaine personnalisé ajouté ensuite) doit y figurer, sinon les couches météo échouent — la console nomme alors explicitement le domaine à ajouter.
Backend sur Render (blueprint render.yaml prêt) :
- render.com : New → Blueprint → choisir le dépôt.
- Env :
CORS_ALLOWED_ORIGINS=https://<votre-app>.vercel.app, optionnelMISTRAL_API_KEY. - Deploy →
https://typhoon-api-xxxx.onrender.com(health/health). Plan gratuit : s'endort après 15 min (~30 s de réveil) — un ping UptimeRobot sur/healthle garde éveillé pour une démo.
Frontend sur Vercel :
- Root Directory :
frontend(lefrontend/vercel.jsongère le fallback SPA). - Env :
VITE_API_BASE=https://typhoon-api-xxxx.onrender.com+ lesVITE_MAPBOX_TOKEN/VITE_SUPABASE_*ci-dessus. - Déployer, puis reporter l'URL Vercel finale dans Render
CORS_ALLOWED_ORIGINS(boucle de retour).
curl https://<render-url>/health
curl "https://<render-url>/api/flood-alea?lat=48.848&lon=2.370" | head -c 200
# puis ouvrir l'URL Vercel et diagnostiquer « Quai de la Rapée, Paris »constitution.md— règles non négociables (amendement avant spec)AGENTS.md— méthodologie spec-driven, invariants acquis, piègesdocs/workflow/— spec, plans (scénarios SCN-xxx, VFX), notes de décisionhandoff.md— décisions architecturales en attente
MIT — voir LICENSE.
Messages concis, préfixés du domaine : feat(etape2): …, fix(flood): …,
test(SCN-xxx): …, docs: …, chore: …. Un commit test(TXXX) échouant
puis un commit feat(TXXX) pour toute feature (workflow spec-driven).

