hestia-earth-models 0.64.14__py3-none-any.whl → 0.65.1__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 (117) hide show
  1. hestia_earth/models/agribalyse2016/fuelElectricity.py +1 -1
  2. hestia_earth/models/cache_sites.py +15 -24
  3. hestia_earth/models/chaudharyBrooks2018/damageToTerrestrialEcosystemsLandTransformation.py +6 -9
  4. hestia_earth/models/cycle/input/hestiaAggregatedData.py +46 -22
  5. hestia_earth/models/cycle/pre_checks/cache_sources.py +3 -25
  6. hestia_earth/models/cycle/product/economicValueShare.py +2 -2
  7. hestia_earth/models/ecoinventV3/__init__.py +3 -2
  8. hestia_earth/models/ecoinventV3/utils.py +1 -2
  9. hestia_earth/models/ecoinventV3AndEmberClimate/__init__.py +3 -2
  10. hestia_earth/models/environmentalFootprintV3/soilQualityIndexLandTransformation.py +17 -37
  11. hestia_earth/models/faostat2018/landTransformation100YearAverageDuringCycle.py +34 -0
  12. hestia_earth/models/faostat2018/landTransformation20YearAverageDuringCycle.py +34 -0
  13. hestia_earth/models/faostat2018/utils.py +47 -3
  14. hestia_earth/models/hestia/landCover.py +5 -5
  15. hestia_earth/models/hestia/seed_emissions.py +284 -0
  16. hestia_earth/models/ipcc2019/aboveGroundBiomass.py +2 -2
  17. hestia_earth/models/ipcc2019/belowGroundBiomass.py +8 -2
  18. hestia_earth/models/ipcc2019/biomass_utils.py +11 -4
  19. hestia_earth/models/ipcc2019/ch4ToAirEntericFermentation.py +19 -10
  20. hestia_earth/models/ipcc2019/co2ToAirAboveGroundBiomassStockChange.py +2 -1
  21. hestia_earth/models/ipcc2019/co2ToAirBelowGroundBiomassStockChange.py +2 -1
  22. hestia_earth/models/ipcc2019/co2ToAirCarbonStockChange_utils.py +8 -7
  23. hestia_earth/models/ipcc2019/co2ToAirSoilOrganicCarbonStockChange.py +2 -1
  24. hestia_earth/models/ipcc2019/organicCarbonPerHa_tier_1_utils.py +28 -34
  25. hestia_earth/models/ipcc2019/organicCarbonPerHa_tier_2_utils.py +8 -12
  26. hestia_earth/models/ipcc2019/organicCarbonPerHa_utils.py +13 -30
  27. hestia_earth/models/linkedImpactAssessment/freshwaterWithdrawalsInputsProduction.py +4 -1
  28. hestia_earth/models/linkedImpactAssessment/landOccupationInputsProduction.py +16 -1
  29. hestia_earth/models/linkedImpactAssessment/{landTransformationFromForest100YearAverageInputsProduction.py → landTransformation100YearAverageInputsProduction.py} +8 -3
  30. hestia_earth/models/linkedImpactAssessment/{landTransformationFromCropland100YearAverageInputsProduction.py → landTransformation20YearAverageInputsProduction.py} +8 -3
  31. hestia_earth/models/linkedImpactAssessment/utils.py +80 -16
  32. hestia_earth/models/mocking/search-results.json +448 -448
  33. hestia_earth/models/pooreNemecek2018/excretaKgN.py +45 -41
  34. hestia_earth/models/pooreNemecek2018/excretaKgVs.py +89 -63
  35. hestia_earth/models/pooreNemecek2018/saplingsDepreciatedAmountPerCycle.py +8 -8
  36. hestia_earth/models/pooreNemecek2018/utils.py +60 -19
  37. hestia_earth/models/schererPfister2015/nErosionSoilFlux.py +4 -3
  38. hestia_earth/models/schererPfister2015/pErosionSoilFlux.py +4 -3
  39. hestia_earth/models/schererPfister2015/utils.py +12 -9
  40. hestia_earth/models/site/management.py +70 -55
  41. hestia_earth/models/site/pre_checks/cache_sources.py +2 -20
  42. hestia_earth/models/utils/__init__.py +12 -1
  43. hestia_earth/models/utils/aggregated.py +1 -1
  44. hestia_earth/models/utils/blank_node.py +20 -12
  45. hestia_earth/models/utils/cache_sources.py +15 -0
  46. hestia_earth/models/utils/crop.py +5 -0
  47. hestia_earth/models/utils/indicator.py +3 -1
  48. hestia_earth/models/version.py +1 -1
  49. {hestia_earth_models-0.64.14.dist-info → hestia_earth_models-0.65.1.dist-info}/LICENSE +1 -1
  50. {hestia_earth_models-0.64.14.dist-info → hestia_earth_models-0.65.1.dist-info}/METADATA +3 -3
  51. {hestia_earth_models-0.64.14.dist-info → hestia_earth_models-0.65.1.dist-info}/RECORD +80 -109
  52. tests/models/cml2001Baseline/test_abioticResourceDepletionMineralsAndMetals.py +1 -1
  53. tests/models/cycle/input/test_hestiaAggregatedData.py +5 -2
  54. tests/models/environmentalFootprintV3/test_soilQualityIndexLandTransformation.py +39 -28
  55. tests/models/{hyde32/test_landTransformationFromForest20YearAverageDuringCycle.py → faostat2018/test_landTransformation100YearAverageDuringCycle.py} +5 -5
  56. tests/models/{hyde32/test_landTransformationFromForest100YearAverageDuringCycle.py → faostat2018/test_landTransformation20YearAverageDuringCycle.py} +5 -5
  57. tests/models/faostat2018/test_utils.py +28 -0
  58. tests/models/hestia/test_landCover.py +2 -1
  59. tests/models/hestia/test_seed_emissions.py +27 -0
  60. tests/models/ipcc2019/test_aboveGroundBiomass.py +40 -4
  61. tests/models/ipcc2019/test_belowGroundBiomass.py +40 -4
  62. tests/models/ipcc2019/test_co2ToAirAboveGroundBiomassStockChange.py +52 -15
  63. tests/models/ipcc2019/test_co2ToAirBelowGroundBiomassStockChange.py +50 -14
  64. tests/models/ipcc2019/test_co2ToAirSoilOrganicCarbonStockChange.py +53 -32
  65. tests/models/ipcc2019/test_organicCarbonPerHa.py +91 -108
  66. tests/models/ipcc2019/test_organicCarbonPerHa_tier_1_utils.py +33 -50
  67. tests/models/ipcc2019/test_organicCarbonPerHa_tier_2_utils.py +0 -52
  68. tests/models/linkedImpactAssessment/test_freshwaterWithdrawalsInputsProduction.py +6 -4
  69. tests/models/linkedImpactAssessment/test_landOccupationInputsProduction.py +6 -4
  70. tests/models/linkedImpactAssessment/{test_landTransformationFromForest100YearAverageInputsProduction.py → test_landTransformation100YearAverageInputsProduction.py} +7 -5
  71. tests/models/linkedImpactAssessment/{test_landTransformationFromForest20YearAverageInputsProduction.py → test_landTransformation20YearAverageInputsProduction.py} +7 -5
  72. tests/models/pooreNemecek2018/test_excretaKgN.py +2 -2
  73. tests/models/pooreNemecek2018/test_excretaKgVs.py +1 -1
  74. tests/models/pooreNemecek2018/test_utils.py +26 -0
  75. tests/models/site/test_management.py +10 -27
  76. tests/models/test_cache_sites.py +40 -12
  77. tests/models/utils/test_blank_node.py +0 -8
  78. tests/models/utils/test_cache_sources.py +21 -0
  79. hestia_earth/models/blonkConsultants2016/landTransformationFromForest20YearAverageDuringCycle.py +0 -90
  80. hestia_earth/models/faostat2018/landTransformationFromCropland100YearAverage.py +0 -74
  81. hestia_earth/models/faostat2018/landTransformationFromCropland20YearAverage.py +0 -74
  82. hestia_earth/models/hyde32/__init__.py +0 -13
  83. hestia_earth/models/hyde32/landTransformationFromCropland100YearAverageDuringCycle.py +0 -60
  84. hestia_earth/models/hyde32/landTransformationFromCropland20YearAverageDuringCycle.py +0 -60
  85. hestia_earth/models/hyde32/landTransformationFromForest100YearAverageDuringCycle.py +0 -60
  86. hestia_earth/models/hyde32/landTransformationFromForest20YearAverageDuringCycle.py +0 -60
  87. hestia_earth/models/hyde32/landTransformationFromOtherNaturalVegetation100YearAverageDuringCycle.py +0 -61
  88. hestia_earth/models/hyde32/landTransformationFromOtherNaturalVegetation20YearAverageDuringCycle.py +0 -61
  89. hestia_earth/models/hyde32/landTransformationFromPermanentPasture100YearAverageDuringCycle.py +0 -61
  90. hestia_earth/models/hyde32/landTransformationFromPermanentPasture20YearAverageDuringCycle.py +0 -61
  91. hestia_earth/models/hyde32/utils.py +0 -72
  92. hestia_earth/models/linkedImpactAssessment/landTransformationFromCropland20YearAverageInputsProduction.py +0 -36
  93. hestia_earth/models/linkedImpactAssessment/landTransformationFromForest20YearAverageInputsProduction.py +0 -36
  94. hestia_earth/models/linkedImpactAssessment/landTransformationFromOtherNaturalVegetation100YearAverageInputsProduction.py +0 -36
  95. hestia_earth/models/linkedImpactAssessment/landTransformationFromOtherNaturalVegetation20YearAverageInputsProduction.py +0 -36
  96. hestia_earth/models/linkedImpactAssessment/landTransformationFromPermanentPasture100YearAverageInputsProduction.py +0 -36
  97. hestia_earth/models/linkedImpactAssessment/landTransformationFromPermanentPasture20YearAverageInputsProduction.py +0 -36
  98. tests/models/blonkConsultants2016/test_landTransformationFromForest20YearAverageDuringCycle.py +0 -36
  99. tests/models/cycle/pre_checks/test_cache_sources.py +0 -25
  100. tests/models/faostat2018/test_landTransformationFromCropland100YearAverage.py +0 -40
  101. tests/models/faostat2018/test_landTransformationFromCropland20YearAverage.py +0 -40
  102. tests/models/hyde32/__init__.py +0 -0
  103. tests/models/hyde32/test_landTransformationFromCropland100YearAverageDuringCycle.py +0 -21
  104. tests/models/hyde32/test_landTransformationFromCropland20YearAverageDuringCycle.py +0 -21
  105. tests/models/hyde32/test_landTransformationFromOtherNaturalVegetation100YearAverageDuringCycle.py +0 -23
  106. tests/models/hyde32/test_landTransformationFromOtherNaturalVegetation20YearAverageDuringCycle.py +0 -21
  107. tests/models/hyde32/test_landTransformationFromPermanentPasture100YearAverageDuringCycle.py +0 -21
  108. tests/models/hyde32/test_landTransformationFromPermanentPasture20YearAverageDuringCycle.py +0 -21
  109. tests/models/linkedImpactAssessment/test_landTransformationFromCropland100YearAverageInputsProduction.py +0 -23
  110. tests/models/linkedImpactAssessment/test_landTransformationFromCropland20YearAverageInputsProduction.py +0 -23
  111. tests/models/linkedImpactAssessment/test_landTransformationFromOtherNaturalVegetation100YearAverageInputsProduction.py +0 -23
  112. tests/models/linkedImpactAssessment/test_landTransformationFromOtherNaturalVegetation20YearAverageInputsProduction.py +0 -23
  113. tests/models/linkedImpactAssessment/test_landTransformationFromPermanentPasture100YearAverageInputsProduction.py +0 -24
  114. tests/models/linkedImpactAssessment/test_landTransformationFromPermanentPasture20YearAverageInputsProduction.py +0 -24
  115. tests/models/site/pre_checks/test_cache_sources.py +0 -21
  116. {hestia_earth_models-0.64.14.dist-info → hestia_earth_models-0.65.1.dist-info}/WHEEL +0 -0
  117. {hestia_earth_models-0.64.14.dist-info → hestia_earth_models-0.65.1.dist-info}/top_level.txt +0 -0
