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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (23) hide show
  1. hestia_earth/models/geospatialDatabase/altitude.py +1 -0
  2. hestia_earth/models/geospatialDatabase/clayContent.py +3 -3
  3. hestia_earth/models/geospatialDatabase/sandContent.py +3 -3
  4. hestia_earth/models/geospatialDatabase/utils.py +1 -2
  5. hestia_earth/models/hestia/landCover.py +25 -12
  6. hestia_earth/models/ipcc2019/ch4ToAirOrganicSoilCultivation.py +7 -7
  7. hestia_earth/models/ipcc2019/co2ToAirOrganicSoilCultivation.py +5 -4
  8. hestia_earth/models/ipcc2019/n2OToAirOrganicSoilCultivationDirect.py +7 -7
  9. hestia_earth/models/ipcc2019/organicCarbonPerHa_tier_1.py +2 -2
  10. hestia_earth/models/ipcc2019/organicSoilCultivation_utils.py +11 -3
  11. hestia_earth/models/mocking/search-results.json +1296 -1310
  12. hestia_earth/models/utils/site.py +2 -1
  13. hestia_earth/models/version.py +1 -1
  14. {hestia_earth_models-0.70.3.dist-info → hestia_earth_models-0.70.4.dist-info}/METADATA +1 -1
  15. {hestia_earth_models-0.70.3.dist-info → hestia_earth_models-0.70.4.dist-info}/RECORD +23 -23
  16. tests/models/hestia/test_landCover.py +24 -1
  17. tests/models/ipcc2019/test_ch4ToAirOrganicSoilCultivation.py +2 -1
  18. tests/models/ipcc2019/test_co2ToAirOrganicSoilCultivation.py +2 -1
  19. tests/models/ipcc2019/test_n2OToAirOrganicSoilCultivationDirect.py +2 -1
  20. tests/models/ipcc2019/test_organicCarbonPerHa_tier_1.py +8 -3
  21. {hestia_earth_models-0.70.3.dist-info → hestia_earth_models-0.70.4.dist-info}/LICENSE +0 -0
  22. {hestia_earth_models-0.70.3.dist-info → hestia_earth_models-0.70.4.dist-info}/WHEEL +0 -0
  23. {hestia_earth_models-0.70.3.dist-info → hestia_earth_models-0.70.4.dist-info}/top_level.txt +0 -0
@@ -27,6 +27,7 @@ EE_PARAMS = {
27
27
  'collection': 'USGS/GMTED2010_FULL',
28
28
  'ee_type': 'raster',
29
29
  'band_name': 'med',
30
+ 'reducer': 'mode',
30
31
  'is_image': True
31
32
  }
32
33
  BIBLIO_TITLE = 'An Enhanced Global Elevation Model Generalized From Multiple Higher Resolution Source Datasets'
@@ -92,14 +92,14 @@ def _run(site: dict):
92
92
  def _should_run(site: dict):
93
93
  contains_geospatial_data = has_geospatial_data(site)
94
94
  below_max_area_size = should_download(TERM_ID, site)
95
- has_original_texture_measurements = has_original_by_ids(site.get('measurements', []), SOIL_TEXTURE_IDS)
95
+ has_no_original_texture_measurements = not has_original_by_ids(site.get('measurements', []), SOIL_TEXTURE_IDS)
96
96
 
97
97
  logRequirements(site, model=MODEL, term=TERM_ID,
98
98
  contains_geospatial_data=contains_geospatial_data,
99
99
  below_max_area_size=below_max_area_size,
100
- has_original_texture_measurements=has_original_texture_measurements)
100
+ has_no_original_texture_measurements=has_no_original_texture_measurements)
101
101
 
102
- should_run = all([contains_geospatial_data, below_max_area_size, not has_original_texture_measurements])
102
+ should_run = all([contains_geospatial_data, below_max_area_size, has_no_original_texture_measurements])
103
103
  logShouldRun(site, MODEL, TERM_ID, should_run)
104
104
  return should_run
105
105
 
