Skip to content

data(nordic): per-zone carbon intensity for Norwegian bidding zones NO1–NO5 - #1260

Open
avalyset wants to merge 5 commits into
mlco2:masterfrom
avalyset:khepri-no-per-zone-ci
Open

data(nordic): per-zone carbon intensity for Norwegian bidding zones NO1–NO5#1260
avalyset wants to merge 5 commits into
mlco2:masterfrom
avalyset:khepri-no-per-zone-ci

Conversation

@avalyset

@avalyset avalyset commented Jun 29, 2026

Copy link
Copy Markdown

Hi! This proposes replacing the uniform 18.0 gCO2eq/kWh placeholder currently shared by all five Norwegian bidding zones in codecarbon/data/private_infra/nordic_emissions.json with per-zone values derived on CodeCarbon's own factor table. Data-only change.

Companion to #1262 (Swedish zones). The two are independent and can be merged in either order.

This revision applies @benoit-cty's review on #1262 to this PR as well, since both points applied here too.

What

Zone before after (gCO2eq/kWh) coverage
NO1 (Oslo) 18.0 26.0 99.58 %
NO2 (Southern) 18.0 27.0 99.89 %
NO3 (Central) 18.0 25.8 99.32 %
NO4 (Northern) 18.0 51.7 98.65 %
NO5 (Western) 18.0 26.6 99.67 %

SE1–SE4 and FI are unchanged (byte-for-byte; only NO blocks + file metadata touched).

Why per-zone, for Norway specifically

NO4 is the case. It carries persistent fossil-gas generation — 3.63 % of all 2025 generation, at 743 gCO2eq/kWh — which contributes roughly half of its carbon intensity. NO4 lands at 51.7, nearly 2.9× the placeholder and about twice any other Norwegian zone. No single constant can cover NO4 and NO1–NO3/NO5 at once.

The other four zones are hydro-dominated and cluster at 25.8–27.0, so the honest summary is: the placeholder is uniformly too low for Norway, and badly wrong for one zone. The per-zone spread here (25.8–51.7) is real, unlike Sweden's (see #1262, where we say so plainly).

NO4 is also event-driven rather than a stable constant: its fossil-gas share moves between years — near zero in 2021, 6.23 % in 2024, 3.63 % in 2025 — and on your factor table that share carries about 52 % of the zone's carbon intensity, so 51.7 is period-dependent. That is recorded in the zone's note field rather than left for a user to discover.

Factor basis: your table, not AR5

The previous version of this PR derived these on IPCC AR5 Annex III lifecycle medians and presented that as aligned with CodeCarbon's own static fallback. Two corrections:

  1. The alignment claim is removed. AR5 is a different factor basis from carbon_intensity_per_source.json, materially so — nuclear 29 vs 12, wind 26 vs 11, hydroelectricity 26 vs 24, natural gas 743 vs 490. Calling it the same methodology conflated "weighted average over the mix" with "the same factors". These values are now re-derived on your table, so the question is moot.
  2. A quotation was inaccurate. The previous text quoted docs/explanation/methodology.md as "weighted average of the emissions from the different energy sources used to generate electricity". The original reads "...energy sources that are used to generate electricity". The quote has been dropped rather than repaired, since the claim it supported is gone.

Coverage — the values are complete factors

Production types with no entry in carbon_intensity_per_source.jsonOther renewable, Waste, Biomass — are kept in the denominator with factor 0, contributing nothing to the numerator, so the stored value is a complete gCO2eq/kWh factor over all zone generation, as CodeCarbon uses it. No factor was invented for them.

Coverage is 98.65–99.89 % for all five Norwegian zones, so the effect is small here; it is disclosed per zone in the file regardless. (Biomass occurs only in NO1, at 0.0004 % — below the rounding of the shipped value.)

Source & method

  • Source: ENTSO-E Transparency Actual Generation per Production Type (A75), per bidding zone, full year 2025.
  • Method: production-based generation-mix — energy-weighted (duration-correct) average of each production type's share × its factor from carbon_intensity_per_source.json. The derivation is described in arXiv:2608.29717.
  • These values replace the static production fallback; they do not touch the Electricity Maps API path, which remains the consumption-based source when a token is configured.

Provenance

The upstream derivation is archived at 10.5281/zenodo.21097430 (Khepri v1.2, versioned record).

That record publishes these zones on the AR5 basis (NO4 39.6, NO1 23.3, …), which also excludes factor-less types from both numerator and denominator instead of carrying them at 0. Those are not the numbers this PR ships; the file metadata states the difference so a reader following the DOI is not surprised by it.

Tests

CodeCarbon's Nordic tests read the factor values dynamically from the JSON and pass unchanged with the new values. No code changes — data only.

Thanks for maintaining CodeCarbon.

Replace the uniform 18.0 gCO2eq/kWh placeholder for NO1-NO5 with per-zone
production-based values (NO1 23.3, NO2 23.9, NO3 21.5, NO4 39.6, NO5 24.5)
derived from ENTSO-E generation-per-type x IPCC AR5 lifecycle factors (2025).
Adds per-zone method/source/drift notes + updated file metadata. SE/FI unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@avalyset

