hestia-earth-models 0.72.2__py3-none-any.whl → 0.73.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.
Files changed (144) hide show
  1. hestia_earth/models/{akagiEtAl2011AndIpcc2006 → akagiEtAl2011}/__init__.py +1 -1
  2. hestia_earth/models/akagiEtAl2011/ch4ToAirCropResidueBurning.py +32 -0
  3. hestia_earth/models/akagiEtAl2011/nh3ToAirCropResidueBurning.py +32 -0
  4. hestia_earth/models/akagiEtAl2011/noxToAirCropResidueBurning.py +32 -0
  5. hestia_earth/models/akagiEtAl2011/pm25ToAirCropResidueBurning.py +32 -0
  6. hestia_earth/models/akagiEtAl2011/so2ToAirCropResidueBurning.py +32 -0
  7. hestia_earth/models/akagiEtAl2011/utils.py +45 -0
  8. hestia_earth/models/aware/scarcityWeightedWaterUse.py +10 -9
  9. hestia_earth/models/cache_sites.py +8 -4
  10. hestia_earth/models/chaudharyBrooks2018/damageToTerrestrialEcosystemsLandTransformation.py +14 -11
  11. hestia_earth/models/chaudharyBrooks2018/damageToTerrestrialEcosystemsTotalLandUseEffects.py +1 -1
  12. hestia_earth/models/chaudharyBrooks2018/utils.py +5 -3
  13. hestia_earth/models/config/Cycle.json +48 -7
  14. hestia_earth/models/config/ImpactAssessment.json +22 -0
  15. hestia_earth/models/cycle/completeness/electricityFuel.py +1 -1
  16. hestia_earth/models/cycle/completeness/freshForage.py +1 -1
  17. hestia_earth/models/cycle/completeness/soilAmendment.py +1 -1
  18. hestia_earth/models/cycle/input/hestiaAggregatedData.py +1 -2
  19. hestia_earth/models/cycle/product/economicValueShare.py +4 -2
  20. hestia_earth/models/cycle/product/price.py +35 -10
  21. hestia_earth/models/cycle/product/revenue.py +5 -2
  22. hestia_earth/models/dammgen2009/noxToAirExcreta.py +14 -18
  23. hestia_earth/models/ecoinventV3/__init__.py +11 -6
  24. hestia_earth/models/ecoinventV3AndEmberClimate/utils.py +1 -1
  25. hestia_earth/models/emepEea2019/utils.py +2 -1
  26. hestia_earth/models/environmentalFootprintV3_1/environmentalFootprintSingleOverallScore.py +1 -1
  27. hestia_earth/models/environmentalFootprintV3_1/soilQualityIndexLandOccupation.py +11 -11
  28. hestia_earth/models/environmentalFootprintV3_1/soilQualityIndexLandTransformation.py +14 -11
  29. hestia_earth/models/faostat2018/liveweightPerHead.py +1 -1
  30. hestia_earth/models/faostat2018/product/price.py +2 -2
  31. hestia_earth/models/faostat2018/seed.py +3 -2
  32. hestia_earth/models/faostat2018/utils.py +32 -18
  33. hestia_earth/models/geospatialDatabase/altitude.py +2 -1
  34. hestia_earth/models/geospatialDatabase/drainageClass.py +2 -1
  35. hestia_earth/models/geospatialDatabase/organicCarbonPerKgSoil.py +2 -1
  36. hestia_earth/models/geospatialDatabase/totalNitrogenPerKgSoil.py +2 -1
  37. hestia_earth/models/geospatialDatabase/totalPhosphorusPerKgSoil.py +2 -1
  38. hestia_earth/models/geospatialDatabase/utils.py +7 -5
  39. hestia_earth/models/globalCropWaterModel2008/rootingDepth.py +1 -1
  40. hestia_earth/models/hestia/brackishWater.py +1 -1
  41. hestia_earth/models/hestia/default_emissions.py +105 -0
  42. hestia_earth/models/hestia/default_resourceUse.py +110 -0
  43. hestia_earth/models/hestia/excretaKgMass.py +7 -9
  44. hestia_earth/models/hestia/freshWater.py +1 -1
  45. hestia_earth/models/hestia/inorganicFertiliser.py +12 -12
  46. hestia_earth/models/hestia/landCover.py +7 -5
  47. hestia_earth/models/hestia/landTransformation100YearAverageDuringCycle.py +3 -0
  48. hestia_earth/models/hestia/landTransformation20YearAverageDuringCycle.py +3 -0
  49. hestia_earth/models/hestia/liveAnimal.py +1 -1
  50. hestia_earth/models/hestia/management.py +1 -1
  51. hestia_earth/models/hestia/netPrimaryProduction.py +1 -1
  52. hestia_earth/models/hestia/organicCarbonPerHa.py +2 -2
  53. hestia_earth/models/hestia/salineWater.py +1 -1
  54. hestia_earth/models/hestia/seed_emissions.py +35 -21
  55. hestia_earth/models/hestia/stockingDensityAnimalHousingAverage.py +1 -1
  56. hestia_earth/models/hestia/totalNitrogenPerKgSoil.py +1 -1
  57. hestia_earth/models/hestia/waterSalinity.py +3 -2
  58. hestia_earth/models/ipcc2006/aboveGroundCropResidueRemoved.py +2 -2
  59. hestia_earth/models/ipcc2006/aboveGroundCropResidueTotal.py +10 -6
  60. hestia_earth/models/ipcc2006/belowGroundCropResidue.py +12 -8
  61. hestia_earth/models/ipcc2019/animal/utils.py +1 -1
  62. hestia_earth/models/ipcc2019/belowGroundCropResidue.py +1 -1
  63. hestia_earth/models/ipcc2019/carbonContent.py +1 -1
  64. hestia_earth/models/ipcc2019/ch4ToAirAquacultureSystems.py +18 -10
  65. hestia_earth/models/ipcc2019/ch4ToAirEntericFermentation.py +6 -6
  66. hestia_earth/models/ipcc2019/ch4ToAirExcreta.py +4 -2
  67. hestia_earth/models/ipcc2019/co2ToAirUreaHydrolysis.py +1 -1
  68. hestia_earth/models/ipcc2019/croppingDuration.py +4 -2
  69. hestia_earth/models/ipcc2019/ligninContent.py +1 -1
  70. hestia_earth/models/{akagiEtAl2011AndIpcc2006 → ipcc2019}/n2OToAirCropResidueBurningDirect.py +8 -4
  71. hestia_earth/models/ipcc2019/nitrogenContent.py +1 -1
  72. hestia_earth/models/ipcc2019/nonCo2EmissionsToAirNaturalVegetationBurning.py +6 -2
  73. hestia_earth/models/ipcc2019/pastureGrass_utils.py +13 -12
  74. hestia_earth/models/ipcc2019/utils.py +6 -2
  75. hestia_earth/models/koble2014/residueBurnt.py +6 -3
  76. hestia_earth/models/koble2014/residueRemoved.py +1 -1
  77. hestia_earth/models/mocking/search-results.json +1614 -1610
  78. hestia_earth/models/pooreNemecek2018/aboveGroundCropResidueTotal.py +1 -1
  79. hestia_earth/models/pooreNemecek2018/belowGroundCropResidue.py +1 -1
  80. hestia_earth/models/pooreNemecek2018/excretaKgVs.py +1 -1
  81. hestia_earth/models/pooreNemecek2018/freshwaterWithdrawalsDuringCycle.py +44 -6
  82. hestia_earth/models/pooreNemecek2018/longFallowDuration.py +1 -1
  83. hestia_earth/models/pooreNemecek2018/nurseryDensity.py +1 -1
  84. hestia_earth/models/pooreNemecek2018/nurseryDuration.py +1 -1
  85. hestia_earth/models/pooreNemecek2018/plantationDensity.py +1 -1
  86. hestia_earth/models/pooreNemecek2018/plantationLifespan.py +1 -1
  87. hestia_earth/models/pooreNemecek2018/plantationProductiveLifespan.py +3 -1
  88. hestia_earth/models/pooreNemecek2018/saplingsDepreciatedAmountPerCycle.py +1 -1
  89. hestia_earth/models/resourceUseNotRelevant/__init__.py +65 -0
  90. hestia_earth/models/schererPfister2015/nErosionSoilFlux.py +5 -3
  91. hestia_earth/models/schererPfister2015/pErosionSoilFlux.py +5 -3
  92. hestia_earth/models/schererPfister2015/utils.py +5 -4
  93. hestia_earth/models/site/grouped_measurement.py +1 -1
  94. hestia_earth/models/stehfestBouwman2006/n2OToAirSoilFlux_utils.py +1 -1
  95. hestia_earth/models/stehfestBouwman2006GisImplementation/noxToAirSoilFlux_utils.py +3 -3
  96. hestia_earth/models/utils/background_emissions.py +15 -11
  97. hestia_earth/models/utils/blank_node.py +6 -4
  98. hestia_earth/models/utils/crop.py +1 -1
  99. hestia_earth/models/utils/cropResidue.py +16 -0
  100. hestia_earth/models/utils/cycle.py +1 -1
  101. hestia_earth/models/utils/ecoClimateZone.py +2 -2
  102. hestia_earth/models/utils/excretaManagement.py +1 -1
  103. hestia_earth/models/utils/feedipedia.py +3 -3
  104. hestia_earth/models/utils/fertiliser.py +7 -1
  105. hestia_earth/models/utils/impact_assessment.py +29 -14
  106. hestia_earth/models/utils/inorganicFertiliser.py +2 -2
  107. hestia_earth/models/utils/input.py +34 -1
  108. hestia_earth/models/utils/liveAnimal.py +2 -2
  109. hestia_earth/models/utils/lookup.py +6 -2
  110. hestia_earth/models/utils/measurement.py +4 -4
  111. hestia_earth/models/utils/productivity.py +1 -1
  112. hestia_earth/models/utils/property.py +4 -2
  113. hestia_earth/models/utils/site.py +2 -1
  114. hestia_earth/models/version.py +1 -1
  115. {hestia_earth_models-0.72.2.dist-info → hestia_earth_models-0.73.1.dist-info}/METADATA +1 -1
  116. {hestia_earth_models-0.72.2.dist-info → hestia_earth_models-0.73.1.dist-info}/RECORD +138 -127
  117. tests/models/akagiEtAl2011/test_ch4ToAirCropResidueBurning.py +33 -0
  118. tests/models/akagiEtAl2011/test_nh3ToAirCropResidueBurning.py +33 -0
  119. tests/models/{akagiEtAl2011AndIpcc2006 → akagiEtAl2011}/test_noxToAirCropResidueBurning.py +5 -17
  120. tests/models/akagiEtAl2011/test_pm25ToAirCropResidueBurning.py +33 -0
  121. tests/models/akagiEtAl2011/test_so2ToAirCropResidueBurning.py +33 -0
  122. tests/models/akagiEtAl2011/test_utils.py +18 -0
  123. tests/models/cycle/product/test_price.py +1 -11
  124. tests/models/dammgen2009/test_noxToAirExcreta.py +30 -10
  125. tests/models/geospatialDatabase/test_utils.py +2 -1
  126. tests/models/hestia/test_default_emissions.py +25 -0
  127. tests/models/hestia/test_default_resourceUse.py +26 -0
  128. tests/models/hestia/test_landCover.py +2 -2
  129. tests/models/ipcc2019/test_ch4ToAirAquacultureSystems.py +2 -2
  130. tests/models/{akagiEtAl2011AndIpcc2006/test_nh3ToAirCropResidueBurning.py → ipcc2019/test_n2OToAirCropResidueBurningDirect.py} +2 -2
  131. tests/models/pooreNemecek2018/test_freshwaterWithdrawalsDuringCycle.py +12 -0
  132. tests/models/test_resourceUseNotRelevant.py +27 -0
  133. tests/models/{akagiEtAl2011AndIpcc2006/test_utils.py → utils/test_cropResidue.py} +6 -6
  134. tests/models/utils/test_impact_assessment.py +29 -13
  135. hestia_earth/models/akagiEtAl2011AndIpcc2006/ch4ToAirCropResidueBurning.py +0 -57
  136. hestia_earth/models/akagiEtAl2011AndIpcc2006/nh3ToAirCropResidueBurning.py +0 -57
  137. hestia_earth/models/akagiEtAl2011AndIpcc2006/noxToAirCropResidueBurning.py +0 -57
  138. hestia_earth/models/akagiEtAl2011AndIpcc2006/utils.py +0 -15
  139. tests/models/akagiEtAl2011AndIpcc2006/test_ch4ToAirCropResidueBurning.py +0 -45
  140. tests/models/akagiEtAl2011AndIpcc2006/test_n2OToAirCropResidueBurningDirect.py +0 -46
  141. {hestia_earth_models-0.72.2.dist-info → hestia_earth_models-0.73.1.dist-info}/LICENSE +0 -0
  142. {hestia_earth_models-0.72.2.dist-info → hestia_earth_models-0.73.1.dist-info}/WHEEL +0 -0
  143. {hestia_earth_models-0.72.2.dist-info → hestia_earth_models-0.73.1.dist-info}/top_level.txt +0 -0
  144. /tests/models/{akagiEtAl2011AndIpcc2006 → akagiEtAl2011}/__init__.py +0 -0