@@ -92,14 +92,14 @@ def _run(site: dict):
92
92
  def _should_run(site: dict):
93
93
  contains_geospatial_data = has_geospatial_data(site)
94
94
  below_max_area_size = should_download(TERM_ID, site)
95
- has_original_texture_measurements = has_original_by_ids(site.get('measurements', []), SOIL_TEXTURE_IDS)
95
+ has_no_original_texture_measurements = not has_original_by_ids(site.get('measurements', []), SOIL_TEXTURE_IDS)
96
96
 
97
97
  logRequirements(site, model=MODEL, term=TERM_ID,
98
98
  contains_geospatial_data=contains_geospatial_data,
99
99
  below_max_area_size=below_max_area_size,
100
- has_original_texture_measurements=has_original_texture_measurements)
100
+ has_no_original_texture_measurements=has_no_original_texture_measurements)
101
101
 
102
- should_run = all([contains_geospatial_data, below_max_area_size, not has_original_texture_measurements])
102
+ should_run = all([contains_geospatial_data, below_max_area_size, has_no_original_texture_measurements])
103
103
  logShouldRun(site, MODEL, TERM_ID, should_run)
104
104
  return should_run
105
105
 
@@ -157,8 +157,7 @@ def _get_cached_data(term: str, site: dict, data: dict):
157
157
  isinstance(cache, dict),
158
158
  cache_sub_key
159
159
  ]) else cache
160
- if value is not None:
161
- debugValues(site, model=MODEL, term=term, value_from_cache=value)
160
+ debugValues(site, model=MODEL, term=term, value_from_cache=value)
162
161
  return value
163
162
 
164
163
 
@@ -16,7 +16,7 @@ from hestia_earth.models.utils.constant import DAYS_IN_YEAR
16
16
  from hestia_earth.models.utils.management import _new_management
17
17
  from hestia_earth.models.utils.term import get_lookup_value
18
18
  from hestia_earth.models.utils.lookup import get_region_lookup_value
19
- from hestia_earth.models.utils.blank_node import _node_date, DatestrFormat, _gapfill_datestr, DatestrGapfillMode
19
+ from hestia_earth.models.utils.blank_node import DatestrFormat, _gapfill_datestr, DatestrGapfillMode
20
20
  from .utils import (
21
21
  IPCC_LAND_USE_CATEGORY_ANNUAL,
22
22
  IPCC_LAND_USE_CATEGORY_PERENNIAL,
@@ -410,8 +410,15 @@ def _get_faostat_name(term: dict) -> str:
410
410
  return _get_lookup_with_cache(term, "cropGroupingFaostatArea")
411
411
 
412
412
 
413
+ def _get_most_common_or_alphabetically_first(crop_terms: list) -> str:
414
+ histogram = {term: crop_terms.count(term) for term in crop_terms}
415
+ max_freq = max(histogram.values())
416
+ # Sorted; to be deterministic
417
+ return sorted([term for term, freq in histogram.items() if freq == max_freq])[0]
418
+
419
+
413
420
  def _get_complete_faostat_to_crop_mapping() -> dict:
414
- """Returns mapping in the format: {faostat_name: IPPC_LAND_USE_CATEGORY, ...}"""
421
+ """Returns mapping in the format: {faostat_name: IPCC_LAND_USE_CATEGORY, ...}"""
415
422
  lookup = download_lookup("crop.csv")
416
423
  mappings = defaultdict(list)
417
424
  for crop_term_id in [row[0] for row in lookup]:
@@ -421,7 +428,7 @@ def _get_complete_faostat_to_crop_mapping() -> dict:
421
428
  if key:
422
429
  mappings[key].append(crop_ipcc_land_use_category(crop_term_id=crop_term_id, lookup_term_type="crop"))
423
430
  return {
424
- fao_name: max(set(crop_terms), key=crop_terms.count)
431
+ fao_name: _get_most_common_or_alphabetically_first(crop_terms)
425
432
  for fao_name, crop_terms in mappings.items()
426
433
  }
427
434
 
@@ -758,26 +765,32 @@ def _collect_land_use_types(nodes: list) -> list:
758
765
  "id": node.get("term", {}).get("@id"),
759
766
  "land-use-type": _get_land_use_term_from_node(node),
760
767
  "endDate": _gapfill_datestr(datestr=node.get("endDate"), mode=DatestrGapfillMode.END)[:10],
761
- "startDate": _gapfill_datestr(
762
- datestr=node.get("startDate"), mode=DatestrGapfillMode.START
763
- )[:10] if node.get("startDate") else None
768
+ "startDate": _gapfill_datestr(datestr=node.get("startDate"), mode=DatestrGapfillMode.START)[:10]
764
769
  } for node in nodes
