hestia-earth-models 0.73.7__py3-none-any.whl → 0.74.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of hestia-earth-models might be problematic. Click here for more details.
- hestia_earth/models/aware/scarcityWeightedWaterUse.py +7 -6
- hestia_earth/models/aware2_0/__init__.py +14 -0
- hestia_earth/models/aware2_0/scarcityWeightedWaterUse.py +115 -0
- hestia_earth/models/config/Cycle.json +121 -29
- hestia_earth/models/config/ImpactAssessment.json +240 -200
- hestia_earth/models/config/__init__.py +26 -2
- hestia_earth/models/cycle/animal/input/hestiaAggregatedData.py +2 -2
- hestia_earth/models/cycle/animal/input/properties.py +6 -5
- hestia_earth/models/cycle/animal/milkYield.py +8 -3
- hestia_earth/models/cycle/utils.py +6 -6
- hestia_earth/models/dammgen2009/noxToAirExcreta.py +11 -9
- hestia_earth/models/data/ecoinventV3/__init__.py +8 -26
- hestia_earth/models/ecoalimV9/cycle.py +51 -45
- hestia_earth/models/ecoalimV9/impact_assessment.py +63 -45
- hestia_earth/models/ecoalimV9/utils.py +21 -15
- hestia_earth/models/ecoinventV3/__init__.py +8 -140
- hestia_earth/models/ecoinventV3/cycle.py +140 -0
- hestia_earth/models/ecoinventV3/utils.py +28 -1
- hestia_earth/models/ecoinventV3AndEmberClimate/__init__.py +8 -137
- hestia_earth/models/ecoinventV3AndEmberClimate/cycle.py +144 -0
- hestia_earth/models/emepEea2019/n2OToAirFuelCombustionDirect.py +2 -2
- hestia_earth/models/emepEea2019/utils.py +2 -3
- hestia_earth/models/environmentalFootprintV3_1/environmentalFootprintSingleOverallScore.py +5 -7
- hestia_earth/models/frischknechtEtAl2000/ionisingRadiationKbqU235Eq.py +41 -43
- hestia_earth/models/geospatialDatabase/awareWaterBasinId.py +2 -2
- hestia_earth/models/geospatialDatabase/awareWaterBasinId_v1.py +45 -0
- hestia_earth/models/hestia/default_emissions.py +7 -7
- hestia_earth/models/hestia/default_resourceUse.py +7 -6
- hestia_earth/models/hestia/landCover.py +110 -12
- hestia_earth/models/hestia/seed_emissions.py +7 -3
- hestia_earth/models/hestia/utils.py +1 -0
- hestia_earth/models/hestia/waterSalinity.py +2 -3
- hestia_earth/models/impact_assessment/emissions.py +3 -5
- hestia_earth/models/ipcc2019/biocharOrganicCarbonPerHa.py +9 -3
- hestia_earth/models/ipcc2019/co2ToAirAboveGroundBiomassStockChange.py +1 -5
- hestia_earth/models/ipcc2019/co2ToAirBelowGroundBiomassStockChange.py +1 -5
- hestia_earth/models/ipcc2019/co2ToAirCarbonStockChange_utils.py +1 -33
- hestia_earth/models/ipcc2019/co2ToAirSoilOrganicCarbonStockChange.py +1 -5
- hestia_earth/models/ipcc2019/n2OToAirAquacultureSystemsIndirect.py +44 -0
- hestia_earth/models/ipcc2019/n2OToAirCropResidueBurningIndirect.py +43 -0
- hestia_earth/models/ipcc2019/n2OToAirCropResidueDecompositionIndirect.py +13 -70
- hestia_earth/models/ipcc2019/n2OToAirExcretaIndirect.py +13 -70
- hestia_earth/models/ipcc2019/n2OToAirFuelCombustionIndirect.py +43 -0
- hestia_earth/models/ipcc2019/n2OToAirInorganicFertiliserIndirect.py +13 -70
- hestia_earth/models/ipcc2019/n2OToAirNaturalVegetationBurningIndirect.py +43 -0
- hestia_earth/models/ipcc2019/n2OToAirOrganicFertiliserIndirect.py +13 -70
- hestia_earth/models/ipcc2019/n2OToAirOrganicSoilBurningIndirect.py +43 -0
- hestia_earth/models/ipcc2019/n2OToAirOrganicSoilCultivationIndirect.py +43 -0
- hestia_earth/models/ipcc2019/n2OToAir_indirect_emissions_utils.py +112 -0
- hestia_earth/models/ipcc2019/utils.py +0 -25
- hestia_earth/models/jarvisAndPain1994/n2ToAirExcreta.py +11 -9
- hestia_earth/models/linkedImpactAssessment/emissions.py +25 -16
- hestia_earth/models/linkedImpactAssessment/utils.py +5 -1
- hestia_earth/models/log.py +8 -3
- hestia_earth/models/mocking/search-results.json +1670 -1666
- hestia_earth/models/utils/__init__.py +3 -0
- hestia_earth/models/utils/background_emissions.py +121 -14
- hestia_earth/models/utils/blank_node.py +1 -11
- hestia_earth/models/utils/emission.py +18 -8
- hestia_earth/models/utils/feedipedia.py +2 -2
- hestia_earth/models/utils/impact_assessment.py +4 -6
- hestia_earth/models/utils/indicator.py +8 -1
- hestia_earth/models/utils/lookup.py +30 -18
- hestia_earth/models/utils/productivity.py +1 -1
- hestia_earth/models/version.py +1 -1
- hestia_earth/orchestrator/log.py +8 -3
- hestia_earth/orchestrator/strategies/merge/merge_list.py +41 -54
- {hestia_earth_models-0.73.7.dist-info → hestia_earth_models-0.74.0.dist-info}/METADATA +3 -3
- {hestia_earth_models-0.73.7.dist-info → hestia_earth_models-0.74.0.dist-info}/RECORD +99 -75
- tests/models/aware2_0/__init__.py +0 -0
- tests/models/aware2_0/test_scarcityWeightedWaterUse.py +58 -0
- tests/models/dammgen2009/test_noxToAirExcreta.py +2 -2
- tests/models/ecoalimV9/test_cycle.py +1 -1
- tests/models/ecoalimV9/test_impact_assessment.py +1 -1
- tests/models/ecoinventV3/__init__.py +0 -0
- tests/models/{test_ecoinventV3.py → ecoinventV3/test_cycle.py} +5 -5
- tests/models/ecoinventV3AndEmberClimate/__init__.py +0 -0
- tests/models/{test_ecoinventV3AndEmberClimate.py → ecoinventV3AndEmberClimate/test_cycle.py} +6 -4
- tests/models/environmentalFootprintV3_1/test_environmentalFootprintSingleOverallScore.py +2 -2
- tests/models/frischknechtEtAl2000/test_ionisingRadiationKbqU235Eq.py +18 -27
- tests/models/hestia/test_landCover.py +16 -6
- tests/models/ipcc2019/test_biocharOrganicCarbonPerHa.py +2 -1
- tests/models/ipcc2019/test_n2OToAirAquacultureSystemsIndirect.py +45 -0
- tests/models/ipcc2019/test_n2OToAirCropResidueBurningIndirect.py +45 -0
- tests/models/ipcc2019/test_n2OToAirCropResidueDecompositionIndirect.py +6 -32
- tests/models/ipcc2019/test_n2OToAirExcretaIndirect.py +6 -32
- tests/models/ipcc2019/test_n2OToAirFuelCombustionIndirect.py +45 -0
- tests/models/ipcc2019/test_n2OToAirInorganicFertiliserIndirect.py +6 -32
- tests/models/ipcc2019/test_n2OToAirNaturalVegetationBurningIndirect.py +45 -0
- tests/models/ipcc2019/test_n2OToAirOrganicFertiliserIndirect.py +6 -32
- tests/models/ipcc2019/test_n2OToAirOrganicSoilBurningIndirect.py +45 -0
- tests/models/ipcc2019/test_n2OToAirOrganicSoilCultivationIndirect.py +45 -0
- tests/models/ipcc2019/test_n2OToAir_indirect_emissions_utils.py +19 -0
- tests/models/site/pre_checks/test_cache_geospatialDatabase.py +4 -4
- tests/models/test_config.py +53 -7
- tests/models/utils/test_background_emissions.py +13 -0
- {hestia_earth_models-0.73.7.dist-info → hestia_earth_models-0.74.0.dist-info}/LICENSE +0 -0
- {hestia_earth_models-0.73.7.dist-info → hestia_earth_models-0.74.0.dist-info}/WHEEL +0 -0
- {hestia_earth_models-0.73.7.dist-info → hestia_earth_models-0.74.0.dist-info}/top_level.txt +0 -0
|
@@ -88,20 +88,18 @@ def _indicator_factors(impact_assessment: dict, indicator: dict):
|
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
|
|
91
|
-
def _map_input_ids(value: dict) -> set[str]:
|
|
92
|
-
return set(map(lambda i: i.get('@id'), value.get('inputs', [])))
|
|
93
|
-
|
|
94
|
-
|
|
95
91
|
def _count_duplicate_indicators(reference_indicator: dict, indicators: list) -> int:
|
|
96
92
|
"""
|
|
97
93
|
Counts the number of `reference_indicator` indicators found in a list of indicators.
|
|
98
|
-
Uses indicator.term.@id and indicator.
|
|
94
|
+
Uses indicator.term.@id and indicator.key to determine uniqueness.
|
|
99
95
|
"""
|
|
100
96
|
return sum([
|
|
101
97
|
1
|
|
102
98
|
for i in indicators
|
|
103
|
-
if (
|
|
104
|
-
|
|
99
|
+
if all([
|
|
100
|
+
i["term"]["@id"] == reference_indicator["term"]["@id"],
|
|
101
|
+
i.get("key", {}).get("@id") == reference_indicator.get("key", {}).get("@id")
|
|
102
|
+
])
|
|
105
103
|
])
|
|
106
104
|
|
|
107
105
|
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
from functools import reduce
|
|
2
1
|
from hestia_earth.schema import TermTermType
|
|
3
2
|
from hestia_earth.utils.lookup import get_table_value, download_lookup, column_name
|
|
4
3
|
from hestia_earth.utils.model import filter_list_term_type
|
|
5
4
|
from hestia_earth.utils.tools import flatten, list_sum
|
|
5
|
+
from hestia_earth.utils.blank_node import group_by_keys
|
|
6
6
|
|
|
7
7
|
from hestia_earth.models.log import logRequirements, logShouldRun, log_as_table
|
|
8
|
-
from hestia_earth.models.utils
|
|
8
|
+
from hestia_earth.models.utils import unique_values, _omit, _include
|
|
9
9
|
from hestia_earth.models.utils.indicator import _new_indicator
|
|
10
10
|
from hestia_earth.models.utils.lookup import _node_value
|
|
11
11
|
from . import MODEL
|
|
@@ -20,7 +20,7 @@ REQUIREMENTS = {
|
|
|
20
20
|
"ionisingCompoundsToWaterInputsProduction",
|
|
21
21
|
"ionisingCompoundsToSaltwaterInputsProduction"
|
|
22
22
|
],
|
|
23
|
-
"
|
|
23
|
+
"key": {"@type": "Term", "term.termType": "waste", "term.units": "kg"}
|
|
24
24
|
}]
|
|
25
25
|
}
|
|
26
26
|
}
|
|
@@ -34,6 +34,7 @@ LOOKUPS = {
|
|
|
34
34
|
RETURNS = {
|
|
35
35
|
"Indicator": [{
|
|
36
36
|
"value": "",
|
|
37
|
+
"key": "",
|
|
37
38
|
"inputs": ""
|
|
38
39
|
}]
|
|
39
40
|
}
|
|
@@ -41,31 +42,32 @@ RETURNS = {
|
|
|
41
42
|
TERM_ID = 'ionisingRadiationKbqU235Eq'
|
|
42
43
|
|
|
43
44
|
|
|
44
|
-
def
|
|
45
|
-
return input.get('units', '').startswith("kg") and input.get('termType', '') == TermTermType.WASTE.value
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
def _valid_emission(emission: dict) -> bool:
|
|
49
|
-
return len(emission.get('inputs', [])) == 1 and isinstance(_node_value(emission), (int, float))
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
def _indicator(value: float, input: dict) -> dict:
|
|
45
|
+
def _indicator(value: float, key: dict, inputs: list) -> dict:
|
|
53
46
|
indicator = _new_indicator(TERM_ID, MODEL)
|
|
47
|
+
indicator['key'] = key
|
|
54
48
|
indicator['value'] = value
|
|
55
|
-
|
|
49
|
+
if inputs:
|
|
50
|
+
indicator['inputs'] = inputs
|
|
56
51
|
return indicator
|
|
57
52
|
|
|
58
53
|
|
|
59
54
|
def _run(emissions: list) -> list[dict]:
|
|
60
55
|
indicators = [
|
|
61
|
-
_indicator(
|
|
62
|
-
|
|
63
|
-
|
|
56
|
+
_indicator(
|
|
57
|
+
value=list_sum([emission['value'] * emission['coefficient'] for emission in emission_group]),
|
|
58
|
+
key=emission_group[0]['key'],
|
|
59
|
+
inputs=unique_values(flatten([emission.get('inputs', []) for emission in emission_group]))
|
|
60
|
+
)
|
|
61
|
+
for emission_group in group_by_keys(emissions, ['key']).values()
|
|
64
62
|
]
|
|
65
63
|
|
|
66
64
|
return indicators
|
|
67
65
|
|
|
68
66
|
|
|
67
|
+
def _valid_key(term: dict) -> bool:
|
|
68
|
+
return term.get('units', '').startswith("kg") and term.get('termType') == TermTermType.WASTE.value
|
|
69
|
+
|
|
70
|
+
|
|
69
71
|
def _should_run(impact_assessment: dict) -> tuple[bool, list]:
|
|
70
72
|
emissions = [
|
|
71
73
|
emission
|
|
@@ -75,38 +77,34 @@ def _should_run(impact_assessment: dict) -> tuple[bool, list]:
|
|
|
75
77
|
|
|
76
78
|
has_emissions = bool(emissions)
|
|
77
79
|
|
|
78
|
-
emissions_unpacked =
|
|
79
|
-
|
|
80
|
-
[
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
]
|
|
96
|
-
)
|
|
80
|
+
emissions_unpacked = [
|
|
81
|
+
{
|
|
82
|
+
"key-term-id": emission['key'].get('@id'),
|
|
83
|
+
"key-term-type": emission['key'].get('termType'),
|
|
84
|
+
"key-is-valid": _valid_key(emission['key']),
|
|
85
|
+
"indicator-term-id": emission['term']['@id'],
|
|
86
|
+
"value": _node_value(emission),
|
|
87
|
+
"coefficient": get_table_value(
|
|
88
|
+
lookup=download_lookup(filename="waste.csv"),
|
|
89
|
+
col_match='termid',
|
|
90
|
+
col_match_with=emission['key'].get('@id'),
|
|
91
|
+
col_val=column_name(emission['term']['@id'])
|
|
92
|
+
)
|
|
93
|
+
} | _include(emission, ['key', 'inputs'])
|
|
94
|
+
for emission in emissions
|
|
95
|
+
if emission.get('key')
|
|
96
|
+
]
|
|
97
97
|
|
|
98
98
|
valid_emission_with_cf = [
|
|
99
99
|
em for em in emissions_unpacked if all([
|
|
100
100
|
em['coefficient'] is not None,
|
|
101
|
-
em['
|
|
102
|
-
em['indicator-input-is-valid'] is True
|
|
101
|
+
em['key-is-valid'] is True
|
|
103
102
|
])
|
|
104
103
|
]
|
|
105
104
|
|
|
106
|
-
|
|
105
|
+
valid_key_requirements = all([
|
|
107
106
|
all([
|
|
108
|
-
em['
|
|
109
|
-
em['indicator-input-is-valid']
|
|
107
|
+
em['key-is-valid']
|
|
110
108
|
])
|
|
111
109
|
for em in emissions_unpacked
|
|
112
110
|
])
|
|
@@ -117,12 +115,12 @@ def _should_run(impact_assessment: dict) -> tuple[bool, list]:
|
|
|
117
115
|
|
|
118
116
|
logRequirements(impact_assessment, model=MODEL, term=TERM_ID,
|
|
119
117
|
has_emissions=has_emissions,
|
|
120
|
-
|
|
118
|
+
valid_key_requirements=valid_key_requirements,
|
|
121
119
|
all_emissions_have_known_CF=all_emissions_have_known_cf,
|
|
122
|
-
emissions=log_as_table(emissions_unpacked)
|
|
120
|
+
emissions=log_as_table([_omit(v, ['key', 'inputs']) for v in emissions_unpacked])
|
|
123
121
|
)
|
|
124
122
|
|
|
125
|
-
should_run =
|
|
123
|
+
should_run = all([emissions_unpacked, valid_key_requirements])
|
|
126
124
|
|
|
127
125
|
logShouldRun(impact_assessment, MODEL, TERM_ID, should_run)
|
|
128
126
|
return should_run, valid_emission_with_cf
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
from hestia_earth.models.log import logRequirements, logShouldRun
|
|
2
|
+
from .utils import download, has_geospatial_data, should_download
|
|
3
|
+
from . import MODEL
|
|
4
|
+
|
|
5
|
+
REQUIREMENTS = {
|
|
6
|
+
"Site": {
|
|
7
|
+
"or": [
|
|
8
|
+
{"latitude": "", "longitude": ""},
|
|
9
|
+
{"boundary": {}},
|
|
10
|
+
{"region": {"@type": "Term", "termType": "region"}}
|
|
11
|
+
]
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
RETURNS = {
|
|
15
|
+
"The AWARE water basin identifier as a `string`": ""
|
|
16
|
+
}
|
|
17
|
+
MODEL_KEY = 'awareWaterBasinId_v1'
|
|
18
|
+
EE_PARAMS = {
|
|
19
|
+
'collection': 'AWARE',
|
|
20
|
+
'ee_type': 'vector',
|
|
21
|
+
'fields': 'Name'
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _download(site: dict):
|
|
26
|
+
return download(MODEL_KEY, site, EE_PARAMS)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _run(site: dict): return _download(site)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _should_run(site: dict):
|
|
33
|
+
contains_geospatial_data = has_geospatial_data(site)
|
|
34
|
+
below_max_area_size = should_download(MODEL_KEY, site)
|
|
35
|
+
|
|
36
|
+
logRequirements(site, model=MODEL, model_key=MODEL_KEY,
|
|
37
|
+
contains_geospatial_data=contains_geospatial_data,
|
|
38
|
+
below_max_area_size=below_max_area_size)
|
|
39
|
+
|
|
40
|
+
should_run = all([contains_geospatial_data, below_max_area_size])
|
|
41
|
+
logShouldRun(site, MODEL, None, should_run, model_key=MODEL_KEY)
|
|
42
|
+
return should_run
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def run(site: dict): return _run(site) if _should_run(site) else None
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
from hestia_earth.schema import EmissionMethodTier
|
|
2
2
|
from hestia_earth.utils.tools import flatten, safe_parse_float
|
|
3
|
-
from hestia_earth.utils.emission import cycle_emissions_in_system_boundary
|
|
4
3
|
|
|
5
|
-
from hestia_earth.models.log import logRequirements, logShouldRun
|
|
4
|
+
from hestia_earth.models.log import logRequirements, logShouldRun, debugValues
|
|
6
5
|
from hestia_earth.models.utils import _omit
|
|
7
|
-
from hestia_earth.models.utils.emission import _new_emission
|
|
6
|
+
from hestia_earth.models.utils.emission import _new_emission, background_emissions_in_system_boundary
|
|
8
7
|
from hestia_earth.models.utils.background_emissions import no_gap_filled_background_emissions
|
|
9
8
|
from hestia_earth.models.utils.term import get_lookup_value
|
|
10
9
|
from hestia_earth.models.utils.input import unique_background_inputs
|
|
@@ -51,10 +50,7 @@ def _default_value(input: dict):
|
|
|
51
50
|
|
|
52
51
|
|
|
53
52
|
def _run_input(cycle: dict):
|
|
54
|
-
required_emission_term_ids =
|
|
55
|
-
id for id in cycle_emissions_in_system_boundary(cycle)
|
|
56
|
-
if id.endswith('InputsProduction')
|
|
57
|
-
]
|
|
53
|
+
required_emission_term_ids = background_emissions_in_system_boundary(cycle)
|
|
58
54
|
|
|
59
55
|
def run(input: dict):
|
|
60
56
|
input_term = input.get('input').get('term')
|
|
@@ -63,6 +59,10 @@ def _run_input(cycle: dict):
|
|
|
63
59
|
|
|
64
60
|
for emission_id in required_emission_term_ids:
|
|
65
61
|
logShouldRun(cycle, MODEL, term_id, True, methodTier=TIER, model_key=MODEL_KEY, emission_id=emission_id)
|
|
62
|
+
debugValues(cycle, model=MODEL, term=emission_id,
|
|
63
|
+
value=value,
|
|
64
|
+
coefficient=1,
|
|
65
|
+
input=term_id)
|
|
66
66
|
|
|
67
67
|
return [
|
|
68
68
|
_emission(term_id, value, input_term) for term_id in required_emission_term_ids
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
from hestia_earth.schema import IndicatorMethodTier, TermTermType
|
|
2
2
|
from hestia_earth.utils.tools import flatten, safe_parse_float
|
|
3
|
-
from hestia_earth.utils.emission import emissions_in_system_boundary
|
|
4
3
|
|
|
5
|
-
from hestia_earth.models.log import logRequirements, logShouldRun
|
|
4
|
+
from hestia_earth.models.log import logRequirements, logShouldRun, debugValues
|
|
6
5
|
from hestia_earth.models.utils import _omit
|
|
6
|
+
from hestia_earth.models.utils.emission import background_emissions_in_system_boundary
|
|
7
7
|
from hestia_earth.models.utils.indicator import _new_indicator
|
|
8
8
|
from hestia_earth.models.utils.background_emissions import no_gap_filled_background_emissions
|
|
9
9
|
from hestia_earth.models.utils.term import get_lookup_value
|
|
@@ -54,10 +54,7 @@ def _default_value(input: dict):
|
|
|
54
54
|
|
|
55
55
|
|
|
56
56
|
def _run_input(impact: dict):
|
|
57
|
-
required_resourceUse_term_ids =
|
|
58
|
-
id for id in emissions_in_system_boundary(TermTermType.RESOURCEUSE)
|
|
59
|
-
if id.endswith('InputsProduction')
|
|
60
|
-
]
|
|
57
|
+
required_resourceUse_term_ids = background_emissions_in_system_boundary(impact, TermTermType.RESOURCEUSE)
|
|
61
58
|
|
|
62
59
|
def run(input: dict):
|
|
63
60
|
input_term = input.get('input').get('term')
|
|
@@ -66,6 +63,10 @@ def _run_input(impact: dict):
|
|
|
66
63
|
|
|
67
64
|
for emission_id in required_resourceUse_term_ids:
|
|
68
65
|
logShouldRun(impact, MODEL, term_id, True, methodTier=TIER, model_key=MODEL_KEY, emission_id=emission_id)
|
|
66
|
+
debugValues(impact, model=MODEL, term=emission_id,
|
|
67
|
+
value=value,
|
|
68
|
+
coefficient=1,
|
|
69
|
+
input=term_id)
|
|
69
70
|
|
|
70
71
|
return [
|
|
71
72
|
_indicator(term_id, value, input_term) for term_id in required_resourceUse_term_ids
|
|
@@ -29,6 +29,7 @@ from .utils import (
|
|
|
29
29
|
TOTAL_AGRICULTURAL_CHANGE,
|
|
30
30
|
ALL_LAND_USE_TERMS,
|
|
31
31
|
crop_ipcc_land_use_category,
|
|
32
|
+
LAND_USE_NAMES_FROM_ID
|
|
32
33
|
)
|
|
33
34
|
from . import MODEL
|
|
34
35
|
|
|
@@ -101,7 +102,8 @@ _BUILDING_SITE_TYPES = [
|
|
|
101
102
|
_DEFAULT_WINDOW_IN_YEARS = 20
|
|
102
103
|
_DATE_TOLERANCE_IN_YEARS = 2
|
|
103
104
|
_OUTPUT_SIGNIFICANT_DIGITS = 3
|
|
104
|
-
_ALLOWED_LAND_USE_TYPES = [ANNUAL_CROPLAND, PERMANENT_CROPLAND, PERMANENT_PASTURE]
|
|
105
|
+
_ALLOWED_LAND_USE_TYPES = [ANNUAL_CROPLAND, PERMANENT_CROPLAND, PERMANENT_PASTURE, TOTAL_CROPLAND]
|
|
106
|
+
_TOP_LEVEL_LAND_USE_TYPE_IDS = {"annualCropland", "permanentCropland", "permanentPasture", "cropland"}
|
|
105
107
|
_LOOKUP_EXPANSION = "region-crop-cropGroupingFaostatProduction-areaHarvestedUpTo20YearExpansion.csv"
|
|
106
108
|
_COMPLETE_CHANGES_OTHER_LAND = {
|
|
107
109
|
OTHER_LAND: 1,
|
|
@@ -214,7 +216,7 @@ def _should_group_landCover(management_node: dict):
|
|
|
214
216
|
)
|
|
215
217
|
|
|
216
218
|
|
|
217
|
-
def _get_changes(country_id: str, end_year: int) -> tuple[dict,
|
|
219
|
+
def _get_changes(country_id: str, end_year: int) -> tuple[dict, list]:
|
|
218
220
|
"""
|
|
219
221
|
For each entry in ALL_LAND_USE_TERMS, creates a key: value in output dictionary, also TOTAL
|
|
220
222
|
"""
|
|
@@ -250,6 +252,25 @@ def _get_ratio_start_and_end_values(
|
|
|
250
252
|
return max(0.0, _safe_divide(numerator=expansion, denominator=end_value))
|
|
251
253
|
|
|
252
254
|
|
|
255
|
+
def _get_ratio_between_land_use_types(
|
|
256
|
+
country_id: str,
|
|
257
|
+
end_year: int,
|
|
258
|
+
first_land_use_term: str,
|
|
259
|
+
second_land_use_term: str
|
|
260
|
+
) -> tuple:
|
|
261
|
+
"""Returns a tuple of the values of the two land use terms for the same country and year."""
|
|
262
|
+
return tuple([
|
|
263
|
+
safe_parse_float(value=extract_grouped_data(
|
|
264
|
+
get_region_lookup_value(
|
|
265
|
+
'region-faostatArea.csv', country_id, land_use_term, model=MODEL, key=MODEL_KEY
|
|
266
|
+
),
|
|
267
|
+
str(end_year)),
|
|
268
|
+
default=None
|
|
269
|
+
)
|
|
270
|
+
for land_use_term in [first_land_use_term, second_land_use_term]
|
|
271
|
+
])
|
|
272
|
+
|
|
273
|
+
|
|
253
274
|
def _estimate_maximum_forest_change(
|
|
254
275
|
forest_change: float, total_cropland_change: float, pasture_change: float, total_agricultural_change: float
|
|
255
276
|
):
|
|
@@ -384,7 +405,7 @@ def _estimate_conversions_to_permanent_cropland(
|
|
|
384
405
|
forest_loss_to_cropland: float,
|
|
385
406
|
other_land_loss_to_annual_cropland: float
|
|
386
407
|
) -> dict:
|
|
387
|
-
"""Estimate percentage of land sources when converted to:
|
|
408
|
+
"""Estimate percentage of land sources when converted to: Permanent cropland"""
|
|
388
409
|
|
|
389
410
|
def conversion_to_permanent_cropland(factor: float):
|
|
390
411
|
return _safe_divide(
|
|
@@ -642,15 +663,92 @@ def _scale_site_area_errors(site_area: dict) -> dict:
|
|
|
642
663
|
if negative_errors and abs(negative_errors[0]) < 1 and all([v < 1 for v in site_area.values()]) else site_area
|
|
643
664
|
|
|
644
665
|
|
|
666
|
+
def _new_landCover_term(new_land_use_term) -> dict:
|
|
667
|
+
return {
|
|
668
|
+
"@id": LAND_USE_TERMS_FOR_TRANSFORMATION[new_land_use_term][0],
|
|
669
|
+
"name": LAND_USE_TERMS_FOR_TRANSFORMATION[new_land_use_term][1],
|
|
670
|
+
"@type": "Term",
|
|
671
|
+
"termType": TermTermType.LANDCOVER.value
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
|
|
675
|
+
def _scaled_value(
|
|
676
|
+
permanent_crops_value: float,
|
|
677
|
+
annual_crops_value: float,
|
|
678
|
+
permanent_crops_factor: float,
|
|
679
|
+
annual_crops_factor: float,
|
|
680
|
+
):
|
|
681
|
+
total_area = permanent_crops_factor + annual_crops_factor
|
|
682
|
+
permanent_crops_scaled = permanent_crops_value * permanent_crops_factor / total_area
|
|
683
|
+
annual_crops_scaled = annual_crops_value * annual_crops_factor / total_area
|
|
684
|
+
return annual_crops_scaled + permanent_crops_scaled
|
|
685
|
+
|
|
686
|
+
|
|
687
|
+
def _scale_from_annual_and_permanent_results(
|
|
688
|
+
annual_cropland_results: dict,
|
|
689
|
+
permanent_cropland_results: dict,
|
|
690
|
+
annual_cropland_factor: float,
|
|
691
|
+
permanent_cropland_factor: float
|
|
692
|
+
) -> dict:
|
|
693
|
+
return {
|
|
694
|
+
land_key: _scaled_value(
|
|
695
|
+
permanent_crops_value=permanent_cropland_results[land_key],
|
|
696
|
+
annual_crops_value=land_value,
|
|
697
|
+
permanent_crops_factor=permanent_cropland_factor,
|
|
698
|
+
annual_crops_factor=annual_cropland_factor
|
|
699
|
+
)
|
|
700
|
+
for land_key, land_value in annual_cropland_results.items()
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
|
|
645
704
|
def _should_run_historical_land_use_change(site: dict, nodes: list, land_use_type: str) -> tuple[bool, dict]:
|
|
646
705
|
# Assume a single management node for single-cropping.
|
|
647
|
-
return
|
|
706
|
+
return (
|
|
707
|
+
_should_run_historical_land_use_change_total_cropland(site, nodes) if land_use_type == TOTAL_CROPLAND else
|
|
708
|
+
_should_run_historical_land_use_change_single_crop(
|
|
709
|
+
site=site,
|
|
710
|
+
term=nodes[0].get("term", {}),
|
|
711
|
+
country_id=site.get("country", {}).get("@id"),
|
|
712
|
+
end_year=_get_year_from_landCover(nodes[0]),
|
|
713
|
+
land_use_type=land_use_type
|
|
714
|
+
)
|
|
715
|
+
)
|
|
716
|
+
|
|
717
|
+
|
|
718
|
+
def _should_run_historical_land_use_change_total_cropland(site: dict, nodes: list) -> tuple[bool, dict]:
|
|
719
|
+
end_year = _get_year_from_landCover(nodes[0])
|
|
720
|
+
country_id = site.get("country", {}).get("@id")
|
|
721
|
+
|
|
722
|
+
# Run _should_run_historical_land_use_change_single_crop for annual and permanent
|
|
723
|
+
should_run_annual, areas_for_annual_cropland = _should_run_historical_land_use_change_single_crop(
|
|
648
724
|
site=site,
|
|
649
|
-
term=
|
|
650
|
-
country_id=
|
|
651
|
-
end_year=
|
|
652
|
-
land_use_type=
|
|
725
|
+
term=_new_landCover_term(ANNUAL_CROPLAND),
|
|
726
|
+
country_id=country_id,
|
|
727
|
+
end_year=end_year,
|
|
728
|
+
land_use_type=ANNUAL_CROPLAND
|
|
653
729
|
)
|
|
730
|
+
should_run_permanent, areas_for_permanent_cropland = _should_run_historical_land_use_change_single_crop(
|
|
731
|
+
site=site,
|
|
732
|
+
term=_new_landCover_term(PERMANENT_CROPLAND),
|
|
733
|
+
country_id=country_id,
|
|
734
|
+
end_year=end_year,
|
|
735
|
+
land_use_type=PERMANENT_CROPLAND
|
|
736
|
+
)
|
|
737
|
+
# Get current ratios ("Arable land" vs "Permanent crops")
|
|
738
|
+
annual_cropland_factor, permanent_crops_factor = _get_ratio_between_land_use_types(
|
|
739
|
+
country_id=country_id,
|
|
740
|
+
end_year=end_year,
|
|
741
|
+
first_land_use_term=ANNUAL_CROPLAND,
|
|
742
|
+
second_land_use_term=PERMANENT_CROPLAND
|
|
743
|
+
) if should_run_annual and should_run_permanent else tuple([0, 0])
|
|
744
|
+
scaled_results = _scale_from_annual_and_permanent_results(
|
|
745
|
+
annual_cropland_results=areas_for_annual_cropland,
|
|
746
|
+
permanent_cropland_results=areas_for_permanent_cropland,
|
|
747
|
+
annual_cropland_factor=annual_cropland_factor,
|
|
748
|
+
permanent_cropland_factor=permanent_crops_factor
|
|
749
|
+
) if should_run_annual and should_run_permanent else {}
|
|
750
|
+
# Scale results according to current ratios.
|
|
751
|
+
return all([should_run_annual, should_run_permanent]), scaled_results
|
|
654
752
|
|
|
655
753
|
|
|
656
754
|
def _should_run_historical_land_use_change_single_crop(
|
|
@@ -743,11 +841,11 @@ def _should_run_historical_land_use_change_single_crop(
|
|
|
743
841
|
|
|
744
842
|
# Cell E8
|
|
745
843
|
expansion_factor = _get_ratio_start_and_end_values(
|
|
746
|
-
expansion=changes[
|
|
747
|
-
fao_name=
|
|
844
|
+
expansion=changes[LAND_USE_NAMES_FROM_ID[term.get("@id")]],
|
|
845
|
+
fao_name=land_use_type,
|
|
748
846
|
country_id=country_id,
|
|
749
847
|
end_year=end_year
|
|
750
|
-
) if
|
|
848
|
+
) if term.get("@id") in _TOP_LEVEL_LAND_USE_TYPE_IDS else get_ratio_of_expanded_area(
|
|
751
849
|
country_id=country_id,
|
|
752
850
|
fao_name=_get_faostat_name(term),
|
|
753
851
|
end_year=end_year
|
|
@@ -774,7 +872,7 @@ def _should_run_historical_land_use_change_single_crop(
|
|
|
774
872
|
) if land_use_type == ANNUAL_CROPLAND else 1
|
|
775
873
|
)
|
|
776
874
|
|
|
777
|
-
# E10: Compare changes to annual/
|
|
875
|
+
# E10: Compare changes to annual/permanent cropland from net expansion.
|
|
778
876
|
net_expansion_cultivated_vs_harvested = _get_net_expansion_cultivated_vs_harvested(
|
|
779
877
|
annual_crops_net_expansion=annual_crops_net_expansion,
|
|
780
878
|
changes=changes,
|
|
@@ -109,10 +109,11 @@ def _map_group_emissions(group_id: str, required_emission_term_ids: list, emissi
|
|
|
109
109
|
missing_emissions = list(filter(lambda v: v not in emission_ids, emissions))
|
|
110
110
|
return {
|
|
111
111
|
'group-id': group_id,
|
|
112
|
+
'is-group-in-system-boundary': group_id in required_emission_term_ids,
|
|
112
113
|
'total-emissions': len(emissions),
|
|
113
114
|
'included-emissions': len(included_emissions),
|
|
114
115
|
'missing-emissions': '-'.join(missing_emissions),
|
|
115
|
-
'
|
|
116
|
+
'has-all-emissions': len(emissions) == len(included_emissions)
|
|
116
117
|
}
|
|
117
118
|
|
|
118
119
|
|
|
@@ -140,7 +141,10 @@ def _filter_emissions(cycle: dict):
|
|
|
140
141
|
for group_id in group_ids
|
|
141
142
|
]
|
|
142
143
|
# only keep groups that have all emissions present in the Cycle
|
|
143
|
-
valid_groups = list(filter(lambda group:
|
|
144
|
+
valid_groups = list(filter(lambda group: all([
|
|
145
|
+
group.get('has-all-emissions'),
|
|
146
|
+
group.get('is-group-in-system-boundary')
|
|
147
|
+
]), emissions_per_group))
|
|
144
148
|
valid_group_ids = set([v.get('group-id') for v in valid_groups])
|
|
145
149
|
|
|
146
150
|
# finally, only return emissions which groups are valid
|
|
@@ -283,7 +287,7 @@ def _should_run(cycle: dict):
|
|
|
283
287
|
# log failed emissions to show in the logs
|
|
284
288
|
for group in emissions_per_group:
|
|
285
289
|
emission_id = group.get('group-id')
|
|
286
|
-
if not group.get('
|
|
290
|
+
if not group.get('has-all-emissions') or not should_run:
|
|
287
291
|
logRequirements(cycle, model=MODEL, term=term_id, model_key=MODEL_KEY, emission_id=emission_id,
|
|
288
292
|
**group,
|
|
289
293
|
**_omit(seed_input, 'input'),
|
|
@@ -35,6 +35,7 @@ LAND_USE_TERMS_FOR_TRANSFORMATION = {
|
|
|
35
35
|
PERMANENT_PASTURE: ("permanentPasture", "Permanent pasture"),
|
|
36
36
|
OTHER_LAND: ("otherLand", OTHER_LAND) # Not used yet
|
|
37
37
|
}
|
|
38
|
+
LAND_USE_NAMES_FROM_ID = {v[0]: k for k, v in LAND_USE_TERMS_FOR_TRANSFORMATION.items()} | {"cropland": TOTAL_CROPLAND}
|
|
38
39
|
|
|
39
40
|
|
|
40
41
|
def crop_ipcc_land_use_category(
|
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
from functools import reduce
|
|
2
1
|
from hestia_earth.schema import MeasurementMethodClassification, TermTermType
|
|
3
2
|
from hestia_earth.utils.model import filter_list_term_type
|
|
4
3
|
from hestia_earth.utils.tools import safe_parse_float, list_average, non_empty_list
|
|
4
|
+
from hestia_earth.utils.blank_node import group_by_keys
|
|
5
5
|
|
|
6
6
|
from hestia_earth.models.log import logRequirements, logShouldRun, log_as_table
|
|
7
7
|
from hestia_earth.models.utils.measurement import _new_measurement
|
|
8
8
|
from hestia_earth.models.utils.site import related_cycles
|
|
9
9
|
from hestia_earth.models.utils.term import get_lookup_value
|
|
10
|
-
from hestia_earth.models.utils.blank_node import group_by_keys
|
|
11
10
|
from . import MODEL
|
|
12
11
|
|
|
13
12
|
REQUIREMENTS = {
|
|
@@ -75,7 +74,7 @@ def _should_run(site: dict):
|
|
|
75
74
|
|
|
76
75
|
def run(site: dict):
|
|
77
76
|
should_run, values = _should_run(site)
|
|
78
|
-
grouped_values =
|
|
77
|
+
grouped_values = group_by_keys(values, ['start-date', 'end-date'])
|
|
79
78
|
return non_empty_list([
|
|
80
79
|
_measurement(
|
|
81
80
|
list_average([v.get('lookup-value') for v in value if v.get('lookup-value')], default=0),
|
|
@@ -2,6 +2,7 @@ from hestia_earth.utils.tools import list_sum
|
|
|
2
2
|
from hestia_earth.utils.emission import cycle_emissions_in_system_boundary
|
|
3
3
|
|
|
4
4
|
from hestia_earth.models.log import logRequirements, logShouldRun
|
|
5
|
+
from hestia_earth.models.utils import _include
|
|
5
6
|
from hestia_earth.models.utils.impact_assessment import get_product, convert_value_from_cycle
|
|
6
7
|
from hestia_earth.models.utils.indicator import _new_indicator
|
|
7
8
|
from . import MODEL
|
|
@@ -44,11 +45,8 @@ def _indicator(impact_assessment: dict, product: dict):
|
|
|
44
45
|
|
|
45
46
|
if len(emission.get('inputs', [])):
|
|
46
47
|
indicator['inputs'] = emission['inputs']
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
if emission.get('transformation'):
|
|
50
|
-
indicator['transformation'] = emission.get('transformation')
|
|
51
|
-
return indicator
|
|
48
|
+
|
|
49
|
+
return indicator | _include(emission, ['animals', 'operation', 'transformation', 'country', 'key'])
|
|
52
50
|
return run
|
|
53
51
|
|
|
54
52
|
|
|
@@ -57,12 +57,16 @@ RETURNS = {
|
|
|
57
57
|
"statsDefinition": "simulated",
|
|
58
58
|
"observations": "",
|
|
59
59
|
"dates": "",
|
|
60
|
+
"depthUpper": "",
|
|
61
|
+
"depthLower": "",
|
|
60
62
|
"methodClassification": "tier 1 model"
|
|
61
63
|
}]
|
|
62
64
|
}
|
|
63
65
|
TERM_ID = 'biocharOrganicCarbonPerHa'
|
|
64
66
|
|
|
65
67
|
_ITERATIONS = 1000
|
|
68
|
+
_DEPTH_UPPER = 0
|
|
69
|
+
_DEPTH_LOWER = 30
|
|
66
70
|
_METHOD_CLASSIFICATION = MeasurementMethodClassification.TIER_1_MODEL.value
|
|
67
71
|
_STATS_DEFINITION = MeasurementStatsDefinition.SIMULATED.value
|
|
68
72
|
|
|
@@ -172,7 +176,7 @@ def _compile_inventory(
|
|
|
172
176
|
cycle_data = {
|
|
173
177
|
cycle.get("@id"): {
|
|
174
178
|
"biochar_nodes": filter_list_term_type(cycle.get("inputs", []), TermTermType.BIOCHAR),
|
|
175
|
-
**{
|
|
179
|
+
**{k: v for k in COPY_FIELDS if (v := cycle.get(k)) is not None}
|
|
176
180
|
} for cycle in cycles
|
|
177
181
|
}
|
|
178
182
|
|
|
@@ -201,7 +205,7 @@ def _compile_inventory(
|
|
|
201
205
|
[
|
|
202
206
|
{
|
|
203
207
|
"total_oc": total_oc.get(id, 0),
|
|
204
|
-
**
|
|
208
|
+
**data
|
|
205
209
|
} for id, data in cycle_data.items()
|
|
206
210
|
],
|
|
207
211
|
include_spillovers=True
|
|
@@ -427,9 +431,11 @@ def _measurement(
|
|
|
427
431
|
"statsDefinition": statsDefinition,
|
|
428
432
|
"observations": observations,
|
|
429
433
|
"dates": dates,
|
|
434
|
+
"depthUpper": _DEPTH_UPPER,
|
|
435
|
+
"depthLower": _DEPTH_LOWER,
|
|
430
436
|
"methodClassification": _METHOD_CLASSIFICATION
|
|
431
437
|
}
|
|
432
438
|
measurement = _new_measurement(TERM_ID, MODEL) | {
|
|
433
|
-
key: value for key, value in update_dict.items() if value
|
|
439
|
+
key: value for key, value in update_dict.items() if value is not None
|
|
434
440
|
}
|
|
435
441
|
return measurement
|
|
@@ -78,9 +78,7 @@ def _emission(
|
|
|
78
78
|
min: list[float] = None,
|
|
79
79
|
max: list[float] = None,
|
|
80
80
|
statsDefinition: str = None,
|
|
81
|
-
observations: list[int] = None
|
|
82
|
-
start_date: str,
|
|
83
|
-
end_date: str
|
|
81
|
+
observations: list[int] = None
|
|
84
82
|
) -> dict:
|
|
85
83
|
"""
|
|
86
84
|
Create an emission node based on the provided value and method tier.
|
|
@@ -108,8 +106,6 @@ def _emission(
|
|
|
108
106
|
"max": max,
|
|
109
107
|
"statsDefinition": statsDefinition,
|
|
110
108
|
"observations": observations,
|
|
111
|
-
"startDate": start_date,
|
|
112
|
-
"endDate": end_date,
|
|
113
109
|
"methodTier": method_tier.value,
|
|
114
110
|
}
|
|
115
111
|
emission = _new_emission(term_id, MODEL) | {
|