hestia-earth-models 0.73.7__py3-none-any.whl → 0.74.0__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 (99) hide show
  1. hestia_earth/models/aware/scarcityWeightedWaterUse.py +7 -6
  2. hestia_earth/models/aware2_0/__init__.py +14 -0
  3. hestia_earth/models/aware2_0/scarcityWeightedWaterUse.py +115 -0
  4. hestia_earth/models/config/Cycle.json +121 -29
  5. hestia_earth/models/config/ImpactAssessment.json +240 -200
  6. hestia_earth/models/config/__init__.py +26 -2
  7. hestia_earth/models/cycle/animal/input/hestiaAggregatedData.py +2 -2
  8. hestia_earth/models/cycle/animal/input/properties.py +6 -5
  9. hestia_earth/models/cycle/animal/milkYield.py +8 -3
  10. hestia_earth/models/cycle/utils.py +6 -6
  11. hestia_earth/models/dammgen2009/noxToAirExcreta.py +11 -9
  12. hestia_earth/models/data/ecoinventV3/__init__.py +8 -26
  13. hestia_earth/models/ecoalimV9/cycle.py +51 -45
  14. hestia_earth/models/ecoalimV9/impact_assessment.py +63 -45
  15. hestia_earth/models/ecoalimV9/utils.py +21 -15
  16. hestia_earth/models/ecoinventV3/__init__.py +8 -140
  17. hestia_earth/models/ecoinventV3/cycle.py +140 -0
  18. hestia_earth/models/ecoinventV3/utils.py +28 -1
  19. hestia_earth/models/ecoinventV3AndEmberClimate/__init__.py +8 -137
  20. hestia_earth/models/ecoinventV3AndEmberClimate/cycle.py +144 -0
  21. hestia_earth/models/emepEea2019/n2OToAirFuelCombustionDirect.py +2 -2
  22. hestia_earth/models/emepEea2019/utils.py +2 -3
  23. hestia_earth/models/environmentalFootprintV3_1/environmentalFootprintSingleOverallScore.py +5 -7
  24. hestia_earth/models/frischknechtEtAl2000/ionisingRadiationKbqU235Eq.py +41 -43
  25. hestia_earth/models/geospatialDatabase/awareWaterBasinId.py +2 -2
  26. hestia_earth/models/geospatialDatabase/awareWaterBasinId_v1.py +45 -0
  27. hestia_earth/models/hestia/default_emissions.py +7 -7
  28. hestia_earth/models/hestia/default_resourceUse.py +7 -6
  29. hestia_earth/models/hestia/landCover.py +110 -12
  30. hestia_earth/models/hestia/seed_emissions.py +7 -3
  31. hestia_earth/models/hestia/utils.py +1 -0
  32. hestia_earth/models/hestia/waterSalinity.py +2 -3
  33. hestia_earth/models/impact_assessment/emissions.py +3 -5
  34. hestia_earth/models/ipcc2019/biocharOrganicCarbonPerHa.py +9 -3
  35. hestia_earth/models/ipcc2019/co2ToAirAboveGroundBiomassStockChange.py +1 -5
  36. hestia_earth/models/ipcc2019/co2ToAirBelowGroundBiomassStockChange.py +1 -5
  37. hestia_earth/models/ipcc2019/co2ToAirCarbonStockChange_utils.py +1 -33
  38. hestia_earth/models/ipcc2019/co2ToAirSoilOrganicCarbonStockChange.py +1 -5
  39. hestia_earth/models/ipcc2019/n2OToAirAquacultureSystemsIndirect.py +44 -0
  40. hestia_earth/models/ipcc2019/n2OToAirCropResidueBurningIndirect.py +43 -0
  41. hestia_earth/models/ipcc2019/n2OToAirCropResidueDecompositionIndirect.py +13 -70
  42. hestia_earth/models/ipcc2019/n2OToAirExcretaIndirect.py +13 -70
  43. hestia_earth/models/ipcc2019/n2OToAirFuelCombustionIndirect.py +43 -0
  44. hestia_earth/models/ipcc2019/n2OToAirInorganicFertiliserIndirect.py +13 -70
  45. hestia_earth/models/ipcc2019/n2OToAirNaturalVegetationBurningIndirect.py +43 -0
  46. hestia_earth/models/ipcc2019/n2OToAirOrganicFertiliserIndirect.py +13 -70
  47. hestia_earth/models/ipcc2019/n2OToAirOrganicSoilBurningIndirect.py +43 -0
  48. hestia_earth/models/ipcc2019/n2OToAirOrganicSoilCultivationIndirect.py +43 -0
  49. hestia_earth/models/ipcc2019/n2OToAir_indirect_emissions_utils.py +112 -0
  50. hestia_earth/models/ipcc2019/utils.py +0 -25
  51. hestia_earth/models/jarvisAndPain1994/n2ToAirExcreta.py +11 -9
  52. hestia_earth/models/linkedImpactAssessment/emissions.py +25 -16
  53. hestia_earth/models/linkedImpactAssessment/utils.py +5 -1
  54. hestia_earth/models/log.py +8 -3
  55. hestia_earth/models/mocking/search-results.json +1670 -1666
  56. hestia_earth/models/utils/__init__.py +3 -0
  57. hestia_earth/models/utils/background_emissions.py +121 -14
  58. hestia_earth/models/utils/blank_node.py +1 -11
  59. hestia_earth/models/utils/emission.py +18 -8
  60. hestia_earth/models/utils/feedipedia.py +2 -2
  61. hestia_earth/models/utils/impact_assessment.py +4 -6
  62. hestia_earth/models/utils/indicator.py +8 -1
  63. hestia_earth/models/utils/lookup.py +30 -18
  64. hestia_earth/models/utils/productivity.py +1 -1
  65. hestia_earth/models/version.py +1 -1
  66. hestia_earth/orchestrator/log.py +8 -3
  67. hestia_earth/orchestrator/strategies/merge/merge_list.py +41 -54
  68. {hestia_earth_models-0.73.7.dist-info → hestia_earth_models-0.74.0.dist-info}/METADATA +3 -3
  69. {hestia_earth_models-0.73.7.dist-info → hestia_earth_models-0.74.0.dist-info}/RECORD +99 -75
  70. tests/models/aware2_0/__init__.py +0 -0
  71. tests/models/aware2_0/test_scarcityWeightedWaterUse.py +58 -0
  72. tests/models/dammgen2009/test_noxToAirExcreta.py +2 -2
  73. tests/models/ecoalimV9/test_cycle.py +1 -1
  74. tests/models/ecoalimV9/test_impact_assessment.py +1 -1
  75. tests/models/ecoinventV3/__init__.py +0 -0
  76. tests/models/{test_ecoinventV3.py → ecoinventV3/test_cycle.py} +5 -5
  77. tests/models/ecoinventV3AndEmberClimate/__init__.py +0 -0
  78. tests/models/{test_ecoinventV3AndEmberClimate.py → ecoinventV3AndEmberClimate/test_cycle.py} +6 -4
  79. tests/models/environmentalFootprintV3_1/test_environmentalFootprintSingleOverallScore.py +2 -2
  80. tests/models/frischknechtEtAl2000/test_ionisingRadiationKbqU235Eq.py +18 -27
  81. tests/models/hestia/test_landCover.py +16 -6
  82. tests/models/ipcc2019/test_biocharOrganicCarbonPerHa.py +2 -1
  83. tests/models/ipcc2019/test_n2OToAirAquacultureSystemsIndirect.py +45 -0
  84. tests/models/ipcc2019/test_n2OToAirCropResidueBurningIndirect.py +45 -0
  85. tests/models/ipcc2019/test_n2OToAirCropResidueDecompositionIndirect.py +6 -32
  86. tests/models/ipcc2019/test_n2OToAirExcretaIndirect.py +6 -32
  87. tests/models/ipcc2019/test_n2OToAirFuelCombustionIndirect.py +45 -0
  88. tests/models/ipcc2019/test_n2OToAirInorganicFertiliserIndirect.py +6 -32
  89. tests/models/ipcc2019/test_n2OToAirNaturalVegetationBurningIndirect.py +45 -0
  90. tests/models/ipcc2019/test_n2OToAirOrganicFertiliserIndirect.py +6 -32
  91. tests/models/ipcc2019/test_n2OToAirOrganicSoilBurningIndirect.py +45 -0
  92. tests/models/ipcc2019/test_n2OToAirOrganicSoilCultivationIndirect.py +45 -0
  93. tests/models/ipcc2019/test_n2OToAir_indirect_emissions_utils.py +19 -0
  94. tests/models/site/pre_checks/test_cache_geospatialDatabase.py +4 -4
  95. tests/models/test_config.py +53 -7
  96. tests/models/utils/test_background_emissions.py +13 -0
  97. {hestia_earth_models-0.73.7.dist-info → hestia_earth_models-0.74.0.dist-info}/LICENSE +0 -0
  98. {hestia_earth_models-0.73.7.dist-info → hestia_earth_models-0.74.0.dist-info}/WHEEL +0 -0
  99. {hestia_earth_models-0.73.7.dist-info → hestia_earth_models-0.74.0.dist-info}/top_level.txt +0 -0