@@ -20,10 +20,11 @@ from hestia_earth.utils.tools import safe_parse_float, flatten
20
20
  from hestia_earth.utils.blank_node import get_node_value
21
21
 
22
22
  from hestia_earth.models.log import logRequirements, logShouldRun, log_as_table
23
- from hestia_earth.models.utils import _include
23
+ from hestia_earth.models.utils import _include, _omit
24
24
  from hestia_earth.models.utils.management import _new_management
25
25
  from hestia_earth.models.utils.term import get_lookup_value
26
26
  from hestia_earth.models.utils.blank_node import condense_nodes
27
+ from hestia_earth.models.utils.crop import get_landCover_term_id
27
28
  from hestia_earth.models.utils.site import (
28
29
  related_cycles, get_land_cover_term_id as get_landCover_term_id_from_site_type
29
30
  )
@@ -39,8 +40,7 @@ REQUIREMENTS = {
39
40
  "products": [
40
41
  {
41
42
  "@type": "Product",
42
- "term.termType": ["crop", "forage", "landCover"],
43
- "units": ["% area", "boolean"]
43
+ "term.termType": ["crop", "forage", "landCover"]
44
44
  }
45
45
  ],
46
46
  "practices": [
@@ -52,7 +52,6 @@ REQUIREMENTS = {
52
52
  "landUseManagement",
53
53
  "system"
54
54
  ],
55
- "units": ["% area", "boolean"],
56
55
  "value": ""
57
56
  }
58
57
  ],
