hestia-earth-models 0.74.4__py3-none-any.whl → 0.74.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 (66) hide show
  1. hestia_earth/models/cml2001Baseline/abioticResourceDepletionMineralsAndMetals.py +0 -1
  2. hestia_earth/models/config/Cycle.json +15 -0
  3. hestia_earth/models/config/ImpactAssessment.json +9 -1
  4. hestia_earth/models/cycle/animal/input/hestiaAggregatedData.py +3 -3
  5. hestia_earth/models/cycle/completeness/seed.py +1 -1
  6. hestia_earth/models/cycle/input/hestiaAggregatedData.py +25 -16
  7. hestia_earth/models/data/hestiaAggregatedData/__init__.py +73 -0
  8. hestia_earth/models/environmentalFootprintV3_1/scarcityWeightedWaterUse.py +1 -1
  9. hestia_earth/models/environmentalFootprintV3_1/soilQualityIndexLandOccupation.py +5 -6
  10. hestia_earth/models/environmentalFootprintV3_1/soilQualityIndexLandTransformation.py +10 -13
  11. hestia_earth/models/fantkeEtAl2016/damageToHumanHealthParticulateMatterFormation.py +1 -1
  12. hestia_earth/models/hestia/default_resourceUse.py +18 -16
  13. hestia_earth/models/hestia/landCover.py +24 -0
  14. hestia_earth/models/hestia/landOccupationDuringCycle.py +80 -51
  15. hestia_earth/models/hestia/landTransformation100YearAverageDuringCycle.py +7 -1
  16. hestia_earth/models/hestia/landTransformation20YearAverageDuringCycle.py +7 -1
  17. hestia_earth/models/hestia/resourceUse_utils.py +58 -119
  18. hestia_earth/models/hestia/waterSalinity.py +57 -12
  19. hestia_earth/models/impact_assessment/post_checks/__init__.py +3 -2
  20. hestia_earth/models/impact_assessment/post_checks/remove_cache_fields.py +9 -0
  21. hestia_earth/models/impact_assessment/pre_checks/cache_emissionsResourceUse.py +21 -0
  22. hestia_earth/models/impact_assessment/pre_checks/cycle.py +5 -0
  23. hestia_earth/models/ipcc2019/co2ToAirAboveGroundBiomassStockChange.py +6 -64
  24. hestia_earth/models/ipcc2019/co2ToAirBelowGroundBiomassStockChange.py +9 -87
  25. hestia_earth/models/ipcc2019/co2ToAirBiocharStockChange.py +140 -0
  26. hestia_earth/models/ipcc2019/co2ToAirCarbonStockChange_utils.py +329 -217
  27. hestia_earth/models/ipcc2019/co2ToAirSoilOrganicCarbonStockChange.py +10 -87
  28. hestia_earth/models/mocking/__init__.py +2 -2
  29. hestia_earth/models/mocking/mock_search.py +20 -10
  30. hestia_earth/models/mocking/search-results.json +1 -7679
  31. hestia_earth/models/pooreNemecek2018/landOccupationDuringCycle.py +8 -7
  32. hestia_earth/models/poschEtAl2008/terrestrialAcidificationPotentialAccumulatedExceedance.py +1 -1
  33. hestia_earth/models/poschEtAl2008/terrestrialEutrophicationPotentialAccumulatedExceedance.py +1 -1
  34. hestia_earth/models/preload_requests.py +18 -4
  35. hestia_earth/models/schmidt2007/utils.py +3 -3
  36. hestia_earth/models/utils/__init__.py +4 -1
  37. hestia_earth/models/utils/aggregated.py +21 -68
  38. hestia_earth/models/utils/cycle.py +3 -3
  39. hestia_earth/models/utils/impact_assessment.py +45 -41
  40. hestia_earth/models/utils/indicator.py +1 -3
  41. hestia_earth/models/utils/lookup.py +92 -67
  42. hestia_earth/models/version.py +1 -1
  43. hestia_earth/orchestrator/models/__init__.py +47 -10
  44. hestia_earth/orchestrator/models/transformations.py +3 -1
  45. hestia_earth/orchestrator/strategies/merge/__init__.py +1 -2
  46. hestia_earth/orchestrator/strategies/merge/merge_list.py +31 -8
  47. hestia_earth/orchestrator/utils.py +29 -0
  48. {hestia_earth_models-0.74.4.dist-info → hestia_earth_models-0.74.6.dist-info}/METADATA +2 -3
  49. {hestia_earth_models-0.74.4.dist-info → hestia_earth_models-0.74.6.dist-info}/RECORD +66 -59
  50. tests/models/cycle/animal/input/test_hestiaAggregatedData.py +3 -3
  51. tests/models/cycle/input/test_hestiaAggregatedData.py +9 -18
  52. tests/models/data/__init__.py +0 -0
  53. tests/models/data/test_hestiaAggregatedData.py +32 -0
  54. tests/models/hestia/test_default_emissions.py +8 -1
  55. tests/models/hestia/test_default_resourceUse.py +7 -1
  56. tests/models/hestia/test_landCover.py +32 -1
  57. tests/models/hestia/test_waterSalinity.py +16 -4
  58. tests/models/ipcc2019/test_co2ToAirAboveGroundBiomassStockChange.py +1 -6
  59. tests/models/ipcc2019/test_co2ToAirBelowGroundBiomassStockChange.py +1 -6
  60. tests/models/ipcc2019/test_co2ToAirBiocharStockChange.py +90 -0
  61. tests/models/ipcc2019/test_co2ToAirSoilOrganicCarbonStockChange.py +1 -6
  62. tests/models/pooreNemecek2018/test_landOccupationDuringCycle.py +1 -0
  63. tests/orchestrator/strategies/merge/test_merge_list.py +5 -0
  64. {hestia_earth_models-0.74.4.dist-info → hestia_earth_models-0.74.6.dist-info}/LICENSE +0 -0
  65. {hestia_earth_models-0.74.4.dist-info → hestia_earth_models-0.74.6.dist-info}/WHEEL +0 -0
  66. {hestia_earth_models-0.74.4.dist-info → hestia_earth_models-0.74.6.dist-info}/top_level.txt +0 -0
