hestia-earth-models 0.64.9__py3-none-any.whl → 0.64.10__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 (80) hide show
  1. hestia_earth/models/cml2001Baseline/abioticResourceDepletionFossilFuels.py +175 -0
  2. hestia_earth/models/cml2001Baseline/abioticResourceDepletionMineralsAndMetals.py +136 -0
  3. hestia_earth/models/environmentalFootprintV3/soilQualityIndexLandTransformation.py +2 -2
  4. hestia_earth/models/ipcc2019/aboveGroundBiomass.py +31 -243
  5. hestia_earth/models/ipcc2019/belowGroundBiomass.py +529 -0
  6. hestia_earth/models/ipcc2019/biomass_utils.py +406 -0
  7. hestia_earth/models/ipcc2019/{co2ToAirAboveGroundBiomassStockChangeLandUseChange.py → co2ToAirAboveGroundBiomassStockChange.py} +19 -7
  8. hestia_earth/models/ipcc2019/{co2ToAirBelowGroundBiomassStockChangeLandUseChange.py → co2ToAirBelowGroundBiomassStockChange.py} +19 -7
  9. hestia_earth/models/ipcc2019/co2ToAirCarbonStockChange_utils.py +402 -73
  10. hestia_earth/models/ipcc2019/{co2ToAirSoilOrganicCarbonStockChangeManagementChange.py → co2ToAirSoilOrganicCarbonStockChange.py} +20 -8
  11. hestia_earth/models/ipcc2019/organicCarbonPerHa.py +3 -1
  12. hestia_earth/models/ipcc2019/pastureGrass_utils.py +6 -7
  13. hestia_earth/models/lcImpactAllEffects100Years/damageToFreshwaterEcosystemsFreshwaterEutrophication.py +2 -2
  14. hestia_earth/models/lcImpactAllEffects100Years/damageToFreshwaterEcosystemsWaterStress.py +2 -2
  15. hestia_earth/models/lcImpactAllEffects100Years/damageToHumanHealthParticulateMatterFormation.py +2 -2
  16. hestia_earth/models/lcImpactAllEffects100Years/damageToHumanHealthPhotochemicalOzoneFormation.py +2 -2
  17. hestia_earth/models/lcImpactAllEffects100Years/damageToHumanHealthWaterStress.py +2 -2
  18. hestia_earth/models/lcImpactAllEffects100Years/damageToMarineEcosystemsMarineEutrophication.py +2 -2
  19. hestia_earth/models/lcImpactAllEffects100Years/damageToTerrestrialEcosystemsPhotochemicalOzoneFormation.py +2 -2
  20. hestia_earth/models/lcImpactAllEffects100Years/damageToTerrestrialEcosystemsTerrestrialAcidification.py +2 -2
  21. hestia_earth/models/lcImpactAllEffectsInfinite/damageToFreshwaterEcosystemsFreshwaterEutrophication.py +2 -2
  22. hestia_earth/models/lcImpactAllEffectsInfinite/damageToFreshwaterEcosystemsWaterStress.py +2 -2
  23. hestia_earth/models/lcImpactAllEffectsInfinite/damageToHumanHealthParticulateMatterFormation.py +2 -2
  24. hestia_earth/models/lcImpactAllEffectsInfinite/damageToHumanHealthPhotochemicalOzoneFormation.py +2 -2
  25. hestia_earth/models/lcImpactAllEffectsInfinite/damageToHumanHealthWaterStress.py +2 -2
  26. hestia_earth/models/lcImpactAllEffectsInfinite/damageToMarineEcosystemsMarineEutrophication.py +2 -2
  27. hestia_earth/models/lcImpactAllEffectsInfinite/damageToTerrestrialEcosystemsPhotochemicalOzoneFormation.py +2 -2
  28. hestia_earth/models/lcImpactAllEffectsInfinite/damageToTerrestrialEcosystemsTerrestrialAcidification.py +2 -2
  29. hestia_earth/models/lcImpactCertainEffects100Years/damageToFreshwaterEcosystemsFreshwaterEutrophication.py +2 -2
  30. hestia_earth/models/lcImpactCertainEffects100Years/damageToFreshwaterEcosystemsWaterStress.py +2 -2
  31. hestia_earth/models/lcImpactCertainEffects100Years/damageToHumanHealthParticulateMatterFormation.py +2 -2
  32. hestia_earth/models/lcImpactCertainEffects100Years/damageToHumanHealthPhotochemicalOzoneFormation.py +2 -2
  33. hestia_earth/models/lcImpactCertainEffects100Years/damageToHumanHealthWaterStress.py +2 -2
  34. hestia_earth/models/lcImpactCertainEffects100Years/damageToMarineEcosystemsMarineEutrophication.py +2 -2
  35. hestia_earth/models/lcImpactCertainEffects100Years/damageToTerrestrialEcosystemsPhotochemicalOzoneFormation.py +2 -2
  36. hestia_earth/models/lcImpactCertainEffects100Years/damageToTerrestrialEcosystemsTerrestrialAcidification.py +2 -2
  37. hestia_earth/models/lcImpactCertainEffectsInfinite/damageToFreshwaterEcosystemsFreshwaterEutrophication.py +2 -2
  38. hestia_earth/models/lcImpactCertainEffectsInfinite/damageToFreshwaterEcosystemsWaterStress.py +2 -2
  39. hestia_earth/models/lcImpactCertainEffectsInfinite/damageToHumanHealthParticulateMatterFormation.py +2 -2
  40. hestia_earth/models/lcImpactCertainEffectsInfinite/damageToHumanHealthPhotochemicalOzoneFormation.py +2 -2
  41. hestia_earth/models/lcImpactCertainEffectsInfinite/damageToHumanHealthWaterStress.py +2 -2
  42. hestia_earth/models/lcImpactCertainEffectsInfinite/damageToMarineEcosystemsMarineEutrophication.py +2 -2
  43. hestia_earth/models/lcImpactCertainEffectsInfinite/damageToTerrestrialEcosystemsPhotochemicalOzoneFormation.py +2 -2
  44. hestia_earth/models/lcImpactCertainEffectsInfinite/damageToTerrestrialEcosystemsTerrestrialAcidification.py +2 -2
  45. hestia_earth/models/mocking/build_mock_search.py +44 -0
  46. hestia_earth/models/mocking/mock_search.py +8 -49
  47. hestia_earth/models/mocking/search-results.json +3075 -578
  48. hestia_earth/models/poschEtAl2008/terrestrialAcidificationPotentialAccumulatedExceedance.py +3 -3
  49. hestia_earth/models/poschEtAl2008/terrestrialEutrophicationPotentialAccumulatedExceedance.py +3 -3
  50. hestia_earth/models/preload_requests.py +1 -1
  51. hestia_earth/models/schmidt2007/utils.py +13 -4
  52. hestia_earth/models/utils/blank_node.py +73 -3
  53. hestia_earth/models/utils/constant.py +8 -1
  54. hestia_earth/models/utils/cycle.py +10 -13
  55. hestia_earth/models/utils/fuel.py +1 -1
  56. hestia_earth/models/utils/impact_assessment.py +31 -16
  57. hestia_earth/models/utils/lookup.py +36 -7
  58. hestia_earth/models/utils/pesticideAI.py +1 -1
  59. hestia_earth/models/utils/property.py +11 -4
  60. hestia_earth/models/utils/term.py +15 -8
  61. hestia_earth/models/version.py +1 -1
  62. {hestia_earth_models-0.64.9.dist-info → hestia_earth_models-0.64.10.dist-info}/METADATA +2 -2
  63. {hestia_earth_models-0.64.9.dist-info → hestia_earth_models-0.64.10.dist-info}/RECORD +78 -71
  64. {hestia_earth_models-0.64.9.dist-info → hestia_earth_models-0.64.10.dist-info}/WHEEL +1 -1
  65. tests/models/cml2001Baseline/test_abioticResourceDepletionFossilFuels.py +196 -0
  66. tests/models/cml2001Baseline/test_abioticResourceDepletionMineralsAndMetals.py +124 -0
  67. tests/models/edip2003/test_ozoneDepletionPotential.py +1 -13
  68. tests/models/environmentalFootprintV3/test_soilQualityIndexLandTransformation.py +1 -2
  69. tests/models/impact_assessment/test_emissions.py +1 -0
  70. tests/models/ipcc2019/test_aboveGroundBiomass.py +27 -63
  71. tests/models/ipcc2019/test_belowGroundBiomass.py +146 -0
  72. tests/models/ipcc2019/test_biomass_utils.py +115 -0
  73. tests/models/ipcc2019/{test_co2ToAirAboveGroundBiomassStockChangeLandUseChange.py → test_co2ToAirAboveGroundBiomassStockChange.py} +5 -5
  74. tests/models/ipcc2019/{test_co2ToAirBelowGroundBiomassStockChangeLandUseChange.py → test_co2ToAirBelowGroundBiomassStockChange.py} +5 -5
  75. tests/models/ipcc2019/{test_co2ToAirSoilOrganicCarbonStockChangeManagementChange.py → test_co2ToAirSoilOrganicCarbonStockChange.py} +5 -5
  76. tests/models/ipcc2021/test_gwp100.py +2 -2
  77. hestia_earth/models/ipcc2019/aboveGroundBiomass_utils.py +0 -180
  78. tests/models/ipcc2019/test_aboveGroundBiomass_utils.py +0 -92
  79. {hestia_earth_models-0.64.9.dist-info → hestia_earth_models-0.64.10.dist-info}/LICENSE +0 -0
  80. {hestia_earth_models-0.64.9.dist-info → hestia_earth_models-0.64.10.dist-info}/top_level.txt +0 -0
