hestia-earth-models 0.70.1__py3-none-any.whl → 0.70.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. hestia_earth/models/cml2001Baseline/resourceUseMineralsAndMetalsDuringCycle.py +2 -1
  2. hestia_earth/models/config/Cycle.json +68 -0
  3. hestia_earth/models/config/Site.json +8 -0
  4. hestia_earth/models/cycle/practice/landCover.py +181 -0
  5. hestia_earth/models/emepEea2019/nh3ToAirExcreta.py +1 -1
  6. hestia_earth/models/hestia/excretaKgMass.py +1 -1
  7. hestia_earth/models/hestia/management.py +15 -113
  8. hestia_earth/models/hestia/pToSurfaceWaterAquacultureSystems.py +148 -0
  9. hestia_earth/models/hestia/soilMeasurement.py +1 -1
  10. hestia_earth/models/ipcc2019/ch4ToAirFloodedRice.py +8 -6
  11. hestia_earth/models/ipcc2019/ch4ToAirOrganicSoilCultivation.py +270 -0
  12. hestia_earth/models/ipcc2019/co2ToAirAboveGroundBiomassStockChange.py +0 -3
  13. hestia_earth/models/ipcc2019/co2ToAirBelowGroundBiomassStockChange.py +0 -3
  14. hestia_earth/models/ipcc2019/co2ToAirCarbonStockChange_utils.py +57 -43
  15. hestia_earth/models/ipcc2019/co2ToAirLimeHydrolysis.py +7 -5
  16. hestia_earth/models/ipcc2019/co2ToAirOrganicSoilCultivation.py +215 -0
  17. hestia_earth/models/ipcc2019/co2ToAirSoilOrganicCarbonStockChange.py +0 -3
  18. hestia_earth/models/ipcc2019/n2OToAirOrganicSoilCultivationDirect.py +161 -0
  19. hestia_earth/models/ipcc2019/no3ToGroundwaterExcreta.py +1 -1
  20. hestia_earth/models/ipcc2019/organicCarbonPerHa_tier_2.py +15 -4
  21. hestia_earth/models/ipcc2019/organicSoilCultivation_utils.py +159 -0
  22. hestia_earth/models/mocking/search-results.json +713 -705
  23. hestia_earth/models/pooreNemecek2018/excretaKgN.py +3 -1
  24. hestia_earth/models/site/grouped_measurement.py +132 -0
  25. hestia_earth/models/utils/__init__.py +4 -3
  26. hestia_earth/models/utils/blank_node.py +40 -11
  27. hestia_earth/models/utils/constant.py +26 -20
  28. hestia_earth/models/utils/excretaManagement.py +2 -2
  29. hestia_earth/models/utils/product.py +39 -1
  30. hestia_earth/models/utils/property.py +25 -12
  31. hestia_earth/models/version.py +1 -1
  32. {hestia_earth_models-0.70.1.dist-info → hestia_earth_models-0.70.3.dist-info}/METADATA +2 -2
  33. {hestia_earth_models-0.70.1.dist-info → hestia_earth_models-0.70.3.dist-info}/RECORD +49 -36
  34. tests/models/cycle/practice/test_landCover.py +27 -0
  35. tests/models/hestia/test_feedConversionRatio.py +2 -3
  36. tests/models/hestia/test_pToSurfaceWaterAquacultureSystems.py +56 -0
  37. tests/models/hestia/test_soilMeasurement.py +11 -19
  38. tests/models/ipcc2019/test_ch4ToAirEntericFermentation.py +4 -7
  39. tests/models/ipcc2019/test_ch4ToAirOrganicSoilCultivation.py +61 -0
  40. tests/models/ipcc2019/test_co2ToAirAboveGroundBiomassStockChange.py +11 -9
  41. tests/models/ipcc2019/test_co2ToAirBelowGroundBiomassStockChange.py +10 -8
  42. tests/models/ipcc2019/test_co2ToAirLimeHydrolysis.py +1 -1
  43. tests/models/ipcc2019/test_co2ToAirOrganicSoilCultivation.py +62 -0
  44. tests/models/ipcc2019/test_co2ToAirSoilOrganicCarbonStockChange.py +11 -9
  45. tests/models/ipcc2019/test_n2OToAirOrganicSoilCultivationDirect.py +61 -0
  46. tests/models/site/test_grouped_measurement.py +20 -0
  47. {hestia_earth_models-0.70.1.dist-info → hestia_earth_models-0.70.3.dist-info}/LICENSE +0 -0
  48. {hestia_earth_models-0.70.1.dist-info → hestia_earth_models-0.70.3.dist-info}/WHEEL +0 -0
  49. {hestia_earth_models-0.70.1.dist-info → hestia_earth_models-0.70.3.dist-info}/top_level.txt +0 -0