@@ -1,4 +1,5 @@
1
1
  from functools import reduce
2
+ from itertools import zip_longest
2
3
  from typing import NamedTuple
3
4
 
4
5
  from hestia_earth.models.log import logRequirements, logShouldRun, log_as_table
@@ -29,9 +30,9 @@ REQUIREMENTS = {
29
30
  "@type": "Site",
30
31
  "country": {"@type": "Term", "termType": "region"}
31
32
  },
32
- "siteArea": "",
33
- "siteDuration": "",
34
- "siteUnusedDuration": "",
33
+ "siteArea": ">= 0",
34
+ "siteDuration": ">= 0",
35
+ "siteUnusedDuration": ">= 0",
35
36
  "optional": {
36
37
  "@doc": "When `otherSites` are provided, `otherSitesArea`, `otherSitesDuration` and `otherSitesUnusedDuration` are required", # noqa: E501
37
38
  "otherSites": [{
@@ -111,82 +112,102 @@ def _calc_land_occupation_m2_per_kg(
111
112
  return land_occupation_m2_per_ha * economic_value_share * 0.01 / yield_
112
113
 
113
114
 
114
- _CYCLE_KEYS = (
115
- "site", "siteArea", "siteDuration", "siteUnusedDuration"
116
- )
115
+ def _extract_site_data(cycle: dict, land_cover_id: dict):
116
+ site = cycle.get("site", {})
117
+ site_data = SiteData(
118
+ id=site.get("@id"),
119
+ area=cycle.get("siteArea"),
120
+ duration=cycle.get("siteDuration"),
121
+ unused_duration=cycle.get("siteUnusedDuration"),
122
+ country_id=site.get("country", {}).get("@id"),
123
+ land_cover_id=land_cover_id or get_landCover_term_id_from_site_type(site.get("siteType"))
124
+ )
117
125
 
118
- _CYCLE_KEY_MAPPING = {
119
- field: field.replace("site", "otherSites", 1) for field in _CYCLE_KEYS
120
- }
126
+ is_valid = _should_run_site_data(site_data)
121
127
 
128
+ logs = {
129
+ "site_data": _format_inventory([site_data])
130
+ }
122
131
 
123
- def _build_inventory(cycle: dict, product: dict):
124
- product_land_cover_id = get_landCover_term_id(product.get("term", {}), skip_debug=True)
132
+ return is_valid, site_data, logs
125
133
 
126
- cycle_data = {
127
- key: [value] + cycle.get(otherSites_key, []) for key, otherSites_key in _CYCLE_KEY_MAPPING.items()
128
- if (value := cycle.get(key))
129
- }
130
134
 
131
- n_sites = len(cycle_data.get("site", []))
132
- should_build_inventory = n_sites > 0 and all([
133
- len(cycle_data.get(key, [])) == n_sites for key in _CYCLE_KEYS[1:]
134
- ])
135
+ def _extract_other_sites_data(cycle: dict, land_cover_id: dict):
136
+ other_sites = cycle.get("otherSites", [])
137
+ other_sites_area = cycle.get("otherSitesArea", [])
138
+ other_sites_duration = cycle.get("otherSitesDuration", [])
139
+ other_sites_unused_duration = cycle.get("otherSitesUnusedDuration", [])
135
140
 
136
- inventory = [
141
+ other_sites_data = [
137
142
  SiteData(
138
143
  id=site.get("@id"),
139
- area=cycle_data["siteArea"][i],
140
- duration=cycle_data["siteDuration"][i],
141
- unused_duration=cycle_data["siteUnusedDuration"][i],
144
+ area=area,
145
+ duration=duration,
146
+ unused_duration=unused_duration,
142
147
  country_id=site.get("country", {}).get("@id"),
143
- land_cover_id=product_land_cover_id or get_landCover_term_id_from_site_type(site.get("siteType", {}))
144
- ) for i, site in enumerate(cycle_data.get("site", []))
145
- ] if should_build_inventory else []
148
+ land_cover_id=land_cover_id or get_landCover_term_id_from_site_type(site.get("siteType"))
149
+ ) for (
150
+ site,
151
+ area,
152
+ duration,
153
+ unused_duration
154
+ ) in zip_longest(
155
+ other_sites,
156
+ other_sites_area,
157
+ other_sites_duration,
158
+ other_sites_unused_duration
159
+ )
160
+ ]
161
+
162
+ is_valid = all(_should_run_site_data(other_site) for other_site in other_sites_data)
146
163
 
147
164
  logs = {
148
- "n_sites": n_sites
165
+ "other_sites_count": len(other_sites),
166
+ "other_sites_data": _format_inventory(other_sites_data, "Not relevant")
149
167
  }
150
168
 
151
- return inventory, logs
169
+ return is_valid, other_sites_data, logs
152
170
 
153
171
 
154
172
  def _should_run_site_data(site_data: SiteData) -> bool:
155
173
  return all([
156
- site_data.area >= 0,
157
- site_data.duration >= 0,
158
- site_data.unused_duration >= 0,
174
+ site_data.area or site_data.area == 0,
175
+ site_data.duration or site_data.duration == 0,
176
+ site_data.unused_duration or site_data.unused_duration == 0,
159
177
  site_data.land_cover_id,
160
178
  site_data.country_id
161
179
  ])
162
180
 
163
181
 
164
- def _format_float(value: float, unit: str = "") -> str:
182
+ def _format_float(value: float, unit: str = "", default: str = "None") -> str:
165
183
  return " ".join(
166
- string for string in [f"{value:.3g}", unit] if string
167
- ) if value else "None"
184
+ string for string in [f"{value}", unit] if string
185
+ ) if isinstance(value, (float, int)) else default
168
186
 
169
187
 
170
188
  _INVALID_CHARS = {"_", ":", ",", "="}
171
189
  _REPLACEMENT_CHAR = "-"
172
190
 
173
191
 
174
- def _format_str(value: str) -> str:
192
+ def _format_str(value: str, default: str = "None") -> str:
175
193
  """Format a string for logging in a table. Remove all characters used to render the table on the front end."""
176
- return reduce(lambda x, char: x.replace(char, _REPLACEMENT_CHAR), _INVALID_CHARS, str(value))
194
+ return (
195
+ reduce(lambda x, char: x.replace(char, _REPLACEMENT_CHAR), _INVALID_CHARS, str(value))
196
+ if value else default
197
+ )
177
198
 
178
199
 
179
- def _format_inventory(inventory: list[SiteData]) -> str:
200
+ def _format_inventory(inventory: list[SiteData], default: str = "None") -> str:
180
201
  return log_as_table(
181
202
  {
182
- "@id": _format_str(site_data.id),
203
+ "site-id": _format_str(site_data.id),
183
204
  "site-area": _format_float(site_data.area, "ha"),
184
205
  "site-duration": _format_float(site_data.duration, "days"),
185
206
  "site-unused-duration": _format_float(site_data.unused_duration, "days"),
186
207
  "land-cover-id": _format_str(site_data.land_cover_id),
187
208
  "country-id": _format_str(site_data.country_id)
188
209
  } for site_data in inventory
189
- ) if inventory else "None"
210
+ ) if inventory else default
190
211
 
191
212
 
192
213
  def _should_run(impact_assessment: dict):
@@ -195,13 +216,21 @@ def _should_run(impact_assessment: dict):
195
216
  functional_unit = cycle.get("functionalUnit")
196
217
 
197
218
  product = get_product(impact_assessment)
198
- yield_ = sum(product.get("value", []))
219
+ product_yield = sum(product.get("value", []))
220
+ product_land_cover_id = get_landCover_term_id(product.get("term", {}), skip_debug=True)
199
221
  economic_value_share = (
200
222
  100 if functional_unit == CycleFunctionalUnit.RELATIVE.value
201
223
  else product.get("economicValueShare")
202
224
  )
203
225
 
204
- inventory, logs = _build_inventory(cycle, product)
226
+ site_data_is_valid, site_data, site_logs = _extract_site_data(cycle, product_land_cover_id)
227
+ (
228
+ other_sites_data_is_valid,
229
+ other_sites_data,
230
+ other_sites_logs
231
+ ) = _extract_other_sites_data(cycle, product_land_cover_id)
232
+
233
+ inventory = [site_data] + other_sites_data
205
234
 
206
235
  valid_inventory = inventory and all(_should_run_site_data(site_data) for site_data in inventory)
207
236
 
@@ -210,25 +239,25 @@ def _should_run(impact_assessment: dict):
210
239
  model=MODEL,
211
240
  term=TERM_ID,
212
241
  functional_unit=functional_unit,
213
- yield_=_format_float(yield_, product.get("term", {}).get("units")),
242
+ product_yield=_format_float(product_yield, product.get("term", {}).get("units")),
214
243
  economic_value_share=_format_float(economic_value_share, "pct"),
215
- site_inventory=_format_inventory(inventory),
216
244
  valid_inventory=valid_inventory,
217
- **logs
245
+ site_data_is_valid=site_data_is_valid,
246
+ **site_logs,
247
+ other_sites_data_is_valid=other_sites_data_is_valid,
248
+ **other_sites_logs
218
249
  )