@@ -36,8 +36,8 @@ def _indicator(value: float):
36
36
 
37
37
  def run(impact_assessment: dict):
38
38
  value = impact_country_value(MODEL, TERM_ID, impact_assessment, f"{list(LOOKUPS.keys())[0]}.csv",
39
- country_fallback=True, default_no_emissions=0)
39
+ country_fallback=True)
40
40
  logRequirements(impact_assessment, model=MODEL, term=TERM_ID,
41
41
  value=value)
42
- logShouldRun(impact_assessment, MODEL, TERM_ID, True)
43
- return _indicator(value)
42
+ logShouldRun(impact_assessment, MODEL, TERM_ID, value is not None)
43
+ return None if value is None else _indicator(value)
@@ -36,8 +36,8 @@ def _indicator(value: float):
36
36
 
37
37
  def run(impact_assessment: dict):
38
38
  value = impact_country_value(MODEL, TERM_ID, impact_assessment, f"{list(LOOKUPS.keys())[0]}.csv",
39
- country_fallback=True, default_no_emissions=0)
39
+ country_fallback=True)
40
40
  logRequirements(impact_assessment, model=MODEL, term=TERM_ID,
41
41
  value=value)
42
- logShouldRun(impact_assessment, MODEL, TERM_ID, True)
43
- return _indicator(value)
42
+ logShouldRun(impact_assessment, MODEL, TERM_ID, value is not None)
43
+ return None if value is None else _indicator(value)
@@ -5,7 +5,7 @@ import json
5
5
  import os
