hestia-earth-models 0.75.0__py3-none-any.whl → 0.75.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of hestia-earth-models might be problematic. Click here for more details.

Files changed (58) hide show
  1. hestia_earth/models/aware/scarcityWeightedWaterUse.py +2 -4
  2. hestia_earth/models/aware2_0/scarcityWeightedWaterUse.py +2 -5
  3. hestia_earth/models/chaudharyBrooks2018/utils.py +2 -2
  4. hestia_earth/models/cml2001Baseline/abioticResourceDepletionFossilFuels.py +3 -2
  5. hestia_earth/models/cml2001Baseline/abioticResourceDepletionMineralsAndMetals.py +13 -6
  6. hestia_earth/models/config/Cycle.json +15 -0
  7. hestia_earth/models/cycle/product/economicValueShare.py +4 -4
  8. hestia_earth/models/ecoalimV9/utils.py +3 -3
  9. hestia_earth/models/ecoinventV3/utils.py +2 -2
  10. hestia_earth/models/ecoinventV3AndEmberClimate/utils.py +2 -2
  11. hestia_earth/models/emissionNotRelevant/__init__.py +2 -2
  12. hestia_earth/models/environmentalFootprintV3_1/environmentalFootprintSingleOverallScore.py +1 -2
  13. hestia_earth/models/environmentalFootprintV3_1/soilQualityIndexLandOccupation.py +8 -5
  14. hestia_earth/models/faostat2018/utils.py +3 -3
  15. hestia_earth/models/frischknechtEtAl2000/ionisingRadiationKbqU235Eq.py +5 -4
  16. hestia_earth/models/geospatialDatabase/ecoClimateZone.py +2 -2
  17. hestia_earth/models/geospatialDatabase/histosol.py +31 -11
  18. hestia_earth/models/hestia/aboveGroundCropResidueTotal.py +2 -2
  19. hestia_earth/models/hestia/landCover_utils.py +8 -12
  20. hestia_earth/models/hestia/management.py +3 -3
  21. hestia_earth/models/hestia/seed_emissions.py +2 -2
  22. hestia_earth/models/hestia/soilClassification.py +31 -13
  23. hestia_earth/models/ipcc2019/animal/pastureGrass.py +3 -1
  24. hestia_earth/models/ipcc2019/burning_utils.py +406 -4
  25. hestia_earth/models/ipcc2019/ch4ToAirEntericFermentation.py +12 -9
  26. hestia_earth/models/ipcc2019/ch4ToAirExcreta.py +28 -10
  27. hestia_earth/models/ipcc2019/ch4ToAirOrganicSoilCultivation.py +8 -11
  28. hestia_earth/models/ipcc2019/co2ToAirOrganicSoilCultivation.py +9 -12
  29. hestia_earth/models/ipcc2019/emissionsToAirOrganicSoilBurning.py +516 -0
  30. hestia_earth/models/ipcc2019/n2OToAirOrganicSoilCultivationDirect.py +10 -13
  31. hestia_earth/models/ipcc2019/nonCo2EmissionsToAirNaturalVegetationBurning.py +56 -433
  32. hestia_earth/models/ipcc2019/organicSoilCultivation_utils.py +2 -2
  33. hestia_earth/models/ipcc2019/pastureGrass.py +3 -1
  34. hestia_earth/models/ipcc2019/pastureGrass_utils.py +6 -3
  35. hestia_earth/models/ipcc2019/utils.py +3 -2
  36. hestia_earth/models/linkedImpactAssessment/emissions.py +2 -2
  37. hestia_earth/models/mocking/search-results.json +1 -1
  38. hestia_earth/models/requirements.py +2 -2
  39. hestia_earth/models/utils/aggregated.py +2 -2
  40. hestia_earth/models/utils/background_emissions.py +6 -5
  41. hestia_earth/models/utils/blank_node.py +68 -0
  42. hestia_earth/models/utils/ecoClimateZone.py +7 -8
  43. hestia_earth/models/utils/excretaManagement.py +3 -3
  44. hestia_earth/models/utils/feedipedia.py +7 -7
  45. hestia_earth/models/utils/impact_assessment.py +3 -0
  46. hestia_earth/models/utils/input.py +2 -2
  47. hestia_earth/models/utils/liveAnimal.py +4 -4
  48. hestia_earth/models/utils/lookup.py +15 -20
  49. hestia_earth/models/utils/property.py +3 -3
  50. hestia_earth/models/utils/term.py +5 -5
  51. hestia_earth/models/version.py +1 -1
  52. hestia_earth/orchestrator/models/transformations.py +2 -2
  53. hestia_earth/orchestrator/strategies/merge/merge_node.py +32 -2
  54. {hestia_earth_models-0.75.0.dist-info → hestia_earth_models-0.75.2.dist-info}/METADATA +2 -2
  55. {hestia_earth_models-0.75.0.dist-info → hestia_earth_models-0.75.2.dist-info}/RECORD +58 -57
  56. {hestia_earth_models-0.75.0.dist-info → hestia_earth_models-0.75.2.dist-info}/WHEEL +0 -0
  57. {hestia_earth_models-0.75.0.dist-info → hestia_earth_models-0.75.2.dist-info}/licenses/LICENSE +0 -0
  58. {hestia_earth_models-0.75.0.dist-info → hestia_earth_models-0.75.2.dist-info}/top_level.txt +0 -0
