hestia-earth-models 0.65.3__py3-none-any.whl → 0.65.5__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 (59) hide show
  1. hestia_earth/models/agribalyse2016/fuelElectricity.py +41 -35
  2. hestia_earth/models/aware/scarcityWeightedWaterUse.py +1 -1
  3. hestia_earth/models/cache_sites.py +2 -0
  4. hestia_earth/models/chaudharyBrooks2018/damageToTerrestrialEcosystemsLandOccupation.py +1 -1
  5. hestia_earth/models/chaudharyBrooks2018/damageToTerrestrialEcosystemsLandTransformation.py +1 -1
  6. hestia_earth/models/chaudharyBrooks2018/damageToTerrestrialEcosystemsTotalLandUseEffects.py +1 -1
  7. hestia_earth/models/cycle/completeness/__init__.py +1 -1
  8. hestia_earth/models/cycle/completeness/electricityFuel.py +4 -2
  9. hestia_earth/models/emepEea2019/nh3ToAirInorganicFertiliser.py +1 -1
  10. hestia_earth/models/geospatialDatabase/precipitationAnnual.py +2 -2
  11. hestia_earth/models/geospatialDatabase/precipitationLongTermAnnualMean.py +2 -2
  12. hestia_earth/models/geospatialDatabase/precipitationMonthly.py +2 -2
  13. hestia_earth/models/geospatialDatabase/temperatureAnnual.py +2 -2
  14. hestia_earth/models/geospatialDatabase/temperatureLongTermAnnualMean.py +2 -2
  15. hestia_earth/models/geospatialDatabase/temperatureMonthly.py +2 -2
  16. hestia_earth/models/hestia/landCover.py +31 -46
  17. hestia_earth/models/hestia/landTransformation100YearAverageDuringCycle.py +49 -0
  18. hestia_earth/models/hestia/landTransformation20YearAverageDuringCycle.py +49 -0
  19. hestia_earth/models/hestia/resourceUse_utils.py +200 -0
  20. hestia_earth/models/hestia/seed_emissions.py +37 -28
  21. hestia_earth/models/hestia/utils.py +48 -0
  22. hestia_earth/models/impact_assessment/emissions.py +20 -5
  23. hestia_earth/models/ipcc2019/co2ToAirCarbonStockChange_utils.py +66 -28
  24. hestia_earth/models/ipcc2019/croppingDuration.py +5 -0
  25. hestia_earth/models/ipcc2019/organicCarbonPerHa_tier_1_utils.py +26 -142
  26. hestia_earth/models/ipcc2019/organicCarbonPerHa_tier_2_utils.py +3 -3
  27. hestia_earth/models/ipcc2019/organicCarbonPerHa_utils.py +8 -5
  28. hestia_earth/models/linkedImpactAssessment/freshwaterWithdrawalsInputsProduction.py +2 -1
  29. hestia_earth/models/linkedImpactAssessment/landOccupationInputsProduction.py +1 -0
  30. hestia_earth/models/linkedImpactAssessment/landTransformation100YearAverageInputsProduction.py +1 -0
  31. hestia_earth/models/linkedImpactAssessment/landTransformation20YearAverageInputsProduction.py +1 -0
  32. hestia_earth/models/linkedImpactAssessment/utils.py +25 -20
  33. hestia_earth/models/mocking/search-results.json +670 -654
  34. hestia_earth/models/pooreNemecek2018/freshwaterWithdrawalsDuringCycle.py +4 -1
  35. hestia_earth/models/schererPfister2015/nErosionSoilFlux.py +23 -14
  36. hestia_earth/models/schererPfister2015/pErosionSoilFlux.py +23 -15
  37. hestia_earth/models/schererPfister2015/utils.py +3 -5
  38. hestia_earth/models/site/management.py +2 -2
  39. hestia_earth/models/utils/__init__.py +9 -0
  40. hestia_earth/models/utils/blank_node.py +28 -0
  41. hestia_earth/models/utils/ecoClimateZone.py +1 -4
  42. hestia_earth/models/utils/fuel.py +4 -1
  43. hestia_earth/models/utils/impact_assessment.py +7 -5
  44. hestia_earth/models/utils/lookup.py +8 -2
  45. hestia_earth/models/utils/pesticideAI.py +1 -0
  46. hestia_earth/models/version.py +1 -1
  47. {hestia_earth_models-0.65.3.dist-info → hestia_earth_models-0.65.5.dist-info}/METADATA +2 -2
  48. {hestia_earth_models-0.65.3.dist-info → hestia_earth_models-0.65.5.dist-info}/RECORD +59 -53
  49. tests/models/hestia/test_landTransformation100YearAverageDuringCycle.py +30 -0
  50. tests/models/hestia/test_landTransformation20YearAverageDuringCycle.py +31 -0
  51. tests/models/ipcc2019/test_co2ToAirAboveGroundBiomassStockChange.py +3 -1
  52. tests/models/ipcc2019/test_co2ToAirBelowGroundBiomassStockChange.py +3 -1
  53. tests/models/ipcc2019/test_co2ToAirSoilOrganicCarbonStockChange.py +3 -1
  54. tests/models/ipcc2019/test_organicCarbonPerHa.py +3 -2
  55. tests/models/ipcc2019/test_organicCarbonPerHa_tier_1_utils.py +15 -11
  56. tests/models/utils/test_blank_node.py +22 -7
  57. {hestia_earth_models-0.65.3.dist-info → hestia_earth_models-0.65.5.dist-info}/LICENSE +0 -0
  58. {hestia_earth_models-0.65.3.dist-info → hestia_earth_models-0.65.5.dist-info}/WHEEL +0 -0
  59. {hestia_earth_models-0.65.3.dist-info → hestia_earth_models-0.65.5.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,200 @@
1
+ """
2
+ Resource Use
3
+
4
+ Provides common code for land transformation models.
5
+ """
6
+ from datetime import datetime
7
+ from dateutil.relativedelta import relativedelta
8
+ from hestia_earth.schema import TermTermType
9
+ from hestia_earth.utils.tools import to_precision
10
+
11
+ from hestia_earth.models.log import logRequirements, logShouldRun
12
+ from hestia_earth.models.utils.blank_node import _gapfill_datestr, DatestrGapfillMode, DatestrFormat, _str_dates_match
13
+ from hestia_earth.models.utils.impact_assessment import get_site
14
+ from hestia_earth.models.utils.indicator import _new_indicator
15
+ from .utils import (
16
+ LAND_USE_TERMS_FOR_TRANSFORMATION,
17
+ crop_ipcc_land_use_category,
18
+ )
19
+ from . import MODEL
20
+
21
+ _MAXIMUM_OFFSET_DAYS = 365 * 2
22
+ _OUTPUT_SIGNIFICANT_DIGITS = 3
23
+ _RESOURCE_USE_TERM_ID = 'landOccupationDuringCycle'
24
+
25
+
26
+ def _new_indicator_with_value(
27
+ term_id: str,
28
+ land_cover_id: str,
29
+ previous_land_cover_id: str,
30
+ value: float
31
+ ) -> dict:
32
+ indicator = _new_indicator(
33
+ term=term_id,
34
+ model=MODEL,
35
+ land_cover_id=land_cover_id,
36
+ previous_land_cover_id=previous_land_cover_id
37
+ )
38
+ indicator["value"] = to_precision(number=value, digits=_OUTPUT_SIGNIFICANT_DIGITS) if value != 0 else 0
39
+ return indicator
40
+
41
+
42
+ def _gap_filled_date_obj(date_str: str) -> datetime:
43
+ return datetime.strptime(
44
+ _gapfill_datestr(datestr=date_str, mode=DatestrGapfillMode.MIDDLE),
45
+ DatestrFormat.YEAR_MONTH_DAY_HOUR_MINUTE_SECOND.value
46
+ )
47
+
48
+
49
+ def _should_run_close_date_found(
50
+ ia_end_date_str: str,
51
+ management_nodes: list,
52
+ historic_date_offset: int
53
+ ) -> tuple[bool, str]:
54
+ historic_ia_date_obj = (
55
+ _gap_filled_date_obj(ia_end_date_str) - relativedelta(years=historic_date_offset)
56
+ if ia_end_date_str else None
57
+ )
58
+ # Calculate all distances in days which are less than MAXIMUM_OFFSET_DAYS from historic date
59
+ # Assumption: if there are two dates are equidistant from the target chose the second.
60
+ filtered_dates = {
61
+ abs((_gap_filled_date_obj(node.get("endDate")) - historic_ia_date_obj).days): node.get("endDate")
62
+ for node in management_nodes
63
+ if node.get("term", {}).get("termType", "") == TermTermType.LANDCOVER.value and
64
+ abs((_gap_filled_date_obj(node.get("endDate")) - historic_ia_date_obj).days) <= _MAXIMUM_OFFSET_DAYS
65
+ }
66
+ nearest_date = filtered_dates[min(filtered_dates.keys())] if filtered_dates else ""
67
+
68
+ return nearest_date != "", nearest_date
69
+
70
+
71
+ def should_run(
72
+ impact_assessment: dict,
73
+ site: dict,
74
+ term_id: str,
75
+ historic_date_offset: int
76
+ ) -> tuple[bool, dict, str]:
77
+ relevant_emission_resource_use = [
78
+ node for node in impact_assessment.get("emissionsResourceUse", [])
79
+ if node.get("term", {}).get("@id", "") == _RESOURCE_USE_TERM_ID and node.get("value", -1) >= 0
80
+ ]
81
+
82
+ filtered_management_nodes = [
83
+ node for node in site.get("management", [])
84
+ if node.get("value", -1) >= 0 and node.get("term", {}).get("termType", "") == TermTermType.LANDCOVER.value
85
+ ]
86
+ current_node_index = next(
87
+ (i for i, node in enumerate(filtered_management_nodes)
88
+ if _str_dates_match(node.get("endDate", ""), impact_assessment.get("endDate", ""))),
89
+ None
90
+ )
91
+ current_node = filtered_management_nodes.pop(current_node_index) if current_node_index is not None else None
92
+
93
+ close_date_found, closest_date_str = _should_run_close_date_found(
94
+ ia_end_date_str=impact_assessment.get("endDate", ""),
95
+ management_nodes=filtered_management_nodes,
96
+ historic_date_offset=historic_date_offset
97
+ )
98
+
99
+ logRequirements(
100
+ log_node=impact_assessment,
101
+ model=MODEL,
102
+ term_id=term_id,
103
+ site=site
104
+ )
105
+
106
+ should_run_result = all([
107
+ relevant_emission_resource_use != [],
108
+ current_node,
109
+ close_date_found
110
+ ])
111
+ logShouldRun(site, MODEL, term=term_id, should_run=should_run_result)
112
+
113
+ return should_run_result, current_node, closest_date_str
114
+
115
+
116
+ def _get_land_occupation_for_land_use_type(impact_assessment: dict, ipcc_land_use_category: str) -> float:
117
+ """
118
+ Returns the sum of all land occupation for the specified land_use_category.
119
+ """
120
+ return sum(
121
+ node.get("value", 0) for node in impact_assessment.get("emissionsResourceUse", [])
122
+ if node.get("term", {}).get("@id", "") == _RESOURCE_USE_TERM_ID
123
+ and crop_ipcc_land_use_category(node.get("landCover", {}).get("@id", "")) == ipcc_land_use_category
124
+ )
125
+
126
+
127
+ def _calculate_indicator_value(
128
+ impact_assessment: dict,
129
+ management_nodes: list,
130
+ ipcc_land_use_category: str,
131
+ previous_land_cover_id: str,
132
+ historic_date_offset: int
133
+ ) -> float:
134
+ """
135
+ Land transformation from [land type] previous management nodes
136
+ = (Land occupation, during Cycle * Historic Site Percentage Area [land type] / 100) / HISTORIC_DATE_OFFSET
137
+ """
138
+ land_occupation_for_cycle = _get_land_occupation_for_land_use_type(
139
+ impact_assessment=impact_assessment,
140
+ ipcc_land_use_category=ipcc_land_use_category
141
+ )
142
+ historical_land_use = sum(
143
+ node.get("value", 0) for node in management_nodes
144
+ if node.get("term", {}).get("@id", "") == previous_land_cover_id
145
+ )
146
+ return ((land_occupation_for_cycle * historical_land_use) / 100) / historic_date_offset
147
+
148
+
149
+ def _run_calculate_transformation(
150
+ term_id: str,
151
+ current_node: dict,
152
+ closest_date_str: str,
153
+ impact_assessment: dict,
154
+ site: dict,
155
+ historic_date_offset: int
156
+ ) -> list:
157
+ """
158
+ Calculate land transformation for all land use categories.
159
+ """
160
+ indicators = [
161
+ _new_indicator_with_value(
162
+ term_id=term_id,
163
+ land_cover_id=current_node.get("term", {}).get("@id"),
164
+ previous_land_cover_id=previous_land_cover_id,
165
+ value=_calculate_indicator_value(
166
+ impact_assessment=impact_assessment,
167
+ management_nodes=[
168
+ node for node in site.get("management", [])
169
+ if _str_dates_match(node.get("endDate", ""), closest_date_str)
170
+ ],
171
+ ipcc_land_use_category=crop_ipcc_land_use_category(current_node.get("term", {}).get("@id", "")),
172
+ previous_land_cover_id=previous_land_cover_id,
173
+ historic_date_offset=historic_date_offset
174
+ )
175
+ ) for previous_land_cover_id in [t[0] for t in LAND_USE_TERMS_FOR_TRANSFORMATION.values()]
176
+ ]
177
+
178
+ return indicators
179
+
180
+
181
+ def run_resource_use(
182
+ impact_assessment: dict,
183
+ historic_date_offset: int,
184
+ term_id: str
185
+ ) -> list:
186
+ site = get_site(impact_assessment)
187
+ _should_run, current_node, closest_date_str = should_run(
188
+ impact_assessment=impact_assessment,
189
+ site=site,
190
+ term_id=term_id,
191
+ historic_date_offset=historic_date_offset
192
+ )
193
+ return _run_calculate_transformation(
194
+ term_id=term_id,
195
+ current_node=current_node,
196
+ closest_date_str=closest_date_str,
197
+ site=site,
198
+ impact_assessment=impact_assessment,
199
+ historic_date_offset=historic_date_offset
200
+ ) if _should_run else []
@@ -18,8 +18,8 @@ from hestia_earth.utils.model import filter_list_term_type
18
18
  from hestia_earth.utils.tools import non_empty_list, flatten, list_sum, safe_parse_float
19
19
  from hestia_earth.utils.emission import cycle_emissions_in_system_boundary
20
20
 
21
- from hestia_earth.models.log import logRequirements, logShouldRun, log_as_table
22
- from hestia_earth.models.utils import _omit
21
+ from hestia_earth.models.log import logRequirements, logShouldRun, log_as_table, debugValues
22
+ from hestia_earth.models.utils import _omit, group_by
23
23
  from hestia_earth.models.utils.emission import _new_emission
24
24
  from hestia_earth.models.utils.site import valid_site_type
25
25
  from hestia_earth.models.utils.cycle import cycle_end_year
@@ -54,7 +54,7 @@ REQUIREMENTS = {
54
54
  "siteType": ["cropland", "glass or high accessible cover"],
55
55
  "country": {"@type": "Term", "termType": "region"}
56
56
  },
57
- "emissions": [{"@type": "Emission"}]
57
+ "emissions": [{"@type": "Emission", "value": ""}]
58
58
  }
