Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ flake8...................................................................Passed

If any of the linters/formatters fail, check the difference with `git diff`, add the differences if there is no behavior changes (isort and black might have change some coding style or import order, this is expected it is their job) with `git add` and finally try to commit again `git commit ...`.

You can also run `pre-commit` with `uv run pre-commit run -v` if you have some changes staged but you are not ready yet to commit.
You can also run `pre-commit` with `uv run pre-commit run --all-files` if you have some changes staged but you are not ready yet to commit.


<!-- TOC --><a name="dependencies-management"></a>
Expand Down
63 changes: 63 additions & 0 deletions codecarbon/core/emissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,69 @@ 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 @@ -60,6 +60,11 @@ def _load_static_data() -> None:
path = _get_resource_path("data/hardware/cpu_power.csv")
_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"