@@ -1,10 +1,16 @@
1
1
  import os
2
2
  import json
3
+ from enum import Enum
3
4
  from hestia_earth.utils.tools import flatten
4
5
 
5
6
  CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
6
7
 
7
8
 
9
+ class AWARE_VERSION(Enum):
10
+ V1 = '1.2'
11
+ V2 = '2.0'
12
+
13
+
8
14
  def _is_aggregated_model(model: dict):
9
15
  return isinstance(model, dict) and 'aggregated' in model.get('value', '').lower()
10
16
 
@@ -17,12 +23,26 @@ def _remove_aggregated(models: list):
17
23
  return list(filter(lambda v: v is not None, values))
18
24
 
19
25
 
26
+ def _use_aware_1(models: list):
27
+ return [
28
+ _use_aware_1(m) if isinstance(m, list) else
29
+ m | {'model': 'aware'} if m.get('model') == 'aware2-0' else
30
+ m | {'value': 'awareWaterBasinId_v1'} if m.get('value') == 'awareWaterBasinId' else
31
+ m
32
+ for m in models
33
+ ]
34
+
35
+
20
36
  def _load_config(filename: str) -> dict:
21
37
  with open(os.path.join(CURRENT_DIR, f"{filename}.json"), 'r') as f:
22
38
  return json.load(f)
