Skip to content

Kovelt/carbon-fr

carbon-fr

L'API d'intensité carbone de l'électricité française — souveraine, open source et dev-first.

L'équivalent français de carbonintensity.org.uk, bâti sur les données ouvertes RTE / éCO2mix via ODRÉ.

Release License Rust Statut Architecture


Pourquoi carbon-fr ?

L'intensité carbone du réseau électrique français (en gCO₂eq/kWh) est une donnée publique précieuse — pour décaler une recharge de véhicule, planifier un batch de calcul, ou afficher l'empreinte d'un service. Mais la source officielle reste brute, plafonnée en appels, et sans prévision d'intensité.

carbon-fr la rend directement consommable par des développeurs et des machines :

  • 🇫🇷 Souverain & auto-hébergeable — aucune dépendance propriétaire obligatoire, licences OSI.
  • 🚀 Dev-first — API REST lisible et versionnée (/v1), OpenAPI 3.1 + Swagger UI servis, collection Bruno, SDK TypeScript (sdk/typescript/).
  • 🛡️ Résilient au quota — un poller unique alimente la base ; l'API sert tous les clients depuis ce read-model, à moins de 8 % du quota RTE.
  • 🔬 Méthodologie versionnée — chaque mesure porte sa méthode de calcul (rte-direct et acv-ademe — cycle de vie production et consommation), jamais de changement silencieux.
  • 🔮 Prévision comme valeur ajoutée — l'intensité prévisionnelle n'existe pas à la source : carbon-fr la modélise derrière un port dédié (climatology@1, gardée par backtest).
  • ⏱️ Carbon-aware & live — primitives de scheduling (créneau sous échéance, lowest-k, seuil, économie), flux SSE, et webhooks signés (sur clé API).

Utiliser l'API

Aucune installation : l'instance hébergée répond tout de suite. L'intensité carbone courante du réseau national, en une requête :

curl https://carbon-fr-api.kovelt.fr/v1/intensity/now
{
  "region": "national",
  "timestamp": "2026-06-17T06:30:00Z",
  "intensity": { "value": 20.0, "unit": "gCO2eq/kWh" },
  "methodology": "rte-direct",
  "methodology_version": 1,
  "vintage": "tr"
}

Ajoute ?region=<slug> pour l'une des 12 régions, ou ?methodology=acv-ademe pour le cycle de vie (cf. Fonctionnalités).

🌐 Dans le navigateur — explore et essaie tous les endpoints depuis la Swagger UI : carbon-fr-api.kovelt.fr/docs.

📦 En TypeScript / JavaScript — le SDK officiel (@carbon-fr/sdk, zéro dépendance runtime) :

npm install @carbon-fr/sdk
import { CarbonFr } from "@carbon-fr/sdk";

const cf = new CarbonFr(); // instance hébergée par défaut
const now = await cf.intensityNow();
console.log(now.intensity.value, now.intensity.unit); // 20 gCO2eq/kWh

Fonctionnalités

Endpoint Nature Statut
GET /v1/intensity/now Intensité courante (national + 12 régions)
GET /v1/mix Mix de production par filière
GET /v1/exchanges Échanges transfrontaliers par frontière (flux signé + carbone du voisin, ENTSO-E)
GET /v1/exchanges/date?from=&to= Série historique des échanges transfrontaliers
GET /v1/weather · /weather/date Météo nationale (vent 100 m + irradiance, Open-Meteo CC-BY)
GET /v1/renewable Production renouvelable estimée depuis la météo + facteur de charge (ADR-0018)
GET /v1/intensity/date?from=&to= Historique sur un intervalle (révisé/consolidé/définitif)
GET /v1/intensity/stats?from=&to=[&interval=hour|day] Résumé (moyenne/min/max) + série agrégée
GET /v1/intensity/forecast Prévision d'intensité (climatology@1 ; acv-ademe@2 via ?methodology=acv-ademe&version=2)
GET /v1/intensity/greenest-window Créneau le plus bas-carbone (+ overlay électrolyseur ?eligibility=rfnbo|low-carbon, ADR-0025/0026)
GET /v1/eligibility/rulesets Catalogue des cadres d'éligibilité électrolyseur (rulesets versionnés, servis + planifiés)
GET /v1/schedule · /schedule/slots · /intensity/below Scheduling carbon-aware (échéance, lowest-k, seuil + économie)
GET /v1/intensity/stream Flux live (Server-Sent Events)
GET /v1/methodologies · /factors Catalogue des méthodes + table des facteurs (vérifiabilité)
GET /v1/price · /price/date Décomposition du prix payé ancrée sur le TRV (énergie spot ENTSO-E + TURPE + taxes + résidu, ADR-0023)
GET /v1/cost-reference Couche comparative LCOE (coût de production), estimation en fourchette, jamais soustraite du marché (ADR-0024)
POST/GET/DELETE /v1/webhooks Abonnements webhook signés (clé API requise)