@@ -87,11 +86,11 @@ LOOKUPS = {
87
86
  "inorganicFertiliser": "nitrogenContent",
88
87
  "organicFertiliser": "ANIMAL_MANURE",
89
88
  "soilAmendment": "PRACTICE_INCREASING_C_INPUT",
90
- "landUseManagement": "GAP_FILL_TO_MANAGEMENT"
89
+ "landUseManagement": "GAP_FILL_TO_MANAGEMENT",
90
+ "property": "GAP_FILL_TO_MANAGEMENT"
91
91
  }
92
92
  MODEL_KEY = 'management'
93
93
 
94
- _LAND_COVER_KEY = LOOKUPS['crop'][0]
95
94
  _ANIMAL_MANURE_USED_TERM_ID = "animalManureUsed"
96
95
  _INORGANIC_NITROGEN_FERTILISER_USED_TERM_ID = "inorganicNitrogenFertiliserUsed"
97
96
  _ORGANIC_FERTILISER_USED_TERM_ID = "organicFertiliserUsed"
@@ -140,6 +139,16 @@ def management(data: dict):
140
139
  return node
141
140
 
142
141
 
142
+ def _should_gap_fill(term: dict):
143
+ value = get_lookup_value(lookup_term=term, column='GAP_FILL_TO_MANAGEMENT')
144
+ return bool(value)
145
+
146
+
147
+ def _filter_properties(blank_node: dict):
148
+ properties = list(filter(lambda p: _should_gap_fill(p.get('term', {})), blank_node.get('properties', [])))
149
+ return _omit(blank_node, ['properties']) | ({'properties': properties} if properties else {})
150
+
151
+
143
152
  def _map_to_value(value: dict):