23
39
 
24
40
 
25
- def load_config(node_type: str, skip_aggregated_models: bool = False) -> dict:
41
+ def load_config(
42
+ node_type: str,
43
+ skip_aggregated_models: bool = False,
44
+ use_aware_version: AWARE_VERSION = AWARE_VERSION.V2
45
+ ) -> dict:
26
46
  """
27
47
  Load the configuration associated with the Node Type.
28
48
 
@@ -32,11 +52,15 @@ def load_config(node_type: str, skip_aggregated_models: bool = False) -> dict:
32
52
  The Node Type to load configuration. Can be: `Cycle`, `Site`, `ImpactAssessment`.
33
53
  skip_aggregated_models : bool
34
54
  Include models using aggregated data. Included by default.
55
+ use_aware_version : AWARE_VERSION
56
+ Choose which AWARE version to use. Defaults to using version `2.0`.
35
57
  """
36
58
  try:
37
59
  config = _load_config(node_type)
38
60
  models = config.get('models')
39
- return config | {'models': _remove_aggregated(models) if skip_aggregated_models else models}
61
+ models = _remove_aggregated(models) if skip_aggregated_models else models
62
+ models = _use_aware_1(models) if use_aware_version == AWARE_VERSION.V1 else models
63
+ return config | {'models': models}
40
64
  except FileNotFoundError:
41
65
  raise Exception(f"Invalid type {node_type}.")
42
66
 
@@ -56,12 +56,12 @@ def _should_run_animal(cycle: dict, animal: dict):
56
56
  inputs = list(filter(should_link_input_to_impact(cycle), inputs))
57
57
  nb_inputs = len(inputs)
58
58
 
59
- logRequirements(cycle, model=MODEL_ID, term=term_id, key=MODEL_KEY,
59
+ logRequirements(cycle, model=MODEL_ID, term=term_id, key=MODEL_KEY, animalId=animal.get('animalId'),
60
60
  end_date=end_date,
61
61
  nb_inputs=nb_inputs)
62
62
 
63
63
  should_run = all([end_date, nb_inputs > 0])
64
- logShouldRun(cycle, MODEL_ID, term_id, should_run, key=MODEL_KEY)
64
+ logShouldRun(cycle, MODEL_ID, term_id, should_run, key=MODEL_KEY, animalId=animal.get('animalId'))
65
65
  return should_run, inputs
66
66
 
67
67
 
@@ -50,7 +50,7 @@ def _find_related_product(input: dict):
50
50
  return find_term_match(products, input.get('term', {}).get('@id'))
51
51
 
52
52
 
53
- def _run_input_by_impactAssessment(cycle: dict):
53
+ def _run_input_by_impactAssessment(cycle: dict, **log_args):
54
54
  def exec(input: dict):
55
55
  term_id = input.get('term', {}).get('@id')
56
56
  product = _find_related_product(input)
@@ -58,7 +58,7 @@ def _run_input_by_impactAssessment(cycle: dict):
58
58
  all_properties = input.get('properties', [])
59
59
  new_properties = [p for p in properties if not find_term_match(all_properties, p.get('term', {}).get('@id'))]
60
60
  for prop in new_properties:
61
- logShouldRun(cycle, MODEL, term_id, True, property=prop.get('term', {}).get('@id'))
61
+ logShouldRun(cycle, MODEL, term_id, True, property=prop.get('term', {}).get('@id'), **log_args)
62
62
  return {**input, 'properties': merge_blank_nodes(all_properties, new_properties)} if new_properties else input
63
63
  return exec
64
64
 
@@ -80,9 +80,10 @@ def _run_animal(cycle: dict, animal: dict):
80
80
  should_run_properties_value(i)
81
81
  ])
82
82
  ]
83
- inputs = list(map(_run_input_by_impactAssessment(cycle), inputs))
84
- inputs = rescale_properties_from_dryMatter(MODEL, cycle, inputs)
85
- inputs = average_blank_node_properties_value(cycle, inputs)
83
+ log_args = {'animalId': animal.get('animalId')}
84
+ inputs = list(map(_run_input_by_impactAssessment(cycle, **log_args), inputs))
85
+ inputs = rescale_properties_from_dryMatter(MODEL, cycle, inputs, **log_args)
86
+ inputs = average_blank_node_properties_value(cycle, inputs, **log_args)
86
87
  return animal | {'inputs': inputs}
87
88
 
88
89
 