59
59
  }
60
60
  RETURNS = {
@@ -86,11 +86,24 @@ def _emission(term_id: str, value: float, input: dict):
86
86
  return emission
87
87
 
88
88
 
89
- def _run(cycle: dict, economicValueShare: float, total_yield: float, seed_input: dict, grouped_emissions: dict):
90
- term = seed_input.get('term', {})
89
+ def _run_emission(
90
+ cycle: dict, economicValueShare: float, total_yield: float, seed_input: dict, term_id: str, emission_value: float
91
+ ):
92
+ input_term = seed_input.get('term', {})
93
+ input_term_id = input_term.get('@id')
91
94
  seed_value = list_sum(seed_input.get('value'))
95
+ value = emission_value * economicValueShare / 100 / total_yield * seed_value
96
+ debugValues(cycle, model=MODEL, term=term_id, model_key=MODEL_KEY,
97
+ value=value,
98
+ coefficient=1,
99
+ input=input_term_id)
100
+
101
+ return _emission(term_id, value, input_term)
102
+
103
+
104
+ def _run(cycle: dict, economicValueShare: float, total_yield: float, seed_input: dict, grouped_emissions: dict):
92
105
  return [
93
- _emission(term_id, emission_value * economicValueShare / 100 / total_yield * seed_value, term)
106
+ _run_emission(cycle, economicValueShare, total_yield, seed_input, term_id, emission_value)
94
107
  for term_id, emission_value in grouped_emissions.items()
95
108
  ]
96
109
 
@@ -102,12 +115,12 @@ def _filter_emissions(cycle: dict):
102
115
  {
103
116
  'id': i.get('term', {}).get('@id'),
104
117
  'group-id': get_lookup_value(i.get('term', {}), LOOKUPS['emission'], model=MODEL, model_key=MODEL_KEY),
105
- 'value': list_sum(i.get('value'), 0)
118
+ 'value': list_sum(i.get('value'))
106
119
  }
107
120
  for i in cycle.get('emissions', [])
108
121
  if all([
109
122
  i.get('term', {}).get('@id') in required_emission_term_ids,
110
- list_sum(i.get('value'), 0) > 0
123
+ len(i.get('value', [])) > 0
111
124
  ])
112
125
  ]