@@ -39,7 +39,7 @@ REQUIREMENTS = {
39
39
  "site": {
40
40
  "@type": "Site",
41
41
  "measurements": [
42
- {"@type": "Measurement", "value": "", "term.@id": "histosol"},
42
+ {"@type": "Measurement", "value": "", "term.@id": "organicSoils"},
43
43
  {"@type": "Measurement", "value": "", "term.@id": "ecoClimateZone"}
44
44
  ]
45
45
  },
@@ -164,10 +164,7 @@ def _should_run(cycle: dict):
164
164
  seed = gen_seed(cycle, MODEL, TERM_ID)
165
165
  rng = np.random.default_rng(seed)
166
166
 
167
- def _get_measurement_content(term_id: str):
168
- return most_relevant_measurement_value(measurements, term_id, end_date)
169
-
170
- histosol = _get_measurement_content('histosol')
167
+ organic_soils = most_relevant_measurement_value(measurements, "organicSoils", end_date)
171
168
  eco_climate_zone = get_eco_climate_zone_value(cycle, as_enum=True)
172
169
  organic_soil_category = assign_organic_soil_category(cycle, log_id=TERM_ID)
173
170
 
@@ -183,7 +180,7 @@ def _should_run(cycle: dict):
183
180
  organic_soil_category=organic_soil_category,
184
181
  emission_factor=format_nd_array(emission_factor),
185
182
  land_occupation=format_float(land_occupation),
186
- histosol=format_float(histosol)
183
+ organic_soils=format_float(organic_soils)
187
184
  )
188
185
 
189
186
  should_run = all([
@@ -193,22 +190,22 @@ def _should_run(cycle: dict):
193
190
  var is not None for var in [
194
191
  emission_factor,
195
192
  land_occupation,
196
- histosol
193
+ organic_soils
197
194
  ]
198
195
  )
199
196
  ])
200
197
 
201
198
  logShouldRun(cycle, MODEL, TERM_ID, should_run, methodTier=TIER)
202
199
 
203
- return should_run, emission_factor, histosol, land_occupation
200
+ return should_run, emission_factor, organic_soils, land_occupation
204
201
 
205
202
 
206
- def _run(emission_factor: npt.NDArray, histosol: float, land_occupation: float):
207
- result = calc_emission(TERM_ID, emission_factor, histosol, land_occupation)
203
+ def _run(emission_factor: npt.NDArray, organic_soils: float, land_occupation: float):
204
+ result = calc_emission(TERM_ID, emission_factor, organic_soils, land_occupation)
208
205
  descriptive_stats = calc_descriptive_stats(result, _STATS_DEFINITION)
209
206
  return [_emission(descriptive_stats)]
210
207
 
211
208
 
212
209
  def run(cycle: dict):