765
770
  ]
766
771
 
767
772
 
768
- def _no_prior_land_cover_data(nodes: list, node: dict) -> bool:
773
+ def _no_prior_land_cover_data(nodes: list, target_node: dict) -> bool:
774
+ """
775
+ Returns true if there are no nodes whose start & end dates the target_node falls within,
776
+ including a tolerance.
777
+ """
769
778
  target_date = (
770
- datetime.strptime(node.get('startDate') or node.get('endDate'), DatestrFormat.YEAR_MONTH_DAY.value)
779
+ datetime.strptime(target_node.get('startDate') or target_node.get('endDate'),
780
+ DatestrFormat.YEAR_MONTH_DAY.value)
771
781
  - timedelta(days=DEFAULT_WINDOW_IN_YEARS * DAYS_IN_YEAR)
772
782
  )
783
+ tolerance = timedelta(days=DATE_TOLERANCE_IN_YEARS * DAYS_IN_YEAR)
773
784
  previous_nodes = [
774
785
  node for node in nodes
775
- if abs(_node_date(node) - target_date) < timedelta(days=DATE_TOLERANCE_IN_YEARS * DAYS_IN_YEAR)
786
+ if datetime.strptime(node.get("startDate"), DatestrFormat.YEAR_MONTH_DAY.value) - tolerance
787
+ < target_date <
788
+ datetime.strptime(node.get("endDate"), DatestrFormat.YEAR_MONTH_DAY.value) + tolerance
776
789
  ]
777
790
  return len(previous_nodes) == 0
778
791
 
779
792
 