113
126
  emission_ids = set([v.get('id') for v in emissions])
@@ -120,7 +133,7 @@ def _filter_emissions(cycle: dict):
120
133
  'id': group_id,
121
134
  'emissions': list(filter(
122
135
  lambda id: id in required_emission_term_ids,
123
- list(lookup[lookup[column_name('inputProductionGroupId')] == group_id].termid)
136
+ set(list(lookup[lookup[column_name('inputProductionGroupId')] == group_id].termid))
124
137
  ))
125
138
  }
126
139
  for group_id in group_ids
@@ -129,8 +142,8 @@ def _filter_emissions(cycle: dict):
129
142
  {
130
143
  'id': group.get('id'),
131
144
  'total-emissions': len(group.get('emissions', [])),
132
- 'included-emissions': len([v in emission_ids for v in group.get('emissions', [])]),
133
- 'missing-emissions': '-'.join([v for v in group.get('emissions', []) if v not in emission_ids])
145
+ 'included-emissions': len(list(filter(lambda v: v in emission_ids, group.get('emissions', [])))),
146
+ 'missing-emissions': '-'.join(list(filter(lambda v: v not in emission_ids, group.get('emissions', []))))
134
147
  }
135
148
  for group in emissions_per_group
136
149
  ]
@@ -154,23 +167,18 @@ def _evs(product: dict):
154
167
  ) or product.get('economicValueShare')