6
6
 
7
7
  from .log import logger
8
- from .mocking.mock_search import create_search_results
8
+ from .mocking.build_mock_search import create_search_results
9
9
  from .mocking import RESULTS_PATH, enable_mock as _mock
10
10
 
11
11
 
@@ -3,14 +3,23 @@ from hestia_earth.utils.model import filter_list_term_type
3
3
  from hestia_earth.utils.tools import non_empty_list
4
4
 
5
5
  from hestia_earth.models.utils.completeness import _is_term_type_complete
6
- from hestia_earth.models.utils.lookup import factor_value
6
+ from hestia_earth.models.utils.lookup import all_factor_value
7
+ from . import MODEL
7
8
 
8
9
 
9
10
  def get_waste_values(term_id: str, cycle: dict, lookup_col: str):
10
11
  products = filter_list_term_type(cycle.get('products', []), TermTermType.WASTE)
11
- values = non_empty_list(map(factor_value(None, term_id, f"{TermTermType.WASTE.value}.csv", lookup_col), products))
12
+ value = all_factor_value(
13
+ model=MODEL,
14
+ term_id=term_id,
15
+ node=cycle,
16
+ lookup_name=f"{TermTermType.WASTE.value}.csv",
17
+ lookup_col=lookup_col,
18
+ blank_nodes=products,
19
+ default_no_values=None
20
+ )
12
21
  return [0] if all([
13
- len(values) == 0,
22
+ value is None,
14
23
  _is_term_type_complete(cycle, TermTermType.WASTE),
15
24
  cycle.get('@type', cycle.get('type')) == NodeType.CYCLE.value # ignore adding 0 value for Transformation
16
- ]) else values
25
+ ]) else non_empty_list([value])
@@ -28,14 +28,14 @@ from hestia_earth.utils.tools import (
28
28
  )
29
29
 
30
30
  from . import is_from_model, _filter_list_term_unit, is_iterable, full_date_str
31
- from .constant import Units
31
+ from .constant import Units, get_atomic_conversion
32
32
  from .lookup import (
33
33
  is_model_siteType_allowed,
34
34
  is_siteType_allowed,
35
35
  is_product_id_allowed, is_product_termType_allowed,
36
- is_input_id_allowed, is_input_termType_allowed
36
+ is_input_id_allowed, is_input_termType_allowed, _node_value
37
37
  )
