hestia-earth-models 0.65.3__py3-none-any.whl → 0.65.5__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.
- hestia_earth/models/agribalyse2016/fuelElectricity.py +41 -35
- hestia_earth/models/aware/scarcityWeightedWaterUse.py +1 -1
- hestia_earth/models/cache_sites.py +2 -0
- hestia_earth/models/chaudharyBrooks2018/damageToTerrestrialEcosystemsLandOccupation.py +1 -1
- hestia_earth/models/chaudharyBrooks2018/damageToTerrestrialEcosystemsLandTransformation.py +1 -1
- hestia_earth/models/chaudharyBrooks2018/damageToTerrestrialEcosystemsTotalLandUseEffects.py +1 -1
- hestia_earth/models/cycle/completeness/__init__.py +1 -1
- hestia_earth/models/cycle/completeness/electricityFuel.py +4 -2
- hestia_earth/models/emepEea2019/nh3ToAirInorganicFertiliser.py +1 -1
- hestia_earth/models/geospatialDatabase/precipitationAnnual.py +2 -2
- hestia_earth/models/geospatialDatabase/precipitationLongTermAnnualMean.py +2 -2
- hestia_earth/models/geospatialDatabase/precipitationMonthly.py +2 -2
- hestia_earth/models/geospatialDatabase/temperatureAnnual.py +2 -2
- hestia_earth/models/geospatialDatabase/temperatureLongTermAnnualMean.py +2 -2
- hestia_earth/models/geospatialDatabase/temperatureMonthly.py +2 -2
- hestia_earth/models/hestia/landCover.py +31 -46
- hestia_earth/models/hestia/landTransformation100YearAverageDuringCycle.py +49 -0
- hestia_earth/models/hestia/landTransformation20YearAverageDuringCycle.py +49 -0
- hestia_earth/models/hestia/resourceUse_utils.py +200 -0
- hestia_earth/models/hestia/seed_emissions.py +37 -28
- hestia_earth/models/hestia/utils.py +48 -0
- hestia_earth/models/impact_assessment/emissions.py +20 -5
- hestia_earth/models/ipcc2019/co2ToAirCarbonStockChange_utils.py +66 -28
- hestia_earth/models/ipcc2019/croppingDuration.py +5 -0
- hestia_earth/models/ipcc2019/organicCarbonPerHa_tier_1_utils.py +26 -142
- hestia_earth/models/ipcc2019/organicCarbonPerHa_tier_2_utils.py +3 -3
- hestia_earth/models/ipcc2019/organicCarbonPerHa_utils.py +8 -5
- hestia_earth/models/linkedImpactAssessment/freshwaterWithdrawalsInputsProduction.py +2 -1
- hestia_earth/models/linkedImpactAssessment/landOccupationInputsProduction.py +1 -0
- hestia_earth/models/linkedImpactAssessment/landTransformation100YearAverageInputsProduction.py +1 -0
- hestia_earth/models/linkedImpactAssessment/landTransformation20YearAverageInputsProduction.py +1 -0
- hestia_earth/models/linkedImpactAssessment/utils.py +25 -20
- hestia_earth/models/mocking/search-results.json +670 -654
- hestia_earth/models/pooreNemecek2018/freshwaterWithdrawalsDuringCycle.py +4 -1
- hestia_earth/models/schererPfister2015/nErosionSoilFlux.py +23 -14
- hestia_earth/models/schererPfister2015/pErosionSoilFlux.py +23 -15
- hestia_earth/models/schererPfister2015/utils.py +3 -5
- hestia_earth/models/site/management.py +2 -2
- hestia_earth/models/utils/__init__.py +9 -0
- hestia_earth/models/utils/blank_node.py +28 -0
- hestia_earth/models/utils/ecoClimateZone.py +1 -4
- hestia_earth/models/utils/fuel.py +4 -1
- hestia_earth/models/utils/impact_assessment.py +7 -5
- hestia_earth/models/utils/lookup.py +8 -2
- hestia_earth/models/utils/pesticideAI.py +1 -0
- hestia_earth/models/version.py +1 -1
- {hestia_earth_models-0.65.3.dist-info → hestia_earth_models-0.65.5.dist-info}/METADATA +2 -2
- {hestia_earth_models-0.65.3.dist-info → hestia_earth_models-0.65.5.dist-info}/RECORD +59 -53
- tests/models/hestia/test_landTransformation100YearAverageDuringCycle.py +30 -0
- tests/models/hestia/test_landTransformation20YearAverageDuringCycle.py +31 -0
- tests/models/ipcc2019/test_co2ToAirAboveGroundBiomassStockChange.py +3 -1
- tests/models/ipcc2019/test_co2ToAirBelowGroundBiomassStockChange.py +3 -1
- tests/models/ipcc2019/test_co2ToAirSoilOrganicCarbonStockChange.py +3 -1
- tests/models/ipcc2019/test_organicCarbonPerHa.py +3 -2
- tests/models/ipcc2019/test_organicCarbonPerHa_tier_1_utils.py +15 -11
- tests/models/utils/test_blank_node.py +22 -7
- {hestia_earth_models-0.65.3.dist-info → hestia_earth_models-0.65.5.dist-info}/LICENSE +0 -0
- {hestia_earth_models-0.65.3.dist-info → hestia_earth_models-0.65.5.dist-info}/WHEEL +0 -0
- {hestia_earth_models-0.65.3.dist-info → hestia_earth_models-0.65.5.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Resource Use
|
|
3
|
+
|
|
4
|
+
Provides common code for land transformation models.
|
|
5
|
+
"""
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from dateutil.relativedelta import relativedelta
|
|
8
|
+
from hestia_earth.schema import TermTermType
|
|
9
|
+
from hestia_earth.utils.tools import to_precision
|
|
10
|
+
|
|
11
|
+
from hestia_earth.models.log import logRequirements, logShouldRun
|
|
12
|
+
from hestia_earth.models.utils.blank_node import _gapfill_datestr, DatestrGapfillMode, DatestrFormat, _str_dates_match
|
|
13
|
+
from hestia_earth.models.utils.impact_assessment import get_site
|
|
14
|
+
from hestia_earth.models.utils.indicator import _new_indicator
|
|
15
|
+
from .utils import (
|
|
16
|
+
LAND_USE_TERMS_FOR_TRANSFORMATION,
|
|
17
|
+
crop_ipcc_land_use_category,
|
|
18
|
+
)
|
|
19
|
+
from . import MODEL
|
|
20
|
+
|
|
21
|
+
_MAXIMUM_OFFSET_DAYS = 365 * 2
|
|
22
|
+
_OUTPUT_SIGNIFICANT_DIGITS = 3
|
|
23
|
+
_RESOURCE_USE_TERM_ID = 'landOccupationDuringCycle'
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _new_indicator_with_value(
|
|
27
|
+
term_id: str,
|
|
28
|
+
land_cover_id: str,
|
|
29
|
+
previous_land_cover_id: str,
|
|
30
|
+
value: float
|
|
31
|
+
) -> dict:
|
|
32
|
+
indicator = _new_indicator(
|
|
33
|
+
term=term_id,
|
|
34
|
+
model=MODEL,
|
|
35
|
+
land_cover_id=land_cover_id,
|
|
36
|
+
previous_land_cover_id=previous_land_cover_id
|
|
37
|
+
)
|
|
38
|
+
indicator["value"] = to_precision(number=value, digits=_OUTPUT_SIGNIFICANT_DIGITS) if value != 0 else 0
|
|
39
|
+
return indicator
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _gap_filled_date_obj(date_str: str) -> datetime:
|
|
43
|
+
return datetime.strptime(
|
|
44
|
+
_gapfill_datestr(datestr=date_str, mode=DatestrGapfillMode.MIDDLE),
|
|
45
|
+
DatestrFormat.YEAR_MONTH_DAY_HOUR_MINUTE_SECOND.value
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _should_run_close_date_found(
|
|
50
|
+
ia_end_date_str: str,
|
|
51
|
+
management_nodes: list,
|
|
52
|
+
historic_date_offset: int
|
|
53
|
+
) -> tuple[bool, str]:
|
|
54
|
+
historic_ia_date_obj = (
|
|
55
|
+
_gap_filled_date_obj(ia_end_date_str) - relativedelta(years=historic_date_offset)
|
|
56
|
+
if ia_end_date_str else None
|
|
57
|
+
)
|
|
58
|
+
# Calculate all distances in days which are less than MAXIMUM_OFFSET_DAYS from historic date
|
|
59
|
+
# Assumption: if there are two dates are equidistant from the target chose the second.
|
|
60
|
+
filtered_dates = {
|
|
61
|
+
abs((_gap_filled_date_obj(node.get("endDate")) - historic_ia_date_obj).days): node.get("endDate")
|
|
62
|
+
for node in management_nodes
|
|
63
|
+
if node.get("term", {}).get("termType", "") == TermTermType.LANDCOVER.value and
|
|
64
|
+
abs((_gap_filled_date_obj(node.get("endDate")) - historic_ia_date_obj).days) <= _MAXIMUM_OFFSET_DAYS
|
|
65
|
+
}
|
|
66
|
+
nearest_date = filtered_dates[min(filtered_dates.keys())] if filtered_dates else ""
|
|
67
|
+
|
|
68
|
+
return nearest_date != "", nearest_date
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def should_run(
|
|
72
|
+
impact_assessment: dict,
|
|
73
|
+
site: dict,
|
|
74
|
+
term_id: str,
|
|
75
|
+
historic_date_offset: int
|
|
76
|
+
) -> tuple[bool, dict, str]:
|
|
77
|
+
relevant_emission_resource_use = [
|
|
78
|
+
node for node in impact_assessment.get("emissionsResourceUse", [])
|
|
79
|
+
if node.get("term", {}).get("@id", "") == _RESOURCE_USE_TERM_ID and node.get("value", -1) >= 0
|
|
80
|
+
]
|
|
81
|
+
|
|
82
|
+
filtered_management_nodes = [
|
|
83
|
+
node for node in site.get("management", [])
|
|
84
|
+
if node.get("value", -1) >= 0 and node.get("term", {}).get("termType", "") == TermTermType.LANDCOVER.value
|
|
85
|
+
]
|
|
86
|
+
current_node_index = next(
|
|
87
|
+
(i for i, node in enumerate(filtered_management_nodes)
|
|
88
|
+
if _str_dates_match(node.get("endDate", ""), impact_assessment.get("endDate", ""))),
|
|
89
|
+
None
|
|
90
|
+
)
|
|
91
|
+
current_node = filtered_management_nodes.pop(current_node_index) if current_node_index is not None else None
|
|
92
|
+
|
|
93
|
+
close_date_found, closest_date_str = _should_run_close_date_found(
|
|
94
|
+
ia_end_date_str=impact_assessment.get("endDate", ""),
|
|
95
|
+
management_nodes=filtered_management_nodes,
|
|
96
|
+
historic_date_offset=historic_date_offset
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
logRequirements(
|
|
100
|
+
log_node=impact_assessment,
|
|
101
|
+
model=MODEL,
|
|
102
|
+
term_id=term_id,
|
|
103
|
+
site=site
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
should_run_result = all([
|
|
107
|
+
relevant_emission_resource_use != [],
|
|
108
|
+
current_node,
|
|
109
|
+
close_date_found
|
|
110
|
+
])
|
|
111
|
+
logShouldRun(site, MODEL, term=term_id, should_run=should_run_result)
|
|
112
|
+
|
|
113
|
+
return should_run_result, current_node, closest_date_str
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _get_land_occupation_for_land_use_type(impact_assessment: dict, ipcc_land_use_category: str) -> float:
|
|
117
|
+
"""
|
|
118
|
+
Returns the sum of all land occupation for the specified land_use_category.
|
|
119
|
+
"""
|
|
120
|
+
return sum(
|
|
121
|
+
node.get("value", 0) for node in impact_assessment.get("emissionsResourceUse", [])
|
|
122
|
+
if node.get("term", {}).get("@id", "") == _RESOURCE_USE_TERM_ID
|
|
123
|
+
and crop_ipcc_land_use_category(node.get("landCover", {}).get("@id", "")) == ipcc_land_use_category
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _calculate_indicator_value(
|
|
128
|
+
impact_assessment: dict,
|
|
129
|
+
management_nodes: list,
|
|
130
|
+
ipcc_land_use_category: str,
|
|
131
|
+
previous_land_cover_id: str,
|
|
132
|
+
historic_date_offset: int
|
|
133
|
+
) -> float:
|
|
134
|
+
"""
|
|
135
|
+
Land transformation from [land type] previous management nodes
|
|
136
|
+
= (Land occupation, during Cycle * Historic Site Percentage Area [land type] / 100) / HISTORIC_DATE_OFFSET
|
|
137
|
+
"""
|
|
138
|
+
land_occupation_for_cycle = _get_land_occupation_for_land_use_type(
|
|
139
|
+
impact_assessment=impact_assessment,
|
|
140
|
+
ipcc_land_use_category=ipcc_land_use_category
|
|
141
|
+
)
|
|
142
|
+
historical_land_use = sum(
|
|
143
|
+
node.get("value", 0) for node in management_nodes
|
|
144
|
+
if node.get("term", {}).get("@id", "") == previous_land_cover_id
|
|
145
|
+
)
|
|
146
|
+
return ((land_occupation_for_cycle * historical_land_use) / 100) / historic_date_offset
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def _run_calculate_transformation(
|
|
150
|
+
term_id: str,
|
|
151
|
+
current_node: dict,
|
|
152
|
+
closest_date_str: str,
|
|
153
|
+
impact_assessment: dict,
|
|
154
|
+
site: dict,
|
|
155
|
+
historic_date_offset: int
|
|
156
|
+
) -> list:
|
|
157
|
+
"""
|
|
158
|
+
Calculate land transformation for all land use categories.
|
|
159
|
+
"""
|
|
160
|
+
indicators = [
|
|
161
|
+
_new_indicator_with_value(
|
|
162
|
+
term_id=term_id,
|
|
163
|
+
land_cover_id=current_node.get("term", {}).get("@id"),
|
|
164
|
+
previous_land_cover_id=previous_land_cover_id,
|
|
165
|
+
value=_calculate_indicator_value(
|
|
166
|
+
impact_assessment=impact_assessment,
|
|
167
|
+
management_nodes=[
|
|
168
|
+
node for node in site.get("management", [])
|
|
169
|
+
if _str_dates_match(node.get("endDate", ""), closest_date_str)
|
|
170
|
+
],
|
|
171
|
+
ipcc_land_use_category=crop_ipcc_land_use_category(current_node.get("term", {}).get("@id", "")),
|
|
172
|
+
previous_land_cover_id=previous_land_cover_id,
|
|
173
|
+
historic_date_offset=historic_date_offset
|
|
174
|
+
)
|
|
175
|
+
) for previous_land_cover_id in [t[0] for t in LAND_USE_TERMS_FOR_TRANSFORMATION.values()]
|
|
176
|
+
]
|
|
177
|
+
|
|
178
|
+
return indicators
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def run_resource_use(
|
|
182
|
+
impact_assessment: dict,
|
|
183
|
+
historic_date_offset: int,
|
|
184
|
+
term_id: str
|
|
185
|
+
) -> list:
|
|
186
|
+
site = get_site(impact_assessment)
|
|
187
|
+
_should_run, current_node, closest_date_str = should_run(
|
|
188
|
+
impact_assessment=impact_assessment,
|
|
189
|
+
site=site,
|
|
190
|
+
term_id=term_id,
|
|
191
|
+
historic_date_offset=historic_date_offset
|
|
192
|
+
)
|
|
193
|
+
return _run_calculate_transformation(
|
|
194
|
+
term_id=term_id,
|
|
195
|
+
current_node=current_node,
|
|
196
|
+
closest_date_str=closest_date_str,
|
|
197
|
+
site=site,
|
|
198
|
+
impact_assessment=impact_assessment,
|
|
199
|
+
historic_date_offset=historic_date_offset
|
|
200
|
+
) if _should_run else []
|
|
@@ -18,8 +18,8 @@ from hestia_earth.utils.model import filter_list_term_type
|
|
|
18
18
|
from hestia_earth.utils.tools import non_empty_list, flatten, list_sum, safe_parse_float
|
|
19
19
|
from hestia_earth.utils.emission import cycle_emissions_in_system_boundary
|
|
20
20
|
|
|
21
|
-
from hestia_earth.models.log import logRequirements, logShouldRun, log_as_table
|
|
22
|
-
from hestia_earth.models.utils import _omit
|
|
21
|
+
from hestia_earth.models.log import logRequirements, logShouldRun, log_as_table, debugValues
|
|
22
|
+
from hestia_earth.models.utils import _omit, group_by
|
|
23
23
|
from hestia_earth.models.utils.emission import _new_emission
|
|
24
24
|
from hestia_earth.models.utils.site import valid_site_type
|
|
25
25
|
from hestia_earth.models.utils.cycle import cycle_end_year
|
|
@@ -54,7 +54,7 @@ REQUIREMENTS = {
|
|
|
54
54
|
"siteType": ["cropland", "glass or high accessible cover"],
|
|
55
55
|
"country": {"@type": "Term", "termType": "region"}
|
|
56
56
|
},
|
|
57
|
-
"emissions": [{"@type": "Emission"}]
|
|
57
|
+
"emissions": [{"@type": "Emission", "value": ""}]
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
60
|
RETURNS = {
|
|
@@ -86,11 +86,24 @@ def _emission(term_id: str, value: float, input: dict):
|
|
|
86
86
|
return emission
|
|
87
87
|
|
|
88
88
|
|
|
89
|
-
def
|
|
90
|
-
|
|
89
|
+
def _run_emission(
|
|
90
|
+
cycle: dict, economicValueShare: float, total_yield: float, seed_input: dict, term_id: str, emission_value: float
|
|
91
|
+
):
|
|
92
|
+
input_term = seed_input.get('term', {})
|
|
93
|
+
input_term_id = input_term.get('@id')
|
|
91
94
|
seed_value = list_sum(seed_input.get('value'))
|
|
95
|
+
value = emission_value * economicValueShare / 100 / total_yield * seed_value
|
|
96
|
+
debugValues(cycle, model=MODEL, term=term_id, model_key=MODEL_KEY,
|
|
97
|
+
value=value,
|
|
98
|
+
coefficient=1,
|
|
99
|
+
input=input_term_id)
|
|
100
|
+
|
|
101
|
+
return _emission(term_id, value, input_term)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _run(cycle: dict, economicValueShare: float, total_yield: float, seed_input: dict, grouped_emissions: dict):
|
|
92
105
|
return [
|
|
93
|
-
|
|
106
|
+
_run_emission(cycle, economicValueShare, total_yield, seed_input, term_id, emission_value)
|
|
94
107
|
for term_id, emission_value in grouped_emissions.items()
|
|
95
108
|
]
|
|
96
109
|
|
|
@@ -102,12 +115,12 @@ def _filter_emissions(cycle: dict):
|
|
|
102
115
|
{
|
|
103
116
|
'id': i.get('term', {}).get('@id'),
|
|
104
117
|
'group-id': get_lookup_value(i.get('term', {}), LOOKUPS['emission'], model=MODEL, model_key=MODEL_KEY),
|
|
105
|
-
'value': list_sum(i.get('value')
|
|
118
|
+
'value': list_sum(i.get('value'))
|
|
106
119
|
}
|
|
107
120
|
for i in cycle.get('emissions', [])
|
|
108
121
|
if all([
|
|
109
122
|
i.get('term', {}).get('@id') in required_emission_term_ids,
|
|
110
|
-
|
|
123
|
+
len(i.get('value', [])) > 0
|
|
111
124
|
])
|
|
112
125
|
]
|
|
113
126
|
emission_ids = set([v.get('id') for v in emissions])
|
|
@@ -120,7 +133,7 @@ def _filter_emissions(cycle: dict):
|
|
|
120
133
|
'id': group_id,
|
|
121
134
|
'emissions': list(filter(
|
|
122
135
|
lambda id: id in required_emission_term_ids,
|
|
123
|
-
list(lookup[lookup[column_name('inputProductionGroupId')] == group_id].termid)
|
|
136
|
+
set(list(lookup[lookup[column_name('inputProductionGroupId')] == group_id].termid))
|
|
124
137
|
))
|
|
125
138
|
}
|
|
126
139
|
for group_id in group_ids
|
|
@@ -129,8 +142,8 @@ def _filter_emissions(cycle: dict):
|
|
|
129
142
|
{
|
|
130
143
|
'id': group.get('id'),
|
|
131
144
|
'total-emissions': len(group.get('emissions', [])),
|
|
132
|
-
'included-emissions': len(
|
|
133
|
-
'missing-emissions': '-'.join(
|
|
145
|
+
'included-emissions': len(list(filter(lambda v: v in emission_ids, group.get('emissions', [])))),
|
|
146
|
+
'missing-emissions': '-'.join(list(filter(lambda v: v not in emission_ids, group.get('emissions', []))))
|
|
134
147
|
}
|
|
135
148
|
for group in emissions_per_group
|
|
136
149
|
]
|
|
@@ -154,23 +167,18 @@ def _evs(product: dict):
|
|
|
154
167
|
) or product.get('economicValueShare')
|
|
155
168
|
|
|
156
169
|
|
|
157
|
-
def
|
|
170
|
+
def _faostat_yield(country_id: str, end_year: int, product: dict):
|
|
158
171
|
grouping = get_crop_grouping_faostat_production(MODEL, product.get('term', {}))
|
|
159
172
|
return safe_parse_float(extract_grouped_data_closest_date(get_table_value(
|
|
160
173
|
download_lookup('region-crop-cropGroupingFaostatProduction-yield.csv'),
|
|
161
174
|
'termid',
|
|
162
175
|
country_id,
|
|
163
176
|
column_name(grouping)
|
|
164
|
-
), end_year))
|
|
177
|
+
), end_year))
|
|
165
178
|
|
|
166
179
|
|
|
167
180
|
def _group_seed_inputs(inputs: list):
|
|
168
|
-
|
|
169
|
-
term_id = input.get('term', {}).get('@id')
|
|
170
|
-
group[term_id] = group.get(term_id, []) + [input]
|
|
171
|
-
return group
|
|
172
|
-
|
|
173
|
-
grouped_inputs = reduce(_group_by, inputs, {})
|
|
181
|
+
grouped_inputs = group_by(inputs, ['term.@id'])
|
|
174
182
|
return [
|
|
175
183
|
inputs[0] | {'value': flatten([v.get('value') for v in inputs])}
|
|
176
184
|
for inputs in grouped_inputs.values()
|
|
@@ -191,10 +199,11 @@ def _should_run(cycle: dict):
|
|
|
191
199
|
{
|
|
192
200
|
'product': product.get('term', {}).get('@id'),
|
|
193
201
|
'seed-id': get_lookup_value(
|
|
194
|
-
product.get('term', {}), 'correspondingSeedTermIds', model=MODEL,
|
|
202
|
+
product.get('term', {}), 'correspondingSeedTermIds', model=MODEL, model_key=MODEL_KEY) or None,
|
|
195
203
|
'economicValueShare': _evs(product),
|
|
196
|
-
'yield':
|
|
197
|
-
'
|
|
204
|
+
'FAOSTAT-yield': _faostat_yield(country_id, end_year, product),
|
|
205
|
+
'product-yield': list_sum(product.get('value')),
|
|
206
|
+
'landCover-id': get_landCover_term_id(product.get('term', {}), model=MODEL, model_key=MODEL_KEY)
|
|
198
207
|
}
|
|
199
208
|
for product in crop_products
|
|
200
209
|
]
|
|
@@ -202,7 +211,7 @@ def _should_run(cycle: dict):
|
|
|
202
211
|
value for value in crop_products if all([
|
|
203
212
|
value.get('seed-id'),
|
|
204
213
|
value.get('economicValueShare'),
|
|
205
|
-
value.get('yield'),
|
|
214
|
+
value.get('FAOSTAT-yield') or value.get('product-yield'),
|
|
206
215
|
value.get('landCover-id'),
|
|
207
216
|
])
|
|
208
217
|
]
|
|
@@ -214,7 +223,7 @@ def _should_run(cycle: dict):
|
|
|
214
223
|
{
|
|
215
224
|
'input': i,
|
|
216
225
|
'is-corresponding-seed': i.get('term', {}).get('@id') in seed_term_ids,
|
|
217
|
-
'input-value': i.get('value'),
|
|
226
|
+
'input-value': list_sum(i.get('value'), default=None),
|
|
218
227
|
'has-linked-impact-assessment': bool(i.get('impactAssessment')),
|
|
219
228
|
'is-fromCycle': i.get('fromCycle', False),
|
|
220
229
|
'is-producedInCycle': i.get('producedInCycle', False),
|
|
@@ -226,7 +235,7 @@ def _should_run(cycle: dict):
|
|
|
226
235
|
v.get('input') for v in seed_inputs
|
|
227
236
|
if all([
|
|
228
237
|
v.get('is-corresponding-seed', False),
|
|
229
|
-
|
|
238
|
+
v.get('input-value') or -1 > 0,
|
|
230
239
|
not v.get('has-linked-impact-assessment'),
|
|
231
240
|
not v.get('is-fromCycle'),
|
|
232
241
|
not v.get('is-producedInCycle'),
|
|
@@ -235,12 +244,12 @@ def _should_run(cycle: dict):
|
|
|
235
244
|
|
|
236
245
|
crop_land_cover_ids = list(set([p.get('landCover-id') for p in valid_crop_products]))
|
|
237
246
|
total_economicValueShare = list_sum([p.get('economicValueShare') for p in valid_crop_products])
|
|
238
|
-
total_yield = list_sum([p.get('yield') for p in valid_crop_products])
|
|
247
|
+
total_yield = list_sum([p.get('FAOSTAT-yield') or p.get('product-yield') for p in valid_crop_products])
|
|
239
248
|
|
|
240
249
|
emissions, emissions_per_group = _filter_emissions(cycle)
|
|
241
250
|
# group emissions with the same group-id
|
|
242
251
|
grouped_emissions = reduce(
|
|
243
|
-
lambda p, c: p | {c.get('group-id'): p.get(c.get('group-id'), 0) + c.get('value'
|
|
252
|
+
lambda p, c: p | {c.get('group-id'): p.get(c.get('group-id'), 0) + (c.get('value') or 0)},
|
|
244
253
|
emissions,
|
|
245
254
|
{}
|
|
246
255
|
)
|
|
@@ -271,7 +280,7 @@ def _should_run(cycle: dict):
|
|
|
271
280
|
emissions_per_group=log_as_table(emissions_per_group),
|
|
272
281
|
**_omit(seed_input, 'input'))
|
|
273
282
|
|
|
274
|
-
logShouldRun(cycle, MODEL, term_id, should_run,
|
|
283
|
+
logShouldRun(cycle, MODEL, term_id, should_run, methodTier=TIER, model_key=MODEL_KEY)
|
|
275
284
|
|
|
276
285
|
return should_run, total_economicValueShare, total_yield, grouped_seed_inputs, grouped_emissions
|
|
277
286
|
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from hestia_earth.schema import TermTermType
|
|
2
|
+
|
|
3
|
+
from hestia_earth.models.utils.term import get_lookup_value
|
|
4
|
+
from . import MODEL
|
|
5
|
+
|
|
6
|
+
IPCC_LAND_USE_CATEGORY_ANNUAL = "Annual crops"
|
|
7
|
+
IPCC_LAND_USE_CATEGORY_PERENNIAL = "Perennial crops"
|
|
8
|
+
TOTAL_CROPLAND = "Cropland"
|
|
9
|
+
ANNUAL_CROPLAND = "Arable land"
|
|
10
|
+
FOREST_LAND = "Forest land"
|
|
11
|
+
OTHER_LAND = "Other land"
|
|
12
|
+
PERMANENT_CROPLAND = "Permanent crops"
|
|
13
|
+
PERMANENT_PASTURE = "Permanent meadows and pastures"
|
|
14
|
+
TOTAL_AGRICULTURAL_CHANGE = "Total agricultural change"
|
|
15
|
+
ALL_LAND_USE_TERMS = [
|
|
16
|
+
FOREST_LAND,
|
|
17
|
+
TOTAL_CROPLAND,
|
|
18
|
+
ANNUAL_CROPLAND,
|
|
19
|
+
PERMANENT_CROPLAND,
|
|
20
|
+
PERMANENT_PASTURE,
|
|
21
|
+
OTHER_LAND
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
# Mapping from Land use terms to Management node terms.
|
|
25
|
+
# land use term: (@id, name)
|
|
26
|
+
LAND_USE_TERMS_FOR_TRANSFORMATION = {
|
|
27
|
+
FOREST_LAND: ("forest", "Forest"),
|
|
28
|
+
ANNUAL_CROPLAND: ("annualCropland", "Annual cropland"),
|
|
29
|
+
PERMANENT_CROPLAND: ("permanentCropland", "Permanent cropland"),
|
|
30
|
+
PERMANENT_PASTURE: ("permanentPasture", "Permanent pasture"),
|
|
31
|
+
OTHER_LAND: ("otherLand", OTHER_LAND) # Not used yet
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def crop_ipcc_land_use_category(
|
|
36
|
+
crop_term_id: str,
|
|
37
|
+
lookup_term_type: str = TermTermType.LANDCOVER.value
|
|
38
|
+
) -> str:
|
|
39
|
+
"""
|
|
40
|
+
Looks up the crop in the lookup.
|
|
41
|
+
Returns the IPCC_LAND_USE_CATEGORY.
|
|
42
|
+
"""
|
|
43
|
+
return get_lookup_value(
|
|
44
|
+
lookup_term={"@id": crop_term_id, "type": "Term", "termType": lookup_term_type},
|
|
45
|
+
column='IPCC_LAND_USE_CATEGORY',
|
|
46
|
+
model=MODEL,
|
|
47
|
+
term={"@id": crop_term_id, "type": "Term", "termType": lookup_term_type}
|
|
48
|
+
)
|
|
@@ -4,9 +4,11 @@ Emissions
|
|
|
4
4
|
Creates an [Indicator](https://hestia.earth/schema/Indicator) for every [Emission](https://hestia.earth/schema/Emission)
|
|
5
5
|
contained within the [ImpactAssesment.cycle](https://hestia.earth/schema/ImpactAssessment#cycle).
|
|
6
6
|
It does this by dividing the Emission amount by the Product amount, and applying an allocation between co-products.
|
|
7
|
+
Note: for any Emission in the system boundary that does not exist in the Cycle, it will log the model as failed,
|
|
8
|
+
so that we know the Emission is missing.
|
|
7
9
|
"""
|
|
8
|
-
|
|
9
10
|
from hestia_earth.utils.tools import list_sum
|
|
11
|
+
from hestia_earth.utils.emission import cycle_emissions_in_system_boundary
|
|
10
12
|
|
|
11
13
|
from hestia_earth.models.log import logRequirements, logShouldRun
|
|
12
14
|
from hestia_earth.models.utils.impact_assessment import get_product, convert_value_from_cycle
|
|
@@ -38,10 +40,12 @@ RETURNS = {
|
|
|
38
40
|
MODEL_KEY = 'emissions'
|
|
39
41
|
|
|
40
42
|
|
|
41
|
-
def _indicator(product: dict):
|
|
43
|
+
def _indicator(impact_assessment: dict, product: dict):
|
|
42
44
|
def run(emission: dict):
|
|
43
45
|
term_id = emission.get('term', {}).get('@id')
|
|
44
|
-
value = convert_value_from_cycle(
|
|
46
|
+
value = convert_value_from_cycle(
|
|
47
|
+
impact_assessment, product, list_sum(emission.get('value', [0])), model=MODEL, term_id=term_id
|
|
48
|
+
)
|
|
45
49
|
|
|
46
50
|
indicator = _new_indicator(emission.get('term', {}), emission.get('methodModel'))
|
|
47
51
|
indicator['value'] = value
|
|
@@ -57,13 +61,23 @@ def _indicator(product: dict):
|
|
|
57
61
|
return run
|
|
58
62
|
|
|
59
63
|
|
|
64
|
+
def _log_missing_emissions(impact_assessment: dict, emissions: list):
|
|
65
|
+
term_ids = cycle_emissions_in_system_boundary(cycle=impact_assessment.get('cycle', {}))
|
|
66
|
+
emission_ids = list(map(lambda e: e.get('term', {}).get('@id'), emissions))
|
|
67
|
+
missing_term_ids = [term_id for term_id in term_ids if term_id not in emission_ids]
|
|
68
|
+
for term_id in missing_term_ids:
|
|
69
|
+
logRequirements(impact_assessment, model=MODEL, term=term_id,
|
|
70
|
+
present_in_cycle=False)
|
|
71
|
+
logShouldRun(impact_assessment, MODEL, term_id, should_run=False)
|
|
72
|
+
|
|
73
|
+
|
|
60
74
|
def _should_run_emission(impact_assessment: dict):
|
|
61
75
|
product = get_product(impact_assessment)
|
|
62
76
|
|
|
63
77
|
def exec(emission: dict):
|
|
64
78
|
term_id = emission.get('term', {}).get('@id')
|
|
65
79
|
has_value = convert_value_from_cycle(
|
|
66
|
-
product, list_sum(emission.get('value', [0])), model=MODEL, term_id=term_id
|
|
80
|
+
impact_assessment, product, list_sum(emission.get('value', [0])), model=MODEL, term_id=term_id
|
|
67
81
|
) is not None
|
|
68
82
|
not_deleted = emission.get('deleted', False) is not True
|
|
69
83
|
|
|
@@ -91,4 +105,5 @@ def run(impact_assessment: dict):
|
|
|
91
105
|
should_run, product = _should_run(impact_assessment)
|
|
92
106
|
emissions = impact_assessment.get('cycle', {}).get(MODEL_KEY, []) if should_run else []
|
|
93
107
|
emissions = list(filter(_should_run_emission(impact_assessment), emissions))
|
|
94
|
-
|
|
108
|
+
_log_missing_emissions(impact_assessment, emissions)
|
|
109
|
+
return list(map(_indicator(impact_assessment, product), emissions))
|