144
153
  return {
145
154
  'id': value.get('term', {}).get('@id'),
@@ -154,37 +163,23 @@ def _extract_node_value(node: dict) -> dict:
154
163
  return node | {'value': get_node_value(node)}
155
164
 
156
165
 
157
- def _default_dates(cycle: dict, values: list):
158
- return [(_include(cycle, ["startDate", "endDate"]) | v) for v in values]
159
-
160
-
161
- def _dates_from_current_cycle(cycle: dict, values: list) -> list:
162
- """Always uses the dates from the cycle."""
163
- return [v | _include(cycle, ["startDate", "endDate"]) for v in values]
164
-
165
-
166
166
  def _copy_item_if_exists(source: dict, keys: list[str] = None, dest: dict = None) -> dict:
167
- keys = keys or []
168
- dest = dest or {}
169
- return reduce(lambda p, c: p | ({c: source[c]} if c in source else {}), keys, dest)
167
+ return reduce(lambda p, c: p | ({c: source[c]} if source.get(c) else {}), keys or [], dest or {})
170
168
 
171
169
 
172
170
  def _get_landCover_term_id(product: dict) -> str:
173
171
  term = product.get('term', {})
174
- value = get_lookup_value(term, _LAND_COVER_KEY, model=MODEL, term=term.get('@id'), model_key=MODEL_KEY)
175
- return value.split(';')[0] if value else None
172
+ return get_landCover_term_id(term, model=MODEL, term=term.get('@id'), model_key=MODEL_KEY)
176
173
 
177
174
 
178
- def _get_relevant_items(
179
- cycle: dict, item_name: str, relevant_terms: list, date_fill: callable = _default_dates
180
- ):
175
+ def _get_relevant_items(cycle: dict, item_name: str, relevant_terms: list):
181
176
  """
182
177
  Get items from the list of cycles with any of the relevant terms.
183
- Also adds dates if missing.
178
+ Also adds dates from Cycle.
184
179
  """
185
180
  return [
186
- item
187
- for item in date_fill(cycle=cycle, values=filter_list_term_type(cycle.get(item_name, []), relevant_terms))
181
+ _include(cycle, ["startDate", "endDate"]) | item
182
+ for item in filter_list_term_type(cycle.get(item_name, []), relevant_terms)
188
183
  ]
189
184
 
190
185
 
@@ -226,54 +221,74 @@ def _run_from_siteType(site: dict, cycle: dict):
226
221
  }] if should_run else []