38
- from .property import get_node_property, get_node_property_value
38
+ from .property import get_node_property, get_node_property_value, find_term_property
39
39
  from .term import get_lookup_value
40
40
  from ..log import debugValues, log_as_table
41
41
 
@@ -1387,3 +1387,73 @@ def most_relevant_blank_node_by_id(nodes: list, term_id: str, date: str):
1387
1387
  return {} if len(filtered_nodes) == 0 \
1388
1388
  else _shallowest_node(_most_recent_nodes(filtered_nodes, date)) \
1389
1389
  if date and len(filtered_nodes) > 1 else filtered_nodes[0]
1390
+
1391
+
1392
+ PROPERTY_UNITS_CONVERSIONS = {
1393
+ Units.KG.value: {
1394
+ Units.MJ.value: [
1395
+ 'energyContentHigherHeatingValue', # "kg" to "mj"
1396
+ ]
1397
+ },
1398
+ Units.M3.value: {
1399
+ Units.MJ.value: [
1400
+ 'density', # "m3" to "kg"
1401
+ 'energyContentHigherHeatingValue', # "kg" to "mj"
1402
+ ]
1403
+ }
1404
+ }
1405
+
1406
+
1407
+ def _convert_via_property(node: dict, node_value: Union[int, float], property_field: str) -> Optional[float]:
1408
+ """
1409
+ Converts a node_value number from one unit to another using a property_field associated
1410
+ with a term inside term node such as "density" or 'energyContentHigherHeatingValue' or listed
1411
+ in https://www.hestia.earth/glossary?page=1&termType=property
1412
+
1413
+ Will return none if the property_field is not found
1414
+
1415
+ Parameters
1416
+ ----------
1417
+ node: a dict containing a term
1418
+ node_value: value to be converted as float or int
1419
+ property_field: str such as "density"
1420
+
1421
+ Returns float or None
1422
+ -------
1423
+ """
1424
+ node_property = find_term_property(node, property_field, default={}, keep_in_memory=True)
1425
+ node_property_value = safe_parse_float(node_property.get("value", 0))
1426
+
1427
+ return node_value * node_property_value if node_value is not None and bool(node_property_value) else None
1428
+
1429
+
1430
+ def convert_unit(node, dest_unit: Units, node_value: Union[int, float] = None) -> Optional[Union[int, float]]:
1431
+ """
1432
+ Convert a number `value` inside a node or a optional `node_value` belonging to a term `node`, to unit `dest_unit`
1433
+ using the ATOMIC_WEIGHT_CONVERSIONS map or failing that, the PROPERTY_UNITS_CONVERSIONS map and lookups
1434
+ """
1435
+ src_unit = node.get("units", "")
1436
+
1437
+ node_value = _node_value(node) if node_value is None else node_value
1438
+
1439
+ return node_value if src_unit == dest_unit.value else (
1440
+ node_value * get_atomic_conversion(src_unit, dest_unit)
1441
+ if get_atomic_conversion(src_unit, dest_unit, default_value=None) is not None
1442
+ else convert_unit_properties(node_value, node, dest_unit)
1443
+ ) if node_value else None
1444
+
1445
+
1446
+ def convert_unit_properties(node_value: Union[int, float], node: dict, dest_unit: Units) -> Optional[Union[int, float]]:
1447
+ """
1448
+ Convert a number `node_value` belonging to a term `node`, to unit `to_units` by chaining multiple unit conversions
1449
+ together.
1450
+ Uses terms properties for the conversion.
1451
+ Uses cached calls to download_hestia() internally for speedup
1452
+ Returns None if no conversion possible.
1453
+ """
1454
+ src_unit = node.get('units', '')
1455
+ conversions = PROPERTY_UNITS_CONVERSIONS.get(src_unit, {}).get(dest_unit.value, [])
1456
+ return reduce(
1457
+ lambda value, conversion_property_field: _convert_via_property(node, value, conversion_property_field),
1458
+ conversions, node_value
1459
+ ) if conversions else None
@@ -1,4 +1,5 @@
1
1
  from enum import Enum
2
+
2
3
  from hestia_earth.utils.tools import list_sum
3
4
 
4
5
 
@@ -35,6 +36,9 @@ class Units(Enum):
35
36
  PERCENTAGE_AREA = '% area'
36
37
  TO_C = '-C'
37
38
  TO_N = '-N'
39
+ KW_H = 'kWh'
40
+ MJ = 'MJ'
41
+ M3 = "m3"
38
42
 
