Skip to content

Commit 8ccb680

Browse files
author
benoit-cty
committed
refacto
1 parent e1cb9a7 commit 8ccb680

2 files changed

Lines changed: 78 additions & 68 deletions

File tree

codecarbon/core/emissions.py

Lines changed: 57 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -155,71 +155,8 @@ def get_private_infra_emissions(self, energy: Energy, geo: GeoMetadata) -> float
155155
+ " >>> Using CodeCarbon's data."
156156
)
157157

158-
# NORDIC EMISSION FACTORS DOCUMENTATION
159-
# ==========================================
160-
# Static emission factors for Nordic electricity regions.
161-
# These values represent the carbon intensity (gCO2eq/kWh) of electricity
162-
# production in specific Nordic bidding zones.
163-
#
164-
# DATA SOURCES:
165-
# - Sweden/Norway (SE1-4, NO1-5): 18 gCO2eq/kWh
166-
# Based on Nordic grid average (<60 gCO2eq/kWh per ENTSO-E)
167-
# Source: https://transparency.entsoe.eu/
168-
# Nordic Energy Research: https://www.nordicenergy.org/indicators/
169-
#
170-
# - Finland (FI): 72 gCO2eq/kWh
171-
# Source: Fingrid real-time CO2 emissions estimate
172-
# https://www.fingrid.fi/en/electricity-market-information/real-time-co2-emissions-estimate/
173-
#
174-
# UPDATE PROCEDURE:
175-
# To update these values annually:
176-
# 1. Check latest data from ENTSO-E Transparency Platform
177-
# 2. Check Fingrid for Finnish-specific data
178-
# 3. Update codecarbon/data/private_infra/nordic_emissions.json
179-
# 4. Values should reflect the most recent annual average
180-
#
181-
182-
# Check for Nordic regions (SE1-4, NO1-5, FI) and use static emission factors
183-
nordic_regions = [
184-
"SE1",
185-
"SE2",
186-
"SE3",
187-
"SE4",
188-
"NO1",
189-
"NO2",
190-
"NO3",
191-
"NO4",
192-
"NO5",
193-
"FI",
194-
]
195-
if geo.region is not None and geo.region.upper() in nordic_regions:
196-
try:
197-
# Get Nordic energy mix data from cache
198-
nordic_data = (
199-
self._data_source.get_nordic_country_energy_mix_data()
200-
)
201-
region_data = nordic_data["data"].get(geo.region.upper())
202-
if region_data:
203-
emission_factor_g = region_data[
204-
"emission_factor"
205-
] # gCO2eq/kWh
206-
emission_factor_kg = (
207-
emission_factor_g / 1000
208-
) # Convert to kgCO2eq/kWh
209-
emissions = emission_factor_kg * energy.kWh # kgCO2eq
210-
logger.debug(
211-
f"Nordic region {geo.region}: Retrieved emissions using static factor "
212-
+ f"{emission_factor_g} gCO2eq/kWh: {emissions * 1000} g CO2eq"
213-
)
214-
return emissions
215-
except Exception as e:
216-
logger.warning(
217-
f"Error loading Nordic emissions data for {geo.region}: {e}. "
218-
+ "Falling back to default emission calculation."
219-
)
220-
221158
compute_with_regional_data: bool = (geo.region is not None) and (
222-
geo.country_iso_code.upper() in ["USA", "CAN"]
159+
geo.country_iso_code.upper() in ["USA", "CAN", "SWE", "NOR", "FIN"]
223160
)
224161

225162
if compute_with_regional_data:
@@ -233,16 +170,72 @@ def get_private_infra_emissions(self, energy: Energy, geo: GeoMetadata) -> float
233170
)
234171
return self.get_country_emissions(energy, geo)
235172

