hestia-earth-models 0.61.6__py3-none-any.whl → 0.61.8__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 (52) hide show
  1. hestia_earth/models/cycle/completeness/electricityFuel.py +56 -0
  2. hestia_earth/models/cycle/input/hestiaAggregatedData.py +1 -1
  3. hestia_earth/models/emepEea2019/nh3ToAirInorganicFertiliser.py +44 -59
  4. hestia_earth/models/geospatialDatabase/histosol.py +4 -0
  5. hestia_earth/models/ipcc2006/co2ToAirOrganicSoilCultivation.py +4 -2
  6. hestia_earth/models/ipcc2006/n2OToAirOrganicSoilCultivationDirect.py +1 -1
  7. hestia_earth/models/ipcc2019/aboveGroundCropResidueTotal.py +1 -1
  8. hestia_earth/models/ipcc2019/belowGroundCropResidue.py +1 -1
  9. hestia_earth/models/ipcc2019/ch4ToAirExcreta.py +1 -1
  10. hestia_earth/models/ipcc2019/co2ToAirSoilOrganicCarbonStockChangeManagementChange.py +511 -458
  11. hestia_earth/models/ipcc2019/co2ToAirUreaHydrolysis.py +5 -1
  12. hestia_earth/models/ipcc2019/organicCarbonPerHa.py +117 -3881
  13. hestia_earth/models/ipcc2019/organicCarbonPerHa_tier_1_utils.py +2060 -0
  14. hestia_earth/models/ipcc2019/organicCarbonPerHa_tier_2_utils.py +1630 -0
  15. hestia_earth/models/ipcc2019/organicCarbonPerHa_utils.py +324 -0
  16. hestia_earth/models/mocking/search-results.json +360 -260
  17. hestia_earth/models/schererPfister2015/pToDrainageWaterSoilFlux.py +1 -1
  18. hestia_earth/models/schererPfister2015/pToGroundwaterSoilFlux.py +1 -1
  19. hestia_earth/models/site/organicCarbonPerHa.py +58 -44
  20. hestia_earth/models/site/soilMeasurement.py +25 -38
  21. hestia_earth/models/utils/__init__.py +28 -0
  22. hestia_earth/models/utils/aquacultureManagement.py +2 -2
  23. hestia_earth/models/utils/array_builders.py +578 -0
  24. hestia_earth/models/utils/blank_node.py +2 -3
  25. hestia_earth/models/utils/crop.py +24 -1
  26. hestia_earth/models/utils/cycle.py +0 -23
  27. hestia_earth/models/utils/descriptive_stats.py +285 -0
  28. hestia_earth/models/utils/emission.py +73 -2
  29. hestia_earth/models/utils/inorganicFertiliser.py +2 -2
  30. hestia_earth/models/utils/lookup.py +6 -3
  31. hestia_earth/models/utils/measurement.py +118 -4
  32. hestia_earth/models/utils/site.py +25 -13
  33. hestia_earth/models/version.py +1 -1
  34. {hestia_earth_models-0.61.6.dist-info → hestia_earth_models-0.61.8.dist-info}/METADATA +1 -1
  35. {hestia_earth_models-0.61.6.dist-info → hestia_earth_models-0.61.8.dist-info}/RECORD +52 -40
  36. tests/models/cycle/completeness/test_electricityFuel.py +21 -0
  37. tests/models/emepEea2019/test_nh3ToAirInorganicFertiliser.py +2 -2
  38. tests/models/ipcc2019/test_co2ToAirSoilOrganicCarbonStockChangeManagementChange.py +54 -165
  39. tests/models/ipcc2019/test_organicCarbonPerHa.py +219 -460
  40. tests/models/ipcc2019/test_organicCarbonPerHa_tier_1_utils.py +471 -0
  41. tests/models/ipcc2019/test_organicCarbonPerHa_tier_2_utils.py +208 -0
  42. tests/models/ipcc2019/test_organicCarbonPerHa_utils.py +75 -0
  43. tests/models/site/test_organicCarbonPerHa.py +3 -12
  44. tests/models/site/test_soilMeasurement.py +5 -19
  45. tests/models/utils/test_array_builders.py +253 -0
  46. tests/models/utils/{test_cycle.py → test_crop.py} +2 -2
  47. tests/models/utils/test_descriptive_stats.py +134 -0
  48. tests/models/utils/test_emission.py +51 -1
  49. tests/models/utils/test_measurement.py +54 -2
  50. {hestia_earth_models-0.61.6.dist-info → hestia_earth_models-0.61.8.dist-info}/LICENSE +0 -0
  51. {hestia_earth_models-0.61.6.dist-info → hestia_earth_models-0.61.8.dist-info}/WHEEL +0 -0
  52. {hestia_earth_models-0.61.6.dist-info → hestia_earth_models-0.61.8.dist-info}/top_level.txt +0 -0
@@ -1,38 +1,25 @@
1
- from unittest.mock import patch
2
1
  import json
3
- from numpy.testing import assert_almost_equal
2
+ from numpy.typing import NDArray
3
+ from os.path import isfile
4
4
  from pytest import mark
5
- import random
6
-
7
- from tests.utils import (
8
- fixtures_path,
9
- fake_new_measurement,
10
- )
11
- from hestia_earth.models.ipcc2019.organicCarbonPerHa import (
12
- _assign_ipcc_carbon_input_category,
13
- _assign_ipcc_land_use_category,
14
- _assign_ipcc_management_category,
15
- _assign_ipcc_soil_category,
16
- _calc_temperature_factor,
17
- _calc_tier_1_soc_stocks,
18
- _calc_water_factor,
19
- _check_cropland_low_category,
20
- _check_cropland_medium_category,
21
- _get_carbon_input_kwargs,
22
- _iterate_soc_equilibriums,
23
- _should_run,
24
- IpccCarbonInputCategory,
25
- IpccLandUseCategory,
26
- IpccManagementCategory,
27
- IpccSoilCategory,
28
- MODEL,
29
- run,
30
- TERM_ID
31
- )
5
+ from unittest.mock import MagicMock, patch
6
+
7
+ from hestia_earth.models.ipcc2019.organicCarbonPerHa import _should_run, MODEL, run, TERM_ID
8
+ from hestia_earth.models.ipcc2019.organicCarbonPerHa_utils import sample_constant
9
+
10
+ from tests.utils import fake_new_measurement, fixtures_path
32
11
 
33
12
  class_path = f"hestia_earth.models.{MODEL}.{TERM_ID}"