@@ -0,0 +1,110 @@
1
+ from hestia_earth.schema import IndicatorMethodTier, TermTermType
2
+ from hestia_earth.utils.tools import flatten, safe_parse_float
3
+ from hestia_earth.utils.lookup import download_lookup, lookup_term_ids
4
+
5
+ from hestia_earth.models.log import logRequirements, logShouldRun
6
+ from hestia_earth.models.utils import _omit
7
+ from hestia_earth.models.utils.indicator import _new_indicator
8
+ from hestia_earth.models.utils.background_emissions import no_gap_filled_background_emissions
9
+ from hestia_earth.models.utils.term import get_lookup_value
10
+ from hestia_earth.models.utils.input import unique_background_inputs
11
+ from . import MODEL
12
+
13
+ REQUIREMENTS = {
14
+ "ImpactAssessment": {
15
+ "cycle": {
16
+ "@type": "Cycle",
17
+ "inputs": [{
18
+ "@type": "Input",
19
+ "value": "> 0",
20
+ "none": {
21
+ "impactAssessment": {"@type": "ImpactAssessment"},
22
+ "fromCycle": "True",
23
+ "producedInCycle": "True"
24
+ }
25
+ }]
26
+ }
27
+ }
28
+ }
29
+ RETURNS = {
30
+ "Indicator": [{
31
+ "value": "",
32
+ "inputs": "",
33
+ "methodTier": "background"
34
+ }]
35
+ }
36
+ LOOKUPS = {
37
+ "organicFertiliser": "backgroundEmissionsResourceUseDefaultValue"
38
+ }
39
+ MODEL_KEY = 'default_resourceUse'
40
+ TIER = IndicatorMethodTier.BACKGROUND.value
41
+
42
+
43
+ def _indicator(term_id: str, value: float, input: dict):
44
+ indicator = _new_indicator(term_id, MODEL)
45
+ indicator['value'] = value
46
+ indicator['inputs'] = [input]
47
+ indicator['methodTier'] = TIER
48
+ return indicator
49
+
50
+
51
+ def _default_value(input: dict):
52
+ return safe_parse_float(get_lookup_value(input.get('term', {}), LOOKUPS['organicFertiliser']), default=None)
53
+
54
+
55
+ def _run_input(impact: dict):
56
+ required_resourceUse_term_ids = [
57
+ id for id in lookup_term_ids(download_lookup(f"{TermTermType.RESOURCEUSE.value}.csv"))
58
+ if id.endswith('InputsProduction')
59
+ ]
60
+
61
+ def run(input: dict):
62
+ input_term = input.get('input').get('term')
63
+ term_id = input_term.get('@id')
64
+ value = input.get('default-value-from-lookup')
65
+
66
+ for emission_id in required_resourceUse_term_ids:
67
+ logShouldRun(impact, MODEL, term_id, True, methodTier=TIER, model_key=MODEL_KEY, emission_id=emission_id)
68
+
69
+ return [
70
+ _indicator(term_id, value, input_term) for term_id in required_resourceUse_term_ids
71
+ ]
72
+
73
+ return run
74
+
75
+
76
+ def _should_run(impact: dict):
77
+ no_gap_filled_background_emissions_func = no_gap_filled_background_emissions(
78
+ node=impact, list_key='emissionsResourceUse', term_type=TermTermType.RESOURCEUSE
79
+ )
80
+
81
+ inputs = [
82
+ input | {
83
+ 'default-value-from-lookup': _default_value(input['input']),
84
+ 'no-gap-filled-background-emissions': no_gap_filled_background_emissions_func(input['input'])
85
+ }
86
+ for input in unique_background_inputs(impact.get('cycle', {}))
87
+ ]
88
+ valid_inputs = [
89
+ input for input in inputs
90
+ if all([
91
+ input.get('default-value-from-lookup') is not None,
92
+ input.get('no-gap-filled-background-emissions')
93
+ ])
94
+ ]
95
+
96
+ should_run = all([bool(valid_inputs)])
97
+
98
+ for input in inputs:
99
+ term_id = input.get('input').get('term', {}).get('@id')
100
+
101
+ logRequirements(impact, model=MODEL, term=term_id, model_key=MODEL_KEY,
102
+ **_omit(input, ['input', 'input-value']))
103
+ logShouldRun(impact, MODEL, term_id, should_run, methodTier=TIER, model_key=MODEL_KEY)
104
+
105
+ return should_run, valid_inputs
106
+
107
+
108
+ def run(impact: dict):
109
+ should_run, grouped_inputs = _should_run(impact)
110
+ return flatten(map(_run_input(impact), grouped_inputs)) if should_run else []
@@ -3,7 +3,7 @@ from hestia_earth.utils.model import filter_list_term_type, find_term_match
3
3
  from hestia_earth.utils.tools import non_empty_list, list_sum
