hestia-earth-models 0.62.1__py3-none-any.whl → 0.62.2__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 (25) hide show
  1. hestia_earth/models/cycle/coldCarcassWeightPerHead.py +4 -2
  2. hestia_earth/models/cycle/coldDressedCarcassWeightPerHead.py +2 -2
  3. hestia_earth/models/cycle/concentrateFeed.py +3 -3
  4. hestia_earth/models/cycle/feedConversionRatio/feedConversionRatioNitrogen.py +2 -1
  5. hestia_earth/models/cycle/readyToCookWeightPerHead.py +2 -2
  6. hestia_earth/models/ipcc2019/animal/pastureGrass.py +50 -40
  7. hestia_earth/models/ipcc2019/organicCarbonPerHa_tier_2_utils.py +208 -39
  8. hestia_earth/models/ipcc2019/organicCarbonPerHa_utils.py +2 -6
  9. hestia_earth/models/ipcc2019/pastureGrass.py +43 -41
  10. hestia_earth/models/ipcc2019/pastureGrass_utils.py +63 -109
  11. hestia_earth/models/mocking/search-results.json +393 -249
  12. hestia_earth/models/site/management.py +52 -29
  13. hestia_earth/models/utils/blank_node.py +38 -0
  14. hestia_earth/models/utils/property.py +6 -6
  15. hestia_earth/models/utils/site.py +7 -0
  16. hestia_earth/models/utils/term.py +21 -1
  17. hestia_earth/models/version.py +1 -1
  18. {hestia_earth_models-0.62.1.dist-info → hestia_earth_models-0.62.2.dist-info}/METADATA +1 -1
  19. {hestia_earth_models-0.62.1.dist-info → hestia_earth_models-0.62.2.dist-info}/RECORD +25 -25
  20. tests/models/ipcc2019/test_organicCarbonPerHa.py +9 -20
  21. tests/models/ipcc2019/test_organicCarbonPerHa_tier_2_utils.py +0 -8
  22. tests/models/site/test_management.py +31 -3
  23. {hestia_earth_models-0.62.1.dist-info → hestia_earth_models-0.62.2.dist-info}/LICENSE +0 -0
  24. {hestia_earth_models-0.62.1.dist-info → hestia_earth_models-0.62.2.dist-info}/WHEEL +0 -0
  25. {hestia_earth_models-0.62.1.dist-info → hestia_earth_models-0.62.2.dist-info}/top_level.txt +0 -0
@@ -42,8 +42,10 @@ def _property(value: float):
42
42
 
43
43
 
44
44
  def _run(product: dict):
45
- liveweightPerHead = get_node_property_value(MODEL, product, 'liveweightPerHead')
46
- processingConversion = get_node_property_value(MODEL, product, 'processingConversionLiveweightToColdCarcassWeight')
45
+ liveweightPerHead = get_node_property_value(MODEL, product, 'liveweightPerHead', term=TERM_ID)
46
+ processingConversion = get_node_property_value(
47
+ MODEL, product, 'processingConversionLiveweightToColdCarcassWeight', term=TERM_ID
48
+ )
47
49
  value = liveweightPerHead * processingConversion if all([liveweightPerHead, processingConversion]) else None
48
50
  prop = _property(value) if value else None
49
51
  return {**product, 'properties': product.get('properties', []) + [prop]} if prop else None
@@ -46,9 +46,9 @@ def _property(value: float):
46
46
 
47
47
 
48
48
  def _run(product: dict):
49
- liveweightPerHead = get_node_property_value(MODEL, product, 'liveweightPerHead')
49
+ liveweightPerHead = get_node_property_value(MODEL, product, 'liveweightPerHead', term=TERM_ID)
50
50
  processingConversion = get_node_property_value(
51
- MODEL, product, 'processingConversionLiveweightToColdDressedCarcassWeight'
51
+ MODEL, product, 'processingConversionLiveweightToColdDressedCarcassWeight', term=TERM_ID
52
52
  )
53
53
  value = liveweightPerHead * processingConversion if all([liveweightPerHead, processingConversion]) else None
