hestia-earth-models 0.62.2__py3-none-any.whl → 0.62.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of hestia-earth-models might be problematic. Click here for more details.

Files changed (50) hide show
  1. hestia_earth/models/agribalyse2016/fuelElectricity.py +1 -1
  2. hestia_earth/models/cycle/animal/input/hestiaAggregatedData.py +1 -1
  3. hestia_earth/models/cycle/cycleDuration.py +7 -2
  4. hestia_earth/models/cycle/input/hestiaAggregatedData.py +1 -1
  5. hestia_earth/models/cycle/product/price.py +5 -1
  6. hestia_earth/models/cycle/product/revenue.py +6 -7
  7. hestia_earth/models/ecoinventV3/__init__.py +25 -52
  8. hestia_earth/models/ecoinventV3/utils.py +40 -0
  9. hestia_earth/models/ecoinventV3AndEmberClimate/__init__.py +92 -91
  10. hestia_earth/models/ecoinventV3AndEmberClimate/utils.py +15 -105
  11. hestia_earth/models/faostat2018/liveweightPerHead.py +1 -1
  12. hestia_earth/models/faostat2018/product/price.py +1 -2
  13. hestia_earth/models/geospatialDatabase/croppingIntensity.py +2 -1
  14. hestia_earth/models/geospatialDatabase/utils.py +1 -1
  15. hestia_earth/models/ipcc2019/aboveGroundCropResidueTotal.py +15 -10
  16. hestia_earth/models/ipcc2019/belowGroundCropResidue.py +16 -11
  17. hestia_earth/models/ipcc2019/carbonContent.py +1 -1
  18. hestia_earth/models/ipcc2019/croppingDuration.py +2 -2
  19. hestia_earth/models/ipcc2019/ligninContent.py +1 -1
  20. hestia_earth/models/ipcc2019/nitrogenContent.py +1 -1
  21. hestia_earth/models/ipcc2019/organicCarbonPerHa.py +3 -3
  22. hestia_earth/models/ipcc2019/organicCarbonPerHa_tier_1_utils.py +5 -5
  23. hestia_earth/models/ipcc2019/organicCarbonPerHa_tier_2_utils.py +17 -17
  24. hestia_earth/models/koble2014/cropResidueManagement.py +1 -1
  25. hestia_earth/models/linkedImpactAssessment/emissions.py +15 -14
  26. hestia_earth/models/log.py +1 -1
  27. hestia_earth/models/mocking/search-results.json +8 -16
  28. hestia_earth/models/pooreNemecek2018/longFallowPeriod.py +1 -1
  29. hestia_earth/models/preload_requests.py +1 -1
  30. hestia_earth/models/requirements.py +6 -6
  31. hestia_earth/models/site/management.py +38 -35
  32. hestia_earth/models/site/organicCarbonPerHa.py +1 -1
  33. hestia_earth/models/utils/__init__.py +1 -1
  34. hestia_earth/models/utils/blank_node.py +14 -9
  35. hestia_earth/models/utils/cycle.py +12 -12
  36. hestia_earth/models/utils/measurement.py +3 -3
  37. hestia_earth/models/utils/site.py +1 -1
  38. hestia_earth/models/utils/term.py +2 -1
  39. hestia_earth/models/version.py +1 -1
  40. {hestia_earth_models-0.62.2.dist-info → hestia_earth_models-0.62.4.dist-info}/METADATA +12 -12
  41. {hestia_earth_models-0.62.2.dist-info → hestia_earth_models-0.62.4.dist-info}/RECORD +50 -49
  42. {hestia_earth_models-0.62.2.dist-info → hestia_earth_models-0.62.4.dist-info}/WHEEL +1 -1
  43. tests/models/cycle/product/test_revenue.py +0 -3
  44. tests/models/cycle/test_cycleDuration.py +1 -1
  45. tests/models/faostat2018/test_liveweightPerHead.py +9 -0
  46. tests/models/site/test_management.py +31 -12
  47. tests/models/test_ecoinventV3.py +12 -0
  48. tests/models/test_ecoinventV3AndEmberClimate.py +5 -72
  49. {hestia_earth_models-0.62.2.dist-info → hestia_earth_models-0.62.4.dist-info}/LICENSE +0 -0
  50. {hestia_earth_models-0.62.2.dist-info → hestia_earth_models-0.62.4.dist-info}/top_level.txt +0 -0
@@ -93,7 +93,7 @@ def _should_run_operation(cycle: dict):
93
93
 
94
94
  logRequirements(cycle, model=MODEL, term=term_id, model_key=MODEL_KEY,
95
95
  has_value=has_value,
96
- has_lookup_value=has_lookup_value)
96
+ has_fuelUse_lookup_value=has_lookup_value)
97
97
 