4
4
 
5
5
  from hestia_earth.models.log import debugValues, logRequirements, logShouldRun
6
- from hestia_earth.models.utils import get_kg_term_id, get_kg_N_term_id, get_kg_VS_term_id
6
+ from hestia_earth.models.utils import get_kg_term_id, get_kg_N_term_id, get_kg_VS_term_id, _filter_list_term_unit
7
7
  from hestia_earth.models.utils.term import download_term
8
8
  from hestia_earth.models.utils.constant import Units
9
9
  from hestia_earth.models.utils.product import _new_product, convert_product_to_unit
@@ -31,12 +31,12 @@ RETURNS = {
31
31
  MODEL_KEY = 'excretaKgMass'
32
32
 
33
33
  UNITS = [
34
- Units.KG_N.value,
35
- Units.KG_VS.value
34
+ Units.KG_N,
35
+ Units.KG_VS
36
36
  ]
37
37
 
38
38
 
39
- def _product(value: float, term_id: str):
39
+ def _product(term_id: str, value: float = None):
40
40
  product = _new_product(term_id, value, MODEL)
41
41
  return product
42
42
 
@@ -80,17 +80,15 @@ def _run_product(cycle: dict, product_term_id: str):
80
80
  # use existing product if exist, else create new one
81
81
  existing_product = find_term_match(cycle.get('products', []), product_term_id)
82
82
 
83
- return (existing_product | _product(value, product_term_id)) if value else None
83
+ return (existing_product | _product(product_term_id, value)) if value else _product(product_term_id)
84
84
 
85
85
 
86
86
  def _should_run(cycle: dict):
87
87
  node_type = cycle.get('type', cycle.get('@type'))
88
88
  excreta_products = filter_list_term_type(cycle.get('products', []), TermTermType.EXCRETA)
89
89
  kg_term_ids = list(set([
90
- get_kg_term_id(p.get('term', {}).get('@id')) for p in excreta_products if all([
91
- p.get('term', {}).get('units') in UNITS,
92
- p.get('value', [])
93
- ])
90
+ get_kg_term_id(p.get('term', {}).get('@id'))
91
+ for p in _filter_list_term_unit(excreta_products, UNITS)
94
92
  ]))
95
93
  gap_fill_term_ids = [
96
94
  term_id for term_id in kg_term_ids if not find_term_match(excreta_products, term_id).get('value', [])
@@ -34,7 +34,7 @@ def _should_run(site: dict):
34
34
  logRequirements(site, model=MODEL, term=TERM_ID,
35
35
  waterSalinity=waterSalinity)
36
36
 
37
- should_run = all([0 < waterSalinity < 500])
37
+ should_run = all([0 < (waterSalinity or 0) < 500])
38
38
  logShouldRun(site, MODEL, TERM_ID, should_run)
39
39
  return should_run
40
40
 
@@ -88,15 +88,15 @@ def _include_term_ids(term_id: str):
88
88
  def _run_input(cycle: dict, input: dict):
89
89
  term_id = input.get('term', {}).get('@id')
90
90
  input_term_ids = _include_term_ids(term_id)
91
- nitrogenContent = safe_parse_float(get_term_lookup(term_id, 'nitrogenContent'), 0)
92
- nitrogenContent_min = safe_parse_float(get_term_lookup(term_id, 'nitrogenContent-min'), None)
93
- nitrogenContent_max = safe_parse_float(get_term_lookup(term_id, 'nitrogenContent-max'), None)
94
- phosphateContentAsP2O5 = safe_parse_float(get_term_lookup(term_id, 'phosphateContentAsP2O5'), 0)
95
- phosphateContentAsP2O5_min = safe_parse_float(get_term_lookup(term_id, 'phosphateContentAsP2O5-min'), None)
96
- phosphateContentAsP2O5_max = safe_parse_float(get_term_lookup(term_id, 'phosphateContentAsP2O5-max'), None)
97
- potassiumContentAsK2O = safe_parse_float(get_term_lookup(term_id, 'potassiumContentAsK2O'), 0)
98
- potassiumContentAsK2O_min = safe_parse_float(get_term_lookup(term_id, 'potassiumContentAsK2O-min'), None)
99
- potassiumContentAsK2O_max = safe_parse_float(get_term_lookup(term_id, 'potassiumContentAsK2O-max'), None)
91
+ nitrogenContent = safe_parse_float(get_term_lookup(term_id, 'nitrogenContent'), default=0)
92
+ nitrogenContent_min = safe_parse_float(get_term_lookup(term_id, 'nitrogenContent-min'), default=None)
93
+ nitrogenContent_max = safe_parse_float(get_term_lookup(term_id, 'nitrogenContent-max'), default=None)
94
+ phosphateContentAsP2O5 = safe_parse_float(get_term_lookup(term_id, 'phosphateContentAsP2O5'), default=0)
95
+ phosphateContentAsP2O5_min = safe_parse_float(get_term_lookup(term_id, 'phosphateContentAsP2O5-min'), default=None)
96
+ phosphateContentAsP2O5_max = safe_parse_float(get_term_lookup(term_id, 'phosphateContentAsP2O5-max'), default=None)
97
+ potassiumContentAsK2O = safe_parse_float(get_term_lookup(term_id, 'potassiumContentAsK2O'), default=0)
98
+ potassiumContentAsK2O_min = safe_parse_float(get_term_lookup(term_id, 'potassiumContentAsK2O-min'), default=None)
99
+ potassiumContentAsK2O_max = safe_parse_float(get_term_lookup(term_id, 'potassiumContentAsK2O-max'), default=None)
100
100
 
101
101
  from_units = input.get('term', {}).get('units')
102
102
  input_value = list_sum(input.get('value'))
@@ -166,9 +166,9 @@ def _run_input(cycle: dict, input: dict):
166
166
  def _should_run_input(cycle: dict, input: dict):
167
167
  term_id = input.get('term', {}).get('@id')
168
168
  has_value = list_sum(input.get('value', [])) > 0
169
- nitrogenContent = safe_parse_float(get_term_lookup(term_id, 'nitrogenContent'), None)
170
- phosphateContentAsP2O5 = safe_parse_float(get_term_lookup(term_id, 'phosphateContentAsP2O5'), None)
171
- potassiumContentAsK2O = safe_parse_float(get_term_lookup(term_id, 'potassiumContentAsK2O'), None)
169
+ nitrogenContent = safe_parse_float(get_term_lookup(term_id, 'nitrogenContent'), default=None)
170
+ phosphateContentAsP2O5 = safe_parse_float(get_term_lookup(term_id, 'phosphateContentAsP2O5'), default=None)
171
+ potassiumContentAsK2O = safe_parse_float(get_term_lookup(term_id, 'potassiumContentAsK2O'), default=None)
172
172
 
173
173
  # skip inputs that already have all the inlcuded term with a value
174
174
  inputs = cycle.get('inputs', [])
@@ -163,7 +163,7 @@ def _should_group_landCover(management_node: dict):
163
163
  )
164
164
 
165
165
 
166
- def get_changes(country_id: str, end_year: int) -> tuple[dict, bool]:
166
+ def _get_changes(country_id: str, end_year: int) -> tuple[dict, bool]:
167
167
  """
168
168
  For each entry in ALL_LAND_USE_TERMS, creates a key: value in output dictionary, also TOTAL
169
169
  """
@@ -178,7 +178,7 @@ def get_changes(country_id: str, end_year: int) -> tuple[dict, bool]:
178
178
  )
179
179
  for land_use_term in ALL_LAND_USE_TERMS + [LAND_AREA]
180
180
  }
181
- missing_changes = any(val is None for val in changes_dict.values())
181
+ missing_changes = [k for k, v in changes_dict.items() if v is None]
182
182
  changes_dict = {k: v if v is not None else 0 for k, v in changes_dict.items()}
183
183
  changes_dict[TOTAL_AGRICULTURAL_CHANGE] = (
184
184
  float(changes_dict.get(TOTAL_CROPLAND, 0)) + float(changes_dict.get(PERMANENT_PASTURE, 0))
@@ -609,7 +609,7 @@ def _should_run_historical_land_use_change_single_crop(
609
609
  ) -> tuple[bool, dict]:
610
610
  """Calculate land use change percentages for a single management node/crop."""
611
611
  # (C-H).
612
- changes, missing_changes = get_changes(country_id=country_id, end_year=end_year)
612
+ changes, missing_changes = _get_changes(country_id=country_id, end_year=end_year)
613
613
 
614
614
  # (L). Estimate maximum forest loss
615
615
  forest_loss = _estimate_maximum_forest_change(
@@ -749,12 +749,13 @@ def _should_run_historical_land_use_change_single_crop(
749
749
  land_use_type=land_use_type,
750
750
  country_id=country_id,
751
751
  changes=log_as_table(changes),
752
+ missing_changes=log_as_table(missing_changes),
752
753
  site_area=log_as_table(capped_site_area),
753
754
  sum_of_site_areas_is_100=sum_of_site_areas_is_100,
754
755
  site_type_allowed=site_type_allowed
755
756
  )
756
757
 
757
- should_run = all([not missing_changes, country_id, site_type_allowed, sum_of_site_areas_is_100])
758
+ should_run = all([len(missing_changes) == 0, country_id, site_type_allowed, sum_of_site_areas_is_100])
758
759
  logShouldRun(site, MODEL, term.get("@id"), should_run, model_key=MODEL_KEY)
759
760
 
760
761
  return should_run, capped_site_area
@@ -831,7 +832,8 @@ def _should_run(site: dict) -> tuple[bool, list, dict]:
831
832
  land_use_type=land_use_type,
832
833
  allowed_land_use_types=';'.join(ALLOWED_LAND_USE_TYPES),
833
834
  has_no_prior_land_cover_data=has_no_prior_land_cover_data,
834
- management_nodes=log_as_table([_omit(n, ['term']) for n in relevant_nodes]))
835
+ management_nodes=log_as_table([_omit(n, ['term']) for n in relevant_nodes]),
836
+ should_run_nodes=should_run_nodes)
835
837
 
836
838
  should_run = all([land_use_type, has_no_prior_land_cover_data, should_run_nodes])
837
839
  logShouldRun(site, MODEL, None, should_run, model_key=MODEL_KEY)
@@ -27,6 +27,9 @@ RETURNS = {
27
27
  "previousLandCover": ""
28
28
  }]
29
29
  }
30
+ LOOKUPS = {
31
+ "crop": "IPCC_LAND_USE_CATEGORY"
32
+ }
30
33
  TERM_ID = 'landTransformation100YearAverageDuringCycle'
31
34
  _HISTORIC_DATE_OFFSET = 100
32
35
 
@@ -27,6 +27,9 @@ RETURNS = {
27
27
  "previousLandCover": ""
28
28
  }]
29
29
  }
30
+ LOOKUPS = {
31
+ "crop": "IPCC_LAND_USE_CATEGORY"
32
+ }
30
33
  TERM_ID = 'landTransformation20YearAverageDuringCycle'
31
34
  _HISTORIC_DATE_OFFSET = 20
32
35
 
@@ -66,7 +66,7 @@ def _should_run(cycle: dict):
66
66
  ) or next(
67
67
  (p for p in product.get('properties', []) if p.get('term', {}).get('@id').endswith('PerHead')), {}
68
68
  )
69
- propertyPerHead_value = safe_parse_float(propertyPerHead.get('value'), None)
69
+ propertyPerHead_value = safe_parse_float(propertyPerHead.get('value'), default=None)
70
70
 
71
71
  # make sure the `liveAnimal` Term is not already present as a Product or Input
72
72
  term_id = get_liveAnimal_term_id(product, model_key=MODEL_KEY)