Les endpoints d'intensité (/intensity/now, /intensity/date, /intensity/stats, /mix) acceptent ?region=<slug> (national par défaut) et ?methodology=<id> : rte-direct (estimation RTE, combustion directe — défaut, national uniquement) ou acv-ademe (cycle de vie ADEME, national + 12 régions, ADR-0008). Les endpoints de prix, coût, échanges, météo, renouvelable et catalogue (/price, /cost-reference, /exchanges, /weather, /renewable, /methodologies, /factors) sont nationaux (/price renvoie 400 hors national).

/intensity/greenest-window accepte en plus ?eligibility=rfnbo|low-carbon (overlay « électrolyseur », ADR-0025/0026) : chaque créneau de la fenêtre est annoté de son éligibilité au regard du cadre choisi — rfnbo (part renouvelable ≥ 90 % ou prix day-ahead ≤ 20 €/MWh, Règl. UE 2023/1184) ou low-carbon (intensité ≤ seuil indicatif dérivé de l'acte délégué 2025/2359) — en réponse additive (rien ne change sans le paramètre), avec verdicts pass/fail/indeterminate et disclaimer de neutralité. Le catalogue des rulesets versionnés est servi par /v1/eligibility/rulesets.

La spécification OpenAPI 3.1 (dérivée du code via utoipa) est servie sous GET /v1/openapi.json, et une Swagger UI sous GET /docs. Une collection Bruno versionnée (dossier bruno/) couvre tous les endpoints (cas nominaux + erreurs).

Une carte « électrolyseurs × carbone live » est servie sous GET /hydrogene (hors contrat /v1, comme /docs) : les 233 électrolyseurs européens géolocalisés (European Hydrogen Observatory, instantané semestriel) croisés avec l'intensité carbone régionale live et les fenêtres d'éligibilité rfnbo/low-carbon (ADR-0029). Page auto-contenue — aucun CDN, aucune tuile externe.

Les erreurs suivent RFC 9457 (application/problem+json) : type/title/status/detail + un champ code court et stable (no_data, bad_request…) sur lequel s'aligner. Côté disponibilité, l'API peut répondre 503 (unavailable) quand une donnée dérivée n'est pas encore prête — p. ex. /v1/renewable si le modèle n'est pas calibré, ou /health/ready si la base est injoignable — et /v1/price renvoie 404 si aucune source de prix spot n'est configurée (token ENTSO-E absent).

Tier hébergé & clés API (ADR-0015, opt-in) : par défaut l'API est anonyme et sans quota (parité avec l'auto-hébergement). L'opérateur peut activer une limite de débit (CARBONFR_RATELIMIT_ENABLED=1) — anonyme 60 req/min, clé Free 600 req/min, en-têtes RateLimit-*, 429 (rate_limited) au dépassement. Une clé API (Bearer, requise pour les webhooks) se délivre côté serveur via la sous-commande mint-key : l'empreinte est stockée, la clé n'est affichée qu'une seule fois.

Un compteur de consultation sobre est exposé (GET /v1/stats, POST /v1/stats/visit) : l'IP n'est jamais stockée — seule une empreinte SHA-256 salée sert à dédupliquer (unique par IP/jour), RGPD-friendly.

Couverture National + 12 régions métropolitaines. Le taux_co2 publié par RTE (rte-direct) n'existe qu'au national ; l'intensité régionale est dérivée via acv-ademe (cycle de vie appliqué au mix régional, ADR-0008). acv-ademe@1 est basée production ; acv-ademe@2 consumption-based (imports valorisés à l'intensité du voisin via ENTSO-E + pertes T&D, ADR-0010) est servie au national via ?methodology=acv-ademe&version=2.

