hestia-earth-models 0.74.4__py3-none-any.whl → 0.74.5__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 (62) hide show
  1. hestia_earth/models/cml2001Baseline/abioticResourceDepletionMineralsAndMetals.py +0 -1
  2. hestia_earth/models/config/Cycle.json +15 -0
  3. hestia_earth/models/config/ImpactAssessment.json +9 -1
  4. hestia_earth/models/cycle/animal/input/hestiaAggregatedData.py +3 -3
  5. hestia_earth/models/cycle/completeness/seed.py +1 -1
  6. hestia_earth/models/cycle/input/hestiaAggregatedData.py +25 -16
  7. hestia_earth/models/data/hestiaAggregatedData/__init__.py +73 -0
  8. hestia_earth/models/environmentalFootprintV3_1/scarcityWeightedWaterUse.py +1 -1
  9. hestia_earth/models/environmentalFootprintV3_1/soilQualityIndexLandOccupation.py +5 -6
  10. hestia_earth/models/environmentalFootprintV3_1/soilQualityIndexLandTransformation.py +10 -13
  11. hestia_earth/models/fantkeEtAl2016/damageToHumanHealthParticulateMatterFormation.py +1 -1
  12. hestia_earth/models/hestia/landCover.py +24 -0
  13. hestia_earth/models/hestia/landOccupationDuringCycle.py +80 -51
  14. hestia_earth/models/hestia/landTransformation100YearAverageDuringCycle.py +7 -1
  15. hestia_earth/models/hestia/landTransformation20YearAverageDuringCycle.py +7 -1
  16. hestia_earth/models/hestia/resourceUse_utils.py +58 -119
  17. hestia_earth/models/hestia/waterSalinity.py +57 -12
  18. hestia_earth/models/impact_assessment/post_checks/__init__.py +3 -2
  19. hestia_earth/models/impact_assessment/post_checks/remove_cache_fields.py +9 -0
  20. hestia_earth/models/impact_assessment/pre_checks/cache_emissionsResourceUse.py +21 -0
  21. hestia_earth/models/impact_assessment/pre_checks/cycle.py +5 -0
  22. hestia_earth/models/ipcc2019/co2ToAirAboveGroundBiomassStockChange.py +6 -64
  23. hestia_earth/models/ipcc2019/co2ToAirBelowGroundBiomassStockChange.py +9 -87
  24. hestia_earth/models/ipcc2019/co2ToAirBiocharStockChange.py +140 -0
  25. hestia_earth/models/ipcc2019/co2ToAirCarbonStockChange_utils.py +329 -217
  26. hestia_earth/models/ipcc2019/co2ToAirSoilOrganicCarbonStockChange.py +10 -87
  27. hestia_earth/models/mocking/__init__.py +2 -2
  28. hestia_earth/models/mocking/mock_search.py +20 -10
  29. hestia_earth/models/mocking/search-results.json +1 -7679
  30. hestia_earth/models/pooreNemecek2018/landOccupationDuringCycle.py +8 -7
  31. hestia_earth/models/poschEtAl2008/terrestrialAcidificationPotentialAccumulatedExceedance.py +1 -1
  32. hestia_earth/models/poschEtAl2008/terrestrialEutrophicationPotentialAccumulatedExceedance.py +1 -1
  33. hestia_earth/models/preload_requests.py +18 -4
  34. hestia_earth/models/schmidt2007/utils.py +3 -3
  35. hestia_earth/models/utils/__init__.py +4 -1
  36. hestia_earth/models/utils/aggregated.py +21 -68
  37. hestia_earth/models/utils/cycle.py +3 -3
  38. hestia_earth/models/utils/impact_assessment.py +45 -41
  39. hestia_earth/models/utils/lookup.py +92 -67
  40. hestia_earth/models/version.py +1 -1
  41. hestia_earth/orchestrator/models/__init__.py +47 -10
  42. hestia_earth/orchestrator/models/transformations.py +3 -1
  43. hestia_earth/orchestrator/strategies/merge/__init__.py +1 -2
  44. hestia_earth/orchestrator/strategies/merge/merge_list.py +31 -8
  45. hestia_earth/orchestrator/utils.py +29 -0
  46. {hestia_earth_models-0.74.4.dist-info → hestia_earth_models-0.74.5.dist-info}/METADATA +2 -3
  47. {hestia_earth_models-0.74.4.dist-info → hestia_earth_models-0.74.5.dist-info}/RECORD +62 -55
  48. tests/models/cycle/animal/input/test_hestiaAggregatedData.py +3 -3
  49. tests/models/cycle/input/test_hestiaAggregatedData.py +9 -18
  50. tests/models/data/__init__.py +0 -0
  51. tests/models/data/test_hestiaAggregatedData.py +32 -0
  52. tests/models/hestia/test_landCover.py +32 -1
  53. tests/models/hestia/test_waterSalinity.py +16 -4
  54. tests/models/ipcc2019/test_co2ToAirAboveGroundBiomassStockChange.py +1 -6
  55. tests/models/ipcc2019/test_co2ToAirBelowGroundBiomassStockChange.py +1 -6
  56. tests/models/ipcc2019/test_co2ToAirBiocharStockChange.py +90 -0
  57. tests/models/ipcc2019/test_co2ToAirSoilOrganicCarbonStockChange.py +1 -6
  58. tests/models/pooreNemecek2018/test_landOccupationDuringCycle.py +1 -0
  59. tests/orchestrator/strategies/merge/test_merge_list.py +5 -0
  60. {hestia_earth_models-0.74.4.dist-info → hestia_earth_models-0.74.5.dist-info}/LICENSE +0 -0
  61. {hestia_earth_models-0.74.4.dist-info → hestia_earth_models-0.74.5.dist-info}/WHEEL +0 -0
  62. {hestia_earth_models-0.74.4.dist-info → hestia_earth_models-0.74.5.dist-info}/top_level.txt +0 -0
@@ -11,7 +11,7 @@ from numpy.typing import NDArray
11
11
  from pydash.objects import merge
12
12
  from typing import Any, Callable, NamedTuple, Optional, Union
13
13
  from hestia_earth.schema import (
14
- EmissionMethodTier, EmissionStatsDefinition, MeasurementMethodClassification
14
+ CycleFunctionalUnit, EmissionMethodTier, EmissionStatsDefinition, MeasurementMethodClassification, SiteSiteType
15
15
  )