@@ -6,7 +6,8 @@ from hestia_earth.utils.model import filter_list_term_type
6
6
  from hestia_earth.utils.tools import list_sum
7
7
 
8
8
  from hestia_earth.models.log import logRequirements, logShouldRun
9
- from hestia_earth.models.utils import _filter_list_term_unit, Units
9
+ from hestia_earth.models.utils import _filter_list_term_unit
10
+ from hestia_earth.models.utils.constant import Units
10
11
  from hestia_earth.models.utils.blank_node import _sum_nodes_value
11
12
  from hestia_earth.models.utils.indicator import _new_indicator
12
13
  from . import MODEL
@@ -182,6 +182,14 @@
182
182
  "mergeStrategy": "list",
183
183
  "stage": 1
184
184
  },
185
+ {
186
+ "key": "practices",
187
+ "model": "cycle",
188
+ "value": "practice.landCover",
189
+ "runStrategy": "always",
190
+ "mergeStrategy": "list",
191
+ "stage": 1
192
+ },
185
193
  {
186
194
  "key": "products",
187
195
  "model": "hestia",
@@ -1759,6 +1767,51 @@
1759
1767
  },
1760
1768
  "stage": 2
1761
1769
  },
1770
+ {
1771
+ "key": "emissions",
1772
+ "model": "ipcc2019",
1773
+ "value": "ch4ToAirOrganicSoilCultivation",
1774
+ "runStrategy": "add_blank_node_if_missing",
1775
+ "runArgs": {
1776
+ "runNonMeasured": true,
1777
+ "runNonAddedTerm": true
1778
+ },
1779
+ "mergeStrategy": "list",
1780
+ "mergeArgs": {
1781
+ "replaceThreshold": ["value", 0.01]
1782
+ },
1783
+ "stage": 2
1784
+ },
1785
+ {
1786
+ "key": "emissions",
1787
+ "model": "ipcc2019",
1788
+ "value": "co2ToAirOrganicSoilCultivation",
1789
+ "runStrategy": "add_blank_node_if_missing",
1790
+ "runArgs": {
1791
+ "runNonMeasured": true,
1792
+ "runNonAddedTerm": true
1793
+ },
1794
+ "mergeStrategy": "list",
1795
+ "mergeArgs": {
1796
+ "replaceThreshold": ["value", 0.01]
1797
+ },
1798
+ "stage": 2
1799
+ },
1800
+ {
1801
+ "key": "emissions",
1802
+ "model": "ipcc2019",
1803
+ "value": "n2OToAirOrganicSoilCultivationDirect",
1804
+ "runStrategy": "add_blank_node_if_missing",
1805
+ "runArgs": {
1806
+ "runNonMeasured": true,
1807
+ "runNonAddedTerm": true
1808
+ },
1809
+ "mergeStrategy": "list",
1810
+ "mergeArgs": {
1811
+ "replaceThreshold": ["value", 0.01]
1812
+ },
1813
+ "stage": 2
1814
+ },
1762
1815
  {
1763
1816
  "key": "emissions",
1764
1817
  "model": "ipcc2019",
@@ -2178,6 +2231,21 @@
2178
2231
  "replaceThreshold": ["value", 0.01]
2179
2232
  },
2180
2233
  "stage": 2
2234
+ },
2235
+ {
2236
+ "key": "emissions",
2237
+ "model": "hestia",
2238
+ "value": "pToSurfaceWaterAquacultureSystems",
2239
+ "runStrategy": "add_blank_node_if_missing",
2240
+ "runArgs": {
2241
+ "runNonMeasured": true,
2242
+ "runNonAddedTerm": true
2243
+ },
2244
+ "mergeStrategy": "list",
2245
+ "mergeArgs": {
2246
+ "replaceThreshold": ["value", 0.01]
2247
+ },
2248
+ "stage": 2
2181
2249
  }
