Skip to content
Open
43 changes: 43 additions & 0 deletions codecarbon/core/emissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,49 @@ def get_private_infra_emissions(self, energy: Energy, geo: GeoMetadata) -> float
+ " >>> Using CodeCarbon's data."
)

# NORDIC EMISSION FACTORS DOCUMENTATION
# ==========================================
# Static emission factors for Nordic electricity regions.
# These values represent the carbon intensity (gCO2eq/kWh) of electricity
# production in specific Nordic bidding zones.
#
# DATA SOURCES:
# - Sweden/Norway (SE1-4, NO1-5): 18 gCO2eq/kWh
# Based on Nordic grid average (<60 gCO2eq/kWh per ENTSO-E)
# Source: https://transparency.entsoe.eu/
# Nordic Energy Research: https://www.nordicenergy.org/indicators/
#
# - Finland (FI): 72 gCO2eq/kWh
# Source: Fingrid real-time CO2 emissions estimate
# https://www.fingrid.fi/en/electricity-market-information/real-time-co2-emissions-estimate/
#
# UPDATE PROCEDURE:
# To update these values annually:
# 1. Check latest data from ENTSO-E Transparency Platform
# 2. Check Fingrid for Finnish-specific data
# 3. Update codecarbon/data/private_infra/nordic_emissions.json
# 4. Values should reflect the most recent annual average
#

# Check for Nordic regions (SE1-4, NO1-5, FI) and use static emission factors
nordic_regions = ["SE1", "SE2", "SE3", "SE4", "NO1", "NO2", "NO3", "NO4", "NO5", "FI"]
if geo.region is not None and geo.region.upper() in nordic_regions:
try:
# Get Nordic energy mix data from cache
nordic_data = self._data_source.get_nordic_country_energy_mix_data()
region_data = nordic_data["data"].get(geo.region.upper())
if region_data:
emission_factor_g = region_data["emission_factor"] # gCO2eq/kWh
emission_factor_kg = emission_factor_g / 1000 # Convert to kgCO2eq/kWh
emissions = emission_factor_kg * energy.kWh # kgCO2eq
logger.debug(f"Nordic region {geo.region}: Retrieved emissions using static factor "
+ f"{emission_factor_g} gCO2eq/kWh: {emissions * 1000} g CO2eq"
)
return emissions
except Exception as e:
logger.warning(f"Error loading Nordic emissions data for {geo.region}: {e}. "
+ "Falling back to default emission calculation.")

compute_with_regional_data: bool = (geo.region is not None) and (
geo.country_iso_code.upper() in ["USA", "CAN"]
)
Expand Down
69 changes: 69 additions & 0 deletions codecarbon/data/private_infra/nordic_emissions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
{
"data": {
"SE1": {
"emission_factor": 18.0,
"unit": "gCO2eq/kWh",
"description": "Sweden Bidding Zone 1 (Northern Sweden)",
"year": 2024
},
"SE2": {
"emission_factor": 18.0,
"unit": "gCO2eq/kWh",
"description": "Sweden Bidding Zone 2 (Central Sweden)",
"year": 2024
},
"SE3": {
"emission_factor": 18.0,
"unit": "gCO2eq/kWh",
"description": "Sweden Bidding Zone 3 (Southern Sweden)",
"year": 2024
},
"SE4": {
"emission_factor": 18.0,
"unit": "gCO2eq/kWh",
"description": "Sweden Bidding Zone 4 (Stockholm region)",
"year": 2024
},
"NO1": {
"emission_factor": 18.0,
"unit": "gCO2eq/kWh",
"description": "Norway Bidding Zone 1 (Oslo)",
"year": 2024
},
"NO2": {
"emission_factor": 18.0,
"unit": "gCO2eq/kWh",
"description": "Norway Bidding Zone 2 (Southern Norway)",
"year": 2024
},
"NO3": {
"emission_factor": 18.0,
"unit": "gCO2eq/kWh",
"description": "Norway Bidding Zone 3 (Central Norway)",
"year": 2024
},
"NO4": {
"emission_factor": 18.0,
"unit": "gCO2eq/kWh",
"description": "Norway Bidding Zone 4 (Northern Norway)",
"year": 2024
},
"NO5": {
"emission_factor": 18.0,
"unit": "gCO2eq/kWh",
"description": "Norway Bidding Zone 5 (Western Norway)",
"year": 2024
},
"FI": {
"emission_factor": 72.0,
"unit": "gCO2eq/kWh",
"description": "Finland",
"year": 2025
}
},
"metadata": {
"source": "Based on historical averages from ENTSO-E data",
"last_updated": "2026-01-24",
"notes": "Static emission factors for Nordic regions. Sweden and Norway have very low carbon intensity due to high renewable energy (primarily hydro and nuclear). Finland has higher emissions due to greater fossil fuel dependency."
}
}
12 changes: 12 additions & 0 deletions codecarbon/input.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@ def _load_static_data() -> None:
_CACHE["cpu_power"] = pd.read_csv(path)


# Nordic country energy mix - used for emissions calculations
path = _get_resource_path("data/private_infra/nordic_emissions.json")
with open(path) as f:
_CACHE["nordic_country_energy_mix"] = json.load(f)

# Load static data at module import
_load_static_data()

Expand Down Expand Up @@ -189,6 +194,13 @@ def get_cpu_power_data(self) -> pd.DataFrame:
"""
return _CACHE["cpu_power"]

def get_nordic_country_energy_mix_data(self) -> Dict:
"""
Returns Nordic Country Energy Mix Data.
Data is cached on first access per country.
"""
return _CACHE["nordic_country_energy_mix"]


class DataSourceException(Exception):
pass
31 changes: 31 additions & 0 deletions tests/test_emissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,3 +172,34 @@ def test_get_emissions_PRIVATE_INFRA_unknown_country(self):
)
assert isinstance(emissions, float)
self.assertAlmostEqual(emissions, 0.475, places=2)

def test_get_emissions_PRIVATE_INFRA_NORDIC_REGION(self):
# WHEN
# Test Nordic region (Sweden SE2)

emissions = self._emissions.get_private_infra_emissions(
Energy.from_energy(kWh=1.0),
GeoMetadata(country_iso_code="SWE", country_name="Sweden", region="SE2"),
)

# THEN
# Nordic regions use static emission factors from the JSON file
# SE2 has an emission factor specified in nordic_country_energy_mix.json
assert isinstance(emissions, float)
assert emissions > 0, "Nordic region emissions should be positive"

def test_get_emissions_PRIVATE_INFRA_NORDIC_FINLAND(self):
# WHEN
# Test Nordic region (Finland)

emissions = self._emissions.get_private_infra_emissions(
Energy.from_energy(kWh=2.5),
GeoMetadata(country_iso_code="FIN", country_name="Finland", region="FI"),
)

# THEN
# Finland (FI) should use Nordic static emission factors
assert isinstance(emissions, float)
assert emissions > 0, "Finland emissions should be positive"
# With 2.5 kWh, emissions should be proportional to energy consumed
assert emissions > 0.1, "Expected reasonable emission value for 2.5 kWh"