hestia-earth-models 0.60.1__py3-none-any.whl → 0.61.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 (54) hide show
  1. hestia_earth/models/cycle/cycleDuration.py +61 -22
  2. hestia_earth/models/cycle/startDate.py +16 -11
  3. hestia_earth/models/cycle/startDateDefinition.py +69 -0
  4. hestia_earth/models/impact_assessment/emissions.py +1 -3
  5. hestia_earth/models/ipcc2019/pastureGrass.py +1 -1
  6. hestia_earth/models/linkedImpactAssessment/__init__.py +8 -162
  7. hestia_earth/models/linkedImpactAssessment/emissions.py +168 -0
  8. hestia_earth/models/{impact_assessment → linkedImpactAssessment}/utils.py +1 -1
  9. hestia_earth/models/mocking/search-results.json +257 -229
  10. hestia_earth/models/{impact_assessment → pooreNemecek2018}/freshwaterWithdrawalsDuringCycle.py +1 -1
  11. hestia_earth/models/site/brackishWater.py +43 -0
  12. hestia_earth/models/site/freshWater.py +43 -0
  13. hestia_earth/models/site/salineWater.py +43 -0
  14. hestia_earth/models/utils/__init__.py +3 -3
  15. hestia_earth/models/version.py +1 -1
  16. {hestia_earth_models-0.60.1.dist-info → hestia_earth_models-0.61.0.dist-info}/METADATA +2 -2
  17. {hestia_earth_models-0.60.1.dist-info → hestia_earth_models-0.61.0.dist-info}/RECORD +49 -41
  18. tests/models/cycle/test_cycleDuration.py +15 -27
  19. tests/models/cycle/test_startDate.py +7 -7
  20. tests/models/cycle/test_startDateDefinition.py +31 -0
  21. tests/models/linkedImpactAssessment/__init__.py +0 -0
  22. tests/models/linkedImpactAssessment/test_emissions.py +22 -0
  23. tests/models/{impact_assessment → linkedImpactAssessment}/test_freshwaterWithdrawalsInputsProduction.py +4 -4
  24. tests/models/{impact_assessment → linkedImpactAssessment}/test_landOccupationInputsProduction.py +4 -4
  25. tests/models/{impact_assessment → linkedImpactAssessment}/test_landTransformationFromCropland100YearAverageInputsProduction.py +4 -4
  26. tests/models/{impact_assessment → linkedImpactAssessment}/test_landTransformationFromCropland20YearAverageInputsProduction.py +4 -4
  27. tests/models/{impact_assessment → linkedImpactAssessment}/test_landTransformationFromForest100YearAverageInputsProduction.py +4 -4
  28. tests/models/{impact_assessment → linkedImpactAssessment}/test_landTransformationFromForest20YearAverageInputsProduction.py +4 -4
  29. tests/models/{impact_assessment → linkedImpactAssessment}/test_landTransformationFromOtherNaturalVegetation100YearAverageInputsProduction.py +4 -4
  30. tests/models/{impact_assessment → linkedImpactAssessment}/test_landTransformationFromOtherNaturalVegetation20YearAverageInputsProduction.py +4 -4
  31. tests/models/linkedImpactAssessment/test_landTransformationFromPermanentPasture100YearAverageInputsProduction.py +24 -0
  32. tests/models/linkedImpactAssessment/test_landTransformationFromPermanentPasture20YearAverageInputsProduction.py +24 -0
  33. tests/models/{impact_assessment → pooreNemecek2018}/test_freshwaterWithdrawalsDuringCycle.py +1 -1
  34. tests/models/site/test_brackishWater.py +32 -0
  35. tests/models/site/test_freshWater.py +32 -0
  36. tests/models/site/test_salineWater.py +32 -0
  37. hestia_earth/models/cycle/endDate.py +0 -50
  38. tests/models/cycle/test_endDate.py +0 -24
  39. tests/models/impact_assessment/test_landTransformationFromPermanentPasture100YearAverageInputsProduction.py +0 -23
  40. tests/models/impact_assessment/test_landTransformationFromPermanentPasture20YearAverageInputsProduction.py +0 -23
  41. tests/models/test_linkedImpactAssessment.py +0 -33
  42. /hestia_earth/models/{impact_assessment → linkedImpactAssessment}/freshwaterWithdrawalsInputsProduction.py +0 -0
  43. /hestia_earth/models/{impact_assessment → linkedImpactAssessment}/landOccupationInputsProduction.py +0 -0
  44. /hestia_earth/models/{impact_assessment → linkedImpactAssessment}/landTransformationFromCropland100YearAverageInputsProduction.py +0 -0
  45. /hestia_earth/models/{impact_assessment → linkedImpactAssessment}/landTransformationFromCropland20YearAverageInputsProduction.py +0 -0
  46. /hestia_earth/models/{impact_assessment → linkedImpactAssessment}/landTransformationFromForest100YearAverageInputsProduction.py +0 -0
  47. /hestia_earth/models/{impact_assessment → linkedImpactAssessment}/landTransformationFromForest20YearAverageInputsProduction.py +0 -0
  48. /hestia_earth/models/{impact_assessment → linkedImpactAssessment}/landTransformationFromOtherNaturalVegetation100YearAverageInputsProduction.py +0 -0
  49. /hestia_earth/models/{impact_assessment → linkedImpactAssessment}/landTransformationFromOtherNaturalVegetation20YearAverageInputsProduction.py +0 -0
  50. /hestia_earth/models/{impact_assessment → linkedImpactAssessment}/landTransformationFromPermanentPasture100YearAverageInputsProduction.py +0 -0
  51. /hestia_earth/models/{impact_assessment → linkedImpactAssessment}/landTransformationFromPermanentPasture20YearAverageInputsProduction.py +0 -0
  52. {hestia_earth_models-0.60.1.dist-info → hestia_earth_models-0.61.0.dist-info}/LICENSE +0 -0
  53. {hestia_earth_models-0.60.1.dist-info → hestia_earth_models-0.61.0.dist-info}/WHEEL +0 -0
  54. {hestia_earth_models-0.60.1.dist-info → hestia_earth_models-0.61.0.dist-info}/top_level.txt +0 -0
