hestia-earth-models 0.74.3__py3-none-any.whl → 0.74.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/cml2001Baseline/abioticResourceDepletionMineralsAndMetals.py +0 -1
- hestia_earth/models/config/Cycle.json +15 -0
- hestia_earth/models/config/ImpactAssessment.json +30 -11
- hestia_earth/models/cycle/animal/input/hestiaAggregatedData.py +3 -3
- hestia_earth/models/cycle/completeness/seed.py +1 -1
- hestia_earth/models/cycle/input/hestiaAggregatedData.py +25 -16
- hestia_earth/models/data/hestiaAggregatedData/__init__.py +73 -0
- hestia_earth/models/environmentalFootprintV3_1/scarcityWeightedWaterUse.py +1 -1
- hestia_earth/models/environmentalFootprintV3_1/soilQualityIndexLandOccupation.py +5 -6
- hestia_earth/models/environmentalFootprintV3_1/soilQualityIndexLandTransformation.py +10 -13
- hestia_earth/models/fantkeEtAl2016/damageToHumanHealthParticulateMatterFormation.py +1 -1
- hestia_earth/models/hestia/landCover.py +24 -0
- hestia_earth/models/hestia/landOccupationDuringCycle.py +80 -51
- hestia_earth/models/hestia/landTransformation100YearAverageDuringCycle.py +7 -1
- hestia_earth/models/hestia/landTransformation20YearAverageDuringCycle.py +7 -1
- hestia_earth/models/hestia/resourceUse_utils.py +58 -119
- hestia_earth/models/hestia/waterSalinity.py +57 -12
- hestia_earth/models/impact_assessment/post_checks/__init__.py +3 -2
- hestia_earth/models/impact_assessment/post_checks/remove_cache_fields.py +9 -0
- hestia_earth/models/impact_assessment/pre_checks/cache_emissionsResourceUse.py +21 -0
- hestia_earth/models/impact_assessment/pre_checks/cycle.py +5 -0
- hestia_earth/models/ipcc2019/co2ToAirAboveGroundBiomassStockChange.py +6 -64
- hestia_earth/models/ipcc2019/co2ToAirBelowGroundBiomassStockChange.py +9 -87
- hestia_earth/models/ipcc2019/co2ToAirBiocharStockChange.py +140 -0
- hestia_earth/models/ipcc2019/co2ToAirCarbonStockChange_utils.py +329 -217
- hestia_earth/models/ipcc2019/co2ToAirSoilOrganicCarbonStockChange.py +10 -87
- hestia_earth/models/mocking/__init__.py +2 -2
- hestia_earth/models/mocking/mock_search.py +20 -10
- hestia_earth/models/mocking/search-results.json +1 -7679
- hestia_earth/models/pooreNemecek2018/landOccupationDuringCycle.py +8 -7
- hestia_earth/models/poschEtAl2008/terrestrialAcidificationPotentialAccumulatedExceedance.py +1 -1
- hestia_earth/models/poschEtAl2008/terrestrialEutrophicationPotentialAccumulatedExceedance.py +1 -1
- hestia_earth/models/preload_requests.py +18 -4
- hestia_earth/models/schmidt2007/utils.py +3 -3
- hestia_earth/models/utils/__init__.py +4 -1
- hestia_earth/models/utils/aggregated.py +21 -68
- hestia_earth/models/utils/cycle.py +3 -3
- hestia_earth/models/utils/impact_assessment.py +45 -41
- hestia_earth/models/utils/lookup.py +92 -67
- hestia_earth/models/version.py +1 -1
- hestia_earth/orchestrator/models/__init__.py +47 -10
- hestia_earth/orchestrator/models/transformations.py +3 -1
- hestia_earth/orchestrator/strategies/merge/__init__.py +1 -2
- hestia_earth/orchestrator/strategies/merge/merge_list.py +31 -8
- hestia_earth/orchestrator/utils.py +29 -0
- {hestia_earth_models-0.74.3.dist-info → hestia_earth_models-0.74.5.dist-info}/METADATA +2 -3
- {hestia_earth_models-0.74.3.dist-info → hestia_earth_models-0.74.5.dist-info}/RECORD +62 -55
- tests/models/cycle/animal/input/test_hestiaAggregatedData.py +3 -3
- tests/models/cycle/input/test_hestiaAggregatedData.py +9 -18
- tests/models/data/__init__.py +0 -0
- tests/models/data/test_hestiaAggregatedData.py +32 -0
- tests/models/hestia/test_landCover.py +32 -1
- tests/models/hestia/test_waterSalinity.py +16 -4
- tests/models/ipcc2019/test_co2ToAirAboveGroundBiomassStockChange.py +1 -6
- tests/models/ipcc2019/test_co2ToAirBelowGroundBiomassStockChange.py +1 -6
- tests/models/ipcc2019/test_co2ToAirBiocharStockChange.py +90 -0
- tests/models/ipcc2019/test_co2ToAirSoilOrganicCarbonStockChange.py +1 -6
- tests/models/pooreNemecek2018/test_landOccupationDuringCycle.py +1 -0
- tests/orchestrator/strategies/merge/test_merge_list.py +5 -0
- {hestia_earth_models-0.74.3.dist-info → hestia_earth_models-0.74.5.dist-info}/LICENSE +0 -0
- {hestia_earth_models-0.74.3.dist-info → hestia_earth_models-0.74.5.dist-info}/WHEEL +0 -0
- {hestia_earth_models-0.74.3.dist-info → hestia_earth_models-0.74.5.dist-info}/top_level.txt +0 -0
|
@@ -80,7 +80,6 @@ def _should_run(impact_assessment: dict) -> tuple[bool, list]:
|
|
|
80
80
|
"input-term-type": input.get('termType'),
|
|
81
81
|
"indicator-term-id": resource_indicator['term']['@id'],
|
|
82
82
|
"indicator-is-valid": _valid_resource_indicator(resource_indicator),
|
|
83
|
-
"input": input,
|
|
84
83
|
"indicator-input-is-valid": _valid_input(input),
|
|
85
84
|
"value": _node_value(resource_indicator),
|
|
86
85
|
"coefficient": get_table_value(
|
|
@@ -1264,6 +1264,21 @@
|
|
|
1264
1264
|
},
|
|
1265
1265
|
"stage": 2
|
|
1266
1266
|
},
|
|
1267
|
+
{
|
|
1268
|
+
"key": "emissions",
|
|
1269
|
+
"model": "ipcc2019",
|
|
1270
|
+
"value": "co2ToAirBiocharStockChange",
|
|
1271
|
+
"runStrategy": "add_blank_node_if_missing",
|
|
1272
|
+
"runArgs": {
|
|
1273
|
+
"runNonMeasured": true,
|
|
1274
|
+
"runNonAddedTerm": true
|
|
1275
|
+
},
|
|
1276
|
+
"mergeStrategy": "list",
|
|
1277
|
+
"mergeArgs": {
|
|
1278
|
+
"replaceThreshold": ["value", 0.01]
|
|
1279
|
+
},
|
|
1280
|
+
"stage": 2
|
|
1281
|
+
},
|
|
1267
1282
|
{
|
|
1268
1283
|
"key": "emissions",
|
|
1269
1284
|
"model": "ipcc2019",
|
|
@@ -67,18 +67,29 @@
|
|
|
67
67
|
},
|
|
68
68
|
"stage": 1
|
|
69
69
|
},
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
"
|
|
78
|
-
"replaceThreshold": ["value", 0.01]
|
|
79
|
-
},
|
|
80
|
-
"stage": 1
|
|
70
|
+
{
|
|
71
|
+
"key": "emissionsResourceUse",
|
|
72
|
+
"model": "hestia",
|
|
73
|
+
"value": "landOccupationDuringCycle",
|
|
74
|
+
"runStrategy": "always",
|
|
75
|
+
"mergeStrategy": "list",
|
|
76
|
+
"mergeArgs": {
|
|
77
|
+
"replaceThreshold": ["value", 0.01]
|
|
81
78
|
},
|
|
79
|
+
"stage": 1
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"key": "emissionsResourceUse",
|
|
83
|
+
"model": "pooreNemecek2018",
|
|
84
|
+
"value": "landOccupationDuringCycle",
|
|
85
|
+
"runStrategy": "add_blank_node_if_missing",
|
|
86
|
+
"mergeStrategy": "list",
|
|
87
|
+
"mergeArgs": {
|
|
88
|
+
"replaceThreshold": ["value", 0.01]
|
|
89
|
+
},
|
|
90
|
+
"stage": 1
|
|
91
|
+
},
|
|
92
|
+
[
|
|
82
93
|
{
|
|
83
94
|
"key": "emissionsResourceUse",
|
|
84
95
|
"model": "linkedImpactAssessment",
|
|
@@ -214,6 +225,14 @@
|
|
|
214
225
|
},
|
|
215
226
|
"stage": 1
|
|
216
227
|
},
|
|
228
|
+
{
|
|
229
|
+
"key": "cache_emissionsResourceUse",
|
|
230
|
+
"model": "impact_assessment",
|
|
231
|
+
"value": "pre_checks.cache_emissionsResourceUse",
|
|
232
|
+
"runStrategy": "always",
|
|
233
|
+
"mergeStrategy": "default",
|
|
234
|
+
"stage": 1
|
|
235
|
+
},
|
|
217
236
|
{
|
|
218
237
|
"key": "impacts",
|
|
219
238
|
"model": "ipcc2021",
|
|
@@ -34,8 +34,8 @@ MODEL_ID = 'hestiaAggregatedData'
|
|
|
34
34
|
MODEL_KEY = 'impactAssessment'
|
|
35
35
|
|
|
36
36
|
|
|
37
|
-
def _run_animal_input(cycle: dict, input: dict):
|
|
38
|
-
inputs = link_inputs_to_impact(MODEL_ID, cycle, [input])
|
|
37
|
+
def _run_animal_input(cycle: dict, animal: dict, input: dict):
|
|
38
|
+
inputs = link_inputs_to_impact(MODEL_ID, cycle, [input], animalId=animal.get('animalId'))
|
|
39
39
|
return inputs[0] if inputs else input
|
|
40
40
|
|
|
41
41
|
|
|
@@ -43,7 +43,7 @@ def _run_animal(cycle: dict, animal: dict):
|
|
|
43
43
|
return animal | {
|
|
44
44
|
'inputs': [
|
|
45
45
|
(
|
|
46
|
-
_run_animal_input(cycle, input) if should_link_input_to_impact(cycle)(input) else input
|
|
46
|
+
_run_animal_input(cycle, animal, input) if should_link_input_to_impact(cycle)(input) else input
|
|
47
47
|
) for input in animal.get('inputs', [])
|
|
48
48
|
]
|
|
49
49
|
}
|
|
@@ -36,7 +36,7 @@ def run(cycle: dict):
|
|
|
36
36
|
site_type = cycle.get('site', {}).get('siteType')
|
|
37
37
|
site_type_allowed = site_type in ALLOWED_SITE_TYPES
|
|
38
38
|
|
|
39
|
-
has_seed = find_term_match(cycle.get('inputs', []), 'seed', None)
|
|
39
|
+
has_seed = find_term_match(cycle.get('inputs', []), 'seed', None) is not None
|
|
40
40
|
|
|
41
41
|
product = find_primary_product(cycle) or {}
|
|
42
42
|
term_id = product.get('term', {}).get('@id')
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
from hestia_earth.schema import TermTermType
|
|
1
|
+
from hestia_earth.schema import TermTermType, NodeType
|
|
2
2
|
from hestia_earth.utils.model import find_primary_product, linked_node, filter_list_term_type
|
|
3
3
|
from hestia_earth.utils.tools import non_empty_list
|
|
4
4
|
|
|
5
5
|
from hestia_earth.models.log import debugValues, logRequirements, logShouldRun
|
|
6
|
+
from hestia_earth.models.data.hestiaAggregatedData import DEFAULT_COUNTRY_ID, find_closest_impact_id
|
|
6
7
|
from hestia_earth.models.utils.crop import valid_site_type
|
|
7
|
-
from hestia_earth.models.utils.term import get_lookup_value, get_generic_crop
|
|
8
|
+
from hestia_earth.models.utils.term import get_lookup_value, get_generic_crop
|
|
8
9
|
from hestia_earth.models.utils.aggregated import (
|
|
9
|
-
should_link_input_to_impact, link_inputs_to_impact,
|
|
10
|
+
should_link_input_to_impact, link_inputs_to_impact, aggregated_end_date
|
|
10
11
|
)
|
|
11
12
|
|
|
12
13
|
REQUIREMENTS = {
|
|
@@ -56,29 +57,37 @@ MODEL_KEY = 'impactAssessment'
|
|
|
56
57
|
|
|
57
58
|
|
|
58
59
|
def _run_seed(cycle: dict, primary_product: dict, seed_input: dict, product_term_id: str):
|
|
59
|
-
product = download_term(product_term_id, TermTermType.SEED)
|
|
60
60
|
country = seed_input.get('country')
|
|
61
|
+
country_id = (country or {}).get('@id')
|
|
62
|
+
|
|
63
|
+
primary_product_id = primary_product.get('term', {}).get('@id')
|
|
64
|
+
default_product_id = get_generic_crop().get('@id')
|
|
65
|
+
|
|
61
66
|
# to avoid double counting seed => aggregated impact => seed, we need to get the impact of the previous decade
|
|
62
67
|
# if the data does not exist, use the aggregated impact of generic crop instead
|
|
63
|
-
date = aggregated_end_date(cycle.get('endDate'))
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
68
|
+
date = aggregated_end_date(cycle.get('endDate')) - 10
|
|
69
|
+
|
|
70
|
+
impact_id = (
|
|
71
|
+
find_closest_impact_id(product_id=product_term_id, country_id=country_id, year=date) or
|
|
72
|
+
find_closest_impact_id(product_id=product_term_id, country_id=DEFAULT_COUNTRY_ID, year=date) or
|
|
73
|
+
find_closest_impact_id(product_id=primary_product_id, country_id=country_id, year=date) or
|
|
74
|
+
find_closest_impact_id(product_id=primary_product_id, country_id=DEFAULT_COUNTRY_ID, year=date) or
|
|
75
|
+
find_closest_impact_id(product_id=default_product_id, country_id=country_id, year=date) or
|
|
76
|
+
find_closest_impact_id(product_id=default_product_id, country_id=DEFAULT_COUNTRY_ID, year=date)
|
|
71
77
|
)
|
|
72
78
|
|
|
73
|
-
search_by_product_term_id =
|
|
74
|
-
search_by_country_id =
|
|
79
|
+
search_by_product_term_id = product_term_id or primary_product_id or default_product_id
|
|
80
|
+
search_by_country_id = country_id or DEFAULT_COUNTRY_ID
|
|
75
81
|
debugValues(cycle, model=MODEL_ID, term=seed_input.get('term', {}).get('@id'), key=MODEL_KEY,
|
|
76
82
|
search_by_product_term_id=search_by_product_term_id,
|
|
77
83
|
search_by_country_id=search_by_country_id,
|
|
78
84
|
search_by_end_date=str(date),
|
|
79
|
-
impact_assessment_id_found=
|
|
85
|
+
impact_assessment_id_found=impact_id)
|
|
80
86
|
|
|
81
|
-
return seed_input | {
|
|
87
|
+
return seed_input | {
|
|
88
|
+
MODEL_KEY: linked_node({'@type': NodeType.IMPACTASSESSMENT.value, '@id': impact_id}),
|
|
89
|
+
'impactAssessmentIsProxy': True
|
|
90
|
+
} if impact_id else None
|
|
82
91
|
|
|
83
92
|
|
|
84
93
|
def _should_run_seed(cycle: dict):
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import json
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from hestia_earth.utils.storage._s3_client import _load_from_bucket
|
|
5
|
+
from hestia_earth.utils.api import _safe_get_request
|
|
6
|
+
|
|
7
|
+
_CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
8
|
+
_FILENAME = 'hestiaAggregatedData.json'
|
|
9
|
+
FILEPATH = os.path.join(_CURRENT_DIR, _FILENAME)
|
|
10
|
+
_CACHED_DATA = {}
|
|
11
|
+
DEFAULT_COUNTRY_ID = 'region-world'
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _today(): return datetime.now().strftime('%Y-%m-%d')
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _download_data():
|
|
18
|
+
try:
|
|
19
|
+
return json.loads(_load_from_bucket('hestia-data', os.path.join('data', _FILENAME)))
|
|
20
|
+
except Exception:
|
|
21
|
+
return _safe_get_request(f"https://hestia.earth/data/{_FILENAME}")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _load_data():
|
|
25
|
+
data = None
|
|
26
|
+
|
|
27
|
+
if os.path.exists(FILEPATH):
|
|
28
|
+
with open(FILEPATH, 'r') as f:
|
|
29
|
+
data = json.load(f)
|
|
30
|
+
|
|
31
|
+
is_data_valid = data and data['date'] == _today()
|
|
32
|
+
|
|
33
|
+
if not is_data_valid:
|
|
34
|
+
data = _download_data()
|
|
35
|
+
with open(FILEPATH, 'w') as f:
|
|
36
|
+
f.write(json.dumps(data))
|
|
37
|
+
|
|
38
|
+
return data
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _get_data():
|
|
42
|
+
global _CACHED_DATA # noqa: F824
|
|
43
|
+
if not _CACHED_DATA or _CACHED_DATA['date'] != _today():
|
|
44
|
+
_CACHED_DATA = _load_data()
|
|
45
|
+
return _CACHED_DATA
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _get_closest_id(data: dict, year: int):
|
|
49
|
+
available_years = [int(y) for y in data.keys() if int(y) <= year]
|
|
50
|
+
return data[str(sorted(available_years, reverse=True)[0])] if available_years else None
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def find_closest_impact_id(product_id: str, country_id: str, year: int):
|
|
54
|
+
"""
|
|
55
|
+
Find the `@id` of the closest ImpactAssessment to the target year.
|
|
56
|
+
|
|
57
|
+
Parameters
|
|
58
|
+
----------
|
|
59
|
+
product_id : str
|
|
60
|
+
The `@id` of the product (Term).
|
|
61
|
+
country_id : str
|
|
62
|
+
The `@id` of the country (Term).
|
|
63
|
+
year : int
|
|
64
|
+
The target year.
|
|
65
|
+
|
|
66
|
+
Returns
|
|
67
|
+
-------
|
|
68
|
+
str
|
|
69
|
+
The `@id` as a string if found.
|
|
70
|
+
"""
|
|
71
|
+
data = _get_data()
|
|
72
|
+
values = data.get(product_id, {}).get(country_id, {})
|
|
73
|
+
return _get_closest_id(data=values, year=year)
|
|
@@ -33,7 +33,7 @@ def _indicator(value: float):
|
|
|
33
33
|
|
|
34
34
|
def run(impact_assessment: dict):
|
|
35
35
|
value = impact_country_value(MODEL, TERM_ID, impact_assessment, f"{list(LOOKUPS.keys())[0]}.csv",
|
|
36
|
-
|
|
36
|
+
default_world_value=True)
|
|
37
37
|
logRequirements(impact_assessment, model=MODEL, term=TERM_ID,
|
|
38
38
|
value=value)
|
|
39
39
|
logShouldRun(impact_assessment, MODEL, TERM_ID, value is not None)
|
|
@@ -7,7 +7,7 @@ from hestia_earth.utils.tools import list_sum
|
|
|
7
7
|
from hestia_earth.models.log import logRequirements, logShouldRun, log_as_table
|
|
8
8
|
from hestia_earth.models.utils.indicator import _new_indicator
|
|
9
9
|
from hestia_earth.models.utils.landCover import get_pef_grouping
|
|
10
|
-
from hestia_earth.models.utils.lookup import
|
|
10
|
+
from hestia_earth.models.utils.lookup import _node_value, get_region_lookup_value
|
|
11
11
|
from . import MODEL
|
|
12
12
|
from ..utils.impact_assessment import get_country_id
|
|
13
13
|
|
|
@@ -68,7 +68,6 @@ def _should_run(impact_assessment: dict) -> Tuple[bool, list]:
|
|
|
68
68
|
'area-by-year-is-valid': _node_value(indicator) is not None and _node_value(
|
|
69
69
|
indicator) >= 0,
|
|
70
70
|
'area-unit-is-valid': indicator.get('term', {}).get("units") == "m2*year",
|
|
71
|
-
'used-country': fallback_country(get_country_id(impact_assessment, blank_node=indicator), [LOOKUP]),
|
|
72
71
|
'pef-grouping': get_pef_grouping(indicator.get('landCover', {}).get("@id"))
|
|
73
72
|
|
|
74
73
|
} for indicator in land_occupation_indicators]
|
|
@@ -79,10 +78,10 @@ def _should_run(impact_assessment: dict) -> Tuple[bool, list]:
|
|
|
79
78
|
model=MODEL,
|
|
80
79
|
term=TERM_ID,
|
|
81
80
|
lookup_name=LOOKUP,
|
|
82
|
-
term_id=indicator['
|
|
83
|
-
column=indicator['pef-grouping']
|
|
84
|
-
|
|
85
|
-
|
|
81
|
+
term_id=indicator['country-id'],
|
|
82
|
+
column=indicator['pef-grouping'],
|
|
83
|
+
fallback_world=True
|
|
84
|
+
)
|
|
86
85
|
} for indicator in found_land_occupation_indicators
|
|
87
86
|
]
|
|
88
87
|
|
|
@@ -8,7 +8,7 @@ from hestia_earth.models.log import logRequirements, logShouldRun, log_as_table
|
|
|
8
8
|
from hestia_earth.models.utils.impact_assessment import get_country_id
|
|
9
9
|
from hestia_earth.models.utils.indicator import _new_indicator
|
|
10
10
|
from hestia_earth.models.utils.landCover import get_pef_grouping
|
|
11
|
-
from hestia_earth.models.utils.lookup import
|
|
11
|
+
from hestia_earth.models.utils.lookup import _node_value, get_region_lookup_value
|
|
12
12
|
from . import MODEL
|
|
13
13
|
|
|
14
14
|
REQUIREMENTS = {
|
|
@@ -104,31 +104,28 @@ def _should_run(impact_assessment: dict) -> Tuple[bool, list]:
|
|
|
104
104
|
'value-is-valid': (
|
|
105
105
|
_node_value(indicator) is not None and
|
|
106
106
|
_node_value(indicator) >= 0
|
|
107
|
-
)
|
|
108
|
-
'lookup-country': fallback_country(
|
|
109
|
-
get_country_id(impact_assessment, blank_node=indicator),
|
|
110
|
-
[from_lookup_file, to_lookup_file]
|
|
111
|
-
),
|
|
107
|
+
)
|
|
112
108
|
} for indicator in resource_uses
|
|
113
109
|
]
|
|
114
110
|
|
|
115
111
|
found_transformations_with_coefficient = [
|
|
116
112
|
transformation | {
|
|
117
|
-
'using-fallback-country-region-world-CFs': transformation['lookup-country'] != transformation['country-id'],
|
|
118
113
|
'factor-from': get_region_lookup_value(
|
|
119
114
|
model=MODEL,
|
|
120
115
|
term=TERM_ID,
|
|
121
116
|
lookup_name=from_lookup_file,
|
|
122
|
-
term_id=transformation['
|
|
123
|
-
column=get_pef_grouping(transformation['land-cover-id-from'])
|
|
124
|
-
|
|
117
|
+
term_id=transformation['country-id'],
|
|
118
|
+
column=get_pef_grouping(transformation['land-cover-id-from']),
|
|
119
|
+
fallback_world=True
|
|
120
|
+
) if transformation['land-cover-id-from'] else None,
|
|
125
121
|
'factor-to': get_region_lookup_value(
|
|
126
122
|
model=MODEL,
|
|
127
123
|
term=TERM_ID,
|
|
128
124
|
lookup_name=to_lookup_file,
|
|
129
|
-
term_id=transformation['
|
|
130
|
-
column=get_pef_grouping(transformation['land-cover-id-to'])
|
|
131
|
-
|
|
125
|
+
term_id=transformation['country-id'],
|
|
126
|
+
column=get_pef_grouping(transformation['land-cover-id-to']),
|
|
127
|
+
fallback_world=True
|
|
128
|
+
) if transformation['land-cover-id-to'] else None
|
|
132
129
|
} for transformation in found_transformations
|
|
133
130
|
]
|
|
134
131
|
|
|
@@ -28,7 +28,7 @@ def _indicator(value: float):
|
|
|
28
28
|
|
|
29
29
|
def run(impact_assessment: dict):
|
|
30
30
|
value = impact_emission_lookup_value(
|
|
31
|
-
model=MODEL, term_id=TERM_ID, impact=impact_assessment, lookup_col=LOOKUPS['emission'],
|
|
31
|
+
model=MODEL, term_id=TERM_ID, impact=impact_assessment, lookup_col=LOOKUPS['emission'], group_key='default'
|
|
32
32
|
)
|
|
33
33
|
logRequirements(impact_assessment, model=MODEL, term=TERM_ID,
|
|
34
34
|
value=value)
|
|
@@ -750,6 +750,22 @@ def _should_run_historical_land_use_change_total_cropland(site: dict, nodes: lis
|
|
|
750
750
|
return all([should_run_annual, should_run_permanent]), scaled_results
|
|
751
751
|
|
|
752
752
|
|
|
753
|
+
def _log_all_terms(site: dict, land_use_type: str, country_id: str, changes: dict, missing_changes: list):
|
|
754
|
+
for land_use_term, _ in LAND_USE_TERMS_FOR_TRANSFORMATION.values():
|
|
755
|
+
logRequirements(
|
|
756
|
+
log_node=site,
|
|
757
|
+
model=MODEL,
|
|
758
|
+
term=land_use_term,
|
|
759
|
+
model_key=MODEL_KEY,
|
|
760
|
+
land_use_type=land_use_type,
|
|
761
|
+
country_id=country_id,
|
|
762
|
+
changes=log_as_table(changes),
|
|
763
|
+
missing_changes=log_as_table(missing_changes)
|
|
764
|
+
)
|
|
765
|
+
|
|
766
|
+
logShouldRun(site, MODEL, land_use_term, False, model_key=MODEL_KEY)
|
|
767
|
+
|
|
768
|
+
|
|
753
769
|
def _should_run_historical_land_use_change_single_crop(
|
|
754
770
|
site: dict,
|
|
755
771
|
term: dict,
|
|
@@ -909,6 +925,14 @@ def _should_run_historical_land_use_change_single_crop(
|
|
|
909
925
|
|
|
910
926
|
should_run = all([len(missing_changes) == 0, country_id, site_type_allowed, sum_of_site_areas_is_100])
|
|
911
927
|
logShouldRun(site, MODEL, term.get("@id"), should_run, model_key=MODEL_KEY)
|
|
928
|
+
if not should_run:
|
|
929
|
+
_log_all_terms(
|
|
930
|
+
site=site,
|
|
931
|
+
land_use_type=land_use_type,
|
|
932
|
+
country_id=country_id,
|
|
933
|
+
changes=changes,
|
|
934
|
+
missing_changes=missing_changes
|
|
935
|
+
)
|
|
912
936
|
|
|
913
937
|
return should_run, capped_site_area
|
|
914
938
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from functools import reduce
|
|
2
|
+
from itertools import zip_longest
|
|
2
3
|
from typing import NamedTuple
|
|
3
4
|
|
|
4
5
|
from hestia_earth.models.log import logRequirements, logShouldRun, log_as_table
|
|
@@ -29,9 +30,9 @@ REQUIREMENTS = {
|
|
|
29
30
|
"@type": "Site",
|
|
30
31
|
"country": {"@type": "Term", "termType": "region"}
|
|
31
32
|
},
|
|
32
|
-
"siteArea": "",
|
|
33
|
-
"siteDuration": "",
|
|
34
|
-
"siteUnusedDuration": "",
|
|
33
|
+
"siteArea": ">= 0",
|
|
34
|
+
"siteDuration": ">= 0",
|
|
35
|
+
"siteUnusedDuration": ">= 0",
|
|
35
36
|
"optional": {
|
|
36
37
|
"@doc": "When `otherSites` are provided, `otherSitesArea`, `otherSitesDuration` and `otherSitesUnusedDuration` are required", # noqa: E501
|
|
37
38
|
"otherSites": [{
|
|
@@ -111,82 +112,102 @@ def _calc_land_occupation_m2_per_kg(
|
|
|
111
112
|
return land_occupation_m2_per_ha * economic_value_share * 0.01 / yield_
|
|
112
113
|
|
|
113
114
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
115
|
+
def _extract_site_data(cycle: dict, land_cover_id: dict):
|
|
116
|
+
site = cycle.get("site", {})
|
|
117
|
+
site_data = SiteData(
|
|
118
|
+
id=site.get("@id"),
|
|
119
|
+
area=cycle.get("siteArea"),
|
|
120
|
+
duration=cycle.get("siteDuration"),
|
|
121
|
+
unused_duration=cycle.get("siteUnusedDuration"),
|
|
122
|
+
country_id=site.get("country", {}).get("@id"),
|
|
123
|
+
land_cover_id=land_cover_id or get_landCover_term_id_from_site_type(site.get("siteType", {}))
|
|
124
|
+
)
|
|
117
125
|
|
|
118
|
-
|
|
119
|
-
field: field.replace("site", "otherSites", 1) for field in _CYCLE_KEYS
|
|
120
|
-
}
|
|
126
|
+
is_valid = _should_run_site_data(site_data)
|
|
121
127
|
|
|
128
|
+
logs = {
|
|
129
|
+
"site_data": _format_inventory([site_data])
|
|
130
|
+
}
|
|
122
131
|
|
|
123
|
-
|
|
124
|
-
product_land_cover_id = get_landCover_term_id(product.get("term", {}), skip_debug=True)
|
|
132
|
+
return is_valid, site_data, logs
|
|
125
133
|
|
|
126
|
-
cycle_data = {
|
|
127
|
-
key: [value] + cycle.get(otherSites_key, []) for key, otherSites_key in _CYCLE_KEY_MAPPING.items()
|
|
128
|
-
if (value := cycle.get(key))
|
|
129
|
-
}
|
|
130
134
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
])
|
|
135
|
+
def _extract_other_sites_data(cycle: dict, land_cover_id: dict):
|
|
136
|
+
other_sites = cycle.get("otherSites", [])
|
|
137
|
+
other_sites_area = cycle.get("otherSitesArea", [])
|
|
138
|
+
other_sites_duration = cycle.get("otherSitesDuration", [])
|
|
139
|
+
other_sites_unused_duration = cycle.get("otherSitesUnusedDuration", [])
|
|
135
140
|
|
|
136
|
-
|
|
141
|
+
other_sites_data = [
|
|
137
142
|
SiteData(
|
|
138
143
|
id=site.get("@id"),
|
|
139
|
-
area=
|
|
140
|
-
duration=
|
|
141
|
-
unused_duration=
|
|
144
|
+
area=area,
|
|
145
|
+
duration=duration,
|
|
146
|
+
unused_duration=unused_duration,
|
|
142
147
|
country_id=site.get("country", {}).get("@id"),
|
|
143
|
-
land_cover_id=
|
|
144
|
-
) for
|
|
145
|
-
|
|
148
|
+
land_cover_id=land_cover_id or get_landCover_term_id_from_site_type(site.get("siteType", {}))
|
|
149
|
+
) for (
|
|
150
|
+
site,
|
|
151
|
+
area,
|
|
152
|
+
duration,
|
|
153
|
+
unused_duration
|
|
154
|
+
) in zip_longest(
|
|
155
|
+
other_sites,
|
|
156
|
+
other_sites_area,
|
|
157
|
+
other_sites_duration,
|
|
158
|
+
other_sites_unused_duration
|
|
159
|
+
)
|
|
160
|
+
]
|
|
161
|
+
|
|
162
|
+
is_valid = all(_should_run_site_data(other_site) for other_site in other_sites_data)
|
|
146
163
|
|
|
147
164
|
logs = {
|
|
148
|
-
"
|
|
165
|
+
"other_sites_count": len(other_sites),
|
|
166
|
+
"other_sites_data": _format_inventory(other_sites_data, "Not relevant")
|
|
149
167
|
}
|
|
150
168
|
|
|
151
|
-
return
|
|
169
|
+
return is_valid, other_sites_data, logs
|
|
152
170
|
|
|
153
171
|
|
|
154
172
|
def _should_run_site_data(site_data: SiteData) -> bool:
|
|
155
173
|
return all([
|
|
156
|
-
site_data.area
|
|
157
|
-
site_data.duration
|
|
158
|
-
site_data.unused_duration
|
|
174
|
+
site_data.area or site_data.area == 0,
|
|
175
|
+
site_data.duration or site_data.duration == 0,
|
|
176
|
+
site_data.unused_duration or site_data.unused_duration == 0,
|
|
159
177
|
site_data.land_cover_id,
|
|
160
178
|
site_data.country_id
|
|
161
179
|
])
|
|
162
180
|
|
|
163
181
|
|
|
164
|
-
def _format_float(value: float, unit: str = "") -> str:
|
|
182
|
+
def _format_float(value: float, unit: str = "", default: str = "None") -> str:
|
|
165
183
|
return " ".join(
|
|
166
|
-
string for string in [f"{value
|
|
167
|
-
) if value else
|
|
184
|
+
string for string in [f"{value}", unit] if string
|
|
185
|
+
) if isinstance(value, (float, int)) else default
|
|
168
186
|
|
|
169
187
|
|
|
170
188
|
_INVALID_CHARS = {"_", ":", ",", "="}
|
|
171
189
|
_REPLACEMENT_CHAR = "-"
|
|
172
190
|
|
|
173
191
|
|
|
174
|
-
def _format_str(value: str) -> str:
|
|
192
|
+
def _format_str(value: str, default: str = "None") -> str:
|
|
175
193
|
"""Format a string for logging in a table. Remove all characters used to render the table on the front end."""
|
|
176
|
-
return
|
|
194
|
+
return (
|
|
195
|
+
reduce(lambda x, char: x.replace(char, _REPLACEMENT_CHAR), _INVALID_CHARS, str(value))
|
|
196
|
+
if value else default
|
|
197
|
+
)
|
|
177
198
|
|
|
178
199
|
|
|
179
|
-
def _format_inventory(inventory: list[SiteData]) -> str:
|
|
200
|
+
def _format_inventory(inventory: list[SiteData], default: str = "None") -> str:
|
|
180
201
|
return log_as_table(
|
|
181
202
|
{
|
|
182
|
-
"
|
|
203
|
+
"site-id": _format_str(site_data.id),
|
|
183
204
|
"site-area": _format_float(site_data.area, "ha"),
|
|
184
205
|
"site-duration": _format_float(site_data.duration, "days"),
|
|
185
206
|
"site-unused-duration": _format_float(site_data.unused_duration, "days"),
|
|
186
207
|
"land-cover-id": _format_str(site_data.land_cover_id),
|
|
187
208
|
"country-id": _format_str(site_data.country_id)
|
|
188
209
|
} for site_data in inventory
|
|
189
|
-
) if inventory else
|
|
210
|
+
) if inventory else default
|
|
190
211
|
|
|
191
212
|
|
|
192
213
|
def _should_run(impact_assessment: dict):
|
|
@@ -195,13 +216,21 @@ def _should_run(impact_assessment: dict):
|
|
|
195
216
|
functional_unit = cycle.get("functionalUnit")
|
|
196
217
|
|
|
197
218
|
product = get_product(impact_assessment)
|
|
198
|
-
|
|
219
|
+
product_yield = sum(product.get("value", []))
|
|
220
|
+
product_land_cover_id = get_landCover_term_id(product.get("term", {}), skip_debug=True)
|
|
199
221
|
economic_value_share = (
|
|
200
222
|
100 if functional_unit == CycleFunctionalUnit.RELATIVE.value
|
|
201
223
|
else product.get("economicValueShare")
|
|
202
224
|
)
|
|
203
225
|
|
|
204
|
-
|
|
226
|
+
site_data_is_valid, site_data, site_logs = _extract_site_data(cycle, product_land_cover_id)
|
|
227
|
+
(
|
|
228
|
+
other_sites_data_is_valid,
|
|
229
|
+
other_sites_data,
|
|
230
|
+
other_sites_logs
|
|
231
|
+
) = _extract_other_sites_data(cycle, product_land_cover_id)
|
|
232
|
+
|
|
233
|
+
inventory = [site_data] + other_sites_data
|
|
205
234
|
|
|
206
235
|
valid_inventory = inventory and all(_should_run_site_data(site_data) for site_data in inventory)
|
|
207
236
|
|
|
@@ -210,25 +239,25 @@ def _should_run(impact_assessment: dict):
|
|
|
210
239
|
model=MODEL,
|
|
211
240
|
term=TERM_ID,
|
|
212
241
|
functional_unit=functional_unit,
|
|
213
|
-
|
|
242
|
+
product_yield=_format_float(product_yield, product.get("term", {}).get("units")),
|
|
214
243
|
economic_value_share=_format_float(economic_value_share, "pct"),
|
|
215
|
-
site_inventory=_format_inventory(inventory),
|
|
216
244
|
valid_inventory=valid_inventory,
|
|
217
|
-
|
|
245
|
+
site_data_is_valid=site_data_is_valid,
|
|
246
|
+
**site_logs,
|
|
247
|
+
other_sites_data_is_valid=other_sites_data_is_valid,
|
|
248
|
+
**other_sites_logs
|
|
218
249
|
)
|
|
219
250
|
|
|
220
251
|
should_run = all([
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
),
|
|
226
|
-
valid_inventory
|
|
252
|
+
product_yield > 0,
|
|
253
|
+
economic_value_share or economic_value_share == 0,
|
|
254
|
+
site_data_is_valid,
|
|
255
|
+
other_sites_data_is_valid
|
|
227
256
|
])
|
|
228
257
|
|
|
229
258
|
logShouldRun(impact_assessment, MODEL, TERM_ID, should_run)
|
|
230
259
|
|
|
231
|
-
return should_run,
|
|
260
|
+
return should_run, product_yield, economic_value_share, inventory
|
|
232
261
|
|
|
233
262
|
|
|
234
263
|
def _run(
|