227
222
 
228
223
 
229
- def _run_from_landCover(cycle: dict):
230
- products = _get_relevant_items(
231
- cycle=cycle,
232
- item_name="products",
233
- relevant_terms=[TermTermType.LANDCOVER]
234
- )
224
+ def _run_products(cycle: dict, products: list, total_products: int = None, use_cycle_dates: bool = False):
225
+ default_dates = _include(cycle, ["startDate", "endDate"])
226
+ return [
227
+ _map_to_value(default_dates | _copy_item_if_exists(
228
+ source=product,
229
+ keys=['properties', 'startDate', 'endDate'],
230
+ dest={
231
+ "term": {'@id': _get_landCover_term_id(product)},
232
+ "value": round(100 / (total_products or len(products)), 2)
233
+ }
234
+ ) | (
235
+ default_dates if use_cycle_dates else {}
236
+ ))
237
+ for product in products
238
+ ]
239
+
240
+
241
+ def _run_from_landCover(cycle: dict, crop_forage_products: list):
242
+ """
243
+ Copy landCover items, and include crop/forage landCover items with properties to count in ratio.
244
+ """
235
245
  products = [
236
246
  _map_to_value(_extract_node_value(
237
247
  _include(
238
248
  value=product,
239
249
  keys=["term", "value", "startDate", "endDate", "properties"]
240
250
  )
241
- )) for product in products
251
+ )) for product in _get_relevant_items(
252
+ cycle=cycle,
253
+ item_name="products",
254
+ relevant_terms=[TermTermType.LANDCOVER]
255
+ )
242
256
  ]
243
- return products
257
+ return products + _run_products(
258
+ cycle,
259
+ crop_forage_products,
260
+ total_products=len(crop_forage_products) + len(products),
261
+ use_cycle_dates=True
262
+ )
244
263
 
245
264
 
246
265
  def _run_from_crop_forage(cycle: dict):
247
266
  products = _get_relevant_items(
248
267
  cycle=cycle,
249
268
  item_name="products",
250
- relevant_terms=[TermTermType.CROP, TermTermType.FORAGE],
251
- date_fill=_dates_from_current_cycle
269
+ relevant_terms=[TermTermType.CROP, TermTermType.FORAGE]
252
270
  )
271
+ # only take products with a matching landCover term
253
272
  products = list(filter(_get_landCover_term_id, products))
254
- products = [
255
- _map_to_value(_copy_item_if_exists(
256
- source=product,
257
- keys=["startDate", "endDate", "properties"],
258
- dest={
259
- "term": {'@id': _get_landCover_term_id(product)},
260
- "value": round(100 / len(products), 2)
261
- }
262
- ))
263
- for product in products
264
- ]
265
- return products
273
+ # remove any properties that should not get gap-filled
274
+ products = list(map(_filter_properties, products))
266
275
 
276
+ # split products with properties and those without
277
+ products_with_gap_filled_props = [p for p in products if p.get('properties')]
278
+ products_without_gap_filled_props = [p for p in products if not p.get('properties')]
267
279
 
268
- def _has_gap_fill_to_management_set(practice: dict):
280
+ return _run_from_landCover(
281
+ cycle=cycle,
282
+ crop_forage_products=products_with_gap_filled_props
283
+ ) + _run_products(cycle, products_without_gap_filled_props, use_cycle_dates=False)
284
+
285
+
286
+ def _should_run_practice(practice: dict):
269
287
  """
270
288
  Include only landUseManagement practices where GAP_FILL_TO_MANAGEMENT = True
271
289
  """