16
16
  from hestia_earth.utils.date import diff_in_days, YEAR
17
17
  from hestia_earth.utils.tools import flatten, non_empty_list, safe_parse_date
@@ -21,8 +21,8 @@ from hestia_earth.utils.descriptive_stats import calc_descriptive_stats
21
21
  from hestia_earth.models.log import log_as_table
22
22
  from hestia_earth.models.utils import pairwise
23
23
  from hestia_earth.models.utils.blank_node import (
24
- _gapfill_datestr, _get_datestr_format, DatestrGapfillMode, DatestrFormat, group_nodes_by_year, node_term_match,
25
- split_node_by_dates
24
+ _gapfill_datestr, _get_datestr_format, cumulative_nodes_term_match, DatestrGapfillMode, DatestrFormat,
25
+ group_nodes_by_year, node_term_match, split_node_by_dates
26
26
  )
27
27
  from hestia_earth.models.utils.constant import Units, get_atomic_conversion
28
28
  from hestia_earth.models.utils.emission import min_emission_method_tier
@@ -47,7 +47,7 @@ Carbon stock measurements without an associated `sd` should be assigned a nomina
47
47
  the mean).
48
48
  """
49
49
  _TRANSITION_PERIOD_YEARS = 20
50
- _TRANSITION_PERIOD_DAYS = 20 * YEAR # 20 years in days
50
+ _TRANSITION_PERIOD_DAYS = _TRANSITION_PERIOD_YEARS * YEAR # 20 years in days
51
51
  _VALID_DATE_FORMATS = {
52
52
  DatestrFormat.YEAR,
53
53
  DatestrFormat.YEAR_MONTH,
@@ -81,6 +81,14 @@ Any `MeasurementMethodClassification` not in the mapping should be assigned `DEF
81
81
  """
82
82
 
83
83
 
84
+ _SITE_TYPE_SYSTEMS_MAPPING = {
85
+ SiteSiteType.GLASS_OR_HIGH_ACCESSIBLE_COVER.value: [
86
+ "protectedCroppingSystemSoilBased",
87
+ "protectedCroppingSystemSoilAndSubstrateBased"
88
+ ]
89
+ }
90
+
91
+
84
92
  class _InventoryKey(Enum):
85
93
  """
86
94
  The inner keys of the annualised inventory created by the `_compile_inventory` function.
@@ -329,70 +337,87 @@ def _add_carbon_stock_change_emissions(
329
337
 
330
338
 
331
339
  def create_should_run_function(
332
- *,
333
340
  carbon_stock_term_id: str,
334
- get_valid_management_nodes_func: Callable[[dict], list[dict]],
335
- should_compile_inventory_func: Callable[[dict, list[dict], list[dict]], tuple[bool, dict]],
336
- summarise_land_use_func: Callable[[list[dict]], Any],
337
- detect_land_use_change_func: Callable[[Any, Any], bool],
338
- should_run_measurement_func: Callable[[dict], bool] = lambda *_: True,
339
- measurement_method_ranking: list[MeasurementMethodClassification] = DEFAULT_MEASUREMENT_METHOD_RANKING
341
+ *,
342
+ depth_upper: Optional[float] = None,
343
+ depth_lower: Optional[float] = None,
344
+ measurements_mandatory: bool = True,
345
+ measurement_method_ranking: list[MeasurementMethodClassification] = DEFAULT_MEASUREMENT_METHOD_RANKING,
346
+ transition_period: float = _TRANSITION_PERIOD_DAYS,
347
+ get_valid_management_nodes_func: Callable[[dict], list[dict]] = lambda *_: [],
348
+ summarise_land_use_func: Callable[[list[dict]], Any] = lambda *_: None,
349
+ detect_land_use_change_func: Callable[[Any, Any], bool] = lambda *_: False
340
350
  ) -> Callable[[dict], tuple[bool, str, dict, dict]]:
341
351
  """
342
- Create a should run function for an emissions from carbon stock change model.
352
+ Create a `should_run` function for a carbon stock change model.
343
353
 
344
- Model-specific validation functions should be passed as parameters to this higher order function to determine which
345
- carbon stock measurements are included in the inventory and whether there is enough data to compile an annual
346
- inventory of carbon stock change data.
354
+ This higher-order function constructs a custom decision function that determines:
355
+ 1. Whether a given cycle has sufficient valid carbon stock measurements and land use information
356
+ to compile an inventory.
357
+ 2. Which measurements are included in the inventory based on depth, method ranking, and other criteria.
347
358
 
348
359
  Parameters
349
360
  ----------
350
361
  carbon_stock_term_id : str
351
362
  The `term.@id` of the carbon stock measurement (e.g., `aboveGroundBiomass`, `belowGroundBiomass`,
352
- `organicCarbonPerHa`, etc.).
363
+ `organicCarbonPerHa`).
353
364
 
354
- should_compile_inventory_func : Callable[[dict, list[dict], list[dict]], tuple[bool, dict]]
355
- A function, with the signature
356
- `(site: dict, cycles: list[dict], carbon_stock_measurements: list[dict]) -> (should_run: bool, logs: dict)`, to
357
- determine whether there is enough site and cycles data available to compile the carbon stock change inventory.
365
+ depth_upper : float, optional
366
+ The upper bound of the measurement depth (e.g., 0 cm). If provided, only measurements matching this bound are
367
+ included.
358
368
 
359
- get_valid_management_nodes_func : Callable[[dict], list[dict]]
360
- A function, with the signature... `(site: dict) -> management_nodes: list[dict]` to extract valid management
361
- nodes from the site for building the land use inventory.
369
+ depth_lower : float, optional
370
+ The lower bound of the measurement depth (e.g., 30 cm). If provided, only measurements matching this bound are
371
+ included.
362
372
 
