hestia-earth-models 0.57.2__py3-none-any.whl → 0.59.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/aboveGroundCropResidueTotal.py +17 -12
- hestia_earth/models/cycle/excretaKgMass.py +4 -5
- hestia_earth/models/cycle/excretaKgN.py +4 -5
- hestia_earth/models/cycle/excretaKgVs.py +4 -5
- hestia_earth/models/cycle/inorganicFertiliser.py +2 -2
- hestia_earth/models/cycle/{irrigated.py → irrigatedTypeUnspecified.py} +4 -4
- hestia_earth/models/cycle/liveAnimal.py +9 -11
- hestia_earth/models/cycle/milkYield.py +154 -0
- hestia_earth/models/cycle/residueIncorporated.py +1 -1
- hestia_earth/models/cycle/utils.py +6 -0
- hestia_earth/models/emepEea2019/nh3ToAirInorganicFertiliser.py +3 -3
- hestia_earth/models/faostat2018/seed.py +2 -3
- hestia_earth/models/geospatialDatabase/clayContent.py +17 -4
- hestia_earth/models/geospatialDatabase/sandContent.py +17 -4
- hestia_earth/models/geospatialDatabase/siltContent.py +2 -2
- hestia_earth/models/impact_assessment/irrigated.py +0 -3
- hestia_earth/models/ipcc2006/co2ToAirOrganicSoilCultivation.py +2 -2
- hestia_earth/models/ipcc2006/n2OToAirCropResidueDecompositionIndirect.py +2 -2
- hestia_earth/models/ipcc2006/n2OToAirExcretaDirect.py +1 -1
- hestia_earth/models/ipcc2006/n2OToAirExcretaIndirect.py +8 -4
- hestia_earth/models/ipcc2006/n2OToAirInorganicFertiliserDirect.py +4 -1
- hestia_earth/models/ipcc2006/n2OToAirInorganicFertiliserIndirect.py +1 -1
- hestia_earth/models/ipcc2006/n2OToAirOrganicFertiliserDirect.py +1 -1
- hestia_earth/models/ipcc2006/n2OToAirOrganicFertiliserIndirect.py +1 -1
- hestia_earth/models/ipcc2006/utils.py +11 -8
- hestia_earth/models/ipcc2019/ch4ToAirEntericFermentation.py +4 -4
- hestia_earth/models/ipcc2019/ch4ToAirFloodedRice.py +16 -7
- hestia_earth/models/ipcc2019/co2ToAirSoilCarbonStockChangeManagementChange.py +759 -0
- hestia_earth/models/ipcc2019/croppingDuration.py +12 -6
- hestia_earth/models/ipcc2019/n2OToAirCropResidueDecompositionDirect.py +5 -52
- hestia_earth/models/ipcc2019/n2OToAirInorganicFertiliserDirect.py +104 -0
- hestia_earth/models/ipcc2019/n2OToAirInorganicFertiliserIndirect.py +1 -1
- hestia_earth/models/ipcc2019/n2OToAirOrganicFertiliserDirect.py +105 -0
- hestia_earth/models/ipcc2019/n2OToAirOrganicFertiliserIndirect.py +1 -1
- hestia_earth/models/ipcc2019/no3ToGroundwaterCropResidueDecomposition.py +1 -1
- hestia_earth/models/ipcc2019/no3ToGroundwaterExcreta.py +1 -1
- hestia_earth/models/ipcc2019/no3ToGroundwaterInorganicFertiliser.py +1 -1
- hestia_earth/models/ipcc2019/no3ToGroundwaterOrganicFertiliser.py +1 -1
- hestia_earth/models/ipcc2019/organicCarbonPerHa.py +1088 -1268
- hestia_earth/models/ipcc2019/pastureGrass.py +4 -4
- hestia_earth/models/ipcc2019/utils.py +102 -1
- hestia_earth/models/koble2014/aboveGroundCropResidue.py +15 -17
- hestia_earth/models/koble2014/cropResidueManagement.py +2 -2
- hestia_earth/models/koble2014/utils.py +19 -3
- hestia_earth/models/linkedImpactAssessment/__init__.py +4 -2
- hestia_earth/models/log.py +15 -3
- hestia_earth/models/mocking/search-results.json +184 -118
- hestia_earth/models/pooreNemecek2018/excretaKgN.py +6 -7
- hestia_earth/models/pooreNemecek2018/excretaKgVs.py +7 -6
- hestia_earth/models/pooreNemecek2018/no3ToGroundwaterCropResidueDecomposition.py +3 -2
- hestia_earth/models/pooreNemecek2018/no3ToGroundwaterExcreta.py +3 -2
- hestia_earth/models/pooreNemecek2018/no3ToGroundwaterInorganicFertiliser.py +3 -2
- hestia_earth/models/pooreNemecek2018/saplings.py +0 -1
- hestia_earth/models/site/management.py +168 -0
- hestia_earth/models/site/organicCarbonPerHa.py +251 -89
- hestia_earth/models/stehfestBouwman2006/n2OToAirCropResidueDecompositionDirect.py +3 -2
- hestia_earth/models/stehfestBouwman2006/n2OToAirExcretaDirect.py +3 -2
- hestia_earth/models/stehfestBouwman2006/n2OToAirInorganicFertiliserDirect.py +3 -2
- hestia_earth/models/stehfestBouwman2006/n2OToAirOrganicFertiliserDirect.py +3 -2
- hestia_earth/models/stehfestBouwman2006/noxToAirCropResidueDecomposition.py +3 -2
- hestia_earth/models/stehfestBouwman2006/noxToAirExcreta.py +3 -2
- hestia_earth/models/stehfestBouwman2006/noxToAirInorganicFertiliser.py +3 -2
- hestia_earth/models/stehfestBouwman2006/noxToAirOrganicFertiliser.py +3 -2
- hestia_earth/models/stehfestBouwman2006GisImplementation/noxToAirCropResidueDecomposition.py +3 -2
- hestia_earth/models/stehfestBouwman2006GisImplementation/noxToAirExcreta.py +3 -2
- hestia_earth/models/stehfestBouwman2006GisImplementation/noxToAirInorganicFertiliser.py +3 -2
- hestia_earth/models/stehfestBouwman2006GisImplementation/noxToAirOrganicFertiliser.py +3 -2
- hestia_earth/models/utils/aggregated.py +1 -0
- hestia_earth/models/utils/blank_node.py +394 -72
- hestia_earth/models/utils/cropResidue.py +13 -0
- hestia_earth/models/utils/cycle.py +18 -9
- hestia_earth/models/utils/measurement.py +1 -1
- hestia_earth/models/utils/property.py +4 -4
- hestia_earth/models/utils/term.py +48 -3
- hestia_earth/models/version.py +1 -1
- {hestia_earth_models-0.57.2.dist-info → hestia_earth_models-0.59.0.dist-info}/METADATA +5 -9
- {hestia_earth_models-0.57.2.dist-info → hestia_earth_models-0.59.0.dist-info}/RECORD +109 -97
- {hestia_earth_models-0.57.2.dist-info → hestia_earth_models-0.59.0.dist-info}/WHEEL +1 -1
- tests/models/cycle/animal/input/test_hestiaAggregatedData.py +2 -14
- tests/models/cycle/input/test_hestiaAggregatedData.py +4 -16
- tests/models/cycle/test_coldCarcassWeightPerHead.py +1 -1
- tests/models/cycle/test_coldDressedCarcassWeightPerHead.py +1 -1
- tests/models/cycle/{test_irrigated.py → test_irrigatedTypeUnspecified.py} +1 -1
- tests/models/cycle/test_milkYield.py +58 -0
- tests/models/cycle/test_readyToCookWeightPerHead.py +1 -1
- tests/models/emepEea2019/test_nh3ToAirInorganicFertiliser.py +1 -1
- tests/models/geospatialDatabase/test_clayContent.py +9 -3
- tests/models/geospatialDatabase/test_sandContent.py +9 -3
- tests/models/ipcc2006/test_n2OToAirExcretaDirect.py +7 -2
- tests/models/ipcc2006/test_n2OToAirExcretaIndirect.py +1 -1
- tests/models/ipcc2006/test_n2OToAirInorganicFertiliserDirect.py +7 -2
- tests/models/ipcc2006/test_n2OToAirInorganicFertiliserIndirect.py +7 -2
- tests/models/ipcc2006/test_n2OToAirOrganicFertiliserDirect.py +7 -2
- tests/models/ipcc2006/test_n2OToAirOrganicFertiliserIndirect.py +7 -2
- tests/models/ipcc2019/test_ch4ToAirEntericFermentation.py +1 -1
- tests/models/ipcc2019/test_co2ToAirSoilCarbonStockChangeManagementChange.py +228 -0
- 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/koble2014/test_residueBurnt.py +1 -2
- tests/models/koble2014/test_residueLeftOnField.py +1 -2
- tests/models/koble2014/test_residueRemoved.py +1 -2
- tests/models/koble2014/test_utils.py +52 -0
- tests/models/site/test_management.py +117 -0
- tests/models/site/test_organicCarbonPerHa.py +51 -5
- tests/models/utils/test_blank_node.py +230 -34
- tests/models/utils/test_term.py +17 -3
- {hestia_earth_models-0.57.2.dist-info → hestia_earth_models-0.59.0.dist-info}/LICENSE +0 -0
- {hestia_earth_models-0.57.2.dist-info → hestia_earth_models-0.59.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,759 @@
|
|
|
1
|
+
from collections.abc import Iterable
|
|
2
|
+
from enum import Enum
|
|
3
|
+
from functools import reduce
|
|
4
|
+
from pydash.objects import merge
|
|
5
|
+
from typing import NamedTuple, Optional, Union
|
|
6
|
+
|
|
7
|
+
from hestia_earth.schema import (
|
|
8
|
+
CycleFunctionalUnit, EmissionMethodTier, MeasurementMethodClassification
|
|
9
|
+
)
|
|
10
|
+
from hestia_earth.utils.date import diff_in_days
|
|
11
|
+
from hestia_earth.utils.tools import flatten, non_empty_list
|
|
12
|
+
|
|
13
|
+
from hestia_earth.models.log import log_as_table, logRequirements, logShouldRun
|
|
14
|
+
from hestia_earth.models.utils.blank_node import (
|
|
15
|
+
group_nodes_by_year, GroupNodesByYearMode, node_term_match,
|
|
16
|
+
)
|
|
17
|
+
from hestia_earth.models.utils.constant import Units, get_atomic_conversion
|
|
18
|
+
from hestia_earth.models.utils.emission import _new_emission
|
|
19
|
+
from hestia_earth.models.utils.measurement import OLDEST_DATE
|
|
20
|
+
from hestia_earth.models.utils.site import related_cycles
|
|
21
|
+
|
|
22
|
+
from .utils import check_consecutive
|
|
23
|
+
from . import MODEL
|
|
24
|
+
|
|
25
|
+
REQUIREMENTS = {
|
|
26
|
+
"Cycle": {
|
|
27
|
+
"site": {
|
|
28
|
+
"measurements": [
|
|
29
|
+
{
|
|
30
|
+
"@type": "Measurement",
|
|
31
|
+
"value": "",
|
|
32
|
+
"dates": "",
|
|
33
|
+
"depthUpper": "0",
|
|
34
|
+
"depthLower": "30",
|
|
35
|
+
"term.@id": " organicCarbonPerHa"
|
|
36
|
+
}
|
|
37
|
+
]
|
|
38
|
+
},
|
|
39
|
+
"functionalUnit": "1 ha",
|
|
40
|
+
"endDate": "",
|
|
41
|
+
"optional": {
|
|
42
|
+
"startDate": ""
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
RETURNS = {
|
|
47
|
+
"Emission": [{
|
|
48
|
+
"value": "",
|
|
49
|
+
"methodTier": "",
|
|
50
|
+
"depth": "30"
|
|
51
|
+
}]
|
|
52
|
+
}
|
|
53
|
+
TERM_ID = 'co2ToAirSoilCarbonStockChangeManagementChange'
|
|
54
|
+
|
|
55
|
+
DEPTH_UPPER = 0
|
|
56
|
+
DEPTH_LOWER = 30
|
|
57
|
+
|
|
58
|
+
ORGANIC_CARBON_PER_HA_TERM_ID = 'organicCarbonPerHa'
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
SocStock = NamedTuple("SocStock", [
|
|
62
|
+
("value", float),
|
|
63
|
+
("method", MeasurementMethodClassification)
|
|
64
|
+
])
|
|
65
|
+
"""
|
|
66
|
+
NamedTuple representing either an SOC stock or SOC stock change.
|
|
67
|
+
|
|
68
|
+
Attributes
|
|
69
|
+
----------
|
|
70
|
+
value : float
|
|
71
|
+
The value of the SOC stock (kg C ha-1).
|
|
72
|
+
method: MeasurementMethodClassification
|
|
73
|
+
The measurement method for the SOC stock.
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
_InnerKey = Enum("_InnerKey", [
|
|
78
|
+
"SOC_STOCK",
|
|
79
|
+
"SOC_STOCK_CHANGE",
|
|
80
|
+
"SHARE_OF_EMISSIONS"
|
|
81
|
+
])
|
|
82
|
+
"""
|
|
83
|
+
The inner keys of the annualised inventory created by the `_should_run` function.
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
REQUIRED_INNER_KEYS = [_InnerKey.SHARE_OF_EMISSIONS, _InnerKey.SOC_STOCK_CHANGE]
|
|
88
|
+
|
|
89
|
+
MEASUREMENT_METHOD_RANKING = [
|
|
90
|
+
MeasurementMethodClassification.UNSOURCED_ASSUMPTION,
|
|
91
|
+
MeasurementMethodClassification.EXPERT_OPINION,
|
|
92
|
+
MeasurementMethodClassification.COUNTRY_LEVEL_STATISTICAL_DATA,
|
|
93
|
+
MeasurementMethodClassification.REGIONAL_STATISTICAL_DATA,
|
|
94
|
+
MeasurementMethodClassification.GEOSPATIAL_DATASET,
|
|
95
|
+
MeasurementMethodClassification.PHYSICAL_MEASUREMENT_ON_NEARBY_SITE,
|
|
96
|
+
MeasurementMethodClassification.TIER_1_MODEL,
|
|
97
|
+
MeasurementMethodClassification.TIER_2_MODEL,
|
|
98
|
+
MeasurementMethodClassification.TIER_3_MODEL,
|
|
99
|
+
MeasurementMethodClassification.MODELLED_USING_OTHER_MEASUREMENTS,
|
|
100
|
+
MeasurementMethodClassification.ON_SITE_PHYSICAL_MEASUREMENT
|
|
101
|
+
]
|
|
102
|
+
"""
|
|
103
|
+
A ranking of `MeasurementMethodClassification`s from weakest to strongest used to determine the `EmissionMethodTier` of
|
|
104
|
+
the `co2ToAirSoilCarbonStockChangeManagementChange` output.
|
|
105
|
+
|
|
106
|
+
The `EmissionMethodTier` should be based on the weakest `MeasurementMethodClassification` between the current SOC and
|
|
107
|
+
previous SOC.
|
|
108
|
+
"""
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _to_measurement_method_classification(
|
|
112
|
+
method: Union[str, MeasurementMethodClassification]
|
|
113
|
+
) -> Optional[MeasurementMethodClassification]:
|
|
114
|
+
"""
|
|
115
|
+
Convert the input to a MeasurementMethodClassification object if possible.
|
|
116
|
+
|
|
117
|
+
Parameters
|
|
118
|
+
----------
|
|
119
|
+
method : str | MeasurementMethodClassification
|
|
120
|
+
The measurement method as either a `str` or `MeasurementMethodClassification`.
|
|
121
|
+
|
|
122
|
+
Returns
|
|
123
|
+
-------
|
|
124
|
+
MeasurementMethodClassification | None
|
|
125
|
+
The matching `MeasurementMethodClassification` or `None` if invalid string.
|
|
126
|
+
"""
|
|
127
|
+
return (
|
|
128
|
+
method if isinstance(method, MeasurementMethodClassification)
|
|
129
|
+
else MeasurementMethodClassification(method) if method in (m.value for m in MeasurementMethodClassification)
|
|
130
|
+
else None
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _get_min_measurement_method(
|
|
135
|
+
*methods: Union[MeasurementMethodClassification, Iterable[MeasurementMethodClassification]]
|
|
136
|
+
) -> MeasurementMethodClassification:
|
|
137
|
+
"""
|
|
138
|
+
Get the minimum ranking measurement method from the provided methods.
|
|
139
|
+
|
|
140
|
+
Parameters
|
|
141
|
+
----------
|
|
142
|
+
*methods : MeasurementMethodClassification | Iterable[MeasurementMethodClassification]
|
|
143
|
+
Measurement methods or iterables of measurement methods.
|
|
144
|
+
|
|
145
|
+
Returns
|
|
146
|
+
-------
|
|
147
|
+
MeasurementMethodClassification
|
|
148
|
+
The measurement method with the minimum ranking.
|
|
149
|
+
"""
|
|
150
|
+
|
|
151
|
+
# flatten methods into a single list, convert any strings into `MeasurementMethodClassification`s
|
|
152
|
+
# and remove invalid methods.
|
|
153
|
+
_methods = non_empty_list(flatten([
|
|
154
|
+
[_to_measurement_method_classification(method) for method in arg] if isinstance(arg, Iterable)
|
|
155
|
+
else [_to_measurement_method_classification(arg)] for arg in methods
|
|
156
|
+
]))
|
|
157
|
+
|
|
158
|
+
return min(
|
|
159
|
+
_methods,
|
|
160
|
+
key=lambda method: MEASUREMENT_METHOD_RANKING.index(method),
|
|
161
|
+
default=list(MEASUREMENT_METHOD_RANKING)[0]
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _get_max_measurement_method(
|
|
166
|
+
*methods: Union[MeasurementMethodClassification, Iterable[MeasurementMethodClassification]]
|
|
167
|
+
) -> MeasurementMethodClassification:
|
|
168
|
+
"""
|
|
169
|
+
Get the max ranking measurement method from the provided methods.
|
|
170
|
+
|
|
171
|
+
Parameters
|
|
172
|
+
----------
|
|
173
|
+
*methods : MeasurementMethodClassification | Iterable[MeasurementMethodClassification]
|
|
174
|
+
Measurement methods or iterables of measurement methods.
|
|
175
|
+
|
|
176
|
+
Returns
|
|
177
|
+
-------
|
|
178
|
+
MeasurementMethodClassification
|
|
179
|
+
The measurement method with the maximum ranking.
|
|
180
|
+
"""
|
|
181
|
+
|
|
182
|
+
# flatten methods into a single list, convert any strings into `MeasurementMethodClassification`s
|
|
183
|
+
# and remove invalid methods.
|
|
184
|
+
_methods = non_empty_list(flatten([
|
|
185
|
+
[_to_measurement_method_classification(method) for method in arg] if isinstance(arg, Iterable)
|
|
186
|
+
else [_to_measurement_method_classification(arg)] for arg in methods
|
|
187
|
+
]))
|
|
188
|
+
|
|
189
|
+
return max(
|
|
190
|
+
_methods,
|
|
191
|
+
key=lambda method: MEASUREMENT_METHOD_RANKING.index(method),
|
|
192
|
+
default=MEASUREMENT_METHOD_RANKING[-1]
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
DEFAULT_EMISSION_METHOD_TIER = EmissionMethodTier.TIER_1
|
|
197
|
+
MEASUREMENT_METHOD_CLASSIFICATION_TO_EMISSION_METHOD_TIER = {
|
|
198
|
+
MeasurementMethodClassification.TIER_2_MODEL: EmissionMethodTier.TIER_2,
|
|
199
|
+
MeasurementMethodClassification.TIER_3_MODEL: EmissionMethodTier.TIER_3,
|
|
200
|
+
MeasurementMethodClassification.MODELLED_USING_OTHER_MEASUREMENTS: EmissionMethodTier.MEASURED,
|
|
201
|
+
MeasurementMethodClassification.ON_SITE_PHYSICAL_MEASUREMENT: EmissionMethodTier.MEASURED,
|
|
202
|
+
}
|
|
203
|
+
"""
|
|
204
|
+
A mapping between `MeasurementMethodClassification`s and `EmissionMethodTier`s. As SOC measurements can be
|
|
205
|
+
measured/estimated through a variety of methods, the emission model needs be able to assign an emission tier for each.
|
|
206
|
+
Any `MeasurementMethodClassification` not in the mapping should be assigned `DEFAULT_EMISSION_METHOD_TIER`.
|
|
207
|
+
"""
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def _get_emission_method_tier(
|
|
211
|
+
measurement_method: MeasurementMethodClassification
|
|
212
|
+
) -> EmissionMethodTier:
|
|
213
|
+
"""
|
|
214
|
+
Get the emission method tier based on the provided measurement method.
|
|
215
|
+
|
|
216
|
+
Parameters
|
|
217
|
+
----------
|
|
218
|
+
measurement_method : MeasurementMethodClassification
|
|
219
|
+
The measurement method classification.
|
|
220
|
+
|
|
221
|
+
Returns
|
|
222
|
+
-------
|
|
223
|
+
EmissionMethodTier
|
|
224
|
+
The corresponding emission method tier.
|
|
225
|
+
"""
|
|
226
|
+
return MEASUREMENT_METHOD_CLASSIFICATION_TO_EMISSION_METHOD_TIER.get(
|
|
227
|
+
measurement_method, DEFAULT_EMISSION_METHOD_TIER
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def _emission(
|
|
232
|
+
value: float, method_tier: EmissionMethodTier
|
|
233
|
+
) -> dict:
|
|
234
|
+
"""
|
|
235
|
+
Create an emission node based on the provided value and method tier.
|
|
236
|
+
|
|
237
|
+
See [Emission schema](https://www.hestia.earth/schema/Emission) for more information.
|
|
238
|
+
|
|
239
|
+
Parameters
|
|
240
|
+
----------
|
|
241
|
+
value : float
|
|
242
|
+
The emission value (kg CO2 ha-1).
|
|
243
|
+
|
|
244
|
+
method_tier : EmissionMethodTier
|
|
245
|
+
The emission method tier.
|
|
246
|
+
|
|
247
|
+
Returns
|
|
248
|
+
-------
|
|
249
|
+
dict
|
|
250
|
+
The emission dictionary with keys 'depth', 'value', and 'methodTier'.
|
|
251
|
+
"""
|
|
252
|
+
emission = _new_emission(TERM_ID, MODEL)
|
|
253
|
+
emission["depth"] = DEPTH_LOWER
|
|
254
|
+
emission["value"] = [value]
|
|
255
|
+
emission["methodTier"] = method_tier.value
|
|
256
|
+
return emission
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def _linear_interpolate_soc_stock(
|
|
260
|
+
start_year: int,
|
|
261
|
+
end_year: int,
|
|
262
|
+
start_soc_stock: SocStock,
|
|
263
|
+
end_soc_stock: SocStock,
|
|
264
|
+
year: int
|
|
265
|
+
) -> SocStock:
|
|
266
|
+
"""
|
|
267
|
+
Linearly interpolate the SocStock value for a specific year between two given years.
|
|
268
|
+
|
|
269
|
+
The `MeasurementMethodClassification` of any SOC stocks estimated using this method should be `tier 1 model` as the
|
|
270
|
+
method is derived from IPCC (2019) Tier 1 SOC model.
|
|
271
|
+
|
|
272
|
+
Parameters
|
|
273
|
+
----------
|
|
274
|
+
start_year : int
|
|
275
|
+
The start year for interpolation.
|
|
276
|
+
end_year : int
|
|
277
|
+
The end year for interpolation.
|
|
278
|
+
start_soc_stock : SocStock
|
|
279
|
+
The `SocStock` corresponding to the start year.
|
|
280
|
+
end_soc_stock : SocStock
|
|
281
|
+
The `SocStock` corresponding to the end year.
|
|
282
|
+
year : int
|
|
283
|
+
The target year for interpolation.
|
|
284
|
+
|
|
285
|
+
Returns
|
|
286
|
+
-------
|
|
287
|
+
SocStock
|
|
288
|
+
The interpolated `SocStock` for the specified year.
|
|
289
|
+
"""
|
|
290
|
+
METHOD = MeasurementMethodClassification.TIER_1_MODEL
|
|
291
|
+
|
|
292
|
+
time_ratio = (year - start_year) / (end_year - start_year)
|
|
293
|
+
soc_delta = (end_soc_stock.value - start_soc_stock.value) * time_ratio
|
|
294
|
+
value = start_soc_stock.value + soc_delta
|
|
295
|
+
|
|
296
|
+
return SocStock(value, METHOD)
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def _calc_soc_stock_change(start_soc_stock: SocStock, end_soc_stock: SocStock) -> SocStock:
|
|
300
|
+
"""
|
|
301
|
+
Calculate the change in SOC stock change between the current and previous states.
|
|
302
|
+
|
|
303
|
+
The method should be the weaker of the two `MeasurementMethodClassification`s.
|
|
304
|
+
|
|
305
|
+
Parameters
|
|
306
|
+
----------
|
|
307
|
+
start_soc_stock : SocStock
|
|
308
|
+
The SOC stock at the start (kg C ha-1).
|
|
309
|
+
|
|
310
|
+
end_soc_stock : SocStock
|
|
311
|
+
The SOC stock at the end (kg C ha-1).
|
|
312
|
+
|
|
313
|
+
Returns
|
|
314
|
+
-------
|
|
315
|
+
SocStock
|
|
316
|
+
The SOC stock change (kg C ha-1).
|
|
317
|
+
"""
|
|
318
|
+
value = end_soc_stock.value - start_soc_stock.value
|
|
319
|
+
method = _get_min_measurement_method(end_soc_stock.method, start_soc_stock.method)
|
|
320
|
+
|
|
321
|
+
return SocStock(value, method)
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def _convert_c_to_co2(kg_c: float) -> float:
|
|
325
|
+
"""
|
|
326
|
+
Convert mass of carbon (C) to carbon dioxide (CO2) using the atomic conversion ratio.
|
|
327
|
+
|
|
328
|
+
n.b. `get_atomic_conversion` returns the ratio C:CO2 (~44/12).
|
|
329
|
+
|
|
330
|
+
Parameters
|
|
331
|
+
----------
|
|
332
|
+
kg_c : float
|
|
333
|
+
Mass of carbon (C) to be converted to carbon dioxide (CO2) (kg C).
|
|
334
|
+
|
|
335
|
+
Returns
|
|
336
|
+
-------
|
|
337
|
+
float
|
|
338
|
+
Mass of carbon dioxide (CO2) resulting from the conversion (kg CO2).
|
|
339
|
+
"""
|
|
340
|
+
return kg_c * get_atomic_conversion(Units.KG_CO2, Units.TO_C)
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def _soc_stock_stock_change_to_co2_emission(
|
|
344
|
+
soc_stock_change_value: float,
|
|
345
|
+
share_of_emission: float
|
|
346
|
+
) -> float:
|
|
347
|
+
"""
|
|
348
|
+
Convert SOC stock change to CO2 emission using the given share of emission.
|
|
349
|
+
|
|
350
|
+
Parameters
|
|
351
|
+
----------
|
|
352
|
+
soc_stock_change_value : float
|
|
353
|
+
The change in SOC stock value.
|
|
354
|
+
|
|
355
|
+
share_of_emission : float
|
|
356
|
+
The share of emission associated with the SOC stock change.
|
|
357
|
+
|
|
358
|
+
Returns
|
|
359
|
+
-------
|
|
360
|
+
float
|
|
361
|
+
The corresponding CO2 emission resulting from the SOC stock change.
|
|
362
|
+
"""
|
|
363
|
+
return -1 * share_of_emission * _convert_c_to_co2(soc_stock_change_value)
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
def _sorted_merge(*sources: Union[dict, list[dict]]) -> dict:
|
|
367
|
+
"""
|
|
368
|
+
Merge dictionaries and return the result as a new dictionary with keys sorted in order to preserve the temporal
|
|
369
|
+
order of inventory years.
|
|
370
|
+
|
|
371
|
+
Parameters
|
|
372
|
+
----------
|
|
373
|
+
*sources : dict | List[dict]
|
|
374
|
+
One or more dictionaries or lists of dictionaries to be merged.
|
|
375
|
+
|
|
376
|
+
Returns
|
|
377
|
+
-------
|
|
378
|
+
dict
|
|
379
|
+
A new dictionary containing the merged key-value pairs, with keys sorted.
|
|
380
|
+
"""
|
|
381
|
+
|
|
382
|
+
_sources = non_empty_list(
|
|
383
|
+
flatten([arg if isinstance(arg, list) else [arg] for arg in sources])
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
merged = reduce(merge, _sources, {})
|
|
387
|
+
return dict(sorted(merged.items()))
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
def _validate_soc_measurement_node(node: dict) -> bool:
|
|
391
|
+
"""
|
|
392
|
+
Validate a SOC measurement node against specified criteria.
|
|
393
|
+
|
|
394
|
+
Parameters
|
|
395
|
+
----------
|
|
396
|
+
node : dict
|
|
397
|
+
The SOC [Measurement node](https://www.hestia.earth/schema/Measurement) to be validated.
|
|
398
|
+
|
|
399
|
+
Returns
|
|
400
|
+
-------
|
|
401
|
+
bool
|
|
402
|
+
True if the node passes all validation criteria, False otherwise.
|
|
403
|
+
"""
|
|
404
|
+
return all([
|
|
405
|
+
node_term_match(node, ORGANIC_CARBON_PER_HA_TERM_ID),
|
|
406
|
+
node.get("depthUpper") == DEPTH_UPPER,
|
|
407
|
+
node.get("depthLower") == DEPTH_LOWER
|
|
408
|
+
])
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
def _nodes_to_soc_stock(year: int, nodes: list[dict]) -> SocStock:
|
|
412
|
+
"""
|
|
413
|
+
Reduces all the the SOC measurement nodes in an inventory year into a single value and measurement method.
|
|
414
|
+
|
|
415
|
+
Any nodes with missing or invalid `dates` field will already have been filtered out at this point, so we can assume
|
|
416
|
+
`node.value` and `node.dates` will have equal number of elements. See test case `missing-measurement-dates`.
|
|
417
|
+
|
|
418
|
+
Parameters
|
|
419
|
+
----------
|
|
420
|
+
year : int
|
|
421
|
+
The target year for calculating the SOC stock.
|
|
422
|
+
|
|
423
|
+
nodes : List[dict]
|
|
424
|
+
List of [Measurement nodes](https://www.hestia.earth/schema/Measurement) containing SOC data.
|
|
425
|
+
|
|
426
|
+
Returns
|
|
427
|
+
-------
|
|
428
|
+
SocStock
|
|
429
|
+
The calculated SOC stock for the specified year.
|
|
430
|
+
"""
|
|
431
|
+
target_date = f"{year}-12-31T23:59:59"
|
|
432
|
+
|
|
433
|
+
values = flatten([measurement.get("value", []) for measurement in nodes])
|
|
434
|
+
dates = flatten([measurement.get("dates", []) for measurement in nodes])
|
|
435
|
+
methods = flatten([
|
|
436
|
+
[measurement.get("methodClassification") for _ in measurement.get("value", [])]
|
|
437
|
+
for measurement in nodes
|
|
438
|
+
])
|
|
439
|
+
|
|
440
|
+
closest_date = min(
|
|
441
|
+
dates,
|
|
442
|
+
key=lambda date: abs(diff_in_days(date if date else OLDEST_DATE, target_date)),
|
|
443
|
+
)
|
|
444
|
+
|
|
445
|
+
closest_method = _get_max_measurement_method(
|
|
446
|
+
method for method, date in zip(methods, dates) if date == closest_date
|
|
447
|
+
)
|
|
448
|
+
|
|
449
|
+
value = next(
|
|
450
|
+
(value for value, method in zip(values, methods) if method == closest_method.value), 0
|
|
451
|
+
)
|
|
452
|
+
|
|
453
|
+
return SocStock(value, closest_method)
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
def _group_soc_stocks(site: dict) -> dict:
|
|
457
|
+
"""
|
|
458
|
+
Group valid `organicCarbonPerHa` measurement nodes by year (based on node "dates" field) and reduce them to a
|
|
459
|
+
single `SocStock` for each year.
|
|
460
|
+
|
|
461
|
+
Parameters
|
|
462
|
+
----------
|
|
463
|
+
site : dict
|
|
464
|
+
A [Site node](https://www.hestia.earth/schema/Cycle).
|
|
465
|
+
|
|
466
|
+
Returns
|
|
467
|
+
-------
|
|
468
|
+
dict
|
|
469
|
+
A dictionary where each key represents a year and its corresponding value is a dictionary containing SOC stock
|
|
470
|
+
information under the inner key specified by _InnerKey.SOC_STOCK.
|
|
471
|
+
"""
|
|
472
|
+
INNER_KEY = _InnerKey.SOC_STOCK
|
|
473
|
+
|
|
474
|
+
grouped_soc_measurements = group_nodes_by_year(
|
|
475
|
+
(node for node in site.get("measurements", []) if _validate_soc_measurement_node(node)),
|
|
476
|
+
mode=GroupNodesByYearMode.DATES
|
|
477
|
+
)
|
|
478
|
+
|
|
479
|
+
return {
|
|
480
|
+
year: {
|
|
481
|
+
INNER_KEY: (
|
|
482
|
+
_nodes_to_soc_stock(year, nodes)
|
|
483
|
+
)
|
|
484
|
+
} for year, nodes in grouped_soc_measurements.items()
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
def _interpolate_grouped_soc_stocks(grouped_soc_stocks: dict) -> dict:
|
|
489
|
+
"""
|
|
490
|
+
Interpolate SOC stocks for years between grouped SOC stock data.
|
|
491
|
+
|
|
492
|
+
This function iterates over the provided grouped SOC stock data and performs linear interpolation for years between
|
|
493
|
+
existing data points. The result is a dictionary with SOC stock information for all years, including those without
|
|
494
|
+
initially available data.
|
|
495
|
+
|
|
496
|
+
Parameters
|
|
497
|
+
----------
|
|
498
|
+
grouped_soc_stocks : dict
|
|
499
|
+
Dictionary containing grouped SOC stock data with years as keys.
|
|
500
|
+
|
|
501
|
+
Returns
|
|
502
|
+
-------
|
|
503
|
+
dict
|
|
504
|
+
A dictionary with interpolated SOC stock data for missing years.
|
|
505
|
+
"""
|
|
506
|
+
|
|
507
|
+
INNER_KEY = _InnerKey.SOC_STOCK
|
|
508
|
+
|
|
509
|
+
def group_interpolate(data: dict, i: int):
|
|
510
|
+
current_year = list(grouped_soc_stocks.keys())[i]
|
|
511
|
+
prev_year = list(grouped_soc_stocks.keys())[i-1]
|
|
512
|
+
|
|
513
|
+
current_soc_stock = grouped_soc_stocks[current_year][INNER_KEY]
|
|
514
|
+
prev_soc_stock = grouped_soc_stocks[prev_year][INNER_KEY]
|
|
515
|
+
|
|
516
|
+
return data | {
|
|
517
|
+
inner_year: {
|
|
518
|
+
INNER_KEY: _linear_interpolate_soc_stock(
|
|
519
|
+
prev_year, current_year, prev_soc_stock, current_soc_stock, inner_year
|
|
520
|
+
)
|
|
521
|
+
} for inner_year in range(prev_year+1, current_year)
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
return reduce(group_interpolate, range(1, len(grouped_soc_stocks)), dict())
|
|
525
|
+
|
|
526
|
+
|
|
527
|
+
def _get_grouped_soc_stocks(site: dict) -> dict:
|
|
528
|
+
"""
|
|
529
|
+
Get grouped and interpolated SOC stocks for a site.
|
|
530
|
+
|
|
531
|
+
This function combines grouping and interpolation of SOC stocks for a given site, providing a comprehensive
|
|
532
|
+
dictionary with SOC stock information for all years.
|
|
533
|
+
|
|
534
|
+
Parameters
|
|
535
|
+
----------
|
|
536
|
+
site : dict
|
|
537
|
+
The site dictionary containing SOC measurements.
|
|
538
|
+
|
|
539
|
+
Returns
|
|
540
|
+
-------
|
|
541
|
+
dict
|
|
542
|
+
A dictionary with grouped and interpolated SOC stock data for all years.
|
|
543
|
+
"""
|
|
544
|
+
grouped_soc_stocks = _group_soc_stocks(site)
|
|
545
|
+
grouped_interpolated_soc_stocks = _interpolate_grouped_soc_stocks(grouped_soc_stocks)
|
|
546
|
+
return _sorted_merge(grouped_soc_stocks, grouped_interpolated_soc_stocks)
|
|
547
|
+
|
|
548
|
+
|
|
549
|
+
def _calc_grouped_soc_stock_changes(grouped_soc_stocks: dict) -> dict:
|
|
550
|
+
"""
|
|
551
|
+
Calculate SOC stock changes between grouped SOC stock data for consecutive years.
|
|
552
|
+
|
|
553
|
+
Parameters
|
|
554
|
+
----------
|
|
555
|
+
grouped_soc_stocks : dict
|
|
556
|
+
Dictionary containing grouped SOC stock data with years as keys.
|
|
557
|
+
|
|
558
|
+
Returns
|
|
559
|
+
-------
|
|
560
|
+
dict
|
|
561
|
+
A dictionary with calculated SOC stock changes for consecutive years.
|
|
562
|
+
"""
|
|
563
|
+
INNER_KEY = _InnerKey.SOC_STOCK_CHANGE
|
|
564
|
+
|
|
565
|
+
def group_changes(data: dict, i: int):
|
|
566
|
+
current_year = list(grouped_soc_stocks.keys())[i]
|
|
567
|
+
prev_year = list(grouped_soc_stocks.keys())[i-1]
|
|
568
|
+
|
|
569
|
+
current_soc_stock = grouped_soc_stocks[current_year][_InnerKey.SOC_STOCK]
|
|
570
|
+
prev_soc_stock = grouped_soc_stocks[prev_year][_InnerKey.SOC_STOCK]
|
|
571
|
+
|
|
572
|
+
return data | {
|
|
573
|
+
current_year: {
|
|
574
|
+
INNER_KEY: _calc_soc_stock_change(prev_soc_stock, current_soc_stock)
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
return reduce(group_changes, range(1, len(grouped_soc_stocks)), dict())
|
|
579
|
+
|
|
580
|
+
|
|
581
|
+
def _calc_sum_cycle_occupancy(cycles: list[dict]) -> float:
|
|
582
|
+
"""
|
|
583
|
+
Calculate the sum of cycle occupancies based on the `fraction_of_group_duration` field added by the
|
|
584
|
+
`group_nodes_by_year` function.
|
|
585
|
+
|
|
586
|
+
If a cycle does not have the "fraction_of_group_duration" key, it is treated as zero occupancy for that year.
|
|
587
|
+
|
|
588
|
+
Parameters
|
|
589
|
+
----------
|
|
590
|
+
cycles : List[dict]
|
|
591
|
+
List of cycles, where each cycle dictionary should contain a "fraction_of_group_duration" key.
|
|
592
|
+
|
|
593
|
+
Returns
|
|
594
|
+
-------
|
|
595
|
+
float
|
|
596
|
+
The sum of cycle occupancies based on the fraction of the year.
|
|
597
|
+
"""
|
|
598
|
+
return sum(cycle.get("fraction_of_group_duration", 0) for cycle in cycles)
|
|
599
|
+
|
|
600
|
+
|
|
601
|
+
def _calc_grouped_share_of_emissions(cycles: list[dict]) -> dict:
|
|
602
|
+
"""
|
|
603
|
+
Calculate grouped share of emissions for cycles based on the amount they contribute the the overall land management
|
|
604
|
+
of an inventory year.
|
|
605
|
+
|
|
606
|
+
This function groups cycles by year, then calculates the share of emissions for each cycle based on the
|
|
607
|
+
"fraction_of_group_duration" value. The share of emissions is normalized by the sum of cycle occupancies for the
|
|
608
|
+
entire dataset to ensure the values represent a valid share.
|
|
609
|
+
|
|
610
|
+
Parameters
|
|
611
|
+
----------
|
|
612
|
+
cycles : List[dict]
|
|
613
|
+
List of [Cycle nodes](https://www.hestia.earth/schema/Cycle), where each cycle dictionary should contain a
|
|
614
|
+
"fraction_of_group_duration" key added by the `group_nodes_by_year` function.
|
|
615
|
+
|
|
616
|
+
Returns
|
|
617
|
+
-------
|
|
618
|
+
dict
|
|
619
|
+
A dictionary with grouped share of emissions for each cycle based on the fraction of the year.
|
|
620
|
+
"""
|
|
621
|
+
INNER_KEY = _InnerKey.SHARE_OF_EMISSIONS
|
|
622
|
+
grouped_cycles = group_nodes_by_year(cycles)
|
|
623
|
+
return {
|
|
624
|
+
year: {
|
|
625
|
+
INNER_KEY: {
|
|
626
|
+
cycle["@id"]: cycle.get("fraction_of_group_duration", 0) / _calc_sum_cycle_occupancy(cycles)
|
|
627
|
+
for cycle in cycles
|
|
628
|
+
}
|
|
629
|
+
} for year, cycles in grouped_cycles.items()
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
|
|
633
|
+
def _run(cycle_id: str, grouped_data: dict) -> list[dict]:
|
|
634
|
+
"""
|
|
635
|
+
Calculate emissions for a specific cycle using grouped SOC stock change and share of emissions data.
|
|
636
|
+
|
|
637
|
+
The emission method tier based on the minimum measurement method tier among the SOC stock change data in the
|
|
638
|
+
grouped data.
|
|
639
|
+
|
|
640
|
+
Parameters
|
|
641
|
+
----------
|
|
642
|
+
cycle_id : str
|
|
643
|
+
The "@id" field of the [Cycle node](https://www.hestia.earth/schema/Cycle).
|
|
644
|
+
|
|
645
|
+
grouped_data : dict
|
|
646
|
+
A dictionary containing grouped SOC stock change and share of emissions data.
|
|
647
|
+
|
|
648
|
+
Returns
|
|
649
|
+
-------
|
|
650
|
+
list[dict]
|
|
651
|
+
A list containing emission data calculated for the specified cycle.
|
|
652
|
+
"""
|
|
653
|
+
|
|
654
|
+
value = sum(
|
|
655
|
+
_soc_stock_stock_change_to_co2_emission(
|
|
656
|
+
group[_InnerKey.SOC_STOCK_CHANGE].value,
|
|
657
|
+
group[_InnerKey.SHARE_OF_EMISSIONS].get(cycle_id, 1)
|
|
658
|
+
) for group in grouped_data.values()
|
|
659
|
+
)
|
|
660
|
+
|
|
661
|
+
method_tier = _get_emission_method_tier(
|
|
662
|
+
_get_min_measurement_method(
|
|
663
|
+
group[_InnerKey.SOC_STOCK_CHANGE].method for group in grouped_data.values()
|
|
664
|
+
)
|
|
665
|
+
)
|
|
666
|
+
|
|
667
|
+
return [_emission(value, method_tier)]
|
|
668
|
+
|
|
669
|
+
|
|
670
|
+
def get_site(cycle: dict) -> dict:
|
|
671
|
+
return cycle.get("site", {})
|
|
672
|
+
|
|
673
|
+
|
|
674
|
+
def _should_run(cycle: dict) -> tuple:
|
|
675
|
+
"""
|
|
676
|
+
Determine if calculations should run for a given cycle based on SOC stock and emissions data.
|
|
677
|
+
|
|
678
|
+
This function assesses whether calculations should run for a given cycle by checking the availability of SOC stock
|
|
679
|
+
data and cycle nodes. It retrieves SOC stock data and for the site linked to the cycle, calculates SOC stock
|
|
680
|
+
changes and share of emissions, and merges the data into a grouped format. The function checks for the presence of
|
|
681
|
+
necessary keys in the grouped data and ensures that the years are consecutive before determining if calculations
|
|
682
|
+
should run.
|
|
683
|
+
|
|
684
|
+
Parameters
|
|
685
|
+
----------
|
|
686
|
+
cycle : dict
|
|
687
|
+
The cycle dictionary for which the calculations will be evaluated.
|
|
688
|
+
|
|
689
|
+
Returns
|
|
690
|
+
-------
|
|
691
|
+
tuple
|
|
692
|
+
A tuple containing a boolean indicating whether calculations should run,
|
|
693
|
+
the cycle identifier, and grouped SOC stock and emissions data.
|
|
694
|
+
"""
|
|
695
|
+
cycle_id = cycle.get("@id")
|
|
696
|
+
site = get_site(cycle)
|
|
697
|
+
cycles = related_cycles(site.get("@id"))
|
|
698
|
+
|
|
699
|
+
grouped_soc_stocks = _get_grouped_soc_stocks(site)
|
|
700
|
+
grouped_soc_stock_changes = _calc_grouped_soc_stock_changes(grouped_soc_stocks)
|
|
701
|
+
grouped_share_of_emissions = _calc_grouped_share_of_emissions(cycles)
|
|
702
|
+
|
|
703
|
+
def _should_run_group(year: int, group: dict) -> bool:
|
|
704
|
+
is_data_complete = all(key in group.keys() for key in REQUIRED_INNER_KEYS)
|
|
705
|
+
is_relevant_for_cycle = cycle_id in group.get(_InnerKey.SHARE_OF_EMISSIONS, {}).keys()
|
|
706
|
+
return is_data_complete and is_relevant_for_cycle
|
|
707
|
+
|
|
708
|
+
inventory = _sorted_merge(grouped_soc_stocks, grouped_soc_stock_changes, grouped_share_of_emissions)
|
|
709
|
+
valid_years = [year for year, group in inventory.items() if _should_run_group(year, group)]
|
|
710
|
+
|
|
711
|
+
num_organic_carbon_per_ha_measurements = len(
|
|
712
|
+
[node for node in site.get('measurements', []) if _validate_soc_measurement_node(node)]
|
|
713
|
+
)
|
|
714
|
+
has_organic_carbon_per_ha_measurements = num_organic_carbon_per_ha_measurements > 1
|
|
715
|
+
has_functional_unit_1_ha = all(
|
|
716
|
+
cycle.get('functionalUnit') == CycleFunctionalUnit._1_HA.value for cycle in cycles
|
|
717
|
+
)
|
|
718
|
+
has_valid_inventory = len(valid_years) > 0 and check_consecutive(valid_years)
|
|
719
|
+
|
|
720
|
+
logRequirements(
|
|
721
|
+
cycle, model=MODEL, term=TERM_ID,
|
|
722
|
+
has_organic_carbon_per_ha_measurements=has_organic_carbon_per_ha_measurements,
|
|
723
|
+
has_functional_unit_1_ha=has_functional_unit_1_ha,
|
|
724
|
+
has_valid_inventory=has_valid_inventory,
|
|
725
|
+
inventory=log_as_table(
|
|
726
|
+
{
|
|
727
|
+
"year": year,
|
|
728
|
+
"should-run": year in valid_years,
|
|
729
|
+
"soc-stock": (
|
|
730
|
+
group.get(_InnerKey.SOC_STOCK).value if group.get(_InnerKey.SOC_STOCK)
|
|
731
|
+
else None
|
|
732
|
+
),
|
|
733
|
+
"soc-stock-method": (
|
|
734
|
+
group.get(_InnerKey.SOC_STOCK).method.value if group.get(_InnerKey.SOC_STOCK)
|
|
735
|
+
else None
|
|
736
|
+
),
|
|
737
|
+
"soc-stock-change": (
|
|
738
|
+
group.get(_InnerKey.SOC_STOCK_CHANGE).value if group.get(_InnerKey.SOC_STOCK_CHANGE)
|
|
739
|
+
else None
|
|
740
|
+
),
|
|
741
|
+
"soc-stock-change-method": (
|
|
742
|
+
group.get(_InnerKey.SOC_STOCK_CHANGE).method.value if group.get(_InnerKey.SOC_STOCK_CHANGE)
|
|
743
|
+
else None
|
|
744
|
+
),
|
|
745
|
+
"share-of-emission": group.get(_InnerKey.SHARE_OF_EMISSIONS, {}).get(cycle_id, 0)
|
|
746
|
+
} for year, group in inventory.items()
|
|
747
|
+
)
|
|
748
|
+
)
|
|
749
|
+
|
|
750
|
+
should_run = has_functional_unit_1_ha and has_valid_inventory
|
|
751
|
+
logShouldRun(cycle, MODEL, TERM_ID, should_run)
|
|
752
|
+
|
|
753
|
+
return should_run, cycle_id, {year: group for year, group in inventory.items() if year in valid_years}
|
|
754
|
+
|
|
755
|
+
|
|
756
|
+
def run(cycle: dict) -> list[dict]:
|
|
757
|
+
should_run, *args = _should_run(cycle)
|
|
758
|
+
|
|
759
|
+
return _run(*args) if should_run else []
|