@@ -1,50 +1,89 @@
1
1
  """
2
2
  Cycle duration
3
3
 
4
- This model calculates the cycle duration using the cropping intensity for a single year.
4
+ This model calculates the cycle duration using:
5
+ * the `endDate` and the `startDate` if both are provided with a day precision;
6
+ * for temporary crops, using the `croppingIntensity` if provided;
7
+ * for permanent crops, the duration is set to `365`.
5
8
  """
6
- from hestia_earth.utils.model import find_term_match
9
+ from hestia_earth.schema import TermTermType
10
+ from hestia_earth.utils.model import find_term_match, find_primary_product
11
+ from hestia_earth.utils.date import diff_in_days
7
12
 
8
13
  from hestia_earth.models.log import logRequirements, logShouldRun
9
- from hestia_earth.models.utils.cycle import valid_site_type
14
+ from hestia_earth.models.utils.crop import get_crop_grouping_fao
10
15
  from . import MODEL
11
16
 
12
17
  REQUIREMENTS = {
13
18
  "Cycle": {
14
- "cycleDuration": "365",
15
19
  "endDate": "",
16
- "practices": [{"@type": "Practice", "value": "", "term.@id": "croppingIntensity"}],
17
- "site": {
18
- "@type": "Site",
19
- "siteType": "cropland"
20
+ "optional": {
21
+ "startDate": "",
22
+ "products": {
23
+ "@type": "Product",
24
+ "primary": "True",
25
+ "term.termType": "crop"
26
+ },
27
+ "practices": [{"@type": "Practice", "value": "", "term.@id": "croppingIntensity"}]
20
28
  }
21
29
  }
22
30
  }
23
31
  RETURNS = {
24
32
  "a `number` or `None` if requirements are not met": ""
25
33
  }
34
+ LOOKUPS = {
35
+ "crop": "cropGroupingFAO"
36
+ }
26
37
  MODEL_KEY = 'cycleDuration'
27
38
  DEFAULT_DURATION = 365
28
39
 
29
40
 
30
- def _run(croppingIntensity: float): return croppingIntensity * DEFAULT_DURATION
41
+ def _run_by_dates(cycle: dict):
42
+ start_date = cycle.get('startDate')
43
+ end_date = cycle.get('endDate')
44
+ return diff_in_days(start_date, end_date)
45
+
46
+
47
+ def _should_run_by_dates(cycle: dict):
48
+ start_date = cycle.get('startDate', '')
49
+ start_date_has_day = len(start_date) == 10
50
+ end_date = cycle.get('endDate', '')
51
+ end_date_has_day = len(start_date) == 10
52
+
53
+ logRequirements(cycle, model=MODEL, key=MODEL_KEY, by='dates',
54
+ start_date_has_day=start_date_has_day,
55
+ start_date=start_date,
56
+ end_date_has_day=end_date_has_day,
57
+ end_date=end_date)
58
+
59
+ should_run = all([start_date_has_day, end_date_has_day])
60
+ logShouldRun(cycle, MODEL, None, should_run, key=MODEL_KEY, by='dates')
61
+ return should_run
62
+
63
+
64
+ def _run_by_crop(cycle: dict):
65
+ product = find_primary_product(cycle)
66
+ grouping = get_crop_grouping_fao(MODEL, MODEL_KEY, product.get('term', {}))
67
+ is_permanent_crop = grouping == 'Permanent crops'
68
+ croppingIntensity = find_term_match(cycle.get('practices', []), 'croppingIntensity').get('value', [1])[0]
69
+ return DEFAULT_DURATION * (1 if is_permanent_crop else croppingIntensity)
31
70
 
32
71
 
33
- def _should_run(cycle: dict):
34
- croppingIntensity = find_term_match(cycle.get('practices', []), 'croppingIntensity').get('value', [None])[0]
35
- cycleDuration = cycle.get('cycleDuration')
36
- site_type_valid = valid_site_type(cycle)
72
+ def _should_run_by_crop(cycle: dict):
73
+ product = find_primary_product(cycle) or {}
74
+ product_term_type = product.get('term', {}).get('termType')
75
+ primary_product_is_crop = product_term_type == TermTermType.CROP.value
37
76
 
38
- logRequirements(cycle, model=MODEL, key=MODEL_KEY,
39
- cycleDuration=cycleDuration,
40
- croppingIntensity=croppingIntensity,
41
- site_type_valid=site_type_valid)
77
+ logRequirements(cycle, model=MODEL, key=MODEL_KEY, by='product',
78
+ primary_product_is_crop=primary_product_is_crop)
42
79
 
43
- should_run = all([site_type_valid, cycleDuration == 365, croppingIntensity])
44
- logShouldRun(cycle, MODEL, None, should_run, key=MODEL_KEY)
45
- return should_run, croppingIntensity
80
+ should_run = all([primary_product_is_crop])
81
+ logShouldRun(cycle, MODEL, None, should_run, key=MODEL_KEY, by='product')
82
+ return should_run
46
83
 
47
84
 
48
85
  def run(cycle: dict):
49
- should_run, croppingIntensity = _should_run(cycle)
50
- return _run(croppingIntensity) if should_run else None
86
+ return _run_by_dates(cycle) if _should_run_by_dates(cycle) else (
87
+ _run_by_crop(cycle) if _should_run_by_crop(cycle)
88
+ else None
89
+ )
@@ -1,18 +1,20 @@
1
1
  """
2
2
  Start Date
3
3
 
4
- This model updates the [Cycle startDate](https://hestia.earth/schema/Cycle#startDate) to be in the following format:
5
- `YYYY-MM-DD`.
4
+ This model sets the [Cycle startDate](https://hestia.earth/schema/Cycle#startDate) based on the `endDate` and the
5
+ `cycleDuration`. This only works when the `endDate` has been provided to a day precision (`2000-01-01`).
6
6
  """
7
+ from datetime import timedelta
7
8
  from hestia_earth.utils.date import is_in_days
8
- from hestia_earth.utils.tools import non_empty_list
9
+ from hestia_earth.utils.tools import safe_parse_date
9
10
 
10
11
  from hestia_earth.models.log import logRequirements, logShouldRun
11
12
  from . import MODEL
12
13
 
13
14
  REQUIREMENTS = {
14
15
  "Cycle": {
15
- "startDate": ""
16
+ "endDate": "to day precision",
17
+ "cycleDuration": ""
16
18
  }
17
19
  }
18
20
  RETURNS = {
@@ -22,19 +24,22 @@ MODEL_KEY = 'startDate'
22
24
 
23
25
 
24
26
  def _run(cycle: dict):
25
- value = cycle.get('startDate')
26
- return '-'.join(non_empty_list([value, '01' if len(value) == 4 else '', '01']))
27
+ endDate = safe_parse_date(cycle.get('endDate'))
28
+ cycleDuration = cycle.get('cycleDuration')
29
+ return (endDate - timedelta(days=cycleDuration)).strftime('%Y-%m-%d')
27
30
 
28
31
 
29
32
  def _should_run(cycle: dict):
30
- has_startDate = cycle.get('startDate') is not None
31
- has_incorrect_format = has_startDate and not is_in_days(cycle.get('startDate'))
33
+ has_endDate = cycle.get('endDate') is not None
34
+ has_day_precision = has_endDate and is_in_days(cycle.get('endDate'))
35
+ has_cycleDuration = cycle.get('cycleDuration') is not None
32
36
 
33
37
  logRequirements(cycle, model=MODEL, key=MODEL_KEY,
34
- has_startDate=has_startDate,
35
- has_incorrect_format=has_incorrect_format)
38
+ has_endDate=has_endDate,
39
+ has_day_precision=has_day_precision,
40
+ has_cycleDuration=has_cycleDuration)
36
41
 
37
- should_run = all([has_startDate, has_incorrect_format])
42
+ should_run = all([has_endDate, has_day_precision, has_cycleDuration])
38
43
  logShouldRun(cycle, MODEL, None, should_run, key=MODEL_KEY)
39
44
  return should_run
40
45
 
@@ -0,0 +1,69 @@
1
+ """
2
+ Start Date Definition
3
+
4
+ This model sets the [Cycle startDateDefinition](https://hestia.earth/schema/Cycle#startDateDefinition)
5
+ based on the result of the `cycleDuration` gap-filling model.
6
+ """
7
+ from hestia_earth.schema import TermTermType, CycleStartDateDefinition
8
+ from hestia_earth.utils.model import find_primary_product
9
+
10
+ from hestia_earth.models.log import logRequirements, logShouldRun
11
+ from hestia_earth.models.utils.crop import get_crop_grouping_fao
12
+ from . import MODEL
13
+
14
+ REQUIREMENTS = {
15
+ "Cycle": {
16
+ "cycleDuration": "",
17
+ "products": {
18
+ "@type": "Product",
19
+ "primary": "True",
20
+ "term.termType": "crop"
21
+ },
22
+ "optional": {
23
+ "endDate": ""
24
+ }
25
+ }
26
+ }
27
+ RETURNS = {
28
+ "The startDateDefinition as a string": ""
29
+ }
30
+ MODEL_KEY = 'startDateDefinition'
31
+
32
+
33
+ def _is_last_day_of_month(date: str):
34
+ date_parts = date.split('-')
35
+ return len(date_parts) > 1 and date_parts[1] == '12' and any([
36
+ len(date_parts) == 3 and date_parts[2] == '31',
37
+ len(date_parts) == 2
38
+ ])
39
+
40
+
41
+ def _run(cycle: dict):
42
+ product = find_primary_product(cycle)
43
+ grouping = get_crop_grouping_fao(MODEL, MODEL_KEY, product.get('term', {}))
44
+ is_permanent_crop = grouping == 'Permanent crops'
45
+ print(is_permanent_crop)
46
+ return (
47
+ CycleStartDateDefinition.START_OF_YEAR.value if _is_last_day_of_month(cycle.get('endDate'))
48
+ else CycleStartDateDefinition.ONE_YEAR_PRIOR.value
49
+ ) if is_permanent_crop else (
50
+ CycleStartDateDefinition.HARVEST_OF_PREVIOUS_CROP.value
51
+ )
52
+
53
+
54
+ def _should_run(cycle: dict):
55
+ cycleDuration_added = 'cycleDuration' in cycle.get('added', [])
56
+ product = find_primary_product(cycle) or {}
57
+ product_term_type = product.get('term', {}).get('termType')
58
+ primary_product_is_crop = product_term_type == TermTermType.CROP.value
59
+
60
+ logRequirements(cycle, model=MODEL, key=MODEL_KEY,
61
+ cycleDuration_added=cycleDuration_added,
62
+ primary_product_is_crop=primary_product_is_crop)
63
+
64
+ should_run = all([cycleDuration_added, primary_product_is_crop])
65
+ logShouldRun(cycle, MODEL, None, should_run, key=MODEL_KEY)
66
+ return should_run
67
+
68
+
69
+ def run(cycle: dict): return _run(cycle) if _should_run(cycle) else None
@@ -42,11 +42,9 @@ def _indicator(product: dict):
42
42
  term_id = emission.get('term', {}).get('@id')
43
43
  value = convert_value_from_cycle(product, list_sum(emission.get('value', [0])), model=MODEL, term_id=term_id)
44
44
 
45
- indicator = _new_indicator(emission.get('term', {}))
45
+ indicator = _new_indicator(emission.get('term', {}), emission.get('methodModel'))
46
46
  indicator['value'] = value
47
47
 
48
- if 'methodModel' in emission:
49
- indicator['methodModel'] = emission['methodModel']
50
48
  if len(emission.get('inputs', [])):
51
49
  indicator['inputs'] = emission['inputs']
52
50
  if emission.get('operation'):
@@ -147,7 +147,7 @@ def _input(term_id: str, value: float):
147
147
  return node
148
148
 
149
149
 
150
- def _sum_values(values: list, index=0): return sum([v[index] for v in values])
150
+ def _sum_values(values: list, index=0): return list_sum([v[index] for v in values])
151
151
 
152
152
 
153
153
  def calculate_NEwool(cycle: dict) -> float:
@@ -1,167 +1,13 @@
1
- """
2
- Linked Impact Assessment
1
+ from os.path import dirname, abspath
2
+ import sys
3
+ from importlib import import_module
3
4
 
4
- A model which takes recalculated data from an Impact Assessment linked to an Input in a Cycle.
5
- """
6
- from functools import reduce
7
- from hestia_earth.schema import EmissionMethodTier
8
- from hestia_earth.utils.lookup import download_lookup, get_table_value, column_name
9
- from hestia_earth.utils.tools import flatten, list_sum
5
+ from hestia_earth.models.utils.blank_node import run_if_required
10
6
 
11
- from hestia_earth.models.log import debugValues, logRequirements, logShouldRun, log_as_table
12
- from hestia_earth.models.utils.emission import _new_emission
13
- from hestia_earth.models.utils.input import load_impacts
14
- from hestia_earth.models.utils.blank_node import group_by_keys
15
-
16
- REQUIREMENTS = {
17
- "Cycle": {
18
- "inputs": [{
19
- "@type": "Input",
20
- "value": "> 0",
21
- "impactAssessment": {
22
- "@type": "ImpactAssessment",
23
- "emissionsResourceUse": [{"@type": "Indicator", "value": ""}]
24
- }
25
- }],
26
- "optional": {
27
- "animals": [{
28
- "@type": "Animal",
29
- "inputs": [{
30
- "@type": "Input",
31
- "value": "> 0",
32
- "impactAssessment": {
33
- "@type": "ImpactAssessment",
34
- "emissionsResourceUse": [{"@type": "Indicator", "value": ""}]
35
- }
36
- }]
37
- }]
38
- }
39
- }
40
- }
41
- RETURNS = {
42
- "Emission": [{
43
- "value": "",
44
- "methodTier": "background",
45
- "inputs": "",
46
- "operation": "",
47
- "animals": ""
48
- }]
49
- }
7
+ CURRENT_DIR = dirname(abspath(__file__)) + '/'
8
+ sys.path.append(CURRENT_DIR)
50
9
  MODEL = 'linkedImpactAssessment'
51
- MODEL_AGGREGATED = 'hestiaAggregatedData'
52
- TIER = EmissionMethodTier.BACKGROUND.value
53
-
54
-
55
- def _emission(model: str, term_id: str, value: float, input: dict, operation={}, animal={}):
56
- emission = _new_emission(term_id, model)
57
- emission['value'] = [value]
58
- emission['methodTier'] = TIER
59
- emission['inputs'] = [input]
60
- if operation:
61
- emission['operation'] = operation
62
- if animal:
63
- emission['animals'] = [animal]
64
- return emission
65
-
66
-
67
- def _run_emission(cycle: dict, term_id: str, data: dict):
68
- def run_input(values: dict):
69
- value = values.get('value', 0)
70
- term = values.get('term', {})
71
- operation = values.get('operation', {})
72
- animal = values.get('animal', {})
73
- is_aggregated = any(values.get('aggregated', []))
74
- model = MODEL_AGGREGATED if is_aggregated else MODEL
75
-
76
- details = values.get('details', {})
77
- logRequirements(cycle, model=model, term=term_id,
78
- values=log_as_table([{'impact-assessment-id': key} | value for key, value in details.items()]))
79
-
80
- logShouldRun(cycle, model, term_id, True, methodTier=TIER,
81
- input=term.get('@id'),
82
- operation=operation.get('@id'),
83
- animal=animal.get('@id'))
84
-
85
- return _emission(model, term_id, value, input=term, operation=operation, animal=animal)
86
-
87
- return list(map(run_input, data.values()))
88
-
89
-
90
- def _emission_group(term_id: str):
91
- lookup = download_lookup('emission.csv', True)
92
- return get_table_value(lookup, 'termid', term_id, column_name('inputProductionGroupId'))
93
-
94
-
95
- def _group_emissions(impact: dict):
96
- def _group_by(group: dict, emission: dict):
97
- term_id = emission.get('term', {}).get('@id')
98
- grouping = _emission_group(term_id)
99
- value = emission.get('value') or 0
100
- if grouping:
101
- group[grouping] = group.get(grouping, 0) + value
102
- return group
103
-
104
- emissions = impact.get('emissionsResourceUse', [])
105
- return reduce(_group_by, emissions, {})
106
-
107
-
108
- def _animal_inputs(animal: dict):
109
- inputs = load_impacts(animal.get('inputs', []))
110
- return [(input | {'animal': animal.get('term', {})}) for input in inputs]
111
-
112
-
113
- def _group_input_emissions(input: dict):
114
- impact = input.get('impactAssessment')
115
- emissions = _group_emissions(impact)
116
- return input | {'emissions': emissions}
117
-
118
-
119
- def _group_inputs(group: dict, values: tuple):
120
- # input_group_key = 'group-id'
121
- # inputs = [{'term': {}, 'value':[], 'impactAssessment': {}, 'emissions': {'co2ToAirInputsProduction': 10}}]
122
- input_group_key, inputs = values
123
- for input in inputs:
124
- input_value = list_sum(input.get('value'))
125
- emissions = input.get('emissions', {})
126
- for emission_term_id, emission_value in emissions.items():
127
- group[emission_term_id] = group.get(emission_term_id, {})
128
-
129
- grouped_inputs = group[emission_term_id].get(input_group_key, {
130
- 'term': input.get('term', {}),
131
- 'operation': input.get('operation', {}),
132
- 'animal': input.get('animal', {}),
133
- 'value': 0,
134
- 'aggregated': [],
135
- 'details': {}
136
- })
137
- grouped_inputs['aggregated'].append(input.get('impactAssessment', {}).get('agregated', False))
138
- grouped_inputs['value'] = grouped_inputs['value'] + (emission_value * input_value)
139
- # for logging
140
- grouped_inputs['details'][input.get('impactAssessment', {}).get('@id')] = {
141
- 'emission-value': emission_value,
142
- 'input-value': input_value
143
- }
144
- group[emission_term_id][input_group_key] = grouped_inputs
145
- return group
146
-
147
-
148
- def run(_, cycle: dict):
149
- inputs = flatten(
150
- load_impacts(cycle.get('inputs', [])) +
151
- list(map(_animal_inputs, cycle.get('animals', [])))
152
- )
153
- inputs = [i for i in inputs if list_sum(i.get('value', [])) > 0]
154
-
155
- # group inputs with same term/operation/animal to avoid adding emissions twice
156
- # inputs = {'group-id': [{'term': {},'value':[10],'impactAssessment': {}}]}
157
- inputs = reduce(group_by_keys(['term', 'operation', 'animal']), inputs, {})
158
- inputs = {key: list(map(_group_input_emissions, value)) for key, value in inputs.items()}
159
-
160
- debugValues(cycle, model=MODEL,
161
- nb_inputs=len(inputs))
10
+ PKG = '.'.join(['hestia_earth', 'models', MODEL])
162
11
 
163
- # finally group everything by emission so we can log inputs together
164
- # emissions = {'co2ToAirInputsProduct': {'group-id':{'term':{},'value':10,'details':{}}}}
165
- emissions = reduce(_group_inputs, inputs.items(), {})
166
12
 
167
- return flatten([_run_emission(cycle, term_id, data) for term_id, data in emissions.items()])
13
+ def run(model: str, data): return run_if_required(MODEL, model, data, import_module(f".{model}", package=PKG))
@@ -0,0 +1,168 @@
1
+ """
2
+ Emissions
3
+
4
+ A model which takes recalculated data from an Impact Assessment linked to an Input in a Cycle.
5
+ """
6
+ from functools import reduce
7
+ from hestia_earth.schema import EmissionMethodTier
8
+ from hestia_earth.utils.lookup import download_lookup, get_table_value, column_name
9
+ from hestia_earth.utils.tools import flatten, list_sum
10
+
11
+ from hestia_earth.models.log import debugValues, logRequirements, logShouldRun, log_as_table
12
+ from hestia_earth.models.utils.emission import _new_emission
13
+ from hestia_earth.models.utils.input import load_impacts
14
+ from hestia_earth.models.utils.blank_node import group_by_keys
15
+ from . import MODEL
16
+
17
+ REQUIREMENTS = {
18
+ "Cycle": {
19
+ "inputs": [{
20
+ "@type": "Input",
21
+ "value": "> 0",
22
+ "impactAssessment": {
23
+ "@type": "ImpactAssessment",
24
+ "emissionsResourceUse": [{"@type": "Indicator", "value": ""}]
25
+ }
26
+ }],
27
+ "optional": {
28
+ "animals": [{
29
+ "@type": "Animal",
30
+ "inputs": [{
31
+ "@type": "Input",
32
+ "value": "> 0",
33
+ "impactAssessment": {
34
+ "@type": "ImpactAssessment",
35
+ "emissionsResourceUse": [{"@type": "Indicator", "value": ""}]
36
+ }
37
+ }]
38
+ }]
39
+ }
40
+ }
41
+ }
42
+ RETURNS = {
43
+ "Emission": [{
44
+ "value": "",
45
+ "methodTier": "background",
46
+ "inputs": "",
47
+ "operation": "",
48
+ "animals": ""
49
+ }]
50
+ }
51
+ MODEL_KEY = 'emissions'
52
+ MODEL_AGGREGATED = 'hestiaAggregatedData'
53
+ TIER = EmissionMethodTier.BACKGROUND.value
54
+
55
+
56
+ def _emission(model: str, term_id: str, value: float, input: dict, operation={}, animal={}):
57
+ emission = _new_emission(term_id, model)
58
+ emission['value'] = [value]
59
+ emission['methodTier'] = TIER
60
+ emission['inputs'] = [input]
61
+ if operation:
62
+ emission['operation'] = operation
63
+ if animal:
64
+ emission['animals'] = [animal]
65
+ return emission
66
+
67
+
68
+ def _run_emission(cycle: dict, term_id: str, data: dict):
69
+ def run_input(values: dict):
70
+ value = values.get('value', 0)
71
+ term = values.get('term', {})
72
+ operation = values.get('operation', {})
73
+ animal = values.get('animal', {})
74
+ is_aggregated = any(values.get('aggregated', []))
75
+ model = MODEL_AGGREGATED if is_aggregated else MODEL
76
+
77
+ details = values.get('details', {})
78
+ logRequirements(cycle, model=model, term=term_id, key=MODEL_KEY,
79
+ values=log_as_table([{'impact-assessment-id': key} | value for key, value in details.items()]))
80
+
81
+ logShouldRun(cycle, model, term_id, True, methodTier=TIER, key=MODEL_KEY,
82
+ input=term.get('@id'),
83
+ operation=operation.get('@id'),
84
+ animal=animal.get('@id'))
85
+
86
+ return _emission(model, term_id, value, input=term, operation=operation, animal=animal)
87
+
88
+ return list(map(run_input, data.values()))
89
+
90
+
91
+ def _emission_group(term_id: str):
92
+ lookup = download_lookup('emission.csv', True)
93
+ return get_table_value(lookup, 'termid', term_id, column_name('inputProductionGroupId'))
94
+
95
+
96
+ def _group_emissions(impact: dict):
97
+ def _group_by(group: dict, emission: dict):
98
+ term_id = emission.get('term', {}).get('@id')
99
+ grouping = _emission_group(term_id)
100
+ value = emission.get('value') or 0
101
+ if grouping:
102
+ group[grouping] = group.get(grouping, 0) + value
103
+ return group
104
+
105
+ emissions = impact.get('emissionsResourceUse', [])
106
+ return reduce(_group_by, emissions, {})
107
+
108
+
109
+ def _animal_inputs(animal: dict):
110
+ inputs = load_impacts(animal.get('inputs', []))
111
+ return [(input | {'animal': animal.get('term', {})}) for input in inputs]
112
+
113
+
114
+ def _group_input_emissions(input: dict):
115
+ impact = input.get('impactAssessment')
116
+ emissions = _group_emissions(impact)
117
+ return input | {'emissions': emissions}
118
+
119
+
120
+ def _group_inputs(group: dict, values: tuple):
121
+ # input_group_key = 'group-id'
122
+ # inputs = [{'term': {}, 'value':[], 'impactAssessment': {}, 'emissions': {'co2ToAirInputsProduction': 10}}]
123
+ input_group_key, inputs = values
124
+ for input in inputs:
125
+ input_value = list_sum(input.get('value'))
126
+ emissions = input.get('emissions', {})
127
+ for emission_term_id, emission_value in emissions.items():
128
+ group[emission_term_id] = group.get(emission_term_id, {})
129
+
130
+ grouped_inputs = group[emission_term_id].get(input_group_key, {
131
+ 'term': input.get('term', {}),
132
+ 'operation': input.get('operation', {}),
133
+ 'animal': input.get('animal', {}),
134
+ 'value': 0,
135
+ 'aggregated': [],
136
+ 'details': {}
137
+ })
138
+ grouped_inputs['aggregated'].append(input.get('impactAssessment', {}).get('agregated', False))
139
+ grouped_inputs['value'] = grouped_inputs['value'] + (emission_value * input_value)
140
+ # for logging
141
+ grouped_inputs['details'][input.get('impactAssessment', {}).get('@id')] = {
142
+ 'emission-value': emission_value,
143
+ 'input-value': input_value
144
+ }
145
+ group[emission_term_id][input_group_key] = grouped_inputs
146
+ return group
147
+
148
+
149
+ def run(cycle: dict):
150
+ inputs = flatten(
151
+ load_impacts(cycle.get('inputs', [])) +
152
+ list(map(_animal_inputs, cycle.get('animals', [])))
153
+ )
154
+ inputs = [i for i in inputs if list_sum(i.get('value', [])) > 0]
155
+
156
+ # group inputs with same term/operation/animal to avoid adding emissions twice
157
+ # inputs = {'group-id': [{'term': {},'value':[10],'impactAssessment': {}}]}
158
+ inputs = reduce(group_by_keys(['term', 'operation', 'animal']), inputs, {})
159
+ inputs = {key: list(map(_group_input_emissions, value)) for key, value in inputs.items()}
160
+
161
+ debugValues(cycle, model=MODEL, key=MODEL_KEY,
162
+ nb_inputs=len(inputs))
163
+
164
+ # finally group everything by emission so we can log inputs together
165
+ # emissions = {'co2ToAirInputsProduct': {'group-id':{'term':{},'value':10,'details':{}}}}
166
+ emissions = reduce(_group_inputs, inputs.items(), {})
167
+
168
+ return flatten([_run_emission(cycle, term_id, data) for term_id, data in emissions.items()])
@@ -9,7 +9,7 @@ from . import MODEL
9
9
 
10
10
 
11
11
  def _indicator(term_id: str, value: float):
12
- indicator = _new_indicator(term_id)
12
+ indicator = _new_indicator(term_id, MODEL)
13
13
  indicator['value'] = value
14
14
  return indicator
15
15