2182
2250
  ],
2183
2251
  {
@@ -400,6 +400,14 @@
400
400
  "mergeStrategy": "list",
401
401
  "stage": 1
402
402
  },
403
+ {
404
+ "key": "measurements",
405
+ "model": "site",
406
+ "value": "grouped_measurement",
407
+ "runStrategy": "always",
408
+ "mergeStrategy": "list",
409
+ "stage": 1
410
+ },
403
411
  {
404
412
  "key": "measurements",
405
413
  "model": "hestia",
@@ -0,0 +1,181 @@
1
+ from functools import reduce
2
+ from hestia_earth.schema import TermTermType, SiteSiteType
3
+ from hestia_earth.utils.model import filter_list_term_type
4
+ from hestia_earth.utils.tools import non_empty_list, flatten, list_sum
5
+
6
+ from hestia_earth.models.log import logRequirements, logShouldRun
7
+ from hestia_earth.models.utils import _omit, _include
8
+ from hestia_earth.models.utils.practice import _new_practice
9
+ from hestia_earth.models.utils.term import get_lookup_value
10
+ from hestia_earth.models.utils.blank_node import condense_nodes
11
+ from hestia_earth.models.utils.crop import get_landCover_term_id
12
+ from .. import MODEL
13
+
14
+ REQUIREMENTS = {
15
+ "Cycle": {
16
+ "endDate": "",
17
+ "products": [
18
+ {
19
+ "@type": "Product",
20
+ "term.termType": ["crop", "forage"],
21
+ "optional": {
22
+ "startDate": "",
23
+ "endDate": ""
24
+ }
25
+ }
26
+ ],
27
+ "site": {
28
+ "@type": "Site",
29
+ "siteType": "cropland"
30
+ },
31
+ "none": {
32
+ "practices": [{"@type": "Practice", "term.termType": "landCover"}]
33
+ },
34
+ "optional": {
35
+ "startDate": ""
36
+ }
37
+ }
38
+ }
39
+ RETURNS = {
40
+ "Practice": [{
41
+ "term.termType": "landCover",
42
+ "value": "",
43
+ "endDate": "",
44
+ "startDate": ""
45
+ }]
46
+ }
47
+ LOOKUPS = {
48
+ "crop": ["landCoverTermId", "maximumCycleDuration"],
49
+ "forage": ["landCoverTermId"],
50
+ "property": ["GAP_FILL_TO_MANAGEMENT", "CALCULATE_TOTAL_LAND_COVER_SHARE_SEPARATELY"]
51
+ }
52
+ MODEL_KEY = 'landCover'
53
+
54
+
55
+ def practice(data: dict):
56
+ node = _new_practice(data.get('id'))
57
+ node['value'] = [data['value']]
58
+ node['endDate'] = data['endDate']
59
+ if data.get('startDate'):
60
+ node['startDate'] = data['startDate']
61
+ if data.get('properties'):
62
+ node['properties'] = data['properties']
63
+ return node
64
+
65
+
66
+ def _should_gap_fill(term: dict):
67
+ value = get_lookup_value(lookup_term=term, column='GAP_FILL_TO_MANAGEMENT')
68
+ return bool(value)
69
+
70
+
71
+ def _filter_properties(blank_node: dict):
72
+ properties = list(filter(lambda p: _should_gap_fill(p.get('term', {})), blank_node.get('properties', [])))
73
+ return _omit(blank_node, ['properties']) | ({'properties': properties} if properties else {})
74
+
75
+
76
+ def _map_to_value(value: dict):
77
+ return {
78
+ 'id': value.get('term', {}).get('@id'),
79
+ 'value': value.get('value'),
80
+ 'startDate': value.get('startDate'),
81
+ 'endDate': value.get('endDate'),
82
+ 'properties': value.get('properties')
83
+ }
84
+
85
+
86
+ def _copy_item_if_exists(source: dict, keys: list[str] = None, dest: dict = None) -> dict:
87
+ return reduce(lambda p, c: p | ({c: source[c]} if source.get(c) else {}), keys or [], dest or {})
88
+
89
+
90
+ def _run(cycle: dict, products: list, total: float):
91
+ # remove any properties that should not get gap-filled
92
+ products = list(map(_filter_properties, products))
93
+
94
+ nodes = [
95
+ _map_to_value(_include(cycle, ["startDate", "endDate"]) | _copy_item_if_exists(
96
+ source=product,
97
+ keys=['properties', 'startDate', 'endDate'],
98
+ dest={
99
+ "term": {'@id': product.get('land-cover-id')},
100
+ "value": round((100 - total) / len(products), 2)
101
+ }
102
+ ))
103
+ for product in products
104
+ ]
105
+
106
+ return condense_nodes(list(map(practice, nodes)))
107
+
108
+
109
+ def _should_group_landCover(term: dict):
110
+ value = get_lookup_value(lookup_term=term, column='CALCULATE_TOTAL_LAND_COVER_SHARE_SEPARATELY')
111
+ return bool(value)
112
+
113
+
114
+ def _has_prop_grouped_with_landCover(product: dict):
115
+ return bool(
116
+ next((
117
+ p
118
+ for p in product.get('properties', [])
119
+ if _should_group_landCover(p.get('term', {}))
120
+ ), None)
121
+ )
122
+
123
+
124
+ def _product_wit_landCover_id(product: dict):
125
+ landCover_id = get_landCover_term_id(product.get('term', {}))
126
+ return product | {'land-cover-id': landCover_id} if landCover_id else None
127
+
128
+
129
+ def _should_run(cycle: dict):
130
+ is_cropland = cycle.get('site', {}).get('siteType') == SiteSiteType.CROPLAND.value
131
+
132
+ practices = filter_list_term_type(cycle.get('practices', []), TermTermType.LANDCOVER)
133
+ # split practices with properties that group with landCover
134
+ practices_max_100 = [
135
+ p for p in practices if _has_prop_grouped_with_landCover(p)
136
+ ]
137
+ total_practices_max_100 = list_sum([
138
+ list_sum(p.get('value', []))
139
+ for p in practices_max_100
140
+ ])
141
+ practices_without_grouped_props = [
142
+ p for p in practices if not _has_prop_grouped_with_landCover(p)
143
+ ]
144
+
145
+ products = filter_list_term_type(cycle.get('products', []), [TermTermType.CROP, TermTermType.FORAGE])
146
+ # only take products with a matching landCover term
147
+ products = non_empty_list(map(_product_wit_landCover_id, products))
148
+
149
+ # Products that can sum up to 100% => run if total is below 100%
150
+ products_max_100 = [
151
+ p for p in products
152
+ if _has_prop_grouped_with_landCover(p)
153
+ ] if total_practices_max_100 < 100 else []
154
+
155
+ # Products that must sum up to 100% => can not run practices already exist as already 100%
156
+ products_is_100 = [
157
+ p for p in products
158
+ if not _has_prop_grouped_with_landCover(p)
159
+ ] if not practices_without_grouped_props else []
160
+
161
+ has_crop_forage_products = bool(products_max_100 + products_is_100)
162
+
163
+ logRequirements(cycle, model=MODEL, model_key=MODEL_KEY,
164
+ is_cropland=is_cropland,
165
+ has_crop_forage_products=has_crop_forage_products)
166
+
167
+ should_run = all([is_cropland, has_crop_forage_products])
168
+ logShouldRun(cycle, MODEL, None, should_run, model_key=MODEL_KEY)
169
+
170
+ return should_run, [
171
+ (products_max_100, total_practices_max_100),
172
+ (products_is_100, 0)
173
+ ]
174
+
175
+
176
+ def run(cycle: dict):
177
+ should_run, products_list = _should_run(cycle)
178
+ return flatten([
179
+ _run(cycle, products, total)
180
+ for products, total in products_list if products
181
+ ]) if should_run else []
@@ -46,7 +46,7 @@ def _run(excreta_EF_input: float):
46
46
  def _should_run(cycle: dict):
47
47
  excreta_complete = _is_term_type_complete(cycle, TermTermType.EXCRETA)
48
48
  excreta_EF_input = get_excreta_inputs_with_factor(
49
- cycle, f"{list(LOOKUPS.keys())[0]}.csv", excreta_convertion_func=total_excreta_tan, model=MODEL, term=TERM_ID
49
+ cycle, f"{list(LOOKUPS.keys())[0]}.csv", excreta_conversion_func=total_excreta_tan, model=MODEL, term=TERM_ID
50
50
  )
51
51
 
52
52
  logRequirements(cycle, model=MODEL, term=TERM_ID,
@@ -70,7 +70,7 @@ def _run_product(cycle: dict, product_term_id: str):
70
70
  # convert to 1kg first, then apply ratio to current value
71
71
  term = download_term(product_term_id, TermTermType.EXCRETA)
72
72
  product = {
73
- 'term': term,
73
+ 'term': term or {},
74
74
  'value': [1]
75
75
  }
76
76
 
@@ -1,6 +1,5 @@
1
1
  from typing import List
2
2
  from datetime import timedelta, datetime
3
- from functools import reduce
4
3
  from hestia_earth.schema import SchemaType, TermTermType, SiteSiteType, COMPLETENESS_MAPPING
5
4
  from hestia_earth.utils.lookup import column_name, get_table_value, download_lookup
6
5
  from hestia_earth.utils.model import filter_list_term_type
@@ -8,7 +7,7 @@ from hestia_earth.utils.tools import safe_parse_float, flatten
8
7
  from hestia_earth.utils.blank_node import get_node_value
9
8
 
10
9
  from hestia_earth.models.log import logRequirements, logShouldRun, log_as_table
11
- from hestia_earth.models.utils import _include, _omit, group_by
10
+ from hestia_earth.models.utils import _include, group_by
12
11
  from hestia_earth.models.utils.management import _new_management
13
12
  from hestia_earth.models.utils.term import get_lookup_value
14
13
  from hestia_earth.models.utils.blank_node import condense_nodes, DatestrFormat, _gapfill_datestr, DatestrGapfillMode
@@ -24,20 +23,16 @@ REQUIREMENTS = {
24
23
  "Cycle": [{
25
24
  "@type": "Cycle",
26
25
  "endDate": "",
27
- "products": [
28
- {
29
- "@type": "Product",
30
- "term.termType": ["crop", "forage", "landCover"]
31
- }
32
- ],
33
26
  "practices": [
34
27
  {
28
+ "@type": "Practice",
35
29
  "term.termType": [
36
30
  "waterRegime",
37
31
  "tillage",
38
32
  "cropResidueManagement",
39
33
  "landUseManagement",
40
- "system"
34
+ "system",
35
+ "landCover"
41
36
  ],
42
37
  "value": ""
43
38
  }
@@ -62,7 +57,6 @@ REQUIREMENTS = {
62
57
  }
63
58
  RETURNS = {
64
59
  "Management": [{
65
- "@type": "Management",
66
60
  "term.termType": [
67
61
  "landCover", "waterRegime", "tillage", "cropResidueManagement", "landUseManagement", "system"
68
62
  ],
@@ -78,7 +72,7 @@ LOOKUPS = {
78
72
  "organicFertiliser": "ANIMAL_MANURE",
79
73
  "soilAmendment": "PRACTICE_INCREASING_C_INPUT",
80
74
  "landUseManagement": "GAP_FILL_TO_MANAGEMENT",
81
- "property": ["GAP_FILL_TO_MANAGEMENT", "CALCULATE_TOTAL_LAND_COVER_SHARE_SEPARATELY"]
75
+ "property": "GAP_FILL_TO_MANAGEMENT"
82
76
  }
83
77
  MODEL_KEY = 'management'
84
78
 
@@ -87,7 +81,8 @@ _PRACTICES_TERM_TYPES = [
87
81
  TermTermType.TILLAGE,
88
82
  TermTermType.CROPRESIDUEMANAGEMENT,
89
83
  TermTermType.LANDUSEMANAGEMENT,
90
- TermTermType.SYSTEM
84
+ TermTermType.SYSTEM,
85
+ TermTermType.LANDCOVER
91
86
  ]
92
87
  _PRACTICES_COMPLETENESS_MAPPING = COMPLETENESS_MAPPING.get(SchemaType.PRACTICE.value, {})
93
88
  _ANIMAL_MANURE_USED_TERM_ID = "animalManureUsed"
@@ -125,7 +120,6 @@ _INPUT_RULES = {
125
120
  _SKIP_LAND_COVER_SITE_TYPES = [
126
121
  SiteSiteType.CROPLAND.value
127
122
  ]
128
- _CYCLE_DATE_TERM_TYPES = {TermTermType.CROP.value, TermTermType.FORAGE.value}
129
123
 
130
124
 
131
125
  def management(data: dict):
@@ -147,7 +141,7 @@ def _get_cycle_duration(cycle: dict, land_cover_id: str):
147
141
  land_cover_id,
148
142
  column_name('maximumCycleDuration')
149
143
  ), default=None)
150
- return cycle_duration or (lookup_value - 1 if lookup_value else None)
144
+ return cycle_duration or lookup_value
151
145
 
152
146
 
153
147
  def _gap_filled_date_only_str(date_str: str, mode: str = DatestrGapfillMode.END) -> str:
@@ -165,11 +159,9 @@ def _gap_filled_start_date(land_cover_id: str, end_date: str, cycle: dict) -> di
165
159
  """If possible, gap-fill the startDate based on the endDate - cycleDuration"""
166
160
  cycle_duration = _get_cycle_duration(cycle, land_cover_id)
167
161
  return {
168
- "startDate": max(
169
- _gap_filled_date_obj(end_date) - timedelta(days=cycle_duration)
170
- if cycle_duration else datetime.fromtimestamp(0),
171
- _gap_filled_date_obj(cycle.get("startDate"), mode=DatestrGapfillMode.START)
172
- if cycle.get("startDate") else datetime.fromtimestamp(0)
162
+ "startDate": (
163
+ _gap_filled_date_obj(cycle.get("startDate"), mode=DatestrGapfillMode.START) if cycle.get("startDate") else
164
+ _gap_filled_date_obj(end_date) - timedelta(days=cycle_duration - 1)
173
165
  )
174
166
  } if any([cycle_duration, cycle.get("startDate")]) else {}
175
167
 
@@ -190,11 +182,6 @@ def _should_gap_fill(term: dict):
190
182
  return bool(value)
191
183
 
192
184
 
193
- def _filter_properties(blank_node: dict):
194
- properties = list(filter(lambda p: _should_gap_fill(p.get('term', {})), blank_node.get('properties', [])))
195
- return _omit(blank_node, ['properties']) | ({'properties': properties} if properties else {})
196
-
197
-
198
185
  def _map_to_value(value: dict):
199
186
  return {
200
187
  'id': value.get('term', {}).get('@id'),
@@ -209,10 +196,6 @@ def _extract_node_value(node: dict) -> dict:
209
196
  return node | {'value': get_node_value(node)}
210
197
 
211
198
 
212
- def _copy_item_if_exists(source: dict, keys: list[str] = None, dest: dict = None) -> dict:
213
- return reduce(lambda p, c: p | ({c: source[c]} if source.get(c) else {}), keys or [], dest or {})
214
-
215
-
216
199
  def _get_relevant_items(cycle: dict, item_name: str, term_types: List[TermTermType], completeness_mapping: dict = {}):
217
200
  """
218
201
  Get items from the list of cycles with any of the relevant terms.
@@ -280,84 +263,6 @@ def _run_from_siteType(site: dict, cycle: dict):
280
263
  }] if should_run else []
281
264
 
282
265
 
283
- def _run_products(cycle: dict, products: list, total_products: int = None, use_cycle_dates: bool = False):
284
- default_dates = _include_with_date_gap_fill(cycle, ["startDate", "endDate"])
285
- return [
286
- _map_to_value(default_dates | _copy_item_if_exists(
287
- source=product,
288
- keys=['properties', 'startDate', 'endDate'],
289
- dest={
290
- "term": {'@id': get_landCover_term_id(product.get('term', {}))},
291
- "value": round(100 / (total_products or len(products)), 2)
292
- }
293
- ) | (
294
- default_dates if use_cycle_dates or product.get("term", {}).get("termType") in _CYCLE_DATE_TERM_TYPES
295
- else {}
296
- ))
297
- for product in products
298
- ]
299
-
300
-
301
- def _run_from_landCover(cycle: dict, crop_forage_products: list):
302
- """
303
- Copy landCover items, and include crop/forage landCover items with properties to count in ratio.
304
- """
305
- land_cover_products = [
306
- _map_to_value(_extract_node_value(
307
- _include_with_date_gap_fill(
308
- value=product,
309
- keys=["term", "value", "startDate", "endDate", "properties"]
310
- )
311
- )) for product in _get_relevant_items(
312
- cycle=cycle,
313
- item_name="products",
314
- term_types=[TermTermType.LANDCOVER]
315
- )
316
- ]
317
- return land_cover_products + _run_products(
318
- cycle,
319
- crop_forage_products,
320
- total_products=len(crop_forage_products) + len(land_cover_products),
321
- use_cycle_dates=True
322
- )
323
-
324
-
325
- def _should_group_landCover(term: dict):
326
- value = get_lookup_value(lookup_term=term, column='CALCULATE_TOTAL_LAND_COVER_SHARE_SEPARATELY')
327
- return bool(value)
328
-
329
-
330
- def _has_prop_grouped_with_landCover(product: dict):
331
- return bool(
332
- next((
333
- p
334
- for p in product.get('properties', [])
335
- if _should_group_landCover(p.get('term', {}))
336
- ), None)
337
- )
338
-
339
-
340
- def _run_from_crop_forage(cycle: dict, site: dict):
341
- products = _get_relevant_items(
342
- cycle=cycle,
343
- item_name="products",
344
- term_types=[TermTermType.CROP, TermTermType.FORAGE]
345
- ) if site.get("siteType", "") == SiteSiteType.CROPLAND.value else []
346
- # only take products with a matching landCover term
347
- products = [p for p in products if get_landCover_term_id(p.get('term', {}))]
348
- # remove any properties that should not get gap-filled
349
- products = list(map(_filter_properties, products))
350
-
351
- # split products with properties that group with landCover
352
- products_with_gap_filled_props = [p for p in products if _has_prop_grouped_with_landCover(p)]
353
- products_without_gap_filled_props = [p for p in products if not _has_prop_grouped_with_landCover(p)]
354
-
355
- return _run_from_landCover(
356
- cycle=cycle,
357
- crop_forage_products=products_with_gap_filled_props
358
- ) + _run_products(cycle, products_without_gap_filled_props, use_cycle_dates=False)
359
-
360
-
361
266
  def _should_run_practice(practice: dict):
362
267
  """
363
268
  Include only landUseManagement practices where GAP_FILL_TO_MANAGEMENT = True
@@ -371,7 +276,7 @@ def _run_from_practices(cycle: dict):
371
276
  _extract_node_value(
372
277
  _include_with_date_gap_fill(
373
278
  value=practice,
374
- keys=["term", "value", "startDate", "endDate"]
279
+ keys=["term", "value", "startDate", "endDate", "properties"]
375
280
  )
376
281
  ) for practice in _get_relevant_items(
377
282
  cycle=cycle,
@@ -380,18 +285,16 @@ def _run_from_practices(cycle: dict):
380
285
  completeness_mapping=_PRACTICES_COMPLETENESS_MAPPING
381
286
  )
382
287
  ]
383
- practices = list(map(_map_to_value, filter(_should_run_practice, practices)))
384
- return practices
288
+ return list(map(_map_to_value, filter(_should_run_practice, practices)))
385
289
 
386
290
 
387
291
  def _run_cycle(site: dict, cycle: dict):
388
292
  inputs = _run_from_inputs(site, cycle)
389
- products = _run_from_crop_forage(site=site, cycle=cycle)
390
293
  site_types = _run_from_siteType(site=site, cycle=cycle)
391
294
  practices = _run_from_practices(cycle)
392
295
  return [
393
296
  node | {'cycle-id': cycle.get('@id')}
394
- for node in inputs + products + site_types + practices
297
+ for node in inputs + site_types + practices
395
298
  ]
396
299
 
397
300
 
@@ -411,5 +314,4 @@ def run(site: dict):
411
314
  )
412
315
  logShouldRun(site, MODEL, id, True, model_key=MODEL_KEY)
413
316
 
414
- management_nodes = condense_nodes(list(map(management, nodes)))
415
- return management_nodes
317
+ return condense_nodes(list(map(management, nodes)))