hestia-earth-models 0.70.5__py3-none-any.whl → 0.71.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.
Files changed (35) hide show
  1. hestia_earth/models/cache_nodes.py +157 -0
  2. hestia_earth/models/cache_sites.py +1 -1
  3. hestia_earth/models/config/Cycle.json +0 -30
  4. hestia_earth/models/data/ecoinventV3/__init__.py +7 -5
  5. hestia_earth/models/ecoinventV3/__init__.py +8 -1
  6. hestia_earth/models/ecoinventV3AndEmberClimate/__init__.py +1 -0
  7. hestia_earth/models/emepEea2019/co2ToAirFuelCombustion.py +20 -15
  8. hestia_earth/models/emepEea2019/n2OToAirFuelCombustionDirect.py +21 -16
  9. hestia_earth/models/emepEea2019/noxToAirFuelCombustion.py +20 -15
  10. hestia_earth/models/emepEea2019/so2ToAirFuelCombustion.py +20 -15
  11. hestia_earth/models/emepEea2019/utils.py +73 -25
  12. hestia_earth/models/hestia/aboveGroundCropResidue.py +3 -3
  13. hestia_earth/models/hestia/management.py +12 -7
  14. hestia_earth/models/hestia/seed_emissions.py +25 -21
  15. hestia_earth/models/mocking/search-results.json +1506 -1502
  16. hestia_earth/models/utils/background_emissions.py +24 -0
  17. hestia_earth/models/utils/blank_node.py +4 -1
  18. hestia_earth/models/utils/pesticideAI.py +1 -1
  19. hestia_earth/models/version.py +1 -1
  20. {hestia_earth_models-0.70.5.dist-info → hestia_earth_models-0.71.0.dist-info}/METADATA +2 -2
  21. {hestia_earth_models-0.70.5.dist-info → hestia_earth_models-0.71.0.dist-info}/RECORD +31 -33
  22. tests/models/emepEea2019/test_co2ToAirFuelCombustion.py +1 -14
  23. tests/models/emepEea2019/test_n2OToAirFuelCombustionDirect.py +1 -14
  24. tests/models/emepEea2019/test_noxToAirFuelCombustion.py +1 -14
  25. tests/models/emepEea2019/test_so2ToAirFuelCombustion.py +1 -14
  26. tests/models/emepEea2019/test_utils.py +1 -49
  27. tests/models/hestia/test_management.py +2 -1
  28. tests/models/test_cache_nodes.py +31 -0
  29. hestia_earth/models/ipcc2006/co2ToAirOrganicSoilCultivation.py +0 -100
  30. hestia_earth/models/ipcc2006/n2OToAirOrganicSoilCultivationDirect.py +0 -99
  31. tests/models/ipcc2006/test_co2ToAirOrganicSoilCultivation.py +0 -49
  32. tests/models/ipcc2006/test_n2OToAirOrganicSoilCultivationDirect.py +0 -32
  33. {hestia_earth_models-0.70.5.dist-info → hestia_earth_models-0.71.0.dist-info}/LICENSE +0 -0
  34. {hestia_earth_models-0.70.5.dist-info → hestia_earth_models-0.71.0.dist-info}/WHEEL +0 -0
  35. {hestia_earth_models-0.70.5.dist-info → hestia_earth_models-0.71.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,157 @@
1
+ import os
2
+ from functools import reduce
3
+ from hestia_earth.schema import NodeType
4
+ from hestia_earth.utils.tools import current_time_ms, flatten
5
+ from hestia_earth.earth_engine import init_gee
6
+
7
+ from .log import logger
8
+ from .utils import CACHE_KEY
9
+ from .utils.site import years_from_cycles
10
+ from .utils.source import CACHE_SOURCES_KEY, find_sources
11
+ from .cache_sites import run as cache_sites
12
+
13
+ CACHE_RELATED_KEY = 'related'
14
+ CACHE_NESTED_KEY = 'nested'
15
+
16
+ _CACHE_BATCH_SIZE = int(os.getenv('CACHE_SITES_BATCH_SIZE', '5000'))
17
+ _ENABLE_CACHE_YEARS = os.getenv('ENABLE_CACHE_YEARS', 'true') == 'true'
18
+ _ENABLE_CACHE_RELATED_NODES = os.getenv('ENABLE_CACHE_RELATED_NODES', 'true') == 'true'
19
+ _CACHE_NODE_TYPES = [
20
+ NodeType.SITE.value,
21
+ NodeType.CYCLE.value,
22
+ NodeType.IMPACTASSESSMENT.value
23
+ ]
24
+
25
+
26
+ def _pop_items(values: list, nb_items: int):
27
+ if len(values) < nb_items:
28
+ removed_items = values[:] # Get a copy of the entire array
29
+ values.clear() # Remove all items from the original array
30
+ else:
31
+ removed_items = values[:nb_items] # Get the first N items
32
+ del values[:nb_items] # Remove the first N items from the original array
33
+
34
+ return removed_items
35
+
36
+
37
+ def _filter_by_type(nodes: list, type: str): return [n for n in nodes if n.get('@type', n.get('type')) == type]
38
+
39
+
40
+ def _node_key(node: dict): return '/'.join([node.get('type', node.get('@type')), node.get('id', node.get('@id'))])
41
+
42
+
43
+ def _years_from_cycles(nodes: dict): return years_from_cycles(_filter_by_type(nodes, NodeType.CYCLE.value))
44
+
45
+
46
+ def _linked_node(data: dict): return {'type': data.get('type'), 'id': data.get('id')}
47
+
48
+
49
+ def _find_nested_nodes(data) -> list[dict]:
50
+ if isinstance(data, dict):
51
+ if data.get('type') in _CACHE_NODE_TYPES and data.get('id'):
52
+ return [_linked_node(data)]
53
+ return flatten(_find_nested_nodes(list(data.values())))
54
+ if isinstance(data, list):
55
+ return flatten(map(_find_nested_nodes, data))
56
+ return []
57
+
58
+
59
+ def _nested_nodes(node_keys: list[str]):
60
+ def exec(group: dict, node: dict):
61
+ nested_nodes = _find_nested_nodes(list(node.values()))
62
+
63
+ for nested_node in nested_nodes:
64
+ group_id = _node_key(nested_node)
65
+ group[group_id] = group.get(group_id, {})
66
+ group[group_id][CACHE_RELATED_KEY] = group.get(group_id, {}).get(CACHE_RELATED_KEY, []) + [
67
+ _linked_node(node)
68
+ ]
69
+
70
+ # cache nodes that current node refers (nesting)
71
+ if group_id in node_keys:
72
+ group_id = _node_key(node)
73
+ group[group_id] = group.get(group_id, {})
74
+ group[group_id][CACHE_NESTED_KEY] = group.get(group_id, {}).get(CACHE_NESTED_KEY, []) + [
75
+ _linked_node(nested_node)
76
+ ]
77
+
78
+ return group
79
+ return exec
80
+
81
+
82
+ def _cache_related_nodes(nodes: list):
83
+ # only cache nodes included in the file
84
+ nodes_keys = list(map(_node_key, nodes))
85
+ # for each node, compile list of nested nodes
86
+ nested_nodes_mapping = reduce(_nested_nodes(nodes_keys), nodes, {})
87
+
88
+ def cache_related_node(node: dict):
89
+ nodes_mapping = nested_nodes_mapping.get(_node_key(node), {})
90
+ related_nodes = nodes_mapping.get(CACHE_RELATED_KEY) or []
91
+ nested_nodes = nodes_mapping.get(CACHE_NESTED_KEY) or []
92
+ # save in cache
93
+ cached_data = node.get(CACHE_KEY, {}) | {
94
+ CACHE_RELATED_KEY: related_nodes,
95
+ CACHE_NESTED_KEY: nested_nodes
96
+ }
97
+ return node | {CACHE_KEY: cached_data}
98
+
99
+ return list(map(cache_related_node, nodes))
100
+
101
+
102
+ def _cache_sources(nodes: list):
103
+ sources = find_sources()
104
+ return [
105
+ n | ({
106
+ CACHE_KEY: n.get(CACHE_KEY, {}) | {CACHE_SOURCES_KEY: sources}
107
+ } if n.get('type', n.get('@type')) in _CACHE_NODE_TYPES else {})
108
+ for n in nodes
109
+ ]
110
+
111
+
112
+ def _safe_cache_sites(sites: list, years: list):
113
+ try:
114
+ return cache_sites(sites, years)
115
+ except Exception as e:
116
+ logger.error(f"An error occured while caching nodes on EE: {str(e)}")
117
+ if 'exceeded' in str(e):
118
+ logger.debug('Fallback to caching sites one by one')
119
+ # run one by one in case the batching does not work
120
+ return flatten([cache_sites([site], years) for site in sites])
121
+ else:
122
+ raise e
123
+
124
+
125
+ def _cache_sites(nodes: list, batch_size: int = _CACHE_BATCH_SIZE):
126
+ start = current_time_ms()
127
+
128
+ # build list of nodes by key to update as sites are processed
129
+ nodes_mapping = {_node_key(n): n for n in nodes}
130
+
131
+ years = _years_from_cycles(nodes) if _ENABLE_CACHE_YEARS else []
132
+ sites = _filter_by_type(nodes, 'Site')
133
+
134
+ while len(sites) > 0:
135
+ batch_values = _pop_items(sites, batch_size)
136
+ logger.info(f"Processing {len(batch_values)} sites / {len(sites)} remaining.")
137
+ results = _safe_cache_sites(batch_values, years)
138
+ for result in results:
139
+ nodes_mapping[_node_key(result)] = result
140
+
141
+ logger.info(f"Done caching sites in {current_time_ms() - start} ms")
142
+
143
+ # replace original sites with new cached sites
144
+ return list(nodes_mapping.values())
145
+
146
+
147
+ def run(nodes: list):
148
+ init_gee()
149
+
150
+ # cache sites data
151
+ cached_nodes = _cache_sites(nodes)
152
+
153
+ # cache related nodes
154
+ cached_nodes = _cache_related_nodes(cached_nodes) if _ENABLE_CACHE_RELATED_NODES else cached_nodes
155
+
156
+ # cache sources
157
+ return _cache_sources(cached_nodes)
@@ -81,7 +81,7 @@ def _run_values(
81
81
  site_cache = merge(
82
82
  site.get(CACHE_KEY, {}),
83
83
  {CACHE_GEOSPATIAL_KEY: cached_data},
84
- ({CACHE_YEARS_KEY: list(set(cached_value(site, CACHE_YEARS_KEY, []) + years))} if years else {})
84
+ ({CACHE_YEARS_KEY: sorted(list(set(cached_value(site, CACHE_YEARS_KEY, []) + years)))} if years else {})
85
85
  )
86
86
  return merge(site, {CACHE_KEY: site_cache})
87
87
 
@@ -2054,36 +2054,6 @@
2054
2054
  },
2055
2055
  "stage": 2
2056
2056
  },
