hestia-earth-models 0.64.8__py3-none-any.whl → 0.64.10__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 (105) hide show
  1. hestia_earth/models/cml2001Baseline/abioticResourceDepletionFossilFuels.py +175 -0
  2. hestia_earth/models/cml2001Baseline/abioticResourceDepletionMineralsAndMetals.py +136 -0
  3. hestia_earth/models/cycle/siteArea.py +2 -1
  4. hestia_earth/models/environmentalFootprintV3/soilQualityIndexLandOccupation.py +73 -82
  5. hestia_earth/models/environmentalFootprintV3/soilQualityIndexLandTransformation.py +102 -116
  6. hestia_earth/models/environmentalFootprintV3/soilQualityIndexTotalLandUseEffects.py +27 -16
  7. hestia_earth/models/faostat2018/landTransformationFromCropland100YearAverage.py +3 -2
  8. hestia_earth/models/faostat2018/landTransformationFromCropland20YearAverage.py +3 -2
  9. hestia_earth/models/frischknechtEtAl2000/ionisingRadiationKbqU235Eq.py +69 -37
  10. hestia_earth/models/ipcc2019/aboveGroundBiomass.py +31 -243
  11. hestia_earth/models/ipcc2019/animal/fatContent.py +38 -0
  12. hestia_earth/models/ipcc2019/animal/liveweightGain.py +3 -54
  13. hestia_earth/models/ipcc2019/animal/liveweightPerHead.py +3 -54
  14. hestia_earth/models/ipcc2019/animal/pregnancyRateTotal.py +38 -0
  15. hestia_earth/models/ipcc2019/animal/trueProteinContent.py +38 -0
  16. hestia_earth/models/ipcc2019/animal/utils.py +87 -3
  17. hestia_earth/models/ipcc2019/animal/weightAtMaturity.py +4 -10
  18. hestia_earth/models/ipcc2019/belowGroundBiomass.py +529 -0
  19. hestia_earth/models/ipcc2019/biomass_utils.py +406 -0
  20. hestia_earth/models/ipcc2019/{co2ToAirAboveGroundBiomassStockChangeLandUseChange.py → co2ToAirAboveGroundBiomassStockChange.py} +19 -7
  21. hestia_earth/models/ipcc2019/{co2ToAirBelowGroundBiomassStockChangeLandUseChange.py → co2ToAirBelowGroundBiomassStockChange.py} +19 -7
  22. hestia_earth/models/ipcc2019/co2ToAirCarbonStockChange_utils.py +402 -73
  23. hestia_earth/models/ipcc2019/{co2ToAirSoilOrganicCarbonStockChangeManagementChange.py → co2ToAirSoilOrganicCarbonStockChange.py} +20 -8
  24. hestia_earth/models/ipcc2019/organicCarbonPerHa.py +3 -1
  25. hestia_earth/models/ipcc2019/pastureGrass_utils.py +6 -7
  26. hestia_earth/models/lcImpactAllEffects100Years/damageToFreshwaterEcosystemsFreshwaterEutrophication.py +2 -2
  27. hestia_earth/models/lcImpactAllEffects100Years/damageToFreshwaterEcosystemsWaterStress.py +2 -2
  28. hestia_earth/models/lcImpactAllEffects100Years/damageToHumanHealthParticulateMatterFormation.py +2 -2
  29. hestia_earth/models/lcImpactAllEffects100Years/damageToHumanHealthPhotochemicalOzoneFormation.py +2 -2
  30. hestia_earth/models/lcImpactAllEffects100Years/damageToHumanHealthWaterStress.py +2 -2
  31. hestia_earth/models/lcImpactAllEffects100Years/damageToMarineEcosystemsMarineEutrophication.py +2 -2
  32. hestia_earth/models/lcImpactAllEffects100Years/damageToTerrestrialEcosystemsPhotochemicalOzoneFormation.py +2 -2
  33. hestia_earth/models/lcImpactAllEffects100Years/damageToTerrestrialEcosystemsTerrestrialAcidification.py +2 -2
  34. hestia_earth/models/lcImpactAllEffectsInfinite/damageToFreshwaterEcosystemsFreshwaterEutrophication.py +2 -2
  35. hestia_earth/models/lcImpactAllEffectsInfinite/damageToFreshwaterEcosystemsWaterStress.py +2 -2
  36. hestia_earth/models/lcImpactAllEffectsInfinite/damageToHumanHealthParticulateMatterFormation.py +2 -2
  37. hestia_earth/models/lcImpactAllEffectsInfinite/damageToHumanHealthPhotochemicalOzoneFormation.py +2 -2
  38. hestia_earth/models/lcImpactAllEffectsInfinite/damageToHumanHealthWaterStress.py +2 -2
  39. hestia_earth/models/lcImpactAllEffectsInfinite/damageToMarineEcosystemsMarineEutrophication.py +2 -2
  40. hestia_earth/models/lcImpactAllEffectsInfinite/damageToTerrestrialEcosystemsPhotochemicalOzoneFormation.py +2 -2
  41. hestia_earth/models/lcImpactAllEffectsInfinite/damageToTerrestrialEcosystemsTerrestrialAcidification.py +2 -2
  42. hestia_earth/models/lcImpactCertainEffects100Years/damageToFreshwaterEcosystemsFreshwaterEutrophication.py +2 -2
  43. hestia_earth/models/lcImpactCertainEffects100Years/damageToFreshwaterEcosystemsWaterStress.py +2 -2
  44. hestia_earth/models/lcImpactCertainEffects100Years/damageToHumanHealthParticulateMatterFormation.py +2 -2
  45. hestia_earth/models/lcImpactCertainEffects100Years/damageToHumanHealthPhotochemicalOzoneFormation.py +2 -2
  46. hestia_earth/models/lcImpactCertainEffects100Years/damageToHumanHealthWaterStress.py +2 -2
  47. hestia_earth/models/lcImpactCertainEffects100Years/damageToMarineEcosystemsMarineEutrophication.py +2 -2
  48. hestia_earth/models/lcImpactCertainEffects100Years/damageToTerrestrialEcosystemsPhotochemicalOzoneFormation.py +2 -2
  49. hestia_earth/models/lcImpactCertainEffects100Years/damageToTerrestrialEcosystemsTerrestrialAcidification.py +2 -2
  50. hestia_earth/models/lcImpactCertainEffectsInfinite/damageToFreshwaterEcosystemsFreshwaterEutrophication.py +2 -2
  51. hestia_earth/models/lcImpactCertainEffectsInfinite/damageToFreshwaterEcosystemsWaterStress.py +2 -2
  52. hestia_earth/models/lcImpactCertainEffectsInfinite/damageToHumanHealthParticulateMatterFormation.py +2 -2
  53. hestia_earth/models/lcImpactCertainEffectsInfinite/damageToHumanHealthPhotochemicalOzoneFormation.py +2 -2
  54. hestia_earth/models/lcImpactCertainEffectsInfinite/damageToHumanHealthWaterStress.py +2 -2
  55. hestia_earth/models/lcImpactCertainEffectsInfinite/damageToMarineEcosystemsMarineEutrophication.py +2 -2
  56. hestia_earth/models/lcImpactCertainEffectsInfinite/damageToTerrestrialEcosystemsPhotochemicalOzoneFormation.py +2 -2
  57. hestia_earth/models/lcImpactCertainEffectsInfinite/damageToTerrestrialEcosystemsTerrestrialAcidification.py +2 -2
  58. hestia_earth/models/mocking/build_mock_search.py +44 -0
  59. hestia_earth/models/mocking/mock_search.py +8 -49
  60. hestia_earth/models/mocking/search-results.json +3078 -575
  61. hestia_earth/models/poschEtAl2008/terrestrialAcidificationPotentialAccumulatedExceedance.py +6 -3
  62. hestia_earth/models/poschEtAl2008/terrestrialEutrophicationPotentialAccumulatedExceedance.py +6 -3
  63. hestia_earth/models/preload_requests.py +1 -1
  64. hestia_earth/models/schmidt2007/utils.py +13 -4
  65. hestia_earth/models/utils/__init__.py +5 -4
  66. hestia_earth/models/utils/blank_node.py +73 -3
  67. hestia_earth/models/utils/constant.py +8 -1
  68. hestia_earth/models/utils/cycle.py +10 -13
  69. hestia_earth/models/utils/fuel.py +1 -1
  70. hestia_earth/models/utils/impact_assessment.py +39 -15
  71. hestia_earth/models/utils/lookup.py +36 -7
  72. hestia_earth/models/utils/pesticideAI.py +1 -1
  73. hestia_earth/models/utils/property.py +11 -4
  74. hestia_earth/models/utils/term.py +15 -8
  75. hestia_earth/models/version.py +1 -1
  76. {hestia_earth_models-0.64.8.dist-info → hestia_earth_models-0.64.10.dist-info}/METADATA +2 -2
  77. {hestia_earth_models-0.64.8.dist-info → hestia_earth_models-0.64.10.dist-info}/RECORD +103 -90
  78. {hestia_earth_models-0.64.8.dist-info → hestia_earth_models-0.64.10.dist-info}/WHEEL +1 -1
  79. tests/models/cml2001Baseline/test_abioticResourceDepletionFossilFuels.py +196 -0
  80. tests/models/cml2001Baseline/test_abioticResourceDepletionMineralsAndMetals.py +124 -0
  81. tests/models/edip2003/test_ozoneDepletionPotential.py +1 -13
  82. tests/models/environmentalFootprintV3/test_soilQualityIndexLandOccupation.py +97 -66
  83. tests/models/environmentalFootprintV3/test_soilQualityIndexLandTransformation.py +136 -74
  84. tests/models/environmentalFootprintV3/test_soilQualityIndexTotalLandUseEffects.py +15 -10
  85. tests/models/frischknechtEtAl2000/test_ionisingRadiationKbqU235Eq.py +67 -44
  86. tests/models/impact_assessment/test_emissions.py +1 -0
  87. tests/models/ipcc2019/animal/test_fatContent.py +22 -0
  88. tests/models/ipcc2019/animal/test_liveweightGain.py +4 -2
  89. tests/models/ipcc2019/animal/test_liveweightPerHead.py +4 -2
  90. tests/models/ipcc2019/animal/test_pregnancyRateTotal.py +22 -0
  91. tests/models/ipcc2019/animal/test_trueProteinContent.py +22 -0
  92. tests/models/ipcc2019/animal/test_weightAtMaturity.py +2 -1
  93. tests/models/ipcc2019/test_aboveGroundBiomass.py +27 -63
  94. tests/models/ipcc2019/test_belowGroundBiomass.py +146 -0
  95. tests/models/ipcc2019/test_biomass_utils.py +115 -0
  96. tests/models/ipcc2019/{test_co2ToAirAboveGroundBiomassStockChangeLandUseChange.py → test_co2ToAirAboveGroundBiomassStockChange.py} +5 -5
  97. tests/models/ipcc2019/{test_co2ToAirBelowGroundBiomassStockChangeLandUseChange.py → test_co2ToAirBelowGroundBiomassStockChange.py} +5 -5
  98. tests/models/ipcc2019/{test_co2ToAirSoilOrganicCarbonStockChangeManagementChange.py → test_co2ToAirSoilOrganicCarbonStockChange.py} +5 -5
  99. tests/models/ipcc2021/test_gwp100.py +2 -2
  100. tests/models/poschEtAl2008/test_terrestrialAcidificationPotentialAccumulatedExceedance.py +30 -17
  101. tests/models/poschEtAl2008/test_terrestrialEutrophicationPotentialAccumulatedExceedance.py +28 -14
  102. hestia_earth/models/ipcc2019/aboveGroundBiomass_utils.py +0 -180
  103. tests/models/ipcc2019/test_aboveGroundBiomass_utils.py +0 -92
  104. {hestia_earth_models-0.64.8.dist-info → hestia_earth_models-0.64.10.dist-info}/LICENSE +0 -0
  105. {hestia_earth_models-0.64.8.dist-info → hestia_earth_models-0.64.10.dist-info}/top_level.txt +0 -0