272
290
  term = practice.get('term', {})
273
- return (
274
- term.get('termType') != TermTermType.LANDUSEMANAGEMENT.value or
275
- get_lookup_value(lookup_term=term, column=LOOKUPS["landUseManagement"])
276
- )
291
+ return term.get('termType') != TermTermType.LANDUSEMANAGEMENT.value or _should_gap_fill(term)
277
292
 
278
293
 
279
294
  def _run_from_practices(cycle: dict):
@@ -295,13 +310,13 @@ def _run_from_practices(cycle: dict):
295
310
  ]
296
311
  )
297
312
  ]
298
- practices = list(map(_map_to_value, filter(_has_gap_fill_to_management_set, practices)))
313
+ practices = list(map(_map_to_value, filter(_should_run_practice, practices)))
299
314
  return practices
300
315
 
301
316
 
302
317
  def _run_cycle(site: dict, cycle: dict):
303
318
  inputs = _run_from_inputs(site, cycle)
304
- products = _run_from_landCover(cycle) + _run_from_crop_forage(cycle)
319
+ products = _run_from_crop_forage(cycle)
305
320
  site_types = _run_from_siteType(site, cycle)
306
321
  practices = _run_from_practices(cycle)
307
322
  return [
@@ -3,9 +3,7 @@ Pre Checks Cache Sources
3
3
 
4
4
  This model caches the sources of all Site models.
5
5
  """
6
- from hestia_earth.models.log import debugValues
7
- from hestia_earth.models.utils import CACHE_KEY, cached_value
8
- from hestia_earth.models.utils.source import CACHE_SOURCES_KEY, find_sources
6
+ from hestia_earth.models.utils.cache_sources import cache_sources
9
7
 
10
8
  REQUIREMENTS = {
11
9
  "Site": {}
@@ -15,20 +13,4 @@ RETURNS = {
15
13
  }
16
14
 
17
15
 
18
- def _run(site: dict):
19
- sources = find_sources()
20
- debugValues(site, sources=';'.join([str(title) for title in sources.keys()]))
21
- return sources
22
-
23
-
24
- def _should_run(site: dict):
25
- has_cache = cached_value(site, CACHE_SOURCES_KEY)
26
- return not bool(has_cache)
27
-
28
-
29
- def run(site: dict):
30
- should_run = _should_run(site)
31
- return {
32
- **site,
33
- CACHE_KEY: cached_value(site) | {CACHE_SOURCES_KEY: _run(site)}
34
- } if should_run else site
16
+ def run(site: dict): return cache_sources(site)
@@ -7,7 +7,7 @@ import sys
7
7
  import datetime
8
8
  from functools import reduce
9
9
  import operator
10
- from typing import Any, Union
10
+ from typing import Any, Callable, Union
11
11
  from hestia_earth.schema import SchemaType
12
12
  from hestia_earth.utils.api import download_hestia
13
13
  from hestia_earth.utils.model import linked_node
@@ -198,3 +198,14 @@ def hectar_to_square_meter(value): return value * 10000
198
198
 
199
199
 
200
200
  def square_meter_to_hectare(value): return value / 10000
201
+
202
+
203
+ def split_on_condition(iterable: Iterable, condition: Callable):
204
+ """
205
+ Split an iterable into two iterables of the same type based on whether or not each item satisfies a condition.
206
+ """
207
+ def construct(iterator: Generator) -> Iterable:
208
+ return ''.join(iterable) if isinstance(iterable, str) else type(iterable)(iterator)
209
+
210
+ cond_true, cond_false = tee((condition(item), item) for item in iterable)
211
+ return construct(i for p, i in cond_true if p), construct(i for p, i in cond_false if not p)
@@ -107,10 +107,10 @@ def should_link_input_to_impact(cycle: dict):
107
107
  def should_run(input: dict):
108
108
  term = input.get('term', {})
109
109
  return all([
110
- not input.get('impactAssessment'),
111
110
  _should_aggregate_input(term),
112
111
  # make sure Input is not a Product as well or we might double-count emissions
113
112
  find_term_match(cycle.get('products', []), term.get('@id'), None) is None,
113
+ not input.get('impactAssessment'),
114
114
  # ignore inputs which are flagged as Product of the Cycle
115
115
  not input.get('fromCycle', False),
116
116
  not input.get('producedInCycle', False)
@@ -377,7 +377,7 @@ def get_P2O5_total(nodes: list) -> list:
377
377
  return get_total_value(kg_P_nodes) + get_total_value_converted(kg_N_nodes + kg_nodes, 'phosphateContentAsP2O5')
378
378
 
379
379
 
380
- def convert_to_nitrogen(node: dict, model: str, term_id: str, blank_nodes: list, **log_args):
380
+ def convert_to_nitrogen(node: dict, model: str, blank_nodes: list, **log_args):
381
381
  def prop_value(input: dict):
382
382
  value = get_node_property_value(model, input, 'nitrogenContent', default=None, **log_args)
383
383
  return value or get_node_property_value(model, input, 'crudeProteinContent', default=0, **log_args) / 6.25
@@ -385,7 +385,7 @@ def convert_to_nitrogen(node: dict, model: str, term_id: str, blank_nodes: list,
385
385
  values = [(i, prop_value(i)) for i in blank_nodes]
386
386
  missing_nitrogen_property = [i.get('term', {}).get('@id') for i, p_value in values if not p_value]
387
387
 
388
- debugValues(node, model=model, term=term_id,
388
+ debugValues(node, model=model,
389
389
  missing_nitrogen_property=';'.join(set(missing_nitrogen_property)),
390
390
  **log_args)
391
391
 
@@ -394,7 +394,7 @@ def convert_to_nitrogen(node: dict, model: str, term_id: str, blank_nodes: list,
394
394
  ]) if len(missing_nitrogen_property) == 0 else None
395
395
 
396
396
 
397
- def convert_to_carbon(node: dict, model: str, term_id: str, blank_nodes: list, **log_args):
397
+ def convert_to_carbon(node: dict, model: str, blank_nodes: list, **log_args):
398
398
  def prop_value(input: dict):
399
399
  value = get_node_property_value(model, input, 'carbonContent', default=None, **log_args)
400
400
  return value or \
@@ -403,7 +403,7 @@ def convert_to_carbon(node: dict, model: str, term_id: str, blank_nodes: list, *
403
403
  values = [(i, prop_value(i)) for i in blank_nodes]
404
404
  missing_carbon_property = [i.get('term', {}).get('@id') for i, p_value in values if not p_value]
405
405
 
406
- debugValues(node, model=model, term=term_id,
406
+ debugValues(node, model=model,
407
407
  missing_carbon_property=';'.join(missing_carbon_property),
408
408
  **log_args)
409
409
 
@@ -1248,6 +1248,12 @@ def get_inputs_from_properties(input: dict, term_types: Union[TermTermType, List
1248
1248
  return filter_list_term_type(inputs, term_types)
1249
1249
 
1250
1250
 
1251
+ def _has_unique_key(nodes: list, key: str): return len(set([n.get(key) for n in nodes])) == 1
1252
+
1253
+
1254
+ def _same_dates(nodes: list): return all([_has_unique_key(nodes, 'startDate'), _has_unique_key(nodes, 'endDate')])
1255
+
1256
+
1251
1257
  def _should_group_node(node: dict): return node.get('startDate') and node.get('endDate')
1252
1258
 
1253
1259
 
@@ -1281,19 +1287,21 @@ def _group_nodes_by_consecutive_dates(nodes: list):
1281
1287
  if group:
1282
1288
  groups.append(group)
1283
1289
 
1284
- return groups
1290
+ # if all nodes have a single startDate and endDate, then they are all the same
1291
+ return [nodes] if _same_dates(nodes) else groups
1292
+
1293
+
1294
+ def _sum_nodes_value(nodes: list):
1295
+ values = flatten([n.get('value', []) for n in nodes])
1296
+ is_boolean = all([isinstance(v, bool) for v in values])
1297
+ return values[0] if is_boolean else [list_sum(values)] if isinstance(nodes[0]['value'], list) else list_sum(values)
1285
1298
 
1286
1299
 
1287
1300
  def _node_from_group(nodes: list):
1288
1301
  # `nodes` contain list with consecutive dates
1289
- # if all nodes have the same dates, sum up the values
1290
- same_startDate = len(set([n.get('startDate') for n in nodes])) == 1
1291
- same_endDate = len(set([n.get('endDate') for n in nodes])) == 1
1292
- total_value = list_sum(flatten([n.get('value', []) for n in nodes]))
1293
1302
  return nodes[0] if len(nodes) == 1 else (
1294
- nodes[0] | {
1295
- 'value': [total_value] if isinstance(nodes[0]['value'], list) else total_value
1296
- } if all([same_startDate, same_endDate])
1303
+ # if all nodes have the same dates, sum up the values
1304
+ nodes[0] | {'value': _sum_nodes_value(nodes)} if _same_dates(nodes)
1297
1305
  else nodes[0] | {
1298
1306
  'startDate': min(n.get('startDate') for n in nodes),
1299
1307
  'endDate': max(n.get('endDate') for n in nodes)
@@ -0,0 +1,15 @@
1
+ from . import CACHE_KEY, cached_value
2
+ from .source import CACHE_SOURCES_KEY, find_sources
3
+
4
+
5
+ def _run(): return {CACHE_SOURCES_KEY: find_sources()}
6
+
7
+
8
+ def _has_value(node: dict): return bool(cached_value(node, CACHE_SOURCES_KEY))
9
+
10
+
11
+ def cache_sources(node: dict):
12
+ return {
13
+ **node,
14
+ CACHE_KEY: cached_value(node) | _run()
15
+ } if not _has_value(node) else node
@@ -61,3 +61,8 @@ def valid_site_type(cycle: dict, include_permanent_pasture=False):
61
61
  [SiteSiteType.PERMANENT_PASTURE.value] if include_permanent_pasture else []
62
62
  )
63
63
  return site_valid_site_type(cycle.get('site', {}), site_types)
64
+
65
+
66
+ def get_landCover_term_id(lookup_term: dict, **log_args) -> str:
67
+ value = get_lookup_value(lookup_term, 'landCoverTermId', **log_args)
68
+ return value.split(';')[0] if value else None
@@ -5,9 +5,11 @@ from hestia_earth.utils.model import linked_node
5
5
  from . import _term_id, _include_methodModel
6
6
 
7
7
 
8
- def _new_indicator(term, model=None, land_cover_id=None):
8
+ def _new_indicator(term, model=None, land_cover_id: str = None, previous_land_cover_id: str = None):
9
9
  node = {'@type': SchemaType.INDICATOR.value}
10
10
  node['term'] = linked_node(term if isinstance(term, dict) else download_hestia(_term_id(term)))
11
11
  if land_cover_id:
12
12
  node['landCover'] = linked_node(download_hestia(land_cover_id))
13
+ if previous_land_cover_id:
14
+ node['previousLandCover'] = linked_node(download_hestia(previous_land_cover_id))
13
15
  return _include_methodModel(node, model)
@@ -1 +1 @@
1
- VERSION = '0.64.14'
1
+ VERSION = '0.65.1'
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2019-2024 Harmonised Environmental Storage and Tracking of the Impacts of Agriculture (HESTIA) Project
3
+ Copyright (c) 2019-2025 Harmonised Environmental Storage and Tracking of the Impacts of Agriculture (HESTIA) Project
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -1,18 +1,18 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: hestia-earth-models
3
- Version: 0.64.14
3
+ Version: 0.65.1
4
4
  Summary: HESTIA's set of modules for filling gaps in the activity data using external datasets (e.g. populating soil properties with a geospatial dataset using provided coordinates) and internal lookups (e.g. populating machinery use from fuel use). Includes rules for when gaps should be filled versus not (e.g. never gap fill yield, gap fill crop residue if yield provided etc.).
5
5
  Home-page: https://gitlab.com/hestia-earth/hestia-engine-models
6
6
  Author: HESTIA Team
7
7
  Author-email: guillaumeroyer.mail@gmail.com
8
- License: GPL
8
+ License: MIT
9
9
  Platform: UNKNOWN
10
10
  Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
11
11
  Classifier: Programming Language :: Python :: 3.6
12
12
  Description-Content-Type: text/markdown
13
13
  License-File: LICENSE
14
14
  Requires-Dist: hestia-earth-schema==30.*
15
- Requires-Dist: hestia-earth-utils>=0.13.11
15
+ Requires-Dist: hestia-earth-utils>=0.13.12
16
16
  Requires-Dist: python-dateutil>=2.8.1
17
17
  Requires-Dist: CurrencyConverter==0.16.8
18
18
  Requires-Dist: haversine>=2.7.0