98
98
  should_run = all([has_value, has_lookup_value])
99
99
  logShouldRun(cycle, MODEL, term_id, should_run, model_key=MODEL_KEY)
@@ -1,5 +1,5 @@
1
1
  """
2
- Animal Input Hestia Aggregated Data
2
+ Animal Input HESTIA Aggregated Data
3
3
 
4
4
  This model adds `impactAssessment` to Animal inputs based on data which has been aggregated into country level averages.
5
5
  Note: to get more accurate impacts, we recommend setting the
@@ -10,7 +10,7 @@ from hestia_earth.schema import TermTermType
10
10
  from hestia_earth.utils.model import find_term_match, find_primary_product
11
11
  from hestia_earth.utils.date import diff_in_days
12
12
 
13
- from hestia_earth.models.log import logRequirements, logShouldRun
13
+ from hestia_earth.models.log import logRequirements, logShouldRun, debugValues
14
14
  from hestia_earth.models.utils.crop import get_crop_grouping_fao
15
15
  from . import MODEL
16
16
 
@@ -66,7 +66,12 @@ def _run_by_crop(cycle: dict):
66
66
  grouping = get_crop_grouping_fao(MODEL, MODEL_KEY, product.get('term', {}))
67
67
  is_permanent_crop = grouping == 'Permanent crops'
68
68
  croppingIntensity = find_term_match(cycle.get('practices', []), 'croppingIntensity').get('value', [1])[0]
69
- return DEFAULT_DURATION * (1 if is_permanent_crop else croppingIntensity)
69
+ ratio = 1 if is_permanent_crop else croppingIntensity
70
+ debugValues(cycle, model=MODEL, key=MODEL_KEY, by='product',
71
+ crop_grouping=grouping,
72
+ croppingIntensity=croppingIntensity,
73
+ ratio=ratio)
74
+ return round(DEFAULT_DURATION * ratio, 3)
70
75
 
71
76
 
72
77
  def _should_run_by_crop(cycle: dict):
@@ -1,5 +1,5 @@
1
1
  """
2
- Input Hestia Aggregated Data
2
+ Input HESTIA Aggregated Data
3
3
 
4
4
  This model adds `impactAssessment` to inputs based on data which has been aggregated into country level averages.
5
5
  Note: to get more accurate impacts, we recommend setting the
@@ -42,9 +42,13 @@ def _should_run_product_by_share_0(cycle: dict, product: dict):
42
42
  return should_run
43
43
 
44
44
 
45
+ def _should_run_product(product: dict): return product.get(MODEL_KEY) is None
46
+
47
+
45
48
  def run(cycle: dict):
49
+ products = list(filter(_should_run_product, cycle.get('products', [])))
46
50
  return non_empty_list([
47
51
  (
48
52
  _product(p, 0) if _should_run_product_by_share_0(cycle, p) else None
49
- ) for p in cycle.get('products', [])
53
+ ) for p in products
50
54
  ])
@@ -53,23 +53,22 @@ def _should_run(cycle: dict):
53
53
  has_yield_and_price = has_yield and has_price
54
54
  is_yield_0 = list_sum(product.get('value', []), -1) == 0
55
55
  is_price_0 = product.get('price', -1) == 0
56
- not_already_set = MODEL_KEY not in product.keys()
57
56
 
58
57
  logRequirements(cycle, model=MODEL, term=term_id, key=MODEL_KEY,
59
- not_already_set=not_already_set,
60
58
  has_yield_and_price=has_yield_and_price,
61
59
  is_yield_0=is_yield_0,
62
60
  is_price_0=is_price_0)
63
61
 
64
- should_run = all([
65
- not_already_set,
66
- any([has_yield_and_price, is_yield_0, is_price_0])
67
- ])
62
+ should_run = any([has_yield_and_price, is_yield_0, is_price_0])
68
63
  logShouldRun(cycle, MODEL, term_id, should_run, key=MODEL_KEY)
69
64
  return should_run
70
65
  return should_run_product
71
66
 
72
67
 
68
+ def _should_run_product(product: dict): return product.get(MODEL_KEY) is None
69
+
70
+
73
71
  def run(cycle: dict):
74
- products = list(filter(_should_run(cycle), cycle.get('products', [])))
72
+ products = list(filter(_should_run_product, cycle.get('products', [])))
73
+ products = list(filter(_should_run(cycle), products))
75
74
  return non_empty_list(map(_run(cycle), products))
@@ -4,7 +4,7 @@ ecoinvent v3
4
4
  This model calculates background emissions related to the production of Inputs from the ecoinvent database, version 3.
