Skip to content

Commit 3095efc

Browse files
authored
Merge pull request #1448 from Zhaohh0706/china-provincial-factors
feat(china): use official 2023 MEE/NBS provincial emission factors
2 parents 8a8776a + 4b1f847 commit 3095efc

6 files changed

Lines changed: 516 additions & 2 deletions

File tree

‎codecarbon/core/emissions.py‎

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
https://github.com/responsibleproblemsolving/energy-usage
77
"""
88

9+
import re
910
from typing import TYPE_CHECKING, Dict, Optional
1011

1112
from codecarbon.core import electricitymaps_api
@@ -23,6 +24,10 @@
2324
"FIN": {"FI"},
2425
}
2526

27+
_CHINA_REGION_SUFFIX = re.compile(
28+
r"\s+(autonomous region|municipality|province|sheng|shi|zizhiqu)$"
29+
)
30+
2631

2732
class Emissions:
2833
def __init__(
@@ -180,7 +185,8 @@ def get_private_infra_emissions(self, energy: Energy, geo: GeoMetadata) -> float
180185
geo.country_iso_code.upper() if geo.country_iso_code is not None else None
181186
)
182187
compute_with_regional_data: bool = (geo.region is not None) and (
183-
country_iso_code in ["USA", "CAN"] or self._is_supported_nordic_region(geo)
188+
country_iso_code in ["USA", "CAN", "CHN"]
189+
or self._is_supported_nordic_region(geo)
184190
)
185191

186192
if compute_with_regional_data:
@@ -231,6 +237,30 @@ def _is_supported_nordic_region(geo: GeoMetadata) -> bool:
231237
)
232238
return geo.region.upper() in country_regions
233239

240+
def _try_get_china_province_emissions(
241+
self, energy: Energy, geo: GeoMetadata
242+
) -> Optional[float]:
243+
"""
244+
Computes emissions with the official 2023 MEE/NBS provincial factor.
245+
Returns None if the province is unknown or not published (Tibet,
246+
Hong Kong, Macao and Taiwan), so the caller uses the national value.
247+
"""
248+
if geo.region is None:
249+
return None
250+
251+
name = _CHINA_REGION_SUFFIX.sub("", " ".join(geo.region.lower().split()))
252+
china_data = self._data_source.get_china_provincial_emissions_data()
253+
for province, province_data in china_data["data"].items():
254+
if name in province_data["names"]:
255+
emission_factor_g = province_data["emission_factor"]
256+
emissions = emission_factor_g / 1000 * energy.kWh
257+
logger.debug(
258+
f"China province {province}: Retrieved emissions using static factor "
259+
+ f"{emission_factor_g} gCO2/kWh: {emissions * 1000} g CO2"
260+
)
261+
return emissions
262+
return None
263+
234264
def get_region_emissions(self, energy: Energy, geo: GeoMetadata) -> float:
235265
"""
236266
Computes emissions for a region on private infra.
@@ -240,10 +270,11 @@ def get_region_emissions(self, energy: Energy, geo: GeoMetadata) -> float:
240270
241271
get_private_infra_emissions
242272
├─ Electricity Maps API (si token)
243-
├─ get_region_emissions (USA/CAN/SWE/NOR/FIN)
273+
├─ get_region_emissions (USA/CAN/CHN/SWE/NOR/FIN)
244274
│ └─ _try_get_nordic_region_emissions (pour SWE/NOR/FIN)
245275
│ └─ country_emissions_data (pour USA)
246276
│ └─ country_energy_mix_data (pour CAN)
277+
│ └─ _try_get_china_province_emissions (pour CHN)
247278
└─ get_country_emissions (fallback)
248279
249280
:param energy: Mean power consumption of the process (kWh)
@@ -260,6 +291,13 @@ def get_region_emissions(self, energy: Energy, geo: GeoMetadata) -> float:
260291
):
261292
return self.get_country_emissions(energy, geo)
262293

