hestia-earth-models 0.65.4__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 +40 -24
- hestia_earth/models/aware/scarcityWeightedWaterUse.py +1 -1
- 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/electricityFuel.py +4 -2
- 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 +35 -21
- 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/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/utils.py +3 -1
- hestia_earth/models/mocking/search-results.json +779 -763
- 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/utils/blank_node.py +28 -0
- hestia_earth/models/utils/fuel.py +4 -1
- hestia_earth/models/utils/impact_assessment.py +7 -5
- hestia_earth/models/utils/pesticideAI.py +1 -0
- hestia_earth/models/version.py +1 -1
- {hestia_earth_models-0.65.4.dist-info → hestia_earth_models-0.65.5.dist-info}/METADATA +2 -2
- {hestia_earth_models-0.65.4.dist-info → hestia_earth_models-0.65.5.dist-info}/RECORD +47 -41
- 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.4.dist-info → hestia_earth_models-0.65.5.dist-info}/LICENSE +0 -0
- {hestia_earth_models-0.65.4.dist-info → hestia_earth_models-0.65.5.dist-info}/WHEEL +0 -0
- {hestia_earth_models-0.65.4.dist-info → hestia_earth_models-0.65.5.dist-info}/top_level.txt +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Fuel and Electricity
|
|
3
3
|
|
|
4
|
-
This model calculates fuel and electricity data from the number of hours each machine is
|
|
4
|
+
This model calculates fuel and electricity data from the number of hours each machine is used for.
|
|
5
5
|
"""
|
|
6
6
|
from hestia_earth.schema import TermTermType
|
|
7
7
|
from hestia_earth.utils.model import filter_list_term_type
|
|
@@ -68,43 +68,59 @@ def _run_operation(cycle: dict):
|
|
|
68
68
|
return exec
|
|
69
69
|
|
|
70
70
|
|
|
71
|
-
def
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
values = practice.get('value', [])
|
|
76
|
-
value = list_sum(values) if all([not isinstance(v, str) for v in values]) else 0 # str allowed for Practice
|
|
77
|
-
has_value = value > 0
|
|
71
|
+
def _operation_data(practice: dict):
|
|
72
|
+
term = practice.get('term', {})
|
|
73
|
+
values = practice.get('value', [])
|
|
74
|
+
value = list_sum(values) if all([not isinstance(v, str) for v in values]) else None # str allowed for Practice
|
|
78
75
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
has_lookup_value = len(inputs) > 0
|
|
76
|
+
coeffs = get_lookup_value(term, LOOKUPS['operation'], model=MODEL, model_key=MODEL_KEY)
|
|
77
|
+
values = non_empty_list(coeffs.split(';')) if coeffs else []
|
|
78
|
+
inputs = [{'id': c.split(':')[0], 'value': float(c.split(':')[1])} for c in values]
|
|
83
79
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
return [{'term': term, 'value': value, 'input': input} for input in inputs] if should_run else []
|
|
91
|
-
return exec
|
|
80
|
+
return [{
|
|
81
|
+
'term': term,
|
|
82
|
+
'value': value,
|
|
83
|
+
'input': input,
|
|
84
|
+
'dates': ';'.join(practice.get('dates', []))
|
|
85
|
+
} for input in inputs]
|
|
92
86
|
|
|
93
87
|
|
|
94
88
|
def _should_run(cycle: dict):
|
|
95
89
|
is_incomplete = not cycle.get('completeness', {}).get('electricityFuel', False)
|
|
96
90
|
operations = filter_list_term_type(cycle.get('practices', []), TermTermType.OPERATION)
|
|
97
|
-
|
|
98
|
-
|
|
91
|
+
|
|
92
|
+
operations = flatten(map(_operation_data, operations))
|
|
93
|
+
term_ids = list(set(non_empty_list(map(lambda v: v.get('term', {}).get('@id'), operations))))
|
|
94
|
+
|
|
95
|
+
valid_operations = [v for v in operations if (v.get('value') or 0) > 0]
|
|
96
|
+
has_operations = len(valid_operations) > 0
|
|
97
|
+
|
|
98
|
+
# group operations by term to show in logs
|
|
99
|
+
grouped_operations = group_by(operations, ['term.@id'])
|
|
100
|
+
|
|
101
|
+
for term_id, operations in grouped_operations.items():
|
|
102
|
+
logs = [
|
|
103
|
+
{
|
|
104
|
+
'value': operation.get('value'),
|
|
105
|
+
'dates': operation.get('dates'),
|
|
106
|
+
'input-id': operation.get('input', {}).get('@id'),
|
|
107
|
+
}
|
|
108
|
+
for operation in operations
|
|
109
|
+
]
|
|
110
|
+
logRequirements(cycle, model=MODEL, term=term_id, model_key=MODEL_KEY,
|
|
111
|
+
details=log_as_table(logs))
|
|
112
|
+
|
|
113
|
+
should_run = any([(v.get('value') or 0) > 0 for v in operations])
|
|
114
|
+
logShouldRun(cycle, MODEL, term_id, should_run, model_key=MODEL_KEY)
|
|
99
115
|
|
|
100
116
|
logRequirements(cycle, model=MODEL, model_key=MODEL_KEY,
|
|
101
117
|
is_term_type_electricityFuel_incomplete=is_incomplete,
|
|
102
118
|
has_operations=has_operations,
|
|
103
|
-
operations=';'.join(
|
|
119
|
+
operations=';'.join(term_ids))
|
|
104
120
|
|
|
105
121
|
should_run = all([is_incomplete, has_operations])
|
|
106
122
|
logShouldRun(cycle, MODEL, None, should_run, model_key=MODEL_KEY)
|
|
107
|
-
return should_run,
|
|
123
|
+
return should_run, valid_operations
|
|
108
124
|
|
|
109
125
|
|
|
110
126
|
def run(cycle: dict):
|
|
@@ -89,7 +89,7 @@ def _run(impact_assessment: dict):
|
|
|
89
89
|
_get_factor_from_basinId(site, aware_id) if aware_id else None
|
|
90
90
|
) or _get_factor_from_region(impact_assessment, site)
|
|
91
91
|
inputs_value = convert_value_from_cycle(
|
|
92
|
-
product, sum_input_impacts(cycle.get('inputs', []), TERM_ID), model=MODEL, term_id=TERM_ID
|
|
92
|
+
impact_assessment, product, sum_input_impacts(cycle.get('inputs', []), TERM_ID), model=MODEL, term_id=TERM_ID
|
|
93
93
|
)
|
|
94
94
|
|
|
95
95
|
logRequirements(impact_assessment, model=MODEL, term=TERM_ID,
|
|
@@ -73,7 +73,7 @@ def _run(impact_assessment: dict):
|
|
|
73
73
|
land_occupation_m2_kg = land_occupation_per_kg(MODEL, TERM_ID, cycle, site, product)
|
|
74
74
|
factor = get_region_factor(TERM_ID, impact_assessment, LOOKUP_SUFFIX, 'medium_intensity')
|
|
75
75
|
inputs_value = convert_value_from_cycle(
|
|
76
|
-
product, sum_input_impacts(cycle.get('inputs', []), TERM_ID), model=MODEL, term_id=TERM_ID
|
|
76
|
+
impact_assessment, product, sum_input_impacts(cycle.get('inputs', []), TERM_ID), model=MODEL, term_id=TERM_ID
|
|
77
77
|
)
|
|
78
78
|
logRequirements(impact_assessment, model=MODEL, term=TERM_ID,
|
|
79
79
|
landOccupation=land_occupation_m2_kg,
|
|
@@ -68,7 +68,7 @@ def _run(impact_assessment: dict):
|
|
|
68
68
|
landTransformation = _value(impact_assessment, _TRANSFORMATION_TERM_ID)
|
|
69
69
|
region_factor = get_region_factor(TERM_ID, impact_assessment, _LOOKUP_SUFFIX, 'medium_intensity')
|
|
70
70
|
inputs_value = convert_value_from_cycle(
|
|
71
|
-
product, sum_input_impacts(cycle.get('inputs', []), TERM_ID), model=MODEL, term_id=TERM_ID
|
|
71
|
+
impact_assessment, product, sum_input_impacts(cycle.get('inputs', []), TERM_ID), model=MODEL, term_id=TERM_ID
|
|
72
72
|
)
|
|
73
73
|
logRequirements(impact_assessment, model=MODEL, term=TERM_ID,
|
|
74
74
|
landTransformation=landTransformation,
|
|
@@ -57,7 +57,7 @@ def run(impact_assessment: dict):
|
|
|
57
57
|
cycle = impact_assessment.get('cycle', {})
|
|
58
58
|
product = get_product(impact_assessment)
|
|
59
59
|
inputs_value = convert_value_from_cycle(
|
|
60
|
-
product, sum_input_impacts(cycle.get('inputs', []), TERM_ID), model=MODEL, term_id=TERM_ID
|
|
60
|
+
impact_assessment, product, sum_input_impacts(cycle.get('inputs', []), TERM_ID), model=MODEL, term_id=TERM_ID
|
|
61
61
|
)
|
|
62
62
|
value = sum_values([landUseEffects, inputs_value])
|
|
63
63
|
logRequirements(impact_assessment, model=MODEL, term=TERM_ID,
|
|
@@ -40,7 +40,7 @@ def _lookup_value(practice: dict, lookup_name: str):
|
|
|
40
40
|
def _practice_value(practice: dict):
|
|
41
41
|
term = practice.get('term', {})
|
|
42
42
|
fuel_use = _lookup_value(practice, LOOKUPS['operation'][0])
|
|
43
|
-
return {'id': term.get('@id'), 'fuel_use': fuel_use}
|
|
43
|
+
return {'id': term.get('@id'), 'value': practice.get('value'), 'fuel_use': fuel_use}
|
|
44
44
|
|
|
45
45
|
|
|
46
46
|
def run(cycle: dict):
|
|
@@ -55,6 +55,8 @@ def run(cycle: dict):
|
|
|
55
55
|
term_type_operation_complete=operation_complete,
|
|
56
56
|
values=log_as_table(practices_values))
|
|
57
57
|
|
|
58
|
-
is_complete = all([operation_complete] + [
|
|
58
|
+
is_complete = all([operation_complete] + [
|
|
59
|
+
all([p.get('fuel_use'), p.get('value') is not None]) for p in practices_values
|
|
60
|
+
])
|
|
59
61
|
|
|
60
62
|
return is_complete
|
|
@@ -31,8 +31,8 @@ RETURNS = {
|
|
|
31
31
|
}
|
|
32
32
|
TERM_ID = 'precipitationAnnual'
|
|
33
33
|
EE_PARAMS = {
|
|
34
|
-
'collection': 'ECMWF/
|
|
35
|
-
'band_name': '
|
|
34
|
+
'collection': 'ECMWF/ERA5_LAND/MONTHLY_AGGR',
|
|
35
|
+
'band_name': 'total_precipitation_sum',
|
|
36
36
|
'ee_type': 'raster',
|
|
37
37
|
'reducer': 'mean',
|
|
38
38
|
'reducer_annual': 'sum'
|
|
@@ -27,8 +27,8 @@ TERM_ID = 'precipitationLongTermAnnualMean'
|
|
|
27
27
|
START_DATE = '1979-01-01'
|
|
28
28
|
END_DATE = '2020-12-31'
|
|
29
29
|
EE_PARAMS = {
|
|
30
|
-
'collection': 'ECMWF/
|
|
31
|
-
'band_name': '
|
|
30
|
+
'collection': 'ECMWF/ERA5_LAND/MONTHLY_AGGR',
|
|
31
|
+
'band_name': 'total_precipitation_sum',
|
|
32
32
|
'ee_type': 'raster',
|
|
33
33
|
'reducer': 'mean',
|
|
34
34
|
'reducer_annual': 'sum',
|
|
@@ -31,8 +31,8 @@ RETURNS = {
|
|
|
31
31
|
}
|
|
32
32
|
TERM_ID = 'precipitationMonthly'
|
|
33
33
|
EE_PARAMS = {
|
|
34
|
-
'collection': 'ECMWF/
|
|
35
|
-
'band_name': '
|
|
34
|
+
'collection': 'ECMWF/ERA5_LAND/MONTHLY_AGGR',
|
|
35
|
+
'band_name': 'total_precipitation_sum',
|
|
36
36
|
'ee_type': 'raster',
|
|
37
37
|
'reducer': 'mean',
|
|
38
38
|
'reducer_annual': 'sum'
|
|
@@ -31,8 +31,8 @@ RETURNS = {
|
|
|
31
31
|
}
|
|
32
32
|
TERM_ID = 'temperatureAnnual'
|
|
33
33
|
EE_PARAMS = {
|
|
34
|
-
'collection': 'ECMWF/
|
|
35
|
-
'band_name': '
|
|
34
|
+
'collection': 'ECMWF/ERA5_LAND/MONTHLY_AGGR',
|
|
35
|
+
'band_name': 'temperature_2m',
|
|
36
36
|
'ee_type': 'raster',
|
|
37
37
|
'reducer': 'mean',
|
|
38
38
|
'reducer_annual': 'mean'
|
|
@@ -27,8 +27,8 @@ TERM_ID = 'temperatureLongTermAnnualMean'
|
|
|
27
27
|
START_DATE = '1979-01-01'
|
|
28
28
|
END_DATE = '2020-12-31'
|
|
29
29
|
EE_PARAMS = {
|
|
30
|
-
'collection': 'ECMWF/
|
|
31
|
-
'band_name': '
|
|
30
|
+
'collection': 'ECMWF/ERA5_LAND/MONTHLY_AGGR',
|
|
31
|
+
'band_name': 'temperature_2m',
|
|
32
32
|
'ee_type': 'raster',
|
|
33
33
|
'reducer': 'mean',
|
|
34
34
|
'reducer_annual': 'mean',
|
|
@@ -31,8 +31,8 @@ RETURNS = {
|
|
|
31
31
|
}
|
|
32
32
|
TERM_ID = 'temperatureMonthly'
|
|
33
33
|
EE_PARAMS = {
|
|
34
|
-
'collection': 'ECMWF/
|
|
35
|
-
'band_name': '
|
|
34
|
+
'collection': 'ECMWF/ERA5_LAND/MONTHLY_AGGR',
|
|
35
|
+
'band_name': 'temperature_2m',
|
|
36
36
|
'ee_type': 'raster',
|
|
37
37
|
'reducer': 'mean',
|
|
38
38
|
'reducer_annual': 'mean'
|
|
@@ -17,6 +17,20 @@ from hestia_earth.utils.tools import safe_parse_float, to_precision, non_empty_v
|
|
|
17
17
|
from hestia_earth.models.log import logRequirements, log_as_table, logShouldRun
|
|
18
18
|
from hestia_earth.models.utils.management import _new_management
|
|
19
19
|
from hestia_earth.models.utils.term import get_lookup_value
|
|
20
|
+
from .utils import (
|
|
21
|
+
IPCC_LAND_USE_CATEGORY_ANNUAL,
|
|
22
|
+
IPCC_LAND_USE_CATEGORY_PERENNIAL,
|
|
23
|
+
LAND_USE_TERMS_FOR_TRANSFORMATION,
|
|
24
|
+
ANNUAL_CROPLAND,
|
|
25
|
+
PERMANENT_CROPLAND,
|
|
26
|
+
FOREST_LAND,
|
|
27
|
+
OTHER_LAND,
|
|
28
|
+
PERMANENT_PASTURE,
|
|
29
|
+
TOTAL_CROPLAND,
|
|
30
|
+
TOTAL_AGRICULTURAL_CHANGE,
|
|
31
|
+
ALL_LAND_USE_TERMS,
|
|
32
|
+
crop_ipcc_land_use_category,
|
|
33
|
+
)
|
|
20
34
|
from . import MODEL
|
|
21
35
|
|
|
22
36
|
REQUIREMENTS = {
|
|
@@ -69,30 +83,6 @@ LOOKUPS = {
|
|
|
69
83
|
MODEL_KEY = 'landCover'
|
|
70
84
|
|
|
71
85
|
LAND_AREA = LOOKUPS["region-faostatArea"][3]
|
|
72
|
-
TOTAL_CROPLAND = "Cropland"
|
|
73
|
-
ANNUAL_CROPLAND = "Arable land"
|
|
74
|
-
FOREST_LAND = "Forest land"
|
|
75
|
-
OTHER_LAND = "Other land"
|
|
76
|
-
PERMANENT_CROPLAND = "Permanent crops"
|
|
77
|
-
PERMANENT_PASTURE = "Permanent meadows and pastures"
|
|
78
|
-
TOTAL_AGRICULTURAL_CHANGE = "Total agricultural change"
|
|
79
|
-
ALL_LAND_USE_TERMS = [
|
|
80
|
-
FOREST_LAND,
|
|
81
|
-
TOTAL_CROPLAND,
|
|
82
|
-
ANNUAL_CROPLAND,
|
|
83
|
-
PERMANENT_CROPLAND,
|
|
84
|
-
PERMANENT_PASTURE,
|
|
85
|
-
OTHER_LAND
|
|
86
|
-
]
|
|
87
|
-
# Mapping from Land use terms to Management node terms.
|
|
88
|
-
# land use term: (@id, name)
|
|
89
|
-
LAND_USE_TERMS_FOR_TRANSFORMATION = {
|
|
90
|
-
FOREST_LAND: ("forest", "Forest"),
|
|
91
|
-
ANNUAL_CROPLAND: ("annualCropland", "Annual cropland"),
|
|
92
|
-
PERMANENT_CROPLAND: ("permanentCropland", "Permanent cropland"),
|
|
93
|
-
PERMANENT_PASTURE: ("permanentPasture", "Permanent pasture"),
|
|
94
|
-
OTHER_LAND: ("otherLand", OTHER_LAND) # Not used yet
|
|
95
|
-
}
|
|
96
86
|
SITE_TYPES = {
|
|
97
87
|
SiteSiteType.CROPLAND.value,
|
|
98
88
|
SiteSiteType.FOREST.value,
|
|
@@ -100,8 +90,6 @@ SITE_TYPES = {
|
|
|
100
90
|
SiteSiteType.PERMANENT_PASTURE.value
|
|
101
91
|
}
|
|
102
92
|
DEFAULT_WINDOW_IN_YEARS = 20
|
|
103
|
-
IPCC_LAND_USE_CATEGORY_ANNUAL = "Annual crops"
|
|
104
|
-
IPCC_LAND_USE_CATEGORY_PERENNIAL = "Perennial crops"
|
|
105
93
|
OUTPUT_SIGNIFICANT_DIGITS = 3
|
|
106
94
|
|
|
107
95
|
|
|
@@ -137,22 +125,6 @@ def _lookup_land_use_type(nodes: list) -> str:
|
|
|
137
125
|
)
|
|
138
126
|
|
|
139
127
|
|
|
140
|
-
def _crop_ipcc_land_use_category(
|
|
141
|
-
crop_term_id: str,
|
|
142
|
-
lookup_term_type: str = TermTermType.LANDCOVER.value
|
|
143
|
-
) -> str:
|
|
144
|
-
"""
|
|
145
|
-
Looks up the crop in the lookup.
|
|
146
|
-
Returns the IPCC_LAND_USE_CATEGORY.
|
|
147
|
-
"""
|
|
148
|
-
return get_lookup_value(
|
|
149
|
-
lookup_term={"@id": crop_term_id, "type": "Term", "termType": lookup_term_type},
|
|
150
|
-
column=LOOKUPS.get("crop")[1],
|
|
151
|
-
model=MODEL,
|
|
152
|
-
term={"@id": crop_term_id, "type": "Term", "termType": lookup_term_type}
|
|
153
|
-
)
|
|
154
|
-
|
|
155
|
-
|
|
156
128
|
def get_changes(country_id: str, end_year: int) -> dict:
|
|
157
129
|
"""
|
|
158
130
|
For each entry in ALL_LAND_USE_TERMS, creates a key: value in output dictionary, also TOTAL
|
|
@@ -399,7 +371,7 @@ def _get_complete_faostat_to_crop_mapping() -> dict:
|
|
|
399
371
|
get_table_value(lookup, 'termid', crop_term_id, column_name("cropGroupingFaostatArea"))
|
|
400
372
|
)
|
|
401
373
|
if key:
|
|
402
|
-
mappings[key].append(
|
|
374
|
+
mappings[key].append(crop_ipcc_land_use_category(crop_term_id=crop_term_id, lookup_term_type="crop"))
|
|
403
375
|
return {
|
|
404
376
|
fao_name: max(set(crop_terms), key=crop_terms.count)
|
|
405
377
|
for fao_name, crop_terms in mappings.items()
|
|
@@ -420,6 +392,18 @@ def _get_harvested_area(country_id: str, year: int, faostat_name: str) -> float:
|
|
|
420
392
|
)
|
|
421
393
|
|
|
422
394
|
|
|
395
|
+
def _get_term_id_for_crop(nodes: set, land_type: str) -> str:
|
|
396
|
+
"""Use the original crop term id for permanent/perennial crops and the land use id for other types."""
|
|
397
|
+
result = next(
|
|
398
|
+
(node for node in nodes if crop_ipcc_land_use_category(node[0]) == IPCC_LAND_USE_CATEGORY_PERENNIAL), None
|
|
399
|
+
)
|
|
400
|
+
return (
|
|
401
|
+
# Take first perennial crop - not multi-cropping
|
|
402
|
+
result[0] if land_type == PERMANENT_CROPLAND and result else
|
|
403
|
+
LAND_USE_TERMS_FOR_TRANSFORMATION[land_type][0]
|
|
404
|
+
)
|
|
405
|
+
|
|
406
|
+
|
|
423
407
|
def _run_make_management_nodes(existing_nodes: list, percentage_transformed_from: dict, start_year: int) -> list:
|
|
424
408
|
"""Creates a list of new management nodes, excluding any dates matching existing ones."""
|
|
425
409
|
existing_nodes_set = {
|
|
@@ -435,7 +419,8 @@ def _run_make_management_nodes(existing_nodes: list, percentage_transformed_from
|
|
|
435
419
|
"percentage": 0 if ratio == -0.0 else to_precision(
|
|
436
420
|
number=ratio * 100,
|
|
437
421
|
digits=OUTPUT_SIGNIFICANT_DIGITS
|
|
438
|
-
)
|
|
422
|
+
),
|
|
423
|
+
"term_id": _get_term_id_for_crop(existing_nodes_set, land_type=land_type)
|
|
439
424
|
}
|
|
440
425
|
for land_type, ratio in percentage_transformed_from.items()
|
|
441
426
|
]
|
|
@@ -443,7 +428,7 @@ def _run_make_management_nodes(existing_nodes: list, percentage_transformed_from
|
|
|
443
428
|
|
|
444
429
|
return [
|
|
445
430
|
_management(
|
|
446
|
-
term_id=
|
|
431
|
+
term_id=value.get("term_id"),
|
|
447
432
|
value=value.get("percentage"),
|
|
448
433
|
start_date=value.get("land_management_key")[1],
|
|
449
434
|
end_date=value.get("land_management_key")[2]
|
|
@@ -698,7 +683,7 @@ def _should_run_historical_land_use_change_single_crop(
|
|
|
698
683
|
sum_of_site_areas_is_100
|
|
699
684
|
]
|
|
700
685
|
)
|
|
701
|
-
logShouldRun(site, MODEL, term
|
|
686
|
+
logShouldRun(site, MODEL, term.get("@id"), should_run, model_key=MODEL_KEY)
|
|
702
687
|
|
|
703
688
|
return should_run, site_area
|
|
704
689
|
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Creates an [emissionsResourceUse](https://hestia.earth/schema/Emission) for every landCover land transformation.
|
|
3
|
+
contained within the [ImpactAssesment.cycle](https://hestia.earth/schema/ImpactAssessment#cycle), averaged over the last
|
|
4
|
+
100 years.
|
|
5
|
+
|
|
6
|
+
It does this by multiplying the land occupation during the cycle by the
|
|
7
|
+
[Site](https://www-staging.hestia.earth/schema/Site) area 100 years ago and dividing by 100.
|
|
8
|
+
|
|
9
|
+
Land transformation from [land type] 100 years =
|
|
10
|
+
(Land occupation, during Cycle * Site Percentage Area 100 years ago [land type] / 100) / 100
|
|
11
|
+
"""
|
|
12
|
+
from .resourceUse_utils import run_resource_use
|
|
13
|
+
|
|
14
|
+
REQUIREMENTS = {
|
|
15
|
+
"ImpactAssessment": {
|
|
16
|
+
"Site": {
|
|
17
|
+
"management": [{"@type": "Management", "value": ">=0", "term.termType": "landCover", "endDate": ""}]
|
|
18
|
+
},
|
|
19
|
+
"emissionsResourceUse": [
|
|
20
|
+
{
|
|
21
|
+
"@type": "Indicator",
|
|
22
|
+
"term.@id": "landOccupationDuringCycle",
|
|
23
|
+
"landCover": {
|
|
24
|
+
"@type": "Term",
|
|
25
|
+
"termType": "landCover"
|
|
26
|
+
},
|
|
27
|
+
"value": ">=0"
|
|
28
|
+
}
|
|
29
|
+
],
|
|
30
|
+
"endDate": ""
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
RETURNS = {
|
|
34
|
+
"Indicator": [{
|
|
35
|
+
"value": "",
|
|
36
|
+
"landCover": "",
|
|
37
|
+
"previousLandCover": ""
|
|
38
|
+
}]
|
|
39
|
+
}
|
|
40
|
+
TERM_ID = 'landTransformation100YearAverageDuringCycle'
|
|
41
|
+
_HISTORIC_DATE_OFFSET = 100
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def run(impact_assessment: dict):
|
|
45
|
+
return run_resource_use(
|
|
46
|
+
impact_assessment=impact_assessment,
|
|
47
|
+
historic_date_offset=_HISTORIC_DATE_OFFSET,
|
|
48
|
+
term_id=TERM_ID
|
|
49
|
+
)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Creates an [emissionsResourceUse](https://hestia.earth/schema/Emission) for every landCover land transformation.
|
|
3
|
+
contained within the [ImpactAssesment.cycle](https://hestia.earth/schema/ImpactAssessment#cycle), averaged over the last
|
|
4
|
+
20 years.
|
|
5
|
+
|
|
6
|
+
It does this by multiplying the land occupation during the cycle by the
|
|
7
|
+
[Site](https://www-staging.hestia.earth/schema/Site) area 20 years ago and dividing by 20.
|
|
8
|
+
|
|
9
|
+
Land transformation from [land type] 20 years =
|
|
10
|
+
(Land occupation, during Cycle * Site Percentage Area 20 years ago [land type] / 100) / 20
|
|
11
|
+
"""
|
|
12
|
+
from .resourceUse_utils import run_resource_use
|
|
13
|
+
|
|
14
|
+
REQUIREMENTS = {
|
|
15
|
+
"ImpactAssessment": {
|
|
16
|
+
"Site": {
|
|
17
|
+
"management": [{"@type": "Management", "value": ">=0", "term.termType": "landCover", "endDate": ""}]
|
|
18
|
+
},
|
|
19
|
+
"emissionsResourceUse": [
|
|
20
|
+
{
|
|
21
|
+
"@type": "Indicator",
|
|
22
|
+
"term.@id": "landOccupationDuringCycle",
|
|
23
|
+
"landCover": {
|
|
24
|
+
"@type": "Term",
|
|
25
|
+
"termType": "landCover"
|
|
26
|
+
},
|
|
27
|
+
"value": ">=0"
|
|
28
|
+
}
|
|
29
|
+
],
|
|
30
|
+
"endDate": ""
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
RETURNS = {
|
|
34
|
+
"Indicator": [{
|
|
35
|
+
"value": "",
|
|
36
|
+
"landCover": "",
|
|
37
|
+
"previousLandCover": ""
|
|
38
|
+
}]
|
|
39
|
+
}
|
|
40
|
+
TERM_ID = 'landTransformation20YearAverageDuringCycle'
|
|
41
|
+
_HISTORIC_DATE_OFFSET = 20
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def run(impact_assessment: dict):
|
|
45
|
+
return run_resource_use(
|
|
46
|
+
impact_assessment=impact_assessment,
|
|
47
|
+
historic_date_offset=_HISTORIC_DATE_OFFSET,
|
|
48
|
+
term_id=TERM_ID
|
|
49
|
+
)
|
|
@@ -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 []
|