Architecture

carbon-fr suit une architecture hexagonale (ports & adapters) stricte : le domaine ne dépend de rien, les dépendances pointent vers l'intérieur. Changer de source de données, de base, ou de modèle de prévision = un nouvel adapter, sans toucher au cœur métier.

        Adapters entrants                       Adapters sortants
       (API axum, CLI…)                  (ODRÉ · PostgreSQL · ForecastModel)
              │                                          ▲
              ▼   appelle les cas d'usage                │  implémentent les ports
        ┌───────────────────────────────────────────────────────────┐
        │                      core  (lib pure, zéro IO)             │
        │   application/  cas d'usage      ports/  traits sortants   │
        │   domain/       intensité, régions, mesures, méthodologie  │
        └───────────────────────────────────────────────────────────┘
                              ▲  assemble tout
                       bin/server (composition root)

Le détail — vision, contraintes, modèle de données, quota — vit dans docs/ARCHITECTURE.md. Le « pourquoi » des choix structurants est tracé dans les ADR.

Structure du workspace

carbon-fr/
├── Cargo.toml                  # workspace Cargo
├── crates/
│   ├── core/                   # ✅ domaine + cas d'usage + ports (lib PURE, zéro IO)
│   ├── adapter-odre/           # ✅ impl Eco2mixSource/Eco2mixArchive (ODRÉ)
│   ├── adapter-postgres/       # ✅ impl repositories (sqlx/Postgres)
│   ├── adapter-http/           # ✅ API axum + OpenAPI + auth + SSE (adapter entrant)
│   ├── adapter-forecast/       # ✅ impl ForecastModel (climatology@1, acv-ademe@2)
│   ├── adapter-meteo/          # ✅ impl WeatherForecastSource (Open-Meteo)
│   ├── adapter-entsoe/         # ✅ impl CrossBorderSource + SpotPriceSource (ENTSO-E)
│   ├── adapter-webhook/        # ✅ impl Notifier (livraison signée, anti-SSRF)
│   └── adapter-gbdt/           # ✅ impl ForecastModel ML (GBDT, exploré — non servi)
├── bin/
│   └── server/                 # ✅ composition root : adapters + poller
├── bruno/                      # collection Bruno (requêtes .bru versionnées)
├── sdk/typescript/             # SDK client TypeScript (@carbon-fr/sdk)
├── deploy/                     # exemples self-host (Caddyfile, systemd) + README (prod Traefik)
├── Dockerfile                  # image de prod multi-stage
├── .env.example                # variables d'environnement documentées
└── docs/
    ├── ARCHITECTURE.md
    └── adr/                    # Architecture Decision Records

Développer / contribuer

