hestia-earth-models 0.59.1__py3-none-any.whl → 0.59.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.

Potentially problematic release.


This version of hestia-earth-models might be problematic. Click here for more details.

Files changed (40) hide show
  1. hestia_earth/models/cycle/irrigatedTypeUnspecified.py +6 -7
  2. hestia_earth/models/impact_assessment/irrigated.py +4 -1
  3. hestia_earth/models/ipcc2006/n2OToAirCropResidueDecompositionDirect.py +83 -0
  4. hestia_earth/models/ipcc2006/n2OToAirCropResidueDecompositionIndirect.py +3 -3
  5. hestia_earth/models/ipcc2006/n2OToAirExcretaIndirect.py +3 -3
  6. hestia_earth/models/ipcc2006/n2OToAirInorganicFertiliserIndirect.py +5 -4
  7. hestia_earth/models/ipcc2006/n2OToAirOrganicFertiliserIndirect.py +5 -4
  8. hestia_earth/models/ipcc2006/utils.py +12 -16
  9. hestia_earth/models/ipcc2019/n2OToAirCropResidueDecompositionDirect.py +8 -7
  10. hestia_earth/models/ipcc2019/n2OToAirCropResidueDecompositionIndirect.py +100 -0
  11. hestia_earth/models/ipcc2019/n2OToAirExcretaIndirect.py +100 -0
  12. hestia_earth/models/ipcc2019/n2OToAirInorganicFertiliserIndirect.py +54 -61
  13. hestia_earth/models/ipcc2019/n2OToAirOrganicFertiliserIndirect.py +58 -66
  14. hestia_earth/models/ipcc2019/nh3ToAirInorganicFertiliser.py +112 -0
  15. hestia_earth/models/ipcc2019/nh3ToAirOrganicFertiliser.py +107 -0
  16. hestia_earth/models/ipcc2019/noxToAirInorganicFertiliser.py +112 -0
  17. hestia_earth/models/ipcc2019/noxToAirOrganicFertiliser.py +107 -0
  18. hestia_earth/models/ipcc2019/organicCarbonPerHa.py +67 -21
  19. hestia_earth/models/ipcc2019/utils.py +28 -16
  20. hestia_earth/models/site/soilMeasurement.py +197 -0
  21. hestia_earth/models/utils/cycle.py +7 -6
  22. hestia_earth/models/utils/emission.py +15 -0
  23. hestia_earth/models/version.py +1 -1
  24. {hestia_earth_models-0.59.1.dist-info → hestia_earth_models-0.59.3.dist-info}/METADATA +1 -1
  25. {hestia_earth_models-0.59.1.dist-info → hestia_earth_models-0.59.3.dist-info}/RECORD +40 -24
  26. tests/models/ipcc2006/test_n2OToAirCropResidueDecompositionDirect.py +50 -0
  27. tests/models/ipcc2019/test_n2OToAirCropResidueDecompositionIndirect.py +71 -0
  28. tests/models/ipcc2019/test_n2OToAirExcretaIndirect.py +71 -0
  29. tests/models/ipcc2019/test_n2OToAirInorganicFertiliserIndirect.py +36 -13
  30. tests/models/ipcc2019/test_n2OToAirOrganicFertiliserIndirect.py +36 -13
  31. tests/models/ipcc2019/test_nh3ToAirInorganicFertiliser.py +47 -0
  32. tests/models/ipcc2019/test_nh3ToAirOrganicFertiliser.py +35 -0
  33. tests/models/ipcc2019/test_noxToAirInorganicFertiliser.py +47 -0
  34. tests/models/ipcc2019/test_noxToAirOrganicFertiliser.py +35 -0
  35. tests/models/ipcc2019/test_organicCarbonPerHa.py +51 -5
  36. tests/models/site/test_soilMeasurement.py +159 -0
  37. tests/models/utils/test_blank_node.py +5 -5
  38. {hestia_earth_models-0.59.1.dist-info → hestia_earth_models-0.59.3.dist-info}/LICENSE +0 -0
  39. {hestia_earth_models-0.59.1.dist-info → hestia_earth_models-0.59.3.dist-info}/WHEEL +0 -0
  40. {hestia_earth_models-0.59.1.dist-info → hestia_earth_models-0.59.3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,35 @@
1
+ from unittest.mock import patch
2
+ import json
3
+ from tests.utils import fixtures_path, fake_new_emission
4
+
5
+ from hestia_earth.models.ipcc2019.noxToAirOrganicFertiliser import MODEL, TERM_ID, run, _should_run
6
+
7
+ class_path = f"hestia_earth.models.{MODEL}.{TERM_ID}"
8
+ fixtures_folder = f"{fixtures_path}/{MODEL}/{TERM_ID}"
9
+
10
+
11
+ @patch(f"{class_path}._is_term_type_complete", return_value=False)
12
+ @patch(f"{class_path}.get_organic_fertiliser_N_total", return_value=0)
13
+ def test_should_run(mock_N_total, mock_complete, *args):
14
+ # no N => no run
15
+ assert not _should_run({})
16
+
17
+ # with N => no run
18
+ mock_N_total.return_value = 10
19
+ assert not _should_run({})
20
+
21
+ # is complete => run
22
+ mock_complete.return_value = True
23
+ assert _should_run({}) is True
24
+
25
+
26
+ @patch(f"{class_path}._new_emission", side_effect=fake_new_emission)
27
+ def test_run(*args):
28
+ with open(f"{fixtures_folder}/cycle.jsonld", encoding='utf-8') as f:
29
+ cycle = json.load(f)
30
+
31
+ with open(f"{fixtures_folder}/result.jsonld", encoding='utf-8') as f:
32
+ expected = json.load(f)
33
+
34
+ value = run(cycle)
35
+ assert value == expected
@@ -16,6 +16,7 @@ from hestia_earth.models.ipcc2019.organicCarbonPerHa import (
16
16
  _calc_temperature_factor,
17
17
  _calc_tier_1_soc_stocks,
18
18
  _calc_water_factor,
19
+ _calculated_cycles,
19
20
  _check_cropland_low_category,
20
21
  _check_cropland_medium_category,
21
22
  _get_carbon_input_kwargs,
@@ -85,6 +86,25 @@ UPLAND_RICE_CROP_TERM_IDS = [
85
86
  "riceGrainInHuskUpland"
86
87
  ]
87
88
 
89
+ DEFAULT_PROPERTIES = {
90
+ "manureDryKgMass": {
91
+ "carbonContent": {
92
+ "value": 38.4
93
+ },
94
+ "nitrogenContent": {
95
+ "value": 2.65
96
+ },
97
+ "ligninContent": {
98
+ "value": 9.67
99
+ }
100
+ }
101
+ }
102
+
103
+
104
+ def find_term_property_side_effect(term: dict, property: str, *_):
105
+ term_id = term.get('@id', None)
106
+ return DEFAULT_PROPERTIES.get(term_id, {}).get(property, {})
107
+
88
108
 
89
109
  # --- TIER 1 & TIER 2 TESTS ---
90
110
 
@@ -96,11 +116,13 @@ RUN_SUBFOLDERS = [
96
116
  ("tier-2/with-incomplete-climate-data", True), # Closes issue 599
97
117
  ("tier-2/with-initial-soc", True),
98
118
  ("tier-2/with-multi-year-cycles", True),
119
+ ("tier-2/with-multi-year-cycles-and-missing-properties", True), # Closes issue 734
99
120
  ("tier-2/without-any-measurements", True), # Closes issue 594
100
121
  ("tier-2/without-initial-soc", True),
101
122
  ("tier-2/with-irrigation", True),
102
123
  ("tier-2/with-irrigation-dates", True),
103
124
  ("tier-2/with-paddy-rice", True),
125
+ ("tier-2/with-sand-without-date", True), # Closes issue 739
104
126
  ("tier-2/with-irrigated-upland-rice", True),
105
127
  ("tier-1/cropland-depth-as-float", False),
106
128
  ("tier-1/cropland-with-measured-soc", False),
@@ -124,9 +146,11 @@ RUN_SUBFOLDERS = [
124
146
  @patch(f"{class_path}.get_residue_removed_or_burnt_terms", return_value=RESIDUE_REMOVED_OR_BURNT_TERM_IDS)
125
147
  @patch(f"{class_path}.get_upland_rice_land_cover_terms", return_value=UPLAND_RICE_LAND_COVER_TERM_IDS)
126
148
  @patch(f"{class_path}.get_upland_rice_crop_terms", return_value=UPLAND_RICE_CROP_TERM_IDS)
127
- @patch(f"{class_path}.related_cycles")
149
+ @patch(f"{class_path}._calculated_cycles")
150
+ @patch("hestia_earth.models.utils.property.find_term_property")
128
151
  def test_run(
129
- mock_related_cycles,
152
+ mock_find_term_property,
153
+ mock_calculated_cycles,
130
154
  _mock_get_upland_rice_crop_terms,
131
155
  _mock_get_upland_rice_land_cover_terms,
132
156
  _mock_get_residue_removed_or_burnt_terms,
@@ -144,7 +168,8 @@ def test_run(
144
168
  with open(f"{folder}/cycles.jsonld", encoding='utf-8') as f:
145
169
  return json.load(f)
146
170
 
147
- mock_related_cycles.return_value = load_cycles_from_file() if load_cycles else []
171
+ mock_find_term_property.side_effect = find_term_property_side_effect
172
+ mock_calculated_cycles.return_value = load_cycles_from_file() if load_cycles else []
148
173
 
149
174
  with open(f"{folder}/site.jsonld", encoding='utf-8') as f:
150
175
  site = json.load(f)
@@ -164,7 +189,7 @@ def test_run(
164
189
  @patch(f"{class_path}.get_residue_removed_or_burnt_terms", return_value=RESIDUE_REMOVED_OR_BURNT_TERM_IDS)
165
190
  @patch(f"{class_path}.get_upland_rice_land_cover_terms", return_value=UPLAND_RICE_LAND_COVER_TERM_IDS)
166
191
  @patch(f"{class_path}.get_upland_rice_crop_terms", return_value=UPLAND_RICE_CROP_TERM_IDS)
167
- @patch(f"{class_path}.related_cycles", return_value=[])
192
+ @patch(f"{class_path}._calculated_cycles", return_value=[])
168
193
  def test_run_no_data(*args):
169
194
  SITE = {}
170
195
  EXPECTED = []
@@ -209,6 +234,22 @@ def test_calc_water_factor():
209
234
  assert _calc_water_factor(1, 1) == _calc_water_factor(1000, 1000)
210
235
 
211
236
 
237
+ @patch(f"{class_path}.find_related")
238
+ @patch("hestia_earth.models.utils.download_hestia")
239
+ def test_calculated_cycles(mock_download_hestia, mock_find_related):
240
+ CYCLES = [{"@id": "cycle"}]
241
+ EXPECTED = [{"@id": "cycle", "@type": "Cycle"}]
242
+
243
+ mock_download_hestia.side_effect = lambda node_id, node_type, **_: {
244
+ "@type": node_type.value,
245
+ "@id": node_id
246
+ }
247
+ mock_find_related.return_value = CYCLES
248
+
249
+ result = _calculated_cycles({})
250
+ assert EXPECTED == result
251
+
252
+
212
253
  # --- IPCC SOIL CATEGORY TESTS ---
213
254
 
214
255
 
@@ -397,7 +438,12 @@ CARBON_INPUT_CATEGORY_PARAMS = [
397
438
  IpccCarbonInputCategory.CROPLAND_HIGH_WITH_MANURE
398
439
  ),
399
440
  (
400
- "cropland-high-without-manure",
441
+ "cropland-high-without-manure/organic-fertiliser", # Closes issue 743
442
+ IpccManagementCategory.FULL_TILLAGE,
443
+ IpccCarbonInputCategory.CROPLAND_HIGH_WITHOUT_MANURE
444
+ ),
445
+ (
446
+ "cropland-high-without-manure/soil-amendment", # Closes issue 743
401
447
  IpccManagementCategory.FULL_TILLAGE,
402
448
  IpccCarbonInputCategory.CROPLAND_HIGH_WITHOUT_MANURE
403
449
  ),
@@ -0,0 +1,159 @@
1
+ from unittest.mock import patch
2
+ import json
3
+ import pytest
4
+
5
+ from tests.utils import fixtures_path, fake_new_measurement
6
+
7
+ from hestia_earth.models.site.soilMeasurement import (
8
+ MODEL,
9
+ MODEL_KEY,
10
+ _get_overlap,
11
+ _harmonise_measurements,
12
+ _should_run, run
13
+ )
14
+
15
+ class_path = f"hestia_earth.models.{MODEL}.{MODEL_KEY}"
16
+ fixtures_folder = f"{fixtures_path}/site/soilMeasurement"
17
+
18
+
19
+ @pytest.mark.parametrize(
20
+ "data,expected",
21
+ [
22
+ ((1, 10, 2, 150), 8),
23
+ ((1, 10, 5, 15), 5),
24
+ ((1, 10, 10, 15), 0),
25
+ ((10, 10, 0, 150), 0),
26
+ ((150, 155, 1, 150), 0),
27
+ ((20, 40, 0, 30), 10),
28
+ ((0, 20, 0, 50), 20),
29
+ ((20, 60, 0, 50), 30),
30
+ ((10, 20, 40, 50), 0),
31
+ ((10, 20, 19, 50), 1),
32
+ ]
33
+ )
34
+ def test_get_overlap(data, expected):
35
+ result = _get_overlap(
36
+ data[0], data[1], data[2], data[3]
37
+ )
38
+ assert result == expected
39
+
40
+
41
+ @pytest.mark.parametrize(
42
+ "measurements_list,returns_dict,expected_value",
43
+ [
44
+ (
45
+ [
46
+ {"value": [7.5], "depthUpper": 0, "depthLower": 20},
47
+ {"value": [6], "depthUpper": 20, "depthLower": 40},
48
+ ],
49
+ {"depthUpper": 0, "depthLower": 30},
50
+ 7
51
+ ),
52
+ (
53
+ [
54
+ {"value": [7.5], "depthUpper": 0, "depthLower": 20},
55
+ {"value": [6], "depthUpper": 20, "depthLower": 40},
56
+ ],
57
+ {"depthUpper": 0, "depthLower": 50},
58
+ 6.75
59
+ ),
60
+ ]
61
+ )
62
+ def test_harmonise_measurements(measurements_list, returns_dict, expected_value):
63
+ actual_value = _harmonise_measurements(
64
+ measurements_list=measurements_list,
65
+ standard_depth_upper=returns_dict["depthUpper"],
66
+ standard_depth_lower=returns_dict["depthLower"],
67
+ )
68
+ assert actual_value == expected_value
69
+
70
+
71
+ @pytest.mark.parametrize(
72
+ "test_name,site,expected_should_run",
73
+ [
74
+ (
75
+ "no measurement => no run",
76
+ {"measurements": []},
77
+ False
78
+ ),
79
+ (
80
+ "missing dates => run",
81
+ {
82
+ "measurements":
83
+ [
84
+ {
85
+ "term": {"@id": "clayContent"},
86
+ "depthUpper": 0,
87
+ "depthLower": 20
88
+ }
89
+ ]
90
+ },
91
+ True
92
+ ),
93
+ (
94
+ "no depthUpper => no run",
95
+ {
96
+ "measurements":
97
+ [
98
+ {
99
+ "term": {"@id": "clayContent"},
100
+ "dates": ["2022-01-02"],
101
+ "depthLower": 20
102
+ }
103
+ ]
104
+ },
105
+ False
106
+ ),
107
+ (
108
+ "all fields => run",
109
+ {
110
+ "measurements":
111
+ [
112
+ {
113
+ "term": {"@id": "clayContent"},
114
+ "dates": ["2022-01-02"],
115
+ "depthUpper": 0,
116
+ "depthLower": 20
117
+ }
118
+ ]
119
+ },
120
+ True
121
+ )
122
+ ]
123
+ )
124
+ @patch(f"{class_path}.get_lookup_value")
125
+ def test_should_run(mock_get_lookup, test_name, site, expected_should_run):
126
+ mock_get_lookup.return_value = True
127
+ model_key = "clayContent"
128
+ should_run, *args = _should_run(site=site, model_key=model_key)
129
+ assert should_run == expected_should_run, test_name
130
+
131
+
132
+ def lookup_side_effect(*args, **kwargs):
133
+ _ = kwargs
134
+ if args[0]["@id"] == "soilPh" and args[1] == "depthSensitive":
135
+ return False
136
+ elif args[0]["@id"] in {"baseSaturation", "soilDepth", "rainfallHourly"}:
137
+ return False
138
+ return True
139
+
140
+
141
+ @pytest.mark.parametrize(
142
+ "test_name",
143
+ [
144
+ "missingDepth", "simpleSoilPh", "clayContent", "missingDepth", "nonUniqueMeasurements", "arrays"
145
+ ]
146
+ )
147
+ @patch(f"{class_path}._new_measurement", side_effect=fake_new_measurement)
148
+ @patch(f"{class_path}.get_lookup_value")
149
+ def test_run(mock_lookup, mock_new_measurement, test_name):
150
+ mock_lookup.side_effect = lookup_side_effect
151
+
152
+ with open(f"{fixtures_folder}/{test_name}/site.jsonld", encoding='utf-8') as f:
153
+ site = json.load(f)
154
+
155
+ with open(f"{fixtures_folder}/{test_name}/result.jsonld", encoding='utf-8') as f:
156
+ result = json.load(f)
157
+
158
+ value = run(site)
159
+ assert value == result
@@ -815,13 +815,13 @@ def test_group_nodes_by_year_scenario_d():
815
815
  "value": 100
816
816
  },
817
817
  {
818
- "term.@id": "organicFertiliserOrSoilCarbonIncreasingAmendmentUsed",
818
+ "term.@id": "organicFertiliserUsed",
819
819
  "endDate": "2000-11",
820
820
  "startDate": "2000-09",
821
821
  "value": True
822
822
  },
823
823
  {
824
- "term.@id": "organicFertiliserOrSoilCarbonIncreasingAmendmentUsed",
824
+ "term.@id": "organicFertiliserUsed",
825
825
  "endDate": "2002-06",
826
826
  "startDate": "2001-07",
827
827
  "value": True
@@ -838,7 +838,7 @@ def test_group_nodes_by_year_scenario_d():
838
838
  "fraction_of_node_duration": 0.9153005464480874
839
839
  },
840
840
  {
841
- "term.@id": "organicFertiliserOrSoilCarbonIncreasingAmendmentUsed",
841
+ "term.@id": "organicFertiliserUsed",
842
842
  "endDate": "2000-11",
843
843
  "startDate": "2000-09",
844
844
  "value": True,
@@ -872,7 +872,7 @@ def test_group_nodes_by_year_scenario_d():
872
872
  "fraction_of_node_duration": 0.8315217391304348
873
873
  },
874
874
  {
875
- "term.@id": "organicFertiliserOrSoilCarbonIncreasingAmendmentUsed",
875
+ "term.@id": "organicFertiliserUsed",
876
876
  "endDate": "2002-06",
877
877
  "startDate": "2001-07",
878
878
  "value": True,
@@ -882,7 +882,7 @@ def test_group_nodes_by_year_scenario_d():
882
882
  ],
883
883
  2002: [
884
884
  {
885
- "term.@id": "organicFertiliserOrSoilCarbonIncreasingAmendmentUsed",
885
+ "term.@id": "organicFertiliserUsed",
886
886
  "endDate": "2002-06",
887
887
  "startDate": "2001-07",
888
888
  "value": True,