780
- def _should_run(site: dict) -> tuple[bool, dict]:
793
+ def _should_run(site: dict) -> tuple[bool, list, dict]:
781
794
  management_nodes = _collect_land_use_types(
782
795
  [
783
796
  node for node in filter_list_term_type(site.get("management", []), TermTermType.LANDCOVER)
@@ -795,8 +808,8 @@ def _should_run(site: dict) -> tuple[bool, dict]:
795
808
  land_use_type = relevant_nodes[0].get("land-use-type") if relevant_nodes else None
796
809
 
797
810
  has_no_prior_land_cover_data = _no_prior_land_cover_data(
798
- nodes=relevant_nodes,
799
- node=relevant_nodes[-1:][0]
811
+ nodes=management_nodes,
812
+ target_node=relevant_nodes[-1:][0]
800
813
  ) if relevant_nodes else None
801
814
 
802
815
  should_run_nodes, site_area = _should_run_historical_land_use_change(
@@ -16,8 +16,8 @@ from hestia_earth.models.utils.measurement import most_relevant_measurement_valu
16
16
  from hestia_earth.models.utils.site import valid_site_type
17
17
 
18
18
  from .organicSoilCultivation_utils import (
19
- assign_ditch_category, assign_organic_soil_category, calc_emission, DitchCategory, get_ditch_frac,
20
- get_emission_factor, OrganicSoilCategory, remap_categories, valid_eco_climate_zone
19
+ assign_ditch_category, assign_organic_soil_category, calc_emission, DitchCategory, format_nd_array, format_number,
20
+ get_ditch_frac, get_emission_factor, OrganicSoilCategory, remap_categories, valid_eco_climate_zone
21
21
  )
22
22
  from . import MODEL
23
23
 
@@ -224,11 +224,11 @@ def _should_run(cycle: dict):
224
224
  eco_climate_zone=eco_climate_zone,
225
225
  organic_soil_category=organic_soil_category,
226
226
  ditch_category=ditch_category,
227
- emission_factor=f"{np.mean(emission_factor):.3f}",
228
- ditch_factor=f"{np.mean(ditch_factor):.3f}",
229
- ditch_frac=f"{np.mean(ditch_frac):.3f}",
230
- land_occupation=land_occupation,
231
- histosol=histosol
227
+ emission_factor=format_nd_array(emission_factor),
228
+ ditch_factor=format_nd_array(ditch_factor),
229
+ ditch_frac=format_nd_array(ditch_frac),
230
+ land_occupation=format_number(land_occupation),
231
+ histosol=format_number(histosol)
232
232
  )
233
233
 
234
234
  should_run = all([
@@ -14,7 +14,8 @@ from hestia_earth.models.utils.measurement import most_relevant_measurement_valu
14
14
  from hestia_earth.models.utils.site import valid_site_type
15
15
 
16
16
  from .organicSoilCultivation_utils import (
17
- assign_organic_soil_category, calc_emission, get_emission_factor, OrganicSoilCategory, valid_eco_climate_zone
17
+ assign_organic_soil_category, calc_emission, format_nd_array, format_number, get_emission_factor,
18
+ OrganicSoilCategory, valid_eco_climate_zone
18
19
  )
19
20
  from . import MODEL
20
21
 
@@ -182,9 +183,9 @@ def _should_run(cycle: dict):
182
183
  cycle, model=MODEL, term=TERM_ID,
183
184
  eco_climate_zone=eco_climate_zone,
184
185
  organic_soil_category=organic_soil_category,
185
- emission_factor=f"{np.mean(emission_factor):.3f}",
186
- land_occupation=land_occupation,
187
- histosol=histosol
186
+ emission_factor=format_nd_array(emission_factor),
187
+ land_occupation=format_number(land_occupation),
188
+ histosol=format_number(histosol)
188
189
  )
189
190
 
190
191
  should_run = all([
@@ -8,8 +8,8 @@ from hestia_earth.models.utils.measurement import most_relevant_measurement_valu
8
8
  from hestia_earth.models.utils.site import valid_site_type
9
9
 
10
10
  from .organicSoilCultivation_utils import (
11
- assign_organic_soil_category, calc_emission, get_emission_factor, OrganicSoilCategory, remap_categories,
12
- valid_eco_climate_zone
11
+ assign_organic_soil_category, calc_emission, format_number, get_emission_factor, OrganicSoilCategory,
12
+ remap_categories, valid_eco_climate_zone
13
13
  )
14
14
  from . import MODEL
15
15
 
@@ -127,9 +127,9 @@ def _should_run(cycle: dict):
127
127
  cycle, model=MODEL, term=TERM_ID,
128
128
  eco_climate_zone=eco_climate_zone,
129
129
  organic_soil_category=organic_soil_category,
130
- emission_factor=f"{emission_factor_mean} ± {emission_factor_sd}",
131
- land_occupation=land_occupation,
132
- histosol=histosol
130
+ emission_factor=f"{format_number(emission_factor_mean)} ± {format_number(emission_factor_sd)}",
131
+ land_occupation=format_number(land_occupation),
132
+ histosol=format_number(histosol)
133
133
  )
134
134
 
135
135
  should_run = all([
@@ -151,8 +151,8 @@ def _should_run(cycle: dict):
151
151
 
152
152
 
153
153
  def _run(emission_factor_mean: float, emission_factor_sd: float, histosol: float, land_occupation: float):
154
- value = calc_emission(TERM_ID, emission_factor_mean, histosol, land_occupation)
155
- sd = calc_emission(TERM_ID, emission_factor_sd, histosol, land_occupation)
154
+ value = round(calc_emission(TERM_ID, emission_factor_mean, histosol, land_occupation), 6)
155
+ sd = round(calc_emission(TERM_ID, emission_factor_sd, histosol, land_occupation), 6)
156
156
  return [_emission(value, sd)]
157
157
 
158
158
 
@@ -1346,7 +1346,7 @@ def _assign_ipcc_management_category(
1346
1346
  ipcc_land_use_category, IpccManagementCategory.NOT_RELEVANT
1347
1347
  )
1348
1348
 
1349
- land_cover_nodes = filter_list_term_type(management_nodes, [TermTermType.LANDCOVER])
1349
+ land_cover_nodes = filter_list_term_type(management_nodes, [TermTermType.PASTUREMANAGEMENT])
1350
1350
  tillage_nodes = filter_list_term_type(management_nodes, [TermTermType.TILLAGE])
1351
1351
 
1352
1352
  should_run_ = any([
@@ -1461,7 +1461,7 @@ Value: Corresponding decision tree for IPCC management categories based on land
1461
1461
  """
1462
1462
 
1463
1463
  _IPCC_LAND_USE_CATEGORY_TO_DEFAULT_IPCC_MANAGEMENT_CATEGORY = {
1464
- IpccLandUseCategory.GRASSLAND: IpccManagementCategory.UNKNOWN,
1464
+ IpccLandUseCategory.GRASSLAND: IpccManagementCategory.NOMINALLY_MANAGED,
1465
1465
  IpccLandUseCategory.ANNUAL_CROPS_WET: IpccManagementCategory.UNKNOWN,
1466
1466
  IpccLandUseCategory.ANNUAL_CROPS: IpccManagementCategory.UNKNOWN
1467
1467
  }
@@ -1,5 +1,6 @@
1
1
  from enum import Enum
2
2
  from typing import Literal
3
+ import numpy as np
3
4
 
4
5
  from hestia_earth.schema import SiteSiteType
5
6
  from hestia_earth.utils.model import find_primary_product
@@ -22,8 +23,7 @@ _NETHERLANDS_TERM_ID = "GADM-NLD"
22
23
 
23
24
  _CONVERSION_FACTORS = {
24
25
  "co2ToAirOrganicSoilCultivation": 1000 * get_atomic_conversion(Units.KG_CO2, Units.TO_C),
25
- "ch4ToAirOrganicSoilCultivation": 1000,
26
- "n2OToAirOrganicSoilCultivationDirect": 1000 * get_atomic_conversion(Units.KG_N2O, Units.TO_N)
26
+ "n2OToAirOrganicSoilCultivationDirect": get_atomic_conversion(Units.KG_N2O, Units.TO_N)
27
27
  }
28
28
  _DEFAULT_FACTOR = {"value": 0}
29
29
  _EXCLUDED_ECO_CLIMATE_ZONES = [EcoClimateZone.POLAR_MOIST, EcoClimateZone.POLAR_DRY]
@@ -136,7 +136,7 @@ def calc_emission(
136
136
  """
137
137
  Calculate the emission and convert it to kg/ha-1.
138
138
  """
139
- return emission_factor * land_occupation * histosol * _CONVERSION_FACTORS[emission_id] / 100
139
+ return emission_factor * land_occupation * histosol * _CONVERSION_FACTORS.get(emission_id, 1) / 100
140
140
 
141
141
 
142
142
  def remap_categories(
@@ -157,3 +157,11 @@ def valid_eco_climate_zone(
157
157
  Validate that the model should run for a specific eco-climate zone.
158
158
  """
159
159
  return isinstance(eco_climate_zone, EcoClimateZone) and eco_climate_zone not in _EXCLUDED_ECO_CLIMATE_ZONES
160
+
161
+
162
+ def format_number(value) -> str:
163
+ return f"{value:.3g}" if isinstance(value, (float, int)) else "None"
164
+
165
+
166
+ def format_nd_array(value) -> str:
167
+ return f"{np.mean(value):.3g} ± {np.std(value):.3g}" if isinstance(value, np.ndarray) else "None"