@@ -42,12 +42,17 @@ def _run(cycle: dict, animal: dict):
42
42
  practices = non_empty_list(
43
43
  [p for p in cycle.get('practices', []) if p.get('term', {}).get('@id') in practice_ids]
44
44
  )
45
+ log_args = {
46
+ 'model_key': MODEL_KEY,
47
+ 'animalId': animal.get('animalId')
48
+ }
45
49
 
46
- logRequirements(cycle, model=MODEL, term=term_id, model_key=MODEL_KEY,
47
- practice_ids=log_blank_nodes_id(practices))
50
+ logRequirements(cycle, model=MODEL, term=term_id,
51
+ practice_ids=log_blank_nodes_id(practices),
52
+ **log_args)
48
53
 
49
54
  for practice in practices:
50
- logShouldRun(cycle, MODEL, practice.get('term', {}).get('@id'), True, model_key=MODEL_KEY)
55
+ logShouldRun(cycle, MODEL, practice.get('term', {}).get('@id'), True, **log_args)
51
56
 
52
57
  return {
53
58
  **animal,
@@ -11,21 +11,21 @@ def _should_run_property_by_min_max(property: dict):
11
11
  ])
12
12
 
13
13
 
14
- def _run_property(cycle: dict, property: dict):
14
+ def _run_property(cycle: dict, property: dict, **log_args):
15
15
  term_id = property.get('term', {}).get('@id')
16
16
 
17
17
  should_run = _should_run_property_by_min_max(property)
18
- logShouldRun(cycle, MODEL, term_id, should_run, key='value')
18
+ logShouldRun(cycle, MODEL, term_id, should_run, key='value', **log_args)
19
19
 
20
20
  return property | ({
21
21
  'value': list_average([property.get('min'), property.get('max')])
22
22
  } if should_run else {})
23
23
 
24
24
 
25
- def _run_properties(cycle: dict, blank_node: dict):
25
+ def _run_properties(cycle: dict, blank_node: dict, **log_args):
26
26
  properties = blank_node.get('properties', [])
27
27
  return blank_node | ({
28
- 'properties': [_run_property(cycle, p) for p in properties]
28
+ 'properties': [_run_property(cycle, p, **log_args) for p in properties]
29
29
  } if properties else {})
30
30
 
31
31
 
@@ -33,5 +33,5 @@ def should_run_properties_value(blank_node: dict):
33
33
  return any(map(_should_run_property_by_min_max, blank_node.get('properties', [])))
34
34
 
35
35
 
36
- def average_blank_node_properties_value(cycle: dict, blank_nodes: list):
37
- return [_run_properties(cycle, v) for v in blank_nodes]
36
+ def average_blank_node_properties_value(cycle: dict, blank_nodes: list, **log_args):
37
+ return [_run_properties(cycle, v, **log_args) for v in blank_nodes]
@@ -22,7 +22,7 @@ RETURNS = {
22
22
  }
23
23
  TERM_ID = 'noxToAirExcreta'
24
24
  TIER = EmissionMethodTier.TIER_1.value
25
- N2O_TERM_ID = 'n2OToAirExcretaDirect'
25
+ _N2O_TERM_ID = 'n2OToAirExcretaDirect'
26
26
 
27
27
 
28
28
  def _emission(value: float):
@@ -32,22 +32,24 @@ def _emission(value: float):
32
32
  return emission
33
33
 
34
34
 
35
- def _run(n2o: dict):
36
- value = 0.1 * list_sum(n2o.get("value", [])) / get_atomic_conversion(Units.KG_N2O, Units.TO_N)
35
+ def _run(n2o_value: float):
36
+ value = 0.1 * n2o_value / get_atomic_conversion(Units.KG_N2O, Units.TO_N)
37
37
  value = value * get_atomic_conversion(Units.KG_NOX, Units.TO_N)
38
38
  return [_emission(value)]
39
39
 
40
40
 
41
41
  def _should_run(cycle: dict):
42
- n2o = find_term_match(cycle.get('emissions', []), N2O_TERM_ID)
42
+ n2o = find_term_match(cycle.get('emissions', []), _N2O_TERM_ID)
43
+ n2o_value = list_sum(n2o.get("value", []), default=None)
43
44
 
44
- logRequirements(cycle, model=MODEL, term=TERM_ID, has_n2o=n2o is not None)
45
+ logRequirements(cycle, model=MODEL, term=TERM_ID,
46
+ **{_N2O_TERM_ID: n2o_value})
45
47
 
46
- should_run = all([n2o])
48
+ should_run = all([n2o_value is not None])
47
49
  logShouldRun(cycle, MODEL, TERM_ID, should_run, methodTier=TIER)
48
- return should_run, n2o
50
+ return should_run, n2o_value
49
51
 
50
52
 
51
53
  def run(cycle: dict):
52
- should_run, n2o = _should_run(cycle)
53
- return _run(n2o) if should_run else []
54
+ should_run, n2o_value = _should_run(cycle)
55
+ return _run(n2o_value) if should_run else []
@@ -1,36 +1,18 @@
1
1
  import os