@@ -1,9 +1,8 @@
1
1
  from enum import Enum
2
2
  from functools import reduce
3
- from math import isclose
4
3
  from numpy import average, copy, random, vstack
5
4
  from numpy.typing import NDArray
6
- from typing import Callable, Optional, Union
5
+ from typing import Optional, Union
7
6
 
8
7
  from hestia_earth.schema import (
9
8
  MeasurementMethodClassification,
@@ -12,7 +11,6 @@ from hestia_earth.schema import (
12
11
  TermTermType
13
12
  )
14
13
 
15
- from hestia_earth.utils.blank_node import get_node_value
16
14
  from hestia_earth.utils.model import filter_list_term_type
17
15
  from hestia_earth.utils.tools import non_empty_list
18
16
 
@@ -23,15 +21,16 @@ from hestia_earth.models.utils.blank_node import group_nodes_by_year
23
21
  from hestia_earth.models.utils.descriptive_stats import calc_descriptive_stats
24
22
  from hestia_earth.models.utils.ecoClimateZone import EcoClimateZone, get_eco_climate_zone_value
25
23
  from hestia_earth.models.utils.measurement import _new_measurement
26
- from hestia_earth.models.utils.term import get_lookup_value
27
24
 
28
25
  from . import MODEL
29
- from .aboveGroundBiomass_utils import assign_biomass_category, BiomassCategory, sample_biomass_equilibrium
26
+ from .biomass_utils import (
27
+ BiomassCategory, detect_land_cover_change, group_by_biomass_category, group_by_term_id, sample_biomass_equilibrium,
28
+ summarise_land_cover_nodes
29
+ )
30
30
 
31
31
 
32
32
  REQUIREMENTS = {
33
33
  "Site": {
34
- "siteType": ["cropland", "permanent pasture", "forest", "other natural vegetation"],
35
34
  "management": [
36
35
  {
37
36
  "@type": "Management",
@@ -49,7 +48,10 @@ REQUIREMENTS = {
49
48
  "value": ["1", "2", "3", "4", "7", "8", "9", "10", "11", "12"],
50
49
  "term.@id": "ecoClimateZone"
51
50
  }
52
- ]
51
+ ],
52
+ "none": {
53
+ "siteType": ["glass or high accessible cover"]
54
+ }
53
55
  }
54
56
  }
55
57
  LOOKUPS = {
@@ -93,28 +95,13 @@ _METHOD_CLASSIFICATION = MeasurementMethodClassification.TIER_1_MODEL.value
93
95
  _STATS_DEFINITION = MeasurementStatsDefinition.SIMULATED.value
94
96
 
95
97
  _LAND_COVER_TERM_TYPE = TermTermType.LANDCOVER
96
- _TARGET_LAND_COVER = 100
97
98
 
98
99
  _EQUILIBRIUM_TRANSITION_PERIOD = 20
99
100
  _EXCLUDED_ECO_CLIMATE_ZONES = {EcoClimateZone.POLAR_MOIST, EcoClimateZone.POLAR_DRY}
100
- _VALID_SITE_TYPES = {
101
- SiteSiteType.CROPLAND.value,
102
- SiteSiteType.FOREST.value,
103
- SiteSiteType.OTHER_NATURAL_VEGETATION.value,
104
- SiteSiteType.PERMANENT_PASTURE.value
101
+ _EXCLUDED_SITE_TYPES = {
102
+ SiteSiteType.GLASS_OR_HIGH_ACCESSIBLE_COVER.value
105
103
  }
106
104
 
107
- _GROUP_LAND_COVER_BY_BIOMASS_CATEGORY = [
108
- BiomassCategory.ANNUAL_CROPS,
109
- BiomassCategory.GRASSLAND,
110
- BiomassCategory.OTHER,
111
- BiomassCategory.SHORT_ROTATION_COPPICE
112
- ]
113
- """
114
- Terms associated with these biomass categories can be grouped together when summarising land cover coverage in
115
- `_group_by_term_id`.
116
- """
117
-
118
105
 
119
106
  class _InventoryKey(Enum):
120
107
  """
@@ -171,7 +158,7 @@ def _should_run(site: dict) -> tuple[bool, dict, dict]:
171
158
 
172
159
  land_cover = filter_list_term_type(site.get("management", []), _LAND_COVER_TERM_TYPE)
173
160
 
174
- has_valid_site_type = site_type in _VALID_SITE_TYPES
161
+ has_valid_site_type = site_type not in _EXCLUDED_SITE_TYPES
175
162
  has_valid_eco_climate_zone = all([
176
163
  eco_climate_zone,
177
164
  eco_climate_zone not in _EXCLUDED_ECO_CLIMATE_ZONES
@@ -267,12 +254,12 @@ def _compile_inventory(land_cover_nodes: list[dict]) -> dict:
267
254
  prev_year, current_year = year_pair
268
255
  land_cover_nodes = land_cover_grouped.get(current_year, {})
269
256
 
270
- biomass_category_summary = _summarise_land_cover_nodes(land_cover_nodes, _group_by_biomass_category)
271
- land_cover_summary = _summarise_land_cover_nodes(land_cover_nodes, _group_by_term_id)
257
+ biomass_category_summary = summarise_land_cover_nodes(land_cover_nodes, group_by_biomass_category)
258
+ land_cover_summary = summarise_land_cover_nodes(land_cover_nodes, group_by_term_id)
272
259
 
273
260
  prev_land_cover_summary = inventory.get(prev_year, {}).get(_InventoryKey.LAND_COVER_SUMMARY, {})
274
261
 
275
- is_lcc_event = _is_lcc_event(land_cover_summary, prev_land_cover_summary)
262
+ is_lcc_event = detect_land_cover_change(land_cover_summary, prev_land_cover_summary)
276
263
 
277
264
  time_delta = current_year - prev_year
278
265
  prev_years_since_lcc_event = inventory.get(prev_year, {}).get(_InventoryKey.YEARS_SINCE_LCC_EVENT, 0)
@@ -295,11 +282,11 @@ def _compile_inventory(land_cover_nodes: list[dict]) -> dict:
295
282
 
296
283
  initial = {
297
284
  start_year: {
298
- _InventoryKey.BIOMASS_CATEGORY_SUMMARY: _summarise_land_cover_nodes(
299
- initial_land_cover_nodes, _group_by_biomass_category
285
+ _InventoryKey.BIOMASS_CATEGORY_SUMMARY: summarise_land_cover_nodes(
286
+ initial_land_cover_nodes, group_by_biomass_category
300
287
  ),
301
- _InventoryKey.LAND_COVER_SUMMARY: _summarise_land_cover_nodes(
302
- initial_land_cover_nodes, _group_by_term_id
288
+ _InventoryKey.LAND_COVER_SUMMARY: summarise_land_cover_nodes(
289
+ initial_land_cover_nodes, group_by_term_id
303
290
  ),
304
291
  _InventoryKey.LAND_COVER_CHANGE_EVENT: False,
305
292
  _InventoryKey.YEARS_SINCE_LCC_EVENT: _EQUILIBRIUM_TRANSITION_PERIOD,
@@ -314,216 +301,6 @@ def _compile_inventory(land_cover_nodes: list[dict]) -> dict:
314
301
  )
315
302
 
316
303
 
317
- def _group_by_biomass_category(result: dict[BiomassCategory, float], node: dict) -> dict[BiomassCategory, float]:
318
- """
319
- Reducer function for `_group_land_cover_nodes_by` that groups and sums node value by their associated
320
- `BiomassCategory`.
321
-
322
- Parameters
323
- ----------
324
- result : dict
325
- A dict with the shape `{category (BiomassCategory): sum_value (float), ...categories}`.
326
- node : dict
327
- A HESTIA `Management` node with `term.termType` = `landCover`.
328
-
329
- Returns
330
- -------
331
- result : dict
332
- A dict with the shape `{category (BiomassCategory): sum_value (float), ...categories}`.
333
- """
334
- biomass_category = _retrieve_biomass_category(node)
335
- value = get_node_value(node)
336
-
337
- update_dict = {biomass_category: result.get(biomass_category, 0) + value}
338
-
339
- should_run = biomass_category and value
340
- return result | update_dict if should_run else result
341
-
342
-
343
- def _group_by_term_id(
344
- result: dict[Union[str, BiomassCategory], float], node: dict
345
- ) -> dict[Union[str, BiomassCategory], float]:
346
- """
347
- Reducer function for `_group_land_cover_nodes_by` that groups and sums node value by their `term.@id` if a the land
348
- cover is a woody plant, else by their associated `BiomassCategory`
349
-
350
- Land cover events can be triggered by changes in land cover within the same `BiomassCategory` (e.g., `peachTree` to
351
- `appleTree`) due to the requirement to clear the previous woody biomass to establish the new land cover.
352
-
353
- Some land covers (e.g., land covers associated with the `BiomassCategory` = `Annual crops`, `Grassland`, `Other` or
354
- `Short rotation coppice`) are exempt from this rule due to the Tier 1 assumptions that biomass does not accumulate
355
- within the category or the maturity cycle of the land cover is significantly shorter than the amortisation period of
356
- 20 years.
357
-
358
- Parameters
359
- ----------
360
- result : dict
361
- A dict with the shape `{category (str | BiomassCategory): sum_value (float), ...categories}`.
362
- node : dict
363
- A HESTIA `Management` node with `term.termType` = `landCover`.
364
-
365
- Returns
366
- -------
367
- result : dict
368
- A dict with the shape `{category (str | BiomassCategory): sum_value (float), ...categories}`.
369
- """
370
- term_id = node.get("term", {}).get("@id")
371
- biomass_category = _retrieve_biomass_category(node)
372
- value = get_node_value(node)
373
-
374
- key = biomass_category if biomass_category in _GROUP_LAND_COVER_BY_BIOMASS_CATEGORY else term_id
375
-
376
- update_dict = {key: result.get(key, 0) + value}
377
-
378
- should_run = biomass_category and value
379
- return result | update_dict if should_run else result
380
-
381
-
382
- def _retrieve_biomass_category(node: dict) -> Optional[BiomassCategory]:
383
- """
384
- Retrieve the `BiomassCategory` associated with a land cover using the `BIOMASS_CATEGORY` lookup.
385
-
386
- If lookup value is missing, return `None`.
387
-
388
- Parameters
389
- ----------
390
- node : dict
391
- A valid `Management` node with `term.termType` = `landCover`.
392
-
393
- Returns
394
- -------
395
- BiomassCategory | None
396
- The associated `BiomassCategory` or `None`
397
- """
398
- LOOKUP = LOOKUPS["landCover"]
399
- term = node.get("term", {})
400
- lookup_value = get_lookup_value(term, LOOKUP)
401
-
402
- return assign_biomass_category(lookup_value) if lookup_value else None
403
-
404
-
405
- def _summarise_land_cover_nodes(
406
- land_cover_nodes: list[dict],
407
- group_by_func: Callable[[dict, dict], dict] = _group_by_biomass_category
408
- ) -> dict[Union[str, BiomassCategory], float]:
409
- """
410
- Group land cover nodes using `group_by_func`.
411
-
412
- Parameters
413
- ----------
414
- land_cover_nodes : list[dict]
415
- A list of HESTIA `Management` nodes with `term.termType` = `landCover`.
416
-
417
- Returns
418
- -------
419
- result : dict
420
- A dict with the shape `{category (str | BiomassCategory): sum_value (float), ...categories}`.
421
- """
422
- category_cover = reduce(group_by_func, land_cover_nodes, dict())
423
- return _rescale_category_cover(category_cover)
424
-
425
-
426
- def _rescale_category_cover(
427
- category_cover: dict[Union[BiomassCategory, str], float]
428
- ) -> dict[Union[BiomassCategory, str], float]:
429
- """
430
- Enforce a land cover coverage of 100%.
431
-
432
- If input coverage is less than 100%, fill the remainder with `BiomassCategory.OTHER`. If the input coverage is
433
- greater than 100%, proportionally downscale all categories.
434
-
435
- Parameters
436
- ----------
437
- category_cover : dict[BiomassCategory | str, float]
438
- The input category cover dict.
439
-
440
- Returns
441
- -------
442
- result : dict[BiomassCategory | str, float]
443
- The rescaled category cover dict.
444
- """
445
- total_cover = sum(category_cover.values())
446
- return (
447
- _fill_category_cover(category_cover) if total_cover < _TARGET_LAND_COVER
448
- else _squash_category_cover(category_cover) if total_cover > _TARGET_LAND_COVER
449
- else category_cover
450
- )
451
-
452
-
453
- def _fill_category_cover(
454
- category_cover: dict[Union[BiomassCategory, str], float]
455
- ) -> dict[Union[BiomassCategory, str], float]:
456
- """
457
- Fill the land cover coverage with `BiomassCategory.OTHER` to enforce a total coverage of 100%.
458
-
459
- Parameters
460
- ----------
461
- category_cover : dict[BiomassCategory | str, float]
462
- The input category cover dict.
463
-
464
- Returns
465
- -------
466
- result : dict[BiomassCategory | str, float]
467
- The rescaled category cover dict.
468
- """
469
- total_cover = sum(category_cover.values())
470
- update_dict = {
471
- BiomassCategory.OTHER: category_cover.get(BiomassCategory.OTHER, 0) + (_TARGET_LAND_COVER - total_cover)
472
- }
473
- return category_cover | update_dict
474
-
475
-
476
- def _squash_category_cover(
477
- category_cover: dict[Union[BiomassCategory, str], float]
478
- ) -> dict[Union[BiomassCategory, str], float]:
479
- """
480
- Proportionally shrink all land cover categories to enforce a total coverage of 100%.
481
-
482
- Parameters
483
- ----------
484
- category_cover : dict[BiomassCategory | str, float]
485
- The input category cover dict.
486
-
487
- Returns
488
- -------
489
- result : dict[BiomassCategory | str, float]
490
- The rescaled category cover dict.
491
- """
492
- total_cover = sum(category_cover.values())
493
- return {
494
- category: (cover / total_cover) * _TARGET_LAND_COVER
495
- for category, cover in category_cover.items()
496
- }
497
-
498
-
499
- def _is_lcc_event(
500
- a: dict[Union[BiomassCategory, str], float],
501
- b: dict[Union[BiomassCategory, str], float]
502
- ) -> bool:
503
- """
504
- Land cover values (% area) are compared with an absolute tolerance of 0.0001, which is equivalent to 1 m2 per
505
- hectare.
506
-
507
- Parameters
508
- ----------
509
- a : dict[BiomassCategory | str, float]
510
- The first land-cover summary dict.
511
- b : dict[BiomassCategory | str, float]
512
- The second land-cover summary dict.
513
-
514
- Returns
515
- -------
516
- bool
517
- Whether a land-cover change event has occured.
518
- """
519
- keys_match = sorted(str(key) for key in b.keys()) == sorted(str(key) for key in a.keys())
520
- values_close = all(
521
- isclose(b.get(key), a.get(key, -999), abs_tol=0.0001) for key in b.keys()
522
- )
523
-
524
- return not all([keys_match, values_close])
525
-
526
-
527
304
  def _format_inventory(inventory: dict) -> str:
528
305
  """
529
306
  Format the SOC inventory for logging as a table. Rows represent inventory years, columns represent soc stock change
@@ -665,7 +442,7 @@ def _run(
665
442
  timestamps = list(inventory.keys())
666
443
 
667
444
  factor_cache = {
668
- category: sample_biomass_equilibrium(iterations, category, eco_climate_zone, rng)
445
+ category: sample_biomass_equilibrium(iterations, category, eco_climate_zone, _build_col_name, seed=rng)
669
446
  for category in unique_biomass_categories
670
447
  }
671
448
 
@@ -710,6 +487,17 @@ def _run(
710
487
  return [_measurement(timestamps, **descriptive_stats)]
711
488
 
712
489
 
490
+ def _build_col_name(biomass_category: BiomassCategory) -> str:
491
+ """
492
+ Get the column name for the `ecoClimateZone-lookup.csv` for a specific biomass category equilibrium.
493
+ """
494
+ COL_NAME_ROOT = "AG_BIOMASS_EQUILIBRIUM_KG_C_HECTARE_"
495
+ return (
496
+ f"{COL_NAME_ROOT}{biomass_category.name}" if isinstance(biomass_category, BiomassCategory)
497
+ else f"{COL_NAME_ROOT}OTHER"
498
+ )
499
+
500
+
713
501
  def _measurement(
714
502
  timestamps: list[int],
715
503
  value: list[float],
@@ -0,0 +1,38 @@
1
+ from .utils import should_run_by_productivity_lookup, run_animal_by_productivity
2
+
3
+ REQUIREMENTS = {
4
+ "Cycle": {
5
+ "site": {
6
+ "@type": "Site",
7
+ "country": {"@type": "Term", "termType": "region"}
8
+ },
9
+ "animals": [{
10
+ "@type": "Animal",
11
+ "term.termType": "liveAnimal",
12
+ "practices": {
13
+ "@type": "Practice",
14
+ "term.termType": "animalManagement"
15
+ }
16
+ }]
17
+ }
18
+ }
19
+ LOOKUPS = {
20
+ "region-liveAnimal-milkFatContent": "fat content",
21
+ "liveAnimal": "milkYieldPracticeTermIds"
22
+ }
23
+ RETURNS = {
24
+ "Animal": [{
25
+ "practices": [{
26
+ "@type": "Practice",
27
+ "value": ""
28
+ }]
29
+ }]
30
+ }
31
+ TERM_ID = 'fatContent'
32
+
33
+
34
+ def run(cycle: dict):
35
+ animals = should_run_by_productivity_lookup(
36
+ TERM_ID, cycle, list(LOOKUPS.keys())[0], practice_id=LOOKUPS['liveAnimal']
37
+ )
38
+ return list(map(run_animal_by_productivity(TERM_ID, include_practice=True), animals))
@@ -1,11 +1,4 @@
1
- from hestia_earth.schema import TermTermType
2
- from hestia_earth.utils.model import filter_list_term_type
3
-
4
- from hestia_earth.models.log import logRequirements, logShouldRun
5
- from hestia_earth.models.utils.blank_node import merge_blank_nodes
6
- from hestia_earth.models.utils.property import _new_property, node_has_no_property
7
- from .utils import productivity_lookup_value
8
- from .. import MODEL
1
+ from .utils import should_run_by_productivity_lookup, run_animal_by_productivity
9
2
 
10
3
  REQUIREMENTS = {
11
4
  "Cycle": {
@@ -40,50 +33,6 @@ RETURNS = {
40
33
  TERM_ID = 'liveweightGain'
41
34
 
42
35
 
43
- def _property(value: float):
44
- prop = _new_property(TERM_ID, MODEL)
45
- prop['value'] = value
46
- return prop
47
-
48
-
49
- def _run_animal(data: dict):
50
- animal = data.get('animal')
51
- value = data.get('value')
52
- return animal | {
53
- 'properties': merge_blank_nodes(animal.get('properties', []), [_property(value)])
54
- }
55
-
56
-
57
- def _should_run(cycle: dict):
58
- country = cycle.get('site', {}).get('country', {})
59
- country_id = country.get('@id')
60
- live_animals = filter_list_term_type(cycle.get('animals', []), TermTermType.LIVEANIMAL)
61
- live_animals = list(filter(node_has_no_property(TERM_ID), live_animals))
62
- live_animals_with_value = [{
63
- 'animal': animal,
64
- 'value': productivity_lookup_value(TERM_ID, list(LOOKUPS.keys())[0], country, animal)
65
- } for animal in live_animals]
66
-
67
- def _should_run_animal(value: dict):
68
- animal = value.get('animal')
69
- lookup_value = value.get('value')
70
- term_id = animal.get('term').get('@id')
71
-
72
- logRequirements(cycle, model=MODEL, term=term_id, property=TERM_ID,
73
- country_id=country_id,
74
- liveweightGain=lookup_value)
75
-
76
- should_run = all([
77
- country_id,
78
- lookup_value is not None
79
- ])
80
- logShouldRun(cycle, MODEL, term_id, should_run, property=TERM_ID)
81
-
82
- return should_run
83
-
84
- return list(filter(_should_run_animal, live_animals_with_value))
85
-
86
-
87
36
  def run(cycle: dict):
88
- animals = _should_run(cycle)
89
- return list(map(_run_animal, animals))
37
+ animals = should_run_by_productivity_lookup(TERM_ID, cycle, list(LOOKUPS.keys())[0])
38
+ return list(map(run_animal_by_productivity(TERM_ID), animals))
@@ -1,11 +1,4 @@
1
- from hestia_earth.schema import TermTermType
2
- from hestia_earth.utils.model import filter_list_term_type
3
-
4
- from hestia_earth.models.log import logRequirements, logShouldRun
5
- from hestia_earth.models.utils.blank_node import merge_blank_nodes
6
- from hestia_earth.models.utils.property import _new_property, node_has_no_property
7
- from .utils import productivity_lookup_value
8
- from .. import MODEL
1
+ from .utils import should_run_by_productivity_lookup, run_animal_by_productivity
9
2
 
10
3
  REQUIREMENTS = {
11
4
  "Cycle": {
@@ -40,50 +33,6 @@ RETURNS = {
40
33
  TERM_ID = 'liveweightPerHead'
41
34
 
42
35
 
43
- def _property(value: float):
44
- prop = _new_property(TERM_ID, MODEL)
45
- prop['value'] = value
46
- return prop
47
-
48
-
49
- def _run_animal(data: dict):
50
- animal = data.get('animal')
51
- value = data.get('value')
52
- return animal | {
53
- 'properties': merge_blank_nodes(animal.get('properties', []), [_property(value)])
54
- }
55
-
56
-
57
- def _should_run(cycle: dict):
58
- country = cycle.get('site', {}).get('country', {})
59
- country_id = country.get('@id')
60
- live_animals = filter_list_term_type(cycle.get('animals', []), TermTermType.LIVEANIMAL)
61
- live_animals = list(filter(node_has_no_property(TERM_ID), live_animals))
62
- live_animals_with_value = [{
63
- 'animal': animal,
64
- 'value': productivity_lookup_value(TERM_ID, list(LOOKUPS.keys())[0], country, animal)
65
- } for animal in live_animals]
66
-
67
- def _should_run_animal(value: dict):
68
- animal = value.get('animal')
69
- lookup_value = value.get('value')
70
- term_id = animal.get('term').get('@id')
71
-
72
- logRequirements(cycle, model=MODEL, term=term_id, property=TERM_ID,
73
- country_id=country_id,
74
- liveweightPerHead=lookup_value)
75
-
76
- should_run = all([
77
- country_id,
78
- lookup_value is not None
79
- ])
80
- logShouldRun(cycle, MODEL, term_id, should_run, property=TERM_ID)
81
-
82
- return should_run
83
-
84
- return list(filter(_should_run_animal, live_animals_with_value))
85
-
86
-
87
36
  def run(cycle: dict):
88
- animals = _should_run(cycle)
89
- return list(map(_run_animal, animals))
37
+ animals = should_run_by_productivity_lookup(TERM_ID, cycle, list(LOOKUPS.keys())[0])
38
+ return list(map(run_animal_by_productivity(TERM_ID), animals))
@@ -0,0 +1,38 @@
1
+ from .utils import should_run_by_productivity_lookup, run_animal_by_productivity
2
+
3
+ REQUIREMENTS = {
4
+ "Cycle": {
5
+ "site": {
6
+ "@type": "Site",
7
+ "country": {"@type": "Term", "termType": "region"}
8
+ },
9
+ "animals": [{
10
+ "@type": "Animal",
11
+ "term.termType": "liveAnimal",
12
+ "none": {
13
+ "properties": [{
14
+ "@type": "Property",
15
+ "value": "",
16
+ "term.@id": "pregnancyRateTotal"
17
+ }]
18
+ }
19
+ }]
20
+ }
21
+ }
22
+ LOOKUPS = {
23
+ "region-liveAnimal-pregnancyRateTotal": "pregnancy rate"
24
+ }
25
+ RETURNS = {
26
+ "Animal": [{
27
+ "properties": [{
28
+ "@type": "Property",
29
+ "value": ""
30
+ }]
31
+ }]
32
+ }
33
+ TERM_ID = 'pregnancyRateTotal'
34
+
35
+
36
+ def run(cycle: dict):
37
+ animals = should_run_by_productivity_lookup(TERM_ID, cycle, list(LOOKUPS.keys())[0])
38
+ return list(map(run_animal_by_productivity(TERM_ID), animals))
@@ -0,0 +1,38 @@
1
+ from .utils import should_run_by_productivity_lookup, run_animal_by_productivity
2
+
3
+ REQUIREMENTS = {
4
+ "Cycle": {
5
+ "site": {
6
+ "@type": "Site",
7
+ "country": {"@type": "Term", "termType": "region"}
8
+ },
9
+ "animals": [{
10
+ "@type": "Animal",
11
+ "term.termType": "liveAnimal",
12
+ "practices": {
13
+ "@type": "Practice",
14
+ "term.termType": "animalManagement"
15
+ }
16
+ }]
17
+ }
18
+ }
19
+ LOOKUPS = {
20
+ "region-liveAnimal-milkTrueProteinContent": "protein content",
21
+ "liveAnimal": "milkYieldPracticeTermIds"
22
+ }
23
+ RETURNS = {
24
+ "Animal": [{
25
+ "practices": [{
26
+ "@type": "Practice",
27
+ "value": ""
28
+ }]
29
+ }]
30
+ }
31
+ TERM_ID = 'trueProteinContent'
32
+
33
+
34
+ def run(cycle: dict):
35
+ animals = should_run_by_productivity_lookup(
36
+ TERM_ID, cycle, list(LOOKUPS.keys())[0], practice_id=LOOKUPS['liveAnimal']
37
+ )
38
+ return list(map(run_animal_by_productivity(TERM_ID, include_practice=True), animals))