13
+ tier_1_path = f"hestia_earth.models.{MODEL}.{TERM_ID}_tier_1_utils"
14
+ tier_2_path = f"hestia_earth.models.{MODEL}.{TERM_ID}_tier_2_utils"
15
+ utils_path = f"hestia_earth.models.{MODEL}.{TERM_ID}_utils"
16
+ term_path = "hestia_earth.models.utils.term"
17
+ property_path = "hestia_earth.models.utils.property"
18
+
34
19
  fixtures_folder = f"{fixtures_path}/{MODEL}/{TERM_ID}"
35
20
 
21
+ ITERATIONS = 1000
22
+
36
23
  COVER_CROP_PROPERTY_TERM_IDS = [
37
24
  "catchCrop",
38
25
  "coverCrop",
@@ -73,14 +60,14 @@ RESIDUE_REMOVED_OR_BURNT_TERM_IDS = [
73
60
  "residueRemoved"
74
61
  ]
75
62
 
76
- UPLAND_RICE_LAND_COVER_TERM_IDS = [
77
- "ricePlantUpland"
78
- ]
79
-
80
63
  UPLAND_RICE_CROP_TERM_IDS = [
81
64
  "riceGrainInHuskUpland"
82
65
  ]
83
66
 
67
+ UPLAND_RICE_LAND_COVER_TERM_IDS = [
68
+ "ricePlantUpland"
69
+ ]
70
+
84
71
  DEFAULT_PROPERTIES = {
85
72
  "manureDryKgMass": {
86
73
  "carbonContent": {
@@ -96,69 +83,76 @@ DEFAULT_PROPERTIES = {
96
83
  }
97
84
 
98
85
 
99
- def find_term_property_side_effect(term: dict, property: str, *_):
86
+ def fake_calc_descriptive_stats(arr: NDArray, *_args, **_kwargs):
87
+ return {"value": [row[0] for row in arr]}
88
+
89
+
90
+ def fake_find_term_property(term: dict, property: str, *_):
100
91
  term_id = term.get('@id', None)
101
92
  return DEFAULT_PROPERTIES.get(term_id, {}).get(property, {})
102
93
 
103
94
 
104
- # --- TIER 1 & TIER 2 TESTS ---
105
-
106
-
107
- # subfolder, load_cycles, should_run
108
- SHOULD_RUN_SUBFOLDERS = [
109
- ("tier-1-and-2/cropland", True, True),
110
- ("tier-1-and-2/with-zero-carbon-input", True, True), # Closes issue 777
111
- ("tier-2/with-generalised-monthly-measurements", True, False), # Closes issue 600
112
- ("tier-2/with-incomplete-climate-data", True, False), # Closes issue 599
113
- ("tier-2/with-initial-soc", True, True),
114
- ("tier-2/with-multi-year-cycles", True, True),
115
- ("tier-2/with-multi-year-cycles-and-missing-properties", True, True), # Closes issue 734
116
- ("tier-2/without-any-measurements", True, False), # Closes issue 594
117
- ("tier-2/without-initial-soc", True, True),
118
- ("tier-2/with-irrigation", True, True),
119
- ("tier-2/with-irrigation-dates", True, True),
120
- ("tier-2/with-paddy-rice", True, False),
121
- ("tier-2/with-sand-without-date", True, True), # Closes issue 739
122
- ("tier-2/with-irrigated-upland-rice", True, False),
123
- ("tier-1/cropland-depth-as-float", False, True),
124
- ("tier-1/cropland-with-measured-soc", False, True),
125
- ("tier-1/cropland-without-measured-soc", False, True),
126
- ("tier-1/permanent-pasture", False, True),
127
- ("tier-1/should-not-run", False, False),
128
- ("tier-1/without-management-with-measured-soc", False, False),
129
- ("tier-1/land-use-change", False, True), # Closes issue 755
130
- ("tier-1/run-with-site-type", False, True), # Closes issue 755
131
- ("tier-1/cropland-polar", False, False) # Closes issue 794
95
+ def order_list(values: list[dict]) -> list[dict]:
96
+ return sorted(values, key=lambda node: (
97
+ node.get("term", {}).get('@id', ""), # sort by `term.@id`
98
+ node.get("methodClassification", ""), # then by `methodClassifaction`
99
+ node.get("dates", []) # then by `dates[0]`
100
+ ))
101
+
102
+
103
+ # subfolder, should_run
104
+ PARAMS_SHOULD_RUN = [
105
+ ("tier-1-and-2/cropland", True),
106
+ ("tier-1-and-2/with-zero-carbon-input", True), # Closes issue 777
107
+ ("tier-1/cropland-depth-as-float", True),
108
+ ("tier-1/cropland-with-measured-soc", True),
109
+ ("tier-1/cropland-without-measured-soc", True),
110
+ ("tier-1/permanent-pasture", True),
111
+ ("tier-1/should-not-run", False),
112
+ ("tier-1/without-management-with-measured-soc", False),
113
+ ("tier-1/land-use-change", True), # Closes issue 755
114
+ ("tier-1/run-with-site-type", True), # Closes issue 755
115
+ ("tier-1/cropland-polar", False), # Closes issue 794
116
+ ("tier-2/with-generalised-monthly-measurements", False), # Closes issue 600
117
+ ("tier-2/with-incomplete-climate-data", False), # Closes issue 599
118
+ ("tier-2/with-initial-soc", True),
119
+ ("tier-2/with-multi-year-cycles", True),
120
+ ("tier-2/with-multi-year-cycles-and-missing-properties", True), # Closes issue 734
121
+ ("tier-2/without-any-measurements", False), # Closes issue 594
122
+ ("tier-2/without-initial-soc", True),
123
+ ("tier-2/with-irrigation", True), # Closes issue 716
124
+ ("tier-2/with-irrigation-dates", True), # Closes issue 716
125
+ ("tier-2/with-paddy-rice", False), # Closes issue 718
126
+ ("tier-2/with-sand-without-date", True), # Closes issue 739
127
+ ("tier-2/with-irrigated-upland-rice", False) # Closes issue 718
132
128
  ]
133
-
134
-
135
- @mark.parametrize(
136
- "subfolder, load_cycles, should_run",
137
- SHOULD_RUN_SUBFOLDERS,
138
- ids=[params[0] for params in SHOULD_RUN_SUBFOLDERS]
139
- )
140
- @patch(f"{class_path}._new_measurement", side_effect=fake_new_measurement)
141
- @patch(f"{class_path}.get_cover_crop_property_terms", return_value=COVER_CROP_PROPERTY_TERM_IDS)
142
- @patch(f"{class_path}.get_crop_residue_incorporated_or_left_on_field_terms", return_value=CROP_RESIDUE_INCORP_TERM_IDS)
143
- @patch(f"{class_path}.get_irrigated_terms", return_value=IRRIGATED_TERM_IDS)
144
- @patch(f"{class_path}.get_residue_removed_or_burnt_terms", return_value=RESIDUE_REMOVED_OR_BURNT_TERM_IDS)
145
- @patch(f"{class_path}.get_upland_rice_land_cover_terms", return_value=UPLAND_RICE_LAND_COVER_TERM_IDS)
146
- @patch(f"{class_path}.get_upland_rice_crop_terms", return_value=UPLAND_RICE_CROP_TERM_IDS)
147
- @patch(f"{class_path}.related_cycles")
148
- @patch("hestia_earth.models.utils.property.find_term_property", side_effect=find_term_property_side_effect)
129
+ IDS_SHOULD_RUN = [p[0] for p in PARAMS_SHOULD_RUN]
130
+
131
+
132
+ @mark.parametrize("subfolder, should_run", PARAMS_SHOULD_RUN, ids=IDS_SHOULD_RUN)
133
+ @patch(f"{term_path}.search")
134
+ @patch(f"{property_path}.download_hestia")
135
+ @patch(f"{property_path}.find_term_property", side_effect=fake_find_term_property)
136
+ @patch(f"{utils_path}.get_upland_rice_land_cover_terms", return_value=UPLAND_RICE_LAND_COVER_TERM_IDS)
137
+ @patch(f"{utils_path}.get_upland_rice_crop_terms", return_value=UPLAND_RICE_CROP_TERM_IDS)
138
+ @patch(f"{utils_path}.get_residue_removed_or_burnt_terms", return_value=RESIDUE_REMOVED_OR_BURNT_TERM_IDS)
139
+ @patch(f"{utils_path}.get_irrigated_terms", return_value=IRRIGATED_TERM_IDS)
140
+ @patch(f"{utils_path}.get_crop_residue_incorporated_or_left_on_field_terms", return_value=CROP_RESIDUE_INCORP_TERM_IDS)
141
+ @patch(f"{utils_path}.get_cover_crop_property_terms", return_value=COVER_CROP_PROPERTY_TERM_IDS)
142
+ @patch(f"{tier_2_path}.related_cycles")
149
143
  def test_should_run(
150
- mock_find_term_property,
151
- mock_related_cycles,
152
- _mock_get_upland_rice_crop_terms,
153
- _mock_get_upland_rice_land_cover_terms,
154
- _mock_get_residue_removed_or_burnt_terms,
155
- _mock_get_irrigated_terms,
156
- _mock_get_crop_residue_incorporated_or_left_on_field_terms,
157
- _mock_get_cover_crop_property_terms,
158
- _mock_new_measurement,
159
- subfolder,
160
- load_cycles,
161
- should_run
144
+ mock_related_cycles: MagicMock,
145
+ mock_get_cover_crop_property_terms: MagicMock, # utils mocks
146
+ mock_get_crop_residue_incorporated_or_left_on_field_terms: MagicMock,
147
+ mock_get_irrigated_terms: MagicMock,
148
+ mock_get_residue_removed_or_burnt_terms: MagicMock,
149
+ mock_get_upland_rice_crop_terms: MagicMock,
150
+ mock_get_upland_rice_land_cover_terms: MagicMock,
151
+ _mock_find_term_property: MagicMock,
152
+ mock_download_hestia: MagicMock,
153
+ mock_search: MagicMock,
154
+ subfolder: str,
155
+ should_run: bool
162
156
  ):
163
157
  folder = f"{fixtures_folder}/{subfolder}"
164
158
 
@@ -166,67 +160,86 @@ def test_should_run(
166
160
  with open(f"{folder}/cycles.jsonld", encoding='utf-8') as f:
167
161
  return json.load(f)
168
162
 
169
- mock_related_cycles.return_value = load_cycles_from_file() if load_cycles else []
163
+ mock_related_cycles.return_value = (
164
+ load_cycles_from_file() if isfile(f"{folder}/cycles.jsonld") else []
165
+ )
170
166
 
171
167
  with open(f"{folder}/site.jsonld", encoding='utf-8') as f:
172
168
  site = json.load(f)
173
169
 
174
- should_run_tier_1, should_run_tier_2, *_ = _should_run(site)
175
- should_run_ = should_run_tier_1 or should_run_tier_2
170
+ result, _ = _should_run(site)
176
171
 
177
- assert should_run_ == should_run
172
+ # Ensure that API calls to retrieve term IDs are properly cached.
173
+ mock_get_cover_crop_property_terms.call_count <= 1
174
+ mock_get_crop_residue_incorporated_or_left_on_field_terms.call_count <= 1
175
+ mock_get_irrigated_terms.call_count <= 1
176
+ mock_get_residue_removed_or_burnt_terms.call_count <= 1
177
+ mock_get_upland_rice_crop_terms.call_count <= 1
178
+ mock_get_upland_rice_land_cover_terms.call_count <= 1
178
179
 
180
+ # Ensure that the property and term utils are properly mocked.
181
+ mock_download_hestia.assert_not_called()
182
+ mock_search.assert_not_called()
179
183
 
180
- @patch(f"{class_path}.get_cover_crop_property_terms", return_value=COVER_CROP_PROPERTY_TERM_IDS)
181
- @patch(f"{class_path}.get_crop_residue_incorporated_or_left_on_field_terms", return_value=CROP_RESIDUE_INCORP_TERM_IDS)
182
- @patch(f"{class_path}.get_irrigated_terms", return_value=IRRIGATED_TERM_IDS)
183
- @patch(f"{class_path}.get_residue_removed_or_burnt_terms", return_value=RESIDUE_REMOVED_OR_BURNT_TERM_IDS)
184
- @patch(f"{class_path}.get_upland_rice_land_cover_terms", return_value=UPLAND_RICE_LAND_COVER_TERM_IDS)
185
- @patch(f"{class_path}.get_upland_rice_crop_terms", return_value=UPLAND_RICE_CROP_TERM_IDS)
186
- @patch(f"{class_path}.related_cycles", return_value=[])
187
- @patch("hestia_earth.models.utils.property.find_term_property", side_effect=find_term_property_side_effect)
188
- def test_should_run_no_data(*args):
189
- SITE = {}
190
- EXPECTED = False
184
+ assert result == should_run
191
185
 
192
- should_run_tier_1, should_run_tier_2, *_ = _should_run(SITE)
193
- should_run = should_run_tier_1 or should_run_tier_2
194
186
 
195
- assert should_run == EXPECTED
187
+ @patch(f"{term_path}.search")
188
+ @patch(f"{property_path}.download_hestia")
189
+ @patch(f"{tier_2_path}.related_cycles", return_value=[])
190
+ def test_should_run_no_data(
191
+ _mock_related_cycles: MagicMock,
192
+ mock_download_hestia: MagicMock,
193
+ mock_search: MagicMock
194
+ ):
195
+ SITE = {}
196
+ EXPECTED = []
196
197
 
198
+ result = run(SITE)
197
199
 
198
- RUN_SUBFOLDERS = [
199
- (subfolder, load_cycles) for subfolder, load_cycles, should_run in SHOULD_RUN_SUBFOLDERS
200
- if should_run
201
- ]
200
+ mock_download_hestia.assert_not_called()
201
+ mock_search.assert_not_called()
202
+ assert result == EXPECTED
202
203
 
203
204
 
204
- @mark.parametrize(
205
- "subfolder, load_cycles",
206
- RUN_SUBFOLDERS,
207
- ids=[params[0] for params in RUN_SUBFOLDERS]
208
- )
209
- @patch(f"{class_path}._new_measurement", side_effect=fake_new_measurement)
210
- @patch(f"{class_path}.get_cover_crop_property_terms", return_value=COVER_CROP_PROPERTY_TERM_IDS)
211
- @patch(f"{class_path}.get_crop_residue_incorporated_or_left_on_field_terms", return_value=CROP_RESIDUE_INCORP_TERM_IDS)
212
- @patch(f"{class_path}.get_irrigated_terms", return_value=IRRIGATED_TERM_IDS)
213
- @patch(f"{class_path}.get_residue_removed_or_burnt_terms", return_value=RESIDUE_REMOVED_OR_BURNT_TERM_IDS)
214
- @patch(f"{class_path}.get_upland_rice_land_cover_terms", return_value=UPLAND_RICE_LAND_COVER_TERM_IDS)
215
- @patch(f"{class_path}.get_upland_rice_crop_terms", return_value=UPLAND_RICE_CROP_TERM_IDS)
216
- @patch(f"{class_path}.related_cycles")
217
- @patch("hestia_earth.models.utils.property.find_term_property", side_effect=find_term_property_side_effect)
205
+ PARAMS_RUN = [subfolder for subfolder, should_run in PARAMS_SHOULD_RUN if should_run]
206
+
207
+
208
+ @mark.parametrize("subfolder", PARAMS_RUN)
209
+ @patch(f"{term_path}.search")
210
+ @patch(f"{property_path}.download_hestia")
211
+ @patch(f"{property_path}.find_term_property", side_effect=fake_find_term_property)
212
+ @patch(f"{utils_path}.get_upland_rice_land_cover_terms", return_value=UPLAND_RICE_LAND_COVER_TERM_IDS)
213
+ @patch(f"{utils_path}.get_upland_rice_crop_terms", return_value=UPLAND_RICE_CROP_TERM_IDS)
214
+ @patch(f"{utils_path}.get_residue_removed_or_burnt_terms", return_value=RESIDUE_REMOVED_OR_BURNT_TERM_IDS)
215
+ @patch(f"{utils_path}.get_irrigated_terms", return_value=IRRIGATED_TERM_IDS)
216
+ @patch(f"{utils_path}.get_crop_residue_incorporated_or_left_on_field_terms", return_value=CROP_RESIDUE_INCORP_TERM_IDS)
217
+ @patch(f"{utils_path}.get_cover_crop_property_terms", return_value=COVER_CROP_PROPERTY_TERM_IDS)
218
+ @patch(f"{tier_2_path}.related_cycles")
219
+ @patch(f"{tier_2_path}.calc_descriptive_stats", side_effect=fake_calc_descriptive_stats)
220
+ @patch(f"{tier_2_path}._new_measurement", side_effect=fake_new_measurement)
221
+ @patch(f"{tier_2_path}._get_sample_func", return_value=sample_constant)
222
+ @patch(f"{tier_1_path}.calc_descriptive_stats", side_effect=fake_calc_descriptive_stats)
223
+ @patch(f"{tier_1_path}._new_measurement", side_effect=fake_new_measurement)
224
+ @patch(f"{tier_1_path}._get_sample_func", return_value=sample_constant)
218
225
  def test_run(
219
- mock_find_term_property,
220
- mock_related_cycles,
221
- _mock_get_upland_rice_crop_terms,
222
- _mock_get_upland_rice_land_cover_terms,
223
- _mock_get_residue_removed_or_burnt_terms,
224
- _mock_get_irrigated_terms,
225
- _mock_get_crop_residue_incorporated_or_left_on_field_terms,
226
- _mock_get_cover_crop_property_terms,
227
- _mock_new_measurement,
228
- subfolder,
229
- load_cycles
226
+ _mock_get_sample_func_t1: MagicMock,
227
+ _mock_new_measurement_t1: MagicMock,
228
+ _mock_calc_descriptive_stats_t1: MagicMock,
229
+ _mock_get_sample_func_t2: MagicMock,
230
+ _mock_new_measurement_t2: MagicMock,
231
+ _mock_calc_descriptive_stats_t2: MagicMock,
232
+ mock_related_cycles: MagicMock,
233
+ mock_get_cover_crop_property_terms: MagicMock, # utils mocks
234
+ mock_get_crop_residue_incorporated_or_left_on_field_terms: MagicMock,
235
+ mock_get_irrigated_terms: MagicMock,
236
+ mock_get_residue_removed_or_burnt_terms: MagicMock,
237
+ mock_get_upland_rice_crop_terms: MagicMock,
238
+ mock_get_upland_rice_land_cover_terms: MagicMock,
239
+ _mock_find_term_property: MagicMock,
240
+ mock_download_hestia: MagicMock,
241
+ mock_search: MagicMock,
242
+ subfolder: str
230
243
  ):
231
244
  folder = f"{fixtures_folder}/{subfolder}"
232
245
 
@@ -234,7 +247,9 @@ def test_run(
234
247
  with open(f"{folder}/cycles.jsonld", encoding='utf-8') as f:
235
248
  return json.load(f)
236
249
 
237
- mock_related_cycles.return_value = load_cycles_from_file() if load_cycles else []
250
+ mock_related_cycles.return_value = (
251
+ load_cycles_from_file() if isfile(f"{folder}/cycles.jsonld") else []
252
+ )
238
253
 
239
254
  with open(f"{folder}/site.jsonld", encoding='utf-8') as f:
240
255
  site = json.load(f)
@@ -242,345 +257,89 @@ def test_run(
242
257
  with open(f"{folder}/result.jsonld", encoding='utf-8') as f:
243
258
  expected = json.load(f)
244
259
 
245
- result = run(site)
246
- assert result == expected
247
-
248
-
249
- # --- TIER 2 TESTS: SUB-MODELS ---
250
-
251
-
252
- def test_calc_temperature_factor():
253
- NUM_RANDOM = 9999
254
- MIN_T, MAX_T = -60, 60
255
- MIN_FAC, MAX_FAC = 0, 1
256
-
257
- temperatures = [random.uniform(MIN_T, MAX_T) for _ in range(0, NUM_RANDOM)]
258
- results = [
259
- _calc_temperature_factor(t) for t in temperatures
260
- ]
261
-
262
- assert all(MIN_FAC <= result <= MAX_FAC for result in results)
263
-
264
-
265
- def test_calc_water_factor():
266
- NUM_RANDOM = 9999
267
- MIN, MAX = 0, 9999
268
- MIN_FAC, MAX_FAC = 0.2129, 1.5
269
- IRR_FAC = 0.775
270
-
271
- precipitations = [random.uniform(MIN, MAX) for _ in range(0, NUM_RANDOM)]
272
- pets = [random.uniform(MIN, MAX) for _ in range(0, NUM_RANDOM)]
273
-
274
- results = [
275
- _calc_water_factor(pre, pet) for pre, pet in zip(precipitations, pets)
276
- ]
277
- irr_results = [
278
- _calc_water_factor(pre, pet, is_irrigated=True) for pre, pet in zip(precipitations, pets)
279
- ]
280
-
281
- assert all(MIN_FAC <= result <= MAX_FAC for result in results)
282
- assert all(result == IRR_FAC for result in irr_results)
283
- assert _calc_water_factor(1, 1) == _calc_water_factor(1000, 1000)
284
-
285
-
286
- def test_calc_water_factor_zero():
287
- """
288
- Closes issue 771. Function should not raise 0 error and should return the maximum water factor.
289
- """
290
- assert _calc_water_factor(0, 0) == 1.49961875
291
-
292
-
293
- # --- IPCC SOIL CATEGORY TESTS ---
294
-
295
-
296
- # subfolder, expected
297
- SOIL_CATEGORY_PARAMS = [
298
- ("fractional", IpccSoilCategory.WETLAND_SOILS),
299
- ("no-measurements", IpccSoilCategory.LOW_ACTIVITY_CLAY_SOILS),
300
- ("sandy-override", IpccSoilCategory.SANDY_SOILS),
301
- ("soilType/hac", IpccSoilCategory.HIGH_ACTIVITY_CLAY_SOILS),
302
- ("soilType/lac", IpccSoilCategory.LOW_ACTIVITY_CLAY_SOILS),
303
- ("soilType/org", IpccSoilCategory.ORGANIC_SOILS),
304
- ("soilType/pod", IpccSoilCategory.SPODIC_SOILS),
305
- ("soilType/san", IpccSoilCategory.SANDY_SOILS),
306
- ("soilType/vol", IpccSoilCategory.VOLCANIC_SOILS),
307
- ("soilType/wet", IpccSoilCategory.WETLAND_SOILS),
308
- ("usdaSoilType/hac", IpccSoilCategory.HIGH_ACTIVITY_CLAY_SOILS),
309
- ("usdaSoilType/lac", IpccSoilCategory.LOW_ACTIVITY_CLAY_SOILS),
310
- ("usdaSoilType/org", IpccSoilCategory.ORGANIC_SOILS),
311
- ("usdaSoilType/pod", IpccSoilCategory.SPODIC_SOILS),
312
- ("usdaSoilType/san", IpccSoilCategory.SANDY_SOILS),
313
- ("usdaSoilType/vol", IpccSoilCategory.VOLCANIC_SOILS),
314
- ("usdaSoilType/wet", IpccSoilCategory.WETLAND_SOILS)
315
- ]
316
-
317
-
318
- @mark.parametrize(
319
- "subfolder, expected",
320
- SOIL_CATEGORY_PARAMS,
321
- ids=[params[0] for params in SOIL_CATEGORY_PARAMS]
322
- )
323
- def test_assign_ipcc_soil_category(subfolder, expected):
324
- folder = f"{fixtures_folder}/IpccSoilCategory/{subfolder}"
325
-
326
- with open(f"{folder}/site.jsonld", encoding='utf-8') as f:
327
- site = json.load(f)
260
+ with patch(f"{class_path}.ITERATIONS", ITERATIONS):
261
+ result = run(site)
328
262
 
329
- result = _assign_ipcc_soil_category(site.get("measurements", []))
330
- assert result == expected
263
+ # Ensure that API calls to retrieve term IDs are properly cached.
264
+ mock_get_cover_crop_property_terms.call_count <= 1
265
+ mock_get_crop_residue_incorporated_or_left_on_field_terms.call_count <= 1
266
+ mock_get_irrigated_terms.call_count <= 1
267
+ mock_get_residue_removed_or_burnt_terms.call_count <= 1
268
+ mock_get_upland_rice_crop_terms.call_count <= 1
269
+ mock_get_upland_rice_land_cover_terms.call_count <= 1
331
270
 
271
+ # Ensure that the property and term utils are properly mocked.
272
+ mock_download_hestia.assert_not_called()
273
+ mock_search.assert_not_called()
332
274
 
333
- # --- IPCC LAND USE CATEGORY TESTS ---
275
+ assert order_list(result) == order_list(expected)
334
276
 
335
277
 
336
- # subfolder, soil_category, expected
337
- LAND_USE_CATEGORY_PARAMS = [
338
- ("annual-crops", IpccSoilCategory.LOW_ACTIVITY_CLAY_SOILS, IpccLandUseCategory.ANNUAL_CROPS),
339
- ("annual-crops-wet", IpccSoilCategory.WETLAND_SOILS, IpccLandUseCategory.ANNUAL_CROPS_WET),
340
- ("forest", IpccSoilCategory.LOW_ACTIVITY_CLAY_SOILS, IpccLandUseCategory.FOREST),
341
- ("fractional", IpccSoilCategory.LOW_ACTIVITY_CLAY_SOILS, IpccLandUseCategory.PERENNIAL_CROPS),
342
- ("grassland", IpccSoilCategory.LOW_ACTIVITY_CLAY_SOILS, IpccLandUseCategory.GRASSLAND),
343
- ("irrigated-upland-rice", IpccSoilCategory.LOW_ACTIVITY_CLAY_SOILS, IpccLandUseCategory.PADDY_RICE_CULTIVATION),
344
- ("native", IpccSoilCategory.LOW_ACTIVITY_CLAY_SOILS, IpccLandUseCategory.NATIVE),
345
- ("other", IpccSoilCategory.LOW_ACTIVITY_CLAY_SOILS, IpccLandUseCategory.OTHER),
346
- ("paddy-rice-cultivation", IpccSoilCategory.LOW_ACTIVITY_CLAY_SOILS, IpccLandUseCategory.PADDY_RICE_CULTIVATION),
347
- ("perennial-crops", IpccSoilCategory.LOW_ACTIVITY_CLAY_SOILS, IpccLandUseCategory.PERENNIAL_CROPS),
348
- ("set-aside", IpccSoilCategory.LOW_ACTIVITY_CLAY_SOILS, IpccLandUseCategory.SET_ASIDE),
349
- ("set-aside-override", IpccSoilCategory.LOW_ACTIVITY_CLAY_SOILS, IpccLandUseCategory.SET_ASIDE),
350
- ("upland-rice", IpccSoilCategory.LOW_ACTIVITY_CLAY_SOILS, IpccLandUseCategory.ANNUAL_CROPS),
278
+ PARAMS_RUN_WITH_STATS = [
279
+ "tier-1-and-2/with-stats", # Closes issue 753
280
+ "tier-1/cropland-with-stats", # Closes issue 753
281
+ "tier-1/land-use-change-with-stats", # Closes issue 753
282
+ "tier-2/with-stats" # Closes issue 753
351
283
  ]
352
284
 
353
285
 
354
- @mark.parametrize(
355
- "subfolder, soil_category, expected",
356
- LAND_USE_CATEGORY_PARAMS,
357
- ids=[params[0] for params in LAND_USE_CATEGORY_PARAMS]
358
- )
359
- @patch(f"{class_path}.get_irrigated_terms", return_value=IRRIGATED_TERM_IDS)
360
- @patch(f"{class_path}.get_upland_rice_land_cover_terms", return_value=UPLAND_RICE_LAND_COVER_TERM_IDS)
361
- def test_assign_ipcc_land_use_category(
362
- _mock_get_upland_rice_land_cover_terms,
363
- _mock_get_irrigated_terms,
364
- subfolder,
365
- soil_category,
366
- expected
367
- ):
368
- folder = f"{fixtures_folder}/IpccLandUseCategory/{subfolder}"
369
-
370
- with open(f"{folder}/site.jsonld", encoding='utf-8') as f:
371
- site = json.load(f)
372
-
373
- result = _assign_ipcc_land_use_category(site.get("management", []), soil_category)
374
- assert result == expected
375
-
376
-
377
- # --- IPCC MANAGEMENT CATEGORY TESTS ---
378
-
379
-
380
- # subfolder, land_use_category, expected
381
- MANAGEMENT_CATEGORY_PARAMS = [
382
- ("fractional-annual-crops", IpccLandUseCategory.ANNUAL_CROPS, IpccManagementCategory.REDUCED_TILLAGE),
383
- ("fractional-annual-crops-wet", IpccLandUseCategory.ANNUAL_CROPS_WET, IpccManagementCategory.REDUCED_TILLAGE),
384
- ("fractional-grassland", IpccLandUseCategory.GRASSLAND, IpccManagementCategory.IMPROVED_GRASSLAND),
385
- ("full-tillage", IpccLandUseCategory.ANNUAL_CROPS, IpccManagementCategory.FULL_TILLAGE),
386
- ("high-intensity-grazing", IpccLandUseCategory.GRASSLAND, IpccManagementCategory.HIGH_INTENSITY_GRAZING),
387
- ("improved-grassland", IpccLandUseCategory.GRASSLAND, IpccManagementCategory.IMPROVED_GRASSLAND),
388
- ("no-management/annual-crops", IpccLandUseCategory.ANNUAL_CROPS, IpccManagementCategory.FULL_TILLAGE),
389
- ("no-management/annual-crops-wet", IpccLandUseCategory.ANNUAL_CROPS_WET, IpccManagementCategory.FULL_TILLAGE),
390
- ("no-management/grassland", IpccLandUseCategory.GRASSLAND, IpccManagementCategory.NOMINALLY_MANAGED),
391
- ("no-tillage", IpccLandUseCategory.ANNUAL_CROPS, IpccManagementCategory.NO_TILLAGE),
392
- ("nominally-managed", IpccLandUseCategory.GRASSLAND, IpccManagementCategory.NOMINALLY_MANAGED),
393
- ("other", IpccLandUseCategory.OTHER, IpccManagementCategory.OTHER),
394
- ("reduced-tillage", IpccLandUseCategory.ANNUAL_CROPS, IpccManagementCategory.REDUCED_TILLAGE),
395
- ("severely-degraded", IpccLandUseCategory.GRASSLAND, IpccManagementCategory.SEVERELY_DEGRADED),
396
- ]
397
-
398
-
399
- @mark.parametrize(
400
- "subfolder, land_use_category, expected",
401
- MANAGEMENT_CATEGORY_PARAMS,
402
- ids=[params[0] for params in MANAGEMENT_CATEGORY_PARAMS]
403
- )
404
- def test_assign_ipcc_management_category(subfolder, land_use_category, expected):
405
- folder = f"{fixtures_folder}/IpccManagementCategory/{subfolder}"
406
-
407
- with open(f"{folder}/site.jsonld", encoding='utf-8') as f:
408
- site = json.load(f)
409
-
410
- result = _assign_ipcc_management_category(site.get("management", []), land_use_category)
411
- assert result == expected
412
-
413
-
414
- # --- IPCC CARBON INPUT CATEGORY TESTS ---
415
-
416
-
417
- @mark.parametrize("key", [1, 2, 3, 4], ids=lambda key: f"scenario-{key}")
418
- @patch(f"{class_path}.get_cover_crop_property_terms", return_value=COVER_CROP_PROPERTY_TERM_IDS)
419
- @patch(f"{class_path}.get_crop_residue_incorporated_or_left_on_field_terms", return_value=CROP_RESIDUE_INCORP_TERM_IDS)
420
- @patch(f"{class_path}.get_irrigated_terms", return_value=IRRIGATED_TERM_IDS)
421
- @patch(f"{class_path}.get_residue_removed_or_burnt_terms", return_value=RESIDUE_REMOVED_OR_BURNT_TERM_IDS)
422
- def test_check_cropland_medium_category(
423
- _mock_get_residue_removed_or_burnt_terms,
424
- _mock_get_irrigated_terms,
425
- _mock_get_crop_residue_incorporated_or_left_on_field_terms,
426
- _mock_get_cover_crop_property_terms,
427
- key
286
+ @mark.parametrize("subfolder", PARAMS_RUN_WITH_STATS)
287
+ @patch(f"{term_path}.search")
288
+ @patch(f"{property_path}.download_hestia")
289
+ @patch(f"{property_path}.find_term_property", side_effect=fake_find_term_property)
290
+ @patch(f"{utils_path}.get_upland_rice_land_cover_terms", return_value=UPLAND_RICE_LAND_COVER_TERM_IDS)
291
+ @patch(f"{utils_path}.get_upland_rice_crop_terms", return_value=UPLAND_RICE_CROP_TERM_IDS)
292
+ @patch(f"{utils_path}.get_residue_removed_or_burnt_terms", return_value=RESIDUE_REMOVED_OR_BURNT_TERM_IDS)
293
+ @patch(f"{utils_path}.get_irrigated_terms", return_value=IRRIGATED_TERM_IDS)
294
+ @patch(f"{utils_path}.get_crop_residue_incorporated_or_left_on_field_terms", return_value=CROP_RESIDUE_INCORP_TERM_IDS)
295
+ @patch(f"{utils_path}.get_cover_crop_property_terms", return_value=COVER_CROP_PROPERTY_TERM_IDS)
296
+ @patch(f"{tier_2_path}.related_cycles")
297
+ @patch(f"{tier_2_path}._new_measurement", side_effect=fake_new_measurement)
298
+ @patch(f"{tier_1_path}._new_measurement", side_effect=fake_new_measurement)
299
+ def test_run_with_stats(
300
+ _mock_new_measurement_t1: MagicMock,
301
+ _mock_new_measurement_t2: MagicMock,
302
+ mock_related_cycles: MagicMock,
303
+ mock_get_cover_crop_property_terms: MagicMock, # utils mocks
304
+ mock_get_crop_residue_incorporated_or_left_on_field_terms: MagicMock,
305
+ mock_get_irrigated_terms: MagicMock,
306
+ mock_get_residue_removed_or_burnt_terms: MagicMock,
307
+ mock_get_upland_rice_crop_terms: MagicMock,
308
+ mock_get_upland_rice_land_cover_terms: MagicMock,
309
+ _mock_find_term_property: MagicMock,
310
+ mock_download_hestia: MagicMock,
311
+ mock_search: MagicMock,
312
+ subfolder: str
428
313
  ):
429
- """
430
- Tests each set of cropland medium conditions against a list of nodes that such satisfy it. The function returns the
431
- key of the matching condition set, which should match the suffix of the fixtures subfolder.
432
- """
433
- folder = f"{fixtures_folder}/IpccCarbonInputCategory/cropland-medium/scenario-{key}"
434
-
435
- with open(f"{folder}/site.jsonld", encoding='utf-8') as f:
436
- site = json.load(f)
437
-
438
- result = _check_cropland_medium_category(**_get_carbon_input_kwargs(site.get("management", [])))
439
- assert result == key
440
-
441
-
442
- @mark.parametrize("key", [1, 2, 3], ids=lambda key: f"scenario-{key}")
443
- @patch(f"{class_path}.get_cover_crop_property_terms", return_value=COVER_CROP_PROPERTY_TERM_IDS)
444
- @patch(f"{class_path}.get_crop_residue_incorporated_or_left_on_field_terms", return_value=CROP_RESIDUE_INCORP_TERM_IDS)
445
- @patch(f"{class_path}.get_irrigated_terms", return_value=IRRIGATED_TERM_IDS)
446
- @patch(f"{class_path}.get_residue_removed_or_burnt_terms", return_value=RESIDUE_REMOVED_OR_BURNT_TERM_IDS)
447
- def test_check_cropland_low_category(
448
- _mock_get_residue_removed_or_burnt_terms,
449
- _mock_get_irrigated_terms,
450
- _mock_get_crop_residue_incorporated_or_left_on_field_terms,
451
- _mock_get_cover_crop_property_terms,
452
- key
453
- ):
454
- """
455
- Tests each set of cropland low conditions against a list of nodes that such satisfy it. The function returns the
456
- key of the matching condition set, which should match the suffix of the fixtures subfolder.
457
- """
458
- folder = f"{fixtures_folder}/IpccCarbonInputCategory/cropland-low/scenario-{key}"
314
+ folder = f"{fixtures_folder}/{subfolder}"
459
315
 
460
- with open(f"{folder}/site.jsonld", encoding='utf-8') as f:
461
- site = json.load(f)
316
+ def load_cycles_from_file():
317
+ with open(f"{folder}/cycles.jsonld", encoding='utf-8') as f:
318
+ return json.load(f)
462
319
 
463
- result = _check_cropland_low_category(**_get_carbon_input_kwargs(site.get("management", [])))
464
- assert result == key
465
-
466
-
467
- # subfolder, management_category, expected
468
- CARBON_INPUT_CATEGORY_PARAMS = [
469
- (
470
- "cropland-high-with-manure",
471
- IpccManagementCategory.FULL_TILLAGE,
472
- IpccCarbonInputCategory.CROPLAND_HIGH_WITH_MANURE
473
- ),
474
- (
475
- "cropland-high-without-manure/organic-fertiliser", # Closes issue 743
476
- IpccManagementCategory.FULL_TILLAGE,
477
- IpccCarbonInputCategory.CROPLAND_HIGH_WITHOUT_MANURE
478
- ),
479
- (
480
- "cropland-high-without-manure/soil-amendment", # Closes issue 743
481
- IpccManagementCategory.FULL_TILLAGE,
482
- IpccCarbonInputCategory.CROPLAND_HIGH_WITHOUT_MANURE
483
- ),
484
- ("cropland-low/scenario-1", IpccManagementCategory.FULL_TILLAGE, IpccCarbonInputCategory.CROPLAND_LOW),
485
- ("cropland-low/scenario-2", IpccManagementCategory.FULL_TILLAGE, IpccCarbonInputCategory.CROPLAND_LOW),
486
- ("cropland-low/scenario-3", IpccManagementCategory.FULL_TILLAGE, IpccCarbonInputCategory.CROPLAND_LOW),
487
- ("cropland-medium/scenario-1", IpccManagementCategory.FULL_TILLAGE, IpccCarbonInputCategory.CROPLAND_MEDIUM),
488
- ("cropland-medium/scenario-2", IpccManagementCategory.FULL_TILLAGE, IpccCarbonInputCategory.CROPLAND_MEDIUM),
489
- ("cropland-medium/scenario-3", IpccManagementCategory.FULL_TILLAGE, IpccCarbonInputCategory.CROPLAND_MEDIUM),
490
- ("cropland-medium/scenario-4", IpccManagementCategory.FULL_TILLAGE, IpccCarbonInputCategory.CROPLAND_MEDIUM),
491
- ("grassland-high", IpccManagementCategory.IMPROVED_GRASSLAND, IpccCarbonInputCategory.GRASSLAND_HIGH),
492
- (
493
- "grassland-medium/0-improvements",
494
- IpccManagementCategory.IMPROVED_GRASSLAND,
495
- IpccCarbonInputCategory.GRASSLAND_MEDIUM
496
- ),
497
- (
498
- "grassland-medium/1-improvements",
499
- IpccManagementCategory.IMPROVED_GRASSLAND,
500
- IpccCarbonInputCategory.GRASSLAND_MEDIUM
320
+ mock_related_cycles.return_value = (
321
+ load_cycles_from_file() if isfile(f"{folder}/cycles.jsonld") else []
501
322
  )
502
- ]
503
-
504
-
505
- @mark.parametrize(
506
- "subfolder, management_category, expected",
507
- CARBON_INPUT_CATEGORY_PARAMS,
508
- ids=[params[0] for params in CARBON_INPUT_CATEGORY_PARAMS]
509
- )
510
- @patch(f"{class_path}.get_cover_crop_property_terms", return_value=COVER_CROP_PROPERTY_TERM_IDS)
511
- @patch(f"{class_path}.get_crop_residue_incorporated_or_left_on_field_terms", return_value=CROP_RESIDUE_INCORP_TERM_IDS)
512
- @patch(f"{class_path}.get_irrigated_terms", return_value=IRRIGATED_TERM_IDS)
513
- @patch(f"{class_path}.get_residue_removed_or_burnt_terms", return_value=RESIDUE_REMOVED_OR_BURNT_TERM_IDS)
514
- def test_assign_ipcc_carbon_input_category(
515
- _mock_get_residue_removed_or_burnt_terms,
516
- _mock_get_irrigated_terms,
517
- _mock_get_crop_residue_incorporated_or_left_on_field_terms,
518
- _mock_get_cover_crop_property_terms,
519
- subfolder,
520
- management_category,
521
- expected
522
- ):
523
- folder = f"{fixtures_folder}/IpccCarbonInputCategory/{subfolder}"
524
323
 
525
324
  with open(f"{folder}/site.jsonld", encoding='utf-8') as f:
526
325
  site = json.load(f)
527
326
 
528
- result = _assign_ipcc_carbon_input_category(site.get("management", []), management_category)
529
- assert result == expected
530
-
531
-
532
- # --- TIER 1 TESTS ---
533
-
534
-
535
- @mark.parametrize(
536
- "soc_equilibriums, expected",
537
- [
538
- (
539
- [77.000, 70.840, 70.840, 70.840, 70.840, 70.840, 70.840],
540
- [77.000, 75.460, 73.920, 72.380, 70.840, 70.840, 70.840]
541
- ),
542
- (
543
- [77.000, 70.840, 70.840, 70.840, 80.850, 80.850, 80.850],
544
- [77.000, 75.460, 73.920, 72.380, 74.498, 76.615, 78.733]
545
- ),
546
- (
547
- [80.850, 70.840, 70.840, 70.840, 70.840, 80.850, 80.850],
548
- [80.850, 78.348, 75.845, 73.343, 70.840, 73.343, 75.845]
549
- ),
550
- (
551
- [80.850, 80.850, 77.000, 77.000, 77.000, 77.000, 77.000],
552
- [80.850, 80.850, 79.888, 78.925, 77.963, 77.000, 77.000]
553
- ),
554
- (
555
- [70.840, 70.840, 70.840, 70.840, 80.850, 80.850, 80.850],
556
- [70.840, 70.840, 70.840, 70.840, 73.343, 75.845, 78.348]
557
- ),
558
- (
559
- [70.840, 70.840, 80.850, 80.850, 80.850, 70.840, 80.850],
560
- [70.840, 70.840, 73.343, 75.845, 78.348, 76.471, 77.565]
561
- ),
562
- ],
563
- ids=["land-unit-1", "land-unit-2", "land-unit-3", "land-unit-4", "land-unit-5", "land-unit-6"]
564
- )
565
- def test_run_tier_1_soc_stocks(soc_equilibriums, expected):
566
- """
567
- Test the interpolation between SOC equilibriums using test data provided in IPCC (2019).
568
- """
569
- TIMESTAMPS = [1990, 1995, 2000, 2005, 2010, 2015, 2020]
570
- result = _calc_tier_1_soc_stocks(
571
- TIMESTAMPS, soc_equilibriums
572
- )
573
- assert_almost_equal(result, expected, decimal=3)
327
+ with open(f"{folder}/result.jsonld", encoding='utf-8') as f:
328
+ expected = json.load(f)
574
329
 
330
+ with patch(f"{class_path}.ITERATIONS", ITERATIONS):
331
+ result = run(site)
575
332
 
576
- def test_iterate_soc_equilibriums():
577
- TIMESTAMPS = [1990, 2020]
578
- SOC_EQUILIBRIUMS = [20000, 40000]
333
+ # Ensure that API calls to retrieve term IDs are properly cached.
334
+ mock_get_cover_crop_property_terms.call_count <= 1
335
+ mock_get_crop_residue_incorporated_or_left_on_field_terms.call_count <= 1
336
+ mock_get_irrigated_terms.call_count <= 1
337
+ mock_get_residue_removed_or_burnt_terms.call_count <= 1
338
+ mock_get_upland_rice_crop_terms.call_count <= 1
339
+ mock_get_upland_rice_land_cover_terms.call_count <= 1
579
340
 
580
- EXPECTED = (
581
- [1990, 2010, 2020],
582
- [20000, 40000, 40000]
583
- )
341
+ # Ensure that the property and term utils are properly mocked.
342
+ mock_download_hestia.assert_not_called()
343
+ mock_search.assert_not_called()
584
344
 
585
- result = _iterate_soc_equilibriums(TIMESTAMPS, SOC_EQUILIBRIUMS)
586
- assert result == EXPECTED
345
+ assert order_list(result) == order_list(expected)