hestia-earth-models 0.65.4__py3-none-any.whl → 0.65.6__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 (52) hide show
  1. hestia_earth/models/agribalyse2016/fuelElectricity.py +40 -24
  2. hestia_earth/models/aware/scarcityWeightedWaterUse.py +1 -1
  3. hestia_earth/models/chaudharyBrooks2018/damageToTerrestrialEcosystemsLandOccupation.py +1 -1
  4. hestia_earth/models/chaudharyBrooks2018/damageToTerrestrialEcosystemsLandTransformation.py +1 -1
  5. hestia_earth/models/chaudharyBrooks2018/damageToTerrestrialEcosystemsTotalLandUseEffects.py +1 -1
  6. hestia_earth/models/cml2001Baseline/abioticResourceDepletionFossilFuels.py +9 -5
  7. hestia_earth/models/cycle/completeness/electricityFuel.py +4 -2
  8. hestia_earth/models/geospatialDatabase/precipitationAnnual.py +2 -2
  9. hestia_earth/models/geospatialDatabase/precipitationLongTermAnnualMean.py +2 -2
  10. hestia_earth/models/geospatialDatabase/precipitationMonthly.py +2 -2
  11. hestia_earth/models/geospatialDatabase/temperatureAnnual.py +2 -2
  12. hestia_earth/models/geospatialDatabase/temperatureLongTermAnnualMean.py +2 -2
  13. hestia_earth/models/geospatialDatabase/temperatureMonthly.py +2 -2
  14. hestia_earth/models/hestia/landCover.py +101 -68
  15. hestia_earth/models/hestia/landTransformation100YearAverageDuringCycle.py +49 -0
  16. hestia_earth/models/hestia/landTransformation20YearAverageDuringCycle.py +49 -0
  17. hestia_earth/models/hestia/resourceUse_utils.py +200 -0
  18. hestia_earth/models/hestia/seed_emissions.py +35 -21
  19. hestia_earth/models/hestia/utils.py +48 -0
  20. hestia_earth/models/impact_assessment/emissions.py +20 -5
  21. hestia_earth/models/ipcc2019/co2ToAirCarbonStockChange_utils.py +66 -28
  22. hestia_earth/models/ipcc2019/organicCarbonPerHa_tier_1_utils.py +26 -142
  23. hestia_earth/models/ipcc2019/organicCarbonPerHa_tier_2_utils.py +3 -3
  24. hestia_earth/models/ipcc2019/organicCarbonPerHa_utils.py +8 -5
  25. hestia_earth/models/linkedImpactAssessment/utils.py +3 -1
  26. hestia_earth/models/mocking/search-results.json +27 -4504
  27. hestia_earth/models/pooreNemecek2018/freshwaterWithdrawalsDuringCycle.py +4 -1
  28. hestia_earth/models/schererPfister2015/nErosionSoilFlux.py +23 -14
  29. hestia_earth/models/schererPfister2015/pErosionSoilFlux.py +23 -15
  30. hestia_earth/models/schererPfister2015/utils.py +3 -5
  31. hestia_earth/models/site/management.py +82 -22
  32. hestia_earth/models/utils/blank_node.py +28 -0
  33. hestia_earth/models/utils/crop.py +5 -1
  34. hestia_earth/models/utils/fuel.py +4 -1
  35. hestia_earth/models/utils/impact_assessment.py +7 -5
  36. hestia_earth/models/utils/pesticideAI.py +1 -0
  37. hestia_earth/models/version.py +1 -1
  38. {hestia_earth_models-0.65.4.dist-info → hestia_earth_models-0.65.6.dist-info}/METADATA +2 -2
  39. {hestia_earth_models-0.65.4.dist-info → hestia_earth_models-0.65.6.dist-info}/RECORD +52 -46
  40. tests/models/cml2001Baseline/test_abioticResourceDepletionFossilFuels.py +1 -1
  41. tests/models/hestia/test_landCover.py +2 -1
  42. tests/models/hestia/test_landTransformation100YearAverageDuringCycle.py +30 -0
  43. tests/models/hestia/test_landTransformation20YearAverageDuringCycle.py +31 -0
  44. tests/models/ipcc2019/test_co2ToAirAboveGroundBiomassStockChange.py +3 -1
  45. tests/models/ipcc2019/test_co2ToAirBelowGroundBiomassStockChange.py +3 -1
  46. tests/models/ipcc2019/test_co2ToAirSoilOrganicCarbonStockChange.py +3 -1
  47. tests/models/ipcc2019/test_organicCarbonPerHa.py +3 -2
  48. tests/models/ipcc2019/test_organicCarbonPerHa_tier_1_utils.py +15 -11
  49. tests/models/utils/test_blank_node.py +22 -7
  50. {hestia_earth_models-0.65.4.dist-info → hestia_earth_models-0.65.6.dist-info}/LICENSE +0 -0
  51. {hestia_earth_models-0.65.4.dist-info → hestia_earth_models-0.65.6.dist-info}/WHEEL +0 -0
  52. {hestia_earth_models-0.65.4.dist-info → hestia_earth_models-0.65.6.dist-info}/top_level.txt +0 -0
