hestia-earth-models 0.71.0__py3-none-any.whl → 0.72.1__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/config/ImpactAssessment.json +1964 -1918
- hestia_earth/models/config/Site.json +8 -0
- hestia_earth/models/environmentalFootprintV3_1/environmentalFootprintSingleOverallScore.py +19 -3
- hestia_earth/models/geospatialDatabase/histosol.py +14 -7
- hestia_earth/models/hestia/histosol.py +53 -0
- hestia_earth/models/hestia/landCover.py +10 -2
- hestia_earth/models/ipcc2019/co2ToAirUreaHydrolysis.py +27 -27
- hestia_earth/models/ipcc2019/organicCarbonPerHa_tier_1.py +22 -7
- hestia_earth/models/mocking/search-results.json +1297 -1297
- hestia_earth/models/utils/aggregated.py +3 -3
- hestia_earth/models/utils/measurement.py +16 -3
- hestia_earth/models/version.py +1 -1
- {hestia_earth_models-0.71.0.dist-info → hestia_earth_models-0.72.1.dist-info}/METADATA +2 -2
- {hestia_earth_models-0.71.0.dist-info → hestia_earth_models-0.72.1.dist-info}/RECORD +22 -20
- tests/models/environmentalFootprintV3_1/test_environmentalFootprintSingleOverallScore.py +22 -2
- tests/models/geospatialDatabase/test_histosol.py +21 -20
- tests/models/hestia/test_histosol.py +24 -0
- tests/models/ipcc2019/test_co2ToAirUreaHydrolysis.py +10 -34
- tests/models/ipcc2019/test_organicCarbonPerHa_tier_1.py +4 -3
- {hestia_earth_models-0.71.0.dist-info → hestia_earth_models-0.72.1.dist-info}/LICENSE +0 -0
- {hestia_earth_models-0.71.0.dist-info → hestia_earth_models-0.72.1.dist-info}/WHEEL +0 -0
- {hestia_earth_models-0.71.0.dist-info → hestia_earth_models-0.72.1.dist-info}/top_level.txt +0 -0
@@ -88,6 +88,14 @@
|
|
88
88
|
"runStrategy": "add_blank_node_if_missing",
|
89
89
|
"mergeStrategy": "list",
|
90
90
|
"stage": 1
|
91
|
+
},
|
92
|
+
{
|
93
|
+
"key": "measurements",
|
94
|
+
"model": "hestia",
|
95
|
+
"value": "histosol",
|
96
|
+
"runStrategy": "add_blank_node_if_missing",
|
97
|
+
"mergeStrategy": "list",
|
98
|
+
"stage": 1
|
91
99
|
}
|
92
100
|
],
|
93
101
|
[
|
@@ -1,8 +1,7 @@
|
|
1
|
-
from typing import List, Optional, Tuple
|
2
|
-
|
3
1
|
from hestia_earth.schema import TermTermType
|
4
2
|
from hestia_earth.utils.model import filter_list_term_type
|
5
3
|
from hestia_earth.utils.tools import list_sum
|
4
|
+
from typing import List, Optional, Tuple
|
6
5
|
|
7
6
|
from hestia_earth.models.log import logRequirements, logShouldRun, log_as_table, debugValues
|
8
7
|
from hestia_earth.models.utils.blank_node import get_lookup_value
|
@@ -89,6 +88,23 @@ def _indicator_factors(impact_assessment: dict, indicator: dict):
|
|
89
88
|
}
|
90
89
|
|
91
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
|
+
def _count_duplicate_indicators(reference_indicator: dict, indicators: list) -> int:
|
96
|
+
"""
|
97
|
+
Counts the number of `reference_indicator` indicators found in a list of indicators.
|
98
|
+
Uses indicator.term.@id and indicator.inputs to determine uniqueness.
|
99
|
+
"""
|
100
|
+
return sum([
|
101
|
+
1
|
102
|
+
for i in indicators
|
103
|
+
if (i["term"]["@id"] == reference_indicator["term"]["@id"]) and (
|
104
|
+
_map_input_ids(i) == _map_input_ids(reference_indicator))
|
105
|
+
])
|
106
|
+
|
107
|
+
|
92
108
|
def _indicator(value: float) -> dict:
|
93
109
|
indicator = _new_indicator(TERM_ID, MODEL)
|
94
110
|
indicator['value'] = value
|
@@ -115,7 +131,7 @@ def _should_run(impact_assessment: dict) -> Tuple[bool, list[dict]]:
|
|
115
131
|
processed_indicators = [{
|
116
132
|
"indicator": indicator['term']['@id'],
|
117
133
|
"valid-indicator": _valid_indicator(indicator),
|
118
|
-
"one-indicator-for-category":
|
134
|
+
"one-indicator-for-category": _count_duplicate_indicators(indicator, indicators) == 1,
|
119
135
|
"indicator-pef-category": indicator.get('term', {}).get('@id'),
|
120
136
|
} | _indicator_factors(impact_assessment, indicator) for indicator in indicators]
|
121
137
|
|
@@ -1,7 +1,7 @@
|
|
1
|
-
from hestia_earth.schema import MeasurementMethodClassification
|
1
|
+
from hestia_earth.schema import MeasurementMethodClassification
|
2
2
|
|
3
3
|
from hestia_earth.models.log import logRequirements, logShouldRun
|
4
|
-
from hestia_earth.models.utils.measurement import _new_measurement
|
4
|
+
from hestia_earth.models.utils.measurement import _new_measurement, total_other_soilType_value
|
5
5
|
from hestia_earth.models.utils.source import get_source
|
6
6
|
from .utils import download, has_geospatial_data, should_download
|
7
7
|
from . import MODEL
|
@@ -14,7 +14,13 @@ REQUIREMENTS = {
|
|
14
14
|
{"region": {"@type": "Term", "termType": "region"}}
|
15
15
|
],
|
16
16
|
"none": {
|
17
|
-
"measurements": [{
|
17
|
+
"measurements": [{
|
18
|
+
"@type": "Measurement",
|
19
|
+
"value": "100",
|
20
|
+
"depthUpper": "0",
|
21
|
+
"depthLower": "30",
|
22
|
+
"term.termType": "soilType"
|
23
|
+
}]
|
18
24
|
}
|
19
25
|
}
|
20
26
|
}
|
@@ -50,17 +56,18 @@ def _run(site: dict):
|
|
50
56
|
|
51
57
|
|
52
58
|
def _should_run(site: dict):
|
53
|
-
measurements = site.get('measurements', [])
|
54
|
-
no_soil_type = all([m.get('term', {}).get('termType') != TermTermType.SOILTYPE.value for m in measurements])
|
55
59
|
contains_geospatial_data = has_geospatial_data(site)
|
56
60
|
below_max_area_size = should_download(TERM_ID, site)
|
57
61
|
|
62
|
+
total_measurements_value = total_other_soilType_value(site.get('measurements', []), TERM_ID)
|
63
|
+
|
58
64
|
logRequirements(site, model=MODEL, term=TERM_ID,
|
59
65
|
contains_geospatial_data=contains_geospatial_data,
|
60
66
|
below_max_area_size=below_max_area_size,
|
61
|
-
|
67
|
+
total_soilType_measurements_value=total_measurements_value,
|
68
|
+
total_soilType_measurements_value_is_0=total_measurements_value == 0)
|
62
69
|
|
63
|
-
should_run = all([contains_geospatial_data, below_max_area_size,
|
70
|
+
should_run = all([contains_geospatial_data, below_max_area_size, total_measurements_value == 0])
|
64
71
|
logShouldRun(site, MODEL, TERM_ID, should_run)
|
65
72
|
return should_run
|
66
73
|
|
@@ -0,0 +1,53 @@
|
|
1
|
+
from hestia_earth.schema import MeasurementMethodClassification
|
2
|
+
|
3
|
+
from hestia_earth.models.log import logRequirements, logShouldRun
|
4
|
+
from hestia_earth.models.utils.measurement import _new_measurement, total_other_soilType_value
|
5
|
+
from . import MODEL
|
6
|
+
|
7
|
+
REQUIREMENTS = {
|
8
|
+
"Site": {
|
9
|
+
"measurements": [{
|
10
|
+
"@type": "Measurement",
|
11
|
+
"value": "100",
|
12
|
+
"depthUpper": "0",
|
13
|
+
"depthLower": "30",
|
14
|
+
"term.termType": "soilType"
|
15
|
+
}]
|
16
|
+
}
|
17
|
+
}
|
18
|
+
RETURNS = {
|
19
|
+
"Measurement": [{
|
20
|
+
"value": "0",
|
21
|
+
"depthUpper": "0",
|
22
|
+
"depthLower": "30",
|
23
|
+
"methodClassification": "modelled using other measurements"
|
24
|
+
}]
|
25
|
+
}
|
26
|
+
LOOKUPS = {
|
27
|
+
"soilType": "sumMax100Group"
|
28
|
+
}
|
29
|
+
TERM_ID = 'histosol'
|
30
|
+
|
31
|
+
|
32
|
+
def _measurement():
|
33
|
+
measurement = _new_measurement(TERM_ID)
|
34
|
+
measurement['value'] = [0]
|
35
|
+
measurement['depthUpper'] = 0
|
36
|
+
measurement['depthLower'] = 30
|
37
|
+
measurement['methodClassification'] = MeasurementMethodClassification.MODELLED_USING_OTHER_MEASUREMENTS.value
|
38
|
+
return measurement
|
39
|
+
|
40
|
+
|
41
|
+
def _should_run(site: dict):
|
42
|
+
total_measurements_value = total_other_soilType_value(site.get('measurements', []), TERM_ID)
|
43
|
+
|
44
|
+
logRequirements(site, model=MODEL, term=TERM_ID,
|
45
|
+
total_soilType_measurements_value=total_measurements_value,
|
46
|
+
total_soilType_measurements_value_is_100=total_measurements_value == 100)
|
47
|
+
|
48
|
+
should_run = all([total_measurements_value == 100])
|
49
|
+
logShouldRun(site, MODEL, TERM_ID, should_run)
|
50
|
+
return should_run
|
51
|
+
|
52
|
+
|
53
|
+
def run(site: dict): return [_measurement()] if _should_run(site) else []
|
@@ -148,7 +148,7 @@ def cap_values(dictionary: dict, lower_limit: float = 0, upper_limit: float = 1)
|
|
148
148
|
|
149
149
|
def site_area_sum_to_100(dict_of_percentages: dict):
|
150
150
|
return False if dict_of_percentages == {} else (
|
151
|
-
math.isclose(sum(dict_of_percentages.values()), 1.0, rel_tol=0.
|
151
|
+
math.isclose(sum(dict_of_percentages.values()), 1.0, rel_tol=0.05) or
|
152
152
|
math.isclose(sum(dict_of_percentages.values()), 0.0, rel_tol=0.01)
|
153
153
|
)
|
154
154
|
|
@@ -581,6 +581,14 @@ def _get_year_from_landCover(node: dict):
|
|
581
581
|
return int(date[:4])
|
582
582
|
|
583
583
|
|
584
|
+
def _scale_site_area_errors(site_area: dict) -> dict:
|
585
|
+
"""Redistribute the result of any rounding error in proportion to the other land use types."""
|
586
|
+
# Positive errors would not have been capped, so won't be missing.
|
587
|
+
negative_errors = [v for v in site_area.values() if v < 0.0]
|
588
|
+
return {k: v + negative_errors[0] * v for k, v in site_area.items()} \
|
589
|
+
if negative_errors and abs(negative_errors[0]) < 1 and all([v < 1 for v in site_area.values()]) else site_area
|
590
|
+
|
591
|
+
|
584
592
|
def _should_run_historical_land_use_change(site: dict, nodes: list, land_use_type: str) -> tuple[bool, dict]:
|
585
593
|
# Assume a single management node for single-cropping.
|
586
594
|
return _should_run_historical_land_use_change_single_crop(
|
@@ -728,7 +736,7 @@ def _should_run_historical_land_use_change_single_crop(
|
|
728
736
|
if land_type != land_use_type
|
729
737
|
}
|
730
738
|
site_area[land_use_type] = 1 - sum(site_area.values())
|
731
|
-
capped_site_area = cap_values(dictionary=site_area
|
739
|
+
capped_site_area = cap_values(dictionary=_scale_site_area_errors(site_area))
|
732
740
|
|
733
741
|
sum_of_site_areas_is_100 = site_area_sum_to_100(capped_site_area)
|
734
742
|
site_type_allowed = site.get("siteType") in SITE_TYPES
|
@@ -2,7 +2,8 @@ from hestia_earth.schema import EmissionMethodTier
|
|
2
2
|
from hestia_earth.utils.tools import list_sum, safe_parse_float, non_empty_list
|
3
3
|
from hestia_earth.utils.model import find_term_match
|
4
4
|
|
5
|
-
from hestia_earth.models.log import
|
5
|
+
from hestia_earth.models.log import logRequirements, logShouldRun, log_as_table
|
6
|
+
from hestia_earth.models.utils import multiply_values
|
6
7
|
from hestia_earth.models.utils.completeness import _is_term_type_complete
|
7
8
|
from hestia_earth.models.utils.emission import _new_emission
|
8
9
|
from hestia_earth.models.utils.term import get_urea_terms
|
@@ -36,66 +37,65 @@ def _emission(value: float):
|
|
36
37
|
return emission
|
37
38
|
|
38
39
|
|
39
|
-
def
|
40
|
-
|
41
|
-
term_id = data.get('id')
|
42
|
-
values = data.get('values')
|
43
|
-
coeff = safe_parse_float(get_term_lookup(term_id, LOOKUPS['inorganicFertiliser'][2]), 1)
|
44
|
-
debugValues(cycle, model=MODEL, term=TERM_ID,
|
45
|
-
product=term_id,
|
46
|
-
coefficient=coeff)
|
47
|
-
return list_sum(values) * coeff
|
48
|
-
return exec
|
40
|
+
def _urea_emission_factor(term_id: str):
|
41
|
+
return safe_parse_float(get_term_lookup(term_id, LOOKUPS['inorganicFertiliser'][2]), None)
|
49
42
|
|
50
43
|
|
51
|
-
def _run(
|
52
|
-
value = list_sum(
|
44
|
+
def _run(urea_values: list):
|
45
|
+
value = list_sum([v.get('value') * v.get('factor') for v in urea_values if v.get('value')])
|
53
46
|
return [_emission(value)]
|
54
47
|
|
55
48
|
|
56
|
-
def
|
49
|
+
def _get_urea_value(cycle: dict, inputs: list, term_id: str):
|
57
50
|
inputs = list(filter(lambda i: i.get('term', {}).get('@id') == term_id, inputs))
|
58
51
|
values = [list_sum(i.get('value'), 0) for i in inputs if len(i.get('value', [])) > 0]
|
59
|
-
return
|
52
|
+
return list_sum(values, default=None)
|
60
53
|
|
61
54
|
|
62
55
|
def _should_run(cycle: dict):
|
56
|
+
is_fertiliser_complete = _is_term_type_complete(cycle, 'fertiliser')
|
63
57
|
inputs = cycle.get('inputs', [])
|
64
58
|
term_ids = get_urea_terms()
|
65
59
|
|
66
60
|
country_id = cycle.get('site', {}).get('country', {}).get('@id')
|
67
61
|
urea_share = get_country_breakdown(MODEL, TERM_ID, country_id, LOOKUPS['inorganicFertiliser'][0])
|
68
62
|
uan_share = get_country_breakdown(MODEL, TERM_ID, country_id, LOOKUPS['inorganicFertiliser'][1])
|
69
|
-
urea_unspecified_as_n = list_sum(find_term_match(inputs, UNSPECIFIED_TERM_ID).get('value', []))
|
63
|
+
urea_unspecified_as_n = list_sum(find_term_match(inputs, UNSPECIFIED_TERM_ID).get('value', []), default=None)
|
70
64
|
|
71
65
|
urea_values = [
|
72
66
|
{
|
73
|
-
'id':
|
74
|
-
'
|
75
|
-
|
67
|
+
'id': term_id,
|
68
|
+
'value': _get_urea_value(cycle, inputs, term_id),
|
69
|
+
'factor': _urea_emission_factor(term_id)
|
70
|
+
} for term_id in term_ids
|
76
71
|
] + non_empty_list([
|
77
72
|
{
|
78
73
|
'id': 'ureaKgN',
|
79
|
-
'
|
80
|
-
|
74
|
+
'value': multiply_values([urea_unspecified_as_n, urea_share]),
|
75
|
+
'factor': _urea_emission_factor('ureaKgN')
|
76
|
+
},
|
81
77
|
{
|
82
78
|
'id': 'ureaAmmoniumNitrateKgN',
|
83
|
-
'
|
84
|
-
|
85
|
-
|
86
|
-
|
79
|
+
'value': multiply_values([urea_unspecified_as_n, uan_share]),
|
80
|
+
'factor': _urea_emission_factor('ureaAmmoniumNitrateKgN')
|
81
|
+
}
|
82
|
+
] if urea_unspecified_as_n is not None else [])
|
83
|
+
|
84
|
+
has_urea_value = any([data.get('value') is not None for data in urea_values])
|
87
85
|
|
88
86
|
logRequirements(cycle, model=MODEL, term=TERM_ID,
|
87
|
+
is_term_type_fertiliser_complete=is_fertiliser_complete,
|
89
88
|
has_urea_value=has_urea_value,
|
89
|
+
urea_values=log_as_table(urea_values),
|
90
90
|
urea_unspecified_as_n=urea_unspecified_as_n,
|
91
91
|
urea_share=urea_share,
|
92
92
|
uan_share=uan_share)
|
93
93
|
|
94
|
-
should_run =
|
94
|
+
should_run = has_urea_value or is_fertiliser_complete
|
95
95
|
logShouldRun(cycle, MODEL, TERM_ID, should_run, methodTier=TIER)
|
96
96
|
return should_run, urea_values
|
97
97
|
|
98
98
|
|
99
99
|
def run(cycle: dict):
|
100
100
|
should_run, urea_values = _should_run(cycle)
|
101
|
-
return _run(
|
101
|
+
return _run(urea_values) if should_run else []
|
@@ -3,17 +3,18 @@ from functools import reduce
|
|
3
3
|
from numpy import empty_like, random, vstack
|
4
4
|
from numpy.typing import NDArray
|
5
5
|
from pydash.objects import merge
|
6
|
-
from typing import Callable, Optional, Union
|
6
|
+
from typing import Callable, Literal, Optional, Union
|
7
7
|
|
8
8
|
from hestia_earth.schema import MeasurementMethodClassification, SiteSiteType, TermTermType
|
9
|
-
from hestia_earth.utils.model import find_term_match, filter_list_term_type
|
10
9
|
from hestia_earth.utils.blank_node import get_node_value
|
10
|
+
from hestia_earth.utils.model import find_term_match, filter_list_term_type
|
11
|
+
from hestia_earth.utils.tools import non_empty_list
|
11
12
|
|
12
13
|
from hestia_earth.models.utils import split_on_condition
|
13
14
|
from hestia_earth.models.utils.array_builders import gen_seed
|
14
15
|
from hestia_earth.models.utils.blank_node import (
|
15
|
-
cumulative_nodes_match, cumulative_nodes_lookup_match, cumulative_nodes_term_match,
|
16
|
-
node_term_match, group_nodes_by_year, validate_start_date_end_date
|
16
|
+
cumulative_nodes_match, cumulative_nodes_lookup_match, cumulative_nodes_term_match, group_by_term,
|
17
|
+
node_lookup_match, node_term_match, group_nodes_by_year, validate_start_date_end_date
|
17
18
|
)
|
18
19
|
from hestia_earth.models.utils.ecoClimateZone import EcoClimateZone, get_eco_climate_zone_value
|
19
20
|
from hestia_earth.models.utils.descriptive_stats import calc_descriptive_stats
|
@@ -1031,8 +1032,8 @@ def _assign_ipcc_soil_category(
|
|
1031
1032
|
IpccSoilCategory
|
1032
1033
|
The assigned IPCC soil category.
|
1033
1034
|
"""
|
1034
|
-
soil_types =
|
1035
|
-
usda_soil_types =
|
1035
|
+
soil_types = _get_soil_type_measurements(measurement_nodes, TermTermType.SOILTYPE)
|
1036
|
+
usda_soil_types = _get_soil_type_measurements(measurement_nodes, TermTermType.USDASOILTYPE)
|
1036
1037
|
|
1037
1038
|
clay_content = get_node_value(find_term_match(measurement_nodes, _CLAY_CONTENT_TERM_ID))
|
1038
1039
|
sand_content = get_node_value(find_term_match(measurement_nodes, _SAND_CONTENT_TERM_ID))
|
@@ -1053,6 +1054,20 @@ def _assign_ipcc_soil_category(
|
|
1053
1054
|
) if len(soil_types) > 0 or len(usda_soil_types) > 0 else default
|
1054
1055
|
|
1055
1056
|
|
1057
|
+
def _get_soil_type_measurements(
|
1058
|
+
nodes: list[dict], term_type: Literal[TermTermType.SOILTYPE, TermTermType.USDASOILTYPE]
|
1059
|
+
) -> list[dict]:
|
1060
|
+
grouped = group_by_term(filter_list_term_type(nodes, term_type))
|
1061
|
+
|
1062
|
+
def depth_distance(node):
|
1063
|
+
upper, lower = node.get("depthUpper", 0), node.get("depthLower", 100)
|
1064
|
+
return abs(upper - DEPTH_UPPER) + abs(lower - DEPTH_LOWER)
|
1065
|
+
|
1066
|
+
return non_empty_list(
|
1067
|
+
min(nodes_, key=depth_distance) for key in grouped if (nodes_ := grouped.get(key, []))
|
1068
|
+
)
|
1069
|
+
|
1070
|
+
|
1056
1071
|
def _check_soil_category(
|
1057
1072
|
*,
|
1058
1073
|
key: IpccSoilCategory,
|
@@ -1461,7 +1476,7 @@ Value: Corresponding decision tree for IPCC management categories based on land
|
|
1461
1476
|
"""
|
1462
1477
|
|
1463
1478
|
_IPCC_LAND_USE_CATEGORY_TO_DEFAULT_IPCC_MANAGEMENT_CATEGORY = {
|
1464
|
-
IpccLandUseCategory.GRASSLAND: IpccManagementCategory.
|
1479
|
+
IpccLandUseCategory.GRASSLAND: IpccManagementCategory.UNKNOWN,
|
1465
1480
|
IpccLandUseCategory.ANNUAL_CROPS_WET: IpccManagementCategory.UNKNOWN,
|
1466
1481
|
IpccLandUseCategory.ANNUAL_CROPS: IpccManagementCategory.UNKNOWN
|
1467
1482
|
}
|