5
5
 
6
6
  Note: to use the `ecoinventV3` model locally or in the
7
- [Hestia Community Edition](https://gitlab.com/hestia-earth/hestia-community-edition) you need a valid ecoinvent license.
7
+ [HESTIA Community Edition](https://gitlab.com/hestia-earth/hestia-community-edition) you need a valid ecoinvent license.
8
8
  Please contact us at community@hestia.earth for instructions to download the required file to run the model.
9
9
 
10
10
  **Pesticide Brand Name**
@@ -15,16 +15,15 @@ default list of [Pesticide Active Ingredient](https://hestia.earth/glossary?pest
15
15
  """
16
16
  from functools import reduce
17
17
  from hestia_earth.schema import EmissionMethodTier
18
- from hestia_earth.utils.model import find_term_match
19
- from hestia_earth.utils.tools import flatten, list_sum, non_empty_list
18
+ from hestia_earth.utils.tools import flatten, list_sum
20
19
 
21
- from hestia_earth.models.log import debugValues, logRequirements, logShouldRun
20
+ from hestia_earth.models.log import debugValues, logShouldRun, logRequirements
22
21
  from hestia_earth.models.data.ecoinventV3 import ecoinventV3_emissions
23
- from hestia_earth.models.utils.term import get_lookup_value
24
22
  from hestia_earth.models.utils.emission import _new_emission
25
23
  from hestia_earth.models.utils.blank_node import group_by_keys
26
24
  from hestia_earth.models.utils.pesticideAI import get_pesticides_from_inputs
27
25
  from hestia_earth.models.utils.fertiliser import get_fertilisers_from_inputs
26
+ from .utils import get_background_inputs, get_input_mappings
28
27
 
29
28
  REQUIREMENTS = {
30
29
  "Cycle": {
@@ -96,69 +95,43 @@ def _add_emission(cycle: dict, input: dict):
96
95
  def add(prev: dict, mapping: tuple):
97
96
  ecoinventName, coefficient = mapping
98
97
  emissions = ecoinventV3_emissions(ecoinventName)
99
- for id, value in emissions:
98
+ for emission_term_id, value in emissions:
100
99
  # log run on each emission so we know it did run
101
- debugValues(cycle, model=MODEL, term=id,
100
+ debugValues(cycle, model=MODEL, term=emission_term_id,
102
101
  value=value,
103
102
  coefficient=coefficient,
104
103
  input=input_term_id,
105
104
  operation=operation_term_id,
106
105
  animal=animal_term_id)
107
- prev[id] = prev.get(id, 0) + (value * coefficient)
106
+ prev[emission_term_id] = prev.get(emission_term_id, 0) + (value * coefficient)
108
107
  return prev
109
108
  return add
110
109
 
111
110
 
112
- def _get_input_mappings(cycle: dict, input: dict):
113
- term = input.get('term', {})
114
- term_id = term.get('@id')
115
- value = get_lookup_value(term, 'ecoinventMapping', model=MODEL, term=term_id)
116
- mappings = non_empty_list(value.split(';')) if value else []
117
- logRequirements(cycle, model=MODEL, term=term_id,
118
- mappings=';'.join(mappings))
119
- return [(m.split(':')[0], float(m.split(':')[1])) for m in mappings]
120
-
121
-
122
111
  def _run_input(cycle: dict):
123
112
  def run(inputs: list):
124
113
  input = inputs[0]
114
+ input_term_id = input.get('term', {}).get('@id')
125
115
  input_value = list_sum(flatten(input.get('value', []) for input in inputs))
126
- mappings = _get_input_mappings(cycle, input)
127
- term_id = input.get('term', {}).get('@id')
128
- should_run = len(mappings) > 0
129
- logShouldRun(cycle, MODEL, term_id, should_run, methodTier=TIER)
130
- grouped_emissions = reduce(_add_emission(cycle, input), mappings, {}) if should_run else {}
131
- return [_emission(term_id, value * input_value, input) for term_id, value in grouped_emissions.items()]
132
- return run
116
+ mappings = get_input_mappings(MODEL, cycle, input)
117
+ has_mappings = len(mappings) > 0
133
118
 
119
+ logRequirements(cycle, model=MODEL, term=input_term_id,
120
+ has_ecoinvent_mappings=has_mappings,
121
+ ecoinvent_mappings=';'.join([v[0] for v in mappings]))
134
122
 
135
- def _should_run_input(products: list):
136
- def should_run(input: dict):
137
- return all([
138
- list_sum(input.get('value', [])) > 0,
139
- # make sure Input is not a Product as well or we might double-count emissions
140
- find_term_match(products, input.get('term', {}).get('@id'), None) is None,
141
- # ignore inputs which are flagged as Product of the Cycle
142
- not input.get('fromCycle', False),
143
- not input.get('producedInCycle', False)
144
- ])
145
- return should_run
146
-
147
-
148
- def _animal_inputs(animal: dict):
149
- inputs = animal.get('inputs', [])
150
- return [(input | {'animal': animal.get('term', {})}) for input in inputs]
123
+ should_run = all([has_mappings])
124
+ logShouldRun(cycle, MODEL, input_term_id, should_run, methodTier=TIER)
125
+ grouped_emissions = reduce(_add_emission(cycle, input), mappings, {}) if should_run else {}
126
+ return [
127
+ _emission(term_id, value * input_value, input)
128
+ for term_id, value in grouped_emissions.items()
129
+ ]
130
+ return run
151
131
 
152
132
 
153
133
  def run(_, cycle: dict):
154
- # add all the properties of some Term that inlcude others with the mapping
155
- inputs = flatten(
156
- cycle.get('inputs', []) +
157
- list(map(_animal_inputs, cycle.get('animals', []))) +
158
- get_pesticides_from_inputs(cycle) +
159
- get_fertilisers_from_inputs(cycle)
160
- )
161
- inputs = list(filter(_should_run_input(cycle.get('products', [])), inputs))
162
- # group inputs with same id/operation to avoid adding emissions twice
163
- inputs = reduce(group_by_keys(['term', 'operation', 'animal']), inputs, {})
164
- return flatten(map(_run_input(cycle), inputs.values()))
134
+ extra_inputs = get_pesticides_from_inputs(cycle) + get_fertilisers_from_inputs(cycle)
135
+ inputs = get_background_inputs(cycle, extra_inputs=extra_inputs)
136
+ grouped_inputs = reduce(group_by_keys(['term', 'operation', 'animal']), inputs, {})
137
+ return flatten(map(_run_input(cycle), grouped_inputs.values()))
@@ -0,0 +1,40 @@
1
+ from hestia_earth.utils.model import find_term_match
2
+ from hestia_earth.utils.tools import list_sum, flatten, non_empty_list
3
+
4
+ from hestia_earth.models.utils.term import get_lookup_value
5
+
6
+
7
+ def _animal_inputs(animal: dict):
8
+ inputs = animal.get('inputs', [])
9
+ return [(input | {'animal': animal.get('term', {})}) for input in inputs]
10
+
11
+
12
+ def _should_run_input(products: list):
13
+ def should_run(input: dict):
14
+ return all([
15
+ list_sum(input.get('value', [])) > 0,
16
+ # make sure Input is not a Product as well or we might double-count emissions
17
+ find_term_match(products, input.get('term', {}).get('@id'), None) is None,
18
+ # ignore inputs which are flagged as Product of the Cycle
19
+ not input.get('fromCycle', False),
20
+ not input.get('producedInCycle', False)
21
+ ])
22
+ return should_run
23
+
24
+
25
+ def get_background_inputs(cycle: dict, extra_inputs: list = []):
26
+ # add all the properties of some Term that inlcude others with the mapping
27
+ inputs = flatten(
28
+ cycle.get('inputs', []) +
29
+ list(map(_animal_inputs, cycle.get('animals', []))) +
30
+ extra_inputs
31
+ )
32
+ return list(filter(_should_run_input(cycle.get('products', [])), inputs))
33
+
34
+
35
+ def get_input_mappings(model: str, cycle: dict, input: dict):
36
+ term = input.get('term', {})
37
+ term_id = term.get('@id')
38
+ value = get_lookup_value(term, 'ecoinventMapping', model=model, term=term_id)
39
+ mappings = non_empty_list(value.split(';')) if value else []
40
+ return [(m.split(':')[0], float(m.split(':')[1])) for m in mappings]
@@ -1,25 +1,30 @@
1
1
  """
2
2
  ecoinvent v3 and Ember Climate
3
3
 
4
- All emissions to air for the cycle.
4
+ This model calculates background emissions related to the production of Inputs from the ecoinvent database, version 3,
5
+ and using the percentage of production for each energy source from
6
+ [Ember](https://ember-climate.org/data-catalogue/yearly-electricity-data/).
7
+
8
+ Note: to use the `ecoinventV3AndEmberClimate` model locally or in the
9
+ [Hestia Community Edition](https://gitlab.com/hestia-earth/hestia-community-edition) you need a valid ecoinvent license.
10
+ Please contact us at community@hestia.earth for instructions to download the required file to run the model.
5
11
  """
6
12
  from functools import reduce
7
- from typing import Tuple
8
-
9
13
  from hestia_earth.utils.tools import list_sum, flatten
10
- from hestia_earth.utils.model import linked_node
11
14
  from hestia_earth.schema import EmissionMethodTier
12
15
 
13
- from hestia_earth.models.log import logShouldRun, logRequirements, log_blank_nodes_id
16
+ from hestia_earth.models.log import logShouldRun, logRequirements, debugValues
14
17
  from hestia_earth.models.utils.emission import _new_emission
15
18
  from hestia_earth.models.utils.blank_node import group_by_keys
16
19
  from hestia_earth.models.utils.completeness import _is_term_type_complete
17
20
  from hestia_earth.models.utils.term import get_electricity_grid_mix_terms
18
- from hestia_earth.models.utils.cycle import cycle_end_year
19
- from .utils import get_emission, get_all_emission_terms
21
+ from hestia_earth.models.data.ecoinventV3 import ecoinventV3_emissions
22
+ from .utils import get_input_coefficient
23
+ from ..ecoinventV3.utils import get_background_inputs, get_input_mappings
20
24
 
21
25
  REQUIREMENTS = {
22
26
  "Cycle": {
27
+ "completeness.electricityFuel": "True",
23
28
  "site": {
24
29
  "@type": "Site",
25
30
  "country": {"@type": "Term", "termType": "region"}
@@ -27,9 +32,26 @@ REQUIREMENTS = {
27
32
  "inputs": [{
28
33
  "@type": "Input",
29
34
  "term.@id": ["electricityGridMarketMix", "electricityGridRenewableMix"],
30
- "value": ""
35
+ "value": "> 0",
36
+ "none": {
37
+ "fromCycle": "True",
38
+ "producedInCycle": "True"
39
+ }
31
40
  }],
32
- "completeness.electricityFuel": "True"
41
+ "optional": {
42
+ "animals": [{
43
+ "@type": "Animal",
44
+ "inputs": [{
45
+ "@type": "Input",
46
+ "term.@id": ["electricityGridMarketMix", "electricityGridRenewableMix"],
47
+ "value": "> 0",
48
+ "none": {
49
+ "fromCycle": "True",
50
+ "producedInCycle": "True"
51
+ }
52
+ }]
53
+ }]
54
+ }
33
55
  }
34
56
  }
35
57
  RETURNS = {
@@ -41,6 +63,7 @@ RETURNS = {
41
63
  }]
42
64
  }
43
65
  LOOKUPS = {
66
+ "electricity": "ecoinventMapping",
44
67
  "region-ember-energySources": "using `country`",
45
68
  "ember-ecoinvent-mapping": ["ember", "ecoinventId", "ecoinventName"]
46
69
  }
@@ -50,94 +73,72 @@ MODEL_KEY = 'impactAssessment' # keep to generate entry in "model-links.json"
50
73
  TIER = EmissionMethodTier.BACKGROUND.value
51
74
 
52
75
 
53
- def _emission(value: float, term_id: str, inputs: list, operation: dict) -> dict:
76
+ def _emission(term_id: str, value: float, input: dict):
54
77
  emission = _new_emission(term_id, MODEL)
55
78
  emission['value'] = [value]
56
79
  emission['methodTier'] = TIER
57
- emission['inputs'] = list(map(linked_node, inputs))
58
- if operation:
59
- emission["operation"] = operation
80
+ emission['inputs'] = [input.get('term')]
81
+ if input.get('operation'):
82
+ emission['operation'] = input.get('operation')
83
+ if input.get('animal'):
84
+ emission['animals'] = [input.get('animal')]
60
85
  return emission
61
86
 
62
87
 
63
- def _grid_inputs(inputs: list, electricity_grid_terms: list):
64
- electricity_grid_term_ids = [v.get('@id') for v in electricity_grid_terms]
65
- return [
66
- i for i in inputs if i.get("term", {}).get("@id") in electricity_grid_term_ids
67
- ]
68
-
69
-
70
- def _run_input(cycle: dict, inputs: list, emission_term_id: str, electricity_grid_term: dict):
71
- electricity_grid_inputs = [electricity_grid_term]
72
- inputs = _grid_inputs(inputs, electricity_grid_inputs)
73
-
74
- def run(grouped_inputs: list):
75
- input = grouped_inputs[0]
76
- term_id = input.get('term', {}).get('@id')
77
- input_value = list_sum(flatten(input.get('value', []) for input in grouped_inputs))
78
- country_id = cycle.get("site", {}).get("country", {}).get("@id")
79
- year = cycle_end_year(cycle)
80
- value = get_emission(
81
- term_id=emission_term_id,
82
- country=country_id,
83
- energy=input_value,
84
- year=year,
85
- model=MODEL
86
- )
87
-
88
- logRequirements(cycle, model=MODEL, term=term_id,
89
- input_value=input_value,
90
- emission_value=value)
91
-
92
- should_run = all([input_value > 0, value])
93
- logShouldRun(cycle, MODEL, term_id, should_run, methodTier=TIER)
94
-
95
- return _emission(value, emission_term_id, electricity_grid_inputs, input.get("operation"))
96
-
97
- return list(map(run, _group_by_operation(inputs).values() if inputs else []))
98
-
99
-
100
- def _group_by_operation(inputs: list) -> dict:
101
- return reduce(group_by_keys(['operation']), inputs, {})
102
-
103
-
104
- def _run_emission(cycle: dict, electricity_grid_terms: list, inputs: list, emission_term_id: str) -> list:
105
- return flatten([
106
- _run_input(
107
- cycle=cycle,
108
- inputs=inputs,
109
- emission_term_id=emission_term_id,
110
- electricity_grid_term=electricity_grid_term
111
- ) for electricity_grid_term in electricity_grid_terms
112
- ])
113
-
114
-
115
- def _should_run_emission(cycle: dict, electricity_grid_terms: list, term_id: str) -> Tuple[bool, list]:
116
- term_type_complete = _is_term_type_complete(cycle, 'electricityFuel')
117
- inputs = _grid_inputs(cycle.get('inputs', []), electricity_grid_terms)
118
- has_relevant_inputs = bool(inputs)
119
- has_country = bool(cycle.get("site", {}).get("country", {}))
120
- has_end_date = bool(cycle.get("endDate"))
121
-
122
- logRequirements(cycle, model=MODEL, term=term_id,
123
- term_type_electricityFuel_complete=term_type_complete,
124
- input_ids=log_blank_nodes_id(inputs),
125
- has_relevant_inputs=has_relevant_inputs,
126
- has_country=has_country,
127
- has_end_date=has_end_date)
128
-
129
- should_run = all([term_type_complete, has_relevant_inputs, has_country, has_end_date])
130
- logShouldRun(cycle, MODEL, term_id, should_run, methodTier=TIER)
131
- return should_run, inputs
132
-
133
-
134
- def _run_emissions(cycle: dict, electricity_grid_terms: list):
135
- def run_emissions_for_term(term_id: str) -> list:
136
- should_run, inputs = _should_run_emission(cycle, electricity_grid_terms, term_id)
137
- return _run_emission(cycle, electricity_grid_terms, inputs, term_id) if should_run else []
138
- return run_emissions_for_term
88
+ def _add_emission(cycle: dict, input: dict):
89
+ input_term_id = input.get('term', {}).get('@id')
90
+ operation_term_id = input.get('operation', {}).get('@id')
91
+ animal_term_id = input.get('animal', {}).get('@id')
92
+ # TODO: as inputs are grouped, there could be more than 1 country
93
+ country_id = input.get('country', cycle.get('site', {}).get('country', {})).get('@id')
94
+
95
+ def add(prev: dict, mapping: tuple):
96
+ ecoinventName, _coefficient = mapping
97
+ # recalculate the coefficient using the country and year if it should be included
98
+ coefficient = get_input_coefficient(MODEL, cycle, country_id, ecoinventName) if _coefficient > 0 else 0
99
+ emissions = ecoinventV3_emissions(ecoinventName)
100
+ for emission_term_id, value in emissions:
101
+ # log run on each emission so we know it did run
102
+ debugValues(cycle, model=MODEL, term=emission_term_id,
103
+ value=value,
104
+ coefficient=coefficient,
105
+ input=input_term_id,
106
+ operation=operation_term_id,
107
+ animal=animal_term_id)
108
+ prev[emission_term_id] = prev.get(emission_term_id, 0) + (value * coefficient)
109
+ return prev
110
+ return add
111
+
112
+
113
+ def _run_input(cycle: dict):
114
+ electricity_complete = _is_term_type_complete(cycle, 'electricityFuel')
115
+
116
+ def run(inputs: list):
117
+ input = inputs[0]
118
+ input_value = list_sum(flatten(input.get('value', []) for input in inputs))
119
+ input_term_id = input.get('term', {}).get('@id')
120
+ mappings = get_input_mappings(MODEL, cycle, input)
121
+ has_mappings = len(mappings) > 0
122
+
123
+ logRequirements(cycle, model=MODEL, term=input_term_id,
124
+ has_ecoinvent_mappings=has_mappings,
125
+ ecoinvent_mappings=';'.join([v[0] for v in mappings]))
126
+
127
+ should_run = all([electricity_complete, has_mappings])
128
+ logShouldRun(cycle, MODEL, input_term_id, should_run, methodTier=TIER)
129
+
130
+ grouped_emissions = reduce(_add_emission(cycle, input), mappings, {}) if should_run else {}
131
+ return [
132
+ _emission(term_id, value * input_value, input)
133
+ for term_id, value in grouped_emissions.items()
134
+ ]
135
+ return run
139
136
 
140
137
 
141
138
  def run(_, cycle: dict):
142
- electricity_grid_terms = get_electricity_grid_mix_terms()
143
- return flatten(list(map(_run_emissions(cycle, electricity_grid_terms), get_all_emission_terms())))
139
+ terms = get_electricity_grid_mix_terms()
140
+ inputs = get_background_inputs(cycle)
141
+ # only keep the inputs matching the grid terms
142
+ inputs = [i for i in inputs if i.get('term', {}).get('@id') in terms]
143
+ grouped_inputs = reduce(group_by_keys(['term', 'operation', 'animal']), inputs, {})
144
+ return flatten(map(_run_input(cycle), grouped_inputs.values()))
@@ -1,116 +1,26 @@
1
- from typing import Dict, Any, Union, List
2
-
3
1
  from hestia_earth.utils.lookup import download_lookup, get_table_value, column_name, extract_grouped_data
4
- from hestia_earth.utils.tools import safe_parse_float, non_empty_list
2
+ from hestia_earth.utils.tools import safe_parse_float
5
3
 
6
4
  from hestia_earth.models.log import debugMissingLookup
7
- from hestia_earth.models.data.ecoinventV3 import ecoinventV3_emissions
8
-
5
+ from hestia_earth.models.utils.cycle import cycle_end_year
9
6
 
10
7
  EMBER_ECOINVENT_LOOKUP_NAME = "ember-ecoinvent-mapping.csv"
8
+ REGION_EMBER_SOURCES_LOOKUP_NAME = "region-ember-energySources.csv"
11
9
 
12
10
 
13
- def _lookup_data(term_id: str, grouping: str, country_id: str, year: str, lookup_name: str, **log_args):
14
- lookup = download_lookup(lookup_name)
15
- column = column_name(grouping)
16
- data = get_table_value(lookup, 'termid', country_id, column)
17
- percentage = extract_grouped_data(data, year)
18
- debugMissingLookup(lookup_name, 'termid', country_id, column, percentage, year=year, term=term_id, **log_args)
19
- return safe_parse_float(percentage, None)
20
-
21
-
22
- def _convert_name(name: str) -> str: return name.replace(";", ",")
23
-
24
-
25
- def _zero_from_non_numeric(value: Any) -> float:
26
- try:
27
- return float(value)
28
- except (ValueError, TypeError):
29
- return 0.0
30
-
31
-
32
- def _extract_emission_value(value_iter: Any) -> Union[float, None]:
33
- value_list = list(value_iter)
34
- try:
35
- if len(list(value_list)) > 0 and len(list(value_list)[0]) > 1:
36
- return safe_parse_float(list(value_list)[0][1], None)
37
- except ValueError:
38
- return None
39
-
40
-
41
- def _get_emission_rate_per_source(ember_ecoinvent_mapping: Dict, emission_term_id: str) -> Dict:
42
- """
43
- Returns the emissions rate in kg/kWh as a mapping indexed by ember energy source name.
44
- eg: {"bioenergy": 0.8947372128046288, "coal": 0.000124581084822, ... }
45
- """
11
+ def get_input_coefficient(model: str, cycle: dict, country_id: str, ecoinventName: str):
12
+ year = cycle_end_year(cycle)
46
13
 
47
- return {
48
- ember_source: _extract_emission_value(filter(
49
- lambda x: x[0] == emission_term_id,
50
- ecoinventV3_emissions(ecoinvent_lookup["ecoinventname"])
51
- ))
52
- for ember_source, ecoinvent_lookup in ember_ecoinvent_mapping.items()
53
- }
54
-
55
-
56
- def get_emission(term_id: str, country: str, year: int, energy: float, model: str) -> float:
57
- """
58
- Get the <term_id> emissions in kg for the energy consumed.
59
- a: ecoInventId of each source - from "ember-ecoinvent-mapping.csv"
60
- b: Ember sources list - from b
61
- c: Percentages per source - from region-ember-energySources.csv (country, ember-source, year:value)
62
- d: Energy per source (kWh) - from energy * each of c
63
- e: Emissions/kWh per source - from ecoinvent (using id from b)
64
- f: Emissions per source (kg) - from e * d for each source
65
- g: Total emissions - from sum(f)
66
- """
67
- # a: ecoInventId of each source
14
+ # find the matching ember source with the ecoinventName.
15
+ # example: "electricity, high voltage, electricity production, hard coal" > "Coal"
68
16
  ember_ecoinvent_lookup = download_lookup(EMBER_ECOINVENT_LOOKUP_NAME)
69
- ember_ecoinvent_mapping = {
70
- row["ember"].lower(): {"ecoinventid": row["ecoinventid"], "ecoinventname": _convert_name(row["ecoinventname"])}
71
- for row in ember_ecoinvent_lookup
72
- }
73
-
74
- # b: Ember sources
75
- ember_sources = list(ember_ecoinvent_mapping.keys())
76
-
77
- # c: Percentages per source
78
- percentages_per_source = {
79
- source: _lookup_data(
80
- term_id=source,
81
- grouping=source,
82
- country_id=country,
83
- year=str(year),
84
- lookup_name="region-ember-energySources.csv",
85
- model=model
86
- )
87
- for source in ember_sources
88
- }
89
-
90
- # d: Energy per source (kWh)
91
- energy_per_source = {
92
- source: energy * _zero_from_non_numeric(percentage) / 100
93
- for source, percentage in percentages_per_source.items()
94
- }
17
+ source_name = get_table_value(ember_ecoinvent_lookup, column_name('ecoinventName'), ecoinventName, 'ember')
95
18
 
96
- # e: Emissions/kWh per source
97
- emission_rate_per_source = _get_emission_rate_per_source(
98
- ember_ecoinvent_mapping=ember_ecoinvent_mapping,
99
- emission_term_id=term_id
100
- )
19
+ # find the ratio for the country / year
20
+ region_ember_sources_lookup = download_lookup(REGION_EMBER_SOURCES_LOOKUP_NAME)
21
+ data = get_table_value(region_ember_sources_lookup, 'termid', country_id, column_name(source_name))
22
+ percentage = extract_grouped_data(data, str(year))
23
+ debugMissingLookup(REGION_EMBER_SOURCES_LOOKUP_NAME,
24
+ 'termid', country_id, source_name, percentage, year=year, model=model)
101
25
 
102
- # f: Emissions per source (kg)
103
- emissions_per_source = {
104
- source: energy_per_source[source] * emission_rate_per_source[source]
105
- for source in ember_sources
106
- if energy_per_source[source] is not None and emission_rate_per_source[source] is not None
107
- }
108
-
109
- # g: Total emissions (kg of <term_id>)
110
- return sum(emissions_per_source.values())
111
-
112
-
113
- def get_all_emission_terms() -> List[str]:
114
- ember_ecoinvent_lookup = download_lookup(EMBER_ECOINVENT_LOOKUP_NAME)
115
- eco_invent_name = _convert_name(ember_ecoinvent_lookup[0]["ecoinventname"])
116
- return [e[0] for e in non_empty_list(ecoinventV3_emissions(eco_invent_name))]
26
+ return safe_parse_float(percentage, 0) / 100
@@ -77,7 +77,7 @@ def _run_liveAnimal(cycle: dict, product: dict, year: int, country_id: str):
77
77
  product_id = product.get('term', {}).get('@id')
78
78
  animal_product_id = get_liveAnimal_to_animalProduct_id(product_id, LOOKUPS['liveAnimal'][0], term=TERM_ID)
79
79
 
80
- animal_product_term = download_hestia(animal_product_id)
80
+ animal_product_term = download_hestia(animal_product_id) if animal_product_id else {}
81
81
  kg_liveweight, groupingFAO = _product_value({**product, 'term': animal_product_term}, year, country_id)
82
82
 
83
83
  logRequirements(cycle, model=MODEL, term=product_id, property=TERM_ID,
@@ -157,8 +157,7 @@ def _run_by_country(cycle: dict, product: dict, country_id: str, year: int = Non
157
157
  return _product(product, value / 1000 * conversion_to_number) if value is not None else None
158
158
 
159
159
 
160
- def _should_run_product(product: dict):
161
- return product.get(MODEL_KEY) is None
160
+ def _should_run_product(product: dict): return product.get(MODEL_KEY) is None
162
161
 
163
162
 
164
163
  def run(cycle: dict):
@@ -37,7 +37,8 @@ TERM_ID = 'croppingIntensity'
37
37
 
38
38
  def _practice(value: float):
39
39
  practice = _new_practice(TERM_ID)
40
- practice['value'] = [round(value, 7)]
40
+ # force conversion to float from numpy, avoiding errors when reading it in other models
41
+ practice['value'] = [float(round(value, 7))]
41
42
  return practice
42
43
 
43
44