2
- from functools import lru_cache
3
- from hestia_earth.utils.lookup import column_name, get_table_value, load_lookup, lookup_columns
4
- from hestia_earth.utils.tools import non_empty_list
5
2
 
6
3
  from hestia_earth.models.log import logger
7
4
 
8
5
  _CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
9
- _ENV_NAME = 'ECOINVENT_V3_FILEPATH'
6
+ _ENV_FOLDER = 'ECOINVENT_V3_FOLDER'
7
+ _ECOINVENT_FOLDER = os.getenv(_ENV_FOLDER) or _CURRENT_DIR
8
+ _ECOINVENT_VERSION = os.getenv('ECOINVENT_VERSION', '3.9')
10
9
 
11
10
 
12
- @lru_cache()
13
- def _get_file():
14
- filepath = os.getenv(_ENV_NAME, f"{os.path.join(_CURRENT_DIR, 'ecoinventV3_excerpt')}.csv")
15
-
11
+ def get_filepath(term_type: str):
12
+ filename = f"ecoinventV{_ECOINVENT_VERSION.replace('.', '_')}-{term_type}.csv"
13
+ filepath = os.path.join(_ECOINVENT_FOLDER, filename)
16
14
  if not os.path.exists(filepath):
17
- logger.warning('Ecoinvent file not found. Please make sure to set env variable "%s".', _ENV_NAME)
15
+ logger.warning('Ecoinvent file not found. Please make sure to set env variable "%s".', _ENV_FOLDER)
18
16
  return None
19
17
 
20
- return load_lookup(filepath=filepath, keep_in_memory=True)
21
-
22
-
23
- def ecoinventV3_emissions(ecoinventName: str):
24
- lookup = _get_file()
25
- col_name = column_name('ecoinventName')
26
-
27
- def emission(column: str):
28
- id = get_table_value(lookup, col_name, ecoinventName, column_name(column))
29
- value = get_table_value(lookup, col_name, ecoinventName, column_name(column.replace('termid', 'value')))
30
- return (id, value) if id else None
31
-
32
- columns = [
33
- col for col in lookup_columns(lookup)
34
- if col.endswith(column_name('termid'))
35
- ]
36
- return non_empty_list(map(emission, columns))
18
+ return filepath
@@ -1,13 +1,18 @@
1
- from functools import reduce
2
1
  from statistics import mean
3
- from hestia_earth.schema import EmissionMethodTier
2
+ from hestia_earth.schema import EmissionMethodTier, TermTermType
4
3
  from hestia_earth.utils.tools import flatten, list_sum
4
+ from hestia_earth.utils.blank_node import group_by_keys
5
5
 
6
- from hestia_earth.models.log import debugValues, logShouldRun, logRequirements
6
+ from hestia_earth.models.log import logShouldRun, logRequirements
7
7
  from hestia_earth.models.utils.emission import _new_emission
8
- from hestia_earth.models.utils.background_emissions import get_background_inputs, no_gap_filled_background_emissions
9
- from hestia_earth.models.utils.blank_node import group_by_keys
10
- from .utils import get_input_mappings, ecoalim_values
8
+ from hestia_earth.models.utils.background_emissions import (
9
+ get_background_inputs,
10
+ no_gap_filled_background_emissions,
11
+ log_missing_emissions,
12
+ parse_term_id,
13
+ process_input_mappings
14
+ )
15
+ from .utils import get_input_mappings, extract_input_mapping
11
16
  from . import MODEL
12
17
 
13
18
  REQUIREMENTS = {
@@ -46,19 +51,20 @@ RETURNS = {
46
51
  }]
47
52
  }
48
53
  LOOKUPS = {
49
- "ecoalim-emissionsResourceUse": "emission-",
50
- "crop": "ecoalimMapping",
51
- "processedFood": "ecoalimMapping",
54
+ "ecoalim-emission": "emission-",
55
+ "emission": "inputProductionGroupId",
52
56
  "animalProduct": "ecoalimMapping",
57
+ "crop": "ecoalimMapping",
58
+ "feedFoodAdditive": "ecoalimMapping",
53
59
  "forage": "ecoalimMapping",
54
- "feedFoodAdditive": "ecoalimMapping"
60
+ "processedFood": "ecoalimMapping"
55
61
  }
56
62
  MODEL_KEY = 'cycle'
57
63
  TIER = EmissionMethodTier.BACKGROUND.value
58
64
 
59
65
 
60
- def _emission(term_id: str, value: float, input: dict):
61
- emission = _new_emission(term_id, MODEL)
66
+ def _emission(term_id: str, value: float, input: dict, country_id: str = None, key_id: str = None):
67
+ emission = _new_emission(term_id, MODEL, country_id, key_id)
62
68
  emission['value'] = [value]
63
69
  emission['methodTier'] = TIER
64
70
  emission['inputs'] = [input.get('term')]
@@ -69,32 +75,9 @@ def _emission(term_id: str, value: float, input: dict):
69
75
  return emission