54
54
  prop = _property(value) if value else None
@@ -79,7 +79,7 @@ def _calculate_value(cycle: dict, product: dict, inputs: list, property_id: str,
79
79
  def _calculate_default_value(cycle: dict, product: dict, inputs: list, property_id: str):
80
80
  values = [(
81
81
  i.get('term', {}).get('@id'),
82
- get_node_property_value(MODEL, i, property_id),
82
+ get_node_property_value(MODEL, i, property_id, term=TERM_ID),
83
83
  list_sum(i.get('value', []))
84
84
  ) for i in inputs]
85
85
  return _calculate_value(cycle, product, inputs, property_id, values)
@@ -88,8 +88,8 @@ def _calculate_default_value(cycle: dict, product: dict, inputs: list, property_
88
88
  def _calculate_N_value(cycle: dict, product: dict, inputs: list, property_id: str):
89
89
  values = [(
90
90
  i.get('term', {}).get('@id'),
91
- get_node_property_value(MODEL, i, property_id) or
92
- get_node_property_value(MODEL, i, 'crudeProteinContent', default=0) * 0.16,
91
+ get_node_property_value(MODEL, i, property_id, term=TERM_ID) or
92
+ get_node_property_value(MODEL, i, 'crudeProteinContent', default=0, term=TERM_ID) * 0.16,
93
93
  list_sum(i.get('value', []))
94
94
  ) for i in inputs]
95
95
  return _calculate_value(cycle, product, inputs, property_id, values)
@@ -72,5 +72,6 @@ TERM_ID = 'feedConversionRatioNitrogen'
72
72
  def run(cycle: dict, feed: float):
73
73
  inputs = get_feed_inputs(cycle)
74
74
  return list_sum(non_empty_list([
75
- get_node_property_value_converted(MODEL, input, 'crudeProteinContent', default=0) / 6.25 for input in inputs
75
+ get_node_property_value_converted(MODEL, input, 'crudeProteinContent', default=0, term=TERM_ID) / 6.25
76
+ for input in inputs
76
77
  ]))
@@ -42,9 +42,9 @@ def _property(value: float):
42
42
 
43
43
 
44
44
  def _run(product: dict):
45
- liveweightPerHead = get_node_property_value(MODEL, product, 'liveweightPerHead')
45
+ liveweightPerHead = get_node_property_value(MODEL, product, 'liveweightPerHead', term=TERM_ID)
46
46
  processingConversion = get_node_property_value(
47
- MODEL, product, 'processingConversionLiveweightToReadyToCookWeight'
47
+ MODEL, product, 'processingConversionLiveweightToReadyToCookWeight', term=TERM_ID
48
48
  )
49
49
  value = liveweightPerHead * processingConversion if all([liveweightPerHead, processingConversion]) else None
50
50
  prop = _property(value) if value else None
@@ -12,7 +12,8 @@ from hestia_earth.schema import TermTermType
12
12
  from hestia_earth.utils.model import filter_list_term_type
13
13
  from hestia_earth.utils.tools import list_sum
14
14
 
15
- from hestia_earth.models.log import logRequirements, logShouldRun, debugValues
15
+ from hestia_earth.models.log import logRequirements, logShouldRun, debugValues, log_as_table
16
+ from hestia_earth.models.utils.blank_node import lookups_logs, properties_logs
16
17
  from hestia_earth.models.utils.input import _new_input
17
18
  from hestia_earth.models.utils.term import get_wool_terms, get_lookup_value
18
19
  from hestia_earth.models.utils.completeness import _is_term_type_complete, _is_term_type_incomplete
@@ -26,6 +27,7 @@ from ..pastureGrass_utils import (
26
27
  calculate_REM,
27
28
  calculate_REG,
28
29
  calculate_NEfeed,
30
+ calculate_GE,
29
31
  product_wool_energy,
30
32
  get_animals,
31
33
  get_animal_values
@@ -115,7 +117,8 @@ REQUIREMENTS = {
115
117
  }
116
118
  LOOKUPS = {
117
119
  "animalManagement": [
118
- "mjKgEvMilkIpcc2019"
120
+ "mjKgEvMilkIpcc2019",
121
+ "defaultFatContentEvMilkIpcc2019"
119
122
  ],
120
123
  "animalProduct": ["mjKgEvWoolNetEnergyWoolIpcc2019", "allowedLiveAnimalTermIds"],
121
124
  "liveAnimal": [
@@ -184,38 +187,50 @@ def calculate_NEwool(cycle: dict, animal: dict, products: list, total_weight: fl
184
187
  return total_energy * animal_weight/total_weight
185
188
 
186
189
 
187
- def _calculate_GE(
188
- cycle: dict, animal: dict, REM: float, REG: float, NEwool: float, system: dict
189
- ) -> float:
190
- term_id = animal.get('term', {}).get('@id')
191
-
192
- NEm, NEa, NEl, NEwork, NEp, NEg = get_animal_values(cycle, animal, system, log_node=animal)
193
-
194
- NEm_feed, NEg_feed = calculate_NEfeed(animal)
195
- debugValues(animal, model=MODEL, term=term_id, model_key=MODEL_KEY,
196
- NEm=NEm,
197
- NEa=NEa,
198
- NEl=NEl,
199
- NEwork=NEwork,
200
- NEp=NEp,
201
- NEg=NEg,
202
- NEm_feed=NEm_feed,
203
- NEg_feed=NEg_feed)
204
-
205
- return (NEm + NEa + NEl + NEwork + NEp - NEm_feed)/REM + (NEg + NEwool - NEg_feed)/REG
206
-
207
-
208
- def _run_practice(animal: dict, GE: float, meanECHHV: float):
190
+ def _run_practice(
191
+ animal: dict, values: dict, meanDE: float, meanECHHV: float, REM: float, REG: float,
192
+ NEwool: float, NEm_feed: float, NEg_feed: float
193
+ ):
209
194
  def run(practice: dict):
210
195
  key = practice.get('key', {})
211
196
  key_id = key.get('@id')
212
197
  input_term_id = practice_input_id(practice)
198
+
199
+ GE = (
200
+ calculate_GE([values], REM, REG, NEwool, NEm_feed, NEg_feed) / (meanDE/100)
201
+ ) if meanDE else 0
202
+
213
203
  value = (GE / meanECHHV) * (list_sum(practice.get('value', [0])) / 100)
214
204
 
205
+ logs = log_as_table(values | {
206
+ 'animalId': animal.get('term', {}).get('@id'),
207
+ 'practiceKeyId': key_id,
208
+ 'GE': GE,
209
+ 'NEmFeed': NEm_feed,
210
+ 'NEgFeed': NEg_feed,
211
+ 'REM': REM,
212
+ 'REG': REG,
213
+ 'NEwool': NEwool,
214
+ 'meanECHHV': meanECHHV,
215
+ 'meanDE': meanDE
216
+ })
217
+ animal_lookups = lookups_logs(MODEL, [animal], LOOKUPS, model_key=MODEL_KEY, term=input_term_id)
218
+ animal_properties = properties_logs([animal], properties=[
219
+ 'liveweightPerHead',
220
+ 'hoursWorkedPerDay',
221
+ 'animalsPerBirth',
222
+ 'pregnancyRateTotal',
223
+ 'weightAtMaturity',
224
+ 'liveweightGain',
225
+ 'weightAtWeaning',
226
+ 'weightAtOneYear',
227
+ 'weightAtSlaughter'
228
+ ])
229
+
215
230
  logRequirements(animal, model=MODEL, term=input_term_id, model_key=MODEL_KEY,
216
- GE=GE,
217
- meanECHHV=meanECHHV,
218
- practice_key_id=key_id)
231
+ animal_logs=logs,
232
+ animal_lookups=animal_lookups,
233
+ animal_properties=animal_properties)
219
234
 
220
235
  logShouldRun(animal, MODEL, input_term_id, True, model_key=MODEL_KEY)
221
236
 
@@ -235,21 +250,16 @@ def _run_animal(cycle: dict, meanDE: float, meanECHHV: float, system: dict, prac
235
250
  total_liveWeightPerHead = _sum_liveWeightPerHead(animals)
236
251
 
237
252
  def run(animal: dict):
238
- term_id = animal.get('term', {}).get('@id')
239
-
240
253
  NEwool = calculate_NEwool(cycle, animal, wool_products, total_liveWeightPerHead) if (
241
254
  total_liveWeightPerHead > 0
242
255
  ) else 0
243
- GE = (_calculate_GE(cycle, animal, REM, REG, NEwool, system) / (meanDE/100)) if all([REM, REG]) else 0
244
-
245
- debugValues(animal, model=MODEL, term=term_id, model_key=MODEL_KEY,
246
- REM=REM,
247
- REG=REG,
248
- NEwool=NEwool,
249
- GE=GE,
250
- meanDE=meanDE)
256
+ NEm_feed, NEg_feed = calculate_NEfeed(animal)
257
+ animal_values = get_animal_values(cycle, animal, system)
251
258
 
252
- inputs = list(map(_run_practice(animal, GE, meanECHHV), practices))
259
+ inputs = list(map(
260
+ _run_practice(animal, animal_values, meanDE, meanECHHV, REM, REG, NEwool, NEm_feed, NEg_feed),
261
+ practices
262
+ ))
253
263
  return animal | {
254
264
  'inputs': animal.get('inputs', []) + inputs
255
265
  }
@@ -289,8 +299,8 @@ def _should_run(cycle: dict, animals: list, practices: dict):
289
299
  term_type_freshForage_incomplete=freshForage_incomplete,
290
300
  no_cycle_inputs_feed=no_cycle_inputs_feed,
291
301
  all_animals_have_value=all_animals_have_value,
292
- meanDE=meanDE,
293
- meanECHHV=meanECHHV)
302
+ meanDE=calculate_meanDE(practices, term=term_id),
303
+ meanECHHV=calculate_meanECHHV(practices, term=term_id))
294
304
 
295
305
  logShouldRun(animal, MODEL, term_id, should_run, model_key=MODEL_KEY)
296
306
 
@@ -26,7 +26,7 @@ from hestia_earth.models.utils.array_builders import (
26
26
  )
27
27
  from hestia_earth.models.utils.blank_node import (
28
28
  cumulative_nodes_lookup_match, cumulative_nodes_term_match, group_nodes_by_year,
29
- group_nodes_by_year_and_month, GroupNodesByYearMode, node_term_match
29
+ group_nodes_by_year_and_month, GroupNodesByYearMode, node_lookup_match, node_term_match
30
30
  )
31
31
  from hestia_earth.models.utils.cycle import check_cycle_site_ids_identical
32
32
  from hestia_earth.models.utils.descriptive_stats import calc_descriptive_stats
@@ -36,16 +36,16 @@ from hestia_earth.models.utils.site import related_cycles
36
36
 
37
37
  from .organicCarbonPerHa_utils import (
38
38
  CarbonSource, check_consecutive, DEPTH_LOWER, DEPTH_UPPER, check_irrigation,
39
- get_crop_residue_inc_or_left_terms_with_cache,
40
- get_upland_rice_crop_terms_with_cache,
41
- get_upland_rice_land_cover_terms_with_cache,
42
- IPCC_LAND_USE_CATEGORY_TO_LAND_COVER_LOOKUP_VALUE, IPCC_MANAGEMENT_CATEGORY_TO_TILLAGE_MANAGEMENT_LOOKUP_VALUE,
43
- IpccLandUseCategory, IpccManagementCategory, MIN_AREA_THRESHOLD, MIN_YIELD_THRESHOLD, sample_constant,
44
- sample_plus_minus_uncertainty, sample_truncated_normal, STATS_DEFINITION
39
+ get_cover_crop_property_terms_with_cache, get_upland_rice_crop_terms_with_cache,
40
+ get_upland_rice_land_cover_terms_with_cache, IPCC_LAND_USE_CATEGORY_TO_LAND_COVER_LOOKUP_VALUE,
41
+ IPCC_MANAGEMENT_CATEGORY_TO_TILLAGE_MANAGEMENT_LOOKUP_VALUE, IpccLandUseCategory, IpccManagementCategory,
42
+ MIN_AREA_THRESHOLD, MIN_YIELD_THRESHOLD, sample_constant, sample_plus_minus_uncertainty, sample_truncated_normal,
43
+ STATS_DEFINITION
45
44
  )
46
45
 
47
46
  _LOOKUPS = {
48
47
  "crop": "IPCC_LAND_USE_CATEGORY",
48
+ "landCover": "IPCC_LAND_USE_CATEGORY",
49
49
  "tillage": "IPCC_TILLAGE_MANAGEMENT_CATEGORY"
50
50
  }
51
51
 
@@ -59,10 +59,27 @@ _NUMBER_OF_TILLAGES_TERM_ID = "numberOfTillages"
59
59
  _TEMPERATURE_MONTHLY_TERM_ID = "temperatureMonthly"
60
60
  _PRECIPITATION_MONTHLY_TERM_ID = "precipitationMonthly"
61
61
  _PET_MONTHLY_TERM_ID = "potentialEvapotranspirationMonthly"
62
+ _ABOVE_GROUND_CROP_RESIDUE_TOTAL_TERM_ID = "aboveGroundCropResidueTotal"
62
63
  _CARBON_CONTENT_TERM_ID = "carbonContent"
63
64
  _NITROGEN_CONTENT_TERM_ID = "nitrogenContent"
64
65
  _LIGNIN_CONTENT_TERM_ID = "ligninContent"
65
66
 
67
+ _CROP_RESIDUE_MANAGEMENT_TERM_IDS = [
68
+ "residueIncorporated",
69
+ "residueIncorporatedLessThan30DaysBeforeCultivation",
70
+ "residueIncorporatedMoreThan30DaysBeforeCultivation",
71
+ "residueLeftOnField"
72
+ ]
73
+
74
+ _CARBON_SOURCE_TERM_IDS = [
75
+ "discardedCropIncorporated",
76
+ "discardedCropLeftOnField",
77
+ "belowGroundCropResidue",
78
+ ]
79
+
80
+ _MIN_RESIDUE_LEFT_ON_FIELD = 20 # TODO: Confirm assumption
81
+ _DEFAULT_COVER_CROP_BIOMASS = 4000 # TODO: Confirm assumption, Source PAS 2050-1:2012
82
+
66
83
  _CARBON_INPUT_PROPERTY_TERM_IDS = [
67
84
  _CARBON_CONTENT_TERM_ID,
68
85
  _NITROGEN_CONTENT_TERM_ID,
@@ -1284,8 +1301,8 @@ def _get_grouped_sand_content_measurements(grouped_measurements: dict) -> dict:
1284
1301
 
1285
1302
  def _get_grouped_carbon_input_data(grouped_cycles: dict) -> dict:
1286
1303
  grouped_carbon_sources = {
1287
- year: _get_carbon_sources_from_cycles(cycle)
1288
- for year, cycle in grouped_cycles.items()
1304
+ year: flatten([_get_carbon_sources(cycle) for cycle in cycles])
1305
+ for year, cycles in grouped_cycles.items()
1289
1306
  }
1290
1307
 
1291
1308
  return {
@@ -1297,67 +1314,219 @@ def _get_grouped_carbon_input_data(grouped_cycles: dict) -> dict:
1297
1314
  }
1298
1315
 
1299
1316
 
1300
- def _get_carbon_sources_from_cycles(cycles: dict) -> list[CarbonSource]:
1317
+ def _get_carbon_sources(cycle: dict) -> list[CarbonSource]:
1301
1318
  """
1302
- Retrieves and formats all of the valid carbon sources from a list of cycles.
1319
+ Extract and format the carbon source data from a cycle's inputs and products.
1303
1320
 
1304
1321
  Carbon sources can be either a Hestia `Product` node (e.g. crop residue) or `Input` node (e.g. organic amendment).
1305
1322
 
1306
1323
  Parameters
1307
1324
  ----------
1308
- cycles : list[dict]
1309
- A list of Hestia `Cycle` nodes, see: https://www.hestia.earth/schema/Cycle.
1325
+ cycle : list[dict]
1326
+ A Hestia `Cycle` node, see: https://www.hestia.earth/schema/Cycle.
1310
1327
 
1311
1328
  Returns
1312
1329
  -------
1313
1330
  list[CarbonSource]
1314
- A formatted list of `CarbonSource`s for the inputted `Cycle`s.
1331
+ A formatted list of `CarbonSource`s.
1315
1332
  """
1316
- inputs_and_products = non_empty_list(flatten(
1317
- [cycle.get("inputs", []) + cycle.get("products", []) for cycle in cycles]
1318
- ))
1319
-
1333
+ inputs_and_products = cycle.get("inputs", []) + cycle.get("products", [])
1320
1334
  return non_empty_list([
1321
- _iterate_carbon_source(node) for node in inputs_and_products
1322
- if any([
1323
- node.get("term", {}).get("@id") in get_crop_residue_inc_or_left_terms_with_cache(),
1324
- node.get("term", {}).get("termType") in _CARBON_SOURCE_TERM_TYPES
1325
- ])
1335
+ next(
1336
+ (_func(node, cycle) for validator, _func in _CARBON_SOURCE_DECISION_TREE.items() if validator(node)),
1337
+ None
1338
+ ) for node in inputs_and_products
1339
+ ])
1340
+
1341
+
1342
+ def _should_run_carbon_source_ag_residue(node: dict) -> bool:
1343
+ """
1344
+ Determine whether an input or product is a valid above-ground biomass carbon source.
1345
+
1346
+ Parameters
1347
+ ----------
1348
+ node : dict
1349
+ A Hestia [Input](https://www.hestia.earth/schema/Input) or [Product](https://www.hestia.earth/schema/Product)
1350
+ node.
1351
+
1352
+ Returns
1353
+ -------
1354
+ bool
1355
+ Whether the node satisfies the critera.
1356
+ """
1357
+ return node.get("term", {}).get("@id") == _ABOVE_GROUND_CROP_RESIDUE_TOTAL_TERM_ID
1358
+
1359
+
1360
+ def _calc_carbon_source_ag_crop_residue(node: dict, cycle: dict) -> Union[CarbonSource, None]:
1361
+ """
1362
+ Extract and format the carbon source data for above-ground crop residues.
1363
+
1364
+ n.b., We assume that even if a cycle's residue management states that 100% of above-ground crop residues are
1365
+ removed or burnt, a minimal amount of crop residues do remain on site to become a carbon source (see
1366
+ `_MIN_RESIDUE_LEFT_ON_FIELD` variable).
1367
+
1368
+ Parameters
1369
+ ----------
1370
+ node : dict
1371
+ A Hestia [Product](https://www.hestia.earth/schema/Product) node with `term.termType` == `landCover`.
1372
+
1373
+ Returns
1374
+ -------
1375
+ CarbonSource | None
1376
+ The carbon source data of the above-ground residues, or `None` if carbon source data incomplete.
1377
+ """
1378
+ value = get_node_value(node)
1379
+ residue_left_on_field = list_sum([
1380
+ get_node_value(practice) for practice in cycle.get("practices", [])
1381
+ if node_term_match(practice, _CROP_RESIDUE_MANAGEMENT_TERM_IDS)
1382
+ ])
1383
+ mass = value * max(residue_left_on_field, _MIN_RESIDUE_LEFT_ON_FIELD) / 100
1384
+
1385
+ carbon_source = CarbonSource(
1386
+ mass,
1387
+ *_retrieve_carbon_source_properties(node)
1388
+ )
1389
+
1390
+ return carbon_source if _validate_carbon_source(carbon_source) else None
1391
+
1392
+
1393
+ def _should_run_carbon_source_cover_crop(node: dict) -> bool:
1394
+ """
1395
+ Determine whether a product is a valid above cover crop carbon source.
1396
+
1397
+ Parameters
1398
+ ----------
1399
+ node : dict
1400
+ A Hestia [Input](https://www.hestia.earth/schema/Input) or [Product](https://www.hestia.earth/schema/Product)
1401
+ node.
1402
+
1403
+ Returns
1404
+ -------
1405
+ bool
1406
+ Whether the node satisfies the critera.
1407
+ """
1408
+ LOOKUP = _LOOKUPS["landCover"]
1409
+ return all([
1410
+ node.get("term", {}).get("termType") in [TermTermType.LANDCOVER.value],
1411
+ node_lookup_match(
1412
+ node, LOOKUP, IPCC_LAND_USE_CATEGORY_TO_LAND_COVER_LOOKUP_VALUE[IpccLandUseCategory.ANNUAL_CROPS]
1413
+ ),
1414
+ any(
1415
+ get_node_property(node, term_id, False).get("value", False)
1416
+ for term_id in get_cover_crop_property_terms_with_cache()
1417
+ )
1326
1418
  ])
1327
1419
 
1328
1420
 
1329
- def _iterate_carbon_source(node: dict) -> Union[CarbonSource, None]:
1421
+ def _calc_carbon_source_cover_crop(node: dict, *_) -> Union[CarbonSource, None]:
1330
1422
  """
1331
- Validates whether a node is a valid carbon source and returns a `CarbonSource` named tuple if yes.
1423
+ Extract and format the carbon source data for an annual cover crop.
1424
+
1425
+ n.b., We make the assumption that the entirety of the cover crop's biomass remains on site.
1332
1426
 
1333
1427
  Parameters
1334
1428
  ----------
1335
1429
  node : dict
1336
- A Hestia `Product` or `Input` node, see: https://www.hestia.earth/schema/Product
1337
- or https://www.hestia.earth/schema/Input.
1430
+ A Hestia [Product](https://www.hestia.earth/schema/Product) node with `term.termType` == `landCover`.
1338
1431
 
1339
1432
  Returns
1340
1433
  -------
1341
1434
  CarbonSource | None
1342
- A `CarbonSource` named tuple if the node is a carbon source with the required properties, else `None`.
1435
+ The carbon source data of the cover crop, or `None` if carbon source data incomplete.
1436
+ """
1437
+ value = get_node_value(node)
1438
+ carbon_source = CarbonSource(
1439
+ _DEFAULT_COVER_CROP_BIOMASS * value / 100,
1440
+ _Parameter.DEFAULT_CARBON_CONTENT.value.get("value"),
1441
+ _Parameter.DEFAULT_NITROGEN_CONTENT.value.get("value"),
1442
+ _Parameter.DEFAULT_NITROGEN_CONTENT.value.get("value")
1443
+ )
1444
+ return carbon_source
1445
+
1446
+
1447
+ def _should_run_carbon_source(node: dict) -> bool:
1448
+ """
1449
+ Determine whether an input or product is a valid carbon source.
1450
+
1451
+ Parameters
1452
+ ----------
1453
+ node : dict
1454
+ A Hestia [Input](https://www.hestia.earth/schema/Input) or [Product](https://www.hestia.earth/schema/Product)
1455
+ node.
1456
+
1457
+ Returns
1458
+ -------
1459
+ bool
1460
+ Whether the node satisfies the critera.
1461
+ """
1462
+ return any([
1463
+ node.get("term", {}).get("@id") in _CARBON_SOURCE_TERM_IDS,
1464
+ node.get("term", {}).get("termType") in _CARBON_SOURCE_TERM_TYPES
1465
+ ])
1466
+
1467
+
1468
+ def _calc_carbon_source(node: dict, *_) -> Union[CarbonSource, None]:
1469
+ """
1470
+ Extract and format the carbon source data for an input or product.
1471
+
1472
+ Parameters
1473
+ ----------
1474
+ node : dict
1475
+ A Hestia [Input](https://www.hestia.earth/schema/Input) or [Product](https://www.hestia.earth/schema/Product)
1476
+ node.
1477
+
1478
+ Returns
1479
+ -------
1480
+ CarbonSource | None
1481
+ The carbon source data of the cover crop, or `None` if carbon source data incomplete.
1482
+ """
1483
+ carbon_source = CarbonSource(
1484
+ get_node_value(node),
1485
+ *_retrieve_carbon_source_properties(node)
1486
+ )
1487
+
1488
+ return carbon_source if _validate_carbon_source(carbon_source) else None
1489
+
1490
+
1491
+ def _retrieve_carbon_source_properties(node: dict) -> tuple[float, float, float]:
1492
+ """
1493
+ Extract the carbon source properties from an input or product node or, if required, retrieve them from default
1494
+ properties.
1495
+
1496
+ Parameters
1497
+ ----------
1498
+ node : dict
1499
+ A Hestia [Input](https://www.hestia.earth/schema/Input) or [Product](https://www.hestia.earth/schema/Product)
1500
+ node.
1501
+
1502
+ Returns
1503
+ -------
1504
+ tuple[float, float, float]
1505
+ `(carbon_content, nitrogen_content, lignin_content)`
1343
1506
  """
1344
- mass = list_sum(node.get("value", []))
1345
1507
  carbon_content, nitrogen_content, lignin_content = (
1346
1508
  get_node_property(node, term_id).get("value", 0)/100 for term_id in _CARBON_INPUT_PROPERTY_TERM_IDS
1347
1509
  )
1510
+ return carbon_content, nitrogen_content, lignin_content
1348
1511
 
1349
- should_run_ = all([
1350
- mass > 0,
1351
- 0 < carbon_content <= 1,
1352
- 0 < nitrogen_content <= 1,
1353
- 0 < lignin_content <= 1
1512
+
1513
+ def _validate_carbon_source(carbon_source: CarbonSource) -> bool:
1514
+ """
1515
+ Validate that a `CarbonSource` named tuple is data complete.
1516
+ """
1517
+ return all([
1518
+ carbon_source.mass > 0,
1519
+ 0 < carbon_source.carbon_content <= 1,
1520
+ 0 < carbon_source.nitrogen_content <= 1,
1521
+ 0 < carbon_source.lignin_content <= 1
1354
1522
  ])
1355
1523
 
1356
- return (
1357
- CarbonSource(
1358
- mass, carbon_content, nitrogen_content, lignin_content
1359
- ) if should_run_ else None
1360
- )
1524
+
1525
+ _CARBON_SOURCE_DECISION_TREE = {
1526
+ _should_run_carbon_source_ag_residue: _calc_carbon_source_ag_crop_residue,
1527
+ _should_run_carbon_source_cover_crop: _calc_carbon_source_cover_crop,
1528
+ _should_run_carbon_source: _calc_carbon_source,
1529
+ }
1361
1530
 
1362
1531
 
1363
1532
  def _calc_total_organic_carbon_input(
@@ -10,8 +10,8 @@ from hestia_earth.models.utils.array_builders import (
10
10
  )
11
11
  from hestia_earth.models.utils.blank_node import cumulative_nodes_term_match
12
12
  from hestia_earth.models.utils.term import (
13
- get_cover_crop_property_terms, get_crop_residue_incorporated_or_left_on_field_terms, get_irrigated_terms,
14
- get_residue_removed_or_burnt_terms, get_upland_rice_crop_terms, get_upland_rice_land_cover_terms
13
+ get_cover_crop_property_terms, get_irrigated_terms, get_residue_removed_or_burnt_terms, get_upland_rice_crop_terms,
14
+ get_upland_rice_land_cover_terms
15
15
  )
16
16
 
17
17
  STATS_DEFINITION = MeasurementStatsDefinition.SIMULATED.value
@@ -27,10 +27,6 @@ def get_cover_crop_property_terms_with_cache():
27
27
  return lru_cache()(get_cover_crop_property_terms)()
28
28
 
29
29
 
30
- def get_crop_residue_inc_or_left_terms_with_cache():
31
- return lru_cache()(get_crop_residue_incorporated_or_left_on_field_terms)()
32
-
33
-
34
30
  def get_irrigated_terms_with_cache():
35
31
  return lru_cache()(get_irrigated_terms)()
36
32