39
43
 
40
44
  C = 12.012
@@ -93,7 +97,10 @@ ATOMIC_WEIGHT_CONVERSIONS = {
93
97
  },
94
98
  Units.KG_NH4.value: {
95
99
  Units.TO_N.value: (N + H*4) / N # Conv_Mol_NH4N_NH4
96
- }
100
+ },
101
+ Units.KW_H.value: {
102
+ Units.MJ.value: 3.6
103
+ },
97
104
  }
98
105
 
99
106
 
@@ -3,7 +3,7 @@ from hestia_earth.utils.model import filter_list_term_type, find_term_match, fin
3
3
  from hestia_earth.utils.tools import list_sum, safe_parse_float, safe_parse_date, non_empty_list
4
4
 
5
5
  from ..log import logRequirements, debugValues
6
- from .lookup import factor_value, is_siteType_allowed
6
+ from .lookup import all_factor_value, is_siteType_allowed
7
7
  from .term import get_lookup_value
8
8
  from .property import get_node_property
9
9
  from .completeness import _is_term_type_complete
@@ -386,9 +386,7 @@ def cycle_end_year(cycle: dict):
386
386
  return date.year if date else None
387
387
 
388
388
 
389
- def impact_lookup_value(
390
- model: str, term_id: str, blank_nodes: list, lookup_col: str, allow_missing: bool = True
391
- ) -> float:
389
+ def impact_lookup_value(model: str, term_id: str, cycle: dict, blank_nodes: list, lookup_col: str) -> float:
392
390
  """
393
391
  Calculate the value of the impact based on lookup factors and cycle values.
394
392
 
@@ -409,15 +407,14 @@ def impact_lookup_value(
409
407
  The impact total value.
410
408
  """
411
409
  term_type = blank_nodes[0].get('term', {}).get('termType') if len(blank_nodes) > 0 else None
412
- get_factor_value = factor_value(model, term_id, f"{term_type}.csv", lookup_col)
413
- factors = [(node.get('term', {}).get('@id'), get_factor_value(node)) for node in blank_nodes]
414
- values = [value for (_term, value) in factors if value is not None]
415
- missing_values = set([term for (term, value) in factors if value is None])
416
- all_nodes_have_value = len(values) == len(factors)
417
- debugValues({'type': 'Cycle'}, model=model, term=term_id,
418
- missing_lookup_factor=';'.join(missing_values),
419
- **{f"all_{term_type}_have_lookup_value": all_nodes_have_value})
420
- return list_sum(values) if len(values) > 0 and (allow_missing or all_nodes_have_value) else None
410
+ return all_factor_value(
411
+ model=model,
412
+ term_id=term_id,
413
+ node=cycle,
414
+ lookup_name=f"{term_type}.csv",
415
+ lookup_col=lookup_col,
416
+ blank_nodes=blank_nodes
417
+ )
421
418
 
422
419
 
423
420
  def get_ecoClimateZone(cycle: dict) -> int:
