hestia-earth-models 0.57.2__py3-none-any.whl → 0.59.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 (109) hide show
  1. hestia_earth/models/cycle/aboveGroundCropResidueTotal.py +17 -12
  2. hestia_earth/models/cycle/excretaKgMass.py +4 -5
  3. hestia_earth/models/cycle/excretaKgN.py +4 -5
  4. hestia_earth/models/cycle/excretaKgVs.py +4 -5
  5. hestia_earth/models/cycle/inorganicFertiliser.py +2 -2
  6. hestia_earth/models/cycle/{irrigated.py → irrigatedTypeUnspecified.py} +4 -4
  7. hestia_earth/models/cycle/liveAnimal.py +9 -11
  8. hestia_earth/models/cycle/milkYield.py +154 -0
  9. hestia_earth/models/cycle/residueIncorporated.py +1 -1
  10. hestia_earth/models/cycle/utils.py +6 -0
  11. hestia_earth/models/emepEea2019/nh3ToAirInorganicFertiliser.py +3 -3
  12. hestia_earth/models/faostat2018/seed.py +2 -3
  13. hestia_earth/models/geospatialDatabase/clayContent.py +17 -4
  14. hestia_earth/models/geospatialDatabase/sandContent.py +17 -4
  15. hestia_earth/models/geospatialDatabase/siltContent.py +2 -2
  16. hestia_earth/models/impact_assessment/irrigated.py +0 -3
  17. hestia_earth/models/ipcc2006/co2ToAirOrganicSoilCultivation.py +2 -2
  18. hestia_earth/models/ipcc2006/n2OToAirCropResidueDecompositionIndirect.py +2 -2
  19. hestia_earth/models/ipcc2006/n2OToAirExcretaDirect.py +1 -1
  20. hestia_earth/models/ipcc2006/n2OToAirExcretaIndirect.py +8 -4
  21. hestia_earth/models/ipcc2006/n2OToAirInorganicFertiliserDirect.py +4 -1
  22. hestia_earth/models/ipcc2006/n2OToAirInorganicFertiliserIndirect.py +1 -1
  23. hestia_earth/models/ipcc2006/n2OToAirOrganicFertiliserDirect.py +1 -1
  24. hestia_earth/models/ipcc2006/n2OToAirOrganicFertiliserIndirect.py +1 -1
  25. hestia_earth/models/ipcc2006/utils.py +11 -8
  26. hestia_earth/models/ipcc2019/ch4ToAirEntericFermentation.py +4 -4
  27. hestia_earth/models/ipcc2019/ch4ToAirFloodedRice.py +16 -7
  28. hestia_earth/models/ipcc2019/co2ToAirSoilCarbonStockChangeManagementChange.py +759 -0
  29. hestia_earth/models/ipcc2019/croppingDuration.py +12 -6
  30. hestia_earth/models/ipcc2019/n2OToAirCropResidueDecompositionDirect.py +5 -52
  31. hestia_earth/models/ipcc2019/n2OToAirInorganicFertiliserDirect.py +104 -0
  32. hestia_earth/models/ipcc2019/n2OToAirInorganicFertiliserIndirect.py +1 -1
  33. hestia_earth/models/ipcc2019/n2OToAirOrganicFertiliserDirect.py +105 -0
  34. hestia_earth/models/ipcc2019/n2OToAirOrganicFertiliserIndirect.py +1 -1
  35. hestia_earth/models/ipcc2019/no3ToGroundwaterCropResidueDecomposition.py +1 -1
  36. hestia_earth/models/ipcc2019/no3ToGroundwaterExcreta.py +1 -1
  37. hestia_earth/models/ipcc2019/no3ToGroundwaterInorganicFertiliser.py +1 -1
  38. hestia_earth/models/ipcc2019/no3ToGroundwaterOrganicFertiliser.py +1 -1
  39. hestia_earth/models/ipcc2019/organicCarbonPerHa.py +1088 -1268
  40. hestia_earth/models/ipcc2019/pastureGrass.py +4 -4
  41. hestia_earth/models/ipcc2019/utils.py +102 -1
  42. hestia_earth/models/koble2014/aboveGroundCropResidue.py +15 -17
  43. hestia_earth/models/koble2014/cropResidueManagement.py +2 -2
  44. hestia_earth/models/koble2014/utils.py +19 -3
  45. hestia_earth/models/linkedImpactAssessment/__init__.py +4 -2
  46. hestia_earth/models/log.py +15 -3
  47. hestia_earth/models/mocking/search-results.json +184 -118
  48. hestia_earth/models/pooreNemecek2018/excretaKgN.py +6 -7
  49. hestia_earth/models/pooreNemecek2018/excretaKgVs.py +7 -6
  50. hestia_earth/models/pooreNemecek2018/no3ToGroundwaterCropResidueDecomposition.py +3 -2
  51. hestia_earth/models/pooreNemecek2018/no3ToGroundwaterExcreta.py +3 -2
  52. hestia_earth/models/pooreNemecek2018/no3ToGroundwaterInorganicFertiliser.py +3 -2
  53. hestia_earth/models/pooreNemecek2018/saplings.py +0 -1
  54. hestia_earth/models/site/management.py +168 -0
  55. hestia_earth/models/site/organicCarbonPerHa.py +251 -89
  56. hestia_earth/models/stehfestBouwman2006/n2OToAirCropResidueDecompositionDirect.py +3 -2
  57. hestia_earth/models/stehfestBouwman2006/n2OToAirExcretaDirect.py +3 -2
  58. hestia_earth/models/stehfestBouwman2006/n2OToAirInorganicFertiliserDirect.py +3 -2
  59. hestia_earth/models/stehfestBouwman2006/n2OToAirOrganicFertiliserDirect.py +3 -2
  60. hestia_earth/models/stehfestBouwman2006/noxToAirCropResidueDecomposition.py +3 -2
  61. hestia_earth/models/stehfestBouwman2006/noxToAirExcreta.py +3 -2
  62. hestia_earth/models/stehfestBouwman2006/noxToAirInorganicFertiliser.py +3 -2
  63. hestia_earth/models/stehfestBouwman2006/noxToAirOrganicFertiliser.py +3 -2
  64. hestia_earth/models/stehfestBouwman2006GisImplementation/noxToAirCropResidueDecomposition.py +3 -2
  65. hestia_earth/models/stehfestBouwman2006GisImplementation/noxToAirExcreta.py +3 -2
  66. hestia_earth/models/stehfestBouwman2006GisImplementation/noxToAirInorganicFertiliser.py +3 -2
  67. hestia_earth/models/stehfestBouwman2006GisImplementation/noxToAirOrganicFertiliser.py +3 -2
  68. hestia_earth/models/utils/aggregated.py +1 -0
  69. hestia_earth/models/utils/blank_node.py +394 -72
  70. hestia_earth/models/utils/cropResidue.py +13 -0
  71. hestia_earth/models/utils/cycle.py +18 -9
  72. hestia_earth/models/utils/measurement.py +1 -1
  73. hestia_earth/models/utils/property.py +4 -4
  74. hestia_earth/models/utils/term.py +48 -3
  75. hestia_earth/models/version.py +1 -1
  76. {hestia_earth_models-0.57.2.dist-info → hestia_earth_models-0.59.0.dist-info}/METADATA +5 -9
  77. {hestia_earth_models-0.57.2.dist-info → hestia_earth_models-0.59.0.dist-info}/RECORD +109 -97
  78. {hestia_earth_models-0.57.2.dist-info → hestia_earth_models-0.59.0.dist-info}/WHEEL +1 -1
  79. tests/models/cycle/animal/input/test_hestiaAggregatedData.py +2 -14
  80. tests/models/cycle/input/test_hestiaAggregatedData.py +4 -16
  81. tests/models/cycle/test_coldCarcassWeightPerHead.py +1 -1
  82. tests/models/cycle/test_coldDressedCarcassWeightPerHead.py +1 -1
  83. tests/models/cycle/{test_irrigated.py → test_irrigatedTypeUnspecified.py} +1 -1
  84. tests/models/cycle/test_milkYield.py +58 -0
  85. tests/models/cycle/test_readyToCookWeightPerHead.py +1 -1
  86. tests/models/emepEea2019/test_nh3ToAirInorganicFertiliser.py +1 -1
  87. tests/models/geospatialDatabase/test_clayContent.py +9 -3
  88. tests/models/geospatialDatabase/test_sandContent.py +9 -3
  89. tests/models/ipcc2006/test_n2OToAirExcretaDirect.py +7 -2
  90. tests/models/ipcc2006/test_n2OToAirExcretaIndirect.py +1 -1
  91. tests/models/ipcc2006/test_n2OToAirInorganicFertiliserDirect.py +7 -2
  92. tests/models/ipcc2006/test_n2OToAirInorganicFertiliserIndirect.py +7 -2
  93. tests/models/ipcc2006/test_n2OToAirOrganicFertiliserDirect.py +7 -2
  94. tests/models/ipcc2006/test_n2OToAirOrganicFertiliserIndirect.py +7 -2
  95. tests/models/ipcc2019/test_ch4ToAirEntericFermentation.py +1 -1
  96. tests/models/ipcc2019/test_co2ToAirSoilCarbonStockChangeManagementChange.py +228 -0
  97. tests/models/ipcc2019/test_n2OToAirInorganicFertiliserDirect.py +74 -0
  98. tests/models/ipcc2019/test_n2OToAirOrganicFertiliserDirect.py +74 -0
  99. tests/models/ipcc2019/test_organicCarbonPerHa.py +303 -1044
  100. tests/models/koble2014/test_residueBurnt.py +1 -2
  101. tests/models/koble2014/test_residueLeftOnField.py +1 -2
  102. tests/models/koble2014/test_residueRemoved.py +1 -2
  103. tests/models/koble2014/test_utils.py +52 -0
  104. tests/models/site/test_management.py +117 -0
  105. tests/models/site/test_organicCarbonPerHa.py +51 -5
  106. tests/models/utils/test_blank_node.py +230 -34
  107. tests/models/utils/test_term.py +17 -3
  108. {hestia_earth_models-0.57.2.dist-info → hestia_earth_models-0.59.0.dist-info}/LICENSE +0 -0
  109. {hestia_earth_models-0.57.2.dist-info → hestia_earth_models-0.59.0.dist-info}/top_level.txt +0 -0