Copy link
Copy Markdown
Author

Hi @benoit-cty — a note in the same spirit as the one on #1262, for the
zone that carries the strongest claim in this set.

Our docs/ mix figures report each type's energy-weighted share of total generation,
while the headline CI normalises over the factor-carrying types — same weighting,
different denominator (full explanation on #1262). For NO4 the excluded category is
"Other renewable" (1.35%); the four types below carry 98.65% of generation and
reproduce the headline exactly.

NO4 (2025) — energy/duration-weighted decomposition

  • Interval coverage: 100.00% (27912 / 27912 intervals)
  • Factors: IPCC AR5 Annex III lifecycle medians (per src/khepri/factors.py)
Production type Energy share AR5 factor (gCO2eq/kWh) Contribution
Hydro Water Reservoir 77.2411 % 24 18.5379
Wind Onshore 11.6688 % 11 1.2836
Hydro Run-of-river and poundage 7.4069 % 24 1.7777
Fossil Gas 3.6832 % 490 18.0477
Total 39.6468 → 39.65

Contributions are display-rounded to 4 decimals; the column as printed sums to 39.6469 against the exact 39.6468.

The point worth making visible: Fossil Gas is only 3.68 % of the mix but 18.05 of
39.65 gCO2eq/kWh — 45.5 % of NO4's carbon intensity.
That is precisely why NO4
separates from the other Norwegian zones (all ≈21–24.5, hydro-dominated): a persistent
gas share at a factor of 490 dominates the number despite a small mix share. This
is the value the uniform 18.0 placeholder is most wrong about, so it is the one that
most deserves a visible calculation.

Full method, EIC codes, and the same-weighting/different-denominator explanation are
in #1262; happy to provide the same decomposition for NO1–NO3 and NO5 on request.

Carries over two of the three points from @benoit-cty's review on the
Swedish PR, which apply here identically.

Factor basis: the metadata said the method was "the same approach as
CodeCarbon's static fallback methodology". It is not the same factor
basis - carbon_intensity_per_source.json carries 743 for natural gas
where IPCC AR5 Annex III carries 490. The metadata now names AR5 as
the basis and records the difference without ranking either one.

Provenance: adds the versioned DOI 10.5281/zenodo.21097430 (Khepri
v1.2), which is the frozen record the values come from, rather than
the concept DOI.

The third point does not apply: these notes already say
"production-based CI" rather than "true CI".

Emission factors are unchanged.
Applies the review on mlco2#1262 to this PR as well, since both points applied here.

NO1 23.3 -> 26.0, NO2 23.9 -> 27.0, NO3 21.5 -> 25.8, NO4 39.6 -> 51.7,
NO5 24.5 -> 26.6.

Re-derived on CodeCarbon's own factor table instead of IPCC AR5. The previous
version presented AR5 as 'aligned with CodeCarbon's own static fallback', which
conflated 'weighted average over the mix' with 'the same factors' — the bases
differ at nuclear 29 vs 12, wind 26 vs 11, hydro 26 vs 24, gas 743 vs 490. That
claim is dropped, along with a methodology.md quote that had 'that are' removed
from inside the quotation marks.

Production types with no entry in carbon_intensity_per_source.json (Other
renewable, Waste, Biomass) are kept in the denominator at factor 0 rather than
dropped from both numerator and denominator, so the stored value is a complete
gCO2eq/kWh factor over all zone generation. No factor was invented. Coverage is
98.65-99.89% for all five zones and is disclosed per zone in the file.

NO4 remains the case for per-zone values: 51.7 against 25.8-27.0 for the other
four, driven by a 3.68% fossil-gas share at 743.

Nordic tests read the JSON dynamically and pass unchanged: 9 Nordic (+3
subtests), 25 test_emissions, 8 package-integrity.
@github-actions github-actions Bot added size/M and removed size/S labels Aug 30, 2026
@avalyset

Copy link
Copy Markdown
Author

Updated here as well: the per-zone values are now derived on CodeCarbon's own factor table instead of AR5, with coverage reported per zone. The full before/after table is in the PR body.

Two things in the earlier text are gone. The claim that the method was aligned with CodeCarbon's fallback was wrong — the bases differ materially, nuclear 29 against 12 and wind 26 against 11. And a quotation from methodology.md dropped two words inside the quote marks; it is removed rather than repaired.

Same change on #1262.

@avalyset

avalyset commented Sep 1, 2026

Copy link
Copy Markdown
Author

The method behind these values is now published as arXiv:2608.29717. I have updated the PR description to cite it alongside the existing Zenodo record.

The NO4 note attributed the 2020-2022 gap in gas generation to a named plant.
ENTSO-E reports generation per production type, not per plant, so that is not
derivable from the source data. The note now states the measured gas share and
its contribution instead. No value changes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants