hestia-earth-models 0.64.14__py3-none-any.whl → 0.65.0__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 (112) 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/environmentalFootprintV3/soilQualityIndexLandTransformation.py +11 -33
  8. hestia_earth/models/faostat2018/landTransformation100YearAverageDuringCycle.py +34 -0
  9. hestia_earth/models/faostat2018/landTransformation20YearAverageDuringCycle.py +34 -0
  10. hestia_earth/models/faostat2018/utils.py +47 -3
  11. hestia_earth/models/hestia/landCover.py +5 -5
  12. hestia_earth/models/hestia/seed_emissions.py +275 -0
  13. hestia_earth/models/ipcc2019/aboveGroundBiomass.py +2 -2
  14. hestia_earth/models/ipcc2019/belowGroundBiomass.py +8 -2
  15. hestia_earth/models/ipcc2019/biomass_utils.py +11 -4
  16. hestia_earth/models/ipcc2019/ch4ToAirEntericFermentation.py +19 -10
  17. hestia_earth/models/ipcc2019/co2ToAirAboveGroundBiomassStockChange.py +2 -1
  18. hestia_earth/models/ipcc2019/co2ToAirBelowGroundBiomassStockChange.py +2 -1
  19. hestia_earth/models/ipcc2019/co2ToAirCarbonStockChange_utils.py +8 -7
  20. hestia_earth/models/ipcc2019/co2ToAirSoilOrganicCarbonStockChange.py +2 -1
  21. hestia_earth/models/ipcc2019/organicCarbonPerHa_tier_1_utils.py +28 -34
  22. hestia_earth/models/ipcc2019/organicCarbonPerHa_tier_2_utils.py +8 -12
  23. hestia_earth/models/ipcc2019/organicCarbonPerHa_utils.py +13 -30
  24. hestia_earth/models/linkedImpactAssessment/{landTransformationFromCropland20YearAverageInputsProduction.py → landTransformation100YearAverageInputsProduction.py} +5 -2
  25. hestia_earth/models/linkedImpactAssessment/{landTransformationFromCropland100YearAverageInputsProduction.py → landTransformation20YearAverageInputsProduction.py} +5 -2
  26. hestia_earth/models/linkedImpactAssessment/utils.py +69 -12
  27. hestia_earth/models/mocking/search-results.json +444 -444
  28. hestia_earth/models/pooreNemecek2018/excretaKgN.py +45 -41
  29. hestia_earth/models/pooreNemecek2018/excretaKgVs.py +89 -63
  30. hestia_earth/models/pooreNemecek2018/saplingsDepreciatedAmountPerCycle.py +8 -8
  31. hestia_earth/models/pooreNemecek2018/utils.py +60 -19
  32. hestia_earth/models/schererPfister2015/nErosionSoilFlux.py +4 -3
  33. hestia_earth/models/schererPfister2015/pErosionSoilFlux.py +4 -3
  34. hestia_earth/models/schererPfister2015/utils.py +12 -9
  35. hestia_earth/models/site/management.py +70 -55
  36. hestia_earth/models/site/pre_checks/cache_sources.py +2 -20
  37. hestia_earth/models/utils/__init__.py +12 -1
  38. hestia_earth/models/utils/aggregated.py +1 -1
  39. hestia_earth/models/utils/blank_node.py +20 -12
  40. hestia_earth/models/utils/cache_sources.py +15 -0
  41. hestia_earth/models/utils/crop.py +5 -0
  42. hestia_earth/models/utils/indicator.py +3 -1
  43. hestia_earth/models/version.py +1 -1
  44. {hestia_earth_models-0.64.14.dist-info → hestia_earth_models-0.65.0.dist-info}/METADATA +2 -2
  45. {hestia_earth_models-0.64.14.dist-info → hestia_earth_models-0.65.0.dist-info}/RECORD +75 -104
  46. tests/models/cml2001Baseline/test_abioticResourceDepletionMineralsAndMetals.py +1 -1
  47. tests/models/cycle/input/test_hestiaAggregatedData.py +5 -2
  48. tests/models/environmentalFootprintV3/test_soilQualityIndexLandTransformation.py +39 -28
  49. tests/models/{hyde32/test_landTransformationFromForest20YearAverageDuringCycle.py → faostat2018/test_landTransformation100YearAverageDuringCycle.py} +5 -5
  50. tests/models/{hyde32/test_landTransformationFromForest100YearAverageDuringCycle.py → faostat2018/test_landTransformation20YearAverageDuringCycle.py} +5 -5
  51. tests/models/faostat2018/test_utils.py +28 -0
  52. tests/models/hestia/test_landCover.py +2 -1
  53. tests/models/hestia/test_seed_emissions.py +27 -0
  54. tests/models/ipcc2019/test_aboveGroundBiomass.py +40 -4
  55. tests/models/ipcc2019/test_belowGroundBiomass.py +40 -4
  56. tests/models/ipcc2019/test_co2ToAirAboveGroundBiomassStockChange.py +52 -15
  57. tests/models/ipcc2019/test_co2ToAirBelowGroundBiomassStockChange.py +50 -14
  58. tests/models/ipcc2019/test_co2ToAirSoilOrganicCarbonStockChange.py +53 -32
  59. tests/models/ipcc2019/test_organicCarbonPerHa.py +91 -108
  60. tests/models/ipcc2019/test_organicCarbonPerHa_tier_1_utils.py +33 -50
  61. tests/models/ipcc2019/test_organicCarbonPerHa_tier_2_utils.py +0 -52
  62. tests/models/linkedImpactAssessment/test_freshwaterWithdrawalsInputsProduction.py +6 -4
  63. tests/models/linkedImpactAssessment/test_landOccupationInputsProduction.py +6 -4
  64. tests/models/linkedImpactAssessment/{test_landTransformationFromForest100YearAverageInputsProduction.py → test_landTransformation100YearAverageInputsProduction.py} +7 -5
  65. tests/models/linkedImpactAssessment/{test_landTransformationFromForest20YearAverageInputsProduction.py → test_landTransformation20YearAverageInputsProduction.py} +7 -5
  66. tests/models/pooreNemecek2018/test_excretaKgN.py +2 -2
  67. tests/models/pooreNemecek2018/test_excretaKgVs.py +1 -1
  68. tests/models/pooreNemecek2018/test_utils.py +26 -0
  69. tests/models/site/test_management.py +10 -27
  70. tests/models/test_cache_sites.py +40 -12
  71. tests/models/utils/test_blank_node.py +0 -8
  72. tests/models/utils/test_cache_sources.py +21 -0
  73. hestia_earth/models/blonkConsultants2016/landTransformationFromForest20YearAverageDuringCycle.py +0 -90
  74. hestia_earth/models/faostat2018/landTransformationFromCropland100YearAverage.py +0 -74
  75. hestia_earth/models/faostat2018/landTransformationFromCropland20YearAverage.py +0 -74
  76. hestia_earth/models/hyde32/__init__.py +0 -13
  77. hestia_earth/models/hyde32/landTransformationFromCropland100YearAverageDuringCycle.py +0 -60
  78. hestia_earth/models/hyde32/landTransformationFromCropland20YearAverageDuringCycle.py +0 -60
  79. hestia_earth/models/hyde32/landTransformationFromForest100YearAverageDuringCycle.py +0 -60
  80. hestia_earth/models/hyde32/landTransformationFromForest20YearAverageDuringCycle.py +0 -60
  81. hestia_earth/models/hyde32/landTransformationFromOtherNaturalVegetation100YearAverageDuringCycle.py +0 -61
  82. hestia_earth/models/hyde32/landTransformationFromOtherNaturalVegetation20YearAverageDuringCycle.py +0 -61
  83. hestia_earth/models/hyde32/landTransformationFromPermanentPasture100YearAverageDuringCycle.py +0 -61
  84. hestia_earth/models/hyde32/landTransformationFromPermanentPasture20YearAverageDuringCycle.py +0 -61
  85. hestia_earth/models/hyde32/utils.py +0 -72
  86. hestia_earth/models/linkedImpactAssessment/landTransformationFromForest100YearAverageInputsProduction.py +0 -36
  87. hestia_earth/models/linkedImpactAssessment/landTransformationFromForest20YearAverageInputsProduction.py +0 -36
  88. hestia_earth/models/linkedImpactAssessment/landTransformationFromOtherNaturalVegetation100YearAverageInputsProduction.py +0 -36
  89. hestia_earth/models/linkedImpactAssessment/landTransformationFromOtherNaturalVegetation20YearAverageInputsProduction.py +0 -36
  90. hestia_earth/models/linkedImpactAssessment/landTransformationFromPermanentPasture100YearAverageInputsProduction.py +0 -36
  91. hestia_earth/models/linkedImpactAssessment/landTransformationFromPermanentPasture20YearAverageInputsProduction.py +0 -36
  92. tests/models/blonkConsultants2016/test_landTransformationFromForest20YearAverageDuringCycle.py +0 -36
  93. tests/models/cycle/pre_checks/test_cache_sources.py +0 -25
  94. tests/models/faostat2018/test_landTransformationFromCropland100YearAverage.py +0 -40
  95. tests/models/faostat2018/test_landTransformationFromCropland20YearAverage.py +0 -40
  96. tests/models/hyde32/__init__.py +0 -0
  97. tests/models/hyde32/test_landTransformationFromCropland100YearAverageDuringCycle.py +0 -21
  98. tests/models/hyde32/test_landTransformationFromCropland20YearAverageDuringCycle.py +0 -21
  99. tests/models/hyde32/test_landTransformationFromOtherNaturalVegetation100YearAverageDuringCycle.py +0 -23
  100. tests/models/hyde32/test_landTransformationFromOtherNaturalVegetation20YearAverageDuringCycle.py +0 -21
  101. tests/models/hyde32/test_landTransformationFromPermanentPasture100YearAverageDuringCycle.py +0 -21
  102. tests/models/hyde32/test_landTransformationFromPermanentPasture20YearAverageDuringCycle.py +0 -21
  103. tests/models/linkedImpactAssessment/test_landTransformationFromCropland100YearAverageInputsProduction.py +0 -23
  104. tests/models/linkedImpactAssessment/test_landTransformationFromCropland20YearAverageInputsProduction.py +0 -23
  105. tests/models/linkedImpactAssessment/test_landTransformationFromOtherNaturalVegetation100YearAverageInputsProduction.py +0 -23
  106. tests/models/linkedImpactAssessment/test_landTransformationFromOtherNaturalVegetation20YearAverageInputsProduction.py +0 -23
  107. tests/models/linkedImpactAssessment/test_landTransformationFromPermanentPasture100YearAverageInputsProduction.py +0 -24
  108. tests/models/linkedImpactAssessment/test_landTransformationFromPermanentPasture20YearAverageInputsProduction.py +0 -24
  109. tests/models/site/pre_checks/test_cache_sources.py +0 -21
  110. {hestia_earth_models-0.64.14.dist-info → hestia_earth_models-0.65.0.dist-info}/LICENSE +0 -0
  111. {hestia_earth_models-0.64.14.dist-info → hestia_earth_models-0.65.0.dist-info}/WHEEL +0 -0
  112. {hestia_earth_models-0.64.14.dist-info → hestia_earth_models-0.65.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,275 @@
1
+ """
2
+ Seed Emissions
3
+
4
+ This model uses the `emissions` recalculated from the current `Cycle` to estimate the `emissions` associated with
5
+ producing the `Input` with `termType` = `seed`.
6
+
7
+ These are the steps:
8
+
9
+ 1. Recalculate all emissions for the Cycle of interest;
10
+ 2. Group Cycle emissions based on their `inputProductionGroupId` lookup value and sum them up;
11
+ 3. Divide the sum of each emission group by the average crop yield (from FAOSTAT) to get emissions per kg of product;
12
+ 4. Multiply by `seed.value` to get total background `emissions` from seed production per ha.
13
+ """
14
+ from functools import reduce
15
+ from hestia_earth.schema import TermTermType, EmissionMethodTier, SiteSiteType
16
+ from hestia_earth.utils.lookup import download_lookup, column_name, get_table_value, extract_grouped_data_closest_date
17
+ from hestia_earth.utils.model import filter_list_term_type
18
+ from hestia_earth.utils.tools import non_empty_list, flatten, list_sum, safe_parse_float
19
+ from hestia_earth.utils.emission import cycle_emissions_in_system_boundary
20
+
21
+ from hestia_earth.models.log import logRequirements, logShouldRun, log_as_table
22
+ from hestia_earth.models.utils.emission import _new_emission
23
+ from hestia_earth.models.utils.site import valid_site_type
24
+ from hestia_earth.models.utils.cycle import cycle_end_year
25
+ from hestia_earth.models.utils.crop import get_crop_grouping_faostat_production, get_landCover_term_id
26
+ from hestia_earth.models.utils.completeness import _is_term_type_complete
27
+ from hestia_earth.models.utils.blank_node import get_lookup_value
28
+ from . import MODEL
29
+
30
+ REQUIREMENTS = {
31
+ "Cycle": {
32
+ "completeness.product": "True",
33
+ "endDate": "",
34
+ "inputs": [{
35
+ "@type": "Input",
36
+ "term.termType": "seed",
37
+ "value": "> 0",
38
+ "none": {
39
+ "impactAssessment": "",
40
+ "fromCycle": "True",
41
+ "producedInCycle": "True"
42
+ }
43
+ }],
44
+ "products": [{
45
+ "@type": "Product",
46
+ "term.termType": "crop",
47
+ "optional": {
48
+ "economicValueShare": "> 0"
49
+ }
50
+ }],
51
+ "site": {
52
+ "@type": "Site",
53
+ "siteType": ["cropland", "glass or high accessible cover"],
54
+ "country": {"@type": "Term", "termType": "region"}
55
+ },
56
+ "emissions": [{"@type": "Emission"}]
57
+ }
58
+ }
59
+ RETURNS = {
60
+ "Emission": [{
61
+ "value": "",
62
+ "inputs": "",
63
+ "methodTier": "background"
64
+ }]
65
+ }
66
+ LOOKUPS = {
67
+ "region-crop-cropGroupingFaostatProduction-yield": "value from cropGroupingFaostatProduction and country",
68
+ "crop": [
69
+ "correspondingSeedTermIds",
70
+ "cropGroupingFaostatProduction",
71
+ "global_economic_value_share",
72
+ "landCoverTermId"
73
+ ],
74
+ "emission": "inputProductionGroupId"
75
+ }
76
+ MODEL_KEY = 'seed_emissions'
77
+ TIER = EmissionMethodTier.BACKGROUND.value
78
+
79
+
80
+ def _emission(term_id: str, value: float, input: dict):
81
+ emission = _new_emission(term_id, MODEL)
82
+ emission['value'] = [round(value, 7)]
83
+ emission['inputs'] = [input]
84
+ emission['methodTier'] = TIER
85
+ return emission
86
+
87
+
88
+ def _run(cycle: dict, economicValueShare: float, total_yield: float, seed_input: dict, grouped_emissions: dict):
89
+ term = seed_input.get('term', {})
90
+ seed_value = list_sum(seed_input.get('value'))
91
+ return [
92
+ _emission(term_id, emission_value * economicValueShare / 100 / total_yield * seed_value, term)
93
+ for term_id, emission_value in grouped_emissions.items()
94
+ ]
95
+
96
+
97
+ def _filter_emissions(cycle: dict):
98
+ required_emission_term_ids = cycle_emissions_in_system_boundary(cycle)
99
+
100
+ emissions = [
101
+ {
102
+ 'id': i.get('term', {}).get('@id'),
103
+ 'group-id': get_lookup_value(i.get('term', {}), LOOKUPS['emission'], model=MODEL, model_key=MODEL_KEY),
104
+ 'value': list_sum(i.get('value'), 0)
105
+ }
106
+ for i in cycle.get('emissions', [])
107
+ if all([
108
+ i.get('term', {}).get('@id') in required_emission_term_ids,
109
+ list_sum(i.get('value'), 0) > 0
110
+ ])
111
+ ]
112
+ emission_ids = set([v.get('id') for v in emissions])
113
+ group_ids = set([v.get('group-id') for v in emissions if v.get('group-id')])
114
+
115
+ # for each group, get the list of all required emissions
116
+ lookup = download_lookup('emission.csv')
117
+ emissions_per_group = [
118
+ {
119
+ 'id': group_id,
120
+ 'emissions': list(filter(
121
+ lambda id: id in required_emission_term_ids,
122
+ list(lookup[lookup[column_name('inputProductionGroupId')] == group_id].termid)
123
+ ))
124
+ }
125
+ for group_id in group_ids
126
+ ]
127
+ emissions_per_group = [
128
+ {
129
+ 'id': group.get('id'),
130
+ 'total-emissions': len(group.get('emissions', [])),
131
+ 'included-emissions': len([v in emission_ids for v in group.get('emissions', [])]),
132
+ 'missing-emissions': '-'.join([v for v in group.get('emissions', []) if v not in emission_ids])
133
+ }
134
+ for group in emissions_per_group
135
+ ]
136
+ # only keep groups that have all emissions present in the Cycle
137
+ valid_groups = list(filter(
138
+ lambda group: group.get('total-emissions') == group.get('included-emissions'),
139
+ emissions_per_group
140
+ ))
141
+ valid_group_ids = set([v.get('id') for v in valid_groups])
142
+
143
+ # finally, only return emissions which groups are valid
144
+ return list(filter(
145
+ lambda emission: emission.get('group-id') in valid_group_ids,
146
+ emissions
147
+ )), emissions_per_group
148
+
149
+
150
+ def _evs(product: dict):
151
+ return safe_parse_float(
152
+ get_lookup_value(product.get('term', {}), 'global_economic_value_share', model=MODEL, model_key=MODEL_KEY)
153
+ ) or product.get('economicValueShare')
154
+
155
+
156
+ def _yield(country_id: str, end_year: int, product: dict):
157
+ grouping = get_crop_grouping_faostat_production(MODEL, product.get('term', {}))
158
+ return safe_parse_float(extract_grouped_data_closest_date(get_table_value(
159
+ download_lookup('region-crop-cropGroupingFaostatProduction-yield.csv'),
160
+ 'termid',
161
+ country_id,
162
+ column_name(grouping)
163
+ ), end_year)) or list_sum(product.get('value'))
164
+
165
+
166
+ def _group_seed_inputs(inputs: list):
167
+ def _group_by(group: dict, input: dict):
168
+ term_id = input.get('term', {}).get('@id')
169
+ group[term_id] = group.get(term_id, []) + [input]
170
+ return group
171
+
172
+ grouped_inputs = reduce(_group_by, inputs, {})
173
+ return [
174
+ inputs[0] | {'value': flatten([v.get('value') for v in inputs])}
175
+ for inputs in grouped_inputs.values()
176
+ ]
177
+
178
+
179
+ def _should_run(cycle: dict):
180
+ crop_products = filter_list_term_type(cycle.get('products', []), TermTermType.CROP)
181
+ site_type_valid = valid_site_type(cycle.get('site', {}), [
182
+ SiteSiteType.CROPLAND.value, SiteSiteType.GLASS_OR_HIGH_ACCESSIBLE_COVER.value
183
+ ])
184
+ is_product_complete = _is_term_type_complete(cycle, 'product')
185
+ end_year = cycle_end_year(cycle)
186
+ country_id = cycle.get('site', {}).get('country', {}).get('@id')
187
+
188
+ # only keep the crop products that map to a seed
189
+ crop_products = [
190
+ {
191
+ 'product': product.get('term', {}).get('@id'),
192
+ 'seed-id': get_lookup_value(
193
+ product.get('term', {}), 'correspondingSeedTermIds', model=MODEL, key=MODEL_KEY),
194
+ 'economicValueShare': _evs(product),
195
+ 'yield': _yield(country_id, end_year, product),
196
+ 'landCover-id': get_landCover_term_id(product.get('term', {}), model=MODEL, key=MODEL_KEY)
197
+ }
198
+ for product in crop_products
199
+ ]
200
+ valid_crop_products = [
201
+ value for value in crop_products if all([
202
+ value.get('seed-id'),
203
+ value.get('economicValueShare'),
204
+ value.get('yield'),
205
+ value.get('landCover-id'),
206
+ ])
207
+ ]
208
+
209
+ # array of ; delimited values
210
+ seed_term_ids = list(set(flatten([v.get('seed-id').split(';') for v in valid_crop_products if v.get('seed-id')])))
211
+
212
+ seed_inputs = [
213
+ i
214
+ for i in cycle.get('inputs', [])
215
+ if all([
216
+ i.get('term', {}).get('termType') == TermTermType.SEED.value,
217
+ i.get('term', {}).get('@id') in seed_term_ids,
218
+ list_sum(i.get('value'), 0) > 0,
219
+ not i.get('impactAssessment'),
220
+ # ignore inputs which are flagged as Product of the Cycle
221
+ not i.get('fromCycle', False),
222
+ not i.get('producedInCycle', False)
223
+ ])
224
+ ]
225
+ # sum up seed inputs with the same id
226
+ seed_inputs = _group_seed_inputs(seed_inputs)
227
+
228
+ crop_land_cover_ids = list(set([p.get('landCover-id') for p in valid_crop_products]))
229
+ total_economicValueShare = list_sum([p.get('economicValueShare') for p in valid_crop_products])
230
+ total_yield = list_sum([p.get('yield') for p in valid_crop_products])
231
+
232
+ emissions, emissions_per_group = _filter_emissions(cycle)
233
+ # group emissions with the same group-id
234
+ grouped_emissions = reduce(
235
+ lambda p, c: p | {c.get('group-id'): p.get(c.get('group-id'), 0) + c.get('value', 0)},
236
+ emissions,
237
+ {}
238
+ )
239
+
240
+ should_run = all([
241
+ site_type_valid,
242
+ len(crop_land_cover_ids) <= 1,
243
+ is_product_complete,
244
+ total_economicValueShare,
245
+ total_yield,
246
+ bool(seed_inputs),
247
+ bool(emissions)
248
+ ])
249
+
250
+ for seed_input in seed_inputs:
251
+ term_id = seed_input.get('term', {}).get('@id')
252
+
253
+ logRequirements(cycle, model=MODEL, term=term_id, model_key=MODEL_KEY,
254
+ site_type_valid=site_type_valid,
255
+ crop_products=log_as_table(crop_products),
256
+ crop_land_cover_ids=';'.join(crop_land_cover_ids),
257
+ is_term_type_product_complete=is_product_complete,
258
+ total_economicValueShare=total_economicValueShare,
259
+ total_yield=total_yield,
260
+ end_year=end_year,
261
+ country_id=country_id,
262
+ emissions=log_as_table(emissions),
263
+ emissions_per_group=log_as_table(emissions_per_group))
264
+
265
+ logShouldRun(cycle, MODEL, term_id, should_run, key=MODEL_KEY)
266
+
267
+ return should_run, total_economicValueShare, total_yield, seed_inputs, grouped_emissions
268
+
269
+
270
+ def run(cycle: dict):
271
+ should_run, economicValueShare, total_yield, seed_inputs, grouped_emissions = _should_run(cycle)
272
+ return flatten(non_empty_list([
273
+ _run(cycle, economicValueShare, total_yield, seed_input, grouped_emissions)
274
+ for seed_input in seed_inputs
275
+ ])) if should_run else []
@@ -22,7 +22,7 @@ from hestia_earth.models.utils.measurement import _new_measurement
22
22
 
23
23
  from . import MODEL
24
24
  from .biomass_utils import (
25
- BiomassCategory, get_valid_land_cover_terms, detect_land_cover_change, group_by_biomass_category, group_by_term_id,
25
+ BiomassCategory, get_valid_management_nodes, detect_land_cover_change, group_by_biomass_category, group_by_term_id,
26
26
  sample_biomass_equilibrium, summarise_land_cover_nodes
27
27
  )
28
28
 
@@ -152,7 +152,7 @@ def _should_run(site: dict) -> tuple[bool, dict, dict]:
152
152
  site_type = site.get("siteType")
153
153
  eco_climate_zone = get_eco_climate_zone_value(site, as_enum=True)
154
154
 
155
- land_cover = get_valid_land_cover_terms(site)
155
+ land_cover = get_valid_management_nodes(site)
156
156
 
157
157
  has_valid_site_type = site_type not in _EXCLUDED_SITE_TYPES
158
158
  has_valid_eco_climate_zone = all([
@@ -22,7 +22,7 @@ from hestia_earth.models.utils.measurement import _new_measurement
22
22
 
23
23
  from . import MODEL
24
24
  from .biomass_utils import (
25
- BiomassCategory, get_valid_land_cover_terms, detect_land_cover_change, group_by_biomass_category,
25
+ BiomassCategory, get_valid_management_nodes, detect_land_cover_change, group_by_biomass_category,
26
26
  sample_biomass_equilibrium, summarise_land_cover_nodes
27
27
  )
28
28
 
@@ -70,12 +70,16 @@ RETURNS = {
70
70
  "statsDefinition": "simulated",
71
71
  "observations": "",
72
72
  "dates": "",
73
+ "depthUpper": "0",
74
+ "depthLower": "30",
73
75
  "methodClassification": "tier 1 model"
74
76
  }]
75
77
  }
76
78
  TERM_ID = 'belowGroundBiomass'
77
79
 
78
80
  _ITERATIONS = 10000
81
+ _DEPTH_UPPER = 0
82
+ _DEPTH_LOWER = 30
79
83
  _METHOD_CLASSIFICATION = MeasurementMethodClassification.TIER_1_MODEL.value
80
84
  _STATS_DEFINITION = MeasurementStatsDefinition.SIMULATED.value
81
85
 
@@ -145,7 +149,7 @@ def _should_run(site: dict) -> tuple[bool, dict, dict]:
145
149
  site_type = site.get("siteType")
146
150
  eco_climate_zone = get_eco_climate_zone_value(site, as_enum=True)
147
151
 
148
- land_cover = get_valid_land_cover_terms(site)
152
+ land_cover = get_valid_management_nodes(site)
149
153
 
150
154
  has_valid_site_type = site_type not in _EXCLUDED_SITE_TYPES
151
155
  has_valid_eco_climate_zone = all([
@@ -522,4 +526,6 @@ def _measurement(
522
526
  measurement = _new_measurement(TERM_ID) | {
523
527
  key: value for key, value in update_dict.items() if value
524
528
  }
529
+ measurement["depthUpper"] = _DEPTH_UPPER
530
+ measurement["depthLower"] = _DEPTH_LOWER
525
531
  return measurement
@@ -10,9 +10,9 @@ from hestia_earth.utils.blank_node import get_node_value
10
10
  from hestia_earth.utils.model import filter_list_term_type
11
11
 
12
12
  from hestia_earth.models.utils.array_builders import repeat_single, truncated_normal_1d
13
- from hestia_earth.models.utils.blank_node import validate_start_date_end_date
13
+ from hestia_earth.models.utils.blank_node import node_term_match, validate_start_date_end_date
14
14
  from hestia_earth.models.utils.ecoClimateZone import EcoClimateZone, get_ecoClimateZone_lookup_grouped_value
15
- from hestia_earth.models.utils.term import get_lookup_value
15
+ from hestia_earth.models.utils.term import get_cover_crop_property_terms, get_lookup_value
16
16
 
17
17
 
18
18
  LOOKUPS = {
@@ -409,9 +409,16 @@ _KWARGS_TO_SAMPLE_FUNC = {
409
409
  }
410
410
 
411
411
 
412
- def get_valid_land_cover_terms(site: dict) -> list[dict]:
412
+ def get_valid_management_nodes(site: dict) -> list[dict]:
413
413
  """Retrieve valid `landCover` nodes from a site's management."""
414
+ COVER_CROP_TERM_IDS = get_cover_crop_property_terms()
414
415
  return [
415
416
  node for node in filter_list_term_type(site.get("management", []), TermTermType.LANDCOVER)
416
- if validate_start_date_end_date(node)
417
+ if (
418
+ validate_start_date_end_date(node)
419
+ and not any(
420
+ prop.get("value", False) for prop in node.get("properties", [])
421
+ if node_term_match(prop, COVER_CROP_TERM_IDS)
422
+ )
423
+ )
417
424
  ]
@@ -3,7 +3,7 @@ from hestia_earth.utils.lookup import column_name, download_lookup, get_table_va
3
3
  from hestia_earth.utils.model import find_primary_product, find_term_match
4
4
  from hestia_earth.utils.tools import list_sum, safe_parse_float
5
5
 
6
- from hestia_earth.models.log import debugMissingLookup, debugValues, logRequirements, logShouldRun
6
+ from hestia_earth.models.log import debugMissingLookup, debugValues, logRequirements, logShouldRun, log_as_table
7
7
  from hestia_earth.models.utils.blank_node import get_total_value_converted_with_min_ratio
8
8
  from hestia_earth.models.utils.input import get_feed_inputs
9
9
  from hestia_earth.models.utils.emission import _new_emission
@@ -103,13 +103,18 @@ def _emission(value: float, sd: float = None, min: float = None, max: float = No
103
103
  return emission
104
104
 
105
105
 
106
- def _run(feed: float, enteric_factor: float = None, enteric_sd: float = None, default_values=(None, None, None)):
107
- value = (feed * ((enteric_factor or default_values[0]) / 100)) / METHANE_EC
108
- min = (feed * (default_values[1] / 100)) / METHANE_EC if all([enteric_factor is None, default_values[1]]) else None
109
- max = (feed * (default_values[2] / 100)) / METHANE_EC if all([enteric_factor is None, default_values[2]]) else None
110
- description = f"Average Ym factor of {default_values[0]}% used as data missing to differentiate Ym." if all([
106
+ def _run(feed: float, enteric_factor: float = None, enteric_sd: float = None, default_values: dict = {}):
107
+ default_value = default_values.get('value')
108
+ value = (feed * ((enteric_factor or default_value) / 100)) / METHANE_EC
109
+ min = (feed * (default_values.get('min') / 100)) / METHANE_EC if all([
110
+ enteric_factor is None, default_values.get('min')
111
+ ]) else None
112
+ max = (feed * (default_values.get('max') / 100)) / METHANE_EC if all([
113
+ enteric_factor is None, default_values.get('max')
114
+ ]) else None
115
+ description = f"Average Ym factor of {default_value}% used as data missing to differentiate Ym." if all([
111
116
  enteric_factor is None,
112
- default_values[0] is not None
117
+ default_value is not None
113
118
  ]) else None
114
119
  return [
115
120
  _emission(value, enteric_sd, min, max, description)
@@ -204,7 +209,11 @@ def _get_default_values(lookup, term: dict):
204
209
  value = get_table_value(lookup, 'termid', term_id, column_name(LOOKUPS['liveAnimal'][3])) if term_id else None
205
210
  min = get_table_value(lookup, 'termid', term_id, column_name(LOOKUPS['liveAnimal'][4])) if term_id else None
206
211
  max = get_table_value(lookup, 'termid', term_id, column_name(LOOKUPS['liveAnimal'][5])) if term_id else None
207
- return safe_parse_float(value, None), safe_parse_float(min, None), safe_parse_float(max, None)
212
+ return {
213
+ 'value': safe_parse_float(value, None),
214
+ 'min': safe_parse_float(min, None),
215
+ 'max': safe_parse_float(max, None)
216
+ }
208
217
 
209
218
 
210
219
  def _should_run(cycle: dict):
@@ -250,7 +259,7 @@ def _should_run(cycle: dict):
250
259
  milk_yield=milk_yield,
251
260
  enteric_factor=enteric_factor,
252
261
  enteric_sd=enteric_sd,
253
- default_values=';'.join(map(str, default_values)))
262
+ default_values=log_as_table(default_values))
254
263
 
255
264
  logRequirements(cycle, model=MODEL, term=TERM_ID,
256
265
  term_type_animalFeed_complete=is_animalFeed_complete,
@@ -259,7 +268,7 @@ def _should_run(cycle: dict):
259
268
 
260
269
  should_run = all([
261
270
  is_animalFeed_complete, is_freshForage_complete, total_feed,
262
- enteric_factor or default_values[0]
271
+ enteric_factor or default_values.get('value')
263
272
  ])
264
273
  logShouldRun(cycle, MODEL, TERM_ID, should_run, methodTier=TIER)
265
274
  return should_run, total_feed, enteric_factor, enteric_sd, default_values
@@ -4,7 +4,7 @@ from hestia_earth.models.log import logRequirements, logShouldRun
4
4
  from hestia_earth.models.utils.blank_node import cumulative_nodes_term_match
5
5
  from hestia_earth.models.utils.emission import _new_emission
6
6
 
7
- from .biomass_utils import detect_land_cover_change, summarise_land_cover_nodes
7
+ from .biomass_utils import detect_land_cover_change, get_valid_management_nodes, summarise_land_cover_nodes
8
8
  from .co2ToAirCarbonStockChange_utils import create_run_function, create_should_run_function
9
9
  from . import MODEL
10
10
 
@@ -133,6 +133,7 @@ def run(cycle: dict) -> list[dict]:
133
133
  """
134
134
  should_run_exec = create_should_run_function(
135
135
  carbon_stock_term_id=_CARBON_STOCK_TERM_ID,
136
+ get_valid_management_nodes_func=get_valid_management_nodes,
136
137
  should_compile_inventory_func=_should_compile_inventory_func,
137
138
  summarise_land_use_func=summarise_land_cover_nodes,
138
139
  detect_land_use_change_func=detect_land_cover_change,
@@ -4,7 +4,7 @@ from hestia_earth.models.log import logRequirements, logShouldRun
4
4
  from hestia_earth.models.utils.blank_node import cumulative_nodes_term_match
5
5
  from hestia_earth.models.utils.emission import _new_emission
6
6
 
7
- from .biomass_utils import detect_land_cover_change, summarise_land_cover_nodes
7
+ from .biomass_utils import detect_land_cover_change, get_valid_management_nodes, summarise_land_cover_nodes
8
8
  from .co2ToAirCarbonStockChange_utils import create_run_function, create_should_run_function
9
9
  from . import MODEL
10
10
 
@@ -125,6 +125,7 @@ def run(cycle: dict) -> list[dict]:
125
125
  """
126
126
  should_run_exec = create_should_run_function(
127
127
  carbon_stock_term_id=_CARBON_STOCK_TERM_ID,
128
+ get_valid_management_nodes_func=get_valid_management_nodes,
128
129
  should_compile_inventory_func=_should_compile_inventory_func,
129
130
  summarise_land_use_func=summarise_land_cover_nodes,
130
131
  detect_land_use_change_func=detect_land_cover_change,
@@ -13,10 +13,9 @@ from pydash.objects import merge
13
13
  from typing import Any, Callable, NamedTuple, Optional, Union
14
14
 
15
15
  from hestia_earth.schema import (
16
- EmissionMethodTier, EmissionStatsDefinition, MeasurementMethodClassification, TermTermType
16
+ EmissionMethodTier, EmissionStatsDefinition, MeasurementMethodClassification
17
17
  )
18
18
  from hestia_earth.utils.date import diff_in_days, YEAR
19
- from hestia_earth.utils.model import filter_list_term_type
20
19
  from hestia_earth.utils.tools import flatten, non_empty_list, safe_parse_date
21
20
 
22
21
  from hestia_earth.models.log import log_as_table
@@ -24,7 +23,7 @@ from hestia_earth.models.utils import pairwise
24
23
  from hestia_earth.models.utils.array_builders import correlated_normal_2d, gen_seed
25
24
  from hestia_earth.models.utils.blank_node import (
26
25
  _gapfill_datestr, _get_datestr_format, DatestrGapfillMode, DatestrFormat, group_nodes_by_year, node_term_match,
27
- split_node_by_dates, validate_start_date_end_date
26
+ split_node_by_dates
28
27
  )
29
28
  from hestia_earth.models.utils.constant import Units, get_atomic_conversion
30
29
  from hestia_earth.models.utils.descriptive_stats import calc_descriptive_stats
@@ -327,6 +326,7 @@ def _add_carbon_stock_change_emissions(
327
326
  def create_should_run_function(
328
327
  *,
329
328
  carbon_stock_term_id: str,
329
+ get_valid_management_nodes_func: Callable[[dict], list[dict]],
330
330
  should_compile_inventory_func: Callable[[dict, list[dict], list[dict]], tuple[bool, dict]],
331
331
  summarise_land_use_func: Callable[[list[dict]], Any],
332
332
  detect_land_use_change_func: Callable[[Any, Any], bool],
@@ -351,6 +351,10 @@ def create_should_run_function(
351
351
  `(site: dict, cycles: list[dict], carbon_stock_measurements: list[dict]) -> (should_run: bool, logs: dict)`, to
352
352
  determine whether there is enough site and cycles data available to compile the carbon stock change inventory.
353
353
 
354
+ get_valid_management_nodes_func : Callable[[dict], list[dict]]
355
+ A function, with the signature... `(site: dict) -> management_nodes: list[dict]` to extract valid management
356
+ nodes from the site for building the land use inventory.
357
+
354
358
  summarise_land_use_func: Callable[[list[dict]], Any]
355
359
  A function with the signature `(nodes: list[dict]) -> Any`, to reduce a list of `landCover`
356
360
  [Management](https://www.hestia.earth/schema/Management) nodes into a land use summary that can be compared
@@ -418,10 +422,7 @@ def create_should_run_function(
418
422
  ])
419
423
  ]
420
424
 
421
- land_cover_nodes = [
422
- node for node in filter_list_term_type(site.get("management", []), TermTermType.LANDCOVER)
423
- if validate_start_date_end_date(node)
424
- ]
425
+ land_cover_nodes = get_valid_management_nodes_func(site)
425
426
 
426
427
  seed = gen_seed(site) # All cycles linked to the same site should be consistent
427
428
  rng = random.default_rng(seed)
@@ -4,7 +4,7 @@ from hestia_earth.models.log import logRequirements, logShouldRun
4
4
  from hestia_earth.models.utils.blank_node import cumulative_nodes_term_match
5
5
  from hestia_earth.models.utils.emission import _new_emission
6
6
 
7
- from .organicCarbonPerHa_tier_1_utils import _assign_ipcc_land_use_category
7
+ from .organicCarbonPerHa_tier_1_utils import _assign_ipcc_land_use_category, get_valid_management_nodes
8
8
  from .co2ToAirCarbonStockChange_utils import create_run_function, create_should_run_function
9
9
  from . import MODEL
10
10
 
@@ -125,6 +125,7 @@ def run(cycle: dict) -> list[dict]:
125
125
  """
126
126
  should_run_exec = create_should_run_function(
127
127
  carbon_stock_term_id=_CARBON_STOCK_TERM_ID,
128
+ get_valid_management_nodes_func=get_valid_management_nodes,
128
129
  should_compile_inventory_func=_should_compile_inventory_func,
129
130
  summarise_land_use_func=lambda nodes: _assign_ipcc_land_use_category(nodes, None),
130
131
  detect_land_use_change_func=lambda a, b: a != b,