hestia-earth-models 0.73.5__py3-none-any.whl → 0.73.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.

@@ -2093,51 +2093,6 @@
2093
2093
  },
2094
2094
  "stage": 2
2095
2095
  },
2096
- {
2097
- "key": "emissions",
2098
- "model": "ipcc2006",
2099
- "value": "n2OToAirCropResidueDecompositionIndirect",
2100
- "runStrategy": "add_blank_node_if_missing",
2101
- "runArgs": {
2102
- "runNonMeasured": true,
2103
- "runNonAddedTerm": true
2104
- },
2105
- "mergeStrategy": "list",
2106
- "mergeArgs": {
2107
- "replaceThreshold": ["value", 0.01]
2108
- },
2109
- "stage": 2
2110
- },
2111
- {
2112
- "key": "emissions",
2113
- "model": "ipcc2006",
2114
- "value": "n2OToAirCropResidueDecompositionDirect",
2115
- "runStrategy": "add_blank_node_if_missing",
2116
- "runArgs": {
2117
- "runNonMeasured": true,
2118
- "runNonAddedTerm": true
2119
- },
2120
- "mergeStrategy": "list",
2121
- "mergeArgs": {
2122
- "replaceThreshold": ["value", 0.01]
2123
- },
2124
- "stage": 2
2125
- },
2126
- {
2127
- "key": "emissions",
2128
- "model": "ipcc2006",
2129
- "value": "n2OToAirExcretaIndirect",
2130
- "runStrategy": "add_blank_node_if_missing",
2131
- "runArgs": {
2132
- "runNonMeasured": true,
2133
- "runNonAddedTerm": true
2134
- },
2135
- "mergeStrategy": "list",
2136
- "mergeArgs": {
2137
- "replaceThreshold": ["value", 0.01]
2138
- },
2139
- "stage": 2
2140
- },
2141
2096
  {
2142
2097
  "key": "emissions",
2143
2098
  "model": "jarvisAndPain1994",
@@ -203,30 +203,6 @@
203
203
  "stage": 1
204
204
  }
205
205
  ],