155
168
 
156
169
 
157
- def _yield(country_id: str, end_year: int, product: dict):
170
+ def _faostat_yield(country_id: str, end_year: int, product: dict):
158
171
  grouping = get_crop_grouping_faostat_production(MODEL, product.get('term', {}))
159
172
  return safe_parse_float(extract_grouped_data_closest_date(get_table_value(
160
173
  download_lookup('region-crop-cropGroupingFaostatProduction-yield.csv'),
161
174
  'termid',
162
175
  country_id,
163
176
  column_name(grouping)
164
- ), end_year)) or list_sum(product.get('value'))
177
+ ), end_year))
165
178
 
166
179
 
167
180
  def _group_seed_inputs(inputs: list):
168
- def _group_by(group: dict, input: dict):
169
- term_id = input.get('term', {}).get('@id')
170
- group[term_id] = group.get(term_id, []) + [input]
171
- return group
172
-
173
- grouped_inputs = reduce(_group_by, inputs, {})
181
+ grouped_inputs = group_by(inputs, ['term.@id'])
174
182
  return [
175
183
  inputs[0] | {'value': flatten([v.get('value') for v in inputs])}
176
184
  for inputs in grouped_inputs.values()
@@ -191,10 +199,11 @@ def _should_run(cycle: dict):
191
199
  {
192
200
  'product': product.get('term', {}).get('@id'),
193
201
  'seed-id': get_lookup_value(
194
- product.get('term', {}), 'correspondingSeedTermIds', model=MODEL, key=MODEL_KEY) or None,
202
+ product.get('term', {}), 'correspondingSeedTermIds', model=MODEL, model_key=MODEL_KEY) or None,
195
203
  'economicValueShare': _evs(product),
196
- 'yield': _yield(country_id, end_year, product),
197
- 'landCover-id': get_landCover_term_id(product.get('term', {}), model=MODEL, key=MODEL_KEY)
204
+ 'FAOSTAT-yield': _faostat_yield(country_id, end_year, product),
205
+ 'product-yield': list_sum(product.get('value')),
206
+ 'landCover-id': get_landCover_term_id(product.get('term', {}), model=MODEL, model_key=MODEL_KEY)
198
207
  }
