hestia-earth-models 0.74.13__py3-none-any.whl → 0.74.15__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 (39) hide show
  1. hestia_earth/models/config/ImpactAssessment.json +4 -2
  2. hestia_earth/models/config/Site.json +11 -3
  3. hestia_earth/models/ecoalimV9/cycle.py +20 -5
  4. hestia_earth/models/ecoalimV9/utils.py +52 -2
  5. hestia_earth/models/emepEea2019/fuelCombustion_utils.py +21 -21
  6. hestia_earth/models/environmentalFootprintV3_1/environmentalFootprintSingleOverallScore.py +27 -18
  7. hestia_earth/models/hestia/landCover.py +27 -41
  8. hestia_earth/models/hestia/landCover_utils.py +40 -45
  9. hestia_earth/models/hestia/landOccupationDuringCycle.py +9 -27
  10. hestia_earth/models/hestia/soilClassification.py +314 -0
  11. hestia_earth/models/ipcc2019/aboveGroundBiomass.py +5 -15
  12. hestia_earth/models/ipcc2019/belowGroundBiomass.py +5 -15
  13. hestia_earth/models/ipcc2019/biocharOrganicCarbonPerHa.py +5 -39
  14. hestia_earth/models/ipcc2019/ch4ToAirEntericFermentation.py +2 -1
  15. hestia_earth/models/ipcc2019/ch4ToAirOrganicSoilCultivation.py +5 -5
  16. hestia_earth/models/ipcc2019/co2ToAirCarbonStockChange_utils.py +6 -21
  17. hestia_earth/models/ipcc2019/co2ToAirOrganicSoilCultivation.py +4 -5
  18. hestia_earth/models/ipcc2019/n2OToAirOrganicSoilCultivationDirect.py +5 -5
  19. hestia_earth/models/ipcc2019/nonCo2EmissionsToAirNaturalVegetationBurning.py +17 -46
  20. hestia_earth/models/ipcc2019/organicCarbonPerHa.py +10 -10
  21. hestia_earth/models/ipcc2019/organicCarbonPerHa_utils.py +4 -19
  22. hestia_earth/models/ipcc2019/organicSoilCultivation_utils.py +0 -9
  23. hestia_earth/models/log.py +71 -1
  24. hestia_earth/models/mocking/search-results.json +1 -1
  25. hestia_earth/models/utils/__init__.py +2 -2
  26. hestia_earth/models/utils/aggregated.py +3 -3
  27. hestia_earth/models/utils/blank_node.py +12 -4
  28. hestia_earth/models/utils/lookup.py +9 -10
  29. hestia_earth/models/version.py +1 -1
  30. {hestia_earth_models-0.74.13.dist-info → hestia_earth_models-0.74.15.dist-info}/METADATA +2 -2
  31. {hestia_earth_models-0.74.13.dist-info → hestia_earth_models-0.74.15.dist-info}/RECORD +39 -36
  32. tests/models/ecoalimV9/test_cycle.py +3 -2
  33. tests/models/hestia/test_landCover.py +1 -1
  34. tests/models/hestia/test_soilClassification.py +72 -0
  35. tests/models/ipcc2019/test_organicCarbonPerHa_utils.py +4 -48
  36. tests/models/test_log.py +128 -0
  37. {hestia_earth_models-0.74.13.dist-info → hestia_earth_models-0.74.15.dist-info}/LICENSE +0 -0
  38. {hestia_earth_models-0.74.13.dist-info → hestia_earth_models-0.74.15.dist-info}/WHEEL +0 -0
  39. {hestia_earth_models-0.74.13.dist-info → hestia_earth_models-0.74.15.dist-info}/top_level.txt +0 -0
@@ -10,7 +10,10 @@ from hestia_earth.utils.tools import safe_parse_float
10
10
  from hestia_earth.utils.stats import gen_seed, repeat_single, truncated_normal_1d