219
250
 
220
251
  should_run = all([
221
- yield_ > 0,
222
- (
223
- economic_value_share is not None
224
- and economic_value_share >= 0
225
- ),
226
- valid_inventory
252
+ product_yield > 0,
253
+ economic_value_share or economic_value_share == 0,
254
+ site_data_is_valid,
255
+ other_sites_data_is_valid
227
256
  ])
228
257
 
229
258
  logShouldRun(impact_assessment, MODEL, TERM_ID, should_run)
230
259
 
231
- return should_run, yield_, economic_value_share, inventory
260
+ return should_run, product_yield, economic_value_share, inventory
232
261
 
233
262
 
234
263
  def _run(
@@ -17,7 +17,13 @@ REQUIREMENTS = {
17
17
  "value": ">=0"
18
18
  }
19
19
  ],
20
- "endDate": ""
20
+ "endDate": "",
21
+ "none": {
22
+ "cycle": {
23
+ "@type": "Cycle",
24
+ "otherSites": []
25
+ }
26
+ }
21
27
  }
22
28
  }
23
29
  RETURNS = {
@@ -17,7 +17,13 @@ REQUIREMENTS = {
17
17
  "value": ">=0"
18
18
  }
19
19
  ],
20
- "endDate": ""
20
+ "endDate": "",
21
+ "none": {
22
+ "cycle": {
23
+ "@type": "Cycle",
24
+ "otherSites": []
25
+ }
26
+ }
21
27
  }