70
76
 
71
77
 
72
- def _add_emission(cycle: dict, input: dict):
73
- input_term_id = input.get('term', {}).get('@id')
74
- operation_term_id = input.get('operation', {}).get('@id')
75
- animal_term_id = input.get('animal', {}).get('@id')
76
-
77
- def add(prev: dict, mapping: tuple):
78
- gadm_id, ecoalim_key = mapping
79
- # all countries have the same coefficient
80
- coefficient = 1
81
- emissions = ecoalim_values(ecoalim_key, 'emission')
82
- for emission_term_id, value in emissions:
83
- # log run on each emission so we know it did run
84
- logShouldRun(cycle, MODEL, input_term_id, True, methodTier=TIER, emission_id=emission_term_id)
85
- debugValues(cycle, model=MODEL, term=emission_term_id, model_key=MODEL_KEY,
86
- value=value,
87
- coefficient=coefficient,
88
- input=input_term_id,
89
- operation=operation_term_id,
90
- animal=animal_term_id)
91
- prev[emission_term_id] = prev.get(emission_term_id, []) + [value * coefficient]
92
- return prev
93
- return add
94
-
95
-
96
78
  def _run_input(cycle: dict):
97
79
  no_gap_filled_background_emissions_func = no_gap_filled_background_emissions(cycle)
80
+ log_missing_emissions_func = log_missing_emissions(cycle, model=MODEL, methodTier=TIER)
98
81
 
99
82
  def run(inputs: list):
100
83
  input = inputs[0]
@@ -103,27 +86,50 @@ def _run_input(cycle: dict):
103
86
  mappings = get_input_mappings(MODEL, input)
104
87
  has_mappings = len(mappings) > 0
105
88
 
89
+ # grouping the inputs together in the logs
90
+ input_parent_term_id = (input.get('parent', {})).get('@id') or input.get('animalId', {})
91
+ extra_logs = {
92
+ **({'input_group_id': input_parent_term_id} if input_parent_term_id else {}),
93
+ **({'animalId': input.get('animalId')} if input.get('animalId') else {})
94
+ }
95
+
106
96
  # skip input that has background emissions we have already gap-filled (model run before)
107
97
  has_no_gap_filled_background_emissions = no_gap_filled_background_emissions_func(input)
108
98
 
109
99
  logRequirements(cycle, model=MODEL, term=input_term_id, model_key=MODEL_KEY,
110
- has_ecoalim_mappings=has_mappings,
111
- ecoalim_mappings=';'.join([v[1] for v in mappings]),
100
+ has_mappings=has_mappings,
101
+ mappings=';'.join([v[1] for v in mappings]),
112
102
  has_no_gap_filled_background_emissions=has_no_gap_filled_background_emissions,
113
- input_value=input_value)
103
+ input_value=input_value,
104
+ **extra_logs)
114
105
 
115
106
  should_run = all([has_mappings, has_no_gap_filled_background_emissions, input_value])
116
- logShouldRun(cycle, MODEL, input_term_id, should_run, methodTier=TIER, model_key=MODEL_KEY)
117
-
118
- grouped_emissions = reduce(_add_emission(cycle, input), mappings, {}) if should_run else {}
107
+ logShouldRun(cycle, MODEL, input_term_id, should_run, methodTier=TIER, model_key=MODEL_KEY, **extra_logs)
108
+
109
+ results = process_input_mappings(
110
+ cycle, input, mappings, TermTermType.EMISSION,
111
+ extract_mapping=extract_input_mapping,
112
+ **(
113
+ extra_logs | {'model': MODEL, 'model_key': MODEL_KEY}
114
+ )
115
+ ) if should_run else {}
116
+ log_missing_emissions_func(input_term_id, list(map(parse_term_id, results.keys())), **(
117
+ extra_logs | {'has_mappings': has_mappings}
118
+ ))
119
119
  return [
120
- _emission(term_id, mean(value) * input_value, input)
121
- for term_id, value in grouped_emissions.items()
120
+ _emission(
121
+ term_id=parse_term_id(term_id),
122
+ value=mean([v['value'] * v['coefficient'] for v in values]) * input_value,
123
+ input=input,
124
+ country_id=values[0].get('country'),
125
+ key_id=values[0].get('key'),
126
+ )
127
+ for term_id, values in results.items()
122
128
  ]
123
129
  return run
124
130
 
125
131
 
126
132
  def run(cycle: dict):
127
133
  inputs = get_background_inputs(cycle)
128
- grouped_inputs = reduce(group_by_keys(['term', 'operation', 'animal']), inputs, {})
134
+ grouped_inputs = group_by_keys(inputs, ['term', 'operation', 'animal'])
129
135
  return flatten(map(_run_input(cycle), grouped_inputs.values()))
@@ -1,13 +1,17 @@
1
- from functools import reduce
2
1
  from statistics import mean