@@ -66,7 +66,10 @@ def _run(impact_assessment: dict, product: dict, irrigation: float):
66
66
  conveyancing = _get_conveyancing_efficiency(impact_assessment, product)
67
67
  # convert from m3 to litre
68
68
  value = convert_value_from_cycle(
69
- product, irrigation / conveyancing * 1000 if irrigation > 0 else 0, model=MODEL, term_id=TERM_ID
69
+ impact_assessment,
70
+ product,
71
+ irrigation / conveyancing * 1000 if irrigation > 0 else 0,
72
+ model=MODEL, term_id=TERM_ID
70
73
  )
71
74
  debugValues(impact_assessment, model=MODEL, term=TERM_ID,
72
75
  value=value)
@@ -1,28 +1,36 @@
1
1
  from hestia_earth.schema import EmissionMethodTier
2
+ from hestia_earth.utils.tools import list_sum
2
3
 
3
4
  from hestia_earth.models.log import debugValues, logRequirements, logShouldRun
4
5
  from hestia_earth.models.utils.emission import _new_emission
5
6
  from hestia_earth.models.utils.measurement import most_relevant_measurement_value
6
- from .utils import get_pcorr, get_p_ef_c1, get_ef_p_c2, get_practice_factor, get_water, calculate_R, calculate_A
7
+ from .utils import get_pcorr, get_p_ef_c1, get_ef_p_c2, get_practice_factor, get_water_input, calculate_R, calculate_A
7
8
  from . import MODEL
8
9
 