Pour travailler sur carbon-fr lui-même (et non simplement consommer l'API). Prérequis : Rust (edition 2024, cargo ≥ 1.85).

git clone git@github.com:Kovelt/carbon-fr.git
cd carbon-fr

cargo check --workspace        # compile tout le workspace
cargo test  --workspace        # lance les tests (le core se teste sans IO)
cargo clippy --all-targets -- -D warnings
cargo fmt --all

Le crate core se teste entièrement en mémoire, avec des fakes implémentant les ports — c'est le bénéfice direct de l'hexagonal (voir crates/core/tests/use_cases.rs).

Déploiement

Image de production via le Dockerfile multi-stage (binaire --release, runtime Debian slim, utilisateur non-root). Chaque tag git vX.Y.Z publie l'image sur GHCR (ghcr.io/kovelt/carbon-fr:X.Y.Z, publique, ADR-0019) — en prod, épingler une version exacte. En bare-metal, une unité systemd (deploy/carbonfr.service) avec Restart=on-failure. Dans les deux cas, placer l'API derrière un reverse proxy TLS (deploy/Caddyfile, exemple self-host) et activer CARBONFR_TRUST_PROXY=1. L'instance hébergée (carbon-fr-api.kovelt.fr) tourne comme un service de la stack Kovelt derrière Traefik (PostgreSQL dédié) ; détails dans deploy/README.md.

docker build -t carbon-fr .
docker run -e DATABASE_URL=postgres://… -e CARBONFR_VISIT_SALT=… -p 8080:8080 carbon-fr

Configuration via variables d'environnement — voir .env.example. Sondes : GET /health (liveness) et GET /health/ready (vérifie la base). Métriques Prometheus sous GET /metrics (fraîcheur du poller, volume ingéré, appels amont — à restreindre au scrapeur côté proxy en prod). Les migrations sont appliquées au démarrage.

Outre le serveur, le binaire expose des sous-commandes one-shot : backfill (rapatrie l'historique par export de masse — prérequis de /intensity/date, /intensity/stats et de la prévision), mint-key (délivre une clé API), les backtest* / train (évaluation et entraînement des modèles de prévision) et --version.

Méthodologie & données

  • Unité canonique : gCO₂eq/kWh.
  • Périmètre : National + 12 régions métropolitaines (couverture éCO2mix régional).
  • Méthodologie MVP : rte-direct — reprise de l'estimation RTE (émissions de la production française), directement comparable à éCO2mix. Versionnée et portée par chaque mesure (ADR-0005).
  • Révisions : la donnée RTE est révisée (trconsolidateddefinitive). L'ingestion fait un upsert conditionnel au millésime : on sert toujours la meilleure version (ADR-0006).
  • Source citée, jamais appropriée : carbon-fr re-traite et cite RTE/ODRÉ, il ne s'y substitue pas.

Données amont : RTE éCO2mix, publiées en open data via ODRÉ sous licence ouverte.

Feuille de route

  • Cadrage — ADR, architecture, modèle de domaine.
  • Phase 1 — Socle : core · adapters ODRÉ / Postgres / HTTP · poller · /intensity/now + /mix (national).
  • Phase 2 — Historique & régional : backfill par export de masse · /intensity/date · rollups + /intensity/stats · régional via acv-ademe (12 régions) · OpenAPI 3.1 + Bruno.
  • Phase 3 — Prévision : climatology@1 (backtest, calibration des intervalles) → /forecast + /greenest-window.
  • Phase 4 — Enrichissement & usage : acv-ademe@2 consumption-based (ENTSO-E) · prévision acv-ademe · scheduling carbon-aware + SSE · clés API + quota · webhooks signés. (ML GBDT exploré, gardé par backtest ; raffinements ouverts.)
  • Phase 5 — Enrichissement, déploiement & SDK : échanges transfrontaliers (/v1/exchanges), météo (/v1/weather), dérivation renouvelable (/v1/renewable) ; prix de l'électricité (/v1/price, décomposition TRV, ADR-0023) + couche comparative LCOE (/v1/cost-reference, ADR-0024) ; déployé sur VPS FR/EU (Traefik + PostgreSQL) ; SDK TypeScript (@carbon-fr/sdk).
  • Extension hydrogène carbon-aware (v0.4.0) : couche A « électrolyseur » — éligibilité RFNBO / bas-carbone par créneau au-dessus de /greenest-window (?eligibility=) + catalogue /v1/eligibility/rulesets (ADR-0025, ADR-0026).
  • À venir : SDK Rust ; site statique (o2switch) ; UsageMeter persistant ; suite hydrogène (roadmap dédiée : ruleset rfnbo:2026-revision quand le droit sera adopté, MixForecast, couche B-light « carte électrolyseurs × carbone live »).

Contribuer

Les contributions sont les bienvenues — lire d'abord CONTRIBUTING.md et les conventions de code dans CLAUDE.md. En résumé : cargo fmt + cargo clippy -D warnings doivent passer, le core reste sans IO, et toute décision structurante passe par un ADR. La branche main est protégée (PR obligatoire, CI verte, historique linéaire — pas de push direct ; voir ADR-0027).

Licence

Distribué sous licence MIT OU Apache 2.0, au choix.

Sauf mention contraire explicite, toute contribution soumise pour inclusion dans ce dépôt — telle que définie par la licence Apache 2.0 — sera distribuée sous cette double licence, sans condition supplémentaire.


Un projet Kovelt.

About

API d'intensité carbone de l'électricité française (gCO₂eq/kWh) — souveraine, open source, dev-first. L'équivalent FR de carbonintensity.org.uk, sur données ouvertes RTE/ODRÉ 🇫🇷.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Releases

Packages

Used by

Contributors

Languages