213
- should_run, emission_factor, histosol, land_occupation = _should_run(cycle)
214
- return _run(emission_factor, histosol, land_occupation) if should_run else []
210
+ should_run, emission_factor, organic_soils, land_occupation = _should_run(cycle)
211
+ return _run(emission_factor, organic_soils, land_occupation) if should_run else []
@@ -0,0 +1,516 @@
1
+ from functools import lru_cache, reduce
2
+ import numpy as np
3
+ import numpy.typing as npt
4
+ from typing import Callable, Union
5
+
6
+ from hestia_earth.utils.stats import gen_seed
7
+ from hestia_earth.utils.tools import safe_parse_float
8
+
9
+ from hestia_earth.models.utils import split_on_condition
10
+ from hestia_earth.models.utils.blank_node import (
11
+ closest_end_date, closest_depth, filter_list_term_id, get_node_value, group_nodes_by_year, pick_shallowest,
12
+ select_nodes_by, split_nodes_by_dates
13
+ )
14
+ from hestia_earth.models.utils.constant import Units, get_atomic_conversion
15
+ from hestia_earth.models.utils.ecoClimateZone import EcoClimateZone, get_eco_climate_zone_value
16
+ from hestia_earth.models.utils.emission import _new_emission
17
+ from hestia_earth.models.utils.site import related_cycles
18
+ from hestia_earth.models.utils.term import get_lookup_value
19
+
20
+ from . import MODEL
21
+ from .biomass_utils import get_valid_management_nodes, summarise_land_cover_nodes
22
+ from .burning_utils import (
23
+ calc_emission, DEFAULT_FACTOR, EmissionCategory, EXCLUDED_ECO_CLIMATE_ZONES, EXCLUDED_SITE_TYPES, FuelCategory,
24
+ ITERATIONS, get_emission_category, get_percent_burned, get_sample_func, Inventory, InventoryYear,
25
+ NATURAL_VEGETATION_CATEGORIES, run_emission, sample_fuel_factor, AMORTISATION_PERIOD, log_emission_data, TIER
26
+ )
27
+
28
+ REQUIREMENTS = {
29
+ "Cycle": {
30
+ "site": {
31
+ "@type": "Site",
32
+ "management": [
33
+ {
34
+ "@type": "Management",
35
+ "value": "",
36
+ "term.termType": "landCover",
37
+ "endDate": "",
38
+ "optional": {
39
+ "startDate": ""
40
+ }
41
+ }
42
+ ],
43
+ "measurements": [
44
+ {
45
+ "@type": "Measurement",
46
+ "value": "",
47
+ "term.@id": "ecoClimateZone",
48
+ "none": {
49
+ "value": ["5, 6"]
50
+ }
51
+ },
52
+ {
53
+ "@type": "Measurement",
54
+ "value": "",
55
+ "term.@id": "organicSoils"
56
+ }
57
+ ],
58
+ "none": {
59
+ "siteType": ["glass or high accessible cover"]
60
+ }
61
+ }
62
+ }
63
+ }
64
+ LOOKUPS = {
65
+ "emission": [
66
+ "IPCC_2013_G_EMITTED_PER_KG_DRY_MATTER_COMBUSTED_EXTRATROPICAL_ORGANIC_SOILS_value",
67
+ "IPCC_2013_G_EMITTED_PER_KG_DRY_MATTER_COMBUSTED_TROPICAL_ORGANIC_SOILS_value"
68
+ ],
69
+ "ipcc2019FuelCategory_tonnesDryMatterCombustedPerHaBurned": "value",
70
+ "landCover": "BIOMASS_CATEGORY",
71
+ "region-percentageAreaBurnedDuringForestClearance": "percentage_area_burned_during_forest_clearance"
72
+ }
73
+ RETURNS = {
74
+ "Emission": [{
75
+ "value": "",
76
+ "sd": "",
77
+ "min": "",
78
+ "max": "",
79
+ "statsDefinition": "simulated",
80
+ "observations": "",
81
+ "dates": "",
82
+ "methodClassification": "tier 1 model",
83
+ "depth": 30
84
+ }]
85
+ }
86
+ TERM_ID = 'ch4ToAirOrganicSoilBurning,co2ToAirOrganicSoilBurning,coToAirOrganicSoilBurning,n2OToAirOrganicSoilBurningDirect,nh3ToAirOrganicSoilBurning,noxToAirOrganicSoilBurning' # noqa: E501
87
+
88
+ _EMISSION_TERM_IDS = TERM_ID.split(",")
89
+
90
+ _ORGANIC_SOILS_TERM_ID = "organicSoils"
91
+ _DEFAULT_ORGANIC_SOILS = 0
92
+ _DEPTH_UPPER = 0
93
+ _DEPTH_LOWER = 30 # TODO: add depth
94
+
95
+ _CONVERSION_FACTORS = {
96
+ "co2ToAirOrganicSoilBurning": get_atomic_conversion(Units.KG_CO2, Units.TO_C)
97
+ }
98
+
99
+ _ECO_CLIMATE_ZONE_TO_FUEL_CATEGORY = {
100
+ EcoClimateZone.WARM_TEMPERATE_MOIST: FuelCategory.DRAINED_EXTRATROPICAL_ORGANIC_SOILS_WILDFIRE,
101
+ EcoClimateZone.WARM_TEMPERATE_DRY: FuelCategory.DRAINED_EXTRATROPICAL_ORGANIC_SOILS_WILDFIRE,
102
+ EcoClimateZone.COOL_TEMPERATE_MOIST: FuelCategory.DRAINED_EXTRATROPICAL_ORGANIC_SOILS_WILDFIRE,
103
+ EcoClimateZone.COOL_TEMPERATE_DRY: FuelCategory.DRAINED_EXTRATROPICAL_ORGANIC_SOILS_WILDFIRE,
104
+ EcoClimateZone.BOREAL_MOIST: FuelCategory.DRAINED_EXTRATROPICAL_ORGANIC_SOILS_WILDFIRE,
105
+ EcoClimateZone.BOREAL_DRY: FuelCategory.DRAINED_EXTRATROPICAL_ORGANIC_SOILS_WILDFIRE,
106
+ EcoClimateZone.TROPICAL_MONTANE: FuelCategory.DRAINED_TROPICAL_ORGANIC_SOILS_WILDFIRE,
107
+ EcoClimateZone.TROPICAL_WET: FuelCategory.DRAINED_TROPICAL_ORGANIC_SOILS_WILDFIRE,
108
+ EcoClimateZone.TROPICAL_MOIST: FuelCategory.DRAINED_TROPICAL_ORGANIC_SOILS_WILDFIRE,
109
+ EcoClimateZone.TROPICAL_DRY: FuelCategory.DRAINED_TROPICAL_ORGANIC_SOILS_WILDFIRE
110
+ }
111
+
112
+
113
+ def _emission(term_id: str, kwargs) -> dict:
114
+ """
115
+ Build a HESTIA [Emission node](https://www.hestia.earth/schema/Emission) using model output data.
116
+ """
117
+ value_keys, other_keys = split_on_condition([*kwargs], lambda k: k in ("value", "sd", "min", "max"))
118
+ emission = _new_emission(term=term_id, model=MODEL, **{k: kwargs.get(k) for k in value_keys})
119
+ return emission | {
120
+ "methodTier": TIER,
121
+ "depth": _DEPTH_LOWER,
122
+ **{k: kwargs.get(k) for k in other_keys}
123
+ }
124
+
125
+
126
+ def _get_emission_factor(term_id: str, emission_category: EmissionCategory) -> dict:
127
+ """
128
+ Retrieve distribution data for a specific emission and emission category.
129
+ """
130
+ TERM_TYPE = "emission"
131
+ TARGET_DATA = "value" # No uncertainty data available yet
132
+
133
+ COLUMN_ROOT = "IPCC_2013_G_EMITTED_PER_KG_DRY_MATTER_COMBUSTED"
134
+
135
+ data = {
136
+ TARGET_DATA: get_lookup_value(
137
+ {"@id": term_id, "termType": TERM_TYPE},
138
+ "_".join([COLUMN_ROOT, emission_category.name, TARGET_DATA]),
139
+ model=MODEL,
140
+ term=term_id
141
+ )
142
+ }
143
+
144
+ return (
145
+ {
146
+ k: parsed for k, v in data.items() if (parsed := safe_parse_float(v, default=None)) is not None
147
+ } # remove missing
148
+ or DEFAULT_FACTOR # if parsed dict empty, return default
149
+ )
150
+
151
+
152
+ def _sample_emission_factor(
153
+ term_id: str,
154
+ emission_category: EmissionCategory,
155
+ *,
156
+ seed: Union[int, np.random.Generator, None] = None
157
+ ) -> npt.NDArray:
158
+ """
159
+ Generate random samples from an emission factor's distribution data.
160
+ """
161
+ factor_data = _get_emission_factor(term_id, emission_category)
162
+ sample_func = get_sample_func(factor_data)
163
+ return sample_func(iterations=ITERATIONS, seed=seed, **factor_data)
164
+
165
+
166
+ def _calc_burnt_fuel(
167
+ area_converted: npt.NDArray, fuel_factor: npt.NDArray, frac_burnt: npt.NDArray, frac_organic_soils: float
168
+ ) -> npt.NDArray:
169
+ """
170
+ Calculate the amount of fuel burnt during a fire event.
171
+
172
+ Parameters
173
+ ----------
174
+ area_converted : NDArray
175
+ Area of land converted (ha).
176
+ fuel_factor : NDArray
177
+ Conversion factor (kg fuel per ha of land cover converted).
178
+ frac_burnt : NDArray
179
+ The fraction of land converted using burning during a land use change event (decimal percentage, 0-1).
180
+ frac_organic_soils : float
181
+ The fraction of land occupied by organic soils (decimal percentage, 0-1)
182
+
183
+ Returns
184
+ -------
185
+ NDArray
186
+ The mass of burnt fuel (kg)
187
+ """
188
+ return area_converted * fuel_factor * frac_burnt * frac_organic_soils
189
+
190
+
191
+ def _build_fuel_burnt_accumulator(
192
+ percent_burned: npt.ArrayLike,
193
+ eco_climate_zone: EcoClimateZone,
194
+ sample_fuel_factor_func: Callable[[FuelCategory], npt.NDArray]
195
+ ):
196
+ """
197
+ Build an `accumulate_fuel_burnt` function to reduce natural vegetation deltas into mass of fuel burnt per
198
+ `_FuelCategory`.
199
+
200
+ Parameters
201
+ ----------
202
+ percent_burned : NDArray
203
+ The percentage of land converted using burning during a land use change event (percentage, 0-100%).
204
+ eco_climate_zone : EcoClimateZone
205
+ The eco-climate zone of the Site.
206
+ sample_fuel_factor_func : Callable[[_FuelCategory], npt.NDArray]
207
+ Function to sample fuel factor parameter.
208
+
209
+ Returns
210
+ -------
211
+ NDArray
212
+ The mass of burnt fuel (kg)
213
+ """
214
+ frac_burnt = percent_burned / 100
215
+
216
+ def accumulate_fuel_burnt(
217
+ result: dict[FuelCategory, npt.NDArray], delta: float, percent_organic_soils: float
218
+ ) -> dict[FuelCategory, npt.NDArray]:
219
+ """
220
+ Calculate the amount of fuel burnt when natural vegetation is lost. Accumulate fuel burnt by `_FuelCategory`.
221
+
222
+ Parameters
223
+ ----------
224
+ result : dict[_FuelCategory, npt.NDArray]
225
+ A dict with the format `{_FuelCategory: kg_fuel_burnt (npt.NDArray)}`.
226
+ delta : float
227
+ The change in land cover for the biomass category (% area).
228
+ percent_organic_soils : NDArray
229
+ The percentage of land occupied by organic soils (percentage, 0-100%).
230
+
231
+ Returns
232
+ -------
233
+ dict[_FuelCategory, npt.NDArray]
234
+ """
235
+ frac_organic_soils = percent_organic_soils / 100
236
+
237
+ fuel_category = _ECO_CLIMATE_ZONE_TO_FUEL_CATEGORY.get(eco_climate_zone)
238
+ fuel_factor = sample_fuel_factor_func(fuel_category)
239
+
240
+ area_converted = abs(delta) / 100 if delta < 0 else 0 # We only care about losses
241
+
242
+ already_burnt = result.get(fuel_category, np.array(0))
243
+
244
+ update_dict = {} if area_converted == 0 else {
245
+ fuel_category: already_burnt + _calc_burnt_fuel(area_converted, fuel_factor, frac_burnt, frac_organic_soils)
246
+ }
247
+
248
+ return result | update_dict
249
+
250
+ return accumulate_fuel_burnt
251
+
252
+
253
+ def _compileInventory(
254
+ cycle: dict,
255
+ site: dict,
256
+ land_cover_nodes: list[dict],
257
+ organic_soil_nodes: list[dict],
258
+ eco_climate_zone: EcoClimateZone
259
+ ):
260
+ """
261
+ Compile the run data for the model, collating data from `site.management` and related cycles. An annualised
262
+ inventory of land cover change and natural vegetation burning events is constructed. Emissions from burning events
263
+ are estimated, amortised over 20 years and allocated to cycles.
264
+
265
+ Parameters
266
+ ----------
267
+ cycle : dict
268
+ The HESTIA [Cycle](https://www.hestia.earth/schema/Cycle) the model is running on.
269
+ site : dict
270
+ The HESTIA [Site](https://www.hestia.earth/schema/Site) the Cycle takes place on.
271
+ land_cover_nodes : list[dict]
272
+ Valid land cover [Management nodes](https://www.hestia.earth/schema/Management) extracted from the Site.
273
+ organic_soil_nodes : list[dict]
274
+ Valid `organicSoils` [Measurement nodes](https://www.hestia.earth/schema/Measurement) extracted from the Site.
275
+ eco_climate_zone : EcoClimateZone
276
+ The eco-climate zone of the Site.
277
+
278
+ Returns
279
+ -------
280
+ should_run : bool
281
+ Whether the model should be run.
282
+ inventory : Inventory
283
+ An inventory of model data.
284
+ logs : dict
285
+ Data about the inventory compilation to be logged.
286
+ """
287
+ cycle_id = cycle.get("@id")
288
+ related_cycles_ = related_cycles(site, cycles_mapping={cycle_id: cycle})
289
+
290
+ seed = gen_seed(site, MODEL, TERM_ID)
291
+ rng = np.random.default_rng(seed)
292
+
293
+ cycles_grouped = group_nodes_by_year(related_cycles_)
294
+ land_cover_grouped = group_nodes_by_year(land_cover_nodes)
295
+ percent_burned = get_percent_burned(site)
296
+
297
+ @lru_cache(maxsize=len(FuelCategory))
298
+ def sample_fuel_factor_(*args):
299
+ """Fuel factors should not be re-sampled between years, so cache results."""
300
+ return sample_fuel_factor(*args, _EMISSION_TERM_IDS, seed=rng)
301
+
302
+ @lru_cache(maxsize=len(_EMISSION_TERM_IDS)*len(EmissionCategory))
303
+ def sample_emission_factor_(*args):
304
+ """Emission factors should not be re-sampled between years, so cache results."""
305
+ return _sample_emission_factor(*args, seed=rng)
306
+
307
+ accumulate_fuel_burnt = _build_fuel_burnt_accumulator(
308
+ percent_burned,
309
+ eco_climate_zone,
310
+ sample_fuel_factor_
311
+ )
312
+
313
+ def buildInventory_year(inventory: Inventory, year: int) -> dict:
314
+ """
315
+ Parameters
316
+ ----------
317
+ inventory : Inventory
318
+ An inventory of model data.
319
+ year : int
320
+ The year of the inventory to build.
321
+
322
+ Returns
323
+ -------
324
+ inventory : dict
325
+ An inventory of model data, updated to include the input year.
326
+ """
327
+ most_relevant_organic_soils_node = select_nodes_by(
328
+ organic_soil_nodes,
329
+ [
330
+ lambda nodes: closest_end_date(nodes, year),
331
+ lambda nodes: pick_shallowest(nodes, default={}) # resolve down to a single node
332
+ ]
333
+ )
334
+
335
+ percent_organic_soils = get_node_value(most_relevant_organic_soils_node, default=_DEFAULT_ORGANIC_SOILS)
336
+
337
+ land_cover_nodes = land_cover_grouped.get(
338
+ next(
339
+ (k for k in sorted(land_cover_grouped) if k >= year), # backfill if possible
340
+ min(land_cover_grouped, key=lambda k: abs(k - year)) # else forward-fill
341
+ ),
342
+ []
343
+ )
344
+
345
+ biomass_category_summary = summarise_land_cover_nodes(land_cover_nodes)
346
+ prev_biomass_category_summary = inventory.get(year-1, {}).get("biomass_category_summary", {})
347
+
348
+ natural_vegetation_delta = {
349
+ category: biomass_category_summary.get(category, 0) - prev_biomass_category_summary.get(category, 0)
350
+ for category in NATURAL_VEGETATION_CATEGORIES
351
+ }
352
+
353
+ fuel_burnt_per_category = reduce(
354
+ lambda result, delta: accumulate_fuel_burnt(result, delta, percent_organic_soils),
355
+ natural_vegetation_delta.values(),
356
+ dict()
357
+ )
358
+
359
+ annual_emissions = {
360
+ term_id: sum(
361
+ calc_emission(
362
+ amount,
363
+ sample_emission_factor_(term_id, get_emission_category(fuel_category)),
364
+ _CONVERSION_FACTORS.get(term_id, 1)
365
+ )
366
+ for fuel_category, amount in fuel_burnt_per_category.items()
367
+ ) for term_id in _EMISSION_TERM_IDS
368
+ }
369
+
370
+ previous_years = list(inventory.keys())
371
+ amortisation_slice_index = max(0, len(previous_years) - (AMORTISATION_PERIOD - 1))
372
+ amortisation_years = previous_years[amortisation_slice_index:] # get the previous 19 years, if available
373
+
374
+ amortised_emissions = {
375
+ term_id: 0.05 * (
376
+ annual_emissions[term_id] + sum(
377
+ inventory[year_]["annual_emissions"][term_id] for year_ in amortisation_years
378
+ )
379
+ ) for term_id in _EMISSION_TERM_IDS
380
+ }
381
+
382
+ cycles = cycles_grouped.get(year, [])
383
+ total_cycle_duration = sum(c.get("fraction_of_group_duration", 0) for c in cycles)
384
+
385
+ share_of_emissions = {
386
+ cycle["@id"]: cycle.get("fraction_of_group_duration", 0) / total_cycle_duration
387
+ for cycle in cycles
388
+ }
389
+
390
+ allocated_emissions = {
391
+ term_id: {
392
+ cycle_id: share_of_emission * amortised_emissions[term_id]
393
+ for cycle_id, share_of_emission in share_of_emissions.items()
394
+ }
395
+ for term_id in _EMISSION_TERM_IDS
396
+ }
397
+
398
+ inventory[year] = InventoryYear(
399
+ biomass_category_summary=biomass_category_summary,
400
+ natural_vegetation_delta=natural_vegetation_delta,
401
+ fuel_burnt_per_category=fuel_burnt_per_category,
402
+ annual_emissions=annual_emissions,
403
+ amortised_emissions=amortised_emissions,
404
+ share_of_emissions=share_of_emissions,
405
+ allocated_emissions=allocated_emissions,
406
+ percent_organic_soils=percent_organic_soils
407
+ )
408
+
409
+ return inventory
410
+
411
+ all_years = list(cycles_grouped.keys()) + list(land_cover_grouped.keys())
412
+ min_year, max_year = min(all_years), max(all_years)
413
+
414
+ inventory = reduce(buildInventory_year, range(min_year, max_year+1), dict())
415
+
416
+ n_land_cover_years = len(land_cover_grouped)
417
+
418
+ logs = {
419
+ "n_land_cover_years": n_land_cover_years,
420
+ "percent_burned": percent_burned,
421
+ "seed": seed,
422
+ }
423
+
424
+ should_run = bool(inventory and n_land_cover_years > 1)
425
+
426
+ return should_run, inventory, logs
427
+
428
+
429
+ def _should_run(cycle: dict):
430
+ """
431
+ Extract, organise and pre-process required data from the input [Cycle node](https://www.hestia.earth/schema/Site)
432
+ and determine whether the model should run.
433
+
434
+ Parameters
435
+ ----------
436
+ cycle : dict
437
+ A HESTIA [Cycle](https://www.hestia.earth/schema/Cycle).
438
+
439
+ Returns
440
+ -------
441
+ tuple[bool, Inventory]
442
+ should_run, inventory
443
+ """
444
+ site = cycle.get("site", {})
445
+
446
+ site_type = site.get("siteType")
447
+ eco_climate_zone = get_eco_climate_zone_value(site, as_enum=True)
448
+
449
+ land_cover_nodes = get_valid_management_nodes(site)
450
+
451
+ organic_soil_nodes = split_nodes_by_dates(
452
+ select_nodes_by(
453
+ site.get("measurements", []),
454
+ [
455
+ lambda nodes: filter_list_term_id(nodes, _ORGANIC_SOILS_TERM_ID),
456
+ lambda nodes: closest_depth(nodes, _DEPTH_UPPER, _DEPTH_LOWER, depth_strict=False)
457
+ ]
458
+ )
459
+ )
460
+
461
+ has_valid_site_type = all([site_type, site_type not in EXCLUDED_SITE_TYPES])
462
+ has_valid_eco_climate_zone = all([eco_climate_zone, eco_climate_zone not in EXCLUDED_ECO_CLIMATE_ZONES])
463
+ has_land_cover_nodes = len(land_cover_nodes) > 1
464
+ has_organic_soil_nodes = bool(organic_soil_nodes)
465
+
466
+ should_compileInventory = all([
467
+ has_valid_site_type,
468
+ has_valid_eco_climate_zone,
469
+ has_land_cover_nodes,
470
+ has_organic_soil_nodes
471
+ ])
472
+
473
+ should_run, inventory, compilation_logs = (
474
+ _compileInventory(cycle, site, land_cover_nodes, organic_soil_nodes, eco_climate_zone)
475
+ if should_compileInventory else (False, {}, {})
476
+ )
477
+
478
+ logs = {
479
+ "site_id": site.get("@id"),
480
+ "site_type": site_type,
481
+ "has_valid_site_type": has_valid_site_type,
482
+ "eco_climate_zone": eco_climate_zone,
483
+ "has_valid_eco_climate_zone": has_valid_eco_climate_zone,
484
+ "has_land_cover_nodes": has_land_cover_nodes,
485
+ "has_organic_soil_nodes": has_organic_soil_nodes,
486
+ "should_compileInventory": should_compileInventory,
487
+ **compilation_logs
488
+ }
489
+
490
+ for term_id in _EMISSION_TERM_IDS:
491
+ log_emission_data(should_run, term_id, cycle, inventory, logs)
492
+
493
+ return should_run, inventory
494
+
495
+
496
+ def run(cycle: dict):
497
+ """
498
+ Run the `nonCo2EmissionsToAirOrganicSoilBurning` model on a Cycle.
499
+
500
+ Parameters
501
+ ----------
502
+ cycle : dict
503
+ A HESTIA [Cycle](https://www.hestia.earth/schema/Cycle).
504
+
505
+ Returns
506
+ -------
507
+ list[dict]
508
+ A list of HESTIA [Emission](https://www.hestia.earth/schema/Emission) nodes with `term.termType` =
509
+ `ch4ToAirOrganicSoilBurning` **OR** `co2ToAirOrganicSoilBurning` **OR** `coToAirOrganicSoilBurning` **OR**
510
+ `n2OToAirOrganicSoilBurningDirect` **OR** `noxToAirOrganicSoilBurning`.
511
+ """
512
+ should_run, inventory = _should_run(cycle)
513
+ return (
514
+ [_emission(*run_emission(term_id, cycle.get("@id"), inventory)) for term_id in _EMISSION_TERM_IDS]
515
+ if should_run else []
516
+ )
@@ -36,7 +36,7 @@ REQUIREMENTS = {
36
36
  "site": {
37
37
  "@type": "Site",
38
38
  "measurements": [
39
- {"@type": "Measurement", "value": "", "term.@id": "histosol"},
39
+ {"@type": "Measurement", "value": "", "term.@id": "organicSoils"},
40
40
  {"@type": "Measurement", "value": "", "term.@id": "ecoClimateZone"}
41
41
  ]
42
42
  },
@@ -103,10 +103,7 @@ def _should_run(cycle: dict):
103
103
  site = cycle.get('site', {})
104
104
  measurements = site.get('measurements', [])
105
105
 
106
- def _get_measurement_content(term_id: str):
107
- return most_relevant_measurement_value(measurements, term_id, end_date)
108
-
109
- histosol = _get_measurement_content('histosol')
106
+ organic_soils = most_relevant_measurement_value(measurements, "organicSoils", end_date)
110
107
  eco_climate_zone = get_eco_climate_zone_value(cycle, as_enum=True)
111
108
  organic_soil_category = assign_organic_soil_category(cycle, log_id=TERM_ID)
112
109
 
@@ -124,7 +121,7 @@ def _should_run(cycle: dict):
124
121
  organic_soil_category=organic_soil_category,
125
122
  emission_factor=f"{format_float(emission_factor_mean)} ± {format_float(emission_factor_sd)}",
126
123
  land_occupation=format_float(land_occupation),
127
- histosol=format_float(histosol)
124
+ organic_soils=format_float(organic_soils)
128
125
  )
