尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證Open-source geospatial monitoring platform for public-interest infrastructure and territorial data
翻譯準備中
取自儲存庫 README
正在載入完整 README。 以下為已儲存的摘錄。
Open-source geospatial monitoring platform for public-interest infrastructure and territorial data
France Monitor is an open-source geospatial monitoring platform for public-interest infrastructure and territorial data. The current implementation is a France-focused reference deployment: it aggregates real-time signals from public APIs, open data, technical feeds, and RSS sources into a single interactive map dashboard covering infrastructure, environment, transport, health, cyber threats, markets, and public information flows.檢視完整 README ↗Open-source geospatial monitoring platform for public-interest infrastructure and territorial data
Features · Architecture · Getting Started · Documentation · Roadmap · Contributing
France Monitor is an open-source geospatial monitoring platform for public-interest infrastructure and territorial data. The current implementation is a France-focused reference deployment: it aggregates real-time signals from public APIs, open data, technical feeds, and RSS sources into a single interactive map dashboard covering infrastructure, environment, transport, health, cyber threats, markets, and public information flows.
The goal is not to replace primary sources, official alerts, or newsroom verification. The project is designed as a public-facing monitoring and correlation tool for analysts, journalists, researchers, and technically curious users who want a unified view of weak signals and public data across France.
France Monitor is not a media outlet and not a press publication. Items shown in the app should be treated as monitoring signals that may require confirmation from primary sources.
The long-term goal is to turn the France prototype into a reusable European commons: documented ingestion patterns, auditable source attribution, deployable API proxies, and country-specific connectors that other civic-tech or research teams can adapt.
| Panel | Data | Cadence |
|---|---|---|
| Electricity grid (Ecowatt) | RTE stress levels by department | 5 min |
| Nuclear fleet | RTE unavailabilities + REMIT/IIP RSS notices | 15 min |
| Gas network | GRTgaz / Teréga flows + vital-organ facilities | 10 min |
| Oil & fuel | Refinery stocks, fuel tension, daily official price history | 10 min |
| Wind energy | RTE real-time éolien production by park | 5 min |
| Hydraulic backbone | Dam levels, barrage signals, Hub'Eau hydrometry | 10 min |
| DROM energy | Overseas territories grid data (Réunion, Martinique…) | 15 min |
| Power outages | EDF OpenData unplanned and planned unavailabilities, RTE IIP lines, EDF SEI island signals, Ecowatt (/api/outages/power) | 10 min |
| Telecom outages | ARCEP « sites indisponibles » daily file, outages classed by age and cause: recent < 24 h, long, maintenance (/api/outages/telecom) | 30 min |
| Internet outages | IODA signals for France by department and operator (read every 10 min), Cloudflare Radar outages and anomalies for France (every 15 min, only with CLOUDFLARE_RADAR_TOKEN, otherwise « non configuré »), RIPEstat BGP visibility of French networks (/api/outages/internet) | 10 min |
| Cloud outages | Status pages of OVHcloud, Scaleway, Outscale, Cloudflare, Google Cloud and AWS filtered on French zones, each incident counted once, maintenances within 7 days; reference inventory of data centres and PeeringDB exchange points, refreshed every 6 h, never shown as a state (/api/outages/cloud) | 30 min |
┌─────────────────────────────────────────────────────────────────┐
│ Browser │
│ │
│ App.ts (orchestrator ~2500 lines) │
│ ├── MapContainer → DeckGLMap (WebGL) / Map (D3/SVG) │
│ ├── 50 components (energy, news, health, cyber, intel…) │
│ ├── RSS Pipeline (stage 1: keywords · stage 2: AI async) │
│ ├── 12 polling loops (military, AIS, finance, cyber, health…) │
│ ├── Intel engine (score v3, situations corrélées, brief) │
│ └── Watchdog — centralised observability registry │
│ │
│ State: in-memory + localStorage + IndexedDB (history) │
└──────────────────────────┬──────────────────────────────────────┘
│ fetch /api/*
┌──────────────────────────▼──────────────────────────────────────┐
│ Vercel — ONE serverless function (api/index.js) routes every │
│ /api/* request to a handler in api/_handlers/ via the generated │
│ table api/_routes.js (Hobby plan caps deployments at 12 │
│ functions; 3 more dedicated functions handle long-running or │
│ large-body routes: ingest/news, fuel-price-series-refresh, │
│ sentinel-ndwi — see docs/deployment.md §1) │
│ │
│ Proxy + cache layer (Upstash Redis, TTL per route) │
│ ├── energy/ RTE Ecowatt, Eco2mix, nuclear REMIT │
│ ├── transport/ SNCF disruptions, air traffic, AIS relay │
│ ├── health/ SPF / ISS, SOS Médecins, OSCOUR │
│ ├── finance/ Boursorama scrape, commodities │
│ ├── fires/ NASA FIRMS MODIS + VIIRS │
│ ├── outages/ power, telecom, internet, cloud │
│ ├── threats.js Cyber OSINT aggregation (Shodan/Censys) │
│ ├── exposure.js Technical exposure scoring │
│ ├── intelligence/ LLM summarisation + brief v14 (Groq) │
│ ├── ingest/ News ingestion (Neon Postgres, QStash 30min)│
│ ├── news/ News query + history timeline API │
│ └── rss / rss-proxy CORS-bypass + Scrapling bypass │
│ │
└─────────────────────────────────────────────────────────────────┘
| Decision | Rationale |
|---|---|
| Vanilla TypeScript, no framework | Zero virtual DOM overhead; direct DOM for 60fps map interactions |
| Vercel Functions as API proxy | CORS bypass, Redis caching, secret isolation without dedicated backend |
| Ollama-first AI | No PII leaves the machine; cloud LLM only as fallback |
| Watchdog registry | Centralised observability — all services self-report; StatusPanel auto-updates |
| Non-blocking init | Critical layers (energy, weather, floods) load first; optional layers in background |
| DeckGLMap lazy-loaded | Mobile users never download the 1 MB WebGL stack |
Dynamic import() for default-hidden modules | Heavy panels/services/configs for off-by-default layers (oil, outages, military bases, hydraulic backbone…) load on demand, keeping index.js ~21% smaller |
Unified loader (fmLoaderHTML) | One spinner everywhere; panels show a loader while data is in flight and swap to content/empty once settled (even on error) — no blank panels, no per-panel loader styles |
| Service Worker (Workbox) | API routes NetworkFirst (4s timeout); map tiles CacheFirst 30d; PWA installable |
hasEverSucceeded flag | Distinguishes "first load in progress" from "persistent failure" in IIP/REMIT |
Optional for full feature coverage:
git clone https://github.com/FraidFraid/France-Monitor.git
cd France-Monitor
npm install
cp .env.example .env.local
# Edit .env.local and fill in your keys
# Vite dev server only (port 3001)
npm run dev
# Vite + Scrapling proxy (Cloudflare-protected RSS, port 8080)
npm run dev:full
# Install Python deps for Scrapling (first time only)
npm run scrapling:install
The app will be available at http://localhost:3001.
Without any API keys the app still starts — energy, weather, and news panels show errors or fallback states, but the map, UI, and classification engine are fully functional.
npm run typecheck # strict, 0 errors required
npm run build # outputs to dist/ with Brotli pre-compression
npm run preview # preview production build locally
vercel
vercel env add UPSTASH_REDIS_REST_URL
vercel env add UPSTASH_REDIS_REST_TOKEN
# … repeat for each required variable (see .env.example)
Full topology (Vercel + Railway + Render + GitHub Actions + Upstash + Neon), the environment variable matrix per platform, and how the single API router works: docs/deployment.md. Moving from Vercel Pro to the free Hobby plan: docs/runbook-passage-hobby.md.
Copy .env.example to .env.local for local dev. On Vercel, configure in Settings → Environment Variables.
| Variable | Description |
|---|---|
UPSTASH_REDIS_REST_URL | Upstash Redis REST endpoint |
UPSTASH_REDIS_REST_TOKEN | Upstash Redis auth token |
RTE_CLIENT_ID | RTE Open Data OAuth client ID (Ecowatt, nuclear) |
RTE_CLIENT_SECRET | RTE Open Data OAuth client secret |
VITE_METEOFRANCE_API_KEY | Météo-France vigilance API key |
| Variable | Description | Fallback |
|---|---|---|
VITE_AISSTREAM_KEY | AISstream.io maritime AIS | Layer disabled |
VITE_TOMTOM_API_KEY | TomTom road traffic tiles | Layer disabled |
OPENSKY_CLIENT_ID / SECRET | OpenSky Network | Anonymous quota |
SNCF_API_KEY | SNCF real-time disruptions | Layer disabled |
NASA_FIRMS_API_KEY | NASA FIRMS wildfires | Layer disabled |
GROQ_API_KEY | Groq cloud LLM (summarisation + cron classification) | Browser Transformers.js |
DATABASE_URL | Neon Postgres (news ingestion + history) | News history disabled |
CRON_SECRET | Vercel Cron auth token | Cron endpoint returns 401 |
SHODAN_API_KEY | Shodan OSINT (cyber exposure) | Cyber panel degraded |
CENSYS_API_ID / SECRET | Censys OSINT | Cyber panel degraded |
CLOUDFLARE_RADAR_TOKEN | Cloudflare Radar outages and anomalies (Internet panel); set on the VM by deploy/oracle/set-radar-token.sh | Radar section « non configuré », IODA and RIPEstat still served |
CDSE_CLIENT_ID / SECRET | Copernicus Sentinel-2 imagery | Satellite overlay disabled |
SCRAPLING_PROXY_URL | Python Scrapling sidecar URL | Direct fetch (may hit CF) |
⚠️ Security: Never prefix server-only secrets with
VITE_— they would be embedded in the client bundle.
| Domain | Source | Type |
|---|---|---|
| Electricity grid | RTE Open Data | REST OAuth |
| Nuclear REMIT | IIP RTE | RSS (public) |
| Gas network | GRTgaz / GIE | REST |
| Weather alerts | Météo-France | REST |
| Flood levels | Vigicrues / Hub'Eau | REST |
| Maritime traffic | AISstream.io | WebSocket |
| Civil air traffic | OpenSky Network | REST |
| Military flights | ADS-B + internal callsign database | Internal |
| Wildfires | NASA FIRMS | REST |
| Rail disruptions | SNCF Open Data | REST |
| Road traffic | TomTom | REST + tiles |
| Health metrics | Santé Publique France | REST |
| Power outages | EDF OpenData + RTE IIP + EDF SEI + Ecowatt | REST + RSS |
| Telecom outages | « sites indisponibles » |
All external API calls are server-side only (Vercel Functions + Upstash cache). No API key is ever exposed to the browser.
A subset of the /api/* endpoints is exposed as a read-only public API so third parties (public agencies, researchers, journalists) can consume the aggregated open-source signals — news feed, energy mix, wildfires, cyber incidents, operational health — without reading the code. Endpoints are GET, unauthenticated, and CORS-open for reads.
The contract is published as a machine-readable OpenAPI 3.1 document at /openapi.json. See docs/api.md for the base URL, curl examples, cache policy and the "signal ≠ confirmed fact" caveat.
france-monitor/
├── api/
│ ├── index.js # THE Vercel function — routes every /api/* to api/_handlers/**
│ ├── _routes.js # GENERATED (npm run generate:api-routes) — url → handler import table
│ ├── _utils/dispatch.js # Router: matches the URL, adapts Node vs. "Edge"-style handlers
│ ├── _handlers/ # Every route lives here (ignored by Vercel — not counted as functions)
│ │ ├── rss.js / rss-proxy.js # RSS CORS bypass + JSON conversion
│ │ ├── sovereignty/ # Défense, Connectivité, Vigilance cyber (adsb.lol, Shom, CERT-FR, RIPEstat…)
│ │ ├── energy/ # Ecowatt, Eco2mix, nuclear REMIT
│ │ ├── health/ # ISS, SOS Médecins, OSCOUR
│ │ ├── finance/ # Market data, commodities
│ │ ├── transport/ # SNCF, air traffic, AIS relay
│ │ ├── news.js / news/history # News query API + history timeline
│ │ └── intelligence/v1/ # LLM summarisation (Groq)
│ ├── _lib/ # Shared: classifier, geocoder, RSS parser, Groq classifier (some generated)
│ ├── ingest/ # Dedicated function — news ingestion (Neon Postgres + Groq LLM),
│ │ # triggered by Upstash QStash every 30 min + a daily Vercel cron safety net
│ ├── fuel-price-series-refresh.js # Dedicated function — daily cron
│ └── sentinel-ndwi.ts # Dedicated function
│
├── services/
│ └── scrapling-proxy/ # FastAPI + Scrapling — Cloudflare bypass for PQR RSS
│
├── src/
│ ├── main.ts # Entry point — SW registration + stale chunk guard
│ ├── App.ts # Main orchestrator (~2500 lines)
│ │
│ ├── components/ # 50 UI panels and map wrappers
│ │ ├── DeckGLMap.ts # WebGL map (MapLibre + Deck.gl) — lazy-loaded
│ │ ├── Map.ts # D3/SVG mobile fallback
│ │ ├── CyberPanel.ts # Cyber threat monitoring panel
│ │ ├── NewsHeatmap.ts # History heatmap grid (day × category)
│ │ ├── CyberBreachPanel.ts # Ransomware / breach map panel
│ │ ├── NuclearPanel.ts # Nuclear fleet status (4 tabs)
│ │ ├── Outages*Panel.ts # Outage layer panels: Power, Telecom, Internet, Cloud
│ │ ├── FranceIntelPanel.ts # Country Intelligence drawer (ops console) — lazy-loaded
│ │ ├── ISNRPanel.ts # Stability index dashboard
│ │ ├── BarometerWidget.ts # Multi-domain health barometer
│ │ └── …
│ │
│ ├── services/ # 70 data fetching and processing modules
│ │ ├── rss.ts # RSS feed aggregation
│ │ ├── classifier.ts # Keyword-based news classification
│ │ ├── ai-classifier.ts # Browser LLM classification override
│ │ ├── situation-engine.ts # 10-rule multi-source situation correlation
│ │ ├── stability-index.ts # ISNR departmental composite index
│ │ ├── france-country-intel.ts # Country snapshot + stability score v3 (explainable breakdown)
│ │ ├── france-intel-brief.ts # Structured brief v14 — evidence-cited LLM JSON + deterministic fallback
│ │ ├── news-events.ts # Consolidated news events (/api/events*) + change digest
│ │ ├── intel-last-visit.ts # "Since your last visit" anchor (localStorage + sessionStorage)
│ │ ├── cyber.ts # Cyber threat feed aggregation
│ │ ├── cyber-threat-scoring.ts # Composite cyber pressure scoring (NEW)
│ │ ├── exposure.ts # Technical exposure (Shodan/Censys)
│ │ ├── threat-map.ts # Geo-located threat cartography
│ │ ├── network-barometer.ts # Internet health signals
│ │ ├── rte-iip.ts # RTE IIP RSS (REMIT nuclear notices)
│ │ ├── nuclear-correlation.ts # REMIT × RTE correlation
│ │ ├── outages-power.ts # Power outages client (/api/outages/power)
│ │ ├── outages-telecom.ts # Telecom outages client (/api/outages/telecom)
│ │ ├── outages-internet.ts # Internet outages client (/api/outages/internet)
│ │ ├── outages-cloud.ts # Cloud outages client (/api/outages/cloud)
│ │ ├── watchdog.ts # Centralised observability registry
│ │ └── …
│ │
│ ├── plugins/ # Vite dev proxy plugins (mirror api/ for local dev)
│ ├── config/ # Static datasets (feeds, military callsigns, geo)
│ ├── utils/ # URL state, caches, spatial helpers, stability history
│ ├── types/index.ts # All shared TypeScript types
│ └── styles/main.css # Global styles — dark mode via CSS custom properties
│
├── vite.config.ts # Dev proxies, PWA, Brotli, chunk splitting
├── tsconfig.json # Strict TS config
└── .env.example # Environment variable reference
| Layer | Technology |
|---|---|
| Language | TypeScript 5.9 — strict, no any |
| Bundler | Vite 7 |
| Map (desktop) | MapLibre GL 5 + Deck.gl 9 |
| Map (mobile) | D3 7 / SVG |
| Clustering | Supercluster |
| Geo processing | Turf.js |
| AI — local | Ollama (Mistral, Llama 3) |
| AI — cloud fallback | Groq API |
| AI — browser fallback | Transformers.js + ONNX Runtime Web |
| Client persistence | IndexedDB (idb), localStorage |
| Server cache | Upstash Redis |
| Deployment | Vercel (Serverless Functions + Edge) |
| PWA | Vite Plugin PWA (Workbox 7) |
| Compression | Brotli quality 11 (pre-compressed at build) |
| Document | Purpose |
|---|---|
| Architecture | Runtime layers, data flow, map architecture, reuse model |
| Deployment | Local development, Vercel deployment, self-hosting direction |
| Data Sources | Source families, provenance principles, reproducibility checklist |
| Public API | Read-only /api/* endpoints for third parties, with the OpenAPI 3.1 spec at /openapi.json |
| Privacy and Safety | Privacy principles, AI processing boundaries, safety scope |
| Roadmap | NLnet-aligned milestones for open-source release and European reuse |
| NLnet Context | Public context for application 2026-06-104 |
Some French regional newspapers serve RSS behind Cloudflare. A lightweight Python sidecar (services/scrapling-proxy/) using Scrapling handles these in dev on port 8080.
npm run scrapling:install # create venv + install deps (first time only)
npm run dev:full # Vite (3001) + Scrapling (8080) together
Articles are ingested server-side every 30 minutes into Neon Postgres, triggered by an Upstash QStash schedule (with a daily Vercel cron as a safety net — see docs/deployment.md §2):
GROQ_API_KEY is set, ambiguous articles (confidence < 0.60) are sent to Groq for reclassification (max 15/tick, llama-3.3-70b-versatile)The History UI exposes this data via /api/news (paginated articles) and /api/news/history (time-bucketed aggregations for the heatmap).
In parallel, the browser runs its own RSS pipeline for real-time display:
Items are shallow-cloned before background enrichment to prevent WebGL marker flicker during geocoding.
The IIP feed (iip.cloud-rte-france.com) is fetched via the /api/rss proxy with:
hasEverSucceeded flag in RTEIIPState: the UI shows a spinner badge "EN COURS" during the first load, and only "INDISPONIBLE" after a confirmed persistent failure.Every data service registers with Watchdog and emits loading / success / failure events. StatusPanel subscribes and auto-updates. Sources automatically transition to stale state when their cache exceeds the configured staleAfterMs threshold — no manual polling needed.
src/services/france-country-intel.ts (scoreFromPillars). Its calibration is locked by contract tests in france-country-intel.test.ts (quiet day 88–94, loaded day 74–86, real tension 58–72, crisis 38–55, major crisis <40). Never adjust the test targets to make a formula change pass.FranceIntelPanel.ts, france-intel-brief.ts and api/_handlers/intelligence/v1/france-intel-brief.js.E<id> consolidated event, S<n> correlated situation). Displayed sources are derived from the cited evidence server-side (api/_lib/brief-evidence.js), never copied from the model; a judgment without valid evidence is shown as UNSUPPORTED with low confidence.src/utils/stability-history.ts, 7-day retention, 30-min write throttle). A cold start shows "—" by design.noUnusedLocals, noUnusedParameters, no anyPascalCase → components · kebab-case → services · camelCase → variables · UPPER_SNAKE_CASE → constants@/* path alias resolves to ./src/*feat: fix: refactor: docs: test:Contributions are welcome.
npm run typecheck # 0 errors required
npm run build # clean build required
npm run lint # no new ESLint warnings
| Area | What to do |
|---|---|
| New data source | src/services/xxx.ts + src/plugins/xxx-proxy.ts + api/xxx.js |
| New panel | Extend Panel.ts, register in App.ts, wire service |
| New layer | Add to MapLayers type → DEFAULT_LAYERS → LAYER_CONFIGS |
| New RSS feed | Add URL to src/config/feeds.ts — classifier handles the rest |
| Bug report | Include browser console, network tab, and reproduction steps |
See docs/roadmap.md for the public milestone plan.
Current high-level milestones:
/api/events, /api/events/detail and /api/events/changes.E<id>/S<n> evidence, clickable in the drawer; sources are derived from the evidence; "high" confidence requires corroborated evidence.stability-history.ts), and a new Correlated Situations block surfacing the 10-rule engine (evidence chains, confidence, sources, recommended actions, keyboard-operable)src/components/shared/loader.ts (fmLoaderHTML({ text?, variant? })) + CSS .fm-loader (spinner ring, --text-accent, spin 0.8s). 11 heterogeneous loaders (emoji+pulse, skeletons, custom spin, empty cards) migrated to it. 6 data panels now show the loader while data is in flight and swap to content (or empty state) once settled, even on fetch error: Fires, Traffic, Environment (weather+floods), Outages, Health barometer, Maritime (AIS). New panels must use fmLoaderHTML — never hand-roll a spinner.index.js reduced 1064 → 842 KB (−21%, gzip −17%) by moving non-critical, default-hidden modules out of the main chunk via dynamic import(): services health/oil/transport, panels OilPanel/OutagesPanel, and large static configs military-bases-db (~1100 lines) + hydraulic-backbone (~1200 lines). Layers concerned are off by default, so the deferred load is invisible. Clean chunk-splitting is now exhausted (remaining heavy modules are shared with DeckGLMap/MapContainer or need risky async refactors).GROQ_API_KEY; zero impact without the key/api/news migrated from collected_at to published_at; new before/until params for cursor pagination; /api/news/history supports week bucket for 90-day viewsmaxSeverity (ne descend jamais sous le seed)loading (spinner ⚛ animé) vs unavailable basé sur hasEverSucceededdata.ransomware.live/posts.json (ancien endpoint 301)France Monitor is released under the GNU Affero General Public License v3.0 (AGPL-3.0).
Third-party data sources are subject to their own terms of service. Review each provider's documentation before commercial use.
Built with public data, for public interest · France, 2026
Star the repo to follow updates — France Monitor evolves fast.
| Daily file |
| Fuel prices | data.economie.gouv.fr | REST |
| Internet outages | IODA + Cloudflare Radar + RIPEstat | REST |
| Cloud outages | OVHcloud, Scaleway, Outscale, Cloudflare and Google Cloud status pages, AWS RSS, PeeringDB | REST + RSS |
| Cyber exposure | Shodan + Censys | REST |
| Ransomware breaches | RansomwareLive | REST (public) |
| Finance | Boursorama (server-side proxy) | HTML scrape |
| News | 60+ RSS feeds (national + PQR) | RSS (CORS-proxied) |
| Satellite imagery | Copernicus CDSE | OData |
| Space weather | NOAA SWPC | REST |
| Élus / communes | data.gouv.fr | REST |