22
28
  }
23
29
  RETURNS = {
@@ -1,16 +1,13 @@
1
1
  from datetime import datetime
2
2
  from dateutil.relativedelta import relativedelta
3
3
  from hestia_earth.schema import TermTermType
4
- from hestia_earth.utils.tools import to_precision
4
+ from hestia_earth.utils.tools import to_precision, list_sum
5
5
 
6
- from hestia_earth.models.log import logRequirements, logShouldRun, debugValues
6
+ from hestia_earth.models.log import logRequirements, logShouldRun, log_as_table
7
7
  from hestia_earth.models.utils.blank_node import _gapfill_datestr, DatestrGapfillMode, DatestrFormat, _str_dates_match
8
8
  from hestia_earth.models.utils.impact_assessment import get_site
9
9
  from hestia_earth.models.utils.indicator import _new_indicator
10
- from .utils import (
11
- LAND_USE_TERMS_FOR_TRANSFORMATION,
12
- crop_ipcc_land_use_category,
13
- )
10
+ from .utils import LAND_USE_TERMS_FOR_TRANSFORMATION, crop_ipcc_land_use_category
14
11
  from . import MODEL
15
12
 
16
13
  _MAXIMUM_OFFSET_DAYS = 365 * 2
@@ -41,7 +38,7 @@ def _gap_filled_date_obj(date_str: str) -> datetime:
41
38
  )