199
208
  for product in crop_products
200
209
  ]
@@ -202,7 +211,7 @@ def _should_run(cycle: dict):
202
211
  value for value in crop_products if all([
203
212
  value.get('seed-id'),
204
213
  value.get('economicValueShare'),
205
- value.get('yield'),
214
+ value.get('FAOSTAT-yield') or value.get('product-yield'),
206
215
  value.get('landCover-id'),
207
216
  ])
208
217
  ]
@@ -214,7 +223,7 @@ def _should_run(cycle: dict):
214
223
  {
215
224
  'input': i,
216
225
  'is-corresponding-seed': i.get('term', {}).get('@id') in seed_term_ids,
217
- 'input-value': i.get('value'),
226
+ 'input-value': list_sum(i.get('value'), default=None),
218
227
  'has-linked-impact-assessment': bool(i.get('impactAssessment')),
219
228
  'is-fromCycle': i.get('fromCycle', False),
220
229
  'is-producedInCycle': i.get('producedInCycle', False),
@@ -226,7 +235,7 @@ def _should_run(cycle: dict):
226
235
  v.get('input') for v in seed_inputs
227
236
  if all([
228
237
  v.get('is-corresponding-seed', False),
229
- list_sum(v.get('input-value') or [-1]) > 0,
238
+ v.get('input-value') or -1 > 0,
230
239
  not v.get('has-linked-impact-assessment'),
231
240
  not v.get('is-fromCycle'),
232
241
  not v.get('is-producedInCycle'),
@@ -235,12 +244,12 @@ def _should_run(cycle: dict):
235
244
 
236
245
  crop_land_cover_ids = list(set([p.get('landCover-id') for p in valid_crop_products]))
237
246
  total_economicValueShare = list_sum([p.get('economicValueShare') for p in valid_crop_products])
238
- total_yield = list_sum([p.get('yield') for p in valid_crop_products])
247
+ total_yield = list_sum([p.get('FAOSTAT-yield') or p.get('product-yield') for p in valid_crop_products])
239
248
 
240
249
  emissions, emissions_per_group = _filter_emissions(cycle)
241
250
  # group emissions with the same group-id
242
251
  grouped_emissions = reduce(
243
- lambda p, c: p | {c.get('group-id'): p.get(c.get('group-id'), 0) + c.get('value', 0)},
252
+ lambda p, c: p | {c.get('group-id'): p.get(c.get('group-id'), 0) + (c.get('value') or 0)},
244
253
  emissions,
245
254
  {}
246
255
  )
@@ -271,7 +280,7 @@ def _should_run(cycle: dict):
271
280
  emissions_per_group=log_as_table(emissions_per_group),
272
281
  **_omit(seed_input, 'input'))
273
282
 
274
- logShouldRun(cycle, MODEL, term_id, should_run, key=MODEL_KEY)
283
+ logShouldRun(cycle, MODEL, term_id, should_run, methodTier=TIER, model_key=MODEL_KEY)
275
284
 
276
285
  return should_run, total_economicValueShare, total_yield, grouped_seed_inputs, grouped_emissions
277
286
 
@@ -0,0 +1,48 @@
1
+ from hestia_earth.schema import TermTermType
2
+
3
+ from hestia_earth.models.utils.term import get_lookup_value
4
+ from . import MODEL
5
+
6
+ IPCC_LAND_USE_CATEGORY_ANNUAL = "Annual crops"
7
+ IPCC_LAND_USE_CATEGORY_PERENNIAL = "Perennial crops"
8
+ TOTAL_CROPLAND = "Cropland"
9
+ ANNUAL_CROPLAND = "Arable land"
10
+ FOREST_LAND = "Forest land"
11
+ OTHER_LAND = "Other land"
12
+ PERMANENT_CROPLAND = "Permanent crops"
13
+ PERMANENT_PASTURE = "Permanent meadows and pastures"
14
+ TOTAL_AGRICULTURAL_CHANGE = "Total agricultural change"
15
+ ALL_LAND_USE_TERMS = [
16
+ FOREST_LAND,
17
+ TOTAL_CROPLAND,
18
+ ANNUAL_CROPLAND,
19
+ PERMANENT_CROPLAND,
20
+ PERMANENT_PASTURE,
21
+ OTHER_LAND
22
+ ]
23
+
24
+ # Mapping from Land use terms to Management node terms.
25
+ # land use term: (@id, name)
26
+ LAND_USE_TERMS_FOR_TRANSFORMATION = {
27
+ FOREST_LAND: ("forest", "Forest"),
28
+ ANNUAL_CROPLAND: ("annualCropland", "Annual cropland"),
29
+ PERMANENT_CROPLAND: ("permanentCropland", "Permanent cropland"),
30
+ PERMANENT_PASTURE: ("permanentPasture", "Permanent pasture"),
31
+ OTHER_LAND: ("otherLand", OTHER_LAND) # Not used yet
32
+ }
33
+
34
+
35
+ def crop_ipcc_land_use_category(
36
+ crop_term_id: str,
37
+ lookup_term_type: str = TermTermType.LANDCOVER.value
38
+ ) -> str:
39
+ """
40
+ Looks up the crop in the lookup.
41
+ Returns the IPCC_LAND_USE_CATEGORY.
42
+ """
43
+ return get_lookup_value(
44
+ lookup_term={"@id": crop_term_id, "type": "Term", "termType": lookup_term_type},
45
+ column='IPCC_LAND_USE_CATEGORY',
46
+ model=MODEL,
47
+ term={"@id": crop_term_id, "type": "Term", "termType": lookup_term_type}
48
+ )
@@ -4,9 +4,11 @@ Emissions
4
4
  Creates an [Indicator](https://hestia.earth/schema/Indicator) for every [Emission](https://hestia.earth/schema/Emission)
5
5
  contained within the [ImpactAssesment.cycle](https://hestia.earth/schema/ImpactAssessment#cycle).
6
6
  It does this by dividing the Emission amount by the Product amount, and applying an allocation between co-products.
7
+ Note: for any Emission in the system boundary that does not exist in the Cycle, it will log the model as failed,
8
+ so that we know the Emission is missing.
7
9
  """
8
-
9
10
  from hestia_earth.utils.tools import list_sum
11
+ from hestia_earth.utils.emission import cycle_emissions_in_system_boundary
10
12
 
11
13
  from hestia_earth.models.log import logRequirements, logShouldRun
12
14
  from hestia_earth.models.utils.impact_assessment import get_product, convert_value_from_cycle
@@ -38,10 +40,12 @@ RETURNS = {
38
40
  MODEL_KEY = 'emissions'
39
41
 
40
42
 
41
- def _indicator(product: dict):
43
+ def _indicator(impact_assessment: dict, product: dict):
42
44
  def run(emission: dict):
43
45
  term_id = emission.get('term', {}).get('@id')
44
- value = convert_value_from_cycle(product, list_sum(emission.get('value', [0])), model=MODEL, term_id=term_id)
46
+ value = convert_value_from_cycle(
47
+ impact_assessment, product, list_sum(emission.get('value', [0])), model=MODEL, term_id=term_id
48
+ )
45
49
 
46
50
  indicator = _new_indicator(emission.get('term', {}), emission.get('methodModel'))
47
51
  indicator['value'] = value
@@ -57,13 +61,23 @@ def _indicator(product: dict):
57
61
  return run
58
62
 
59
63
 
64
+ def _log_missing_emissions(impact_assessment: dict, emissions: list):
65
+ term_ids = cycle_emissions_in_system_boundary(cycle=impact_assessment.get('cycle', {}))
66
+ emission_ids = list(map(lambda e: e.get('term', {}).get('@id'), emissions))
67
+ missing_term_ids = [term_id for term_id in term_ids if term_id not in emission_ids]
68
+ for term_id in missing_term_ids:
69
+ logRequirements(impact_assessment, model=MODEL, term=term_id,
70
+ present_in_cycle=False)
71
+ logShouldRun(impact_assessment, MODEL, term_id, should_run=False)
72
+
73
+
60
74
  def _should_run_emission(impact_assessment: dict):
61
75
  product = get_product(impact_assessment)
62
76
 
63
77
  def exec(emission: dict):
64
78
  term_id = emission.get('term', {}).get('@id')
65
79
  has_value = convert_value_from_cycle(
66
- product, list_sum(emission.get('value', [0])), model=MODEL, term_id=term_id
80
+ impact_assessment, product, list_sum(emission.get('value', [0])), model=MODEL, term_id=term_id
67
81
  ) is not None
68
82
  not_deleted = emission.get('deleted', False) is not True
69
83
 
@@ -91,4 +105,5 @@ def run(impact_assessment: dict):
91
105
  should_run, product = _should_run(impact_assessment)
92
106
  emissions = impact_assessment.get('cycle', {}).get(MODEL_KEY, []) if should_run else []
93
107
  emissions = list(filter(_should_run_emission(impact_assessment), emissions))
94
- return list(map(_indicator(product), emissions))
108
+ _log_missing_emissions(impact_assessment, emissions)
109
+ return list(map(_indicator(impact_assessment, product), emissions))