@@ -95,7 +95,7 @@ _INPUT_RULES = {
95
95
  TermTermType.INORGANICFERTILISER.value: (
96
96
  (
97
97
  TermTermType.INORGANICFERTILISER.value, # Lookup column
98
- lambda x: safe_parse_float(x) > 0, # Condition
98
+ lambda x: safe_parse_float(x, default=0) > 0, # Condition
99
99
  _INORGANIC_NITROGEN_FERTILISER_USED_TERM_ID # New term.
100
100
  ),
101
101
  ),
@@ -45,7 +45,7 @@ def _should_run(site: dict):
45
45
  logRequirements(site, model=MODEL, term=TERM_ID,
46
46
  temperature=temperature)
47
47
 
48
- should_run = temperature > 0
48
+ should_run = all([(temperature or 0) > 0])
49
49
  logShouldRun(site, MODEL, TERM_ID, should_run)
50
50
  return should_run, temperature
51
51
 
@@ -189,8 +189,8 @@ def _run_calculation(site: dict, depth_key: str, measurement_nodes: list[dict])
189
189
  A list of `organicCarbonPerHa` [Measurement nodes](https://www.hestia.earth/schema/Measurement).
190
190
  """
191
191
  split = depth_key.split('_') # "a_to_b"
192
- depth_upper = safe_parse_float(split[0])
193
- depth_lower = safe_parse_float(split[2])
192
+ depth_upper = safe_parse_float(split[0], default=0)
193
+ depth_lower = safe_parse_float(split[2], default=0)
194
194
 
195
195
  soil_bulk_density_nodes = [
196
196
  node for node in measurement_nodes if node.get('term', {}).get('@id') == SOIL_BULK_DENSITY_TERM_ID
@@ -34,7 +34,7 @@ def _should_run(site: dict):
34
34
  logRequirements(site, model=MODEL, term=TERM_ID,
35
35
  waterSalinity=waterSalinity)
36
36
 
37
- should_run = all([waterSalinity > 18000])
37
+ should_run = all([(waterSalinity or 0) > 18000])
38
38
  logShouldRun(site, MODEL, TERM_ID, should_run)
39
39
  return should_run
40
40
 
@@ -14,7 +14,7 @@ from hestia_earth.models.utils.site import valid_site_type
14
14
  from hestia_earth.models.utils.cycle import cycle_end_year
15
15
  from hestia_earth.models.utils.crop import get_crop_grouping_faostat_production, get_landCover_term_id
16
16
  from hestia_earth.models.utils.completeness import _is_term_type_complete
17
- from hestia_earth.models.utils.blank_node import get_lookup_value
17
+ from hestia_earth.models.utils.term import get_lookup_value
18
18
  from hestia_earth.models.utils.lookup import get_region_lookup_value
19
19
  from . import MODEL
20
20
 
@@ -83,6 +83,7 @@ def _run_emission(
83
83
  input_term_id = input_term.get('@id')
84
84
  seed_value = list_sum(seed_input.get('value'))
85
85
  value = emission_value * economicValueShare / 100 / total_yield * seed_value
86
+ logShouldRun(cycle, MODEL, input_term_id, True, methodTier=TIER, model_key=MODEL_KEY, emission_id=term_id)
86
87
  debugValues(cycle, model=MODEL, term=term_id, model_key=MODEL_KEY,
87
88
  value=value,
88
89
  coefficient=1,
@@ -107,7 +108,7 @@ def _map_group_emissions(group_id: str, required_emission_term_ids: list, emissi
107
108
  included_emissions = list(filter(lambda v: v in emission_ids, emissions))
108
109
  missing_emissions = list(filter(lambda v: v not in emission_ids, emissions))
109
110
  return {
110
- 'id': group_id,
111
+ 'group-id': group_id,
111
112
  'total-emissions': len(emissions),
112
113
  'included-emissions': len(included_emissions),
113
114
  'missing-emissions': '-'.join(missing_emissions),
@@ -120,7 +121,7 @@ def _filter_emissions(cycle: dict):
120
121
 
121
122
  emissions = [
122
123
  {
123
- 'id': i.get('term', {}).get('@id'),
124
+ 'emission-id': i.get('term', {}).get('@id'),
124
125
  'group-id': get_lookup_value(i.get('term', {}), LOOKUPS['emission'], model=MODEL, model_key=MODEL_KEY),
125
126
  'value': list_sum(i.get('value'))
126
127
  }
@@ -130,7 +131,7 @@ def _filter_emissions(cycle: dict):
130
131
  len(i.get('value', [])) > 0
131
132
  ])
132
133
  ]
133
- emission_ids = set([v.get('id') for v in emissions])
134
+ emission_ids = set([v.get('emission-id') for v in emissions])
134
135
  group_ids = set([v.get('group-id') for v in emissions if v.get('group-id')])
135
136
 
136
137
  # for each group, get the list of all required emissions
@@ -140,7 +141,7 @@ def _filter_emissions(cycle: dict):
140
141
  ]
141
142
  # only keep groups that have all emissions present in the Cycle
142
143
  valid_groups = list(filter(lambda group: group.get('is-valid'), emissions_per_group))
143
- valid_group_ids = set([v.get('id') for v in valid_groups])
144
+ valid_group_ids = set([v.get('group-id') for v in valid_groups])
144
145
 
145
146
  # finally, only return emissions which groups are valid
146
147
  return list(filter(
@@ -151,19 +152,21 @@ def _filter_emissions(cycle: dict):
151
152
 
152
153
  def _evs(product: dict):
153
154
  return safe_parse_float(
154
- get_lookup_value(product.get('term', {}), 'global_economic_value_share', model=MODEL, model_key=MODEL_KEY)
155
+ get_lookup_value(product.get('term', {}), 'global_economic_value_share', model=MODEL, model_key=MODEL_KEY),
156
+ default=None
155
157
  ) or product.get('economicValueShare')
156
158
 
157
159
 
158
160
  def _faostat_yield(country_id: str, end_year: int, product: dict):
159
161
  grouping = get_crop_grouping_faostat_production(MODEL, product.get('term', {}))
160
- return safe_parse_float(extract_grouped_data_closest_date(get_region_lookup_value(
162
+ value = extract_grouped_data_closest_date(get_region_lookup_value(
161
163
  'region-crop-cropGroupingFaostatProduction-yield.csv',
162
164
  country_id,
163
165
  grouping,
164
166
  model=MODEL,
165
167
  model_key=MODEL_KEY
166
- ), end_year))
168
+ ), end_year)
169
+ return safe_parse_float(value, default=None)
167
170
 
168
171
 
169
172
  def _group_seed_inputs(inputs: list):
@@ -242,10 +245,11 @@ def _should_run(cycle: dict):
242
245
  emissions,
243
246
  {}
244
247
  )
248
+ has_single_land_cover = len(crop_land_cover_ids) <= 1
245
249
 
246
250
  should_run = all([
247
251
  site_type_valid,
248
- len(crop_land_cover_ids) <= 1,
252
+ has_single_land_cover,
249
253
  is_product_complete,
250
254
  total_economicValueShare,
251
255
  total_yield,
@@ -253,29 +257,39 @@ def _should_run(cycle: dict):
253
257
  bool(emissions)
254
258
  ])
255
259
 
260
+ logs = {
261
+ 'site_type_valid': site_type_valid,
262
+ 'crop_products': log_as_table(crop_products),
263
+ 'crop_land_cover_ids': ';'.join(crop_land_cover_ids),
264
+ 'has_single_land_cover': has_single_land_cover,
265
+ 'is_term_type_product_complete': is_product_complete,
266
+ 'total_economicValueShare': total_economicValueShare,
267
+ 'total_yield': total_yield,
268
+ 'end_year': end_year,
269
+ 'country_id': country_id
270
+ }
271
+
256
272
  for seed_input in seed_inputs:
257
273
  term_id = seed_input.get('input').get('term', {}).get('@id')
258
274
 
259
275
  logRequirements(cycle, model=MODEL, term=term_id, model_key=MODEL_KEY,
260
- site_type_valid=site_type_valid,
261
- crop_products=log_as_table(crop_products),
262
- crop_land_cover_ids=';'.join(crop_land_cover_ids),
263
- is_term_type_product_complete=is_product_complete,
264
- total_economicValueShare=total_economicValueShare,
265
- total_yield=total_yield,
266
- end_year=end_year,
267
- country_id=country_id,
268
276
  emissions=log_as_table(emissions),
269
277
  emissions_per_group=log_as_table(emissions_per_group),
270
- **_omit(seed_input, 'input'))
278
+ **_omit(seed_input, 'input'),
279
+ **logs)
271
280
 
272
281
  logShouldRun(cycle, MODEL, term_id, should_run, methodTier=TIER, model_key=MODEL_KEY)
273
282
 
274
- # log missing emissions to show in the logs
283
+ # log failed emissions to show in the logs
275
284
  for group in emissions_per_group:
276
- if not group.get('is-valid'):
285
+ emission_id = group.get('group-id')
286
+ if not group.get('is-valid') or not should_run:
287
+ logRequirements(cycle, model=MODEL, term=term_id, model_key=MODEL_KEY, emission_id=emission_id,
288
+ **group,
289
+ **_omit(seed_input, 'input'),
290
+ **logs)
277
291
  logShouldRun(cycle, MODEL, term_id, False,
278
- methodTier=TIER, model_key=MODEL_KEY, emission_id=group.get('id'))
292
+ methodTier=TIER, model_key=MODEL_KEY, emission_id=emission_id)
279
293
 
280
294
  return should_run, total_economicValueShare, total_yield, grouped_seed_inputs, grouped_emissions
281
295
 
@@ -3,7 +3,7 @@ from hestia_earth.utils.model import filter_list_term_type
3
3
 
4
4
  from hestia_earth.models.log import logRequirements, logShouldRun, log_as_table
5
5
  from hestia_earth.models.utils import weighted_average
6
- from hestia_earth.models.utils.blank_node import get_lookup_value
6
+ from hestia_earth.models.utils.term import get_lookup_value
7
7
  from hestia_earth.models.utils.practice import _new_practice
8
8
  from . import MODEL
9
9
 
@@ -45,7 +45,7 @@ def _should_run(site: dict):
45
45
  logRequirements(site, model=MODEL, term=TERM_ID,
46
46
  carbon_content_value=carbon_content_value)
47
47
 
48
- should_run = not is_from_model(carbon_content) and carbon_content_value > 0
48
+ should_run = all([not is_from_model(carbon_content), (carbon_content_value or 0) > 0])
49
49
  logShouldRun(site, MODEL, TERM_ID, should_run)
50
50
  return should_run, carbon_content_value
51
51
 
@@ -5,7 +5,7 @@ from hestia_earth.utils.tools import safe_parse_float
5
5
  from hestia_earth.models.log import logRequirements, logShouldRun, log_as_table
6
6
  from hestia_earth.models.utils.measurement import _new_measurement
7
7
  from hestia_earth.models.utils.site import related_cycles
8
- from hestia_earth.models.utils.blank_node import get_lookup_value
8
+ from hestia_earth.models.utils.term import get_lookup_value
9
9
  from . import MODEL
10
10
 
11
11
  REQUIREMENTS = {
@@ -52,7 +52,8 @@ def _should_run(site: dict):
52
52
  {
53
53
  'product-id': product.get('term', {}).get('@id'),
54
54
  'lookup-value': safe_parse_float(
55
- get_lookup_value(product.get('term', {}), LOOKUPS['liveAquaticSpecies']), default=None
55
+ get_lookup_value(product.get('term', {}), LOOKUPS['liveAquaticSpecies']),
56
+ default=None
56
57
  ),
57
58
  'start-date': product.get('startDate') or cycle.get('startDate'),
58
59
  'end-date': product.get('endDate') or cycle.get('endDate')
@@ -38,11 +38,11 @@ def _product(value: float):
38
38
 
39
39
  def _get_value(product: dict, product_dm_property: dict):
40
40
  value = product.get('value', [0])[0]
41
- dm_percent = safe_parse_float(product_dm_property.get('value'))
41
+ dm_percent = safe_parse_float(product_dm_property.get('value'), default=None)
42
42
  debugValues(product, model=MODEL, term=product.get('term', {}).get('@id'),
43
43
  value=value,
44
44
  dm_percent=dm_percent)
45
- return value * dm_percent / 100
45
+ return value * dm_percent / 100 if dm_percent is not None else 0
46
46
 
47
47
 
48
48
  def _run(products: list):
@@ -41,21 +41,25 @@ def _product(value: float):
41
41
  def _get_value_dm(product: dict, dm_percent: float):
42
42
  term_id = product.get('term', {}).get('@id', '')
43
43
  product_yield = list_sum(product.get('value', [0]))
44
- yield_dm = product_yield * (dm_percent / 100)
44
+ yield_dm = product_yield * (dm_percent / 100) if dm_percent is not None else None
45
45
 
46
46
  # estimate the AG DM calculation
47
- slope = safe_parse_float(get_crop_lookup_value(MODEL, TERM_ID, term_id, LOOKUPS['crop'][1]), None)
48
- intercept = safe_parse_float(get_crop_lookup_value(MODEL, TERM_ID, term_id, LOOKUPS['crop'][0]), None)
47
+ slope = safe_parse_float(get_crop_lookup_value(MODEL, TERM_ID, term_id, LOOKUPS['crop'][1]), default=None)
48
+ intercept = safe_parse_float(get_crop_lookup_value(MODEL, TERM_ID, term_id, LOOKUPS['crop'][0]), default=None)
49
49
  debugValues(product, model=MODEL, term=TERM_ID,
50
50
  yield_dm=yield_dm,
51
51
  dryMatter_percent=dm_percent,
52
52
  slope=slope,
53
53
  intercept=intercept)
54
- return None if slope is None or intercept is None else (yield_dm * slope + intercept * 1000)
54
+ return None if any([
55
+ slope is None,
56
+ intercept is None,
57
+ yield_dm is None
58
+ ]) else (yield_dm * slope + intercept * 1000)
55
59
 
56
60
 
57
61
  def _run(product: dict, dm_property: dict):
58
- value = _get_value_dm(product, safe_parse_float(dm_property.get('value')))
62
+ value = _get_value_dm(product, safe_parse_float(dm_property.get('value'), default=None))
59
63
  return [_product(value)] if value is not None else []
60
64
 
61
65
 
@@ -63,7 +67,7 @@ def _should_run_product(product: dict):
63
67
  term_id = product.get('term', {}).get('@id')
64
68
  value = list_sum(product.get('value', [0]))
65
69
  return value > 0 and (
66
- safe_parse_float(get_crop_lookup_value(MODEL, TERM_ID, term_id, LOOKUPS['crop'][0]), None) is not None
70
+ safe_parse_float(get_crop_lookup_value(MODEL, TERM_ID, term_id, LOOKUPS['crop'][0]), default=None) is not None
67
71
  )
68
72
 
69
73
 
@@ -41,17 +41,17 @@ def _product(value: float):
41
41
  def _get_value_dm(product: dict, dm_percent: float):
42
42
  term_id = product.get('term', {}).get('@id', '')
43
43
  product_yield = list_sum(product.get('value', [0]))
44
- yield_dm = product_yield * (dm_percent / 100)
44
+ yield_dm = product_yield * (dm_percent / 100) if dm_percent is not None else None
45
45
 
46
46
  # TODO with the spreadsheet there are a number of ways this value is calculated.
47
47
  # Currently, the result of this model when applied to Sah et al does not match
48
48
  # the example due to hardcoded calc in the spreadsheet
49
49
 
50
50
  # estimate the BG DM calculation
51
- intercept = safe_parse_float(get_crop_lookup_value(MODEL, TERM_ID, term_id, LOOKUPS['crop'][0]))
52
- slope = safe_parse_float(get_crop_lookup_value(MODEL, TERM_ID, term_id, LOOKUPS['crop'][1]))
51
+ intercept = safe_parse_float(get_crop_lookup_value(MODEL, TERM_ID, term_id, LOOKUPS['crop'][0]), default=None)
52
+ slope = safe_parse_float(get_crop_lookup_value(MODEL, TERM_ID, term_id, LOOKUPS['crop'][1]), default=None)
53
53
  ab_bg_ratio = safe_parse_float(
54
- get_crop_lookup_value(MODEL, TERM_ID, term_id, LOOKUPS['crop'][2])
54
+ get_crop_lookup_value(MODEL, TERM_ID, term_id, LOOKUPS['crop'][2]), default=None
55
55
  )
56
56
  debugValues(product, model=MODEL, term=TERM_ID,
57
57
  yield_dm=yield_dm,
@@ -64,12 +64,16 @@ def _get_value_dm(product: dict, dm_percent: float):
64
64
  # https://www.ipcc-nggip.iges.or.jp/public/2019rf/pdf/4_Volume4/19R_V4_Ch11_Soils_N2O_CO2.pdf
65
65
  # only if site.type = pasture
66
66
  # multiply by the ratio of above to below matter
67
- return None if slope is None or intercept is None or ab_bg_ratio is None \
68
- else ((yield_dm * slope + intercept * 1000) + yield_dm) * ab_bg_ratio
67
+ return None if any([
68
+ yield_dm is None,
69
+ slope is None,
70
+ intercept is None,
71
+ ab_bg_ratio is None
72
+ ]) else ((yield_dm * slope + intercept * 1000) + yield_dm) * ab_bg_ratio
69
73
 
70
74
 
71
75
  def _run(product: dict, dm_property: dict):
72
- value = _get_value_dm(product, safe_parse_float(dm_property.get('value')))
76
+ value = _get_value_dm(product, safe_parse_float(dm_property.get('value'), default=None))
73
77
  return [_product(value)] if value is not None else []
74
78
 
75
79
 
@@ -77,7 +81,7 @@ def _should_run_product(product: dict):
77
81
  term_id = product.get('term', {}).get('@id')
78
82
  value = list_sum(product.get('value', [0]))
79
83
  return value > 0 and (
80
- safe_parse_float(get_crop_lookup_value(MODEL, TERM_ID, term_id, LOOKUPS['crop'][0]), None) is not None
84
+ safe_parse_float(get_crop_lookup_value(MODEL, TERM_ID, term_id, LOOKUPS['crop'][0]), default=None) is not None
81
85
  )
82
86
 
83
87
 
@@ -30,7 +30,7 @@ def productivity_lookup_value(term_id: str, lookup: str, country: dict, animal:
30
30
  return safe_parse_float(
31
31
  extract_grouped_data(value, productivity_key.value) or
32
32
  extract_grouped_data(value, PRODUCTIVITY.HIGH.value), # defaults to high if low is not found
33
- None
33
+ default=None
34
34
  )
35
35
 
36
36
 
@@ -42,7 +42,7 @@ def _product(value: float):
42
42
 
43
43
 
44
44
  def _get_lookup_value(term: dict, column: str):
45
- return safe_parse_float(get_lookup_value(term, column, model=MODEL, term=TERM_ID), None)
45
+ return safe_parse_float(get_lookup_value(term, column, model=MODEL, term=TERM_ID), default=None)
46
46
 
47
47
 
48
48
  def _product_value(product: dict):
@@ -67,7 +67,7 @@ def _crop_residue_lookup_col(term):
67
67
 
68
68
 
69
69
  def _get_lookup_value(term: dict, column: str):
70
- return safe_parse_float(get_lookup_value(term, column, model=MODEL, term=TERM_ID), None)
70
+ return safe_parse_float(get_lookup_value(term, column, model=MODEL, term=TERM_ID), default=None)
71
71
 
72
72
 
73
73
  # Single crop