@@ -5,10 +5,11 @@ and crop residue products are provided. Examples:
5
5
  - if `residueLeftOnField` = `100%` and `aboveGroundCropResidueLeftOnField` = `1000kg`, the total is `1000kg`.
6
6
  """
7
7
  from hestia_earth.schema import TermTermType
8
- from hestia_earth.utils.model import filter_list_term_type
8
+ from hestia_earth.utils.model import filter_list_term_type, find_term_match
9
9
  from hestia_earth.utils.tools import list_sum, non_empty_list
10
10
 
11
11
  from hestia_earth.models.log import logRequirements, logShouldRun
12
+ from hestia_earth.models.utils.cropResidue import PRODUCT_ID_TO_PRACTICES_ID
12
13
  from hestia_earth.models.utils.product import _new_product
13
14
  from . import MODEL
14
15
 
@@ -34,24 +35,28 @@ def _product(value: float):
34
35
  def _run(practice: dict, product: dict):
35
36
  practice_value = list_sum(practice.get('value', []))
36
37
  product_value = list_sum(product.get('value', []))
37
- value = 0 if any([practice_value == 0 or product_value == 0]) else product_value / (practice_value / 100)
38
- return [_product(value)]
38
+ value = product_value / (practice_value / 100)
39
+ return [_product(round(value, 2))]
40
+
41
+
42
+ def _matching_product_by_practice(term_id: str):
43
+ return next((v.get('product') for v in PRODUCT_ID_TO_PRACTICES_ID if term_id in v.get('practices')), None)
39
44
 
40
45
 
41
46
  def _should_run(cycle: dict):
42
- # run if any practice equals 100% with a product value
47
+ # run if any practice with a value matches a product with a value
43
48
  practices = filter_list_term_type(cycle.get('practices', []), TermTermType.CROPRESIDUEMANAGEMENT)
44
49
  products = filter_list_term_type(cycle.get('products', []), TermTermType.CROPRESIDUE)
45
50
 
46
51
  def _matching_product(practice: dict):
47
- term_id = practice.get('term', {}).get('@id')
48
- product = next((
49
- p for p in products if all([
50
- p.get('value', []),
51
- p.get('term', {}).get('@id').lower().endswith(term_id.lower())
52
- ])
53
- ), None)
54
- return (practice, product) if product else None
52
+ practice_term_id = practice.get('term', {}).get('@id')
53
+ product_term_id = _matching_product_by_practice(practice_term_id)
54
+ product = find_term_match(products, product_term_id)
55
+
56
+ practice_value = list_sum(practice.get('value', []))
57
+ product_value = list_sum(product.get('value', []))
58
+
59
+ return (practice, product) if all([practice_value, product_value]) else None
55
60
 
56
61
  matching_practices = non_empty_list(map(_matching_product, practices))
57
62
  practice, matching_product = (matching_practices or [(None, None)])[0]
@@ -33,7 +33,6 @@ RETURNS = {
33
33
  }]
34
34
  }
35
35
  MODEL_KEY = 'excretaKgMass'
36
- MODEL_LOG = '/'.join([MODEL, MODEL_KEY])
37
36
 
38
37
  UNITS = [
39
38
  Units.KG_N.value,
@@ -53,7 +52,7 @@ def _convert_by_product(cycle: dict, product: dict, term_id: str):
53
52
  if existing_product else None
54
53
  value = list_sum(existing_product.get('value', [])) / conversion_to_kg_ratio if conversion_to_kg_ratio else None
55
54
 
56
- debugValues(cycle, model=MODEL_LOG, term=term_id,
55
+ debugValues(cycle, model=MODEL, term=term_id,
57
56
  using_excreta_product=existing_product.get('term', {}).get('@id'),
58
57
  conversion_to_kg_ratio=conversion_to_kg_ratio,
59
58
  value=value)
@@ -98,7 +97,7 @@ def _should_run(cycle: dict):
98
97
  ]
99
98
  has_gap_fill_term_ids = len(gap_fill_term_ids) > 0
100
99
 
101
- logRequirements(cycle, model=MODEL_LOG,
100
+ logRequirements(cycle, model=MODEL, model_key=MODEL_KEY,
102
101
  node_type=node_type,
103
102
  has_gap_fill_term_ids=has_gap_fill_term_ids,
104
103
  gap_fill_term_ids=';'.join(gap_fill_term_ids))
@@ -106,9 +105,9 @@ def _should_run(cycle: dict):
106
105
  should_run = all([node_type == NodeType.CYCLE.value, has_gap_fill_term_ids])
107
106
 
108
107
  for term_id in gap_fill_term_ids:
109
- logShouldRun(cycle, MODEL_LOG, term_id, should_run)
108
+ logShouldRun(cycle, MODEL, term_id, should_run, model_key=MODEL_KEY)
110
109
 
111
- logShouldRun(cycle, MODEL_LOG, None, should_run)
110
+ logShouldRun(cycle, MODEL, None, should_run)
112
111
  return should_run, gap_fill_term_ids
113
112
 
114
113
 
@@ -32,7 +32,6 @@ RETURNS = {
32
32
  }]
33
33
  }
34
34
  MODEL_KEY = 'excretaKgN'
35
- MODEL_LOG = '/'.join([MODEL, MODEL_KEY])
36
35
 
37
36
 
38
37
  def _product(value: float, term_id: str):
@@ -44,7 +43,7 @@ def _run_product(cycle: dict, term_id: str):
44
43
  existing_kg_product = find_term_match(cycle.get('products', []), get_kg_term_id(term_id))
45
44
  value = convert_product_to_unit(existing_kg_product, Units.KG_N)
46
45
 
47
- debugValues(cycle, model=MODEL_LOG, term=term_id,
46
+ debugValues(cycle, model=MODEL, term=term_id,
48
47
  using_excreta_product=existing_kg_product.get('term', {}).get('@id'),
49
48
  value=value)
50
49
 
@@ -66,7 +65,7 @@ def _should_run(cycle: dict):
66
65
  ]
67
66
  has_gap_fill_term_ids = len(gap_fill_term_ids) > 0
68
67
 
69
- logRequirements(cycle, model=MODEL_LOG,
68
+ logRequirements(cycle, model=MODEL, model_key=MODEL_KEY,
70
69
  node_type=node_type,
71
70
  has_gap_fill_term_ids=has_gap_fill_term_ids,
72
71
  gap_fill_term_ids=';'.join(gap_fill_term_ids))
@@ -74,9 +73,9 @@ def _should_run(cycle: dict):
74
73
  should_run = all([node_type == NodeType.CYCLE.value, has_gap_fill_term_ids])
75
74
 
76
75
  for term_id in gap_fill_term_ids:
77
- logShouldRun(cycle, MODEL_LOG, term_id, should_run)
76
+ logShouldRun(cycle, MODEL, term_id, should_run, model_key=MODEL_KEY)
78
77
 
79
- logShouldRun(cycle, MODEL_LOG, None, should_run)
78
+ logShouldRun(cycle, MODEL, None, should_run)
80
79
  return should_run, gap_fill_term_ids
81
80
 
82
81
 
@@ -32,7 +32,6 @@ RETURNS = {
32
32
  }]
33
33
  }
34
34
  MODEL_KEY = 'excretaKgVs'
35
- MODEL_LOG = '/'.join([MODEL, MODEL_KEY])
36
35
 
37
36
 
38
37
  def _product(value: float, term_id: str):
@@ -44,7 +43,7 @@ def _run_product(cycle: dict, term_id: str):
44
43
  existing_kg_product = find_term_match(cycle.get('products', []), get_kg_term_id(term_id))
45
44
  value = convert_product_to_unit(existing_kg_product, Units.KG_VS)
46
45
 
47
- debugValues(cycle, model=MODEL_LOG, term=term_id,
46
+ debugValues(cycle, model=MODEL, term=term_id,
48
47
  using_excreta_product=existing_kg_product.get('term', {}).get('@id'),
49
48
  value=value)
50
49
 
@@ -66,7 +65,7 @@ def _should_run(cycle: dict):
66
65
  ]
67
66
  has_gap_fill_term_ids = len(gap_fill_term_ids) > 0
68
67
 
69
- logRequirements(cycle, model=MODEL_LOG,
68
+ logRequirements(cycle, model=MODEL, model_key=MODEL_KEY,
70
69
  node_type=node_type,
71
70
  has_gap_fill_term_ids=has_gap_fill_term_ids,
72
71
  gap_fill_term_ids=';'.join(gap_fill_term_ids))
@@ -74,9 +73,9 @@ def _should_run(cycle: dict):
74
73
  should_run = all([node_type == NodeType.CYCLE.value, gap_fill_term_ids])
75
74
 
76
75
  for term_id in gap_fill_term_ids:
77
- logShouldRun(cycle, MODEL_LOG, term_id, should_run)
76
+ logShouldRun(cycle, MODEL, term_id, should_run, model_key=MODEL_KEY)
78
77
 
79
- logShouldRun(cycle, MODEL_LOG, None, should_run)
78
+ logShouldRun(cycle, MODEL, None, should_run)
80
79
  return should_run, gap_fill_term_ids
81
80
 
82
81
 
@@ -138,12 +138,12 @@ def _should_run_input(cycle: dict, input: dict):
138
138
  ])
139
139
 
140
140
  for term_id in include_term_ids:
141
- logRequirements(cycle, model=MODEL_LOG, term=term_id,
141
+ logRequirements(cycle, model=MODEL_LOG, term=term_id, model_key=MODEL_KEY,
142
142
  nitrogenContent=nitrogenContent,
143
143
  phosphateContentAsP2O5=phosphateContentAsP2O5,
144
144
  potassiumContentAsK2O=potassiumContentAsK2O)
145
145
 
146
- logShouldRun(cycle, MODEL_LOG, term_id, should_run)
146
+ logShouldRun(cycle, MODEL_LOG, term_id, should_run, model_key=MODEL_KEY)
147
147
  return should_run
148
148
 
149
149
 
@@ -24,7 +24,7 @@ RETURNS = {
24
24
  "value": ""
25
25
  }]
26
26
  }
27
- TERM_ID = 'irrigated'
27
+ TERM_ID = 'irrigatedTypeUnspecified'
28
28
  MIN_IRRIGATION_M3 = 250
29
29
 
30
30
 
@@ -34,6 +34,9 @@ def _practice(value: float):
34
34
  return practice
35
35
 
36
36
 
37
+ def _is_irrigation_practice(practice: dict): return practice.get('term', {}).get('units', '') in ['%', '% area']
38
+
39
+
37
40
  def _run(irrigation_m3: float):
38
41
  value = 100 if irrigation_m3 > MIN_IRRIGATION_M3 else 0
39
42
  return [_practice(value)]
@@ -61,9 +64,6 @@ def _should_run(cycle: dict):
61
64
  return should_run, irrigation_value_m3
62
65
 
63
66
 
64
- def _is_irrigation_practice(practice: dict): return practice.get('term', {}).get('units', '') in ['%', '% area']
65
-
66
-
67
67
  def run(cycle: dict):
68
68
  should_run, irrigation_m3 = _should_run(cycle)
69
69
  return _run(irrigation_m3) if should_run else []
@@ -9,8 +9,8 @@ from hestia_earth.utils.tools import list_sum, safe_parse_float
9
9
 
10
10
  from hestia_earth.models.log import logRequirements, logShouldRun
11
11
  from hestia_earth.models.utils.product import _new_product
12
- from hestia_earth.models.utils.term import get_lookup_value
13
12
  from hestia_earth.models.utils.site import valid_site_type
13
+ from .utils import _get_liveAnimal_term_id
14
14
  from . import MODEL
15
15
 
16
16
  REQUIREMENTS = {
@@ -57,25 +57,21 @@ def _run(term_id: str, product_value: dict, propertyPerHead: float):
57
57
  return [_product(term_id, value)] if value else []
58
58
 
59
59
 
60
- def _get_liveAnimal_term_id(product: dict):
61
- return get_lookup_value(product.get('term', {}), MODEL_KEY, model=MODEL, model_key=MODEL_KEY)
62
-
63
-
64
60
  def _should_run(cycle: dict):
65
61
  site_type_valid = valid_site_type(cycle.get('site'), site_types=VALID_SITE_TYPES)
66
62
  product = find_primary_product(cycle) or {}
67
63
  product_value = list_sum(product.get('value', []))
68
64
  is_animalProduct = product.get('term', {}).get('termType') == TermTermType.ANIMALPRODUCT.value
69
65
  units = f"{product.get('term', {}).get('units')} / head"
70
- property = next(
66
+ propertyPerHead = next(
71
67
  (p for p in product.get('properties', []) if p.get('term', {}).get('units') == units), {}
72
68
  ) or next(
73
69
  (p for p in product.get('properties', []) if p.get('term', {}).get('@id').endswith('PerHead')), {}
74
70
  )
75
- propertyPerHead = safe_parse_float(property.get('value'), 0)
71
+ propertyPerHead_value = safe_parse_float(propertyPerHead.get('value'), None)
76
72
 
77
73
  # make sure the `liveAnimal` Term is not already present as a Product or Input
78
- term_id = _get_liveAnimal_term_id(product)
74
+ term_id = _get_liveAnimal_term_id(product, model_key=MODEL_KEY)
79
75
  has_liveAnimal_product = find_term_match(cycle.get('products', []), term_id, None) is not None
80
76
  has_liveAnimal_input = find_term_match(cycle.get('products', []), term_id, None) is not None
81
77
 
@@ -85,7 +81,8 @@ def _should_run(cycle: dict):
85
81
  is_animalProduct,
86
82
  not has_liveAnimal_product,
87
83
  not has_liveAnimal_input,
88
- product_value, propertyPerHead
84
+ product_value,
85
+ propertyPerHead_value
89
86
  ])
90
87
 
91
88
  # if the Term is added as Input, do not shows logs for it
@@ -95,10 +92,11 @@ def _should_run(cycle: dict):
95
92
  is_animalProduct=is_animalProduct,
96
93
  has_liveAnimal_product=has_liveAnimal_product,
97
94
  product_value=product_value,
98
- propertyPerHead=propertyPerHead)
95
+ propertyPerHead_term_id=propertyPerHead.get('term', {}).get('@id'),
96
+ propertyPerHead_value=propertyPerHead_value)
99
97
 
100
98
  logShouldRun(cycle, MODEL, term_id, should_run, model_key=MODEL_KEY)
101
- return should_run, term_id, product_value, propertyPerHead
99
+ return should_run, term_id, product_value, propertyPerHead_value
102
100
 
103
101
 
104
102
  def run(cycle: dict):
@@ -0,0 +1,154 @@
1
+ """
2
+ Milk Yield
3
+
4
+ This model gap-fills the practice "Milk yield per animal X (raw/FPCM)" (e.g. `Milk yield per cow (raw)`) when:
5
+
6
+ 1. The corresponding milk term in the animalProduct glossary (e.g. Milk, cow, raw) is added as a Product of the Cycle;
7
+ 2. The number of lactating animals is known.
8
+
9
+ It also adds the properties of the Product to the gap-filled Practice.
10
+ """
11
+ from hestia_earth.schema import TermTermType, SiteSiteType, PracticeStatsDefinition
12
+ from hestia_earth.utils.model import filter_list_term_type, find_term_match
13
+ from hestia_earth.utils.tools import list_sum
14
+
15
+ from hestia_earth.models.log import logRequirements, logShouldRun, log_blank_nodes_id
16
+ from hestia_earth.models.utils.practice import _new_practice
17
+ from hestia_earth.models.utils.site import valid_site_type
18
+ from hestia_earth.models.utils.term import get_lookup_value
19
+ from .utils import _get_liveAnimal_term_id
20
+ from . import MODEL
21
+
22
+ REQUIREMENTS = {
23
+ "Cycle": {
24
+ "cycleDuration": "",
25
+ "site": {
26
+ "@type": "Site",
27
+ "siteType": ["cropland", "permanent pasture", "animal housing"]
28
+ },
29
+ "products": [{
30
+ "@type": "Product",
31
+ "value": "> 0",
32
+ "termType": "animalProduct"
33
+ }],
34
+ "animals": [{
35
+ "@type": "Animal",
36
+ "value": "> 0",
37
+ "termType": "liveAnimal"
38
+ }]
39
+ }
40
+ }
41
+ RETURNS = {
42
+ "Practice": [{
43
+ "value": "",
44
+ "min": "",
45
+ "max": "",
46
+ "sd": "",
47
+ "statsDefinition": "modelled"
48
+ }]
49
+ }
50
+ LOOKUPS = {
51
+ "animalProduct": ["liveAnimal", "milkYieldPracticeId"]
52
+ }
53
+
54
+ MODEL_KEY = 'milkYield'
55
+ VALID_SITE_TYPES = [
56
+ SiteSiteType.CROPLAND.value, SiteSiteType.ANIMAL_HOUSING.value, SiteSiteType.PERMANENT_PASTURE.value
57
+ ]
58
+
59
+
60
+ def practice(term_id: str, value: float, properties: list, sd: float = None, min: float = None, max: float = None):
61
+ data = _new_practice(term_id)
62
+ data['value'] = [round(value, 2)]
63
+ if properties:
64
+ data['properties'] = properties
65
+ if sd is not None:
66
+ data['sd'] = [round(sd, 2)]
67
+ data['statsDefinition'] = PracticeStatsDefinition.MODELLED.value
68
+ if min is not None:
69
+ data['min'] = [round(min, 2)]
70
+ data['statsDefinition'] = PracticeStatsDefinition.MODELLED.value
71
+ if max is not None:
72
+ data['max'] = [round(max, 2)]
73
+ data['statsDefinition'] = PracticeStatsDefinition.MODELLED.value
74
+ return data
75
+
76
+
77
+ def _run(cycle: dict, product: dict):
78
+ cycleDuration = cycle.get('cycleDuration')
79
+
80
+ term = product.get('term', {})
81
+ practice_id = get_lookup_value(term, 'milkYieldPracticeId')
82
+
83
+ live_animal_term_id = _get_liveAnimal_term_id(product, model_key=MODEL_KEY)
84
+ live_animal_node = find_term_match(cycle.get('animals', []), live_animal_term_id)
85
+
86
+ value = list_sum(product.get('value')) / cycleDuration / live_animal_node.get('value')
87
+ sd = list_sum(product.get('sd')) / cycleDuration / live_animal_node.get('value') if all([
88
+ list_sum(product.get('sd', [])) > 0
89
+ ]) else None
90
+ min = list_sum(product.get('min')) / cycleDuration / live_animal_node.get('value') if all([
91
+ list_sum(product.get('min', [])) > 0
92
+ ]) else None
93
+ max = list_sum(product.get('max')) / cycleDuration / live_animal_node.get('value') if all([
94
+ list_sum(product.get('max', [])) > 0
95
+ ]) else None
96
+
97
+ return live_animal_node | {
98
+ 'practices': live_animal_node.get('practices', []) + [
99
+ practice(practice_id, value, product.get('properties', []), sd=sd, min=min, max=max)
100
+ ]
101
+ }
102
+
103
+
104
+ def _should_run_product(cycle: dict, product: dict):
105
+ cycleDuration = cycle.get('cycleDuration')
106
+ site_type_valid = valid_site_type(cycle.get('site'), site_types=VALID_SITE_TYPES)
107
+
108
+ term = product.get('term', {})
109
+ term_id = term.get('@id')
110
+
111
+ has_product_value = list_sum(product.get('value', [])) > 0
112
+
113
+ live_animal_term_id = _get_liveAnimal_term_id(product, model_key=MODEL_KEY)
114
+ live_animal_node = find_term_match(cycle.get('animals', []), live_animal_term_id)
115
+ has_live_animal_node_value = live_animal_node.get('value', 0) > 0
116
+
117
+ practice_id = get_lookup_value(term, 'milkYieldPracticeId', model=MODEL, model_key=MODEL_KEY)
118
+ has_practice_id = any([
119
+ find_term_match(cycle.get('practices', []), practice_id),
120
+ find_term_match((live_animal_node or {}).get('practices', []), practice_id)
121
+ ])
122
+ missing_milkYield_practice = not has_practice_id
123
+
124
+ logRequirements(cycle, model=MODEL, term=term_id,
125
+ cycleDuration=cycleDuration,
126
+ site_type_valid=site_type_valid,
127
+ has_product_value=has_product_value,
128
+ live_animal_term_id=live_animal_term_id,
129
+ has_live_animal_node_value=has_live_animal_node_value,
130
+ practice_id=practice_id,
131
+ missing_milkYield_practice=missing_milkYield_practice)
132
+
133
+ should_run = all([
134
+ has_product_value, has_live_animal_node_value, practice_id, missing_milkYield_practice
135
+ ])
136
+ logShouldRun(cycle, MODEL, term_id, should_run, model_key=MODEL_KEY)
137
+ return should_run
138
+
139
+
140
+ def _should_run(cycle: dict):
141
+ products = filter_list_term_type(cycle.get('products', []), TermTermType.ANIMALPRODUCT)
142
+ products = [p for p in products if _should_run_product(cycle, p)]
143
+
144
+ logRequirements(cycle, model=MODEL, term=None,
145
+ animal_product_ids=log_blank_nodes_id(products))
146
+
147
+ should_run = all([products])
148
+ logShouldRun(cycle, MODEL, None, should_run, model_key=MODEL_KEY)
149
+ return should_run, products
150
+
151
+
152
+ def run(cycle: dict):
153
+ should_run, products = _should_run(cycle)
154
+ return [_run(cycle, p) for p in products] if should_run else []
@@ -15,7 +15,7 @@ REQUIREMENTS = {
15
15
  {"@type": "Product", "term.@id": "aboveGroundCropResidueTotal", "value": "> 0"},
16
16
  {"@type": "Product", "term.@id": "aboveGroundCropResidueIncorporated", "value": "> 0"}
17
17
  ],
18
- "not": {
18
+ "none": {
19
19
  "practices": [{
20
20
  "@type": "Practice",
21
21
  "term.@id": [
@@ -1,9 +1,15 @@
1
1
  from hestia_earth.utils.tools import list_average
2
2
 
3
3
  from hestia_earth.models.log import logShouldRun
4
+ from hestia_earth.models.utils.term import get_lookup_value
4
5
  from . import MODEL
5
6
 
6
7
 
8
+ def _get_liveAnimal_term_id(product: dict, **log_ars):
9
+ term_id = get_lookup_value(product.get('term', {}), 'liveAnimal', model=MODEL, **log_ars)
10
+ return term_id.split(';')[0] if term_id else None
11
+
12
+
7
13
  def _should_run_property_by_min_max(property: dict):
8
14
  return all([
9
15
  property.get('min') is not None,
@@ -42,11 +42,11 @@ LOOKUPS = {
42
42
  RETURNS = {
43
43
  "Emission": [{
44
44
  "value": "",
45
- "methodTier": "tier 1"
45
+ "methodTier": "tier 2"
46
46
  }]
47
47
  }
48
48
  TERM_ID = 'nh3ToAirInorganicFertiliser'
49
- TIER = EmissionMethodTier.TIER_1.value
49
+ TIER = EmissionMethodTier.TIER_2.value
50
50
  UNSPECIFIED_TERM_ID = 'inorganicNitrogenFertiliserUnspecifiedKgN'
51
51
 
52
52
 
@@ -127,7 +127,7 @@ def _should_run(cycle: dict):
127
127
  run_with_unspecified = (has_country_data and has_unspecifiedKgN) or fertiliser_complete
128
128
  unspecifiedKgN = (
129
129
  0 if len(unspecifiedKgN) == 0 and fertiliser_complete else list_sum(unspecifiedKgN, None)
130
- ) if run_with_unspecified else []
130
+ ) if run_with_unspecified else None
131
131
 
132
132
  logRequirements(cycle, model=MODEL, term=TERM_ID,
133
133
  temperature=temperature,
@@ -2,7 +2,7 @@ from hestia_earth.schema import InputStatsDefinition, TermTermType
2
2
  from hestia_earth.utils.model import filter_list_term_type
3
3
  from hestia_earth.utils.tools import list_sum, safe_parse_float
4
4
 
5
- from hestia_earth.models.log import logRequirements, logShouldRun
5
+ from hestia_earth.models.log import logRequirements, logShouldRun, log_blank_nodes_id
6
6
  from hestia_earth.models.utils.term import get_lookup_value
7
7
  from hestia_earth.models.utils.input import _new_input
8
8
  from hestia_earth.models.utils.completeness import _is_term_type_incomplete
@@ -67,12 +67,11 @@ def _should_run(cycle: dict):
67
67
  products = cycle.get('products', [])
68
68
  crop_products = list(filter(_should_run_product, filter_list_term_type(products, TermTermType.CROP)))
69
69
  has_crop_products = len(crop_products) > 0
70
- product_ids = ';'.join([p.get('term', {}).get('@id') for p in crop_products])
71
70
  term_type_incomplete = _is_term_type_incomplete(cycle, TermTermType.SEED.value)
72
71
 
73
72
  logRequirements(cycle, model=MODEL, term=TERM_ID,
74
73
  term_type_seed_incomplete=term_type_incomplete,
75
- crop_product_ids=product_ids or 'None')
74
+ crop_product_ids=log_blank_nodes_id(crop_products))
76
75
 
77
76
  should_run = all([term_type_incomplete, has_crop_products])
78
77
  logShouldRun(cycle, MODEL, TERM_ID, should_run)
@@ -1,7 +1,8 @@
1
1
  from hestia_earth.schema import MeasurementMethodClassification
2
2
 
3
3
  from hestia_earth.models.log import logRequirements, logShouldRun
4
- from hestia_earth.models.utils.measurement import _new_measurement
4
+ from hestia_earth.models.utils.blank_node import has_original_by_ids
5
+ from hestia_earth.models.utils.measurement import SOIL_TEXTURE_IDS, _new_measurement
5
6
  from hestia_earth.models.utils.source import get_source
6
7
  from .utils import download, has_geospatial_data, should_download
7
8
  from . import MODEL
@@ -12,7 +13,17 @@ REQUIREMENTS = {
12
13
  {"latitude": "", "longitude": ""},
13
14
  {"boundary": {}},
14
15
  {"region": {"@type": "Term", "termType": "region"}}
15
- ]
16
+ ],
17
+ "none": {
18
+ "measurements": [{
19
+ "@type": "Measurement",
20
+ "term.@id": [
21
+ "clayContent",
22
+ "sandContent",
23
+ "siltContent"
24
+ ]
25
+ }]
26
+ }
16
27
  }
17
28
  }
18
29
  RETURNS = {
@@ -54,12 +65,14 @@ def _run(site: dict):
54
65
  def _should_run(site: dict):
55
66
  contains_geospatial_data = has_geospatial_data(site)
56
67
  below_max_area_size = should_download(TERM_ID, site)
68
+ has_original_texture_measurements = has_original_by_ids(site.get('measurements', []), SOIL_TEXTURE_IDS)
57
69
 
58
70
  logRequirements(site, model=MODEL, term=TERM_ID,
59
71
  contains_geospatial_data=contains_geospatial_data,
60
- below_max_area_size=below_max_area_size)
72
+ below_max_area_size=below_max_area_size,
73
+ has_original_texture_measurements=has_original_texture_measurements)
61
74
 
62
- should_run = all([contains_geospatial_data, below_max_area_size])
75
+ should_run = all([contains_geospatial_data, below_max_area_size, not has_original_texture_measurements])
63
76
  logShouldRun(site, MODEL, TERM_ID, should_run)
64
77
  return should_run
65
78
 
@@ -1,7 +1,8 @@
1
1
  from hestia_earth.schema import MeasurementMethodClassification
2
2
 
3
3
  from hestia_earth.models.log import logRequirements, logShouldRun
4
- from hestia_earth.models.utils.measurement import _new_measurement
4
+ from hestia_earth.models.utils.blank_node import has_original_by_ids
5
+ from hestia_earth.models.utils.measurement import SOIL_TEXTURE_IDS, _new_measurement
5
6
  from hestia_earth.models.utils.source import get_source
6
7
  from .utils import download, has_geospatial_data, should_download
7
8
  from . import MODEL
@@ -12,7 +13,17 @@ REQUIREMENTS = {
12
13
  {"latitude": "", "longitude": ""},
13
14
  {"boundary": {}},
14
15
  {"region": {"@type": "Term", "termType": "region"}}
15
- ]
16
+ ],
17
+ "none": {
18
+ "measurements": [{
19
+ "@type": "Measurement",
20
+ "term.@id": [
21
+ "clayContent",
22
+ "sandContent",
23
+ "siltContent"
24
+ ]
25
+ }]
26
+ }
16
27
  }
17
28
  }
18
29
  RETURNS = {
@@ -54,12 +65,14 @@ def _run(site: dict):
54
65
  def _should_run(site: dict):
55
66
  contains_geospatial_data = has_geospatial_data(site)
56
67
  below_max_area_size = should_download(TERM_ID, site)
68
+ has_original_texture_measurements = has_original_by_ids(site.get('measurements', []), SOIL_TEXTURE_IDS)
57
69
 
58
70
  logRequirements(site, model=MODEL, term=TERM_ID,
59
71
  contains_geospatial_data=contains_geospatial_data,
60
- below_max_area_size=below_max_area_size)
72
+ below_max_area_size=below_max_area_size,
73
+ has_original_texture_measurements=has_original_texture_measurements)
61
74
 
62
- should_run = all([contains_geospatial_data, below_max_area_size])
75
+ should_run = all([contains_geospatial_data, below_max_area_size, not has_original_texture_measurements])
63
76
  logShouldRun(site, MODEL, TERM_ID, should_run)
64
77
  return should_run
65
78
 
@@ -1,6 +1,6 @@
1
1
  from hestia_earth.schema import MeasurementMethodClassification
2
2
 
3
- from hestia_earth.models.log import logRequirements, logShouldRun
3
+ from hestia_earth.models.log import logRequirements, logShouldRun, log_blank_nodes_id
4
4
  from hestia_earth.models.utils.measurement import _new_measurement, measurement_value
5
5
  from hestia_earth.models.utils.source import get_source
6
6
  from . import MODEL, clayContent, sandContent
@@ -53,7 +53,7 @@ def _should_run(site: dict):
53
53
 
54
54
  logRequirements(site, model=MODEL, term=TERM_ID,
55
55
  has_all_measurements=has_all_measurements,
56
- measurements=';'.join([m.get('term', {}).get('@id') for m in measurements]))
56
+ measurement_ids=log_blank_nodes_id(measurements))
57
57
 
58
58
  should_run = all([has_all_measurements])
59
59
  logShouldRun(site, MODEL, TERM_ID, should_run)
@@ -18,9 +18,6 @@ REQUIREMENTS = {
18
18
  RETURNS = {
19
19
  "`true` if the `Cycle` was irrigated, `false` otherwise": ""
20
20
  }
21
- LOOKUPS = {
22
- "waterRegime": "irrigated"
23
- }
24
21
  MODEL_KEY = 'irrigated'
25
22
 
26
23
 
@@ -67,7 +67,7 @@ def _should_run(cycle: dict):
67
67
  def _get_measurement_content(term_id: str):
68
68
  return most_relevant_measurement_value(measurements, term_id, end_date)
69
69
 
70
- histosol = _get_measurement_content('histosol') or 0
70
+ histosol = _get_measurement_content('histosol')
71
71
  eco_climate_zone = _get_measurement_content('ecoClimateZone')
72
72
  organic_soil_factor = _get_CO2_factor(eco_climate_zone, site_type) if eco_climate_zone else 0
73
73
  land_occupation = land_occupation_per_ha(MODEL, TERM_ID, cycle)
@@ -77,7 +77,7 @@ def _should_run(cycle: dict):
77
77
  land_occupation=land_occupation,
78
78
  histosol=histosol)
79
79
 
80
- should_run = all([organic_soil_factor, land_occupation])
80
+ should_run = all([organic_soil_factor, land_occupation, histosol is not None])
81
81
  logShouldRun(cycle, MODEL, TERM_ID, should_run, methodTier=TIER)
82
82
  return should_run, histosol, organic_soil_factor, land_occupation
83
83
 
@@ -43,7 +43,7 @@ def _run(no3: float, nh3: float, nox: float):
43
43
 
44
44
 
45
45
  def _should_run(cycle: dict):
46
- nh3_n, no3_n, nox_n = get_nh3_no3_nox_to_n(cycle, NH3_TERM_ID, NO3_TERM_ID, NOX_TERM_ID)
46
+ nh3_n, no3_n, nox_n = get_nh3_no3_nox_to_n(cycle, NH3_TERM_ID, NO3_TERM_ID, NOX_TERM_ID, allow_none=False)
47
47
  term_type_complete = _is_term_type_complete(cycle, TermTermType.CROPRESIDUE)
48
48
 
49
49
  logRequirements(cycle, model=MODEL, term=TERM_ID,
@@ -52,7 +52,7 @@ def _should_run(cycle: dict):
52
52
  nox_n=nox_n,
53
53
  term_type_cropResidue_complete=term_type_complete)
54
54
 
55
- should_run = all([no3_n, nh3_n, nox_n, term_type_complete])
55
+ should_run = all([no3_n is not None, nh3_n is not None, nox_n is not None, term_type_complete])
56
56
  logShouldRun(cycle, MODEL, TERM_ID, should_run, methodTier=TIER)
57
57
  return should_run, no3_n, nh3_n, nox_n
58
58