3
- from hestia_earth.schema import IndicatorMethodTier
2
+ from hestia_earth.schema import IndicatorMethodTier, TermTermType
4
3
  from hestia_earth.utils.tools import flatten, list_sum
4
+ from hestia_earth.utils.blank_node import group_by_keys
5
5
 
6
- from hestia_earth.models.log import debugValues, logShouldRun, logRequirements
6
+ from hestia_earth.models.log import logShouldRun, logRequirements
7
7
  from hestia_earth.models.utils.indicator import _new_indicator
8
- from hestia_earth.models.utils.background_emissions import get_background_inputs
9
- from hestia_earth.models.utils.blank_node import group_by_keys
10
- from .utils import get_input_mappings, ecoalim_values
8
+ from hestia_earth.models.utils.background_emissions import (
9
+ get_background_inputs,
10
+ log_missing_emissions,
11
+ parse_term_id,
12
+ process_input_mappings
13
+ )
14
+ from .utils import get_input_mappings, extract_input_mapping
11
15
  from . import MODEL
12
16
 
13
17
  REQUIREMENTS = {
@@ -48,53 +52,42 @@ RETURNS = {
48
52
  }]
49
53
  }
50
54
  LOOKUPS = {
51
- "ecoalim-emissionsResourceUse": "resourceUse-",
52
- "crop": "ecoalimMapping",
53
- "processedFood": "ecoalimMapping",
55
+ "ecoalim-resourceUse": "resourceUse-",
54
56
  "animalProduct": "ecoalimMapping",
57
+ "crop": "ecoalimMapping",
58
+ "feedFoodAdditive": "ecoalimMapping",
55
59
  "forage": "ecoalimMapping",
56
- "feedFoodAdditive": "ecoalimMapping"
60
+ "processedFood": "ecoalimMapping"
57
61
  }
58
62
  MODEL_KEY = 'impact_assessment'
59
63
  TIER = IndicatorMethodTier.BACKGROUND.value
60
64
 
61
65
 
62
- def _indicator(term_id: str, value: float, input: dict):
63
- indicator = _new_indicator(term_id, MODEL)
66
+ def _indicator(
67
+ term_id: str,
68
+ value: float,
69
+ input: dict,
70
+ country_id: str = None,
71
+ key_id: str = None,
72
+ land_cover_id: str = None,
73
+ previous_land_cover_id: str = None,
74
+ ):
75
+ indicator = _new_indicator(term_id, MODEL, land_cover_id, previous_land_cover_id, country_id, key_id)
64
76
  indicator['value'] = value
65
77
  indicator['methodTier'] = TIER
66
78
  indicator['inputs'] = [input.get('term')]
67
79
  if input.get('operation'):
68
80
  indicator['operation'] = input.get('operation')
81
+ if input.get('animal'):
82
+ indicator['animals'] = [input.get('animal')]
69
83
  return indicator
70
84
 
71
85
 
72
- def _add_indicator(cycle: dict, input: dict):
73
- input_term_id = input.get('term', {}).get('@id')
74
- operation_term_id = input.get('operation', {}).get('@id')
75
- animal_term_id = input.get('animal', {}).get('@id')
76
-
77
- def add(prev: dict, mapping: tuple):
78
- gadm_id, ecoalim_key = mapping
79
- # all countries have the same coefficient
80
- coefficient = 1
81
- indicators = ecoalim_values(ecoalim_key, 'resourceUse')
82
- for indicator_term_id, value in indicators:
83
- # log run on each indicator so we know it did run
84
- logShouldRun(cycle, MODEL, input_term_id, True, methodTier=TIER, emission_id=indicator_term_id)
85
- debugValues(cycle, model=MODEL, term=indicator_term_id, model_key=MODEL_KEY,
86
- value=value,
87
- coefficient=coefficient,
88
- input=input_term_id,
89
- operation=operation_term_id,
90
- animal=animal_term_id)
91
- if value is not None:
92
- prev[indicator_term_id] = prev.get(indicator_term_id, []) + [value * coefficient]
93
- return prev
94
- return add
95
-
96
-
97
86
  def _run_input(impact_assessment: dict):
87
+ log_missing_emissions_func = log_missing_emissions(
88
+ impact_assessment, TermTermType.RESOURCEUSE, model=MODEL, methodTier=TIER
89
+ )
90
+
98
91
  def run(inputs: list):
99
92
  input = inputs[0]
100
93
  input_term_id = input.get('term', {}).get('@id')
@@ -102,25 +95,50 @@ def _run_input(impact_assessment: dict):
102
95
  mappings = get_input_mappings(MODEL, input)
103
96
  has_mappings = len(mappings) > 0
104
97
 
98
+ # grouping the inputs together in the logs
99
+ input_parent_term_id = (input.get('parent', {})).get('@id') or input.get('animalId', {})
100
+ extra_logs = {
101
+ **({'input_group_id': input_parent_term_id} if input_parent_term_id else {}),
102
+ **({'animalId': input.get('animalId')} if input.get('animalId') else {})
103
+ }
104
+
105
105
  logRequirements(impact_assessment, model=MODEL, term=input_term_id, model_key=MODEL_KEY,
106
- has_ecoalim_mappings=has_mappings,
107
- ecoalim_mappings=';'.join([v[1] for v in mappings]),
108
- input_value=input_value)
106
+ has_mappings=has_mappings,
107
+ mappings=';'.join([v[1] for v in mappings]),
108
+ input_value=input_value,
109
+ **extra_logs)
109
110
 
