hestia-earth-models 0.58.0__py3-none-any.whl → 0.59.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.
Potentially problematic release.
This version of hestia-earth-models might be problematic. Click here for more details.
- hestia_earth/models/cycle/{irrigated.py → irrigatedTypeUnspecified.py} +4 -4
- hestia_earth/models/cycle/residueIncorporated.py +1 -1
- hestia_earth/models/emepEea2019/nh3ToAirInorganicFertiliser.py +2 -2
- hestia_earth/models/geospatialDatabase/clayContent.py +17 -4
- hestia_earth/models/geospatialDatabase/sandContent.py +17 -4
- hestia_earth/models/impact_assessment/irrigated.py +0 -3
- hestia_earth/models/ipcc2019/co2ToAirSoilCarbonStockChangeManagementChange.py +10 -9
- hestia_earth/models/ipcc2019/n2OToAirCropResidueDecompositionDirect.py +4 -51
- hestia_earth/models/ipcc2019/n2OToAirInorganicFertiliserDirect.py +104 -0
- hestia_earth/models/ipcc2019/n2OToAirOrganicFertiliserDirect.py +105 -0
- hestia_earth/models/ipcc2019/organicCarbonPerHa.py +1059 -1220
- hestia_earth/models/ipcc2019/utils.py +82 -1
- hestia_earth/models/mocking/search-results.json +165 -95
- hestia_earth/models/site/management.py +12 -9
- hestia_earth/models/site/organicCarbonPerHa.py +251 -89
- hestia_earth/models/utils/blank_node.py +157 -34
- hestia_earth/models/utils/cycle.py +6 -3
- hestia_earth/models/utils/measurement.py +1 -1
- hestia_earth/models/utils/site.py +1 -1
- hestia_earth/models/utils/term.py +46 -1
- hestia_earth/models/version.py +1 -1
- {hestia_earth_models-0.58.0.dist-info → hestia_earth_models-0.59.1.dist-info}/METADATA +4 -8
- {hestia_earth_models-0.58.0.dist-info → hestia_earth_models-0.59.1.dist-info}/RECORD +35 -31
- tests/models/cycle/{test_irrigated.py → test_irrigatedTypeUnspecified.py} +1 -1
- tests/models/geospatialDatabase/test_clayContent.py +9 -3
- tests/models/geospatialDatabase/test_sandContent.py +9 -3
- tests/models/ipcc2019/test_n2OToAirInorganicFertiliserDirect.py +74 -0
- tests/models/ipcc2019/test_n2OToAirOrganicFertiliserDirect.py +74 -0
- tests/models/ipcc2019/test_organicCarbonPerHa.py +303 -1044
- tests/models/site/test_organicCarbonPerHa.py +51 -5
- tests/models/utils/test_blank_node.py +102 -42
- tests/models/utils/test_term.py +17 -3
- {hestia_earth_models-0.58.0.dist-info → hestia_earth_models-0.59.1.dist-info}/LICENSE +0 -0
- {hestia_earth_models-0.58.0.dist-info → hestia_earth_models-0.59.1.dist-info}/WHEEL +0 -0
- {hestia_earth_models-0.58.0.dist-info → hestia_earth_models-0.59.1.dist-info}/top_level.txt +0 -0
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
from typing import Optional
|
|
2
2
|
|
|
3
3
|
from hestia_earth.schema import MeasurementMethodClassification
|
|
4
|
+
from hestia_earth.utils.date import diff_in_days
|
|
4
5
|
from hestia_earth.utils.model import find_term_match
|
|
6
|
+
from hestia_earth.utils.tools import flatten, safe_parse_float
|
|
5
7
|
|
|
6
|
-
from hestia_earth.models.log import logRequirements, logShouldRun
|
|
7
|
-
from hestia_earth.models.utils.
|
|
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
|
|
8
10
|
from hestia_earth.models.utils.measurement import (
|
|
9
|
-
_new_measurement, group_measurements_by_depth,
|
|
11
|
+
_new_measurement, group_measurements_by_depth, measurement_value, OLDEST_DATE
|
|
10
12
|
)
|
|
13
|
+
from hestia_earth.models.utils.source import get_source
|
|
11
14
|
from . import MODEL
|
|
12
15
|
|
|
13
16
|
REQUIREMENTS = {
|
|
@@ -43,6 +46,12 @@ BIBLIO_TITLE = 'Soil organic carbon sequestration rates in vineyard agroecosyste
|
|
|
43
46
|
RESCALE_DEPTH_UPPER = 0
|
|
44
47
|
RESCALE_DEPTH_LOWER = 30
|
|
45
48
|
|
|
49
|
+
# --- UTILS ---
|
|
50
|
+
|
|
51
|
+
MAX_DEPTH_LOWER = 100
|
|
52
|
+
SOIL_BULK_DENSITY_TERM_ID = 'soilBulkDensity'
|
|
53
|
+
ORGANIC_CARBON_PER_KG_SOIL_TERM_ID = 'organicCarbonPerKgSoil'
|
|
54
|
+
|
|
46
55
|
|
|
47
56
|
def _measurement(site: dict, value: float, depthUpper: int, depthLower: int, date: Optional[str] = None):
|
|
48
57
|
data = _new_measurement(TERM_ID)
|
|
@@ -55,14 +64,156 @@ def _measurement(site: dict, value: float, depthUpper: int, depthLower: int, dat
|
|
|
55
64
|
return data | get_source(site, BIBLIO_TITLE)
|
|
56
65
|
|
|
57
66
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
67
|
+
# --- CALCULATE `organicCarbonPerHa` ---
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _calc_organic_carbon_per_ha(
|
|
71
|
+
depth_upper: float,
|
|
72
|
+
depth_lower: float,
|
|
73
|
+
soil_bulk_density: float,
|
|
74
|
+
organic_carbon_per_kg_soil: float
|
|
75
|
+
) -> float:
|
|
76
|
+
"""
|
|
77
|
+
Calculate `organicCarbonPerHa` from `soilBulkDensity` and `organicCarbonPerKgSoil` using method adapted from
|
|
78
|
+
[Payen et al (2021)](https://doi.org/10.1016/j.jclepro.2020.125736).
|
|
79
|
+
|
|
80
|
+
Parameters
|
|
81
|
+
----------
|
|
82
|
+
depth_upper : float
|
|
83
|
+
Measurement depth upper in centimetres (min `0`).
|
|
84
|
+
depth_lower : float,
|
|
85
|
+
Measurement depth upper in centimetres (min `0`).
|
|
86
|
+
soil_bulk_density : float,
|
|
87
|
+
Soil bulk density between depth upper and depth lower, Mg soil m-3
|
|
88
|
+
organic_carbon_per_kg_soil : float
|
|
89
|
+
Soil organic carbon concentration between depth upper and depth lower, kg C kg soil-1
|
|
90
|
+
|
|
91
|
+
Return
|
|
92
|
+
------
|
|
93
|
+
float
|
|
94
|
+
The SOC stock per hectare within the specified depth interval, kg C ha-1.
|
|
95
|
+
"""
|
|
96
|
+
return (depth_lower - depth_upper) * soil_bulk_density * organic_carbon_per_kg_soil * 100
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _should_run_calculation_group(nodes: list) -> bool:
|
|
100
|
+
"""
|
|
101
|
+
Determines whether a depth interval group has sufficient data to calculate `organicCarbonPerHa` from
|
|
102
|
+
`soilBulkDensity` and `organicCarbonPerKgSoil`.
|
|
103
|
+
"""
|
|
104
|
+
soilBulkDensity = find_term_match(nodes, SOIL_BULK_DENSITY_TERM_ID, None)
|
|
105
|
+
has_soil_bulk_density_depth_lower = (soilBulkDensity or {}).get('depthLower') is not None
|
|
106
|
+
has_soil_bulk_density_depth_upper = (soilBulkDensity or {}).get('depthUpper') is not None
|
|
107
|
+
|
|
108
|
+
organicCarbonPerKgSoil = find_term_match(nodes, ORGANIC_CARBON_PER_KG_SOIL_TERM_ID, None)
|
|
109
|
+
has_organic_carbon_per_kg_soil_depth_lower = (organicCarbonPerKgSoil or {}).get('depthLower') is not None
|
|
110
|
+
has_organic_carbon_per_kg_soil_depth_upper = (organicCarbonPerKgSoil or {}).get('depthUpper') is not None
|
|
111
|
+
|
|
112
|
+
return all([
|
|
113
|
+
has_soil_bulk_density_depth_lower,
|
|
114
|
+
has_soil_bulk_density_depth_upper,
|
|
115
|
+
has_organic_carbon_per_kg_soil_depth_lower,
|
|
116
|
+
has_organic_carbon_per_kg_soil_depth_upper
|
|
117
|
+
])
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _should_run_calculation(site: dict) -> tuple[bool, dict[str, list[dict]]]:
|
|
121
|
+
"""
|
|
122
|
+
Pre-process site data and determine whether there is sufficient data to calculate `organicCarbonPerHa`.
|
|
123
|
+
"""
|
|
124
|
+
grouped_measurements = {
|
|
125
|
+
depth_key: nodes for depth_key, nodes in group_measurements_by_depth(site.get('measurements', [])).items()
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
valid_grouped_measurements = {
|
|
129
|
+
depth_key: nodes for depth_key, nodes in grouped_measurements.items()
|
|
130
|
+
if _should_run_calculation_group(nodes)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
should_run = bool(valid_grouped_measurements)
|
|
134
|
+
|
|
135
|
+
logs = {
|
|
136
|
+
"should_run_calculation": should_run,
|
|
137
|
+
"inventory_calculation": log_as_table(
|
|
138
|
+
{
|
|
139
|
+
"depth-key": str(depth_key).replace("_", "-"),
|
|
140
|
+
"should-run": depth_key in valid_grouped_measurements.keys(),
|
|
141
|
+
"has-soil-bulk-density": (
|
|
142
|
+
find_term_match(nodes, SOIL_BULK_DENSITY_TERM_ID, {}).get('depthLower')
|
|
143
|
+
and find_term_match(nodes, SOIL_BULK_DENSITY_TERM_ID, {}).get('depthUpper')
|
|
144
|
+
),
|
|
145
|
+
"has-organic-carbon-per-kg-soil": (
|
|
146
|
+
find_term_match(nodes, ORGANIC_CARBON_PER_KG_SOIL_TERM_ID, {}).get('depthLower')
|
|
147
|
+
and find_term_match(nodes, ORGANIC_CARBON_PER_KG_SOIL_TERM_ID, {}).get('depthUpper')
|
|
148
|
+
)
|
|
149
|
+
} for depth_key, nodes in grouped_measurements.items()
|
|
150
|
+
) or None
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return should_run, logs, valid_grouped_measurements
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _run_calculation(site: dict, depth_key: str, measurement_nodes: list[dict]) -> list[dict]:
|
|
157
|
+
"""
|
|
158
|
+
Returns an `organicCarbonPerHa` measurement node for each `organicCarbonPerKgSoil` node in depth group using the
|
|
159
|
+
most relevant `soilBulkDensity` node available.
|
|
160
|
+
|
|
161
|
+
Parameters
|
|
162
|
+
----------
|
|
163
|
+
site : dict
|
|
164
|
+
A [Site node](https://www.hestia.earth/schema/Site).
|
|
165
|
+
depth_key : str
|
|
166
|
+
A depth key in the format `"a_to_b"`.
|
|
167
|
+
measurement_nodes : list[dict]
|
|
168
|
+
A list of pre-validated [Measurement nodes](https://www.hestia.earth/schema/Measurement).
|
|
169
|
+
|
|
170
|
+
Return
|
|
171
|
+
------
|
|
172
|
+
list[dict]
|
|
173
|
+
A list of `organicCarbonPerHa` [Measurement nodes](https://www.hestia.earth/schema/Measurement).
|
|
174
|
+
"""
|
|
175
|
+
split = depth_key.split('_') # "a_to_b"
|
|
176
|
+
depth_upper = safe_parse_float(split[0])
|
|
177
|
+
depth_lower = safe_parse_float(split[2])
|
|
178
|
+
|
|
179
|
+
soil_bulk_density_nodes = [
|
|
180
|
+
node for node in measurement_nodes if node.get('term', {}).get('@id') == SOIL_BULK_DENSITY_TERM_ID
|
|
181
|
+
]
|
|
182
|
+
|
|
183
|
+
organic_carbon_per_kg_soil_nodes = [
|
|
184
|
+
node for node in measurement_nodes if node.get('term', {}).get('@id') == ORGANIC_CARBON_PER_KG_SOIL_TERM_ID
|
|
185
|
+
]
|
|
186
|
+
|
|
187
|
+
dates = [_get_last_date(node.get('dates', [])) for node in organic_carbon_per_kg_soil_nodes]
|
|
188
|
+
|
|
189
|
+
def closest_bd(datestr: str):
|
|
190
|
+
"""
|
|
191
|
+
Returns the `soilBulkDensity` node closest to target datestr. `nodes` input are pre-validated to always contain
|
|
192
|
+
at least one `soilBulkDensity` node.
|
|
193
|
+
"""
|
|
194
|
+
return next(
|
|
195
|
+
iter(sorted(
|
|
196
|
+
soil_bulk_density_nodes,
|
|
197
|
+
key=lambda node: abs(diff_in_days(
|
|
198
|
+
_get_last_date(node.get('dates', [])) or OLDEST_DATE,
|
|
199
|
+
datestr or OLDEST_DATE
|
|
200
|
+
))
|
|
201
|
+
))
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
values = [
|
|
205
|
+
_calc_organic_carbon_per_ha(
|
|
206
|
+
depth_upper,
|
|
207
|
+
depth_lower,
|
|
208
|
+
measurement_value(closest_bd(datestr)),
|
|
209
|
+
measurement_value(organic_carbon_per_kg_soil_node)
|
|
210
|
+
) for organic_carbon_per_kg_soil_node, datestr in zip(organic_carbon_per_kg_soil_nodes, dates)
|
|
211
|
+
]
|
|
212
|
+
|
|
213
|
+
return [_measurement(site, value, depth_upper, depth_lower, datestr) for value, datestr in zip(values, dates)]
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
# --- RESCALE `organicCarbonPerHa` ---
|
|
66
217
|
|
|
67
218
|
|
|
68
219
|
def _c_to_depth(d: float) -> float:
|
|
@@ -72,7 +223,7 @@ def _c_to_depth(d: float) -> float:
|
|
|
72
223
|
Parameters
|
|
73
224
|
----------
|
|
74
225
|
d : float
|
|
75
|
-
Measurement depth in
|
|
226
|
+
Measurement depth in metres (min `0`, max `1`).
|
|
76
227
|
|
|
77
228
|
Returns
|
|
78
229
|
-------
|
|
@@ -84,20 +235,19 @@ def _c_to_depth(d: float) -> float:
|
|
|
84
235
|
|
|
85
236
|
def _cdf(depth_upper: float, depth_lower: float) -> float:
|
|
86
237
|
"""
|
|
87
|
-
The ratio between the carbon stock per m2 to depth `d` and the carbon
|
|
88
|
-
stock per meter to depth `1`.
|
|
238
|
+
The ratio between the carbon stock per m2 to depth `d` and the carbon stock per m2 to depth `1`.
|
|
89
239
|
|
|
90
240
|
Parameters
|
|
91
241
|
----------
|
|
92
242
|
depth_upper : float
|
|
93
|
-
Measurement depth upper in
|
|
243
|
+
Measurement depth upper in metres (min `0`, max `1`).
|
|
94
244
|
depth_lower : float
|
|
95
|
-
Measurement depth lower in
|
|
245
|
+
Measurement depth lower in metres (min `0`, max `1`).
|
|
96
246
|
|
|
97
247
|
Returns
|
|
98
248
|
-------
|
|
99
249
|
float
|
|
100
|
-
The proportion of carbon stored between `depth_upper` and `depth_lower` compared to between `0` and `1`
|
|
250
|
+
The proportion of carbon stored between `depth_upper` and `depth_lower` compared to between `0` and `1` metres.
|
|
101
251
|
"""
|
|
102
252
|
return (_c_to_depth(depth_lower) - _c_to_depth(depth_upper)) / _c_to_depth(1)
|
|
103
253
|
|
|
@@ -137,99 +287,111 @@ def _rescale_soc_value(
|
|
|
137
287
|
return source_value * (cd_target / cd_measurement)
|
|
138
288
|
|
|
139
289
|
|
|
140
|
-
def
|
|
290
|
+
def _should_run_rescale_node(node: list) -> bool:
|
|
141
291
|
"""
|
|
142
|
-
|
|
143
|
-
`organicCarbonPerHa` is `mean` the latest date should be selected.
|
|
292
|
+
Validate that a node has `depthUpper` = `0` and a `depthLower` < `100`.
|
|
144
293
|
"""
|
|
145
|
-
return
|
|
294
|
+
return all([
|
|
295
|
+
node.get('depthUpper', 1) == RESCALE_DEPTH_UPPER,
|
|
296
|
+
node.get('depthLower', 101) <= MAX_DEPTH_LOWER
|
|
297
|
+
])
|
|
146
298
|
|
|
147
299
|
|
|
148
|
-
def
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
300
|
+
def _should_run_rescale_group(nodes: list) -> bool:
|
|
301
|
+
"""
|
|
302
|
+
Validate that a list of nodes doesn't contain a node with `depthUpper` = `0` and a `depthLower` < `30`.
|
|
303
|
+
"""
|
|
304
|
+
return not any([
|
|
305
|
+
node for node in nodes if all([
|
|
306
|
+
node.get('depthUpper', 1) == RESCALE_DEPTH_UPPER,
|
|
307
|
+
node.get('depthLower', 101) == RESCALE_DEPTH_LOWER
|
|
308
|
+
])
|
|
309
|
+
])
|
|
155
310
|
|
|
156
|
-
value = _rescale_soc_value(
|
|
157
|
-
measurement_value(measurement),
|
|
158
|
-
RESCALE_DEPTH_UPPER, measurement.get('depthLower'),
|
|
159
|
-
RESCALE_DEPTH_UPPER, RESCALE_DEPTH_LOWER,
|
|
160
|
-
) if measurement else None
|
|
161
311
|
|
|
162
|
-
|
|
312
|
+
def _should_run_rescale(organic_carbon_per_ha_nodes: list) -> tuple[bool, dict[str, list[dict]]]:
|
|
313
|
+
"""
|
|
314
|
+
Pre-process `organicCarbonPerHa` nodes and determine whether any need to be rescaled to a depth interval of 0-30cm.
|
|
315
|
+
"""
|
|
316
|
+
grouped_nodes = group_nodes_by_last_date(
|
|
317
|
+
[node for node in organic_carbon_per_ha_nodes if _should_run_rescale_node(node)]
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
valid_grouped_nodes = {
|
|
321
|
+
datestr: nodes for datestr, nodes in grouped_nodes.items()
|
|
322
|
+
if _should_run_rescale_group(nodes)
|
|
323
|
+
}
|
|
163
324
|
|
|
164
|
-
|
|
325
|
+
should_run = bool(valid_grouped_nodes)
|
|
165
326
|
|
|
327
|
+
logs = {
|
|
328
|
+
"should_run_rescale": should_run,
|
|
329
|
+
"inventory_rescale": log_as_table(
|
|
330
|
+
{
|
|
331
|
+
"date": str(datestr),
|
|
332
|
+
"should-run": datestr in valid_grouped_nodes.keys()
|
|
333
|
+
} for datestr, nodes in grouped_nodes.items()
|
|
334
|
+
) or None
|
|
335
|
+
}
|
|
166
336
|
|
|
167
|
-
|
|
168
|
-
soilBulkDensity = measurement_value(find_term_match(measurements, 'soilBulkDensity'))
|
|
169
|
-
organicCarbonPerKgSoil = find_term_match(measurements, 'organicCarbonPerKgSoil')
|
|
170
|
-
organicCarbonPerKgSoil_value = measurement_value(organicCarbonPerKgSoil)
|
|
337
|
+
return should_run, logs, valid_grouped_nodes
|
|
171
338
|
|
|
172
|
-
value = (
|
|
173
|
-
organicCarbonPerKgSoil.get('depthLower') - organicCarbonPerKgSoil.get('depthUpper')
|
|
174
|
-
) * soilBulkDensity * (organicCarbonPerKgSoil_value/10) * 1000
|
|
175
339
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
date = _get_last_date(organicCarbonPerKgSoil.get("dates", []))
|
|
340
|
+
def _depth_distance(node: dict):
|
|
341
|
+
return abs(node.get('depthLower', 101) - RESCALE_DEPTH_LOWER)
|
|
179
342
|
|
|
180
|
-
return _measurement(site, value, depthUpper, depthLower, date)
|
|
181
343
|
|
|
344
|
+
def _get_most_relevant_soc_node(organic_carbon_per_ha_nodes: list[dict]):
|
|
345
|
+
"""
|
|
346
|
+
Find the `organic_carbon_per_ha_node` with the closest depth interval to 0 - 30cm. `depthLowers` greater than 30cm
|
|
347
|
+
are prioritised. Returns `{}` if input list is empty.
|
|
348
|
+
"""
|
|
349
|
+
priority_nodes = [
|
|
350
|
+
node for node in organic_carbon_per_ha_nodes
|
|
351
|
+
if 'depthLower' in node and node.get('depthLower') >= RESCALE_DEPTH_LOWER
|
|
352
|
+
]
|
|
353
|
+
nodes = priority_nodes or organic_carbon_per_ha_nodes # If priority nodes are available use them.
|
|
182
354
|
|
|
183
|
-
|
|
184
|
-
soilBulkDensity = find_term_match(measurements, 'soilBulkDensity', None)
|
|
185
|
-
has_soilBulkDensity_depthLower = (soilBulkDensity or {}).get('depthLower') is not None
|
|
186
|
-
has_soilBulkDensity_depthUpper = (soilBulkDensity or {}).get('depthUpper') is not None
|
|
187
|
-
organicCarbonPerKgSoil = find_term_match(measurements, 'organicCarbonPerKgSoil', None)
|
|
188
|
-
has_organicCarbonPerKgSoil_depthLower = (organicCarbonPerKgSoil or {}).get('depthLower') is not None
|
|
189
|
-
has_organicCarbonPerKgSoil_depthUpper = (organicCarbonPerKgSoil or {}).get('depthUpper') is not None
|
|
355
|
+
return next(node for node in nodes if _depth_distance(node) == min(_depth_distance(node) for node in nodes))
|
|
190
356
|
|
|
191
|
-
depth_logs = {
|
|
192
|
-
_group_measurement_key(measurements[0], include_dates=False): ';'.join([
|
|
193
|
-
'_'.join([
|
|
194
|
-
'id:soilBulkDensity',
|
|
195
|
-
f"hasDepthLower:{has_soilBulkDensity_depthLower}",
|
|
196
|
-
f"hasDepthUpper:{has_soilBulkDensity_depthUpper}"
|
|
197
|
-
]),
|
|
198
|
-
'_'.join([
|
|
199
|
-
'id:organicCarbonPerKgSoil',
|
|
200
|
-
f"hasDepthLower:{has_organicCarbonPerKgSoil_depthLower}",
|
|
201
|
-
f"hasDepthUpper:{has_organicCarbonPerKgSoil_depthUpper}"
|
|
202
|
-
])
|
|
203
|
-
])
|
|
204
|
-
} if len(measurements) > 0 else {}
|
|
205
357
|
|
|
206
|
-
|
|
207
|
-
|
|
358
|
+
def _run_rescale(site: dict, organic_carbon_per_ha_nodes: list[dict]) -> list[dict]:
|
|
359
|
+
"""
|
|
360
|
+
For each unique measurement date, rescale the deepest `organicCarbonPerHa` node to a depth of 0 to 30cm.
|
|
361
|
+
"""
|
|
362
|
+
node = _get_most_relevant_soc_node(organic_carbon_per_ha_nodes)
|
|
208
363
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
364
|
+
value = _rescale_soc_value(
|
|
365
|
+
measurement_value(node),
|
|
366
|
+
RESCALE_DEPTH_UPPER, node.get('depthLower'),
|
|
367
|
+
RESCALE_DEPTH_UPPER, RESCALE_DEPTH_LOWER,
|
|
368
|
+
) if node else None
|
|
369
|
+
date = _get_last_date(node.get("dates", [])) if node else None
|
|
370
|
+
|
|
371
|
+
return _measurement(site, value, RESCALE_DEPTH_UPPER, RESCALE_DEPTH_LOWER, date) if value is not None else []
|
|
214
372
|
|
|
215
373
|
|
|
216
|
-
|
|
217
|
-
grouped_measurements = list(group_measurements_by_depth(site.get('measurements', [])).values())
|
|
218
|
-
values = [(measurements, _should_run_measurements(site, measurements)) for measurements in grouped_measurements]
|
|
219
|
-
should_run = any([_should_run for measurements, _should_run in values])
|
|
220
|
-
logShouldRun(site, MODEL, TERM_ID, should_run)
|
|
221
|
-
return should_run, [measurements for measurements, _should_run in values if _should_run]
|
|
374
|
+
# --- RUN MODEL ---
|
|
222
375
|
|
|
223
376
|
|
|
224
377
|
def run(site: dict):
|
|
225
|
-
|
|
226
|
-
|
|
378
|
+
should_run_calculation, logs_calculation, grouped_measurements = _should_run_calculation(site)
|
|
379
|
+
result_calculation = (
|
|
380
|
+
flatten([_run_calculation(site, depth_key, nodes) for depth_key, nodes in grouped_measurements.items()])
|
|
381
|
+
if should_run_calculation else []
|
|
382
|
+
)
|
|
227
383
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
m for m in (site.get('measurements', []) + calculated_measurements)
|
|
231
|
-
if m.get('term', {}).get('@id') == TERM_ID
|
|
232
|
-
]
|
|
233
|
-
return calculated_measurements + (
|
|
234
|
-
_run_rescale(site, all_measurements) if _should_run_rescale(all_measurements) else []
|
|
384
|
+
oc_per_ha_nodes = (
|
|
385
|
+
result_calculation + [m for m in site.get('measurements', []) if m.get('term', {}).get('@id') == TERM_ID]
|
|
235
386
|
)
|
|
387
|
+
|
|
388
|
+
should_run_rescale, logs_rescale, grouped_oc_per_ha_nodes = _should_run_rescale(oc_per_ha_nodes)
|
|
389
|
+
result_rescale = (
|
|
390
|
+
[_run_rescale(site, nodes) for nodes in grouped_oc_per_ha_nodes.values()]
|
|
391
|
+
if should_run_rescale else []
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
logRequirements(site, model=MODEL, term=TERM_ID, **logs_calculation, **logs_rescale)
|
|
395
|
+
logShouldRun(site, MODEL, TERM_ID, should_run=should_run_calculation or should_run_rescale)
|
|
396
|
+
|
|
397
|
+
return result_calculation + result_rescale
|