2057
- {
2058
- "key": "emissions",
2059
- "model": "ipcc2006",
2060
- "value": "n2OToAirOrganicSoilCultivationDirect",
2061
- "runStrategy": "add_blank_node_if_missing",
2062
- "runArgs": {
2063
- "runNonMeasured": true,
2064
- "runNonAddedTerm": true
2065
- },
2066
- "mergeStrategy": "list",
2067
- "mergeArgs": {
2068
- "replaceThreshold": ["value", 0.01]
2069
- },
2070
- "stage": 2
2071
- },
2072
- {
2073
- "key": "emissions",
2074
- "model": "ipcc2006",
2075
- "value": "co2ToAirOrganicSoilCultivation",
2076
- "runStrategy": "add_blank_node_if_missing",
2077
- "runArgs": {
2078
- "runNonMeasured": true,
2079
- "runNonAddedTerm": true
2080
- },
2081
- "mergeStrategy": "list",
2082
- "mergeArgs": {
2083
- "replaceThreshold": ["value", 0.01]
2084
- },
2085
- "stage": 2
2086
- },
2087
2057
  {
2088
2058
  "key": "emissions",
2089
2059
  "model": "ipcc2006",
@@ -5,17 +5,19 @@ from hestia_earth.utils.tools import non_empty_list
5
5
 
6
6
  from hestia_earth.models.log import logger
7
7
 
8
- CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
9
- _FILEPATH = os.getenv('ECOINVENT_V3_FILEPATH', f"{os.path.join(CURRENT_DIR, 'ecoinventV3_excerpt')}.csv")
8
+ _CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
9
+ _ENV_NAME = 'ECOINVENT_V3_FILEPATH'
10
10
 
11
11
 
12
12
  @lru_cache()
13
13
  def _get_file():
14
- if not os.path.exists(_FILEPATH):
15
- logger.warning('Ecoinvent file not found. Please make sure to set env variable "ECOINVENT_V3_FILEPATH".')
14
+ filepath = os.getenv(_ENV_NAME, f"{os.path.join(_CURRENT_DIR, 'ecoinventV3_excerpt')}.csv")
15
+
16
+ if not os.path.exists(filepath):
17
+ logger.warning('Ecoinvent file not found. Please make sure to set env variable "%s".', _ENV_NAME)
16
18
  return None
17
19
 
18
- return load_lookup(filepath=_FILEPATH, keep_in_memory=True)
20
+ return load_lookup(filepath=filepath, keep_in_memory=True)
19
21
 
20
22
 
21
23
  def ecoinventV3_emissions(ecoinventName: str):
@@ -5,7 +5,11 @@ from hestia_earth.utils.tools import flatten, list_sum
5
5
  from hestia_earth.models.log import debugValues, logShouldRun, logRequirements
6
6
  from hestia_earth.models.data.ecoinventV3 import ecoinventV3_emissions
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
8
+ from hestia_earth.models.utils.background_emissions import (
9
+ get_background_inputs,
10
+ no_gap_filled_background_emissions,
11
+ log_missing_emissions
12
+ )
9
13
  from hestia_earth.models.utils.blank_node import group_by_keys
10
14
  from hestia_earth.models.utils.pesticideAI import get_pesticides_from_inputs
11
15
  from hestia_earth.models.utils.fertiliser import get_fertilisers_from_inputs
@@ -47,6 +51,7 @@ RETURNS = {
47
51
  }]
