hestia-earth-models 0.65.11__py3-none-any.whl → 0.67.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.
- hestia_earth/models/cache_sites.py +7 -9
- hestia_earth/models/cml2001Baseline/abioticResourceDepletionFossilFuels.py +23 -54
- hestia_earth/models/cml2001Baseline/resourceUseEnergyDepletionDuringCycle.py +152 -0
- hestia_earth/models/cml2001Baseline/resourceUseEnergyDepletionInputsProduction.py +40 -0
- hestia_earth/models/cml2001Baseline/resourceUseMineralsAndMetalsDuringCycle.py +80 -0
- hestia_earth/models/cml2001Baseline/resourceUseMineralsAndMetalsInputsProduction.py +40 -0
- hestia_earth/models/config/Cycle.json +34 -16
- hestia_earth/models/config/ImpactAssessment.json +1867 -1832
- hestia_earth/models/config/Site.json +4 -1
- hestia_earth/models/cycle/completeness/freshForage.py +10 -2
- hestia_earth/models/cycle/cropResidueManagement.py +3 -1
- hestia_earth/models/cycle/input/hestiaAggregatedData.py +13 -10
- hestia_earth/models/ecoinventV3/__init__.py +2 -1
- hestia_earth/models/{environmentalFootprintV3 → environmentalFootprintV3_1}/__init__.py +4 -3
- hestia_earth/models/environmentalFootprintV3_1/environmentalFootprintSingleOverallScore.py +135 -0
- hestia_earth/models/environmentalFootprintV3_1/marineEutrophicationPotential.py +36 -0
- hestia_earth/models/environmentalFootprintV3_1/scarcityWeightedWaterUse.py +40 -0
- hestia_earth/models/{environmentalFootprintV3 → environmentalFootprintV3_1}/soilQualityIndexLandTransformation.py +17 -6
- hestia_earth/models/geospatialDatabase/{aware.py → awareWaterBasinId.py} +1 -1
- hestia_earth/models/hestia/landCover.py +42 -34
- hestia_earth/models/hestia/residueRemoved.py +80 -0
- hestia_earth/models/hestia/resourceUse_utils.py +43 -29
- hestia_earth/models/impact_assessment/product/value.py +1 -1
- hestia_earth/models/ipcc2019/aboveGroundBiomass.py +34 -13
- hestia_earth/models/ipcc2019/belowGroundBiomass.py +33 -12
- hestia_earth/models/ipcc2019/ch4ToAirEntericFermentation.py +17 -8
- hestia_earth/models/ipcc2019/co2ToAirCarbonStockChange_utils.py +7 -4
- hestia_earth/models/ipcc2019/organicCarbonPerHa_tier_1_utils.py +2 -1
- hestia_earth/models/ipcc2019/organicCarbonPerHa_tier_2_utils.py +29 -18
- hestia_earth/models/ipcc2019/pastureGrass_utils.py +8 -1
- hestia_earth/models/log.py +1 -1
- hestia_earth/models/mocking/search-results.json +872 -872
- hestia_earth/models/site/defaultMethodClassification.py +9 -2
- hestia_earth/models/site/defaultMethodClassificationDescription.py +4 -2
- hestia_earth/models/site/management.py +48 -30
- hestia_earth/models/site/pre_checks/cache_geospatialDatabase.py +19 -14
- hestia_earth/models/utils/__init__.py +6 -0
- hestia_earth/models/utils/aggregated.py +13 -10
- hestia_earth/models/utils/array_builders.py +4 -3
- hestia_earth/models/utils/blank_node.py +23 -13
- hestia_earth/models/utils/lookup.py +4 -2
- hestia_earth/models/utils/property.py +5 -2
- hestia_earth/models/version.py +1 -1
- hestia_earth/orchestrator/log.py +11 -0
- hestia_earth/orchestrator/models/__init__.py +8 -3
- hestia_earth/orchestrator/strategies/merge/merge_list.py +17 -6
- {hestia_earth_models-0.65.11.dist-info → hestia_earth_models-0.67.0.dist-info}/METADATA +1 -1
- {hestia_earth_models-0.65.11.dist-info → hestia_earth_models-0.67.0.dist-info}/RECORD +86 -69
- tests/models/cml2001Baseline/test_abioticResourceDepletionFossilFuels.py +51 -87
- tests/models/cml2001Baseline/test_resourceUseEnergyDepletionDuringCycle.py +103 -0
- tests/models/cml2001Baseline/test_resourceUseEnergyDepletionInputsProduction.py +23 -0
- tests/models/cml2001Baseline/test_resourceUseMineralsAndMetalsDuringCycle.py +58 -0
- tests/models/cml2001Baseline/test_resourceUseMineralsAndMetalsInputsProduction.py +23 -0
- tests/models/environmentalFootprintV3_1/test_environmentalFootprintSingleOverallScore.py +93 -0
- tests/models/{environmentalFootprintV3 → environmentalFootprintV3_1}/test_freshwaterEcotoxicityPotentialCtue.py +6 -5
- tests/models/environmentalFootprintV3_1/test_marineEutrophicationPotential.py +27 -0
- tests/models/environmentalFootprintV3_1/test_scarcityWeightedWaterUse.py +32 -0
- tests/models/{environmentalFootprintV3 → environmentalFootprintV3_1}/test_soilQualityIndexLandOccupation.py +4 -3
- tests/models/{environmentalFootprintV3 → environmentalFootprintV3_1}/test_soilQualityIndexLandTransformation.py +8 -22
- tests/models/{environmentalFootprintV3 → environmentalFootprintV3_1}/test_soilQualityIndexTotalLandUseEffects.py +4 -4
- tests/models/faostat2018/product/test_price.py +1 -1
- tests/models/geospatialDatabase/{test_aware.py → test_awareWaterBasinId.py} +1 -1
- tests/models/hestia/test_landCover.py +2 -1
- tests/models/hestia/test_landTransformation20YearAverageDuringCycle.py +2 -1
- tests/models/hestia/test_residueRemoved.py +20 -0
- tests/models/impact_assessment/test_emissions.py +0 -1
- tests/models/ipcc2019/test_aboveGroundBiomass.py +3 -1
- tests/models/ipcc2019/test_belowGroundBiomass.py +4 -2
- tests/models/ipcc2019/test_organicCarbonPerHa.py +94 -1
- tests/models/site/pre_checks/test_cache_geospatialDatabase.py +22 -0
- tests/models/site/test_defaultMethodClassification.py +6 -0
- tests/models/site/test_defaultMethodClassificationDescription.py +6 -0
- tests/models/site/test_management.py +4 -4
- tests/models/test_cache_sites.py +2 -2
- tests/models/test_config.py +3 -3
- tests/models/test_ecoinventV3.py +0 -1
- tests/models/utils/test_array_builders.py +2 -2
- tests/orchestrator/strategies/merge/test_merge_list.py +11 -1
- /hestia_earth/models/{environmentalFootprintV3 → environmentalFootprintV3_1}/freshwaterEcotoxicityPotentialCtue.py +0 -0
- /hestia_earth/models/{environmentalFootprintV3 → environmentalFootprintV3_1}/soilQualityIndexLandOccupation.py +0 -0
- /hestia_earth/models/{environmentalFootprintV3 → environmentalFootprintV3_1}/soilQualityIndexTotalLandUseEffects.py +0 -0
- /hestia_earth/models/{environmentalFootprintV3 → environmentalFootprintV3_1}/utils.py +0 -0
- {hestia_earth_models-0.65.11.dist-info → hestia_earth_models-0.67.0.dist-info}/LICENSE +0 -0
- {hestia_earth_models-0.65.11.dist-info → hestia_earth_models-0.67.0.dist-info}/WHEEL +0 -0
- {hestia_earth_models-0.65.11.dist-info → hestia_earth_models-0.67.0.dist-info}/top_level.txt +0 -0
- /tests/models/{environmentalFootprintV3 → environmentalFootprintV3_1}/__init__.py +0 -0
@@ -128,7 +128,7 @@
|
|
128
128
|
{
|
129
129
|
"key": "awareWaterBasinId",
|
130
130
|
"model": "geospatialDatabase",
|
131
|
-
"value": "
|
131
|
+
"value": "awareWaterBasinId",
|
132
132
|
"runStrategy": "add_key_if_missing",
|
133
133
|
"mergeStrategy": "default",
|
134
134
|
"stage": 1
|
@@ -406,6 +406,9 @@
|
|
406
406
|
"value": "management",
|
407
407
|
"runStrategy": "always",
|
408
408
|
"mergeStrategy": "list",
|
409
|
+
"mergeArgs": {
|
410
|
+
"matchDatesFormat": "%Y"
|
411
|
+
},
|
409
412
|
"stage": 2
|
410
413
|
},
|
411
414
|
[
|
@@ -9,6 +9,7 @@ from hestia_earth.utils.tools import list_sum
|
|
9
9
|
|
10
10
|
from hestia_earth.models.log import logRequirements
|
11
11
|
from hestia_earth.models.utils import is_from_model
|
12
|
+
from hestia_earth.models.utils.blank_node import get_lookup_value
|
12
13
|
from . import MODEL
|
13
14
|
|
14
15
|
REQUIREMENTS = {
|
@@ -32,13 +33,17 @@ RETURNS = {
|
|
32
33
|
"freshForage": ""
|
33
34
|
}
|
34
35
|
}
|
36
|
+
LOOKUPS = {
|
37
|
+
"liveAnimal": "isGrazingAnimal",
|
38
|
+
"liveAquaticSpecies": "isGrazingAnimal"
|
39
|
+
}
|
35
40
|
MODEL_KEY = 'freshForage'
|
36
41
|
ALLOWED_SITE_TYPES = [
|
37
42
|
SiteSiteType.PERMANENT_PASTURE.value
|
38
43
|
]
|
39
44
|
|
40
45
|
|
41
|
-
def _valid_input(input: dict): return is_from_model(input) and list_sum(input.get('value', [-1]))
|
46
|
+
def _valid_input(input: dict): return is_from_model(input) and list_sum(input.get('value', [-1])) >= 0
|
42
47
|
|
43
48
|
|
44
49
|
def run(cycle: dict):
|
@@ -47,7 +52,10 @@ def run(cycle: dict):
|
|
47
52
|
|
48
53
|
cycle_has_added_forage_input = any(map(_valid_input, cycle.get('inputs', [])))
|
49
54
|
|
50
|
-
animals =
|
55
|
+
animals = [
|
56
|
+
a for a in cycle.get('animals', [])
|
57
|
+
if get_lookup_value(a.get('term', {}), 'isGrazingAnimal', model=MODEL, key=MODEL_KEY)
|
58
|
+
]
|
51
59
|
all_animals_have_added_forage_input = bool(animals) and all([
|
52
60
|
any(map(_valid_input, animal.get('inputs', []))) for animal in animals
|
53
61
|
])
|
@@ -30,6 +30,8 @@ PRACTICE_IDS = [
|
|
30
30
|
residueIncorporated.TERM_ID,
|
31
31
|
residueLeftOnField.TERM_ID,
|
32
32
|
residueRemoved.TERM_ID,
|
33
|
+
'residueIncorporatedLessThan30DaysBeforeCultivation',
|
34
|
+
'residueIncorporatedMoreThan30DaysBeforeCultivation',
|
33
35
|
]
|
34
36
|
|
35
37
|
|
@@ -50,7 +52,7 @@ def _should_run(cycle: dict):
|
|
50
52
|
])]
|
51
53
|
missing_practices = [term_id for term_id in practice_ids if term_id not in existing_practices]
|
52
54
|
|
53
|
-
should_run = all([sum_practices
|
55
|
+
should_run = all([99.5 <= sum_practices <= 100.5])
|
54
56
|
|
55
57
|
for term_id in missing_practices:
|
56
58
|
logRequirements(cycle, model=MODEL, term=term_id,
|
@@ -77,19 +77,22 @@ def _run_seed(cycle: dict, primary_product: dict, seed_input: dict, product_term
|
|
77
77
|
# to avoid double counting seed => aggregated impact => seed, we need to get the impact of the previous decade
|
78
78
|
# if the data does not exist, use the aggregated impact of generic crop instead
|
79
79
|
date = aggregated_end_date(cycle.get('endDate'))
|
80
|
-
match_end_date = [
|
81
|
-
|
82
|
-
]
|
80
|
+
match_end_date = [{'match': {'endDate': date - 10}}]
|
81
|
+
default_product = get_generic_crop()
|
83
82
|
|
84
|
-
impact =
|
85
|
-
find_closest_impact(cycle, date,
|
86
|
-
find_closest_impact(cycle, date,
|
83
|
+
impact = (
|
84
|
+
find_closest_impact(cycle, date, product, region, country, match_end_date) or
|
85
|
+
find_closest_impact(cycle, date, primary_product.get('term', {}), region, country, match_end_date) or
|
86
|
+
find_closest_impact(cycle, date, default_product, region, country)
|
87
|
+
)
|
87
88
|
|
89
|
+
search_by_product_term_id = (product or primary_product or default_product).get('@id')
|
90
|
+
search_by_region_id = (region or country or {}).get('@id') or 'region-world'
|
88
91
|
debugValues(cycle, model=MODEL_ID, term=seed_input.get('term', {}).get('@id'), key=MODEL_KEY,
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
92
|
+
search_by_product_term_id=search_by_product_term_id,
|
93
|
+
search_by_region_id=search_by_region_id,
|
94
|
+
search_by_end_date=str(date),
|
95
|
+
impact_assessment_id_found=(impact or {}).get('@id'))
|
93
96
|
|
94
97
|
return seed_input | {MODEL_KEY: linked_node(impact), 'impactAssessmentIsProxy': True} if impact else None
|
95
98
|
|
@@ -69,7 +69,8 @@ LOOKUPS = {
|
|
69
69
|
"pesticideAI": "ecoinventMapping",
|
70
70
|
"soilAmendment": "ecoinventMapping",
|
71
71
|
"transport": "ecoinventMapping",
|
72
|
-
"veterinaryDrugs": "ecoinventMapping"
|
72
|
+
"veterinaryDrugs": "ecoinventMapping",
|
73
|
+
"feedFoodAdditive": "ecoinventMapping"
|
73
74
|
}
|
74
75
|
MODEL = 'ecoinventV3'
|
75
76
|
MODEL_KEY = 'impactAssessment' # keep to generate entry in "model-links.json"
|
@@ -1,13 +1,14 @@
|
|
1
|
-
from os.path import dirname, abspath
|
2
1
|
import sys
|
3
2
|
from importlib import import_module
|
3
|
+
from os.path import dirname, abspath
|
4
4
|
|
5
5
|
from hestia_earth.models.utils.blank_node import run_if_required
|
6
6
|
|
7
7
|
CURRENT_DIR = dirname(abspath(__file__)) + '/'
|
8
8
|
sys.path.append(CURRENT_DIR)
|
9
|
-
MODEL = 'environmentalFootprintV3'
|
10
|
-
|
9
|
+
MODEL = 'environmentalFootprintV3-1'
|
10
|
+
MODEL_FOLDER = MODEL.replace('-', '_')
|
11
|
+
PKG = '.'.join(['hestia_earth', 'models', MODEL_FOLDER])
|
11
12
|
|
12
13
|
|
13
14
|
def run(model: str, data): return run_if_required(MODEL, model, data, import_module(f".{model}", package=PKG))
|
@@ -0,0 +1,135 @@
|
|
1
|
+
"""
|
2
|
+
The inputs and outputs from the life cycle inventory are aggregated in 16 midpoint
|
3
|
+
characterised impact categories. These impact categories are then normalised (i.e., the results are divided by
|
4
|
+
the overall inventory of a reference unit, e.g., the entire world, to convert the characterised impact categories in
|
5
|
+
relative shares of the impacts of the analysed system) and weighted (i.e., each impact category is multiplied by
|
6
|
+
a weighting factor to reflect their perceived relative importance). The weighted impact categories can then be
|
7
|
+
summed to obtain the EF single overall score. The number and the name of the impact categories is the same
|
8
|
+
in EF3.0 and EF3.1.
|
9
|
+
"""
|
10
|
+
from typing import List, Optional, Tuple
|
11
|
+
|
12
|
+
from hestia_earth.schema import TermTermType
|
13
|
+
from hestia_earth.utils.lookup import get_table_value, download_lookup, column_name
|
14
|
+
from hestia_earth.utils.model import filter_list_term_type
|
15
|
+
from hestia_earth.utils.tools import list_sum
|
16
|
+
|
17
|
+
from hestia_earth.models.log import logRequirements, logShouldRun, log_as_table, debugMissingLookup
|
18
|
+
from hestia_earth.models.utils.indicator import _new_indicator
|
19
|
+
from hestia_earth.models.utils.lookup import _node_value
|
20
|
+
from . import MODEL
|
21
|
+
|
22
|
+
REQUIREMENTS = {
|
23
|
+
"ImpactAssessment": {
|
24
|
+
"impacts": [
|
25
|
+
{
|
26
|
+
"@type": "Indicator",
|
27
|
+
"value": "",
|
28
|
+
"term.name": "PEF indicators only"
|
29
|
+
}
|
30
|
+
]
|
31
|
+
}
|
32
|
+
}
|
33
|
+
|
34
|
+
LOOKUPS = {
|
35
|
+
"@doc": "Normalisation factors in PEF v3.1 are calculated using a Global population number of 6,895,889,018",
|
36
|
+
"characterisedIndicator": ["pefTerm-normalisation-v3_1", "pefTerm-weighing-v3_1"]
|
37
|
+
}
|
38
|
+
|
39
|
+
RETURNS = {
|
40
|
+
"Indicator": {
|
41
|
+
"value": ""
|
42
|
+
}
|
43
|
+
}
|
44
|
+
|
45
|
+
TERM_ID = 'environmentalFootprintSingleOverallScore'
|
46
|
+
|
47
|
+
normalisation_column = LOOKUPS['characterisedIndicator'][0]
|
48
|
+
weighing_column = LOOKUPS['characterisedIndicator'][1]
|
49
|
+
|
50
|
+
|
51
|
+
def _is_a_PEF_indicator(indicator_id) -> bool:
|
52
|
+
return (_get_factor(indicator_id, normalisation_column) not in [None, 0, 0.0] and
|
53
|
+
_get_factor(indicator_id, weighing_column) not in [None, 0, 0.0])
|
54
|
+
|
55
|
+
|
56
|
+
def _get_factor(indicator_id: str, column) -> Optional[float]:
|
57
|
+
factor = get_table_value(download_lookup(f"{list(LOOKUPS.keys())[1]}.csv", keep_in_memory=True),
|
58
|
+
'termid', indicator_id, column_name(column))
|
59
|
+
if factor is None:
|
60
|
+
debugMissingLookup(f"{list(LOOKUPS.keys())[1]}.csv", 'termid', indicator_id, column, None, model=MODEL,
|
61
|
+
term=TERM_ID)
|
62
|
+
return float(factor) if factor is not None else None
|
63
|
+
|
64
|
+
|
65
|
+
def _normalise(indicator: dict) -> Optional[float]:
|
66
|
+
return (_node_value(indicator) / _get_factor(indicator['term']['@id'], normalisation_column)) \
|
67
|
+
if (_node_value(indicator) is not None and
|
68
|
+
_get_factor(indicator['term']['@id'], normalisation_column) not in [None, 0, 0.0]) else None
|
69
|
+
|
70
|
+
|
71
|
+
def _weighted_normalise(indicator: dict) -> Optional[float]:
|
72
|
+
return (_normalise(indicator) * (_get_factor(indicator['term']['@id'], weighing_column) / 100)
|
73
|
+
) if (_normalise(indicator) is not None and
|
74
|
+
_get_factor(indicator['term']['@id'], weighing_column) not in [None, 0, 0.0]) else None
|
75
|
+
|
76
|
+
|
77
|
+
def _indicator(value: float) -> dict:
|
78
|
+
indicator = _new_indicator(TERM_ID, MODEL)
|
79
|
+
indicator['value'] = value
|
80
|
+
return indicator
|
81
|
+
|
82
|
+
|
83
|
+
def _run(indicators: List[dict]):
|
84
|
+
return _indicator(value=list_sum([indicator["weighted-normalised"] for indicator in indicators]))
|
85
|
+
|
86
|
+
|
87
|
+
def _valid_indicator(indicator: Optional[dict]) -> bool:
|
88
|
+
return all([indicator is not None,
|
89
|
+
isinstance(_node_value(indicator), (int, float)),
|
90
|
+
_node_value(indicator) is not None,
|
91
|
+
_is_a_PEF_indicator(indicator.get('term', {}).get('@id', ''))])
|
92
|
+
|
93
|
+
|
94
|
+
def _should_run(impact_assessment: dict) -> Tuple[bool, list[dict]]:
|
95
|
+
indicators = [
|
96
|
+
indicator for indicator in
|
97
|
+
filter_list_term_type(impact_assessment.get('impacts', []), TermTermType.CHARACTERISEDINDICATOR)
|
98
|
+
if _is_a_PEF_indicator(indicator.get('term', {}).get('@id', ''))
|
99
|
+
]
|
100
|
+
|
101
|
+
has_pef_indicators = bool(indicators)
|
102
|
+
|
103
|
+
processed_indicators = [{
|
104
|
+
"indicator": indicator,
|
105
|
+
"valid-indicator": _valid_indicator(indicator),
|
106
|
+
"one-indicator-for-category": sum(1 for i in indicators if i['term']['@id'] == indicator['term']['@id']) == 1,
|
107
|
+
"indicator-pef-category": indicator['term']['@id'],
|
108
|
+
"value": _node_value(indicator),
|
109
|
+
"normalised": _normalise(indicator),
|
110
|
+
"normalisation-used": _get_factor(indicator['term']['@id'], normalisation_column),
|
111
|
+
"weighted-normalised": _weighted_normalise(indicator),
|
112
|
+
"weighting-used": _get_factor(indicator['term']['@id'], weighing_column),
|
113
|
+
}
|
114
|
+
for indicator in indicators
|
115
|
+
]
|
116
|
+
|
117
|
+
no_duplicate_indicators = all([indicator['one-indicator-for-category'] for indicator in processed_indicators])
|
118
|
+
valid_indicators = [indicator for indicator in processed_indicators if indicator['valid-indicator']]
|
119
|
+
all_indicators_valid = all([indicator['valid-indicator'] for indicator in processed_indicators])
|
120
|
+
|
121
|
+
logRequirements(impact_assessment, model=MODEL, term=TERM_ID,
|
122
|
+
has_pef_indicators=has_pef_indicators,
|
123
|
+
no_duplicate_indicators=no_duplicate_indicators,
|
124
|
+
all_indicators_valid=all_indicators_valid,
|
125
|
+
processed_indicators=log_as_table(processed_indicators),
|
126
|
+
)
|
127
|
+
|
128
|
+
should_run = all([has_pef_indicators, all_indicators_valid, no_duplicate_indicators])
|
129
|
+
logShouldRun(impact_assessment, MODEL, TERM_ID, should_run)
|
130
|
+
return should_run, valid_indicators
|
131
|
+
|
132
|
+
|
133
|
+
def run(impact_assessment: dict):
|
134
|
+
should_run, indicators = _should_run(impact_assessment)
|
135
|
+
return _run(indicators) if should_run else None
|
@@ -0,0 +1,36 @@
|
|
1
|
+
from hestia_earth.models.log import logRequirements, logShouldRun
|
2
|
+
from hestia_earth.models.utils.impact_assessment import impact_emission_lookup_value
|
3
|
+
from hestia_earth.models.utils.indicator import _new_indicator
|
4
|
+
from . import MODEL
|
5
|
+
|
6
|
+
REQUIREMENTS = {
|
7
|
+
"ImpactAssessment": {
|
8
|
+
"emissionsResourceUse": [{"@type": "Indicator", "value": "", "term.termType": "emission"}]
|
9
|
+
}
|
10
|
+
}
|
11
|
+
|
12
|
+
RETURNS = {
|
13
|
+
"Indicator": {
|
14
|
+
"value": ""
|
15
|
+
}
|
16
|
+
}
|
17
|
+
|
18
|
+
LOOKUPS = {
|
19
|
+
"emission": "nEqMarineEutrophicationEnvironmentalFootprintV3"
|
20
|
+
}
|
21
|
+
|
22
|
+
TERM_ID = 'marineEutrophicationPotential'
|
23
|
+
|
24
|
+
|
25
|
+
def _indicator(value: float):
|
26
|
+
indicator = _new_indicator(TERM_ID, MODEL)
|
27
|
+
indicator['value'] = value
|
28
|
+
return indicator
|
29
|
+
|
30
|
+
|
31
|
+
def run(impact_assessment: dict):
|
32
|
+
value = impact_emission_lookup_value(MODEL, TERM_ID, impact_assessment, LOOKUPS['emission'])
|
33
|
+
logRequirements(impact_assessment, model=MODEL, term=TERM_ID,
|
34
|
+
value=value)
|
35
|
+
logShouldRun(impact_assessment, MODEL, TERM_ID, value is not None)
|
36
|
+
return _indicator(value) if value is not None else None
|
@@ -0,0 +1,40 @@
|
|
1
|
+
from hestia_earth.models.log import logRequirements, logShouldRun
|
2
|
+
from . import MODEL
|
3
|
+
from ..utils.impact_assessment import impact_country_value
|
4
|
+
from ..utils.indicator import _new_indicator
|
5
|
+
|
6
|
+
REQUIREMENTS = {
|
7
|
+
"ImpactAssessment": {
|
8
|
+
"emissionsResourceUse": [{"@type": "Indicator",
|
9
|
+
"term.@id": "freshwaterWithdrawalsDuringCycle",
|
10
|
+
"value": ""
|
11
|
+
}],
|
12
|
+
"optional": {"country": {"@type": "Term", "termType": "region"}}
|
13
|
+
}
|
14
|
+
}
|
15
|
+
|
16
|
+
LOOKUPS = {
|
17
|
+
"region-resourceUse-environmentalFootprintV31WaterUse": ""
|
18
|
+
}
|
19
|
+
|
20
|
+
RETURNS = {
|
21
|
+
"Indicator": {
|
22
|
+
"value": ""
|
23
|
+
}
|
24
|
+
}
|
25
|
+
TERM_ID = 'scarcityWeightedWaterUse'
|
26
|
+
|
27
|
+
|
28
|
+
def _indicator(value: float):
|
29
|
+
indicator = _new_indicator(TERM_ID, MODEL)
|
30
|
+
indicator['value'] = value
|
31
|
+
return indicator
|
32
|
+
|
33
|
+
|
34
|
+
def run(impact_assessment: dict):
|
35
|
+
value = impact_country_value(MODEL, TERM_ID, impact_assessment, f"{list(LOOKUPS.keys())[0]}.csv",
|
36
|
+
country_fallback=True)
|
37
|
+
logRequirements(impact_assessment, model=MODEL, term=TERM_ID,
|
38
|
+
value=value)
|
39
|
+
logShouldRun(impact_assessment, MODEL, TERM_ID, value is not None)
|
40
|
+
return None if value is None else _indicator(value)
|
@@ -25,14 +25,25 @@ REQUIREMENTS = {
|
|
25
25
|
{
|
26
26
|
"@type": "Indicator",
|
27
27
|
"term.termType": "resourceUse",
|
28
|
-
"term.@id":
|
29
|
-
"landTransformation20YearAverageDuringCycle"],
|
28
|
+
"term.@id": "landTransformation20YearAverageDuringCycle",
|
30
29
|
"value": "> 0",
|
31
30
|
"landCover": {"@type": "Term", "term.termType": "landCover"},
|
32
31
|
"previousLandCover": {"@type": "Term", "term.termType": "landCover"}
|
33
32
|
}
|
34
33
|
],
|
35
|
-
"optional": {
|
34
|
+
"optional": {
|
35
|
+
"country": {"@type": "Term", "termType": "region"},
|
36
|
+
"emissionsResourceUse": [
|
37
|
+
{
|
38
|
+
"@type": "Indicator",
|
39
|
+
"term.termType": "resourceUse",
|
40
|
+
"term.@id": "landTransformation20YearAverageInputsProduction",
|
41
|
+
"value": "> 0",
|
42
|
+
"landCover": {"@type": "Term", "term.termType": "landCover"},
|
43
|
+
"previousLandCover": {"@type": "Term", "term.termType": "landCover"}
|
44
|
+
}
|
45
|
+
]
|
46
|
+
}
|
36
47
|
}
|
37
48
|
}
|
38
49
|
|
@@ -76,7 +87,8 @@ def _run(transformations: List[dict]):
|
|
76
87
|
|
77
88
|
|
78
89
|
def _is_valid_indicator(indicator: dict) -> bool:
|
79
|
-
return indicator['term']['@id'] in
|
90
|
+
return indicator['term']['@id'] in ["landTransformation20YearAverageInputsProduction",
|
91
|
+
"landTransformation20YearAverageDuringCycle"]
|
80
92
|
|
81
93
|
|
82
94
|
def _should_run(impact_assessment: dict) -> Tuple[bool, list]:
|
@@ -144,8 +156,7 @@ def _should_run(impact_assessment: dict) -> Tuple[bool, list]:
|
|
144
156
|
found_transformations=log_as_table(found_transformations_with_coefficient)
|
145
157
|
)
|
146
158
|
|
147
|
-
should_run =
|
148
|
-
all_transformations_are_valid])
|
159
|
+
should_run = all([has_land_transformation_indicators, all_transformations_are_valid])
|
149
160
|
|
150
161
|
logShouldRun(impact_assessment, MODEL, TERM_ID, should_run)
|
151
162
|
return should_run, valid_transformations_with_coef
|
@@ -1,8 +1,8 @@
|
|
1
1
|
"""
|
2
2
|
Land Cover
|
3
3
|
|
4
|
-
This model calculates historic land use change over a twenty-year period,
|
5
|
-
functionality of the Blonk model.
|
4
|
+
This model calculates historic land use change over a twenty-year period,
|
5
|
+
extending the functionality of the Blonk model.
|
6
6
|
"""
|
7
7
|
import math
|
8
8
|
from collections import defaultdict
|
@@ -10,15 +10,16 @@ from datetime import datetime, timedelta
|
|
10
10
|
|
11
11
|
from hestia_earth.schema import SiteSiteType, TermTermType
|
12
12
|
from hestia_earth.utils.lookup import (
|
13
|
-
download_lookup, get_table_value, column_name,
|
14
|
-
extract_grouped_data_closest_date, _is_missing_value, extract_grouped_data
|
13
|
+
download_lookup, get_table_value, column_name, _is_missing_value, extract_grouped_data
|
15
14
|
)
|
16
15
|
from hestia_earth.utils.model import filter_list_term_type
|
17
|
-
from hestia_earth.utils.tools import safe_parse_float, to_precision
|
16
|
+
from hestia_earth.utils.tools import safe_parse_float, to_precision
|
18
17
|
|
19
18
|
from hestia_earth.models.log import logRequirements, log_as_table, logShouldRun
|
19
|
+
from hestia_earth.models.utils.constant import DAYS_IN_YEAR
|
20
20
|
from hestia_earth.models.utils.management import _new_management
|
21
21
|
from hestia_earth.models.utils.term import get_lookup_value
|
22
|
+
from hestia_earth.models.utils.blank_node import _node_date, DatestrFormat, _gapfill_datestr, DatestrGapfillMode
|
22
23
|
from .utils import (
|
23
24
|
IPCC_LAND_USE_CATEGORY_ANNUAL,
|
24
25
|
IPCC_LAND_USE_CATEGORY_PERENNIAL,
|
@@ -34,8 +35,6 @@ from .utils import (
|
|
34
35
|
crop_ipcc_land_use_category,
|
35
36
|
)
|
36
37
|
from . import MODEL
|
37
|
-
from ..utils.blank_node import _node_date, DatestrFormat, _gapfill_datestr, DatestrGapfillMode
|
38
|
-
from ..utils.constant import DAYS_IN_YEAR
|
39
38
|
|
40
39
|
REQUIREMENTS = {
|
41
40
|
"Site": {
|
@@ -51,7 +50,10 @@ REQUIREMENTS = {
|
|
51
50
|
"@type": "Management",
|
52
51
|
"value": "",
|
53
52
|
"term.termType": "landCover",
|
54
|
-
"
|
53
|
+
"or": {
|
54
|
+
"startDate": "",
|
55
|
+
"endDate": ""
|
56
|
+
}
|
55
57
|
}
|
56
58
|
]
|
57
59
|
}
|
@@ -128,7 +130,7 @@ def _should_group_landCover(term: dict):
|
|
128
130
|
)
|
129
131
|
|
130
132
|
|
131
|
-
def get_changes(country_id: str, end_year: int) -> dict:
|
133
|
+
def get_changes(country_id: str, end_year: int) -> tuple[dict, bool]:
|
132
134
|
"""
|
133
135
|
For each entry in ALL_LAND_USE_TERMS, creates a key: value in output dictionary, also TOTAL
|
134
136
|
"""
|
@@ -137,14 +139,18 @@ def get_changes(country_id: str, end_year: int) -> dict:
|
|
137
139
|
land_use_term: safe_parse_float(
|
138
140
|
extract_grouped_data(
|
139
141
|
get_table_value(lookup, 'termid', country_id, column_name(land_use_term)),
|
140
|
-
str(end_year))
|
142
|
+
str(end_year)),
|
143
|
+
default=None
|
141
144
|
)
|
142
145
|
for land_use_term in ALL_LAND_USE_TERMS + [LAND_AREA]
|
143
146
|
}
|
144
|
-
|
145
|
-
|
147
|
+
missing_changes = any(val is None for val in changes_dict.values())
|
148
|
+
changes_dict = {k: v if v is not None else 0 for k, v in changes_dict.items()}
|
149
|
+
changes_dict[TOTAL_AGRICULTURAL_CHANGE] = (
|
150
|
+
float(changes_dict.get(TOTAL_CROPLAND, 0)) + float(changes_dict.get(PERMANENT_PASTURE, 0))
|
151
|
+
)
|
146
152
|
|
147
|
-
return changes_dict
|
153
|
+
return changes_dict, missing_changes
|
148
154
|
|
149
155
|
|
150
156
|
def _get_ratio_start_and_end_values(
|
@@ -156,7 +162,7 @@ def _get_ratio_start_and_end_values(
|
|
156
162
|
# expansion over twenty years / current area
|
157
163
|
lookup = download_lookup('region-faostatArea.csv')
|
158
164
|
table_value = get_table_value(lookup, 'termid', country_id, column_name(fao_name))
|
159
|
-
end_value = safe_parse_float(value=
|
165
|
+
end_value = safe_parse_float(value=extract_grouped_data(table_value, str(end_year)), default=None)
|
160
166
|
return max(0.0, _safe_divide(numerator=expansion, denominator=end_value))
|
161
167
|
|
162
168
|
|
@@ -387,9 +393,9 @@ def _get_harvested_area(country_id: str, year: int, faostat_name: str) -> float:
|
|
387
393
|
"""
|
388
394
|
lookup = download_lookup("region-crop-cropGroupingFaostatProduction-areaHarvested.csv")
|
389
395
|
return safe_parse_float(
|
390
|
-
value=
|
396
|
+
value=extract_grouped_data(
|
391
397
|
data=get_table_value(lookup, "termid", country_id, column_name(faostat_name)),
|
392
|
-
|
398
|
+
key=str(year)
|
393
399
|
),
|
394
400
|
default=None
|
395
401
|
)
|
@@ -407,7 +413,9 @@ def _get_term_id_for_crop(nodes: set, land_type: str) -> str:
|
|
407
413
|
)
|
408
414
|
|
409
415
|
|
410
|
-
def
|
416
|
+
def _run(site: dict, existing_nodes: list, percentage_transformed_from: dict):
|
417
|
+
start_year = _get_year_from_landCover(existing_nodes[0]) - DEFAULT_WINDOW_IN_YEARS
|
418
|
+
|
411
419
|
"""Creates a list of new management nodes, excluding any dates matching existing ones."""
|
412
420
|
existing_nodes_set = {
|
413
421
|
(node.get("term", {}).get("@id", ""), node.get("startDate"), node.get("endDate"))
|
@@ -429,6 +437,9 @@ def _run_make_management_nodes(existing_nodes: list, percentage_transformed_from
|
|
429
437
|
]
|
430
438
|
values = [v for v in values if v.get("land_management_key") not in existing_nodes_set]
|
431
439
|
|
440
|
+
for value in values:
|
441
|
+
logShouldRun(site, MODEL, value.get("term_id"), True, model_key=MODEL_KEY)
|
442
|
+
|
432
443
|
return [
|
433
444
|
_management(
|
434
445
|
term_id=value.get("term_id"),
|
@@ -515,13 +526,18 @@ def _get_net_expansion_cultivated_vs_harvested(annual_crops_net_expansion, chang
|
|
515
526
|
return net_expansion_cultivated_vs_harvested
|
516
527
|
|
517
528
|
|
529
|
+
def _get_year_from_landCover(node: dict):
|
530
|
+
date = node.get('startDate') or node.get('endDate')
|
531
|
+
return int(date[:4])
|
532
|
+
|
533
|
+
|
518
534
|
def _should_run_historical_land_use_change(site: dict, nodes: list, land_use_type: str) -> tuple[bool, dict]:
|
519
535
|
# Assume a single management node for single-cropping.
|
520
536
|
return _should_run_historical_land_use_change_single_crop(
|
521
537
|
site=site,
|
522
538
|
term=nodes[0].get("term", {}),
|
523
539
|
country_id=site.get("country", {}).get("@id"),
|
524
|
-
end_year=
|
540
|
+
end_year=_get_year_from_landCover(nodes[0]),
|
525
541
|
land_use_type=land_use_type
|
526
542
|
)
|
527
543
|
|
@@ -535,7 +551,7 @@ def _should_run_historical_land_use_change_single_crop(
|
|
535
551
|
) -> tuple[bool, dict]:
|
536
552
|
"""Calculate land use change percentages for a single management node/crop."""
|
537
553
|
# (C-H).
|
538
|
-
changes = get_changes(country_id=country_id, end_year=end_year)
|
554
|
+
changes, missing_changes = get_changes(country_id=country_id, end_year=end_year)
|
539
555
|
|
540
556
|
# (L). Estimate maximum forest loss
|
541
557
|
forest_loss = _estimate_maximum_forest_change(
|
@@ -665,6 +681,8 @@ def _should_run_historical_land_use_change_single_crop(
|
|
665
681
|
site_area[land_use_type] = 1 - sum(site_area.values())
|
666
682
|
|
667
683
|
sum_of_site_areas_is_100 = site_area_sum_to_100(site_area)
|
684
|
+
site_type_allowed = site.get("siteType") in SITE_TYPES
|
685
|
+
|
668
686
|
logRequirements(
|
669
687
|
log_node=site,
|
670
688
|
model=MODEL,
|
@@ -672,19 +690,13 @@ def _should_run_historical_land_use_change_single_crop(
|
|
672
690
|
model_key=MODEL_KEY,
|
673
691
|
land_use_type=land_use_type,
|
674
692
|
country_id=country_id,
|
693
|
+
changes=log_as_table(changes),
|
675
694
|
site_area=log_as_table(site_area),
|
676
|
-
sum_of_site_areas_is_100=sum_of_site_areas_is_100
|
695
|
+
sum_of_site_areas_is_100=sum_of_site_areas_is_100,
|
696
|
+
site_type_allowed=site_type_allowed
|
677
697
|
)
|
678
698
|
|
679
|
-
should_run = all(
|
680
|
-
[
|
681
|
-
site.get("siteType"),
|
682
|
-
country_id,
|
683
|
-
non_empty_value(term),
|
684
|
-
site.get("siteType") in SITE_TYPES,
|
685
|
-
sum_of_site_areas_is_100
|
686
|
-
]
|
687
|
-
)
|
699
|
+
should_run = all([not missing_changes, country_id, site_type_allowed, sum_of_site_areas_is_100])
|
688
700
|
logShouldRun(site, MODEL, term.get("@id"), should_run, model_key=MODEL_KEY)
|
689
701
|
|
690
702
|
return should_run, site_area
|
@@ -763,8 +775,4 @@ def run(site: dict) -> list:
|
|
763
775
|
if not _should_group_landCover(node)
|
764
776
|
]
|
765
777
|
should_run, site_area = _should_run(site=site, management_nodes=management_nodes)
|
766
|
-
return
|
767
|
-
existing_nodes=management_nodes,
|
768
|
-
percentage_transformed_from=site_area,
|
769
|
-
start_year=int(management_nodes[0].get("endDate")[:4]) - DEFAULT_WINDOW_IN_YEARS
|
770
|
-
) if should_run else []
|
778
|
+
return _run(site, management_nodes, site_area) if should_run else []
|