@@ -13,7 +13,7 @@ def impact_lookup_value(model: str, term_id: str, impact_assessment: dict, looku
13
13
  fuels = filter_list_term_type(cycle.get('inputs', []), TermTermType.FUEL)
14
14
  has_fuels_inputs = len(fuels) > 0
15
15
  fuels_total_value = convert_value_from_cycle(
16
- product, cycle_lookup_value(model, term_id, fuels, lookup_col), model=model, term_id=term_id
16
+ product, cycle_lookup_value(model, term_id, cycle, fuels, lookup_col), model=model, term_id=term_id
17
17
  ) if has_fuels_inputs else None
18
18
  logRequirements(impact_assessment, model=model, term=term_id,
19
19
  term_type_electricityFuel_complete=fuel_complete,
@@ -4,10 +4,10 @@ from hestia_earth.utils.lookup import download_lookup
4
4
  from hestia_earth.utils.model import find_term_match
5
5
  from hestia_earth.utils.tools import list_sum, safe_parse_date
6
6
 
7
- from .lookup import factor_value, _term_factor_value, _aware_factor_value, fallback_country
7
+ from hestia_earth.models.log import logRequirements, debugValues, log_as_table
8
+ from .lookup import all_factor_value, _term_factor_value, _aware_factor_value, fallback_country
8
9
  from .product import find_by_product
9
10
  from .site import region_level_1_id
10
- from ..log import logRequirements
11
11
 
12
12
 
13
13
  def impact_end_year(impact_assessment: dict) -> int:
@@ -110,15 +110,19 @@ def impact_lookup_value(model: str, term_id: str, impact: dict, lookup_col: str,
110
110
  int
111
111
  The impact total value.
112
112
  """
113
- nodes = impact.get('emissionsResourceUse', [])
114
-
115
- factors = list(map(factor_value(model, term_id, 'emission.csv', lookup_col, grouped_key), nodes))
116
- values = [value for value in factors if value is not None]
117
- return list_sum(values) if len(values) > 0 else None
113
+ return all_factor_value(
114
+ model=model,
115
+ term_id=term_id,
116
+ node=impact,
117
+ lookup_name='emission.csv',
118
+ lookup_col=lookup_col,
119
+ blank_nodes=impact.get('emissionsResourceUse', []),
120
+ grouped_key=grouped_key
121
+ )
118
122
 
119
123
 
120
124
  def impact_country_value(model: str, term_id: str, impact: dict, lookup: str, group_key: str = None,
121
- country_fallback: bool = False, default_no_emissions=None) -> float:
125
+ country_fallback: bool = False) -> float:
122
126
  """
123
127
  Calculate the value of the impact based on lookup factors and `site.country.@id`.
124
128
 
@@ -137,8 +141,6 @@ def impact_country_value(model: str, term_id: str, impact: dict, lookup: str, gr
137
141
  country_fallback : bool
138
142
  Optional: if True fallback to default `region-world` country_id if country_id in `ImpactAssessment` not found in
139
143
  lookup file containing factors.
140
- default_no_emissions :
141
- Optional: if set, will return this value if no contributing terms found in emissionsResourceUse
142
144
 
143
145
  Returns
144
146
  -------
@@ -149,9 +151,17 @@ def impact_country_value(model: str, term_id: str, impact: dict, lookup: str, gr
149
151
  country_id = get_country_id(impact)
150
152
  country_id = fallback_country(country_id, [download_lookup(lookup)]) if country_fallback else country_id
151
153
 
152
- factors = list(map(_term_factor_value(model, term_id, lookup, country_id, group_key), nodes))
153
- values = [value for value in factors if value is not None]
154
- return list_sum(values) if len(values) > 0 else default_no_emissions
154
+ values = list(map(_term_factor_value(model, term_id, lookup, country_id, group_key), nodes))
155
+ debugValues(impact, model=model, term=term_id,
156
+ values_used=log_as_table(values))
157
+
158
+ all_with_factors = all([v.get('coefficient') is not None for v in values if v.get('value') is not None])
159
+ values = [float((v.get('value') or 0) * (v.get('coefficient') or 0)) for v in values]
160
+
161
+ # fail if some factors are missing
162
+ return None if not all_with_factors else (
163
+ list_sum(values) if len(values) > 0 else 0
164
+ )
155
165
 
156
166
 
157
167
  def impact_aware_value(model: str, term_id: str, impact: dict, lookup: str, group_key: str = None) -> float:
@@ -210,9 +220,14 @@ def impact_endpoint_value(model: str, term_id: str, impact: dict, lookup_col: st
210
220
  i.get('methodModel').get('@id') == model or
211
221
  not i.get('methodModel').get('@id').startswith(model[0:6]) # allow other non-related models to be accounted for
212
222
  )]
213
- factors = list(map(factor_value(model, term_id, 'characterisedIndicator.csv', lookup_col), nodes))
214
- values = [value for value in factors if value is not None]
215
- return list_sum(values) if len(values) > 0 else None
223
+ return all_factor_value(
224
+ model=model,
225
+ term_id=term_id,
226
+ node=impact,
227
+ lookup_name='characterisedIndicator.csv',
228
+ lookup_col=lookup_col,
229
+ blank_nodes=nodes
230
+ )
216
231
 
217
232
 
218
233
  def emission_value(impact_assessment: dict, term_id: str):
@@ -6,7 +6,7 @@ from hestia_earth.utils.lookup import (
6
6
  )
7
7
  from hestia_earth.utils.tools import list_sum, safe_parse_float, non_empty_list
8
8
 
9
- from ..log import debugValues
9
+ from ..log import debugValues, log_as_table, debugMissingLookup
10
10
 
11
11
 
12
12
  def _node_value(node):
@@ -14,7 +14,7 @@ def _node_value(node):
14
14
  return list_sum(value) if isinstance(value, list) else value
15
15
 
16
16
 
17
- def factor_value(model: str, term_id: str, lookup_name: str, lookup_col: str, grouped_key: Optional[str] = None):
17
+ def _factor_value(model: str, term_id: str, lookup_name: str, lookup_col: str, grouped_key: Optional[str] = None):
18
18
  lookup = download_lookup(lookup_name)
19
19
 
20
20
  def get_value(data: dict):
@@ -33,11 +33,41 @@ def factor_value(model: str, term_id: str, lookup_name: str, lookup_col: str, gr
33
33
  operation=data.get('operation', {}).get('@id'),
34
34
  value=value,
35
35
  coefficient=coefficient)
36
- return value * coefficient
37
- return None
36
+ return {'id': node_term_id, 'value': value, 'coefficient': coefficient}
38
37
  return get_value
39
38
 
40
39
 
40
+ def all_factor_value(
41
+ model: str,
42
+ term_id: str,
43
+ node: dict,
44
+ lookup_name: str,
45
+ lookup_col: str,
46
+ blank_nodes: List[dict],
47
+ grouped_key: Optional[str] = None,
48
+ default_no_values=0
49
+ ):
50
+ values = list(map(_factor_value(model, term_id, lookup_name, lookup_col, grouped_key), blank_nodes))
51
+
52
+ missing_values = set([v.get('id') for v in values if v.get('value') is not None and v.get('coefficient') is None])
53
+ all_with_factors = all([v.get('coefficient') is not None for v in values if v.get('value') is not None])
54
+
55
+ for term_id in missing_values:
56
+ debugMissingLookup(lookup_name, 'termid', term_id, lookup_col, None)
57
+
58
+ debugValues(node, model=model, term=term_id,
59
+ all_with_factors=all_with_factors,
60
+ missing_lookup_factor=';'.join(missing_values),
61
+ values_used=log_as_table(values))
62
+
63
+ values = [float((v.get('value') or 0) * (v.get('coefficient') or 0)) for v in values]
64
+
65
+ # fail if some factors are missing
66
+ return None if not all_with_factors else (
67
+ list_sum(values) if len(values) > 0 else default_no_values
68
+ )
69
+
70
+
41
71
  def _term_factor_value(model: str, term_id: str, lookup_name: str, lookup_term_id: str, group_key: str = None):
42
72
  lookup = download_lookup(lookup_name, False) # avoid saving in memory as there could be many different files used
43
73
 
@@ -45,11 +75,10 @@ def _term_factor_value(model: str, term_id: str, lookup_name: str, lookup_term_i
45
75
  node_term_id = data.get('term', {}).get('@id')
46
76
  value = _node_value(data)
47
77
  coefficient = get_table_value(lookup, 'termid', lookup_term_id, column_name(node_term_id))
48
- coefficient = safe_parse_float(extract_grouped_data(coefficient, group_key)) if group_key else coefficient
78
+ coefficient = safe_parse_float(extract_grouped_data(coefficient, group_key) if group_key else coefficient)
49
79
  if value is not None and coefficient is not None:
50
80
  debugValues(data, model=model, term=term_id, node=node_term_id, value=value, coefficient=coefficient)
51
- return value * coefficient
52
- return None
81
+ return {'id': node_term_id, 'value': value, 'coefficient': coefficient}
53
82
  return get_value
54
83
 
55
84
 
@@ -22,7 +22,7 @@ def impact_lookup_value(model: str, term_id: str, impact_assessment: dict, looku
22
22
  get_pesticides_from_inputs(cycle)
23
23
  has_pesticides_inputs = len(pesticides) > 0
24
24
  pesticides_total_value = convert_value_from_cycle(
25
- product, cycle_lookup_value(model, term_id, pesticides, lookup_col, False), model=model, term_id=term_id
25
+ product, cycle_lookup_value(model, term_id, cycle, pesticides, lookup_col), model=model, term_id=term_id
26
26
  ) if has_pesticides_inputs else None
27
27
 
28
28
  logRequirements(impact_assessment, model=model, term=term_id,
@@ -1,12 +1,16 @@
1
+ from functools import cache
2
+
1
3
  from hestia_earth.schema import SchemaType, TermTermType
2
4
  from hestia_earth.utils.api import download_hestia
3
- from hestia_earth.utils.model import find_term_match, linked_node
4
5
  from hestia_earth.utils.lookup import download_lookup, extract_grouped_data, get_table_value, column_name
6
+ from hestia_earth.utils.model import find_term_match, linked_node
5
7
  from hestia_earth.utils.tools import list_sum, safe_parse_float
6
8
 
7
- from ..log import debugMissingLookup
8
9
  from . import _term_id, _include_methodModel
9
10
  from .term import get_lookup_value
11
+ from ..log import debugMissingLookup
12
+
13
+ download_hestia_cached = cache(download_hestia)
10
14
 
11
15
 
12
16
  def _new_property(term, model=None):
@@ -26,7 +30,7 @@ def get_property_lookup_value(model: str, term_id: str, column: str):
26
30
  return get_lookup_value(term, column, model=model, term=term_id)
27
31
 
28
32
 
29
- def find_term_property(term, property: str, default=None) -> dict:
33
+ def find_term_property(term, property: str, default=None, keep_in_memory=False) -> dict:
30
34
  """
31
35
  Get the property by `@id` linked to the `Term` in the glossary.
32
36
 
@@ -38,15 +42,18 @@ def find_term_property(term, property: str, default=None) -> dict:
38
42
  The `term.@id` of the property. Example: `nitrogenContent`.
39
43
  default : Any
40
44
  The default value if the property is not found. Defaults to `None`.
45
+ keep_in_memory: bool
46
+ Should we cache results from download_hestia(). Default False
41
47
 
42
48
  Returns
43
49
  -------
44
50
  dict
45
51
  The property if found, `default` otherwise.
46
52
  """
53
+ download_func = download_hestia_cached if keep_in_memory else download_hestia
47
54
  props = term.get('defaultProperties', []) if isinstance(term, dict) else []
48
55
  term_id = _term_id(term)
49
- props = (download_hestia(term_id) or {}).get('defaultProperties', []) if len(props) == 0 and term_id else props
56
+ props = (download_func(term_id) or {}).get('defaultProperties', []) if len(props) == 0 and term_id else props
50
57
  return find_term_match(props, property, default)
51
58
 
52
59
 
@@ -673,8 +673,8 @@ def get_land_cover_siteTypes():
673
673
  return search({
674
674
  "bool": {
675
675
  "must": [
676
- {"match": {"@type": "Term"}},
677
- {"match": {"termType": "landCover"}}
676
+ {"match": {"@type": SchemaType.TERM.value}},
677
+ {"match": {"termType": TermTermType.LANDCOVER.value}}
678
678
  ],
679
679
  "should": [{"match": {"name": siteType.value}} for siteType in SiteSiteType],
680
680
  "minimum_should_match": 1
@@ -682,13 +682,20 @@ def get_land_cover_siteTypes():
682
682
  }, limit=LIMIT)
683
683
 
684
684
 
685
- def download_all_land_cover_terms():
686
- result = search({
685
+ def get_land_cover_terms():
686
+ """
687
+ Find all `Land Cover` terms from the Glossary: https://hestia.earth/glossary?termType=landCover
688
+
689
+ Returns
690
+ -------
691
+ List of landCover terms with associated siteTypes.
692
+ """
693
+ terms = search({
687
694
  "bool": {
688
695
  "must": [
689
- {"match": {"@type": "Term"}},
690
- {"match": {"termType": "landCover"}}
696
+ {"match": {"@type": SchemaType.TERM.value}},
697
+ {"match": {"termType": TermTermType.LANDCOVER.value}}
691
698
  ]
692
699
  },
693
- }, fields=['@id', 'nameNormalized', 'subClassOf'], limit=10000)
694
- return result
700
+ }, limit=LIMIT, fields=['@id'])
701
+ return list(map(lambda n: n["@id"], terms))
@@ -1 +1 @@
1
- VERSION = '0.64.9'
1
+ VERSION = '0.64.10'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: hestia-earth-models
3
- Version: 0.64.9
3
+ Version: 0.64.10
4
4
  Summary: HESTIA's set of modules for filling gaps in the activity data using external datasets (e.g. populating soil properties with a geospatial dataset using provided coordinates) and internal lookups (e.g. populating machinery use from fuel use). Includes rules for when gaps should be filled versus not (e.g. never gap fill yield, gap fill crop residue if yield provided etc.).
5
5
  Home-page: https://gitlab.com/hestia-earth/hestia-engine-models
6
6
  Author: HESTIA Team
@@ -12,7 +12,7 @@ Classifier: Programming Language :: Python :: 3.6
12
12
  Description-Content-Type: text/markdown
13
13
  License-File: LICENSE
14
14
  Requires-Dist: hestia-earth.schema==30.*
15
- Requires-Dist: hestia-earth.utils>=0.13.5
15
+ Requires-Dist: hestia-earth.utils>=0.13.9
16
16
  Requires-Dist: python-dateutil>=2.8.1
17
17
  Requires-Dist: CurrencyConverter==0.16.8
18
18
  Requires-Dist: haversine>=2.7.0