129
126
 
130
127
  should_run = all([
@@ -135,22 +132,22 @@ def _should_run(cycle: dict):
135
132
  emission_factor_mean,
136
133
  emission_factor_sd,
137
134
  land_occupation,
138
- histosol
135
+ organic_soils
139
136
  ]
140
137
  )
141
138
  ])
142
139
 
143
140
  logShouldRun(cycle, MODEL, TERM_ID, should_run, methodTier=TIER)
144
141
 
145
- return should_run, emission_factor_mean, emission_factor_sd, histosol, land_occupation
142
+ return should_run, emission_factor_mean, emission_factor_sd, organic_soils, land_occupation
146
143
 
147
144
 
148
- def _run(emission_factor_mean: float, emission_factor_sd: float, histosol: float, land_occupation: float):
149
- value = round(calc_emission(TERM_ID, emission_factor_mean, histosol, land_occupation), 6)
150
- sd = round(calc_emission(TERM_ID, emission_factor_sd, histosol, land_occupation), 6)
145
+ def _run(emission_factor_mean: float, emission_factor_sd: float, organic_soils: float, land_occupation: float):
146
+ value = round(calc_emission(TERM_ID, emission_factor_mean, organic_soils, land_occupation), 6)
147
+ sd = round(calc_emission(TERM_ID, emission_factor_sd, organic_soils, land_occupation), 6)
151
148
  return [_emission(value, sd)]
152
149
 
153
150
 
154
151
  def run(cycle: dict):
155
- should_run, emission_factor_mean, emission_factor_sd, histosol, land_occupation = _should_run(cycle)
156
- return _run(emission_factor_mean, emission_factor_sd, histosol, land_occupation) if should_run else []
152
+ should_run, emission_factor_mean, emission_factor_sd, organic_soils, land_occupation = _should_run(cycle)
153
+ return _run(emission_factor_mean, emission_factor_sd, organic_soils, land_occupation) if should_run else []