48
52
  }
49
53
  LOOKUPS = {
54
+ "emission": "inputProductionGroupId",
50
55
  "electricity": "ecoinventMapping",
51
56
  "fuel": "ecoinventMapping",
52
57
  "inorganicFertiliser": "ecoinventMapping",
@@ -97,6 +102,7 @@ def _add_emission(cycle: dict, input: dict):
97
102
 
98
103
  def _run_input(cycle: dict):
99
104
  no_gap_filled_background_emissions_func = no_gap_filled_background_emissions(cycle)
105
+ log_missing_emissions_func = log_missing_emissions(cycle, model=MODEL, methodTier=TIER)
100
106
 
101
107
  def run(inputs: list):
102
108
  input = inputs[0]
@@ -118,6 +124,7 @@ def _run_input(cycle: dict):
118
124
  logShouldRun(cycle, MODEL, input_term_id, should_run, methodTier=TIER)
119
125
 
120
126
  grouped_emissions = reduce(_add_emission(cycle, input), mappings, {}) if should_run else {}
127
+ log_missing_emissions_func(input_term_id, list(grouped_emissions.keys()))
121
128
  return [
122
129
  _emission(term_id, value * input_value, input)
123
130
  for term_id, value in grouped_emissions.items()
@@ -118,6 +118,7 @@ def _run_input(cycle: dict):
118
118
  has_ecoinvent_mappings=has_mappings,
119
119
  ecoinvent_mappings=';'.join([v[0] for v in mappings]),
120
120
  has_no_gap_filled_background_emissions=has_no_gap_filled_background_emissions,
121
+ termType_electricityFuel_complete=electricity_complete,
121
122
  input_value=input_value)
122
123
 
123
124
  should_run = all([electricity_complete, has_mappings, has_no_gap_filled_background_emissions, input_value])
@@ -1,8 +1,8 @@
1
1
  from hestia_earth.schema import EmissionMethodTier
2
- from hestia_earth.utils.tools import list_sum
3
2
 
4
- from hestia_earth.models.log import logRequirements, logShouldRun
5
- from .utils import get_fuel_values, _emission
3
+ from hestia_earth.models.log import logRequirements, logShouldRun, log_as_table
4
+ from hestia_earth.models.utils.completeness import _is_term_type_complete
5
+ from .utils import get_fuel_inputs, group_fuel_inputs, _emission, _run_inputs
6
6
  from . import MODEL
7
7
 
8
8
  REQUIREMENTS = {
@@ -20,6 +20,8 @@ REQUIREMENTS = {
20
20
  RETURNS = {
21
21
  "Emission": [{
22
22
  "value": "",
23
+ "inputs": "",
24
+ "operation": "",
23
25
  "methodTier": "tier 1"
24
26
  }]
25
27
  }
@@ -31,23 +33,26 @@ TERM_ID = 'co2ToAirFuelCombustion'
31
33
  TIER = EmissionMethodTier.TIER_1.value
32
34
 
33
35
 
34
- def _run(fuel_values: list):
35
- value = list_sum(fuel_values)
36
- return [_emission(value=value, tier=TIER, term_id=TERM_ID)]
37
-
38
-
39
36
  def _should_run(cycle: dict):
40
- fuel_values = get_fuel_values(TERM_ID, cycle, LOOKUPS['fuel'])
41
- has_fuel = len(fuel_values) > 0
37
+ electricity_complete = _is_term_type_complete(cycle, 'electricityFuel')
38
+ fuel_inputs, valid_inputs = get_fuel_inputs(TERM_ID, cycle, LOOKUPS['fuel'])
42
39
 
43
40
  logRequirements(cycle, model=MODEL, term=TERM_ID,
44
- has_fuel=has_fuel)
41
+ termType_electricityFuel_complete=electricity_complete,
42
+ fuel_inputs=log_as_table(fuel_inputs))
45
43
 
46
- should_run = any([has_fuel])
44
+ should_run = any([bool(valid_inputs), electricity_complete])
47
45
  logShouldRun(cycle, MODEL, TERM_ID, should_run, methodTier=TIER)
48
- return should_run, fuel_values
46
+ return should_run, group_fuel_inputs(valid_inputs)
49
47
 
50
48
 
51
49
  def run(cycle: dict):
52
- should_run, fuel_values = _should_run(cycle)
53
- return _run(fuel_values) if should_run else []
50
+ should_run, fuel_inputs = _should_run(cycle)
51
+ return (
52
+ [
53
+ _run_inputs(inputs, tier=TIER, term_id=TERM_ID)
54
+ for inputs in fuel_inputs.values()
55
+ ] if fuel_inputs else [
56
+ _emission(value=0, tier=TIER, term_id=TERM_ID)
57
+ ]
58
+ ) if should_run else []
@@ -1,8 +1,8 @@
1
1
  from hestia_earth.schema import EmissionMethodTier
2
- from hestia_earth.utils.tools import list_sum
3
2
 
4
- from hestia_earth.models.log import logRequirements, logShouldRun
5
- from .utils import get_fuel_values, _emission
3
+ from hestia_earth.models.log import logRequirements, logShouldRun, log_as_table
4
+ from hestia_earth.models.utils.completeness import _is_term_type_complete
5
+ from .utils import get_fuel_inputs, group_fuel_inputs, _emission, _run_inputs
6
6
  from . import MODEL
7
7
 
8
8
  REQUIREMENTS = {
@@ -10,7 +10,7 @@ REQUIREMENTS = {
10
10
  "or": {
11
11
  "inputs": [
12
12
  {"@type": "Input", "value": "", "term.termType": "fuel", "optional": {
13
- "operation": ""
13
+ "operation": ""
14
14
  }}
15
15
  ],
16
16
  "completeness.electricityFuel": "True"
@@ -20,6 +20,8 @@ REQUIREMENTS = {
20
20
  RETURNS = {
21
21
  "Emission": [{
22
22
  "value": "",
23
+ "inputs": "",
24
+ "operation": "",
23
25
  "methodTier": "tier 1"
24
26
  }]
25
27
  }
@@ -31,23 +33,26 @@ TERM_ID = 'n2OToAirFuelCombustionDirect'
31
33
  TIER = EmissionMethodTier.TIER_1.value
32
34
 
33
35
 
34
- def _run(fuel_values: list):
35
- value = list_sum(fuel_values)
36
- return [_emission(value=value, tier=TIER, term_id=TERM_ID)]
37
-
38
-
39
36
  def _should_run(cycle: dict):
40
- fuel_values = get_fuel_values(TERM_ID, cycle, LOOKUPS['fuel'])
41
- has_fuel = len(fuel_values) > 0
37
+ electricity_complete = _is_term_type_complete(cycle, 'electricityFuel')
38
+ fuel_inputs, valid_inputs = get_fuel_inputs(TERM_ID, cycle, LOOKUPS['fuel'])
42
39
 
43
40
  logRequirements(cycle, model=MODEL, term=TERM_ID,
44
- has_fuel=has_fuel)
41
+ termType_electricityFuel_complete=electricity_complete,
42
+ fuel_inputs=log_as_table(fuel_inputs))
45
43
 
46
- should_run = any([has_fuel])
44
+ should_run = any([bool(valid_inputs), electricity_complete])
47
45
  logShouldRun(cycle, MODEL, TERM_ID, should_run, methodTier=TIER)
48
- return should_run, fuel_values
46
+ return should_run, group_fuel_inputs(valid_inputs)
49
47
 
50
48
 
51
49
  def run(cycle: dict):
52
- should_run, fuel_values = _should_run(cycle)
53
- return _run(fuel_values) if should_run else []
50
+ should_run, fuel_inputs = _should_run(cycle)
51
+ return (
52
+ [
53
+ _run_inputs(inputs, tier=TIER, term_id=TERM_ID)
54
+ for inputs in fuel_inputs.values()
55
+ ] if fuel_inputs else [
56
+ _emission(value=0, tier=TIER, term_id=TERM_ID)
57
+ ]
58
+ ) if should_run else []
@@ -1,8 +1,8 @@
1
1
  from hestia_earth.schema import EmissionMethodTier
2
- from hestia_earth.utils.tools import list_sum
3
2
 
4
- from hestia_earth.models.log import logRequirements, logShouldRun
5
- from .utils import get_fuel_values, _emission
3
+ from hestia_earth.models.log import logRequirements, logShouldRun, log_as_table
4
+ from hestia_earth.models.utils.completeness import _is_term_type_complete
5
+ from .utils import get_fuel_inputs, group_fuel_inputs, _emission, _run_inputs
6
6
  from . import MODEL
7
7
 
8
8
  REQUIREMENTS = {
@@ -20,6 +20,8 @@ REQUIREMENTS = {
20
20
  RETURNS = {
21
21
  "Emission": [{
22
22
  "value": "",
23
+ "inputs": "",
24
+ "operation": "",
23
25
  "methodTier": "tier 1"
24
26
  }]
25
27
  }
@@ -31,23 +33,26 @@ TERM_ID = 'noxToAirFuelCombustion'
31
33
  TIER = EmissionMethodTier.TIER_1.value
32
34
 
33
35
 
34
- def _run(fuel_values: list):
35
- value = list_sum(fuel_values)
36
- return [_emission(value=value, tier=TIER, term_id=TERM_ID)]
37
-
38
-
39
36
  def _should_run(cycle: dict):
40
- fuel_values = get_fuel_values(TERM_ID, cycle, LOOKUPS['fuel'])
41
- has_fuel = len(fuel_values) > 0
37
+ electricity_complete = _is_term_type_complete(cycle, 'electricityFuel')
38
+ fuel_inputs, valid_inputs = get_fuel_inputs(TERM_ID, cycle, LOOKUPS['fuel'])
42
39
 
43
40
  logRequirements(cycle, model=MODEL, term=TERM_ID,
44
- has_fuel=has_fuel)
41
+ termType_electricityFuel_complete=electricity_complete,
42
+ fuel_inputs=log_as_table(fuel_inputs))
45
43
 
46
- should_run = any([has_fuel])
44
+ should_run = any([bool(valid_inputs), electricity_complete])
47
45
  logShouldRun(cycle, MODEL, TERM_ID, should_run, methodTier=TIER)
48
- return should_run, fuel_values
46
+ return should_run, group_fuel_inputs(valid_inputs)
49
47
 
50
48
 
51
49
  def run(cycle: dict):
52
- should_run, fuel_values = _should_run(cycle)
53
- return _run(fuel_values) if should_run else []
50
+ should_run, fuel_inputs = _should_run(cycle)
51
+ return (
52
+ [
53
+ _run_inputs(inputs, tier=TIER, term_id=TERM_ID)
54
+ for inputs in fuel_inputs.values()
55
+ ] if fuel_inputs else [
56
+ _emission(value=0, tier=TIER, term_id=TERM_ID)
57
+ ]
58
+ ) if should_run else []
@@ -1,8 +1,8 @@
1
1
  from hestia_earth.schema import EmissionMethodTier
2
- from hestia_earth.utils.tools import list_sum
3
2
 
4
- from hestia_earth.models.log import logRequirements, logShouldRun
5
- from .utils import get_fuel_values, _emission
3
+ from hestia_earth.models.log import logRequirements, logShouldRun, log_as_table
4
+ from hestia_earth.models.utils.completeness import _is_term_type_complete
5
+ from .utils import get_fuel_inputs, group_fuel_inputs, _emission, _run_inputs
6
6
  from . import MODEL
7
7
 
8
8
  REQUIREMENTS = {
@@ -20,6 +20,8 @@ REQUIREMENTS = {
20
20
  RETURNS = {
21
21
  "Emission": [{
22
22
  "value": "",
23
+ "inputs": "",
24
+ "operation": "",
23
25
  "methodTier": "tier 1"
24
26
  }]
25
27
  }
@@ -31,23 +33,26 @@ TERM_ID = 'so2ToAirFuelCombustion'
31
33
  TIER = EmissionMethodTier.TIER_1.value
32
34
 
33
35
 
34
- def _run(fuel_values: list):
35
- value = list_sum(fuel_values)
36
- return [_emission(value=value, tier=TIER, term_id=TERM_ID)]
37
-
38
-
39
36
  def _should_run(cycle: dict):
40
- fuel_values = get_fuel_values(TERM_ID, cycle, LOOKUPS['fuel'])
41
- has_fuel = len(fuel_values) > 0
37
+ electricity_complete = _is_term_type_complete(cycle, 'electricityFuel')
38
+ fuel_inputs, valid_inputs = get_fuel_inputs(TERM_ID, cycle, LOOKUPS['fuel'])
42
39
 
43
40
  logRequirements(cycle, model=MODEL, term=TERM_ID,
44
- has_fuel=has_fuel)
41
+ termType_electricityFuel_complete=electricity_complete,
42
+ fuel_inputs=log_as_table(fuel_inputs))
45
43
 
46
- should_run = any([has_fuel])
44
+ should_run = any([bool(valid_inputs), electricity_complete])
47
45
  logShouldRun(cycle, MODEL, TERM_ID, should_run, methodTier=TIER)
48
- return should_run, fuel_values
46
+ return should_run, group_fuel_inputs(valid_inputs)
49
47
 
50
48
 
51
49
  def run(cycle: dict):
52
- should_run, fuel_values = _should_run(cycle)
53
- return _run(fuel_values) if should_run else []
50
+ should_run, fuel_inputs = _should_run(cycle)
51
+ return (
52
+ [
53
+ _run_inputs(inputs, tier=TIER, term_id=TERM_ID)
54
+ for inputs in fuel_inputs.values()
55
+ ] if fuel_inputs else [
56
+ _emission(value=0, tier=TIER, term_id=TERM_ID)
57
+ ]
58
+ ) if should_run else []
@@ -1,48 +1,96 @@
1
+ from functools import reduce
1
2
  from hestia_earth.schema import TermTermType, SiteSiteType
2
3
  from hestia_earth.utils.model import filter_list_term_type
3
4
  from hestia_earth.utils.lookup import extract_grouped_data
4
- from hestia_earth.utils.tools import list_sum, safe_parse_float, non_empty_list
5
+ from hestia_earth.utils.tools import list_sum, safe_parse_float
5
6
 
6
7
  from hestia_earth.models.log import logRequirements, logShouldRun
7
8
  from hestia_earth.models.utils.completeness import _is_term_type_complete
9
+ from hestia_earth.models.utils.blank_node import group_by_keys
8
10
  from hestia_earth.models.utils.term import get_lookup_value
9
11
  from hestia_earth.models.utils.cycle import get_animals_by_period
10
12
  from hestia_earth.models.utils.emission import _new_emission
11
13
  from . import MODEL
12
14
 
13
15
 
14
- def _emission(value: float, tier: str, term_id: str):
16
+ def _emission(value: float, tier: str, term_id: str, input: dict = None, operation: dict = None):
15
17
  emission = _new_emission(term_id, MODEL)
16
18
  emission['value'] = [value]
17
19
  emission['methodTier'] = tier
20
+ if input:
21
+ emission['inputs'] = [input]
22
+ if operation:
23
+ emission['operation'] = operation
18
24
  return emission
19
25
 
20
26
 
21
- def _get_fuel_input_value(term_id: str, lookup_col: str):
22
- def get_value(input: dict):
23
- input_term = input.get('term', {})
24
- input_term_id = input_term.get('@id')
25
- operation_term = input.get('operation', {})
26
- input_value = list_sum(input.get('value', []))
27
-
28
- operation_factor = extract_grouped_data(
29
- get_lookup_value(operation_term, lookup_col, model=MODEL, term=term_id), input_term_id
30
- ) if operation_term else None
31
- input_factor = operation_factor or get_lookup_value(input_term, lookup_col, model=MODEL, term=term_id)
32
- factor = safe_parse_float(input_factor, None)
33
-
34
- return input_value * factor if factor is not None else None
35
- return get_value
36
-
27
+ def _run_inputs(inputs: list, tier: str, term_id: str):
28
+ total_value = list_sum([
29
+ (i.get('input-value') or 0) * (i.get('operation-factor') or i.get('input-default-factor') or 0)
30
+ for i in inputs
31
+ ])
32
+ input_term = {
33
+ '@type': 'Term',
34
+ '@id': inputs[0].get('input-id'),
35
+ 'termType': inputs[0].get('input-termType'),
36
+ 'units': inputs[0].get('input-units'),
37
+ }
38
+ operation_term = {
39
+ '@type': 'Term',
40
+ '@id': inputs[0].get('operation-id'),
41
+ 'termType': inputs[0].get('operation-termType'),
42
+ 'units': inputs[0].get('operation-units'),
43
+ } if inputs[0].get('operation-id') else None
44
+ return _emission(
45
+ value=total_value,
46
+ tier=tier,
47
+ term_id=term_id,
48
+ input=input_term,
49
+ operation=operation_term
50
+ )
37
51
 
38
- def get_fuel_values(term_id: str, cycle: dict, lookup_col: str):
39
- inputs = filter_list_term_type(cycle.get('inputs', []), TermTermType.FUEL)
40
- values = non_empty_list(map(_get_fuel_input_value(term_id, lookup_col), inputs))
41
52
 
42
- return [0] if all([
43
- len(values) == 0,
44
- _is_term_type_complete(cycle, 'electricityFuel')
45
- ]) else values
53
+ def _fuel_input_data(term_id: str, lookup_col: str, input: dict):
54
+ input_term = input.get('term', {})
55
+ input_term_id = input_term.get('@id')
56
+ operation_term = input.get('operation', {})
57
+ input_value = list_sum(input.get('value', []), None)
58
+
59
+ operation_factor = extract_grouped_data(
60
+ data=get_lookup_value(operation_term, lookup_col, model=MODEL, term=term_id),
61
+ key=input_term_id
62
+ ) if operation_term else None
63
+ input_factor = get_lookup_value(input_term, lookup_col, model=MODEL, term=term_id)
64
+
65
+ return {
66
+ 'input-id': input_term_id,
67
+ 'input-termType': input_term.get('termType'),
68
+ 'input-units': input_term.get('units'),
69
+ 'input-value': input_value,
70
+ 'input-default-factor': safe_parse_float(input_factor, default=None),
71
+ 'operation-id': operation_term.get('@id'),
72
+ 'operation-termType': operation_term.get('termType'),
73
+ 'operation-units': operation_term.get('units'),
74
+ 'operation-factor': safe_parse_float(operation_factor, default=None)
75
+ }
76
+
77
+
78
+ def get_fuel_inputs(term_id: str, cycle: dict, lookup_col: str):
79
+ inputs = [
80
+ _fuel_input_data(term_id, lookup_col, i)
81
+ for i in filter_list_term_type(cycle.get('inputs', []), TermTermType.FUEL)
82
+ ]
83
+ valid_inputs = [
84
+ i for i in inputs if all([
85
+ i.get('input-value') is not None,
86
+ (i.get('operation-factor') or i.get('input-default-factor')) is not None
87
+ ])
88
+ ]
89
+ return inputs, valid_inputs
90
+
91
+
92
+ def group_fuel_inputs(inputs: list):
93
+ return reduce(group_by_keys(['input-id', 'operation-id']), inputs, {}) if len(inputs) > 0 else None
46
94
 
47
95
 
48
96
  def _get_emissions_factor(animal: dict, lookup_col: str) -> float:
@@ -105,9 +105,9 @@ def _run(cycle: dict, total_values: list):
105
105
  term_id = model.get('product')
106
106
  value = _run_model(model, cycle, total_value)
107
107
  debugValues(cycle, model=MODEL, term=term_id,
108
- total_value=total_value,
109
- remaining_value=remaining_value,
110
- value=value)
108
+ total_above_ground_crop_residue=total_value,
109
+ remaining_crop_residue_value=remaining_value,
110
+ allocated_value=value)
111
111
 
112
112
  if value == 0:
113
113
  values.extend([_product(term_id, value)])