110
111
  should_run = all([has_mappings, input_value])
111
112
  logShouldRun(
112
- impact_assessment, MODEL, input_term_id, should_run, methodTier=TIER, model_key=MODEL_KEY
113
+ impact_assessment, MODEL, input_term_id, should_run, methodTier=TIER, model_key=MODEL_KEY, **extra_logs
113
114
  )
114
115
 
115
- grouped_indicators = reduce(_add_indicator(impact_assessment, input), mappings, {}) if should_run else {}
116
+ results = process_input_mappings(
117
+ impact_assessment, input, mappings, TermTermType.RESOURCEUSE,
118
+ extract_mapping=extract_input_mapping,
119
+ **(
120
+ extra_logs | {'model': MODEL, 'model_key': MODEL_KEY}
121
+ )
122
+ ) if should_run else {}
123
+ log_missing_emissions_func(input_term_id, list(map(parse_term_id, results.keys())), **(
124
+ extra_logs | {'has_mappings': has_mappings}
125
+ ))
116
126
  return [
117
- _indicator(term_id, mean(value) * input_value, input)
118
- for term_id, value in grouped_indicators.items()
127
+ _indicator(
128
+ term_id=parse_term_id(term_id),
129
+ value=mean([v['value'] * v['coefficient'] for v in values]) * input_value,
130
+ input=input,
131
+ country_id=values[0].get('country'),
132
+ key_id=values[0].get('key'),
133
+ land_cover_id=values[0].get('landCover'),
134
+ previous_land_cover_id=values[0].get('previousLandCover'),
135
+ )
136
+ for term_id, values in results.items()
119
137
  ]
120
138
  return run
121
139
 
122
140
 
123
141
  def run(impact_assessment: dict):
124
142
  inputs = get_background_inputs(impact_assessment.get('cycle', {}))
125
- grouped_inputs = reduce(group_by_keys(['term', 'operation']), inputs, {})
143
+ grouped_inputs = group_by_keys(inputs, ['term', 'operation', 'animal'])
126
144
  return flatten(map(_run_input(impact_assessment), grouped_inputs.values()))
@@ -1,10 +1,12 @@
1
- from hestia_earth.utils.lookup import download_lookup, get_table_value, column_name, lookup_columns
1
+ from functools import lru_cache
2
+ from hestia_earth.schema import TermTermType
3
+ from hestia_earth.utils.lookup import download_lookup, column_name
2
4
  from hestia_earth.utils.tools import non_empty_list
3
5
 
4
6
  from hestia_earth.models.utils.term import get_lookup_value
7
+ from hestia_earth.models.utils.background_emissions import convert_background_lookup
5
8
 
6
-
7
- _LOOKUP = "ecoalim-emissionsResourceUse.csv"
9
+ _LOOKUP_INDEX_KEY = column_name('ecoalimMappingName')
8
10
 
9
11
 
10
12
  def get_input_mappings(model: str, input: dict):
@@ -15,17 +17,21 @@ def get_input_mappings(model: str, input: dict):
15
17
  return [(m.split(':')[0], m.split(':')[1]) for m in mappings]
16
18
 
17
19
 
18
- def ecoalim_values(mapping: str, column_prefix: str):
19
- lookup = download_lookup(_LOOKUP)
20
- col_name = column_name('ecoalimMappingName')
20
+ def extract_input_mapping(mapping: tuple, term_type: TermTermType):
21
+ gadm_id, mapping_name = mapping
22
+ # # all countries have the same coefficient
23
+ coefficient = 1
24
+ values = ecoalim_values(mapping_name, term_type)
25
+ return values, coefficient
26
+
27
+
28
+ @lru_cache()
29
+ def _build_lookup(term_type: str):
30
+ lookup = download_lookup(f"ecoalim-{term_type}.csv", keep_in_memory=False)
31
+ return convert_background_lookup(lookup=lookup, index_column=_LOOKUP_INDEX_KEY)
21
32
 
22
- def emission(column: str):
23
- id = get_table_value(lookup, col_name, mapping, column)
24
- value = get_table_value(lookup, col_name, mapping, column.replace('term', 'value'))
25
- return (id, value) if id else None
26
33
 
27
- columns = [
28
- col for col in lookup_columns(lookup)
29
- if col.startswith(column_name(column_prefix)) and col.endswith(column_name('term'))
30
- ]
31
- return non_empty_list(map(emission, columns))
34
+ @lru_cache()
35
+ def ecoalim_values(mapping: str, term_type: TermTermType):
36
+ data = _build_lookup(term_type.value)
37
+ return list(data[mapping].items())