294+
# Handle China provinces (official MEE/NBS factors)
295+
if geo.country_iso_code is not None and geo.country_iso_code.upper() == "CHN":
296+
china_emissions = self._try_get_china_province_emissions(energy, geo)
297+
if china_emissions is not None:
298+
return china_emissions
299+
return self.get_country_emissions(energy, geo)
300+
263301
# Handle USA and Canada regional data
264302
try:
265303
country_emissions_data = self._data_source.get_country_emissions_data(
Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
1+
{
2+
"data": {
3+
"CN-BJ": {
4+
"emission_factor": 555.4,
5+
"unit": "gCO2/kWh",
6+
"description": "Beijing",
7+
"year": 2023,
8+
"names": [
9+
"beijing"
10+
]
11+
},
12+
"CN-TJ": {
13+
"emission_factor": 679.6,
14+
"unit": "gCO2/kWh",
15+
"description": "Tianjin",
16+
"year": 2023,
17+
"names": [
18+
"tianjin"
19+
]
20+
},
21+
"CN-HE": {
22+
"emission_factor": 651.6,
23+
"unit": "gCO2/kWh",
24+
"description": "Hebei",
25+
"year": 2023,
26+
"names": [
27+
"hebei"
28+
]
29+
},
30+
"CN-SX": {
31+
"emission_factor": 663.4,
32+
"unit": "gCO2/kWh",
33+
"description": "Shanxi",
34+
"year": 2023,
35+
"names": [
36+
"shanxi"
37+
]
38+
},
39+
"CN-NM": {
40+
"emission_factor": 647.9,
41+
"unit": "gCO2/kWh",
42+
"description": "Inner Mongolia",
43+
"year": 2023,
44+
"names": [
45+
"inner mongolia",
46+
"nei mongol",
47+
"neimenggu"
48+
]
49+
},
50+
"CN-LN": {
51+
"emission_factor": 487.8,
52+
"unit": "gCO2/kWh",
53+
"description": "Liaoning",
54+
"year": 2023,
55+
"names": [
56+
"liaoning"
57+
]
58+
},
59+
"CN-JL": {
60+
"emission_factor": 467.1,
61+
"unit": "gCO2/kWh",
62+
"description": "Jilin",
63+
"year": 2023,
64+
"names": [
65+
"jilin"
66+
]
67+
},
68+
"CN-HL": {
69+
"emission_factor": 522.9,
70+
"unit": "gCO2/kWh",
71+
"description": "Heilongjiang",
72+
"year": 2023,
73+
"names": [
74+
"heilongjiang"
75+
]
76+
},
77+
"CN-SH": {
78+
"emission_factor": 573.7,
79+
"unit": "gCO2/kWh",
80+
"description": "Shanghai",
81+
"year": 2023,
82+
"names": [
83+
"shanghai"
84+
]
85+
},
86+
"CN-JS": {
87+
"emission_factor": 582.7,
88+
"unit": "gCO2/kWh",
89+
"description": "Jiangsu",
90+
"year": 2023,
91+
"names": [
92+
"jiangsu"
93+
]
94+
},
95+
"CN-ZJ": {
96+
"emission_factor": 497.4,
97+
"unit": "gCO2/kWh",
98+
"description": "Zhejiang",
99+
"year": 2023,
100+
"names": [
101+
"zhejiang"
102+
]
103+
},
104+
"CN-AH": {
105+
"emission_factor": 655.3,
106+
"unit": "gCO2/kWh",
107+
"description": "Anhui",
108+
"year": 2023,
109+
"names": [
110+
"anhui"
111+
]
112+
},
113+
"CN-FJ": {
114+
"emission_factor": 421.1,
115+
"unit": "gCO2/kWh",
116+
"description": "Fujian",
117+
"year": 2023,
118+
"names": [
119+
"fujian"
120+
]
121+
},
122+
"CN-JX": {
123+
"emission_factor": 583.6,
124+
"unit": "gCO2/kWh",
125+
"description": "Jiangxi",
126+
"year": 2023,
127+
"names": [
128+
"jiangxi"
129+
]
130+
},
131+
"CN-SD": {
132+
"emission_factor": 619.1,
133+
"unit": "gCO2/kWh",
134+
"description": "Shandong",
135+
"year": 2023,
136+
"names": [
137+
"shandong"
138+
]
139+
},
140+
"CN-HA": {
141+
"emission_factor": 589.7,
142+
"unit": "gCO2/kWh",
143+
"description": "Henan",
144+
"year": 2023,
145+
"names": [
146+
"henan"
147+
]
148+
},
149+
"CN-HB": {
150+
"emission_factor": 404.4,
151+
"unit": "gCO2/kWh",
152+
"description": "Hubei",
153+
"year": 2023,
154+
"names": [
155+
"hubei"
156+
]
157+
},
158+
"CN-HN": {
159+
"emission_factor": 497.6,
160+
"unit": "gCO2/kWh",
161+
"description": "Hunan",
162+
"year": 2023,
163+
"names": [
164+
"hunan"
165+
]
166+
},
167+
"CN-GD": {
168+
"emission_factor": 441.9,
169+
"unit": "gCO2/kWh",
170+
"description": "Guangdong",
171+
"year": 2023,
172+
"names": [
173+
"guangdong"
174+
]
175+
},
176+
"CN-GX": {
177+
"emission_factor": 447.6,
178+
"unit": "gCO2/kWh",
179+
"description": "Guangxi",
180+
"year": 2023,
181+
"names": [
182+
"guangxi",
183+
"guangxi zhuang"
184+
]
185+
},
186+
"CN-HI": {
187+
"emission_factor": 364.8,
188+
"unit": "gCO2/kWh",
189+
"description": "Hainan",
190+
"year": 2023,
191+
"names": [
192+
"hainan"
193+
]
194+
},
195+
"CN-CQ": {
196+
"emission_factor": 558.1,
197+
"unit": "gCO2/kWh",
198+
"description": "Chongqing",
199+
"year": 2023,
200+
"names": [
201+
"chongqing"
202+
]
203+
},
204+
"CN-SC": {
205+
"emission_factor": 156.4,
206+
"unit": "gCO2/kWh",
207+
"description": "Sichuan",
208+
"year": 2023,
209+
"names": [
210+
"sichuan"
211+
]
212+
},
213+
"CN-GZ": {
214+
"emission_factor": 568.3,
215+
"unit": "gCO2/kWh",
216+
"description": "Guizhou",
217+
"year": 2023,
218+
"names": [
219+
"guizhou"
220+
]
221+
},
222+
"CN-YN": {
223+
"emission_factor": 133.3,
224+
"unit": "gCO2/kWh",
225+
"description": "Yunnan",
226+
"year": 2023,
227+
"names": [
228+
"yunnan"
229+
]
230+
},
231+
"CN-SN": {
232+
"emission_factor": 633.5,
233+
"unit": "gCO2/kWh",
234+
"description": "Shaanxi",
235+
"year": 2023,
236+
"names": [
237+
"shaanxi"
238+
]
239+
},
240+
"CN-GS": {
241+
"emission_factor": 447.1,
242+
"unit": "gCO2/kWh",
243+
"description": "Gansu",
244+
"year": 2023,
245+
"names": [
246+
"gansu"
247+
]
248+
},
249+
"CN-QH": {
250+
"emission_factor": 179.6,
251+
"unit": "gCO2/kWh",
252+
"description": "Qinghai",
253+
"year": 2023,
254+
"names": [
255+
"qinghai"
256+
]
257+
},
258+
"CN-NX": {
259+
"emission_factor": 618.7,
260+
"unit": "gCO2/kWh",
261+
"description": "Ningxia",
262+
"year": 2023,
263+
"names": [
264+
"ningxia",
265+
"ningxia hui"
266+
]
267+
},
268+
"CN-XJ": {
269+
"emission_factor": 602.1,
270+
"unit": "gCO2/kWh",
271+
"description": "Xinjiang",
272+
"year": 2023,
273+
"names": [
274+
"xinjiang",
275+
"xinjiang uygur",
276+
"xinjiang uyghur"
277+
]
278+
}
279+
},
280+
"metadata": {
281+
"source": "Ministry of Ecology and Environment (MEE) and National Bureau of Statistics (NBS) of China, Announcement 2025 No. 47 (published 2025-12-31), 'Announcement on the release of the 2023 electricity CO2 emission factors', Table 3: provincial average electricity CO2 emission factors, 2023.",
282+
"source_url": "https://www.mee.gov.cn/xxgk2018/xxgk/xxgk01/202512/t20251231_1139517.html",
283+
"announcement": "2025 No. 47",
284+
"published": "2025-12-31",
285+
"year": 2023,
286+
"unit": "gCO2/kWh",
287+
"convention": "CO2 only (not CO2eq, no lifecycle emissions), average over all electricity generated in the province (thermal, hydro, wind, nuclear, solar), location-based, accounting for inter-provincial electricity transfers. Values are the official figures as published (kgCO2/kWh x 1000).",
288+
"not_published": "Tibet (Xizang), Hong Kong, Macao and Taiwan are not in the official table; they have no entry here and fall back to the national value for China.",
289+
"names": "Lower-case province names as returned by the geolocation services (ipinfo.io returns the short English name, e.g. 'Inner Mongolia', 'Ningxia', 'Xinjiang', 'Guangxi'), plus common variants. Matching is case-insensitive, ignores a trailing 'province', 'municipality' or 'autonomous region', and is exact otherwise.",
290+
"last_updated": "2026-10-12"
291+
}
292+
}

0 commit comments

Comments
 (0)