hestia-earth-models 0.61.7__py3-none-any.whl → 0.62.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/cycle/completeness/electricityFuel.py +60 -0
- hestia_earth/models/cycle/product/economicValueShare.py +47 -31
- hestia_earth/models/emepEea2019/nh3ToAirInorganicFertiliser.py +44 -59
- hestia_earth/models/geospatialDatabase/histosol.py +4 -0
- hestia_earth/models/ipcc2006/co2ToAirOrganicSoilCultivation.py +4 -2
- hestia_earth/models/ipcc2006/n2OToAirOrganicSoilCultivationDirect.py +1 -1
- hestia_earth/models/ipcc2019/aboveGroundCropResidueTotal.py +1 -1
- hestia_earth/models/ipcc2019/animal/pastureGrass.py +30 -24
- hestia_earth/models/ipcc2019/belowGroundCropResidue.py +1 -1
- hestia_earth/models/ipcc2019/ch4ToAirExcreta.py +1 -1
- hestia_earth/models/ipcc2019/co2ToAirSoilOrganicCarbonStockChangeManagementChange.py +511 -458
- hestia_earth/models/ipcc2019/co2ToAirUreaHydrolysis.py +5 -1
- hestia_earth/models/ipcc2019/organicCarbonPerHa.py +116 -3882
- hestia_earth/models/ipcc2019/organicCarbonPerHa_tier_1_utils.py +2060 -0
- hestia_earth/models/ipcc2019/organicCarbonPerHa_tier_2_utils.py +1630 -0
- hestia_earth/models/ipcc2019/organicCarbonPerHa_utils.py +324 -0
- hestia_earth/models/ipcc2019/pastureGrass.py +37 -19
- hestia_earth/models/ipcc2019/pastureGrass_utils.py +4 -21
- hestia_earth/models/mocking/search-results.json +293 -289
- hestia_earth/models/site/organicCarbonPerHa.py +58 -44
- hestia_earth/models/site/soilMeasurement.py +18 -13
- hestia_earth/models/utils/__init__.py +28 -0
- hestia_earth/models/utils/array_builders.py +578 -0
- hestia_earth/models/utils/blank_node.py +55 -39
- hestia_earth/models/utils/descriptive_stats.py +285 -0
- hestia_earth/models/utils/emission.py +73 -2
- hestia_earth/models/utils/inorganicFertiliser.py +2 -2
- hestia_earth/models/utils/measurement.py +118 -4
- hestia_earth/models/version.py +1 -1
- {hestia_earth_models-0.61.7.dist-info → hestia_earth_models-0.62.0.dist-info}/METADATA +2 -2
- {hestia_earth_models-0.61.7.dist-info → hestia_earth_models-0.62.0.dist-info}/RECORD +51 -39
- tests/models/cycle/completeness/test_electricityFuel.py +21 -0
- tests/models/cycle/product/test_economicValueShare.py +8 -0
- tests/models/emepEea2019/test_nh3ToAirInorganicFertiliser.py +2 -2
- tests/models/ipcc2019/animal/test_pastureGrass.py +2 -2
- tests/models/ipcc2019/test_co2ToAirSoilOrganicCarbonStockChangeManagementChange.py +55 -165
- tests/models/ipcc2019/test_organicCarbonPerHa.py +219 -460
- tests/models/ipcc2019/test_organicCarbonPerHa_tier_1_utils.py +471 -0
- tests/models/ipcc2019/test_organicCarbonPerHa_tier_2_utils.py +208 -0
- tests/models/ipcc2019/test_organicCarbonPerHa_utils.py +75 -0
- tests/models/ipcc2019/test_pastureGrass.py +0 -16
- tests/models/site/test_organicCarbonPerHa.py +3 -12
- tests/models/site/test_soilMeasurement.py +3 -18
- tests/models/utils/test_array_builders.py +253 -0
- tests/models/utils/test_blank_node.py +154 -15
- tests/models/utils/test_descriptive_stats.py +134 -0
- tests/models/utils/test_emission.py +51 -1
- tests/models/utils/test_measurement.py +54 -2
- {hestia_earth_models-0.61.7.dist-info → hestia_earth_models-0.62.0.dist-info}/LICENSE +0 -0
- {hestia_earth_models-0.61.7.dist-info → hestia_earth_models-0.62.0.dist-info}/WHEEL +0 -0
- {hestia_earth_models-0.61.7.dist-info → hestia_earth_models-0.62.0.dist-info}/top_level.txt +0 -0
|
@@ -3,10 +3,10 @@ from typing import Optional, Union
|
|
|
3
3
|
from hestia_earth.schema import MeasurementMethodClassification
|
|
4
4
|
from hestia_earth.utils.date import diff_in_days
|
|
5
5
|
from hestia_earth.utils.model import find_term_match
|
|
6
|
-
from hestia_earth.utils.tools import flatten, safe_parse_float
|
|
6
|
+
from hestia_earth.utils.tools import flatten, non_empty_list, safe_parse_float
|
|
7
7
|
|
|
8
8
|
from hestia_earth.models.log import log_as_table, logRequirements, logShouldRun
|
|
9
|
-
from hestia_earth.models.utils.blank_node import _get_last_date, group_nodes_by_last_date
|
|
9
|
+
from hestia_earth.models.utils.blank_node import _get_last_date, group_nodes_by_last_date, node_term_match
|
|
10
10
|
from hestia_earth.models.utils.measurement import (
|
|
11
11
|
_new_measurement, group_measurements_by_depth, measurement_value, OLDEST_DATE
|
|
12
12
|
)
|
|
@@ -21,14 +21,17 @@ REQUIREMENTS = {
|
|
|
21
21
|
"value": "",
|
|
22
22
|
"term.@id": "soilBulkDensity",
|
|
23
23
|
"depthUpper": "",
|
|
24
|
-
"depthLower": ""
|
|
24
|
+
"depthLower": "",
|
|
25
|
+
"methodClassification": ["on-site physical measurement", "modelled using other measurements"]
|
|
25
26
|
},
|
|
26
27
|
{
|
|
27
28
|
"@type": "Measurement",
|
|
28
29
|
"value": "",
|
|
30
|
+
"dates": "",
|
|
29
31
|
"term.@id": "organicCarbonPerKgSoil",
|
|
30
32
|
"depthUpper": "",
|
|
31
|
-
"depthLower": ""
|
|
33
|
+
"depthLower": "",
|
|
34
|
+
"methodClassification": ["on-site physical measurement", "modelled using other measurements"]
|
|
32
35
|
}
|
|
33
36
|
]
|
|
34
37
|
}
|
|
@@ -36,6 +39,7 @@ REQUIREMENTS = {
|
|
|
36
39
|
RETURNS = {
|
|
37
40
|
"Measurement": [{
|
|
38
41
|
"value": "",
|
|
42
|
+
"dates": "",
|
|
39
43
|
"depthUpper": "",
|
|
40
44
|
"depthLower": "",
|
|
41
45
|
"methodClassification": "modelled using other measurements"
|
|
@@ -51,6 +55,10 @@ RESCALE_DEPTH_LOWER = 30
|
|
|
51
55
|
MAX_DEPTH_LOWER = 100
|
|
52
56
|
SOIL_BULK_DENSITY_TERM_ID = 'soilBulkDensity'
|
|
53
57
|
ORGANIC_CARBON_PER_KG_SOIL_TERM_ID = 'organicCarbonPerKgSoil'
|
|
58
|
+
VALID_MEASUREMENT_METHOD_CLASSIFICATIONS = {
|
|
59
|
+
MeasurementMethodClassification.ON_SITE_PHYSICAL_MEASUREMENT.value,
|
|
60
|
+
MeasurementMethodClassification.MODELLED_USING_OTHER_MEASUREMENTS.value
|
|
61
|
+
}
|
|
54
62
|
|
|
55
63
|
|
|
56
64
|
def _measurement(
|
|
@@ -98,38 +106,40 @@ def _calc_organic_carbon_per_ha(
|
|
|
98
106
|
return (depth_lower - depth_upper) * soil_bulk_density * organic_carbon_per_kg_soil * 100
|
|
99
107
|
|
|
100
108
|
|
|
101
|
-
def
|
|
109
|
+
def _should_run_calculation(site: dict) -> tuple[bool, dict[str, list[dict]]]:
|
|
102
110
|
"""
|
|
103
|
-
|
|
104
|
-
`soilBulkDensity` and `organicCarbonPerKgSoil`.
|
|
111
|
+
Pre-process site data and determine whether there is sufficient data to calculate `organicCarbonPerHa`.
|
|
105
112
|
"""
|
|
106
|
-
|
|
107
|
-
has_soil_bulk_density_depth_lower = (soilBulkDensity or {}).get('depthLower') is not None
|
|
108
|
-
has_soil_bulk_density_depth_upper = (soilBulkDensity or {}).get('depthUpper') is not None
|
|
113
|
+
oc_nodes = [node for node in site.get("measurements", []) if _valid_measurement(node, TERM_ID)]
|
|
109
114
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
has_organic_carbon_per_kg_soil_depth_upper = (organicCarbonPerKgSoil or {}).get('depthUpper') is not None
|
|
115
|
+
# We don't need to run the model for any dates we already have an `organicCarbonPerHa` value for.
|
|
116
|
+
oc_node_dates = set(non_empty_list(flatten(measurement.get("dates", []) for measurement in oc_nodes)))
|
|
113
117
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
118
|
+
occ_nodes = [
|
|
119
|
+
node for node in site.get("measurements", [])
|
|
120
|
+
if all([
|
|
121
|
+
_valid_measurement(node, ORGANIC_CARBON_PER_KG_SOIL_TERM_ID),
|
|
122
|
+
len(node.get("dates", [])) > 0,
|
|
123
|
+
_get_last_date(node.get("dates", [])) not in oc_node_dates
|
|
124
|
+
])
|
|
125
|
+
]
|
|
120
126
|
|
|
127
|
+
bd_nodes = [node for node in site.get("measurements", []) if _valid_measurement(node, SOIL_BULK_DENSITY_TERM_ID)]
|
|
121
128
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
129
|
+
measurements = occ_nodes + bd_nodes
|
|
130
|
+
grouped_measurements = group_measurements_by_depth(measurements, include_dates=False)
|
|
131
|
+
|
|
132
|
+
inventory = {
|
|
133
|
+
depth_key: {
|
|
134
|
+
"measurements": nodes,
|
|
135
|
+
"has-soil-bulk-density": bool(find_term_match(nodes, SOIL_BULK_DENSITY_TERM_ID)),
|
|
136
|
+
"has-organic-carbon-per-kg-soil": bool(find_term_match(nodes, SOIL_BULK_DENSITY_TERM_ID))
|
|
137
|
+
} for depth_key, nodes in grouped_measurements.items()
|
|
128
138
|
}
|
|
129
139
|
|
|
130
140
|
valid_grouped_measurements = {
|
|
131
|
-
depth_key:
|
|
132
|
-
if
|
|
141
|
+
depth_key: group["measurements"] for depth_key, group in inventory.items()
|
|
142
|
+
if all([group["has-soil-bulk-density"], group["has-organic-carbon-per-kg-soil"]])
|
|
133
143
|
}
|
|
134
144
|
|
|
135
145
|
should_run = bool(valid_grouped_measurements)
|
|
@@ -139,20 +149,24 @@ def _should_run_calculation(site: dict) -> tuple[bool, dict[str, list[dict]]]:
|
|
|
139
149
|
"inventory_calculation": log_as_table(
|
|
140
150
|
{
|
|
141
151
|
"depth-key": str(depth_key).replace("_", "-"),
|
|
142
|
-
"should-run": depth_key in valid_grouped_measurements
|
|
143
|
-
"has-soil-bulk-density":
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
"has-organic-carbon-per-kg-soil": (
|
|
148
|
-
find_term_match(nodes, ORGANIC_CARBON_PER_KG_SOIL_TERM_ID, {}).get('depthLower')
|
|
149
|
-
and find_term_match(nodes, ORGANIC_CARBON_PER_KG_SOIL_TERM_ID, {}).get('depthUpper')
|
|
150
|
-
)
|
|
151
|
-
} for depth_key, nodes in grouped_measurements.items()
|
|
152
|
-
) or None
|
|
152
|
+
"should-run": depth_key in valid_grouped_measurements,
|
|
153
|
+
"has-soil-bulk-density": group["has-soil-bulk-density"],
|
|
154
|
+
"has-organic-carbon-per-kg-soil": group["has-organic-carbon-per-kg-soil"]
|
|
155
|
+
} for depth_key, group in inventory.items()
|
|
156
|
+
) if inventory else "None"
|
|
153
157
|
}
|
|
154
158
|
|
|
155
|
-
return should_run,
|
|
159
|
+
return should_run, valid_grouped_measurements, logs
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def _valid_measurement(node: dict, target_term_id: str) -> bool:
|
|
163
|
+
return all([
|
|
164
|
+
node_term_match(node, target_term_id),
|
|
165
|
+
node.get("value"),
|
|
166
|
+
node.get("depthLower") is not None,
|
|
167
|
+
node.get("depthUpper") is not None,
|
|
168
|
+
node.get("methodClassification") in VALID_MEASUREMENT_METHOD_CLASSIFICATIONS
|
|
169
|
+
])
|
|
156
170
|
|
|
157
171
|
|
|
158
172
|
def _run_calculation(site: dict, depth_key: str, measurement_nodes: list[dict]) -> list[dict]:
|
|
@@ -332,11 +346,11 @@ def _should_run_rescale(organic_carbon_per_ha_nodes: list) -> tuple[bool, dict[s
|
|
|
332
346
|
{
|
|
333
347
|
"date": str(datestr),
|
|
334
348
|
"should-run": datestr in valid_grouped_nodes.keys()
|
|
335
|
-
} for datestr
|
|
336
|
-
)
|
|
349
|
+
} for datestr in grouped_nodes.keys()
|
|
350
|
+
) if grouped_nodes else "None"
|
|
337
351
|
}
|
|
338
352
|
|
|
339
|
-
return should_run,
|
|
353
|
+
return should_run, valid_grouped_nodes, logs
|
|
340
354
|
|
|
341
355
|
|
|
342
356
|
def _depth_distance(node: dict):
|
|
@@ -377,7 +391,7 @@ def _run_rescale(site: dict, organic_carbon_per_ha_nodes: list[dict]) -> list[di
|
|
|
377
391
|
|
|
378
392
|
|
|
379
393
|
def run(site: dict):
|
|
380
|
-
should_run_calculation,
|
|
394
|
+
should_run_calculation, grouped_measurements, logs_calculation = _should_run_calculation(site)
|
|
381
395
|
result_calculation = (
|
|
382
396
|
flatten([_run_calculation(site, depth_key, nodes) for depth_key, nodes in grouped_measurements.items()])
|
|
383
397
|
if should_run_calculation else []
|
|
@@ -387,7 +401,7 @@ def run(site: dict):
|
|
|
387
401
|
result_calculation + [m for m in site.get('measurements', []) if m.get('term', {}).get('@id') == TERM_ID]
|
|
388
402
|
)
|
|
389
403
|
|
|
390
|
-
should_run_rescale,
|
|
404
|
+
should_run_rescale, grouped_oc_per_ha_nodes, logs_rescale = _should_run_rescale(oc_per_ha_nodes)
|
|
391
405
|
result_rescale = (
|
|
392
406
|
[_run_rescale(site, nodes) for nodes in grouped_oc_per_ha_nodes.values()]
|
|
393
407
|
if should_run_rescale else []
|
|
@@ -8,7 +8,7 @@ from copy import deepcopy
|
|
|
8
8
|
from hestia_earth.schema import MeasurementMethodClassification
|
|
9
9
|
from hestia_earth.utils.tools import non_empty_list, flatten
|
|
10
10
|
|
|
11
|
-
from hestia_earth.models.log import logRequirements, logShouldRun, logErrorRun
|
|
11
|
+
from hestia_earth.models.log import logRequirements, logShouldRun, logErrorRun, log_as_table
|
|
12
12
|
from hestia_earth.models.utils.measurement import _new_measurement
|
|
13
13
|
from hestia_earth.models.utils.term import get_lookup_value
|
|
14
14
|
from . import MODEL
|
|
@@ -20,7 +20,6 @@ REQUIREMENTS = {
|
|
|
20
20
|
]
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
|
-
|
|
24
23
|
RETURNS = {
|
|
25
24
|
"Measurement": [{
|
|
26
25
|
"value": "",
|
|
@@ -30,11 +29,9 @@ RETURNS = {
|
|
|
30
29
|
"methodClassification": "modelled using other measurements"
|
|
31
30
|
}]
|
|
32
31
|
}
|
|
33
|
-
|
|
34
32
|
LOOKUPS = {
|
|
35
|
-
"measurement":
|
|
33
|
+
"measurement": "depthSensitive"
|
|
36
34
|
}
|
|
37
|
-
|
|
38
35
|
MODEL_KEY = 'soilMeasurement'
|
|
39
36
|
STANDARD_DEPTHS = {(0, 30), (0, 50)}
|
|
40
37
|
|
|
@@ -154,17 +151,25 @@ def _get_depths_from_measurements(measurements: list) -> list:
|
|
|
154
151
|
return needed_depths
|
|
155
152
|
|
|
156
153
|
|
|
157
|
-
def _should_run(site: dict
|
|
154
|
+
def _should_run(site: dict):
|
|
158
155
|
# we only work with measurements with depths
|
|
159
|
-
measurements =
|
|
160
|
-
|
|
161
|
-
m.get('
|
|
156
|
+
measurements = site.get("measurements", [])
|
|
157
|
+
measurement_sensitivity = {
|
|
158
|
+
m.get('term', {}).get('@id'): get_lookup_value(
|
|
159
|
+
m.get('term', {}), LOOKUPS["measurement"], model=MODEL, model_key=MODEL_KEY
|
|
160
|
+
)
|
|
161
|
+
for m in measurements
|
|
162
|
+
}
|
|
163
|
+
measurements_with_depths = [m for m in measurements if all([
|
|
164
|
+
not measurement_sensitivity[m.get("term", {}).get('@id')],
|
|
165
|
+
m.get('value', []),
|
|
166
|
+
"depthUpper" in m,
|
|
167
|
+
"depthLower" in m
|
|
162
168
|
])]
|
|
163
|
-
|
|
164
|
-
measurements_with_depths = [m for m in measurements if "depthUpper" in m and "depthLower" in m]
|
|
165
169
|
has_measurements_with_depths = len(measurements_with_depths) > 0
|
|
166
170
|
|
|
167
|
-
logRequirements(site, model=MODEL, model_key=
|
|
171
|
+
logRequirements(site, model=MODEL, model_key=MODEL_KEY,
|
|
172
|
+
measurements_depth_sensitive=log_as_table(measurement_sensitivity),
|
|
168
173
|
has_measurements_with_depths=has_measurements_with_depths)
|
|
169
174
|
|
|
170
175
|
should_run = has_measurements_with_depths
|
|
@@ -175,5 +180,5 @@ def _should_run(site: dict, model_key: str):
|
|
|
175
180
|
|
|
176
181
|
|
|
177
182
|
def run(site: dict):
|
|
178
|
-
should_run, measurements_with_depths = _should_run(site
|
|
183
|
+
should_run, measurements_with_depths = _should_run(site)
|
|
179
184
|
return non_empty_list(flatten(_run_harmonisation(measurements=measurements_with_depths))) if should_run else []
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
from os.path import dirname, abspath
|
|
2
|
+
from collections.abc import Generator, Iterable
|
|
3
|
+
from itertools import tee
|
|
2
4
|
import sys
|
|
3
5
|
import datetime
|
|
4
6
|
from functools import reduce
|
|
@@ -7,6 +9,7 @@ from typing import Any, Union
|
|
|
7
9
|
from hestia_earth.schema import SchemaType
|
|
8
10
|
from hestia_earth.utils.api import download_hestia
|
|
9
11
|
from hestia_earth.utils.model import linked_node
|
|
12
|
+
from hestia_earth.utils.tools import flatten, non_empty_list
|
|
10
13
|
|
|
11
14
|
from .constant import Units
|
|
12
15
|
|
|
@@ -137,3 +140,28 @@ def last_day_of_month(year: int, month: int):
|
|
|
137
140
|
return datetime.date(int(year), 12, 31) if month == 12 else (
|
|
138
141
|
datetime.date(int(year) + int(int(month) / 12), (int(month) % 12) + 1, 1) - datetime.timedelta(days=1)
|
|
139
142
|
)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def flatten_args(args) -> list:
|
|
146
|
+
"""
|
|
147
|
+
Flatten the input args into a single list.
|
|
148
|
+
"""
|
|
149
|
+
return non_empty_list(flatten([list(arg) if is_iterable(arg) else [arg] for arg in args]))
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def is_iterable(arg) -> bool:
|
|
153
|
+
"""
|
|
154
|
+
Return `True` if the input arg is an instance of an `Iterable` (excluding `str` and `bytes`) or a `Generator`, else
|
|
155
|
+
return `False`.
|
|
156
|
+
"""
|
|
157
|
+
return isinstance(arg, (Iterable, Generator)) and not isinstance(arg, (str, bytes))
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def pairwise(iterable):
|
|
161
|
+
"""
|
|
162
|
+
from https://docs.python.org/3.9/library/itertools.html#itertools-recipes
|
|
163
|
+
s -> (s0,s1), (s1,s2), (s2, s3), ...
|
|
164
|
+
"""
|
|
165
|
+
a, b = tee(iterable)
|
|
166
|
+
next(b, None)
|
|
167
|
+
return zip(a, b)
|