206
- [
207
- {
208
- "key": "emissionsResourceUse",
209
- "model": "faostat2018",
210
- "value": "landTransformation20YearAverageDuringCycle",
211
- "runStrategy": "always",
212
- "mergeStrategy": "list",
213
- "mergeArgs": {
214
- "replaceThreshold": ["value", 0.01]
215
- },
216
- "stage": 1
217
- },
218
- {
219
- "key": "emissionsResourceUse",
220
- "model": "faostat2018",
221
- "value": "landTransformation100YearAverageDuringCycle",
222
- "runStrategy": "always",
223
- "mergeStrategy": "list",
224
- "mergeArgs": {
225
- "replaceThreshold": ["value", 0.01]
226
- },
227
- "stage": 1
228
- }
229
- ],
230
206
  {
231
207
  "key": "emissionsResourceUse",
232
208
  "model": "resourceUseNotRelevant",
@@ -2,6 +2,7 @@ from hestia_earth.schema import MeasurementMethodClassification
2
2
  from hestia_earth.utils.tools import non_empty_list
3
3
 
4
4
  from hestia_earth.models.log import logRequirements, logShouldRun
5
+ from hestia_earth.models.utils import max_date
5
6
  from hestia_earth.models.utils.measurement import _new_measurement
6
7
  from hestia_earth.models.utils.site import related_years
7
8
  from .utils import download, has_geospatial_data, should_download
@@ -39,7 +40,7 @@ def _measurement(value: float, year: int):
39
40
  measurement['value'] = [value]
40
41
  measurement['methodClassification'] = MeasurementMethodClassification.GEOSPATIAL_DATASET.value
41
42
  measurement['startDate'] = f"{year}-01-01"
42
- measurement['endDate'] = f"{year}-12-31"
43
+ measurement['endDate'] = max_date(f"{year}-12-31")
43
44
  return measurement
44
45
 
45
46
 
@@ -2,6 +2,7 @@ from hestia_earth.schema import MeasurementMethodClassification
2
2
  from hestia_earth.utils.tools import non_empty_list
3
3
 
4
4
  from hestia_earth.models.log import logRequirements, logShouldRun
5
+ from hestia_earth.models.utils import max_date
5
6
  from hestia_earth.models.utils.measurement import _new_measurement
6
7
  from hestia_earth.models.utils.source import get_source
7
8
  from hestia_earth.models.utils.site import related_years
@@ -41,7 +42,7 @@ def _measurement(site: dict, value: float, year: int):
41
42
  measurement['value'] = [value]
42
43
  measurement['methodClassification'] = MeasurementMethodClassification.GEOSPATIAL_DATASET.value
43
44
  measurement['startDate'] = f"{year}-01-01"
44
- measurement['endDate'] = f"{year}-12-31"
45
+ measurement['endDate'] = max_date(f"{year}-12-31")
45
46
  return measurement | get_source(site, BIBLIO_TITLE)
46
47
 
47
48
 
@@ -2,6 +2,7 @@ from hestia_earth.schema import MeasurementMethodClassification
2
2
  from hestia_earth.utils.tools import non_empty_list
3
3
 
4
4
  from hestia_earth.models.log import logRequirements, logShouldRun
5
+ from hestia_earth.models.utils import max_date
5
6
  from hestia_earth.models.utils.measurement import _new_measurement
6
7
  from hestia_earth.models.utils.source import get_source
7
8
  from hestia_earth.models.utils.site import related_years
@@ -40,7 +41,7 @@ def _measurement(site: dict, value: float, year: int):
40
41
  measurement = _new_measurement(TERM_ID)
41
42
  measurement['value'] = [value]
42
43
  measurement['startDate'] = f"{year}-01-01"
43
- measurement['endDate'] = f"{year}-12-31"
44
+ measurement['endDate'] = max_date(f"{year}-12-31")
44
45
  measurement['methodClassification'] = MeasurementMethodClassification.GEOSPATIAL_DATASET.value
45
46
  return measurement | get_source(site, BIBLIO_TITLE)
46
47
 
@@ -2,7 +2,6 @@ import functools
2
2
  import math
3
3
  from collections import defaultdict
4
4
  from datetime import datetime, timedelta
5
-
6
5
  from hestia_earth.schema import SiteSiteType, TermTermType
7
6
  from hestia_earth.utils.lookup import (
8
7
  download_lookup, get_table_value, column_name, _is_missing_value, extract_grouped_data, lookup_columns
@@ -87,18 +86,30 @@ LOOKUPS = {
87
86
  }
88
87
  MODEL_KEY = 'landCover'
89
88
 
90
- LAND_AREA = LOOKUPS["region-faostatArea"][3]
91
- SITE_TYPES = {
89
+ _LAND_AREA = LOOKUPS["region-faostatArea"][3]
90
+ _ALLOWED_SITE_TYPES = {
92
91
  SiteSiteType.CROPLAND.value,
93
92
  SiteSiteType.FOREST.value,
94
93
  SiteSiteType.OTHER_NATURAL_VEGETATION.value,
95
94
  SiteSiteType.PERMANENT_PASTURE.value
96
95
  }
97
- DEFAULT_WINDOW_IN_YEARS = 20
98
- DATE_TOLERANCE_IN_YEARS = 2
99
- OUTPUT_SIGNIFICANT_DIGITS = 3
100
- ALLOWED_LAND_USE_TYPES = [ANNUAL_CROPLAND, PERMANENT_CROPLAND, PERMANENT_PASTURE]
96
+ _BUILDING_SITE_TYPES = [
97
+ SiteSiteType.AGRI_FOOD_PROCESSOR.value,
98
+ SiteSiteType.ANIMAL_HOUSING.value,
99
+ SiteSiteType.FOOD_RETAILER.value
100
+ ]
101
+ _DEFAULT_WINDOW_IN_YEARS = 20
102
+ _DATE_TOLERANCE_IN_YEARS = 2
103
+ _OUTPUT_SIGNIFICANT_DIGITS = 3
104
+ _ALLOWED_LAND_USE_TYPES = [ANNUAL_CROPLAND, PERMANENT_CROPLAND, PERMANENT_PASTURE]
101
105
  _LOOKUP_EXPANSION = "region-crop-cropGroupingFaostatProduction-areaHarvestedUpTo20YearExpansion.csv"
106
+ _COMPLETE_CHANGES_OTHER_LAND = {
107
+ OTHER_LAND: 1,
108
+ FOREST_LAND: 0,
109
+ PERMANENT_PASTURE: 0,
110
+ ANNUAL_CROPLAND: 0,
111
+ PERMANENT_CROPLAND: 0
112
+ }
102
113
 
103
114
 
104
115
  def _get_lookup_with_cache(lookup_term, column):
@@ -121,6 +132,10 @@ def _get_lookup_with_cache(lookup_term, column):
121
132
  )
122
133
 
123
134
 
135
+ def _get_land_cover_lookup_suffix(land_type: str) -> str:
136
+ return LAND_USE_TERMS_FOR_TRANSFORMATION[land_type][0]
137
+
138
+
124
139
  def get_landCover_lookups(country_id: str, end_year: int, product_name: str):
125
140
  """
126
141
  Attempts to get the pre-calculated values for the landCover model calculation.
@@ -128,15 +143,16 @@ def get_landCover_lookups(country_id: str, end_year: int, product_name: str):
128
143
  "Permanent crops": <value>, "Permanent meadows and pastures": <value>}
129
144
  Missing values are returned as None.
130
145
  """
146
+ lookup_prefix = 'region-crop-cropGroupingFAOSTAT-landCover'
131
147
  return {
132
148
  # Divide by 100 to match site_area ratios
133
- suffix: value / 100 if value is not None else value
134
- for suffix, value in
149
+ land_type: value / 100 if value is not None else value
150
+ for land_type, value in
135
151
  {
136
- suffix: safe_parse_float(
152
+ land_type: safe_parse_float(
137
153
  value=extract_grouped_data(
138
154
  data=get_region_lookup_value(
139
- lookup_name=f"region-crop-cropGroupingFAOSTAT-landCover-{suffix}.csv",
155
+ lookup_name=f"{lookup_prefix}-{_get_land_cover_lookup_suffix(land_type)}.csv",
140
156
  term_id=country_id,
141
157
  column=product_name,
142
158
  model=MODEL,
@@ -146,7 +162,7 @@ def get_landCover_lookups(country_id: str, end_year: int, product_name: str):
146
162
  ),
147
163
  default=None
148
164
  )
149
- for suffix in LAND_USE_TERMS_FOR_TRANSFORMATION.keys()
165
+ for land_type in LAND_USE_TERMS_FOR_TRANSFORMATION.keys()
150
166
  }.items()
151
167
  }
152
168
 
@@ -211,7 +227,7 @@ def _get_changes(country_id: str, end_year: int) -> tuple[dict, bool]:
211
227
  ),
212
228
  default=None
213
229
  )
214
- for land_use_term in ALL_LAND_USE_TERMS + [LAND_AREA]
230
+ for land_use_term in ALL_LAND_USE_TERMS + [_LAND_AREA]
215
231
  }
216
232
  missing_changes = [k for k, v in changes_dict.items() if v is None]
217
233
  changes_dict = {k: v if v is not None else 0 for k, v in changes_dict.items()}
@@ -302,7 +318,7 @@ def _allocate_pasture_loss_to_cropland(changes: dict, land_required_for_cropland
302
318
 
303
319
 
304
320
  def _allocate_other_land(
305
- changes: dict, max_forest_loss_to: dict, pasture_loss_to_cropland: float, cropland_loss_to_pasture: float
321
+ changes: dict, max_forest_loss_to: dict, pasture_loss_to_cropland: float, cropland_loss_to_pasture: float
306
322
  ) -> dict:
307
323
  """Allocate changes between Other land and cropland"""
308
324
  other_land_loss_to_cropland = (
@@ -496,7 +512,7 @@ def _get_term_id_for_crop(nodes: set, land_type: str) -> str:
496
512
 
497
513
 
498
514
  def _run(site: dict, existing_nodes: list, percentage_transformed_from: dict):
499
- start_year = _get_year_from_landCover(existing_nodes[0]) - DEFAULT_WINDOW_IN_YEARS
515
+ start_year = _get_year_from_landCover(existing_nodes[0]) - _DEFAULT_WINDOW_IN_YEARS
500
516
 
501
517
  """Creates a list of new management nodes, excluding any dates matching existing ones."""
502
518
  existing_nodes_set = {
@@ -511,7 +527,7 @@ def _run(site: dict, existing_nodes: list, percentage_transformed_from: dict):
511
527
  "land_type": land_type,
512
528
  "percentage": 0 if ratio == -0.0 else to_precision(
513
529
  number=ratio * 100,
514
- digits=OUTPUT_SIGNIFICANT_DIGITS
530
+ digits=_OUTPUT_SIGNIFICANT_DIGITS
515
531
  ),
516
532
  "term_id": _get_term_id_for_crop(existing_nodes_set, land_type=land_type)
517
533
  }
@@ -600,15 +616,17 @@ def _get_sums_of_crop_expansion(country_id: str, year: int, include_negatives: b
600
616
  def _get_net_expansion_cultivated_vs_harvested(
601
617
  annual_crops_net_expansion, changes, land_use_type, permanent_crops_net_expansion
602
618
  ):
603
- if land_use_type == ANNUAL_CROPLAND:
604
- net_expansion_cultivated_vs_harvested = _safe_divide(numerator=max(0, changes[ANNUAL_CROPLAND]),
605
- denominator=(annual_crops_net_expansion / 1000))
606
- elif land_use_type == PERMANENT_CROPLAND:
607
- net_expansion_cultivated_vs_harvested = _safe_divide(numerator=max(0, changes[PERMANENT_CROPLAND]),
608
- denominator=(permanent_crops_net_expansion / 1000))
609
- else:
610
- net_expansion_cultivated_vs_harvested = 1
611
- return net_expansion_cultivated_vs_harvested
619
+ return (
620
+ _safe_divide(
621
+ numerator=max(0, changes[ANNUAL_CROPLAND]),
622
+ denominator=(annual_crops_net_expansion / 1000)
623
+ ) if land_use_type == ANNUAL_CROPLAND else
624
+ _safe_divide(
625
+ numerator=max(0, changes[PERMANENT_CROPLAND]),
626
+ denominator=(permanent_crops_net_expansion / 1000)
627
+ ) if land_use_type == PERMANENT_CROPLAND else
628
+ 1
629
+ )
612
630
 
613
631
 
614
632
  def _get_year_from_landCover(node: dict):
@@ -774,7 +792,7 @@ def _should_run_historical_land_use_change_single_crop(
774
792
  capped_site_area = cap_values(dictionary=_scale_site_area_errors(site_area))
775
793
 
776
794
  sum_of_site_areas_is_100 = site_area_sum_to_100(capped_site_area)
777
- site_type_allowed = site.get("siteType") in SITE_TYPES
795
+ site_type_allowed = site.get("siteType") in _ALLOWED_SITE_TYPES
778
796
 
779
797
  logRequirements(
780
798
  log_node=site,
@@ -822,9 +840,9 @@ def _no_prior_land_cover_data(nodes: list, target_node: dict) -> bool:
822
840
  target_date = (
823
841
  datetime.strptime(target_node.get('startDate') or target_node.get('endDate'),
824
842
  DatestrFormat.YEAR_MONTH_DAY.value)
825
- - timedelta(days=DEFAULT_WINDOW_IN_YEARS * DAYS_IN_YEAR)
843
+ - timedelta(days=_DEFAULT_WINDOW_IN_YEARS * DAYS_IN_YEAR)
826
844
  )
827
- tolerance = timedelta(days=DATE_TOLERANCE_IN_YEARS * DAYS_IN_YEAR)
845
+ tolerance = timedelta(days=_DATE_TOLERANCE_IN_YEARS * DAYS_IN_YEAR)
828
846
  previous_nodes = [
829
847
  node for node in nodes
830
848
  if datetime.strptime(node.get("startDate"), DatestrFormat.YEAR_MONTH_DAY.value) - tolerance
@@ -835,16 +853,18 @@ def _no_prior_land_cover_data(nodes: list, target_node: dict) -> bool:
835
853
 
836
854
 
837
855
  def _should_run(site: dict) -> tuple[bool, list, dict]:
838
- management_nodes = _collect_land_use_types(
839
- [
840
- node for node in filter_list_term_type(site.get("management", []), TermTermType.LANDCOVER)
841
- if not _should_group_landCover(node)
842
- ]
843
- )
856
+ is_site_building = site.get('siteType') in _BUILDING_SITE_TYPES
857
+
858
+ allowed_land_use_types = _ALLOWED_LAND_USE_TYPES + ([OTHER_LAND] if is_site_building else [])
859
+
860
+ management_nodes = _collect_land_use_types([
861
+ node for node in filter_list_term_type(site.get("management", []), TermTermType.LANDCOVER)
862
+ if not _should_group_landCover(node)
863
+ ])
844
864
  relevant_nodes = sorted(
845
865
  [
846
866
  node for node in management_nodes
847
- if node["land-use-type"] in ALLOWED_LAND_USE_TYPES
867
+ if node["land-use-type"] in allowed_land_use_types
848
868
  ],
849
869
  key=lambda n: n.get("startDate") or n.get("endDate")
850
870
  )
@@ -865,6 +885,7 @@ def _should_run(site: dict) -> tuple[bool, list, dict]:
865
885
  should_run_nodes, site_area = (
866
886
  (False, {}) if not all([land_use_type, has_no_prior_land_cover_data])
867
887
  else (True, landCover_from_lookups) if landCover_from_lookups and all(landCover_from_lookups.values())
888
+ else (True, _COMPLETE_CHANGES_OTHER_LAND) if is_site_building
868
889
  else _should_run_historical_land_use_change(
869
890
  site=site,
870
891
  nodes=relevant_nodes,
@@ -875,10 +896,12 @@ def _should_run(site: dict) -> tuple[bool, list, dict]:
875
896
  logRequirements(site, model=MODEL, model_key=MODEL_KEY,
876
897
  has_management_nodes=bool(relevant_nodes),
877
898
  land_use_type=land_use_type,
878
- allowed_land_use_types=';'.join(ALLOWED_LAND_USE_TYPES),
899
+ allowed_land_use_types=';'.join(allowed_land_use_types),
879
900
  has_no_prior_land_cover_data=has_no_prior_land_cover_data,
880
901
  management_nodes=log_as_table([_omit(n, ['term']) for n in relevant_nodes]),
881
- landCover_from_lookups=landCover_from_lookups,
902
+ landCover_from_lookups=log_as_table([
903
+ {land_type: value} for land_type, value in landCover_from_lookups.items()
904
+ ]),
882
905
  should_run_nodes=should_run_nodes)
883
906
 
884
907
  should_run = all([land_use_type, has_no_prior_land_cover_data, should_run_nodes])