hestia-earth-models 0.70.1__py3-none-any.whl → 0.70.3__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/cml2001Baseline/resourceUseMineralsAndMetalsDuringCycle.py +2 -1
- hestia_earth/models/config/Cycle.json +68 -0
- hestia_earth/models/config/Site.json +8 -0
- hestia_earth/models/cycle/practice/landCover.py +181 -0
- hestia_earth/models/emepEea2019/nh3ToAirExcreta.py +1 -1
- hestia_earth/models/hestia/excretaKgMass.py +1 -1
- hestia_earth/models/hestia/management.py +15 -113
- hestia_earth/models/hestia/pToSurfaceWaterAquacultureSystems.py +148 -0
- hestia_earth/models/hestia/soilMeasurement.py +1 -1
- hestia_earth/models/ipcc2019/ch4ToAirFloodedRice.py +8 -6
- hestia_earth/models/ipcc2019/ch4ToAirOrganicSoilCultivation.py +270 -0
- hestia_earth/models/ipcc2019/co2ToAirAboveGroundBiomassStockChange.py +0 -3
- hestia_earth/models/ipcc2019/co2ToAirBelowGroundBiomassStockChange.py +0 -3
- hestia_earth/models/ipcc2019/co2ToAirCarbonStockChange_utils.py +57 -43
- hestia_earth/models/ipcc2019/co2ToAirLimeHydrolysis.py +7 -5
- hestia_earth/models/ipcc2019/co2ToAirOrganicSoilCultivation.py +215 -0
- hestia_earth/models/ipcc2019/co2ToAirSoilOrganicCarbonStockChange.py +0 -3
- hestia_earth/models/ipcc2019/n2OToAirOrganicSoilCultivationDirect.py +161 -0
- hestia_earth/models/ipcc2019/no3ToGroundwaterExcreta.py +1 -1
- hestia_earth/models/ipcc2019/organicCarbonPerHa_tier_2.py +15 -4
- hestia_earth/models/ipcc2019/organicSoilCultivation_utils.py +159 -0
- hestia_earth/models/mocking/search-results.json +713 -705
- hestia_earth/models/pooreNemecek2018/excretaKgN.py +3 -1
- hestia_earth/models/site/grouped_measurement.py +132 -0
- hestia_earth/models/utils/__init__.py +4 -3
- hestia_earth/models/utils/blank_node.py +40 -11
- hestia_earth/models/utils/constant.py +26 -20
- hestia_earth/models/utils/excretaManagement.py +2 -2
- hestia_earth/models/utils/product.py +39 -1
- hestia_earth/models/utils/property.py +25 -12
- hestia_earth/models/version.py +1 -1
- {hestia_earth_models-0.70.1.dist-info → hestia_earth_models-0.70.3.dist-info}/METADATA +2 -2
- {hestia_earth_models-0.70.1.dist-info → hestia_earth_models-0.70.3.dist-info}/RECORD +49 -36
- tests/models/cycle/practice/test_landCover.py +27 -0
- tests/models/hestia/test_feedConversionRatio.py +2 -3
- tests/models/hestia/test_pToSurfaceWaterAquacultureSystems.py +56 -0
- tests/models/hestia/test_soilMeasurement.py +11 -19
- tests/models/ipcc2019/test_ch4ToAirEntericFermentation.py +4 -7
- tests/models/ipcc2019/test_ch4ToAirOrganicSoilCultivation.py +61 -0
- tests/models/ipcc2019/test_co2ToAirAboveGroundBiomassStockChange.py +11 -9
- tests/models/ipcc2019/test_co2ToAirBelowGroundBiomassStockChange.py +10 -8
- tests/models/ipcc2019/test_co2ToAirLimeHydrolysis.py +1 -1
- tests/models/ipcc2019/test_co2ToAirOrganicSoilCultivation.py +62 -0
- tests/models/ipcc2019/test_co2ToAirSoilOrganicCarbonStockChange.py +11 -9
- tests/models/ipcc2019/test_n2OToAirOrganicSoilCultivationDirect.py +61 -0
- tests/models/site/test_grouped_measurement.py +20 -0
- {hestia_earth_models-0.70.1.dist-info → hestia_earth_models-0.70.3.dist-info}/LICENSE +0 -0
- {hestia_earth_models-0.70.1.dist-info → hestia_earth_models-0.70.3.dist-info}/WHEEL +0 -0
- {hestia_earth_models-0.70.1.dist-info → hestia_earth_models-0.70.3.dist-info}/top_level.txt +0 -0
@@ -204,11 +204,19 @@ _CARBON_INPUT_PROPERTY_TERM_IDS = [
|
|
204
204
|
_DRY_MATTER_TERM_ID
|
205
205
|
]
|
206
206
|
|
207
|
-
|
207
|
+
_INPUT_CARBON_SOURCE_TERM_TYPES = [
|
208
208
|
TermTermType.ORGANICFERTILISER.value,
|
209
209
|
TermTermType.SOILAMENDMENT.value
|
210
210
|
]
|
211
211
|
|
212
|
+
_PRACTICE_CARBON_SOURCE_TERM_TYPES = [
|
213
|
+
TermTermType.LANDCOVER
|
214
|
+
]
|
215
|
+
|
216
|
+
_PRODUCT_CARBON_SOURCE_TERM_TYPES = [
|
217
|
+
TermTermType.CROPRESIDUE
|
218
|
+
]
|
219
|
+
|
212
220
|
_VALID_SITE_TYPES = [
|
213
221
|
SiteSiteType.CROPLAND.value
|
214
222
|
]
|
@@ -1447,7 +1455,10 @@ def _get_carbon_sources(cycle: dict) -> list[CarbonSource]:
|
|
1447
1455
|
list[CarbonSource]
|
1448
1456
|
A formatted list of `CarbonSource`s.
|
1449
1457
|
"""
|
1450
|
-
|
1458
|
+
carbon_source_nodes = filter_list_term_type(
|
1459
|
+
cycle.get("inputs", []) + cycle.get("practices", []) + cycle.get("products", []),
|
1460
|
+
_INPUT_CARBON_SOURCE_TERM_TYPES + _PRACTICE_CARBON_SOURCE_TERM_TYPES + _PRODUCT_CARBON_SOURCE_TERM_TYPES
|
1461
|
+
)
|
1451
1462
|
|
1452
1463
|
group_fac = cycle.get('fraction_of_group_duration')
|
1453
1464
|
node_fac = cycle.get('fraction_of_node_duration')
|
@@ -1466,7 +1477,7 @@ def _get_carbon_sources(cycle: dict) -> list[CarbonSource]:
|
|
1466
1477
|
if validator(node)
|
1467
1478
|
),
|
1468
1479
|
None
|
1469
|
-
) for node in
|
1480
|
+
) for node in carbon_source_nodes
|
1470
1481
|
])
|
1471
1482
|
|
1472
1483
|
|
@@ -1603,7 +1614,7 @@ def _should_run_carbon_source(node: dict) -> bool:
|
|
1603
1614
|
"""
|
1604
1615
|
return any([
|
1605
1616
|
node.get("term", {}).get("@id") in _CARBON_SOURCE_TERM_IDS,
|
1606
|
-
node.get("term", {}).get("termType") in
|
1617
|
+
node.get("term", {}).get("termType") in _INPUT_CARBON_SOURCE_TERM_TYPES
|
1607
1618
|
])
|
1608
1619
|
|
1609
1620
|
|
@@ -0,0 +1,159 @@
|
|
1
|
+
from enum import Enum
|
2
|
+
from typing import Literal
|
3
|
+
|
4
|
+
from hestia_earth.schema import SiteSiteType
|
5
|
+
from hestia_earth.utils.model import find_primary_product
|
6
|
+
|
7
|
+
from hestia_earth.models.log import debugMissingLookup
|
8
|
+
from hestia_earth.models.utils.constant import Units, get_atomic_conversion
|
9
|
+
from hestia_earth.models.utils.ecoClimateZone import EcoClimateZone, get_ecoClimateZone_lookup_grouped_value
|
10
|
+
from hestia_earth.models.utils.term import get_lookup_value
|
11
|
+
|
12
|
+
from . import MODEL
|
13
|
+
|
14
|
+
_PRODUCT_LOOKUP = "IPCC_2013_ORGANIC_SOIL_CULTIVATION_CATEGORY"
|
15
|
+
_DITCH_LOOKUP = "IPCC_2013_DRAINED_ORGANIC_SOILS_DITCH_FRAC_"
|
16
|
+
_FACTOR_LOOKUPS = {
|
17
|
+
"ch4ToAirOrganicSoilCultivation": "IPCC_2013_ORGANIC_SOILS_KG_CH4_HECTARE_",
|
18
|
+
"co2ToAirOrganicSoilCultivation": "IPCC_2013_ORGANIC_SOILS_TONNES_CO2-C_HECTARE_",
|
19
|
+
"n2OToAirOrganicSoilCultivationDirect": "IPCC_2013_ORGANIC_SOILS_KG_N2O-N_HECTARE_"
|
20
|
+
}
|
21
|
+
_NETHERLANDS_TERM_ID = "GADM-NLD"
|
22
|
+
|
23
|
+
_CONVERSION_FACTORS = {
|
24
|
+
"co2ToAirOrganicSoilCultivation": 1000 * get_atomic_conversion(Units.KG_CO2, Units.TO_C),
|
25
|
+
"ch4ToAirOrganicSoilCultivation": 1000,
|
26
|
+
"n2OToAirOrganicSoilCultivationDirect": 1000 * get_atomic_conversion(Units.KG_N2O, Units.TO_N)
|
27
|
+
}
|
28
|
+
_DEFAULT_FACTOR = {"value": 0}
|
29
|
+
_EXCLUDED_ECO_CLIMATE_ZONES = [EcoClimateZone.POLAR_MOIST, EcoClimateZone.POLAR_DRY]
|
30
|
+
|
31
|
+
|
32
|
+
class OrganicSoilCategory(Enum):
|
33
|
+
ANNUAL_CROPS = "Annual crops"
|
34
|
+
PERENNIAL_CROPS = "Perennial crops"
|
35
|
+
ACACIA = "Acacia"
|
36
|
+
OIL_PALM = "Oil palm"
|
37
|
+
SAGO_PALM = "Sago palm"
|
38
|
+
PADDY_RICE_CULTIVATION = "Paddy rice cultivation"
|
39
|
+
GRASSLAND = "Grassland"
|
40
|
+
DITCH = "Ditch"
|
41
|
+
OTHER = "Other"
|
42
|
+
|
43
|
+
|
44
|
+
class DitchCategory(Enum):
|
45
|
+
AGRICULTURAL_LAND = "Agricultural land"
|
46
|
+
NETHERLANDS = "Netherlands"
|
47
|
+
|
48
|
+
|
49
|
+
def assign_organic_soil_category(cycle: dict, log_id: str) -> OrganicSoilCategory:
|
50
|
+
"""
|
51
|
+
Assign an emission factor category to a cycle based on `site.siteType` and primary product.
|
52
|
+
|
53
|
+
Cropland cycles without a primary product cannot be categorised - the function will return
|
54
|
+
`OrganicSoilCategory.OTHER`.
|
55
|
+
"""
|
56
|
+
site = cycle.get("site", {})
|
57
|
+
site_type = site.get("siteType", None)
|
58
|
+
|
59
|
+
if site_type == SiteSiteType.PERMANENT_PASTURE.value:
|
60
|
+
return OrganicSoilCategory.GRASSLAND
|
61
|
+
|
62
|
+
product = find_primary_product(cycle)
|
63
|
+
|
64
|
+
if product is None:
|
65
|
+
return OrganicSoilCategory.OTHER
|
66
|
+
|
67
|
+
lookup_value = get_lookup_value(product.get("term", {}), _PRODUCT_LOOKUP, model=MODEL, term=log_id)
|
68
|
+
|
69
|
+
return (
|
70
|
+
next(
|
71
|
+
(category for category in OrganicSoilCategory if lookup_value == category.value),
|
72
|
+
OrganicSoilCategory.OTHER
|
73
|
+
)
|
74
|
+
if lookup_value else OrganicSoilCategory.OTHER
|
75
|
+
)
|
76
|
+
|
77
|
+
|
78
|
+
def assign_ditch_category(cycle: dict) -> DitchCategory:
|
79
|
+
"""
|
80
|
+
Assign a ditch category to a cycle based. Cycles that take place in Netherlands are given a special category, all
|
81
|
+
others return the default.
|
82
|
+
"""
|
83
|
+
site = cycle.get("site", {})
|
84
|
+
country_id = site.get("country", {}).get("@id")
|
85
|
+
return DitchCategory.NETHERLANDS if country_id == _NETHERLANDS_TERM_ID else DitchCategory.AGRICULTURAL_LAND
|
86
|
+
|
87
|
+
|
88
|
+
def get_emission_factor(
|
89
|
+
emission_id: Literal[
|
90
|
+
"co2ToAirOrganicSoilCultivation",
|
91
|
+
"ch4ToAirOrganicSoilCultivation",
|
92
|
+
"n2OToAirOrganicSoilCultivationDirect"
|
93
|
+
],
|
94
|
+
eco_climate_zone: EcoClimateZone,
|
95
|
+
organic_soil_category: OrganicSoilCategory
|
96
|
+
) -> dict:
|
97
|
+
"""
|
98
|
+
Retrieve emission factor data from the eco-climate zone lookup.
|
99
|
+
"""
|
100
|
+
col_name = "".join([_FACTOR_LOOKUPS[emission_id], organic_soil_category.name])
|
101
|
+
row_value = eco_climate_zone.value
|
102
|
+
|
103
|
+
data = get_ecoClimateZone_lookup_grouped_value(row_value, col_name)
|
104
|
+
debugMissingLookup("ecoClimateZone.csv", "ecoClimateZone", row_value, col_name, data, model=MODEL, term=emission_id)
|
105
|
+
|
106
|
+
return data or _DEFAULT_FACTOR
|
107
|
+
|
108
|
+
|
109
|
+
def get_ditch_frac(
|
110
|
+
eco_climate_zone: EcoClimateZone,
|
111
|
+
ditch_category: DitchCategory,
|
112
|
+
**debug_kwargs: dict
|
113
|
+
) -> dict:
|
114
|
+
"""
|
115
|
+
Retrieve ditch fraction data from the eco-climate zone lookup.
|
116
|
+
"""
|
117
|
+
col_name = "".join([_DITCH_LOOKUP, ditch_category.name])
|
118
|
+
row_value = eco_climate_zone.value
|
119
|
+
|
120
|
+
data = get_ecoClimateZone_lookup_grouped_value(row_value, col_name)
|
121
|
+
debugMissingLookup("ecoClimateZone.csv", "ecoClimateZone", row_value, col_name, data, model=MODEL, **debug_kwargs)
|
122
|
+
|
123
|
+
return data or _DEFAULT_FACTOR
|
124
|
+
|
125
|
+
|
126
|
+
def calc_emission(
|
127
|
+
emission_id: Literal[
|
128
|
+
"co2ToAirOrganicSoilCultivation",
|
129
|
+
"ch4ToAirOrganicSoilCultivation",
|
130
|
+
"n2OToAirOrganicSoilCultivationDirect"
|
131
|
+
],
|
132
|
+
emission_factor: float,
|
133
|
+
histosol: float,
|
134
|
+
land_occupation: float
|
135
|
+
):
|
136
|
+
"""
|
137
|
+
Calculate the emission and convert it to kg/ha-1.
|
138
|
+
"""
|
139
|
+
return emission_factor * land_occupation * histosol * _CONVERSION_FACTORS[emission_id] / 100
|
140
|
+
|
141
|
+
|
142
|
+
def remap_categories(
|
143
|
+
category: OrganicSoilCategory,
|
144
|
+
mapping: dict[OrganicSoilCategory, OrganicSoilCategory]
|
145
|
+
) -> OrganicSoilCategory:
|
146
|
+
"""
|
147
|
+
Remap emission factor categories for cases in which emission factors are not available for a specific category and
|
148
|
+
a more general one must be used.
|
149
|
+
"""
|
150
|
+
return mapping.get(category, category)
|
151
|
+
|
152
|
+
|
153
|
+
def valid_eco_climate_zone(
|
154
|
+
eco_climate_zone: EcoClimateZone,
|
155
|
+
):
|
156
|
+
"""
|
157
|
+
Validate that the model should run for a specific eco-climate zone.
|
158
|
+
"""
|
159
|
+
return isinstance(eco_climate_zone, EcoClimateZone) and eco_climate_zone not in _EXCLUDED_ECO_CLIMATE_ZONES
|