9
10
  REQUIREMENTS = {
10
11
  "Cycle": {
11
12
  "endDate": "",
12
- "inputs": [
13
- {"@type": "Input", "value": "", "term.termType": "water"}
14
- ],
13
+ "or": {
14
+ "inputs": [
15
+ {"@type": "Input", "value": "> 0", "term.termType": "water"}
16
+ ],
17
+ "site": {
18
+ "@type": "Site",
19
+ "measurements": [
20
+ {"@type": "Measurement", "value": "> 0", "term.@id": "precipitationAnnual"}
21
+ ]
22
+ }
23
+ },
15
24
  "site": {
16
25
  "@type": "Site",
17
26
  "country": {"@type": "Term", "termType": "region"},
18
27
  "measurements": [
19
- {"@type": "Measurement", "value": "", "term.@id": "nutrientLossToAquaticEnvironment"},
20
- {"@type": "Measurement", "value": "", "term.@id": "heavyWinterPrecipitation"},
21
- {"@type": "Measurement", "value": "", "term.@id": "totalNitrogenPerKgSoil"},
22
- {"@type": "Measurement", "value": "", "term.@id": "precipitationAnnual"},
23
- {"@type": "Measurement", "value": "", "term.@id": "erodibility"},
24
- {"@type": "Measurement", "value": "", "term.@id": "slopeLength"},
25
- {"@type": "Measurement", "value": "", "term.@id": "slope"}
28
+ {"@type": "Measurement", "value": "> 0", "term.@id": "nutrientLossToAquaticEnvironment"},
29
+ {"@type": "Measurement", "value": "> 0", "term.@id": "heavyWinterPrecipitation"},
30
+ {"@type": "Measurement", "value": "> 0", "term.@id": "totalNitrogenPerKgSoil"},
31
+ {"@type": "Measurement", "value": "> 0", "term.@id": "erodibility"},
32
+ {"@type": "Measurement", "value": "> 0", "term.@id": "slopeLength"},
33
+ {"@type": "Measurement", "value": "> 0", "term.@id": "slope"}
26
34
  ]
27
35
  }
28
36
  }
@@ -82,7 +90,7 @@ def _should_run(cycle: dict):
82
90
  heavy_winter_precipitation = _get_measurement_content('heavyWinterPrecipitation')
83
91
 
84
92
  precipitation = _get_measurement_content('precipitationAnnual')
85
- water = get_water(cycle, precipitation)
93
+ inputs_water = get_water_input(cycle)
86
94
 
87
95
  practice_factor = get_practice_factor(TERM_ID, site)
88
96
  pcorr = get_pcorr(slope / 100) if slope is not None else None
@@ -92,7 +100,7 @@ def _should_run(cycle: dict):
92
100
  list_of_contents_for_A = [
93
101
  practice_factor, erodibility, slope_length,
94
102
  pcorr, p_ef_c1, ef_p_c2]
95
- list_of_contents_for_R = [heavy_winter_precipitation, water]
103
+ list_of_contents_for_R = [heavy_winter_precipitation, list_sum([(inputs_water or 0)/10, precipitation or 0])]
96
104
  list_of_contents_for_value = [nla_environment, soil_nitrogen_content]
97
105
 
98
106
  logRequirements(cycle, model=MODEL, term=TERM_ID,
@@ -104,7 +112,8 @@ def _should_run(cycle: dict):
104
112
  p_ef_c1=p_ef_c1,
105
113
  ef_p_c2=ef_p_c2,
106
114
  heavy_winter_precipitation=heavy_winter_precipitation,
107
- water=water,
115
+ inputs_water=inputs_water,
116
+ precipitationAnnual=precipitation,
108
117
  nla_environment=nla_environment,
109
118
  soil_nitrogen_content=soil_nitrogen_content)
110
119
 
@@ -1,29 +1,36 @@
1
1
  from hestia_earth.schema import EmissionMethodTier
2
+ from hestia_earth.utils.tools import list_sum
2
3
 
3
4
  from hestia_earth.models.log import debugValues, logRequirements, logShouldRun
4
5
  from hestia_earth.models.utils.emission import _new_emission
5
6
  from hestia_earth.models.utils.measurement import most_relevant_measurement_value
6
- from .utils import get_pcorr, get_p_ef_c1, get_ef_p_c2, get_practice_factor, get_water, calculate_R, calculate_A
7
+ from .utils import get_pcorr, get_p_ef_c1, get_ef_p_c2, get_practice_factor, get_water_input, calculate_R, calculate_A
7
8
  from . import MODEL
8
9
 
9
10
  REQUIREMENTS = {
10
11
  "Cycle": {
11
12
  "endDate": "",
12
- "inputs": [
13
- {"@type": "Input", "value": "", "term.termType": "water"}
14
- ],
13
+ "or": {
14
+ "inputs": [
15
+ {"@type": "Input", "value": "> 0", "term.termType": "water"}
16
+ ],
17
+ "site": {
18
+ "@type": "Site",
19
+ "measurements": [
20
+ {"@type": "Measurement", "value": "> 0", "term.@id": "precipitationAnnual"}
21
+ ]
22
+ }
23
+ },
15
24
  "site": {
16
25
  "@type": "Site",
17
26
  "country": {"@type": "Term", "termType": "region"},
18
27
  "measurements": [
19
- {"@type": "Measurement", "value": "", "term.@id": "nutrientLossToAquaticEnvironment"},
20
- {"@type": "Measurement", "value": "", "term.@id": "heavyWinterPrecipitation"},
21
- {"@type": "Measurement", "value": "", "term.@id": "totalPhosphorusPerKgSoil"},
22
- {"@type": "Measurement", "value": "", "term.@id": "totalNitrogenPerKgSoil"},
23
- {"@type": "Measurement", "value": "", "term.@id": "precipitationAnnual"},
24
- {"@type": "Measurement", "value": "", "term.@id": "erodibility"},
25
- {"@type": "Measurement", "value": "", "term.@id": "slopeLength"},
26
- {"@type": "Measurement", "value": "", "term.@id": "slope"}
28
+ {"@type": "Measurement", "value": "> 0", "term.@id": "nutrientLossToAquaticEnvironment"},
29
+ {"@type": "Measurement", "value": "> 0", "term.@id": "heavyWinterPrecipitation"},
30
+ {"@type": "Measurement", "value": "> 0", "term.@id": "totalPhosphorusPerKgSoil"},
31
+ {"@type": "Measurement", "value": "> 0", "term.@id": "erodibility"},
32
+ {"@type": "Measurement", "value": "> 0", "term.@id": "slopeLength"},
33
+ {"@type": "Measurement", "value": "> 0", "term.@id": "slope"}
27
34
  ]
28
35
  }
29
36
  }
@@ -83,7 +90,7 @@ def _should_run(cycle: dict):
83
90
  heavy_winter_precipitation = _get_measurement_content('heavyWinterPrecipitation')
84
91
 
85
92
  precipitation = _get_measurement_content('precipitationAnnual')
86
- water = get_water(cycle, precipitation)
93
+ inputs_water = get_water_input(cycle)
87
94
 
88
95
  practice_factor = get_practice_factor(TERM_ID, site)
89
96
  pcorr = get_pcorr(slope / 100) if slope is not None else None
@@ -93,7 +100,7 @@ def _should_run(cycle: dict):
93
100
  list_of_contents_for_A = [
94
101
  practice_factor, erodibility, slope_length,
95
102
  pcorr, p_ef_c1, ef_p_c2]
96
- list_of_contents_for_R = [heavy_winter_precipitation, water]
103
+ list_of_contents_for_R = [heavy_winter_precipitation, list_sum([(inputs_water or 0)/10, precipitation or 0])]
97
104
  list_of_contents_for_value = [nla_environment, soil_phosphorus_content]
98
105
 
99
106
  logRequirements(cycle, model=MODEL, term=TERM_ID,
@@ -105,7 +112,8 @@ def _should_run(cycle: dict):
105
112
  p_ef_c1=p_ef_c1,
106
113
  ef_p_c2=ef_p_c2,
107
114
  heavy_winter_precipitation=heavy_winter_precipitation,
108
- water=water,
115
+ inputs_water=inputs_water,
116
+ precipitationAnnual=precipitation,
109
117
  nla_environment=nla_environment,
110
118
  soil_phosphorus_content=soil_phosphorus_content)
111
119
 
@@ -64,11 +64,9 @@ def get_ef_p_c2(term_id: str, cycle: dict):
64
64
  get_lookup_value(country, 'EF_P_C2', model=MODEL, term=term_id), None)
65
65
 
66
66
 
67
- def get_water(cycle: dict, precipitation: float):
68
- inputs = cycle.get('inputs', [])
69
- filter_irrigation = filter_list_term_type(inputs, TermTermType.WATER)
70
- irrigation = list_sum(get_total_value(filter_irrigation))
71
- return list_sum([irrigation/10, precipitation or 0])
67
+ def get_water_input(cycle: dict):
68
+ inputs_water = filter_list_term_type(cycle.get('inputs', []), TermTermType.WATER)
69
+ return list_sum(get_total_value(inputs_water), default=None)
72
70
 
73
71
 
74
72
  def calculate_R(heavy_winter_precipitation: bool, water: float):
@@ -10,11 +10,18 @@ tillage, cropResidueManagement and landUseManagement.
10
10
  All values are copied from the source node, except for crop and forage terms in which case the dates are copied from the
11
11
  cycle.
12
12
 
13
+ Where `startDate` is missing from landCover products, gap-filling is attempted using `endDate` - `maximumCycleDuration`.
14
+ This is the `endDate` of the `landCover` product.
15
+ This ensures no overlapping date ranges.
16
+ If both `endDate` and `startDate` are missing from the product, these will be gap-filled from the `Cycle`.
17
+
13
18
  When nodes are chronologically consecutive with "% area" or "boolean" units and the same term and value, they are
14
19
  condensed into a single node to aid readability.
15
20
  """
21
+ from datetime import timedelta, datetime
16
22
  from functools import reduce
17
23
  from hestia_earth.schema import TermTermType, SiteSiteType
24
+ from hestia_earth.utils.lookup import column_name, get_table_value, download_lookup
18
25
  from hestia_earth.utils.model import filter_list_term_type
19
26
  from hestia_earth.utils.tools import safe_parse_float, flatten
20
27
  from hestia_earth.utils.blank_node import get_node_value
@@ -23,12 +30,12 @@ from hestia_earth.models.log import logRequirements, logShouldRun, log_as_table
23
30
  from hestia_earth.models.utils import _include, _omit, group_by
24
31
  from hestia_earth.models.utils.management import _new_management
25
32
  from hestia_earth.models.utils.term import get_lookup_value
26
- from hestia_earth.models.utils.blank_node import condense_nodes
27
- from hestia_earth.models.utils.crop import get_landCover_term_id
33
+ from hestia_earth.models.utils.blank_node import condense_nodes, DatestrFormat, _gapfill_datestr, DatestrGapfillMode
28
34
  from hestia_earth.models.utils.site import (
29
35
  related_cycles, get_land_cover_term_id as get_landCover_term_id_from_site_type
30
36
  )
31
37
  from . import MODEL
38
+ from ..utils.crop import get_landCover_term_id
32
39
 
33
40
  REQUIREMENTS = {
34
41
  "Site": {
@@ -81,13 +88,13 @@ RETURNS = {
81
88
  }]
82
89
  }
83
90
  LOOKUPS = {
84
- "crop": ["landCoverTermId"],
91
+ "crop": ["landCoverTermId", "maximumCycleDuration"],
85
92
  "forage": ["landCoverTermId"],
86
93
  "inorganicFertiliser": "nitrogenContent",
87
94
  "organicFertiliser": "ANIMAL_MANURE",
88
95
  "soilAmendment": "PRACTICE_INCREASING_C_INPUT",
89
96
  "landUseManagement": "GAP_FILL_TO_MANAGEMENT",
90
- "property": "GAP_FILL_TO_MANAGEMENT"
97
+ "property": ["GAP_FILL_TO_MANAGEMENT", "CALCULATE_TOTAL_LAND_COVER_SHARE_SEPARATELY"]
91
98
  }
92
99
  MODEL_KEY = 'management'
93
100
 
@@ -126,19 +133,49 @@ _INPUT_RULES = {
126
133
  _SKIP_LAND_COVER_SITE_TYPES = [
127
134
  SiteSiteType.CROPLAND.value
128
135
  ]
136
+ _CYCLE_DATE_TERM_TYPES = {TermTermType.CROP.value, TermTermType.FORAGE.value}
129
137
 
130
138
 
131
139
  def management(data: dict):
132
140
  node = _new_management(data.get('id'))
133
141
  node['value'] = data['value']
134
- node['endDate'] = data['endDate']
142
+ node['endDate'] = _gap_filled_date_only_str(data['endDate'])
135
143
  if data.get('startDate'):
136
- node['startDate'] = data['startDate']
144
+ node['startDate'] = _gap_filled_date_only_str(date_str=data['startDate'], mode=DatestrGapfillMode.START)
137
145
  if data.get('properties'):
138
146
  node['properties'] = data['properties']
139
147
  return node
140
148
 
141
149
 
150
+ def _get_maximum_cycle_duration(land_cover_id: str):
151
+ lookup = download_lookup("crop.csv")
152
+ return safe_parse_float(
153
+ get_table_value(lookup, column_name('landCoverTermId'), land_cover_id, column_name('maximumCycleDuration'))
154
+ )
155
+
156
+
157
+ def _gap_filled_date_only_str(date_str: str, mode: str = DatestrGapfillMode.END) -> str:
158
+ return _gapfill_datestr(datestr=date_str, mode=mode)[:10]
159
+
160
+
161
+ def _gap_filled_date_obj(date_str: str, mode: str = DatestrGapfillMode.END) -> datetime:
162
+ return datetime.strptime(
163
+ _gap_filled_date_only_str(date_str=date_str, mode=mode),
164
+ DatestrFormat.YEAR_MONTH_DAY.value
165
+ )
166
+
167
+
168
+ def _gap_filled_start_date(land_cover_id: str, end_date: str, cycle: dict) -> str:
169
+ """If possible, gap-fill the startDate based on the endDate - maximumCycleDuration"""
170
+ maximum_cycle_duration = _get_maximum_cycle_duration(land_cover_id)
171
+ return max(
172
+ _gap_filled_date_obj(end_date) - timedelta(days=maximum_cycle_duration)
173
+ if maximum_cycle_duration else datetime.fromtimestamp(0),
174
+ _gap_filled_date_obj(cycle.get("startDate"), mode=DatestrGapfillMode.START)
175
+ if cycle.get("startDate") else datetime.fromtimestamp(0)
176
+ ) if any([maximum_cycle_duration, cycle.get("startDate")]) else None
177
+
178
+
142
179
  def _should_gap_fill(term: dict):
143
180
  value = get_lookup_value(lookup_term=term, column='GAP_FILL_TO_MANAGEMENT')
144
181
  return bool(value)
@@ -167,20 +204,27 @@ def _copy_item_if_exists(source: dict, keys: list[str] = None, dest: dict = None
167
204
  return reduce(lambda p, c: p | ({c: source[c]} if source.get(c) else {}), keys or [], dest or {})
168
205
 
169
206
 
170
- def _get_landCover_term_id(product: dict) -> str:
171
- term = product.get('term', {})
172
- return get_landCover_term_id(term, model=MODEL, term=term.get('@id'), model_key=MODEL_KEY)
173
-
174
-
175
207
  def _get_relevant_items(cycle: dict, item_name: str, relevant_terms: list):
176
208
  """
177
209
  Get items from the list of cycles with any of the relevant terms.
178
210
  Also adds dates from Cycle.
179
211
  """
180
- return [
181
- _include(cycle, ["startDate", "endDate"]) | item
212
+ items = [
213
+ _include(cycle, ["startDate", "endDate"]) |
214
+ _include(
215
+ {
216
+ "startDate": _gap_filled_start_date(
217
+ land_cover_id=get_landCover_term_id(item.get('term', {})),
218
+ end_date=item.get("endDate") if "endDate" in item else cycle.get("endDate", ""),
219
+ cycle=cycle
220
+ )
221
+ } if "startDate" not in item else {},
222
+ "startDate"
223
+ ) |
224
+ item
182
225
  for item in filter_list_term_type(cycle.get(item_name, []), relevant_terms)
183
226
  ]
227
+ return items
184
228
 
185
229
 
186
230
  def _process_rule(node: dict, term: dict) -> list:
@@ -228,11 +272,12 @@ def _run_products(cycle: dict, products: list, total_products: int = None, use_c
228
272
  source=product,
229
273
  keys=['properties', 'startDate', 'endDate'],
230
274
  dest={
231
- "term": {'@id': _get_landCover_term_id(product)},
275
+ "term": {'@id': get_landCover_term_id(product.get('term', {}))},
232
276
  "value": round(100 / (total_products or len(products)), 2)
233
277
  }
234
278
  ) | (
235
- default_dates if use_cycle_dates else {}
279
+ default_dates if use_cycle_dates or product.get("term", {}).get("termType") in _CYCLE_DATE_TERM_TYPES
280
+ else {}
236
281
  ))
237
282
  for product in products
238
283
  ]
@@ -242,7 +287,7 @@ def _run_from_landCover(cycle: dict, crop_forage_products: list):
242
287
  """
243
288
  Copy landCover items, and include crop/forage landCover items with properties to count in ratio.
244
289
  """
245
- products = [
290
+ land_cover_products = [
246
291
  _map_to_value(_extract_node_value(
247
292
  _include(
248
293
  value=product,
@@ -254,14 +299,29 @@ def _run_from_landCover(cycle: dict, crop_forage_products: list):
254
299
  relevant_terms=[TermTermType.LANDCOVER]
255
300
  )
256
301
  ]
257
- return products + _run_products(
302
+ return land_cover_products + _run_products(
258
303
  cycle,
259
304
  crop_forage_products,
260
- total_products=len(crop_forage_products) + len(products),
305
+ total_products=len(crop_forage_products) + len(land_cover_products),
261
306
  use_cycle_dates=True
262
307
  )
263
308
 
264
309
 
310
+ def _should_group_landCover(term: dict):
311
+ value = get_lookup_value(lookup_term=term, column='CALCULATE_TOTAL_LAND_COVER_SHARE_SEPARATELY')
312
+ return bool(value)
313
+
314
+
315
+ def _has_prop_grouped_with_landCover(product: dict):
316
+ return bool(
317
+ next((
318
+ p
319
+ for p in product.get('properties', [])
320
+ if _should_group_landCover(p.get('term', {}))
321
+ ), None)
322
+ )
323
+
324
+
265
325
  def _run_from_crop_forage(cycle: dict):
266
326
  products = _get_relevant_items(
267
327
  cycle=cycle,
@@ -269,13 +329,13 @@ def _run_from_crop_forage(cycle: dict):
269
329
  relevant_terms=[TermTermType.CROP, TermTermType.FORAGE]
270
330
  )
271
331
  # only take products with a matching landCover term
272
- products = list(filter(_get_landCover_term_id, products))
332
+ products = [p for p in products if get_landCover_term_id(p.get('term', {}))]
273
333
  # remove any properties that should not get gap-filled
274
334
  products = list(map(_filter_properties, products))
275
335
 
276
- # split products with properties and those without
277
- products_with_gap_filled_props = [p for p in products if p.get('properties')]
278
- products_without_gap_filled_props = [p for p in products if not p.get('properties')]
336
+ # split products with properties that group with landCover
337
+ products_with_gap_filled_props = [p for p in products if _has_prop_grouped_with_landCover(p)]
338
+ products_without_gap_filled_props = [p for p in products if not _has_prop_grouped_with_landCover(p)]
279
339
 
280
340
  return _run_from_landCover(
281
341
  cycle=cycle,
@@ -663,6 +663,7 @@ DATESTR_FORMAT_TO_EXPECTED_LENGTH = {
663
663
 
664
664
  DatestrGapfillMode = Enum("DatestrGapfillMode", [
665
665
  "START",
666
+ "MIDDLE",
666
667
  "END"
667
668
  ])
668
669
  """
@@ -738,8 +739,25 @@ def _gapfill_datestr_end(datestr: str, format: DatestrFormat) -> str:
738
739
  return datestr + completion_str[len(datestr):]
739
740
 
740
741
 
742
+ def _gapfill_datestr_middle(datestr: str, format: DatestrFormat) -> str:
743
+ """
744
+ Gap-fill an incomplete datestr with the middle value, halfway between the latest and earliest values.
745
+ """
746
+ start_date_obj = datetime.strptime(
747
+ _gapfill_datestr_start(datestr),
748
+ DatestrFormat.YEAR_MONTH_DAY_HOUR_MINUTE_SECOND.value
749
+ )
750
+ end_date_obj = datetime.strptime(
751
+ _gapfill_datestr_end(datestr, format=format),
752
+ DatestrFormat.YEAR_MONTH_DAY_HOUR_MINUTE_SECOND.value
753
+ )
754
+ middle_date = start_date_obj + (end_date_obj - start_date_obj) / 2
755
+ return datetime.strftime(middle_date, DatestrFormat.YEAR_MONTH_DAY_HOUR_MINUTE_SECOND.value)
756
+
757
+
741
758
  DATESTR_GAPFILL_MODE_TO_GAPFILL_FUNCTION = {
742
759
  DatestrGapfillMode.START: _gapfill_datestr_start,
760
+ DatestrGapfillMode.MIDDLE: _gapfill_datestr_middle,
743
761
  DatestrGapfillMode.END: _gapfill_datestr_end
744
762
  }
745
763
 
@@ -757,6 +775,16 @@ def _gapfill_datestr(datestr: str, mode: DatestrGapfillMode = DatestrGapfillMode
757
775
  return DATESTR_GAPFILL_MODE_TO_GAPFILL_FUNCTION[mode](_datestr, format) if should_run else _datestr
758
776
 
759
777
 
778
+ def _str_dates_match(date_str_one: str, date_str_two: str, mode=DatestrGapfillMode.END) -> bool:
779
+ """
780
+ Comparison of non-gap-filled string dates.
781
+ example: For end dates, '2010' would match '2010-12-31', but not '2010-01-01'
782
+ """
783
+ return (
784
+ _gapfill_datestr(datestr=date_str_one, mode=mode) == _gapfill_datestr(datestr=date_str_two, mode=mode)
785
+ )
786
+
787
+
760
788
  def _datetime_within_range(datetime: datetime, range: DatetimeRange) -> bool:
761
789
  """
762
790
  Determine whether or not a `datetime` falls within a `DatetimeRange`.
@@ -65,4 +65,8 @@ def valid_site_type(cycle: dict, include_permanent_pasture=False):
65
65
 
66
66
  def get_landCover_term_id(lookup_term: dict, **log_args) -> str:
67
67
  value = get_lookup_value(lookup_term, 'landCoverTermId', **log_args)
68
- return value.split(';')[0] if value else None
68
+ return (
69
+ lookup_term.get("@id") if lookup_term.get("termType") == TermTermType.LANDCOVER.value else
70
+ value.split(';')[0] if value else
71
+ None
72
+ )
@@ -13,7 +13,10 @@ def impact_lookup_value(model: str, term_id: str, impact_assessment: dict, looku
13
13
  fuels = filter_list_term_type(cycle.get('inputs', []), TermTermType.FUEL)
14
14
  has_fuels_inputs = len(fuels) > 0
15
15
  fuels_total_value = convert_value_from_cycle(
16
- product, cycle_lookup_value(model, term_id, cycle, fuels, lookup_col), model=model, term_id=term_id
16
+ impact_assessment,
17
+ product,
18
+ cycle_lookup_value(model, term_id, cycle, fuels, lookup_col),
19
+ model=model, term_id=term_id
17
20
  ) if has_fuels_inputs else None
18
21
  logRequirements(impact_assessment, model=model, term=term_id,
19
22
  term_type_electricityFuel_complete=fuel_complete,
@@ -4,7 +4,7 @@ from hestia_earth.utils.lookup import download_lookup
4
4
  from hestia_earth.utils.model import find_term_match, filter_list_term_type
5
5
  from hestia_earth.utils.tools import list_sum, safe_parse_date
6
6
 
7
- from hestia_earth.models.log import logRequirements, debugValues, log_as_table
7
+ from hestia_earth.models.log import debugValues, log_as_table
8
8
  from .lookup import all_factor_value, _term_factor_value, _aware_factor_value, fallback_country
9
9
  from .product import find_by_product
10
10
  from .site import region_level_1_id
@@ -244,13 +244,15 @@ def emission_value(impact_assessment: dict, term_id: str):
244
244
  return find_term_match(impact_assessment.get('emissionsResourceUse', []), term_id).get('value')
245
245
 
246
246
 
247
- def convert_value_from_cycle(product: dict, value: float, default=None, model: str = None, term_id: str = None):
247
+ def convert_value_from_cycle(
248
+ log_node: dict, product: dict, value: float, default=None, model: str = None, term_id: str = None
249
+ ):
248
250
  pyield = list_sum(product.get('value', [])) if product else 0
249
251
  economic_value = product.get('economicValueShare') if product else 0
250
252
 
251
- logRequirements({}, model=model, term=term_id,
252
- product_yield=pyield,
253
- economicValueShare=economic_value)
253
+ debugValues(log_node, model=model, term=term_id,
254
+ product_yield=pyield,
255
+ economicValueShare=economic_value)
254
256
 
255
257
  return (value / pyield) * economic_value / 100 if all([
256
258
  value is not None, pyield > 0, economic_value
@@ -22,6 +22,7 @@ def impact_lookup_value(model: str, term_id: str, impact_assessment: dict, looku
22
22
  get_pesticides_from_inputs(cycle)
23
23
  has_pesticides_inputs = len(pesticides) > 0
24
24
  pesticides_total_value = convert_value_from_cycle(
25
+ log_node=impact_assessment,
25
26
  product=product,
26
27
  value=cycle_lookup_value(model, term_id, cycle, pesticides, lookup_col),
27
28
  default=None,
@@ -1 +1 @@
1
- VERSION = '0.65.4'
1
+ VERSION = '0.65.6'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: hestia-earth-models
3
- Version: 0.65.4
3
+ Version: 0.65.6
4
4
  Summary: HESTIA's set of modules for filling gaps in the activity data using external datasets (e.g. populating soil properties with a geospatial dataset using provided coordinates) and internal lookups (e.g. populating machinery use from fuel use). Includes rules for when gaps should be filled versus not (e.g. never gap fill yield, gap fill crop residue if yield provided etc.).
5
5
  Home-page: https://gitlab.com/hestia-earth/hestia-engine-models
6
6
  Author: HESTIA Team
@@ -12,7 +12,7 @@ Classifier: Programming Language :: Python :: 3.6
12
12
  Description-Content-Type: text/markdown
13
13
  License-File: LICENSE
14
14
  Requires-Dist: hestia-earth-schema==30.*
15
- Requires-Dist: hestia-earth-utils>=0.13.12
15
+ Requires-Dist: hestia-earth-utils>=0.13.14
16
16
  Requires-Dist: python-dateutil>=2.8.1
17
17
  Requires-Dist: CurrencyConverter==0.16.8
18
18
  Requires-Dist: haversine>=2.7.0