42
39
 
43
40
 
44
- def _find_closest_node(
41
+ def _find_closest_node_date(
45
42
  ia_date_str: str,
46
43
  management_nodes: list,
47
44
  historic_date_offset: int,
@@ -62,32 +59,22 @@ def _find_closest_node(
62
59
  return filtered_dates[min(filtered_dates.keys())] if filtered_dates else ""
63
60
 
64
61
 
65
- def should_run(
66
- impact_assessment: dict,
67
- site: dict,
68
- term_id: str,
69
- historic_date_offset: int
70
- ) -> tuple[bool, dict, str, str]:
71
- relevant_emission_resource_use = [
72
- node for node in impact_assessment.get("emissionsResourceUse", [])
73
- if node.get("term", {}).get("@id", "") == _RESOURCE_USE_TERM_ID and node.get("value", -1) >= 0
74
- ]
62
+ def should_run(impact_assessment: dict, term_id: str, historic_date_offset: int) -> tuple[bool, dict, str, str]:
63
+ cycle = impact_assessment.get('cycle', {})
64
+ has_otherSites = len(cycle.get('otherSites') or []) != 0
65
+
66
+ site = get_site(impact_assessment)
75
67
  filtered_management_nodes = [
76
68
  node for node in site.get("management", [])
77
- if node.get("value", -1) >= 0 and node.get("term", {}).get("termType", "") == TermTermType.LANDCOVER.value
69
+ if node.get("value", -1) >= 0 and node.get("term", {}).get("termType") == TermTermType.LANDCOVER.value
78
70
  ]
79
- land_occupation_during_cycle_found = any(
80
- node.get("term", {}).get("@id") in
81
- {node.get("landCover", {}).get("@id") for node in relevant_emission_resource_use}
82
- for node in filtered_management_nodes
83
- )
84
71
  match_mode = (
85
72
  DatestrGapfillMode.START if impact_assessment.get("cycle", {}).get("aggregated") is True
86
73
  else DatestrGapfillMode.END
87
74
  )
88
75
  match_date = "startDate" if match_mode == DatestrGapfillMode.START else "endDate"
89
76
 
90
- closest_date = _find_closest_node(
77
+ closest_date = _find_closest_node_date(
91
78
  ia_date_str=impact_assessment.get(match_date, ""),
92
79
  management_nodes=filtered_management_nodes,
93
80
  historic_date_offset=historic_date_offset,
@@ -104,98 +91,53 @@ def should_run(
104
91
  None
105
92
  )
106
93
  current_node = filtered_management_nodes.pop(current_node_index) if current_node_index is not None else None
94
+ landCover_term_id = (current_node or {}).get('term', {}).get('@id')
107
95
 
108
- logRequirements(impact_assessment, model=MODEL, term=term_id,
109
- closest_end_date=closest_end_date,
110
- closest_start_date=closest_start_date,
111
- has_landOccupationDuringCycle=land_occupation_during_cycle_found,
112
- landCover_term_id=(current_node or {}).get('term', {}).get('@id'))
113
-
114
- should_run_result = all([
115
- relevant_emission_resource_use,
116
- land_occupation_during_cycle_found,
117
- current_node,
118
- closest_end_date or closest_start_date
119
- ])
120
- logShouldRun(impact_assessment, MODEL, term=term_id, should_run=should_run_result)
121
-
122
- return should_run_result, current_node, closest_end_date, closest_start_date
96
+ prior_management_nodes = [
97
+ node for node in filtered_management_nodes
98
+ if _str_dates_match(node.get("endDate", ""), closest_end_date) or
99
+ _str_dates_match(node.get("startDate", ""), closest_start_date)
100
+ ]
123
101
 
102
+ ipcc_land_use_category = crop_ipcc_land_use_category(landCover_term_id)
124
103
 
125
- def _get_land_occupation_for_land_use_type(impact_assessment: dict, ipcc_land_use_category: str) -> float:
126
- """
127
- Returns the sum of all land occupation for the specified land_use_category.
128
- """
129
- return sum(
130
- node.get("value", 0) for node in impact_assessment.get("emissionsResourceUse", [])
104
+ total_landOccupationDuringCycle = list_sum([
105
+ node.get("value") for node in impact_assessment.get("emissionsResourceUse", [])
131
106
  if node.get("term", {}).get("@id", "") == _RESOURCE_USE_TERM_ID
132
107
  and crop_ipcc_land_use_category(node.get("landCover", {}).get("@id", "")) == ipcc_land_use_category
133
- )
134
-
135
-
136
- def _calculate_indicator_value(
137
- impact_assessment: dict,
138
- term_id: str,
139
- management_nodes: list,
140
- ipcc_land_use_category: str,
141
- previous_land_cover_id: str,
142
- historic_date_offset: int
143
- ) -> float:
144
- """
145
- Land transformation from [land type] previous management nodes
146
- = (Land occupation, during Cycle * Historic Site Percentage Area [land type] / 100) / HISTORIC_DATE_OFFSET
147
- """
148
- land_occupation_for_cycle = _get_land_occupation_for_land_use_type(
149
- impact_assessment=impact_assessment,
150
- ipcc_land_use_category=ipcc_land_use_category
151
- )
152
- historical_land_use = sum(
153
- node.get("value", 0) for node in management_nodes
154
- if node.get("term", {}).get("@id", "") == previous_land_cover_id
155
- )
156
-
157
- debugValues(impact_assessment, model=MODEL, term=term_id,
158
- ipcc_land_use_category=ipcc_land_use_category,
159
- land_occupation_for_cycle=land_occupation_for_cycle,
160
- historical_land_use=historical_land_use,
161
- historic_date_offset=historic_date_offset)
108
+ ], default=None)
162
109
 
163
- return ((land_occupation_for_cycle * historical_land_use) / 100) / historic_date_offset
110
+ indicators = [
111
+ {
112
+ 'landCover-id': landCover_term_id,
113
+ 'previous-landCover-id': previous_land_cover_id,
114
+ 'landOccupationDuringCycle': total_landOccupationDuringCycle,
115
+ 'historical-landUse-change': list_sum([
116
+ node.get("value") for node in prior_management_nodes
117
+ if node.get("term", {}).get("@id") == previous_land_cover_id
118
+ ], default=None),
119
+ }
120
+ for previous_land_cover_id in [t[0] for t in LAND_USE_TERMS_FOR_TRANSFORMATION.values()]
121
+ ] if landCover_term_id else []
122
+ valid_indicators = [indicator for indicator in indicators if indicator['historical-landUse-change'] is not None]
164
123
 
124
+ logRequirements(impact_assessment, model=MODEL, term=term_id,
125
+ has_otherSites=has_otherSites,
126
+ closest_end_date=closest_end_date,
127
+ closest_start_date=closest_start_date,
128
+ landCover_term_id=landCover_term_id,
129
+ ipcc_land_use_category=ipcc_land_use_category,
130
+ indicators=log_as_table(indicators))
165
131
 
166
- def _run_calculate_transformation(
167
- term_id: str,
168
- current_node: dict,
169
- closest_end_date: str,
170
- closest_start_date: str,
171
- impact_assessment: dict,
172
- site: dict,
173
- historic_date_offset: int
174
- ) -> list:
175
- """
176
- Calculate land transformation for all land use categories.
177
- """
178
- indicators = [
179
- _new_indicator_with_value(
180
- term_id=term_id,
181
- land_cover_id=current_node.get("term", {}).get("@id"),
182
- previous_land_cover_id=previous_land_cover_id,
183
- value=_calculate_indicator_value(
184
- impact_assessment=impact_assessment,
185
- term_id=term_id,
186
- management_nodes=[
187
- node for node in site.get("management", [])
188
- if _str_dates_match(node.get("endDate", ""), closest_end_date) or
189
- _str_dates_match(node.get("startDate", ""), closest_start_date)
190
- ],
191
- ipcc_land_use_category=crop_ipcc_land_use_category(current_node.get("term", {}).get("@id", "")),
192
- previous_land_cover_id=previous_land_cover_id,
193
- historic_date_offset=historic_date_offset
194
- )
195
- ) for previous_land_cover_id in [t[0] for t in LAND_USE_TERMS_FOR_TRANSFORMATION.values()]
196
- ]
132
+ should_run_result = all([
133
+ not has_otherSites,
134
+ ipcc_land_use_category,
135
+ total_landOccupationDuringCycle is not None,
136
+ valid_indicators
137
+ ])
138
+ logShouldRun(impact_assessment, MODEL, term=term_id, should_run=should_run_result)
197
139
 
198
- return indicators
140
+ return should_run_result, valid_indicators
199
141
 
200
142
 
201
143
  def run_resource_use(
@@ -203,19 +145,16 @@ def run_resource_use(
203
145
  historic_date_offset: int,
204
146
  term_id: str
205
147
  ) -> list:
206
- site = get_site(impact_assessment)
207
- _should_run, current_node, closest_end_date, closest_start_date = should_run(
148
+ _should_run, indicators = should_run(
208
149
  impact_assessment=impact_assessment,
209
- site=site,
210
150
  term_id=term_id,
211
151
  historic_date_offset=historic_date_offset
212
152
  )
213
- return _run_calculate_transformation(
214
- impact_assessment=impact_assessment,
215
- site=site,
216
- term_id=term_id,
217
- current_node=current_node,
218
- closest_end_date=closest_end_date,
219
- closest_start_date=closest_start_date,
220
- historic_date_offset=historic_date_offset
221
- ) if _should_run else []
153
+ return [
154
+ _new_indicator_with_value(
155
+ term_id=term_id,
156
+ land_cover_id=i['landCover-id'],
157
+ previous_land_cover_id=i['previous-landCover-id'],
158
+ value=(i['landOccupationDuringCycle'] * i['historical-landUse-change'] / 100) / historic_date_offset
159
+ ) for i in indicators
160
+ ] if _should_run else []
@@ -4,6 +4,7 @@ from hestia_earth.utils.tools import safe_parse_float, list_average, non_empty_l
4
4
  from hestia_earth.utils.blank_node import group_by_keys
5
5
 
6
6
  from hestia_earth.models.log import logRequirements, logShouldRun, log_as_table
7
+ from hestia_earth.models.utils import is_from_model
7
8
  from hestia_earth.models.utils.measurement import _new_measurement
8
9
  from hestia_earth.models.utils.site import related_cycles
9
10
  from hestia_earth.models.utils.term import get_lookup_value
@@ -11,14 +12,21 @@ from . import MODEL
11
12
 
12
13
  REQUIREMENTS = {
13
14
  "Site": {
14
- "related": {
15
- "Cycle": {
16
- "@type": "Cycle",
17
- "products": [{
18
- "@type": "Product",
19
- "primary": "True",
20
- "term.termType": "liveAquaticSpecies"
21
- }]
15
+ "optional": {
16
+ "measurements": [{
17
+ "@type": "Measurement",
18
+ "term.@id": ["brackishWater", "salineWater", "freshWater"],
19
+ "value": "true"
20
+ }],
21
+ "related": {
22
+ "Cycle": {
23
+ "@type": "Cycle",
24
+ "products": [{
25
+ "@type": "Product",
26
+ "primary": "True",
27
+ "term.termType": "liveAquaticSpecies"
28
+ }]
29
+ }
22
30
  }
23
31
  }
24
32
  }
@@ -35,19 +43,26 @@ LOOKUPS = {
35
43
  "liveAquaticSpecies": "defaultSalinity"
36
44
  }
37
45
  TERM_ID = 'waterSalinity'
46
+ _MEASUREMENT_TERM_IDS = ["brackishWater", "salineWater", "freshWater"]
47
+ _DEFAULT_SALINITY = {
48
+ 'freshWater': 0,
49
+ 'brackishWater': 5000,
50
+ 'salineWater': 32000
51
+ }
38
52
 
39
53
 
40
54
  def _measurement(value: float, start_date: str = None, end_date: str = None):
41
55
  data = _new_measurement(TERM_ID, MODEL)
42
56
  data['value'] = [value]
43
- data['endDate'] = end_date
44
57
  if start_date:
45
58
  data['startDate'] = start_date
59
+ if end_date:
60
+ data['endDate'] = end_date
46
61
  data['methodClassification'] = MeasurementMethodClassification.EXPERT_OPINION.value
47
62
  return data if value is not None else None
48
63
 
49
64
 
50
- def _should_run(site: dict):
65
+ def _should_run_by_products(site: dict):
51
66
  cycles = related_cycles(site)
52
67
  relevant_products = [
53
68
  {
@@ -72,8 +87,8 @@ def _should_run(site: dict):
72
87
  return should_run, relevant_products
73
88
 
74
89
 
75
- def run(site: dict):
76
- should_run, values = _should_run(site)
90
+ def _run_by_products(site: dict):
91
+ should_run, values = _should_run_by_products(site)
77
92
  grouped_values = group_by_keys(values, ['start-date', 'end-date'])
78
93
  return non_empty_list([
79
94
  _measurement(
@@ -83,3 +98,33 @@ def run(site: dict):
83
98
  )
84
99
  for value in grouped_values.values()
85
100
  ]) if should_run else []
101
+
102
+
103
+ def _should_run_by_measurement(site: dict):
104
+ measurements = [
105
+ m for m in site.get('measurements', [])
106
+ if all([
107
+ m.get('term', {}).get('@id') in _MEASUREMENT_TERM_IDS,
108
+ not is_from_model(m),
109
+ m.get('value')
110
+ ])
111
+ ]
112
+
113
+ logRequirements(site, model=MODEL, term=TERM_ID,
114
+ existing_measurements=';'.join([m.get('term', {}).get('@id') for m in measurements]))
115
+
116
+ should_run = all([measurements])
117
+ logShouldRun(site, MODEL, TERM_ID, should_run)
118
+ return should_run, measurements
119
+
120
+
121
+ def _run_by_measurement(site: dict):
122
+ should_run, measurements = _should_run_by_measurement(site)
123
+ return [
124
+ _measurement(_DEFAULT_SALINITY[m.get('term', {}).get('@id')], m.get('startDate'), m.get('endDate'))
125
+ for m in measurements
126
+ ] if should_run else []
127
+
128
+
129
+ def run(site: dict):
130
+ return _run_by_measurement(site) or _run_by_products(site)
@@ -2,14 +2,15 @@ from os.path import dirname, abspath
2
2
  import sys
3
3
 
4
4
  from hestia_earth.models.utils import _run_in_serie
5
- from . import cycle, site
5
+ from . import cycle, site, remove_cache_fields
6
6
 
7
7
  CURRENT_DIR = dirname(abspath(__file__)) + '/'
8
8
  sys.path.append(CURRENT_DIR)
9
9
 
10
10
  MODELS = [
11
11
  cycle.run,
12
- site.run
12
+ site.run,
13
+ remove_cache_fields.run
13
14
  ]
14
15
 
15
16
 
@@ -0,0 +1,9 @@
1
+ def run(impact: dict):
2
+ cache_keys = [key for key in impact.keys() if key.startswith('cache')]
3
+ for key in cache_keys:
4
+ del impact[key]
5
+ try:
6
+ impact['added'].remove(key)
7
+ except Exception:
8
+ continue
9
+ return impact
@@ -0,0 +1,21 @@
1
+ from hestia_earth.utils.blank_node import group_by_keys, get_node_value, ArrayTreatment
2
+ from hestia_earth.utils.tools import non_empty_list
3
+
4
+ _GROUP_KEYS = ['term', 'methodModel', 'inputs', 'operation', 'country', 'region']
5
+
6
+
7
+ def _grouped_blank_node(blank_nodes: list):
8
+ blank_node = {
9
+ key: blank_nodes[0].get(key) for key in _GROUP_KEYS if blank_nodes[0].get(key)
10
+ } | {
11
+ 'value': non_empty_list([v.get('value') for v in blank_nodes])
12
+ }
13
+ value = get_node_value(blank_node, default_array_treatment=ArrayTreatment.SUM)
14
+ return blank_node | {'value': value}
15
+
16
+
17
+ def run(impact: dict):
18
+ blank_nodes = impact.get('emissionsResourceUse', [])
19
+ grouped_blank_nodes = group_by_keys(blank_nodes, _GROUP_KEYS)
20
+ grouped_blank_nodes = [_grouped_blank_node(value) for value in grouped_blank_nodes.values()]
21
+ return grouped_blank_nodes
@@ -23,6 +23,11 @@ def _run(impact: dict):
23
23
  site = _load_calculated_node(cycle.get('site', {}), SchemaType.SITE)
24
24
  if site:
25
25
  cycle['site'] = site
26
+
27
+ # need to download `otherSites` as well
28
+ if 'otherSites' in cycle:
29
+ cycle['otherSites'] = [_load_calculated_node(s, SchemaType.SITE) for s in cycle['otherSites']]
30
+
26
31
  return cycle
27
32
 
28
33