11
11
  from hestia_earth.utils.descriptive_stats import calc_descriptive_stats
12
12
 
13
- from hestia_earth.models.log import debugMissingLookup, log_as_table, logRequirements, logShouldRun
13
+ from hestia_earth.models.log import (
14
+ debugMissingLookup, format_bool, format_decimal_percentage, format_float, format_nd_array, format_str, log_as_table,
15
+ logRequirements, logShouldRun
16
+ )
14
17
  from hestia_earth.models.utils.blank_node import group_nodes_by_year
15
18
  from hestia_earth.models.utils.ecoClimateZone import EcoClimateZone, get_eco_climate_zone_value
16
19
  from hestia_earth.models.utils.emission import _new_emission
@@ -670,60 +673,28 @@ def _compile_inventory(
670
673
  return should_run, inventory, logs
671
674
 
672
675
 
673
- def _format_bool(value: Optional[bool]) -> str:
674
- """Format a bool for logging in a table."""
675
- return str(bool(value))
676
-
677
-
678
- def _format_number(value: Optional[float], unit: Optional[str] = None) -> str:
679
- """Format a float for logging in a table."""
680
- return f"{value:.1f}{f' {unit}' if unit else ''}" if isinstance(value, (float, int)) else "None"
681
-
682
-
683
- def _format_nd_array(value: Optional[npt.NDArray], unit: Optional[str] = None) -> str:
684
- """Format a numpy array for logging in a table."""
685
- return (
686
- f"{_format_number(value.mean())} ± {_format_number(value.std())}" + f"{f' {unit}' if unit else ''}"
687
- if isinstance(value, np.ndarray) else "None"
688
- )
689
-
690
-
691
- def _format_decimal_percentage(value: Optional[float], unit: Optional[str] = "pct") -> str:
692
- """Format a decimal percentage (0-1) as a percentage (0-100%) for logging in a table."""
693
- return _format_number(value * 100, unit) if isinstance(value, (float, int)) else "None"
694
-
695
-
696
- _INVALID_CHARS = {"_", ":", ",", "="}
697
- _REPLACEMENT_CHAR = "-"
698
-
699
-
700
- def _format_str(value: str, *_) -> str:
701
- """Format a string for logging in a table. Remove all characters used to render the table on the front end."""
702
- return reduce(lambda x, char: x.replace(char, _REPLACEMENT_CHAR), _INVALID_CHARS, str(value))
703
-
704
-
705
676
  def _format_column_header(*keys: tuple[Union[Enum, str], ...]) -> str:
706
677
  """Format a variable number of enums and strings for logging as a table column header."""
707
- return " ".join(_format_str(k.value if isinstance(k, Enum) else str(k)) for k in keys)
678
+ return " ".join(format_str(k.value if isinstance(k, Enum) else format_str(k)) for k in keys)
708
679
 
709
680
 
710
681
  def _format_eco_climate_zone(value: EcoClimateZone) -> str:
711
682
  """Format an eco-climate zone for logging."""
712
683
  return (
713
- _format_str(str(value.name).lower().replace("_", " ").capitalize()) if isinstance(value, EcoClimateZone)
684
+ format_str(str(value.name).lower().replace("_", " ").capitalize()) if isinstance(value, EcoClimateZone)
714
685
  else "None"
715
686
  )
716
687
 
717
688
 
718
689
  _LOGS_FORMAT_DATA: dict[str, Callable] = {
719
- "has_valid_site_type": _format_bool,
690
+ "has_valid_site_type": format_bool,
720
691
  "eco_climate_zone": _format_eco_climate_zone,
721
- "has_valid_eco_climate_zone": _format_bool,
722
- "has_land_cover_nodes": _format_bool,
723
- "should_compile_inventory": _format_bool,
724
- "percent_burned": lambda x: _format_number(x, "pct"),
692
+ "has_valid_eco_climate_zone": format_bool,
693
+ "has_land_cover_nodes": format_bool,
694
+ "should_compile_inventory": format_bool,
695
+ "percent_burned": lambda x: format_float(x, "pct"),
725
696
  }
726
- _DEFAULT_FORMAT_FUNC = _format_str
697
+ _DEFAULT_FORMAT_FUNC = format_str
727
698
 
728
699
 
729
700
  def _format_logs(logs: dict) -> dict[str, str]:
@@ -735,23 +706,23 @@ def _format_logs(logs: dict) -> dict[str, str]:
735
706
 
736
707
  _INVENTORY_FORMAT_DATA: dict[_InventoryKey, dict[Literal["filter_by", "format_func"], Any]] = {
737
708
  "fuel_burnt_per_category": {
738
- "format_func": lambda x: _format_nd_array(x, "kg")
709
+ "format_func": lambda x: format_nd_array(x, "kg")
739
710
  },
740
711
  "annual_emissions": {
741
712
  "filter_by": ("term_id", ),
742
- "format_func": lambda x: _format_nd_array(x, "kg")
713
+ "format_func": lambda x: format_nd_array(x, "kg")
743
714
  },
744
715
  "amortised_emissions": {
745
716
  "filter_by": ("term_id", ),
746
- "format_func": lambda x: _format_nd_array(x, "kg")
717
+ "format_func": lambda x: format_nd_array(x, "kg")
747
718
  },
748
719
  "share_of_emissions": {
749
720
  "filter_by": ("cycle_id", ),
750
- "format_func": _format_decimal_percentage
721
+ "format_func": format_decimal_percentage
751
722
  },
752
723
  "allocated_emissions": {
753
724
  "filter_by": ("term_id", "cycle_id"),
754
- "format_func": lambda x: _format_nd_array(x, "kg")
725
+ "format_func": lambda x: format_nd_array(x, "kg")
755
726
  }
756
727
  }
757
728
  """
@@ -2,9 +2,9 @@ from functools import reduce
2
2
  from pydash.objects import merge
3
3
  from types import ModuleType
4
4
 
5
- from hestia_earth.models.log import log_as_table, logRequirements, logShouldRun
5
+ from hestia_earth.models.log import format_bool, format_enum, format_float, log_as_table, logRequirements, logShouldRun
6
6
 
7
- from .organicCarbonPerHa_utils import format_bool, format_bool_list, format_enum, format_number, format_number_list
7
+ from .organicCarbonPerHa_utils import format_bool_list, format_float_list
8
8
  from . import organicCarbonPerHa_tier_1 as tier_1
9
9
  from . import organicCarbonPerHa_tier_2 as tier_2
10
10
  from . import MODEL # noqa
@@ -204,20 +204,20 @@ def _get_unique_inventory_keys(inventory: dict) -> list:
204
204
 
205
205
  _INVENTORY_KEY_TO_FORMAT_FUNC = {
206
206
  tier_2._InventoryKey.SHOULD_RUN: format_bool,
207
- tier_2._InventoryKey.TEMP_MONTHLY: format_number_list,
208
- tier_2._InventoryKey.PRECIP_MONTHLY: format_number_list,
209
- tier_2._InventoryKey.PET_MONTHLY: format_number_list,
207
+ tier_2._InventoryKey.TEMP_MONTHLY: format_float_list,
208
+ tier_2._InventoryKey.PRECIP_MONTHLY: format_float_list,
209
+ tier_2._InventoryKey.PET_MONTHLY: format_float_list,
210
210
  tier_2._InventoryKey.IRRIGATED_MONTHLY: format_bool_list,
211
- tier_2._InventoryKey.SAND_CONTENT: format_number,
212
- tier_2._InventoryKey.CARBON_INPUT: format_number,
213
- tier_2._InventoryKey.N_CONTENT: format_number,
214
- tier_2._InventoryKey.LIGNIN_CONTENT: format_number,
211
+ tier_2._InventoryKey.SAND_CONTENT: format_float,
212
+ tier_2._InventoryKey.CARBON_INPUT: format_float,
213
+ tier_2._InventoryKey.N_CONTENT: format_float,
214
+ tier_2._InventoryKey.LIGNIN_CONTENT: format_float,
215
215
  tier_2._InventoryKey.TILLAGE_CATEGORY: format_enum,
216
216
  tier_2._InventoryKey.IS_PADDY_RICE: format_bool,
217
217
  tier_1._InventoryKey.SHOULD_RUN: format_bool,
218
218
  tier_1._InventoryKey.LU_CATEGORY: format_enum,
219
219
  tier_1._InventoryKey.MG_CATEGORY: format_enum,
220
- tier_1._InventoryKey.CI_CATEGORY: format_enum,
220
+ tier_1._InventoryKey.CI_CATEGORY: format_enum
221
221
  }
222
222
  """
223
223
  Map inventory keys to format functions. The columns in inventory logged as a table will also be sorted in the order of
@@ -8,7 +8,7 @@ from hestia_earth.utils.stats import calc_z_critical
8
8
  from hestia_earth.utils.stats import (
9
9
  repeat_single, truncated_normal_1d
10
10
  )
11
- from hestia_earth.models.log import log_as_table
11
+ from hestia_earth.models.log import format_bool, format_enum, format_float, log_as_table
12
12
  from hestia_earth.models.utils.blank_node import cumulative_nodes_term_match, node_term_match
13
13
  from hestia_earth.models.utils.term import get_cover_crop_property_terms, get_irrigated_terms
14
14
 
@@ -268,21 +268,6 @@ def sample_constant(*, iterations: int, value: float, **_) -> NDArray:
268
268
  return repeat_single(shape=(1, iterations), value=value)
269
269
 
270
270
 
271
- def format_bool(value: Optional[bool]) -> str:
272
- """Format a bool for logging in a table."""
273
- return str(bool(value))
274
-
275
-
276
- def format_number(value: Optional[float]) -> str:
277
- """Format a float for logging in a table."""
278
- return f"{value:.1f}" if isinstance(value, (float, int)) else "None"
279
-
280
-
281
- def format_enum(value: Optional[Enum]) -> str:
282
- """Format an enum for logging in a table."""
283
- return value.value if isinstance(value, Enum) else "None"
284
-
285
-
286
271
  def format_bool_list(values: Optional[list[bool]]) -> str:
287
272
  """Format a list of bools for logging in a table."""
288
273
  return (
@@ -291,10 +276,10 @@ def format_bool_list(values: Optional[list[bool]]) -> str:
291
276
  )
292
277
 
293
278
 
294
- def format_number_list(values: Optional[list[float]]) -> str:
279
+ def format_float_list(values: Optional[list[float]]) -> str:
295
280
  """Format a list of floats for logging in a table."""
296
281
  return (
297
- " ".join(format_number(value) for value in values) or "None"if isinstance(values, list)
282
+ " ".join(format_float(value, ndigits=1) for value in values) or "None"if isinstance(values, list)
298
283
  else "None"
299
284
  )
300
285
 
@@ -303,7 +288,7 @@ def format_soil_inventory(inventory: list[SoilData]) -> str:
303
288
  return log_as_table(
304
289
  {
305
290
  "term-id": data.term_id,
306
- "value": format_number(data.value),
291
+ "value": format_float(data.value),
307
292
  "category": format_enum(data.category)
308
293
  } for data in inventory
309
294
  ) if inventory else "None"
@@ -1,6 +1,5 @@
1
1
  from enum import Enum
2
2
  from typing import Literal
3
- import numpy as np
4
3
 
5
4
  from hestia_earth.schema import SiteSiteType
6
5
  from hestia_earth.utils.model import find_primary_product
@@ -157,11 +156,3 @@ def valid_eco_climate_zone(
157
156
  Validate that the model should run for a specific eco-climate zone.
158
157
  """
159
158
  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"
@@ -1,7 +1,11 @@
1
+ from enum import Enum
2
+ from functools import reduce
1
3
  import os
2
4
  import sys
3
5
  import logging
4
- from typing import Union, List
6
+ from typing import List, Optional, Union
7
+ from numpy.typing import NDArray
8
+ from numpy import ndarray
5
9
 
6
10
  LOG_LEVEL = os.getenv('LOG_LEVEL', 'INFO')
7
11
  _EXTENDED_LOGS = os.getenv('LOG_EXTENDED', 'true') == 'true'
@@ -108,3 +112,69 @@ def log_blank_nodes_id(blank_nodes: List[dict]):
108
112
  List of blank nodes, like Product, Input, Measurement, etc.
109
113
  """
110
114
  return ';'.join([p.get('term', {}).get('@id') for p in blank_nodes if p.get('term', {}).get('@id')]) or 'None'
115
+
116
+
117
+ _INVALID_CHARS = {"_", ":", ",", "="}
118
+ _REPLACEMENT_CHAR = "-"
119
+
120
+
121
+ def format_str(value: Optional[str], default: str = "None") -> str:
122
+ """Format a string for logging in a table. Remove all characters used to render the table on the front end."""
123
+ return (
124
+ reduce(lambda x, char: x.replace(char, _REPLACEMENT_CHAR), _INVALID_CHARS, str(value))
125
+ if value else default
126
+ )
127
+
128
+
129
+ def format_bool(value: Optional[bool], default: str = "None") -> str:
130
+ return str(value) if isinstance(value, bool) else default
131
+
132
+
133
+ def format_float(value: Union[int, float, None], unit: str = "", default: str = "None", ndigits: int = 3) -> str:
134
+ return " ".join(
135
+ string for string in [f"{round(value, ndigits)}", format_str(unit, "")] if string
136
+ ) if isinstance(value, (float, int)) else default
137
+
138
+
139
+ def format_int(value: Union[int, float, None], unit: str = "", default: str = "None") -> str:
140
+ return format_float(value, unit=unit, default=default, ndigits=None)
141
+
142
+
143
+ def _format_nd_array(value: Optional[NDArray], unit: str = "", default: str = "None", ndigits: int = 3) -> str:
144
+ return " ".join(
145
+ string for string in [
146
+ f"{format_float(value.mean(), ndigits=ndigits)} ± {format_float(value.std(), ndigits=ndigits)}",
147
+ format_str(unit, "")
148
+ ] if string
149
+ ) if isinstance(value, ndarray) else default
150
+
151
+
152
+ TYPE_TO_FORMAT_FUNC = {
153
+ ndarray: _format_nd_array,
154
+ (float, int): format_float
155
+ }
156
+
157
+
158
+ def format_nd_array(value: Optional[NDArray], unit: str = "", default: str = "None", ndigits: int = 3) -> str:
159
+ """
160
+ Format a numpy array for logging in a table.
161
+
162
+ Values that are floats and ints are logged using `format_float`.
163
+ """
164
+ format_func = next(
165
+ (func for type_, func in TYPE_TO_FORMAT_FUNC.items() if isinstance(value, type_)),
166
+ None
167
+ )
168
+ return format_func(value, unit=unit, default=default, ndigits=ndigits) if format_func else default
169
+
170
+
171
+ def format_decimal_percentage(
172
+ value: Optional[float], unit: str = "pct", default: str = "None", ndigits: int = 3
173
+ ) -> str:
174
+ """Format a decimal percentage (0-1) as a percentage (0-100%) for logging in a table."""
175
+ return format_float(value * 100, unit=unit, ndigits=ndigits) if isinstance(value, (float, int)) else default
176
+
177
+
178
+ def format_enum(value: Optional[Enum], default: str = "None") -> str:
179
+ """Format an enum for logging in a table."""
180
+ return format_str(value.value) if isinstance(value, Enum) else default