173+
def _try_get_nordic_region_emissions(
174+
self, energy: Energy, geo: GeoMetadata
175+
) -> Optional[float]:
176+
nordic_regions = {
177+
"SE1",
178+
"SE2",
179+
"SE3",
180+
"SE4",
181+
"NO1",
182+
"NO2",
183+
"NO3",
184+
"NO4",
185+
"NO5",
186+
"FI",
187+
}
188+
if geo.region is None:
189+
return None
190+
191+
region_upper = geo.region.upper()
192+
if region_upper not in nordic_regions:
193+
return None
194+
195+
try:
196+
nordic_data = self._data_source.get_nordic_country_energy_mix_data()
197+
region_data = nordic_data["data"].get(region_upper)
198+
if region_data:
199+
emission_factor_g = region_data["emission_factor"]
200+
emission_factor_kg = emission_factor_g / 1000
201+
emissions = emission_factor_kg * energy.kWh
202+
logger.debug(
203+
f"Nordic region {geo.region}: Retrieved emissions using static factor "
204+
+ f"{emission_factor_g} gCO2eq/kWh: {emissions * 1000} g CO2eq"
205+
)
206+
return emissions
207+
except Exception as e:
208+
logger.warning(
209+
f"Error loading Nordic emissions data for {geo.region}: {e}. "
210+
+ "Falling back to default emission calculation."
211+
)
212+
return None
213+
236214
def get_region_emissions(self, energy: Energy, geo: GeoMetadata) -> float:
237215
"""
238216
Computes emissions for a region on private infra.
239217
Given an quantity of power consumed, use regional data
240218
on emissions per unit power consumed or the mix of energy sources.
241219
https://github.com/responsibleproblemsolving/energy-usage#calculating-co2-emissions
220+
221+
get_private_infra_emissions
222+
├─ Electricity Maps API (si token)
223+
├─ get_region_emissions (USA/CAN/SWE/NOR/FIN)
224+
│ └─ _try_get_nordic_region_emissions (pour SWE/NOR/FIN)
225+
│ └─ country_emissions_data (pour USA)
226+
│ └─ country_energy_mix_data (pour CAN)
227+
└─ get_country_emissions (fallback)
228+
242229
:param energy: Mean power consumption of the process (kWh)
243230
:param geo: Country and region metadata.
244231
:return: CO2 emissions in kg
245232
"""
233+
# Handle Nordic regions (Sweden, Norway, Finland electricity bidding zones)
234+
nordic_emissions = self._try_get_nordic_region_emissions(energy, geo)
235+
if nordic_emissions is not None:
236+
return nordic_emissions
237+
238+
# Handle USA and Canada regional data
246239
try:
247240
country_emissions_data = self._data_source.get_country_emissions_data(
248241
geo.country_iso_code.lower()

tests/test_emissions.py

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ def test_get_emissions_PRIVATE_INFRA_NORDIC_REGION(self):
186186
# Nordic regions use static emission factors from the JSON file
187187
# SE2 has an emission factor specified in nordic_country_energy_mix.json
188188
assert isinstance(emissions, float)
189-
assert emissions > 0, "Nordic region emissions should be positive"
189+
self.assertAlmostEqual(emissions, 0.018, places=6)
190190

191191
def test_get_emissions_PRIVATE_INFRA_NORDIC_FINLAND(self):
192192
# WHEN
@@ -200,6 +200,23 @@ def test_get_emissions_PRIVATE_INFRA_NORDIC_FINLAND(self):
200200
# THEN
201201
# Finland (FI) should use Nordic static emission factors
202202
assert isinstance(emissions, float)
203-
assert emissions > 0, "Finland emissions should be positive"
204-
# With 2.5 kWh, emissions should be proportional to energy consumed
205-
assert emissions > 0.1, "Expected reasonable emission value for 2.5 kWh"
203+
expected_emissions = 0.072 * 2.5
204+
self.assertAlmostEqual(emissions, expected_emissions, places=6)
205+
206+
def test_get_emissions_PRIVATE_INFRA_NORDIC_REGION_uses_static_factor_without_token(
207+
self,
208+
):
209+
# GIVEN
210+
energy = Energy.from_energy(kWh=1.0)
211+
geo = GeoMetadata(country_iso_code="SWE", country_name="Sweden", region="SE2")
212+
213+
# WHEN
214+
emissions = self._emissions.get_private_infra_emissions(energy, geo)
215+
216+
# THEN
217+
expected_country = self._emissions.get_country_emissions(energy, geo)
218+
nordic_data = self._data_source.get_nordic_country_energy_mix_data()
219+
emission_factor_g = nordic_data["data"]["SE2"]["emission_factor"]
220+
expected_nordic = (emission_factor_g / 1000) * energy.kWh
221+
self.assertAlmostEqual(emissions, expected_nordic, places=6)
222+
self.assertNotAlmostEqual(emissions, expected_country, places=4)

0 commit comments

Comments
 (0)