363
- summarise_land_use_func: Callable[[list[dict]], Any]
364
- A function with the signature `(nodes: list[dict]) -> Any`, to reduce a list of `landCover`
365
- [Management](https://www.hestia.earth/schema/Management) nodes into a land use summary that can be compared
366
- with other summaries to determine whether land use change events have occured.
367
-
368
- detect_land_use_change_func: Callable[[Any, Any], bool]
369
- A function with the signature `(summary_a: Any, summary_b: Any) -> bool`, to determine whether a land use
370
- change event has occured.
371
-
372
- should_run_measurement_func : Callable[[dict], bool], optional.
373
- An optional measurement validation function, with the signature `(measurement: dict) -> bool`, that can be used
374
- to add in additional criteria (`depthUpper`, `depthLower`, etc.) for the inclusion of a measurement in the
375
- inventory.
373
+ measurements_mandatory : bool, default=True
374
+ If `True`, at least two valid measurement must be present for the cycle to be included in the inventory. If
375
+ `False`, the function may allow an inventory to be generated without direct measurements.
376
376
 
377
377
  measurement_method_ranking : list[MeasurementMethodClassification], optional
378
- The order in which to prioritise `MeasurementMethodClassification`s when reducing the inventory down to a
379
- single method per year. Defaults to:
378
+ The priority order for selecting among multiple measurement methods. Defaults to:
380
379
  ```
381
- MeasurementMethodClassification.ON_SITE_PHYSICAL_MEASUREMENT,
382
- MeasurementMethodClassification.MODELLED_USING_OTHER_MEASUREMENTS,
383
- MeasurementMethodClassification.TIER_3_MODEL,
384
- MeasurementMethodClassification.TIER_2_MODEL,
385
- MeasurementMethodClassification.TIER_1_MODEL
380
+ [
381
+ MeasurementMethodClassification.ON_SITE_PHYSICAL_MEASUREMENT,
382
+ MeasurementMethodClassification.MODELLED_USING_OTHER_MEASUREMENTS,
383
+ MeasurementMethodClassification.TIER_3_MODEL,
384
+ MeasurementMethodClassification.TIER_2_MODEL,
385
+ MeasurementMethodClassification.TIER_1_MODEL,
386
+ ]
386
387
  ```
387
- n.b., measurements with methods not included in the ranking will not be included in the inventory.
388
+ Measurements using methods not in this ranking are excluded.
389
+
390
+ transition_period : float, default=_TRANSITION_PERIOD_DAYS
391
+ The transition period (in days) over which management changes are assumed to take effect. Used to generate a
392
+ correlation matrix for multivariate sampling of carbon stock values.
393
+
394
+ get_valid_management_nodes_func : Callable[[dict], list[dict]], optional
395
+ Function with signature `(site: dict) -> list[dict]`.
396
+
397
+ Extracts valid management nodes from the site for building the land use inventory.
398
+
399
+ summarise_land_use_func : Callable[[list[dict]], Any], optional
400
+ Function with signature `(nodes: list[dict]) -> Any`.
401
+
402
+ Summarises a list of `landCover` [Management](https://www.hestia.earth/schema/Management) nodes into a
403
+ comparable representation for detecting land use changes.
404
+
405
+ detect_land_use_change_func : Callable[[Any, Any], bool], optional
406
+ Function with signature `(summary_a: Any, summary_b: Any) -> bool`.
407
+
408
+ Detects whether a land use change event has occurred between two summaries.
388
409
 
389
410
  Returns
390
411
  -------
391
- Callable[[dict], tuple[bool, str, dict, dict]]
392
- The customised `should_run` function with the signature
393
- `(cycle: dict) -> (should_run_: bool, cycle_id: str, inventory: dict, logs: dict)`.
412
+ should_run_func : Callable[[dict], tuple[bool, str, dict, dict]]
413
+ A function with the signature:
414
+ `(cycle: dict) -> (should_run: bool, cycle_id: str, inventory: dict, logs: dict)`
415
+
416
+ - `should_run` : Whether the model should run for the cycle.
417
+ - `cycle_id` : Identifier of the cycle.
418
+ - `inventory` : The constructed carbon stock inventory for the cycle.
419
+ - `logs` : Diagnostic logs describing validation and filtering decisions.
394
420
  """
395
-
396
421
  def should_run(cycle: dict) -> tuple[bool, str, dict, dict]:
397
422
  """
398
423
  Determine if calculations should run for a given [Cycle](https://www.hestia.earth/schema/Cycle) based on
@@ -410,21 +435,24 @@ def create_should_run_function(
410
435
  `(should_run, cycle_id, inventory, logs)`
411
436
  """
412
437
  cycle_id = cycle.get("@id")
413
- cycle_start_date = cycle.get("startDate")
414
- cycle_end_date = cycle.get("endDate")
415
438
 
416
- site = _get_site(cycle)
439
+ site = cycle.get("site", {})
440
+ site_type = site.get("siteType")
441
+
417
442
  cycles = related_cycles(site, cycles_mapping={cycle_id: cycle})
418
443
 
419
444
  carbon_stock_measurements = [
420
445
  node for node in site.get("measurements", [])
421
- if all([
422
- node_term_match(node, carbon_stock_term_id),
423
- _has_valid_array_fields(node),
424
- _has_valid_dates(node),
425
- node.get("methodClassification") in (m.value for m in measurement_method_ranking),
426
- should_run_measurement_func(node)
427
- ])
446
+ if (
447
+ node_term_match(node, carbon_stock_term_id)
448
+ and all([
449
+ _has_valid_array_fields(node),
450
+ _has_valid_dates(node),
451
+ node.get("methodClassification") in (m.value for m in measurement_method_ranking),
452
+ depth_upper is None or node.get("depthUpper") == depth_upper,
453
+ depth_lower is None or node.get("depthLower") == depth_lower
454
+ ])
455
+ )
428
456
  ]
429
457
 
430
458
  land_cover_nodes = get_valid_management_nodes_func(site)
@@ -432,16 +460,27 @@ def create_should_run_function(
432
460
  seed = gen_seed(site, MODEL, carbon_stock_term_id) # All cycles linked to the same site should be consistent
433
461
  rng = random.default_rng(seed)
434
462
 
435
- should_compile_inventory, should_compile_logs = should_compile_inventory_func(
436
- site, cycles, carbon_stock_measurements
463
+ has_soil = is_soil_based_system(cycles, site_type)
464
+ has_cycles = len(cycles) > 0
465
+ has_functional_unit_1_ha = all(
466
+ cycle.get('functionalUnit') == CycleFunctionalUnit._1_HA.value for cycle in cycles
467
+ )
468
+ has_stock_measurements = bool(carbon_stock_measurements)
469
+
470
+ should_compile_inventory = (
471
+ has_soil
472
+ and has_cycles
473
+ and has_functional_unit_1_ha
474
+ and (has_stock_measurements or not measurements_mandatory)
437
475
  )
438
476
 
439
477
  compile_inventory_func = _create_compile_inventory_function(
478
+ transition_period=transition_period,
479
+ seed=rng,
480
+ iterations=_ITERATIONS,
481
+ measurement_method_ranking=measurement_method_ranking,
440
482
  summarise_land_use_func=summarise_land_use_func,
441
483
  detect_land_use_change_func=detect_land_use_change_func,
442
- iterations=_ITERATIONS,
443
- seed=rng,
444
- measurement_method_ranking=measurement_method_ranking
445
484
  )
446
485
 
447
486
  inventory, inventory_logs = (
@@ -457,21 +496,20 @@ def create_should_run_function(
457
496
 
458
497
  should_run_ = all([has_valid_inventory, has_consecutive_years])
459
498
 
460
- kwargs = {
461
- "cycle_id": cycle_id,
462
- "cycle_start_date": cycle_start_date,
463
- "cycle_end_date": cycle_end_date,
464
- "inventory": inventory
465
- }
466
-
467
- logs = should_compile_logs | inventory_logs | {
499
+ logs = inventory_logs | {
500
+ "carbon_stock_term": carbon_stock_term_id,
468
501
  "seed": seed,
502
+ "site_type": site_type,
503
+ "has_soil": has_soil,
504
+ "has_cycles": has_cycles,
505
+ "has_functional_unit_1_ha": has_functional_unit_1_ha,
469
506
  "has_valid_inventory": has_valid_inventory,
470
507
  "has_consecutive_years": has_consecutive_years,
471
- "has_stock_measurements": bool(carbon_stock_measurements)
508
+ "has_stock_measurements": has_stock_measurements,
509
+ "measurements_mandatory": measurements_mandatory
472
510
  }
473
511
 
474
- return should_run_, kwargs, logs
512
+ return should_run_, cycle_id, inventory, logs
475
513
 
476
514
  return should_run
477
515
 
@@ -493,69 +531,62 @@ def _has_valid_dates(node: dict) -> bool:
493
531
  return all(_get_datestr_format(datestr) in _VALID_DATE_FORMATS for datestr in node.get("dates", []))
494
532
 
495
533
 
496
- def _get_site(cycle: dict) -> dict:
497
- """
498
- Get the [Site](https://www.hestia.earth/schema/Site) data from a [Cycle](https://www.hestia.earth/schema/Cycle).
499
-
500
- Parameters
501
- ----------
502
- cycle : dict
503
-
504
- Returns
505
- -------
506
- str
507
- """
508
- return cycle.get("site", {})
509
-
510
-
511
534
  def _create_compile_inventory_function(
512
535
  *,
513
- summarise_land_use_func: Callable[[list[dict]], Any],
514
- detect_land_use_change_func: Callable[[Any, Any], bool],
515
- iterations: int = 10000,
536
+ transition_period: float,
516
537
  seed: Union[int, random.Generator, None] = None,
517
- measurement_method_ranking: list[MeasurementMethodClassification] = DEFAULT_MEASUREMENT_METHOD_RANKING
538
+ iterations: int = 10000,
539
+ measurement_method_ranking: list[MeasurementMethodClassification] = DEFAULT_MEASUREMENT_METHOD_RANKING,
540
+ summarise_land_use_func: Callable[[list[dict]], Any],
541
+ detect_land_use_change_func: Callable[[Any, Any], bool]
518
542
  ) -> Callable:
519
543
  """
520
- Create a compile inventory function for an emissions from carbon stock change model.
544
+ Create a `compile_inventory` function for a carbon stock change model.
521
545
 
522
- Model-specific validation functions should be passed as parameters to this higher order function to determine how
523
- land cover data is reduced down to a land use summary and how those land use summaries should be compared to
524
- determine when land use change events have occured.
546
+ This higher-order function produces a callable that generates annual inventories of carbon stock, carbon stock
547
+ change, emissions, and land-use-related events. It combines data from cycles, measurements, and land cover to build
548
+ a unified inventory.
525
549
 
526
550
  Parameters
527
551
  ----------
528
- summarise_land_use_func: Callable[[list[dict]], Any]
529
- A function with the signature `(nodes: list[dict]) -> Any`, to reduce a list of `landCover`
530
- [Management](https://www.hestia.earth/schema/Management) nodes into a land use summary that can be compared
531
- with other summaries to determine whether land use change events have occured.
532
-
533
- detect_land_use_change_func: Callable[[Any, Any], bool]
534
- A function with the signature `(summary_a: Any, summary_b: Any) -> bool`, to determine whether a land use
535
- change event has occured.
536
-
537
- iterations : int, optional
538
- The number of iterations for stochastic processing (default is 10,000).
552
+ transition_period : float, default=_TRANSITION_PERIOD_DAYS
553
+ The transition period (in days) over which management changes are assumed to take effect. Used to generate a
554
+ correlation matrix for multivariate sampling of carbon stock values.
539
555
 
540
556
  seed : int, random.Generator, or None, optional
541
- Seed for random number generation to ensure reproducibility. Default is None.
557
+ Seed for random number generation to ensure reproducibility.
558
+
559
+ iterations : int, optional, default=`10000`
560
+ The number of iterations for the Monte Carlo simualation.
542
561
 
543
562
  measurement_method_ranking : list[MeasurementMethodClassification], optional
544
- The order in which to prioritise `MeasurementMethodClassification`s when reducing the inventory down to a
545
- single method per year. Defaults to:
563
+ The priority order for selecting among multiple measurement methods. Defaults to:
546
564
  ```
547
- MeasurementMethodClassification.ON_SITE_PHYSICAL_MEASUREMENT,
548
- MeasurementMethodClassification.MODELLED_USING_OTHER_MEASUREMENTS,
549
- MeasurementMethodClassification.TIER_3_MODEL,
550
- MeasurementMethodClassification.TIER_2_MODEL,
551
- MeasurementMethodClassification.TIER_1_MODEL
565
+ [
566
+ MeasurementMethodClassification.ON_SITE_PHYSICAL_MEASUREMENT,
567
+ MeasurementMethodClassification.MODELLED_USING_OTHER_MEASUREMENTS,
568
+ MeasurementMethodClassification.TIER_3_MODEL,
569
+ MeasurementMethodClassification.TIER_2_MODEL,
570
+ MeasurementMethodClassification.TIER_1_MODEL,
571
+ ]
552
572
  ```
553
- n.b., measurements with methods not included in the ranking will not be included in the inventory.
573
+ Measurements using methods not in this ranking are excluded.
574
+
575
+ summarise_land_use_func : Callable[[list[dict]], Any], optional
576
+ Function with signature `(nodes: list[dict]) -> Any`.
577
+
578
+ Summarises a list of `landCover` [Management](https://www.hestia.earth/schema/Management) nodes into a
579
+ comparable representation for detecting land use changes.
580
+
581
+ detect_land_use_change_func : Callable[[Any, Any], bool], optional
582
+ Function with signature `(summary_a: Any, summary_b: Any) -> bool`.
583
+
584
+ Detects whether a land use change event has occurred between two summaries.
554
585
 
555
586
  Returns
556
587
  ----------
557
- Callable
558
- The `compile_inventory` function.
588
+ compile_inventory : Callable[[list[dict], list[dict], list[dict]], tuple[dict, dict]]
589
+ A function that compiles an annual inventory given cycles, carbon stock measurements, and land cover nodes.
559
590
  """
560
591
  def compile_inventory(
561
592
  cycles: list[dict],
@@ -563,52 +594,60 @@ def _create_compile_inventory_function(
563
594
  land_cover_nodes: list[dict]
564
595
  ) -> tuple[dict, dict]:
565
596
  """
566
- Compile an annual inventory of carbon stocks, changes in carbon stocks, carbon stock change emissions, and the
567
- share of emissions of cycles based on the provided cycles and measurement data.
568
-
569
- A separate inventory is compiled for each valid `MeasurementMethodClassification` present in the data, and the
570
- strongest available method is chosen for each relevant inventory year. These inventories are then merged into
571
- one final result.
572
-
573
- The final inventory structure is:
574
- ```
575
- {
576
- year (int): {
577
- _InventoryKey.CARBON_STOCK: value (CarbonStock),
578
- _InventoryKey.CARBON_STOCK_CHANGE: value (CarbonStockChange),
579
- _InventoryKey.CO2_EMISSION: value (CarbonStockChangeEmission),
580
- _InventoryKey.SHARE_OF_EMISSION: {
581
- cycle_id (str): value (float),
582
- ...cycle_ids
583
- },
584
- _InventoryKey.YEARS_SINCE_LUC_EVENT: value (int)
585
- },
586
- ...years
587
- }
597
+ Compile an annual inventory of carbon stocks, stock changes, emissions, and land
598
+ use events.
599
+
600
+ The function integrates data from cycles, carbon stock measurements, and land cover management nodes. For each
601
+ year, the inventory includes:
602
+ - carbon stock values,
603
+ - carbon stock changes,
604
+ - CO₂ emissions,
605
+ - attribution of emissions to cycles,
606
+ - and time since land use change (LUC) events.
607
+
608
+ A separate inventory is compiled for each valid measurement method present in the data. The best available
609
+ method per year (according to `measurement_method_ranking`) is chosen, and inventories are merged into the
610
+ final result.
588
611
  ```
589
612
 
590
613
  Parameters
591
614
  ----------
592
- cycle_id : str
593
- The unique identifier of the cycle being processed.
594
615
  cycles : list[dict]
595
616
  A list of [Cycle](https://www.hestia.earth/schema/Cycles) nodes related to the site.
617
+
596
618
  carbon_stock_measurements : list[dict]
597
619
  A list of [Measurement](https://www.hestia.earth/schema/Measurement) nodes, representing carbon stock
598
620
  measurements across time and methods.
621
+
599
622
  land_cover_nodes : list[dict]
600
- A list of `landCover `[Management](https://www.hestia.earth/schema/Management) nodes, representing the
623
+ A list of `landCover` [Management](https://www.hestia.earth/schema/Management) nodes, representing the
601
624
  site's land cover over time.
602
625
 
603
626
 
604
627
  Returns
605
628
  -------
606
- tuple[dict, dict]
607
- `(inventory, logs)`
629
+ inventory : dict
630
+ Annual inventory of carbon stock, carbon stock change, emissions, and land use
631
+ change information. Structure:
632
+ ```
633
+ {
634
+ year (int): {
635
+ _InventoryKey.CARBON_STOCK: CarbonStock,
636
+ _InventoryKey.CARBON_STOCK_CHANGE: CarbonStockChange,
637
+ _InventoryKey.CO2_EMISSION: CarbonStockChangeEmission,
638
+ _InventoryKey.SHARE_OF_EMISSION: {cycle_id (str): float, ...},
639
+ _InventoryKey.YEARS_SINCE_LUC_EVENT: int
640
+ },
641
+ ...years
642
+ }
643
+ ```
644
+
645
+ logs : dict
646
+ Diagnostic logs describing intermediate steps and validation decisions.
608
647
  """
609
648
  cycle_inventory = _compile_cycle_inventory(cycles)
610
649
  carbon_stock_inventory = _compile_carbon_stock_inventory(
611
- carbon_stock_measurements, iterations=iterations, seed=seed
650
+ carbon_stock_measurements, transition_period=transition_period, iterations=iterations, seed=seed
612
651
  )
613
652
  land_use_inventory = _compile_land_use_inventory(
614
653
  land_cover_nodes, summarise_land_use_func, detect_land_use_change_func
@@ -630,20 +669,19 @@ def _create_compile_inventory_function(
630
669
 
631
670
  def _compile_cycle_inventory(cycles: list[dict]) -> dict:
632
671
  """
633
- Calculate grouped share of emissions for cycles based on the amount they contribute the the overall land management
634
- of an inventory year.
672
+ Compile the share of emissions for each cycle, grouped by inventory year.
635
673
 
636
- This function groups cycles by year, then calculates the share of emissions for each cycle based on the
637
- `fraction_of_group_duration` value. The share of emissions is normalized by the sum of cycle occupancies for the
638
- entire dataset to ensure the values represent a valid share.
674
+ Each cycle is assumed to occupy a fraction of its group's duration (given by the `"fraction_of_group_duration"`
675
+ key). For each year, this function normalizes those fractions so that the shares of emissions across all cycles in
676
+ that year sum to 1.0.
639
677
 
640
- The returned inventory has the shape:
678
+ The returned inventory has the structure:
641
679
  ```
642
680
  {
643
681
  year (int): {
644
682
  _InventoryKey.SHARE_OF_EMISSION: {
645
- cycle_id (str): value (float),
646
- ...cycle_ids
683
+ cycle_id (str): float, # share of emissions attributed to this cycle
684
+ ...more cycle_ids
647
685
  }
648
686
  },
649
687
  ...more years
@@ -653,17 +691,17 @@ def _compile_cycle_inventory(cycles: list[dict]) -> dict:
653
691
  Parameters
654
692
  ----------
655
693
  cycles : list[dict]
656
- List of [Cycle nodes](https://www.hestia.earth/schema/Cycle), where each cycle dictionary should contain a
657
- "fraction_of_group_duration" key added by the `group_nodes_by_year` function.
658
- iterations : int, optional
659
- Number of iterations for stochastic sampling when processing carbon stock values (default is 10,000).
660
- seed : int, random.Generator, or None, optional
661
- Seed for random number generation (default is None).
694
+ List of [Cycle](https://www.hestia.earth/schema/Cycle) nodes.
695
+
696
+ Each cycle dictionary must include:
697
+ - `@id` : str, unique identifier for the cycle
698
+ - `fraction_of_group_duration` : float, the fraction of the year this cycle contributes, typically added by
699
+ `group_nodes_by_year`.
662
700
 
663
701
  Returns
664
702
  -------
665
703
  dict
666
- A dictionary with grouped share of emissions for each cycle based on the fraction of the year.
704
+ A dictionary mapping each year to its inventory of emission shares per cycle.
667
705
  """
668
706
  grouped_cycles = group_nodes_by_year(cycles)
669
707
 
@@ -682,27 +720,30 @@ def _compile_cycle_inventory(cycles: list[dict]) -> dict:
682
720
 
683
721
  def _compile_carbon_stock_inventory(
684
722
  carbon_stock_measurements: list[dict],
723
+ transition_period: float,
685
724
  iterations: int = 10000,
686
725
  seed: Union[int, random.Generator, None] = None
687
726
  ) -> dict:
688
727
  """
689
- Compile an annual inventory of carbon stock data and pre-computed carbon stock change emissions.
728
+ Compile an annual inventory of carbon stock, stock change, and associated CO₂ emissions.
690
729
 
691
- Carbon stock measurements are grouped by the method used (MeasurementMethodClassification). For each method,
692
- carbon stocks are processed for each year and changes between years are computed, followed by the calculation of
693
- CO2 emissions.
730
+ Carbon stock measurements are grouped by their measurement method (`MeasurementMethodClassification`). For each
731
+ method:
732
+ - Annual carbon stock values are estimated.
733
+ - Year-to-year changes in carbon stocks are calculated.
734
+ - CO₂ emissions are derived from the changes.
694
735
 
695
- The returned inventory has the shape:
736
+ The returned inventory has the structure:
696
737
  ```
697
738
  {
698
739
  method (MeasurementMethodClassification): {
699
740
  year (int): {
700
- _InventoryKey.CARBON_STOCK: value (CarbonStock),
701
- _InventoryKey.CARBON_STOCK_CHANGE: value (CarbonStockChange),
702
- _InventoryKey.CO2_EMISSION: value (CarbonStockChangeEmission)
741
+ _InventoryKey.CARBON_STOCK: CarbonStock,
742
+ _InventoryKey.CARBON_STOCK_CHANGE: CarbonStockChange,
743
+ _InventoryKey.CO2_EMISSION: CarbonStockChangeEmission
703
744
  },
704
745
  ...more years
705
- }
746
+ },
706
747
  ...more methods
707
748
  }
708
749
  ```
@@ -710,41 +751,60 @@ def _compile_carbon_stock_inventory(
710
751
  Parameters
711
752
  ----------
712
753
  carbon_stock_measurements : list[dict]
713
- List of carbon [Measurement nodes](https://www.hestia.earth/schema/Measurement) nodes.
714
- iterations : int, optional
715
- Number of iterations for stochastic sampling when processing carbon stock values (default is 10,000).
754
+ List of [Measurement](https://www.hestia.earth/schema/Measurement) nodes representing
755
+ carbon stock observations. Each measurement should include:
756
+ - `@id` : str, unique measurement identifier
757
+ - `measurementMethod` : MeasurementMethodClassification
758
+ - `value` : float
759
+ - `timestamp` or `year` : temporal reference
760
+
761
+ transition_period : float, default=_TRANSITION_PERIOD_DAYS
762
+ The transition period (in days) over which management changes are assumed to take effect. Used to generate a
763
+ correlation matrix for multivariate sampling of carbon stock values.
764
+
765
+ iterations : int, default=10000
766
+ Number of iterations for stochastic sampling when estimating carbon stock values.
767
+
716
768
  seed : int, random.Generator, or None, optional
717
- Seed for random number generation (default is None).
769
+ Seed for random number generation. Default is `None`.
718
770
 
719
771
  Returns
720
772
  -------
721
773
  dict
722
- The carbon stock inventory grouped by measurement method classification.
774
+ Nested dictionary of annual inventories, grouped by measurement method classification.
723
775
  """
724
776
  carbon_stock_measurements_by_method = group_measurements_by_method_classification(carbon_stock_measurements)
725
777
 
726
778
  return {
727
- method: _process_carbon_stock_measurements(measurements, iterations=iterations, seed=seed)
779
+ method: _process_carbon_stock_measurements(measurements, transition_period, iterations, seed)
728
780
  for method, measurements in carbon_stock_measurements_by_method.items()
729
781
  }
730
782
 
731
783
 
732
784
  def _process_carbon_stock_measurements(
733
785
  carbon_stock_measurements: list[dict],
786
+ transition_period: float,
734
787
  iterations: int = 10000,
735
788
  seed: Union[int, random.Generator, None] = None
736
789
  ) -> dict:
737
790
  """
738
- Process carbon stock measurements to compile an annual inventory of carbon stocks, carbon stock changes, and CO2
739
- emissions. The inventory is built by interpolating between measured values and calculating changes across years.
791
+ Process carbon stock measurements to build an annual inventory of carbon stocks,
792
+ carbon stock changes, and CO₂ emissions.
740
793
 
741
- The returned inventory has the shape:
794
+ The function:
795
+ - Preprocesses measurements (e.g., applies decay/transition dynamics using `transition_period`).
796
+ - Interpolates carbon stock values across years.
797
+ - Calculates year-to-year stock changes.
798
+ - Derives CO₂ emissions from the stock changes.
799
+ - Merges results into a single annual inventory.
800
+
801
+ The returned inventory has the structure:
742
802
  ```
743
803
  {
744
804
  year (int): {
745
- _InventoryKey.CARBON_STOCK: value (CarbonStock),
746
- _InventoryKey.CARBON_STOCK_CHANGE: value (CarbonStockChange),
747
- _InventoryKey.CO2_EMISSION: value (CarbonStockChangeEmission)
805
+ _InventoryKey.CARBON_STOCK: CarbonStock,
806
+ _InventoryKey.CARBON_STOCK_CHANGE: CarbonStockChange,
807
+ _InventoryKey.CO2_EMISSION: CarbonStockChangeEmission,
748
808
  },
749
809
  ...more years
750
810
  }
@@ -753,18 +813,33 @@ def _process_carbon_stock_measurements(
753
813
  Parameters
754
814
  ----------
755
815
  carbon_stock_measurements : list[dict]
756
- List of pre-validated carbon stock [Measurement nodes](https://www.hestia.earth/schema/Measurement).
757
- iterations : int, optional
758
- Number of iterations for stochastic sampling when processing carbon stock values (default is 10,000).
816
+ List of carbon stock [Measurement](https://www.hestia.earth/schema/Measurement) nodes.
817
+
818
+ Each measurement is expected to include:
819
+ - `@id` : str, unique identifier
820
+ - `value` : float, the carbon stock estimate
821
+ - `timestamp` or `year` : temporal reference
822
+ - `measurementMethod` : MeasurementMethodClassification
823
+
824
+ transition_period : float, default=_TRANSITION_PERIOD_DAYS
825
+ The transition period (in days) over which management changes are assumed to take effect. Used to generate a
826
+ correlation matrix for multivariate sampling of carbon stock values.
827
+
828
+ iterations : int, default=10000
829
+ Number of iterations for stochastic sampling when estimating carbon stock values.
830
+
759
831
  seed : int, random.Generator, or None, optional
760
- Seed for random number generation (default is None).
832
+ Seed for random number generation to ensure reproducibility. Default is `None`.
761
833
 
762
834
  Returns
763
835
  -------
764
836
  dict
765
- The annual inventory.
837
+ Annual inventory mapping years to:
838
+ - `_InventoryKey.CARBON_STOCK` : CarbonStock
839
+ - `_InventoryKey.CARBON_STOCK_CHANGE` : CarbonStockChange
840
+ - `_InventoryKey.CO2_EMISSION` : CarbonStockChangeEmission
766
841
  """
767
- carbon_stocks = _preprocess_carbon_stocks(carbon_stock_measurements, iterations, seed)
842
+ carbon_stocks = _preprocess_carbon_stocks(carbon_stock_measurements, transition_period, iterations, seed)
768
843
 
769
844
  carbon_stocks_by_year = _interpolate_carbon_stocks(carbon_stocks)
770
845
  carbon_stock_changes_by_year = _calculate_stock_changes(carbon_stocks_by_year)
@@ -775,29 +850,47 @@ def _process_carbon_stock_measurements(
775
850
 
776
851
  def _preprocess_carbon_stocks(
777
852
  carbon_stock_measurements: list[dict],
853
+ half_life: float,
778
854
  iterations: int = 10000,
779
855
  seed: Union[int, random.Generator, None] = None
780
856
  ) -> list[CarbonStock]:
781
857
  """
782
- Pre-process a list of carbon stock measurements by normalizing and sorting them by date. The measurements are used
783
- to create correlated samples using stochastic sampling methods.
858
+ Preprocess a list of carbon stock measurements by normalizing, filling missing values, and generating correlated
859
+ stochastic samples.
784
860
 
785
- The carbon stock measurements are processed to fill in any gaps in data (e.g., missing standard deviations), and
786
- correlated samples are drawn to handle measurement uncertainty.
861
+ Steps:
862
+ - Measurements are expanded and sorted by date.
863
+ - Missing uncertainty values (e.g., standard deviations) are filled if necessary.
864
+ - A correlation matrix across time is built using an exponential decay function parameterized by `half_life`.
865
+ - Correlated random samples are drawn to represent measurement uncertainty over time.
866
+ - The results are returned as a list of `CarbonStock` objects.
787
867
 
788
868
  Parameters
789
869
  ----------
790
870
  carbon_stock_measurements : list[dict]
791
- List of pre-validated carbon stock [Measurement nodes](https://www.hestia.earth/schema/Measurement).
792
- iterations : int, optional
793
- Number of iterations for stochastic sampling when processing carbon stock values (default is 10,000).
871
+ List of carbon stock [Measurement](https://www.hestia.earth/schema/Measurement) nodes.
872
+ Each measurement is expected to include:
873
+ - `@id` : str, unique identifier
874
+ - `value` : float, measured carbon stock
875
+ - `timestamp` or `year` : temporal reference
876
+ - `standardDeviation` : float, optional measurement uncertainty
877
+ - `measurementMethod` : MeasurementMethodClassification
878
+
879
+ half_life : float
880
+ Transition period (in days) used to parameterize the exponential decay function for building the correlation
881
+ matrix across time.
882
+
883
+ iterations : int, default=10000
884
+ Number of stochastic samples to draw for each measurement.
885
+
794
886
  seed : int, random.Generator, or None, optional
795
- Seed for random number generation (default is None).
887
+ Seed for random number generation, to ensure reproducibility.
796
888
 
797
889
  Returns
798
890
  -------
799
891
  list[CarbonStock]
800
- A list of carbon stocks sorted by date.
892
+ A list of `CarbonStock` objects, one per measurement date and method, each containing simulated sample values
893
+ and associated metadata.
801
894
  """
802
895
  dates, values, sds, methods = _extract_node_data(
803
896
  flatten([split_node_by_dates(m) for m in carbon_stock_measurements])
@@ -807,7 +900,7 @@ def _preprocess_carbon_stocks(
807
900
  dates,
808
901
  decay_fn=lambda dt: exponential_decay(
809
902
  dt,
810
- tau=calc_tau(_TRANSITION_PERIOD_DAYS),
903
+ tau=calc_tau(half_life),
811
904
  initial_value=_MAX_CORRELATION,
812
905
  final_value=_MIN_CORRELATION
813
906
  )
@@ -982,13 +1075,17 @@ def _compile_land_use_inventory(
982
1075
  ----------
983
1076
  land_cover_nodes : list[dict]
984
1077
  A list of `landCover` Management nodes, representing the site's land cover over time.
985
- summarise_land_use_func: Callable[[list[dict]], Any]
986
- A function with the signature `(nodes: list[dict]) -> Any`, to reduce a list of `landCover` Management nodes
987
- into a land use summary that can be compared with other summaries to determine whether land use change events
988
- have occured.
989
- detect_land_use_change_func: Callable[[Any, Any], bool]
990
- A function with the signature `(summary_a: Any, summary_b: Any) -> bool`, to determine whether a land use
991
- change event has occured.
1078
+
1079
+ summarise_land_use_func : Callable[[list[dict]], Any]
1080
+ Function with signature `(nodes: list[dict]) -> Any`.
1081
+
1082
+ Summarises a list of `landCover` [Management](https://www.hestia.earth/schema/Management) nodes into a
1083
+ comparable representation for detecting land use changes.
1084
+
1085
+ detect_land_use_change_func : Callable[[Any, Any], bool]
1086
+ Function with signature `(summary_a: Any, summary_b: Any) -> bool`.
1087
+
1088
+ Detects whether a land use change event has occurred between two summaries.
992
1089
 
993
1090
  Returns
994
1091
  -------
@@ -1086,13 +1183,14 @@ def _squash_inventory(
1086
1183
  measurement_method_ranking: list[MeasurementMethodClassification] = DEFAULT_MEASUREMENT_METHOD_RANKING
1087
1184
  ) -> dict:
1088
1185
  """
1089
- Combine the `cycle_inventory` and `carbon_stock_inventory` into a single inventory by merging data for each year
1090
- using the strongest available `MeasurementMethodClassification`. Any years not relevant to the cycle identified
1091
- by `cycle_id` are excluded.
1186
+ Combine the `cycle_inventory`, `carbon_stock_inventory`, and `land_use_inventory` into a single inventory.
1187
+
1188
+ For each year, the function selects the strongest available `MeasurementMethodClassification` (based on the
1189
+ ranking) to provide carbon stock and emissions data, and merges it with cycle-level emissions shares and land use
1190
+ change information.
1092
1191
 
1093
1192
  Parameters
1094
1193
  ----------
1095
-
1096
1194
  cycle_inventory : dict
1097
1195
  A dictionary representing the share of emissions for each cycle, grouped by year.
1098
1196
  Format:
@@ -1133,15 +1231,16 @@ def _squash_inventory(
1133
1231
  year (int): {
1134
1232
  _InventoryKey.LAND_USE_SUMMARY: value (Any),
1135
1233
  _InventoryKey.LAND_USE_CHANGE_EVENT: value (bool),
1136
- _InventoryKey.YEARS_SINCE_LUC_EVENT: value (int)
1234
+ _InventoryKey.YEARS_SINCE_LUC_EVENT: value (int),
1235
+ _InventoryKey.YEARS_SINCE_INVENTORY_START: value (int)
1137
1236
  },
1138
1237
  ...years
1139
1238
  }
1140
1239
  ```
1141
1240
 
1142
1241
  measurement_method_ranking : list[MeasurementMethodClassification], optional
1143
- The order in which to prioritise `MeasurementMethodClassification`s when reducing the inventory down to a
1144
- single method per year. Defaults to:
1242
+ The order in which to prioritise `MeasurementMethodClassification`s when reducing the inventory to a single
1243
+ method per year. Defaults to:
1145
1244
  ```
1146
1245
  MeasurementMethodClassification.ON_SITE_PHYSICAL_MEASUREMENT,
1147
1246
  MeasurementMethodClassification.MODELLED_USING_OTHER_MEASUREMENTS,
@@ -1149,13 +1248,12 @@ def _squash_inventory(
1149
1248
  MeasurementMethodClassification.TIER_2_MODEL,
1150
1249
  MeasurementMethodClassification.TIER_1_MODEL
1151
1250
  ```
1152
- n.b., measurements with methods not included in the ranking will not be included in the inventory.
1251
+ Note: measurements with methods not included in the ranking are ignored.
1153
1252
 
1154
1253
  Returns
1155
1254
  -------
1156
1255
  dict
1157
- A combined inventory that merges cycle and carbon stock inventories for relevant years and cycles.
1158
- The resulting structure is:
1256
+ A combined inventory with one entry per year containing:
1159
1257
  ```
1160
1258
  {
1161
1259
  year (int): {
@@ -1165,7 +1263,11 @@ def _squash_inventory(
1165
1263
  _InventoryKey.SHARE_OF_EMISSION: {
1166
1264
  cycle_id (str): value (float),
1167
1265
  ...other cycle_ids
1168
- }
1266
+ },
1267
+ _InventoryKey.LAND_USE_SUMMARY: value (Any),
1268
+ _InventoryKey.LAND_USE_CHANGE_EVENT: value (bool),
1269
+ _InventoryKey.YEARS_SINCE_LUC_EVENT: value (int),
1270
+ _InventoryKey.YEARS_SINCE_INVENTORY_START: value (int)
1169
1271
  },
1170
1272
  ...more years
1171
1273
  }
@@ -1473,7 +1575,7 @@ def create_run_function(
1473
1575
 
1474
1576
  return result | emission_dict | zero_emission_dict
1475
1577
 
1476
- def run(cycle_id: str, cycle_start_date: str, cycle_end_date: str, inventory: dict) -> list[dict]:
1578
+ def run(cycle_id: str, inventory: dict) -> list[dict]:
1477
1579
  """
1478
1580
  Calculate emissions for a specific cycle using from a carbon stock change using pre-compiled inventory data.
1479
1581
 
@@ -1530,3 +1632,13 @@ def get_zero_emission(year):
1530
1632
  def _get_emission_method(emission: CarbonStockChangeEmission):
1531
1633
  method = emission.method
1532
1634
  return method if isinstance(method, EmissionMethodTier) else EmissionMethodTier.TIER_1
1635
+
1636
+
1637
+ def is_soil_based_system(cycles, site_type):
1638
+ return site_type not in _SITE_TYPE_SYSTEMS_MAPPING or all(
1639
+ cumulative_nodes_term_match(
1640
+ cycle.get("practices", []),
1641
+ target_term_ids=_SITE_TYPE_SYSTEMS_MAPPING[site_type],
1642
+ cumulative_threshold=0
1643
+ ) for cycle in cycles
1644
+ )