From 9d4f4d003fd31989044c2c43399260e4be8ec6d4 Mon Sep 17 00:00:00 2001 From: Matthew Liu Date: Tue, 31 Mar 2026 20:23:11 +0900 Subject: [PATCH 01/27] Added files --- input/metadata/cross_validation/crop_cross_validation.json | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 input/metadata/cross_validation/crop_cross_validation.json diff --git a/input/metadata/cross_validation/crop_cross_validation.json b/input/metadata/cross_validation/crop_cross_validation.json new file mode 100644 index 0000000000..e69de29bb2 From cf0b1f68c86c6c63f6d80a8d9b346039cc83e397 Mon Sep 17 00:00:00 2001 From: Matthew Liu Date: Tue, 31 Mar 2026 22:54:11 +0900 Subject: [PATCH 02/27] Updated contents --- RUFAS/data_validator.py | 1 - .../example_alf_corn_silage_rotation.json | 2 +- input/data/tasks/example_freestall_task.json | 2 +- .../crop_cross_validation.json | 686 ++++++++++++++++++ tests/test_cross_validation_crop_paths.py | 35 + 5 files changed, 723 insertions(+), 3 deletions(-) create mode 100644 tests/test_cross_validation_crop_paths.py diff --git a/RUFAS/data_validator.py b/RUFAS/data_validator.py index b1716fb772..5a8033b4d6 100644 --- a/RUFAS/data_validator.py +++ b/RUFAS/data_validator.py @@ -1709,7 +1709,6 @@ def extract_value_by_key_list( >>> DataValidator.extract_value_by_key_list(example_data, var_path) 'straw' """ - for key in variable_path: if isinstance(data, list) and 0 <= int(key) < len(data): data = data[int(key)] diff --git a/input/data/crop/example_alf_corn_silage_rotation.json b/input/data/crop/example_alf_corn_silage_rotation.json index 6b53739c95..d1e7b88b20 100644 --- a/input/data/crop/example_alf_corn_silage_rotation.json +++ b/input/data/crop/example_alf_corn_silage_rotation.json @@ -1,6 +1,6 @@ {"crop_schedules": [ - + { "crop_species": "corn_silage", "planting_days": [ diff --git a/input/data/tasks/example_freestall_task.json b/input/data/tasks/example_freestall_task.json index e36254fe3a..009e888694 100644 --- a/input/data/tasks/example_freestall_task.json +++ b/input/data/tasks/example_freestall_task.json @@ -9,7 +9,7 @@ "random_seed": 42, "exclude_info_maps": false, "cross_validation_file_paths": [ - "input/metadata/cross_validation/example_cross_validation.json" + "input/metadata/cross_validation/crop_cross_validation.json" ] } ] diff --git a/input/metadata/cross_validation/crop_cross_validation.json b/input/metadata/cross_validation/crop_cross_validation.json index e69de29bb2..a838d5b335 100644 --- a/input/metadata/cross_validation/crop_cross_validation.json +++ b/input/metadata/cross_validation/crop_cross_validation.json @@ -0,0 +1,686 @@ +{ + "cross-validation": [ + { + "description": "alfalfa_silage temperature and harvest index bounds", + "target_and_save": { + "variables": { + "minimum_temperature": "crop_configurations.crop_configurations.0.minimum_temperature", + "optimal_temperature": "crop_configurations.crop_configurations.0.optimal_temperature", + "minimum_harvest_index": "crop_configurations.crop_configurations.0.minimum_harvest_index", + "optimal_harvest_index": "crop_configurations.crop_configurations.0.optimal_harvest_index" + } + }, + "rules": [ + { + "left_hand": { + "ordered_variables": ["optimal_temperature"] + }, + "right_hand": { + "ordered_variables": ["minimum_temperature"] + }, + "relationship": "greater_or_equal_to" + }, + { + "left_hand": { + "ordered_variables": ["optimal_harvest_index"] + }, + "right_hand": { + "ordered_variables": ["minimum_harvest_index"] + }, + "relationship": "greater_or_equal_to" + } + ] + }, + { + "description": "alfalfa_baleage temperature and harvest index bounds", + "target_and_save": { + "variables": { + "minimum_temperature": "crop_configurations.crop_configurations.1.minimum_temperature", + "optimal_temperature": "crop_configurations.crop_configurations.1.optimal_temperature", + "minimum_harvest_index": "crop_configurations.crop_configurations.1.minimum_harvest_index", + "optimal_harvest_index": "crop_configurations.crop_configurations.1.optimal_harvest_index" + } + }, + "rules": [ + { + "left_hand": { + "ordered_variables": ["optimal_temperature"] + }, + "right_hand": { + "ordered_variables": ["minimum_temperature"] + }, + "relationship": "greater_or_equal_to" + }, + { + "left_hand": { + "ordered_variables": ["optimal_harvest_index"] + }, + "right_hand": { + "ordered_variables": ["minimum_harvest_index"] + }, + "relationship": "greater_or_equal_to" + } + ] + }, + { + "description": "alfalfa_hay temperature and harvest index bounds", + "target_and_save": { + "variables": { + "minimum_temperature": "crop_configurations.crop_configurations.2.minimum_temperature", + "optimal_temperature": "crop_configurations.crop_configurations.2.optimal_temperature", + "minimum_harvest_index": "crop_configurations.crop_configurations.2.minimum_harvest_index", + "optimal_harvest_index": "crop_configurations.crop_configurations.2.optimal_harvest_index" + } + }, + "rules": [ + { + "left_hand": { + "ordered_variables": ["optimal_temperature"] + }, + "right_hand": { + "ordered_variables": ["minimum_temperature"] + }, + "relationship": "greater_or_equal_to" + }, + { + "left_hand": { + "ordered_variables": ["optimal_harvest_index"] + }, + "right_hand": { + "ordered_variables": ["minimum_harvest_index"] + }, + "relationship": "greater_or_equal_to" + } + ] + }, + { + "description": "cereal_rye_grain temperature and harvest index bounds", + "target_and_save": { + "variables": { + "minimum_temperature": "crop_configurations.crop_configurations.3.minimum_temperature", + "optimal_temperature": "crop_configurations.crop_configurations.3.optimal_temperature", + "minimum_harvest_index": "crop_configurations.crop_configurations.3.minimum_harvest_index", + "optimal_harvest_index": "crop_configurations.crop_configurations.3.optimal_harvest_index" + } + }, + "rules": [ + { + "left_hand": { + "ordered_variables": ["optimal_temperature"] + }, + "right_hand": { + "ordered_variables": ["minimum_temperature"] + }, + "relationship": "greater_or_equal_to" + }, + { + "left_hand": { + "ordered_variables": ["optimal_harvest_index"] + }, + "right_hand": { + "ordered_variables": ["minimum_harvest_index"] + }, + "relationship": "greater_or_equal_to" + } + ] + }, + { + "description": "cereal_rye_silage temperature and harvest index bounds", + "target_and_save": { + "variables": { + "minimum_temperature": "crop_configurations.crop_configurations.4.minimum_temperature", + "optimal_temperature": "crop_configurations.crop_configurations.4.optimal_temperature", + "minimum_harvest_index": "crop_configurations.crop_configurations.4.minimum_harvest_index", + "optimal_harvest_index": "crop_configurations.crop_configurations.4.optimal_harvest_index" + } + }, + "rules": [ + { + "left_hand": { + "ordered_variables": ["optimal_temperature"] + }, + "right_hand": { + "ordered_variables": ["minimum_temperature"] + }, + "relationship": "greater_or_equal_to" + }, + { + "left_hand": { + "ordered_variables": ["optimal_harvest_index"] + }, + "right_hand": { + "ordered_variables": ["minimum_harvest_index"] + }, + "relationship": "greater_or_equal_to" + } + ] + }, + { + "description": "cereal_rye_baleage temperature and harvest index bounds", + "target_and_save": { + "variables": { + "minimum_temperature": "crop_configurations.crop_configurations.5.minimum_temperature", + "optimal_temperature": "crop_configurations.crop_configurations.5.optimal_temperature", + "minimum_harvest_index": "crop_configurations.crop_configurations.5.minimum_harvest_index", + "optimal_harvest_index": "crop_configurations.crop_configurations.5.optimal_harvest_index" + } + }, + "rules": [ + { + "left_hand": { + "ordered_variables": ["optimal_temperature"] + }, + "right_hand": { + "ordered_variables": ["minimum_temperature"] + }, + "relationship": "greater_or_equal_to" + }, + { + "left_hand": { + "ordered_variables": ["optimal_harvest_index"] + }, + "right_hand": { + "ordered_variables": ["minimum_harvest_index"] + }, + "relationship": "greater_or_equal_to" + } + ] + }, + { + "description": "cereal_rye_hay temperature and harvest index bounds", + "target_and_save": { + "variables": { + "minimum_temperature": "crop_configurations.crop_configurations.6.minimum_temperature", + "optimal_temperature": "crop_configurations.crop_configurations.6.optimal_temperature", + "minimum_harvest_index": "crop_configurations.crop_configurations.6.minimum_harvest_index", + "optimal_harvest_index": "crop_configurations.crop_configurations.6.optimal_harvest_index" + } + }, + "rules": [ + { + "left_hand": { + "ordered_variables": ["optimal_temperature"] + }, + "right_hand": { + "ordered_variables": ["minimum_temperature"] + }, + "relationship": "greater_or_equal_to" + }, + { + "left_hand": { + "ordered_variables": ["optimal_harvest_index"] + }, + "right_hand": { + "ordered_variables": ["minimum_harvest_index"] + }, + "relationship": "greater_or_equal_to" + } + ] + }, + { + "description": "corn_grain temperature and harvest index bounds", + "target_and_save": { + "variables": { + "minimum_temperature": "crop_configurations.crop_configurations.7.minimum_temperature", + "optimal_temperature": "crop_configurations.crop_configurations.7.optimal_temperature", + "minimum_harvest_index": "crop_configurations.crop_configurations.7.minimum_harvest_index", + "optimal_harvest_index": "crop_configurations.crop_configurations.7.optimal_harvest_index" + } + }, + "rules": [ + { + "left_hand": { + "ordered_variables": ["optimal_temperature"] + }, + "right_hand": { + "ordered_variables": ["minimum_temperature"] + }, + "relationship": "greater_or_equal_to" + }, + { + "left_hand": { + "ordered_variables": ["optimal_harvest_index"] + }, + "right_hand": { + "ordered_variables": ["minimum_harvest_index"] + }, + "relationship": "greater_or_equal_to" + } + ] + }, + { + "description": "corn_silage temperature and harvest index bounds", + "target_and_save": { + "variables": { + "minimum_temperature": "crop_configurations.crop_configurations.8.minimum_temperature", + "optimal_temperature": "crop_configurations.crop_configurations.8.optimal_temperature", + "minimum_harvest_index": "crop_configurations.crop_configurations.8.minimum_harvest_index", + "optimal_harvest_index": "crop_configurations.crop_configurations.8.optimal_harvest_index" + } + }, + "rules": [ + { + "left_hand": { + "ordered_variables": ["optimal_temperature"] + }, + "right_hand": { + "ordered_variables": ["minimum_temperature"] + }, + "relationship": "greater_or_equal_to" + }, + { + "left_hand": { + "ordered_variables": ["optimal_harvest_index"] + }, + "right_hand": { + "ordered_variables": ["minimum_harvest_index"] + }, + "relationship": "greater_or_equal_to" + } + ] + }, + { + "description": "soybean_grain temperature and harvest index bounds", + "target_and_save": { + "variables": { + "minimum_temperature": "crop_configurations.crop_configurations.9.minimum_temperature", + "optimal_temperature": "crop_configurations.crop_configurations.9.optimal_temperature", + "minimum_harvest_index": "crop_configurations.crop_configurations.9.minimum_harvest_index", + "optimal_harvest_index": "crop_configurations.crop_configurations.9.optimal_harvest_index" + } + }, + "rules": [ + { + "left_hand": { + "ordered_variables": ["optimal_temperature"] + }, + "right_hand": { + "ordered_variables": ["minimum_temperature"] + }, + "relationship": "greater_or_equal_to" + }, + { + "left_hand": { + "ordered_variables": ["optimal_harvest_index"] + }, + "right_hand": { + "ordered_variables": ["minimum_harvest_index"] + }, + "relationship": "greater_or_equal_to" + } + ] + }, + { + "description": "soybean_hay temperature and harvest index bounds", + "target_and_save": { + "variables": { + "minimum_temperature": "crop_configurations.crop_configurations.10.minimum_temperature", + "optimal_temperature": "crop_configurations.crop_configurations.10.optimal_temperature", + "minimum_harvest_index": "crop_configurations.crop_configurations.10.minimum_harvest_index", + "optimal_harvest_index": "crop_configurations.crop_configurations.10.optimal_harvest_index" + } + }, + "rules": [ + { + "left_hand": { + "ordered_variables": ["optimal_temperature"] + }, + "right_hand": { + "ordered_variables": ["minimum_temperature"] + }, + "relationship": "greater_or_equal_to" + }, + { + "left_hand": { + "ordered_variables": ["optimal_harvest_index"] + }, + "right_hand": { + "ordered_variables": ["minimum_harvest_index"] + }, + "relationship": "greater_or_equal_to" + } + ] + }, + { + "description": "tall_fescue_silage temperature and harvest index bounds", + "target_and_save": { + "variables": { + "minimum_temperature": "crop_configurations.crop_configurations.11.minimum_temperature", + "optimal_temperature": "crop_configurations.crop_configurations.11.optimal_temperature", + "minimum_harvest_index": "crop_configurations.crop_configurations.11.minimum_harvest_index", + "optimal_harvest_index": "crop_configurations.crop_configurations.11.optimal_harvest_index" + } + }, + "rules": [ + { + "left_hand": { + "ordered_variables": ["optimal_temperature"] + }, + "right_hand": { + "ordered_variables": ["minimum_temperature"] + }, + "relationship": "greater_or_equal_to" + }, + { + "left_hand": { + "ordered_variables": ["optimal_harvest_index"] + }, + "right_hand": { + "ordered_variables": ["minimum_harvest_index"] + }, + "relationship": "greater_or_equal_to" + } + ] + }, + { + "description": "tall_fescue_baleage temperature and harvest index bounds", + "target_and_save": { + "variables": { + "minimum_temperature": "crop_configurations.crop_configurations.12.minimum_temperature", + "optimal_temperature": "crop_configurations.crop_configurations.12.optimal_temperature", + "minimum_harvest_index": "crop_configurations.crop_configurations.12.minimum_harvest_index", + "optimal_harvest_index": "crop_configurations.crop_configurations.12.optimal_harvest_index" + } + }, + "rules": [ + { + "left_hand": { + "ordered_variables": ["optimal_temperature"] + }, + "right_hand": { + "ordered_variables": ["minimum_temperature"] + }, + "relationship": "greater_or_equal_to" + }, + { + "left_hand": { + "ordered_variables": ["optimal_harvest_index"] + }, + "right_hand": { + "ordered_variables": ["minimum_harvest_index"] + }, + "relationship": "greater_or_equal_to" + } + ] + }, + { + "description": "tall_fescue_hay temperature and harvest index bounds", + "target_and_save": { + "variables": { + "minimum_temperature": "crop_configurations.crop_configurations.13.minimum_temperature", + "optimal_temperature": "crop_configurations.crop_configurations.13.optimal_temperature", + "minimum_harvest_index": "crop_configurations.crop_configurations.13.minimum_harvest_index", + "optimal_harvest_index": "crop_configurations.crop_configurations.13.optimal_harvest_index" + } + }, + "rules": [ + { + "left_hand": { + "ordered_variables": ["optimal_temperature"] + }, + "right_hand": { + "ordered_variables": ["minimum_temperature"] + }, + "relationship": "greater_or_equal_to" + }, + { + "left_hand": { + "ordered_variables": ["optimal_harvest_index"] + }, + "right_hand": { + "ordered_variables": ["minimum_harvest_index"] + }, + "relationship": "greater_or_equal_to" + } + ] + }, + { + "description": "triticale_grain temperature and harvest index bounds", + "target_and_save": { + "variables": { + "minimum_temperature": "crop_configurations.crop_configurations.14.minimum_temperature", + "optimal_temperature": "crop_configurations.crop_configurations.14.optimal_temperature", + "minimum_harvest_index": "crop_configurations.crop_configurations.14.minimum_harvest_index", + "optimal_harvest_index": "crop_configurations.crop_configurations.14.optimal_harvest_index" + } + }, + "rules": [ + { + "left_hand": { + "ordered_variables": ["optimal_temperature"] + }, + "right_hand": { + "ordered_variables": ["minimum_temperature"] + }, + "relationship": "greater_or_equal_to" + }, + { + "left_hand": { + "ordered_variables": ["optimal_harvest_index"] + }, + "right_hand": { + "ordered_variables": ["minimum_harvest_index"] + }, + "relationship": "greater_or_equal_to" + } + ] + }, + { + "description": "triticale_silage temperature and harvest index bounds", + "target_and_save": { + "variables": { + "minimum_temperature": "crop_configurations.crop_configurations.15.minimum_temperature", + "optimal_temperature": "crop_configurations.crop_configurations.15.optimal_temperature", + "minimum_harvest_index": "crop_configurations.crop_configurations.15.minimum_harvest_index", + "optimal_harvest_index": "crop_configurations.crop_configurations.15.optimal_harvest_index" + } + }, + "rules": [ + { + "left_hand": { + "ordered_variables": ["optimal_temperature"] + }, + "right_hand": { + "ordered_variables": ["minimum_temperature"] + }, + "relationship": "greater_or_equal_to" + }, + { + "left_hand": { + "ordered_variables": ["optimal_harvest_index"] + }, + "right_hand": { + "ordered_variables": ["minimum_harvest_index"] + }, + "relationship": "greater_or_equal_to" + } + ] + }, + { + "description": "triticale_baleage temperature and harvest index bounds", + "target_and_save": { + "variables": { + "minimum_temperature": "crop_configurations.crop_configurations.16.minimum_temperature", + "optimal_temperature": "crop_configurations.crop_configurations.16.optimal_temperature", + "minimum_harvest_index": "crop_configurations.crop_configurations.16.minimum_harvest_index", + "optimal_harvest_index": "crop_configurations.crop_configurations.16.optimal_harvest_index" + } + }, + "rules": [ + { + "left_hand": { + "ordered_variables": ["optimal_temperature"] + }, + "right_hand": { + "ordered_variables": ["minimum_temperature"] + }, + "relationship": "greater_or_equal_to" + }, + { + "left_hand": { + "ordered_variables": ["optimal_harvest_index"] + }, + "right_hand": { + "ordered_variables": ["minimum_harvest_index"] + }, + "relationship": "greater_or_equal_to" + } + ] + }, + { + "description": "triticale_hay temperature and harvest index bounds", + "target_and_save": { + "variables": { + "minimum_temperature": "crop_configurations.crop_configurations.17.minimum_temperature", + "optimal_temperature": "crop_configurations.crop_configurations.17.optimal_temperature", + "minimum_harvest_index": "crop_configurations.crop_configurations.17.minimum_harvest_index", + "optimal_harvest_index": "crop_configurations.crop_configurations.17.optimal_harvest_index" + } + }, + "rules": [ + { + "left_hand": { + "ordered_variables": ["optimal_temperature"] + }, + "right_hand": { + "ordered_variables": ["minimum_temperature"] + }, + "relationship": "greater_or_equal_to" + }, + { + "left_hand": { + "ordered_variables": ["optimal_harvest_index"] + }, + "right_hand": { + "ordered_variables": ["minimum_harvest_index"] + }, + "relationship": "greater_or_equal_to" + } + ] + }, + { + "description": "winter_wheat_grain temperature and harvest index bounds", + "target_and_save": { + "variables": { + "minimum_temperature": "crop_configurations.crop_configurations.18.minimum_temperature", + "optimal_temperature": "crop_configurations.crop_configurations.18.optimal_temperature", + "minimum_harvest_index": "crop_configurations.crop_configurations.18.minimum_harvest_index", + "optimal_harvest_index": "crop_configurations.crop_configurations.18.optimal_harvest_index" + } + }, + "rules": [ + { + "left_hand": { + "ordered_variables": ["optimal_temperature"] + }, + "right_hand": { + "ordered_variables": ["minimum_temperature"] + }, + "relationship": "greater_or_equal_to" + }, + { + "left_hand": { + "ordered_variables": ["optimal_harvest_index"] + }, + "right_hand": { + "ordered_variables": ["minimum_harvest_index"] + }, + "relationship": "greater_or_equal_to" + } + ] + }, + { + "description": "winter_wheat_silage temperature and harvest index bounds", + "target_and_save": { + "variables": { + "minimum_temperature": "crop_configurations.crop_configurations.19.minimum_temperature", + "optimal_temperature": "crop_configurations.crop_configurations.19.optimal_temperature", + "minimum_harvest_index": "crop_configurations.crop_configurations.19.minimum_harvest_index", + "optimal_harvest_index": "crop_configurations.crop_configurations.19.optimal_harvest_index" + } + }, + "rules": [ + { + "left_hand": { + "ordered_variables": ["optimal_temperature"] + }, + "right_hand": { + "ordered_variables": ["minimum_temperature"] + }, + "relationship": "greater_or_equal_to" + }, + { + "left_hand": { + "ordered_variables": ["optimal_harvest_index"] + }, + "right_hand": { + "ordered_variables": ["minimum_harvest_index"] + }, + "relationship": "greater_or_equal_to" + } + ] + }, + { + "description": "winter_wheat_baleage temperature and harvest index bounds", + "target_and_save": { + "variables": { + "minimum_temperature": "crop_configurations.crop_configurations.20.minimum_temperature", + "optimal_temperature": "crop_configurations.crop_configurations.20.optimal_temperature", + "minimum_harvest_index": "crop_configurations.crop_configurations.20.minimum_harvest_index", + "optimal_harvest_index": "crop_configurations.crop_configurations.20.optimal_harvest_index" + } + }, + "rules": [ + { + "left_hand": { + "ordered_variables": ["optimal_temperature"] + }, + "right_hand": { + "ordered_variables": ["minimum_temperature"] + }, + "relationship": "greater_or_equal_to" + }, + { + "left_hand": { + "ordered_variables": ["optimal_harvest_index"] + }, + "right_hand": { + "ordered_variables": ["minimum_harvest_index"] + }, + "relationship": "greater_or_equal_to" + } + ] + }, + { + "description": "winter_wheat_hay temperature and harvest index bounds", + "target_and_save": { + "variables": { + "minimum_temperature": "crop_configurations.crop_configurations.21.minimum_temperature", + "optimal_temperature": "crop_configurations.crop_configurations.21.optimal_temperature", + "minimum_harvest_index": "crop_configurations.crop_configurations.21.minimum_harvest_index", + "optimal_harvest_index": "crop_configurations.crop_configurations.21.optimal_harvest_index" + } + }, + "rules": [ + { + "left_hand": { + "ordered_variables": ["optimal_temperature"] + }, + "right_hand": { + "ordered_variables": ["minimum_temperature"] + }, + "relationship": "greater_or_equal_to" + }, + { + "left_hand": { + "ordered_variables": ["optimal_harvest_index"] + }, + "right_hand": { + "ordered_variables": ["minimum_harvest_index"] + }, + "relationship": "greater_or_equal_to" + } + ] + } + ] +} diff --git a/tests/test_cross_validation_crop_paths.py b/tests/test_cross_validation_crop_paths.py new file mode 100644 index 0000000000..976eaf4901 --- /dev/null +++ b/tests/test_cross_validation_crop_paths.py @@ -0,0 +1,35 @@ +from RUFAS.input_manager import InputManager + + +def test_extract_target_and_save_block_reads_nested_crop_configuration_paths() -> None: + """Cross-validation paths should match the nested pool shape for crop configurations.""" + im = InputManager() + setattr( + im, + "_InputManager__pool", + { + "crop_configurations": { + "crop_configurations": [ + { + "minimum_temperature": 4.0, + "optimal_temperature": 25.0, + } + ] + } + }, + ) + + result = im._extract_target_and_save_block( + { + "variables": { + "minimum_temperature": "crop_configurations.crop_configurations.0.minimum_temperature", + "optimal_temperature": "crop_configurations.crop_configurations.0.optimal_temperature", + } + }, + eager_termination=True, + ) + + assert result == { + "minimum_temperature": 4.0, + "optimal_temperature": 25.0, + } From ed41f3f23987587c38cf74bce953b620c982ecf0 Mon Sep 17 00:00:00 2001 From: Matthew Liu Date: Tue, 31 Mar 2026 22:54:47 +0900 Subject: [PATCH 03/27] Updated contents --- .../field_cross_validation.json | 140 ++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100644 input/metadata/cross_validation/field_cross_validation.json diff --git a/input/metadata/cross_validation/field_cross_validation.json b/input/metadata/cross_validation/field_cross_validation.json new file mode 100644 index 0000000000..46518a2503 --- /dev/null +++ b/input/metadata/cross_validation/field_cross_validation.json @@ -0,0 +1,140 @@ +{ + "cross-validation": [ + { + "description": "field_1 watering amount must be zero when watering interval is zero", + "target_and_save": { + "variables": { + "watering_amount_in_liters": "field_1.watering_amount_in_liters", + "watering_interval": "field_1.watering_interval" + }, + "constants": { + "zero": 0 + } + }, + "apply_when": [ + { + "left_hand": { + "ordered_variables": ["watering_interval"] + }, + "right_hand": { + "ordered_variables": ["zero"] + }, + "relationship": "equal" + } + ], + "rules": [ + { + "left_hand": { + "ordered_variables": ["watering_amount_in_liters"] + }, + "right_hand": { + "ordered_variables": ["zero"] + }, + "relationship": "equal" + } + ] + }, + { + "description": "field_1 watering interval must be greater than zero when watering amount is positive", + "target_and_save": { + "variables": { + "watering_amount_in_liters": "field_1.watering_amount_in_liters", + "watering_interval": "field_1.watering_interval" + }, + "constants": { + "zero": 0 + } + }, + "apply_when": [ + { + "left_hand": { + "ordered_variables": ["watering_amount_in_liters"] + }, + "right_hand": { + "ordered_variables": ["zero"] + }, + "relationship": "greater" + } + ], + "rules": [ + { + "left_hand": { + "ordered_variables": ["watering_interval"] + }, + "right_hand": { + "ordered_variables": ["zero"] + }, + "relationship": "greater" + } + ] + }, + { + "description": "field_2 watering amount must be zero when watering interval is zero", + "target_and_save": { + "variables": { + "watering_amount_in_liters": "field_2.watering_amount_in_liters", + "watering_interval": "field_2.watering_interval" + }, + "constants": { + "zero": 0 + } + }, + "apply_when": [ + { + "left_hand": { + "ordered_variables": ["watering_interval"] + }, + "right_hand": { + "ordered_variables": ["zero"] + }, + "relationship": "equal" + } + ], + "rules": [ + { + "left_hand": { + "ordered_variables": ["watering_amount_in_liters"] + }, + "right_hand": { + "ordered_variables": ["zero"] + }, + "relationship": "equal" + } + ] + }, + { + "description": "field_2 watering interval must be greater than zero when watering amount is positive", + "target_and_save": { + "variables": { + "watering_amount_in_liters": "field_2.watering_amount_in_liters", + "watering_interval": "field_2.watering_interval" + }, + "constants": { + "zero": 0 + } + }, + "apply_when": [ + { + "left_hand": { + "ordered_variables": ["watering_amount_in_liters"] + }, + "right_hand": { + "ordered_variables": ["zero"] + }, + "relationship": "greater" + } + ], + "rules": [ + { + "left_hand": { + "ordered_variables": ["watering_interval"] + }, + "right_hand": { + "ordered_variables": ["zero"] + }, + "relationship": "greater" + } + ] + } + ] +} From 28982d0d0f072a54694b16f611ad488f711b8a9f Mon Sep 17 00:00:00 2001 From: Matthew Liu Date: Tue, 31 Mar 2026 23:07:20 +0900 Subject: [PATCH 04/27] Updated contents in soil and field --- RUFAS/data_validator.py | 1 + input/data/tasks/example_freestall_task.json | 2 +- .../field_cross_validation.json | 210 ++++++++++++++++++ 3 files changed, 212 insertions(+), 1 deletion(-) diff --git a/RUFAS/data_validator.py b/RUFAS/data_validator.py index 5a8033b4d6..b61834568f 100644 --- a/RUFAS/data_validator.py +++ b/RUFAS/data_validator.py @@ -1834,6 +1834,7 @@ def _get_alias_value(self, alias_name: str, eager_termination: bool, relationshi } ) if eager_termination: + print(self._alias_pool) raise ValueError(f"Cross-validation error: Unknown alias name: {alias_name}") return value diff --git a/input/data/tasks/example_freestall_task.json b/input/data/tasks/example_freestall_task.json index 009e888694..3c110d77aa 100644 --- a/input/data/tasks/example_freestall_task.json +++ b/input/data/tasks/example_freestall_task.json @@ -9,7 +9,7 @@ "random_seed": 42, "exclude_info_maps": false, "cross_validation_file_paths": [ - "input/metadata/cross_validation/crop_cross_validation.json" + "input/metadata/cross_validation/field_cross_validation.json" ] } ] diff --git a/input/metadata/cross_validation/field_cross_validation.json b/input/metadata/cross_validation/field_cross_validation.json index 46518a2503..67025d625d 100644 --- a/input/metadata/cross_validation/field_cross_validation.json +++ b/input/metadata/cross_validation/field_cross_validation.json @@ -135,6 +135,216 @@ "relationship": "greater" } ] + }, + { + "description": "soil_1 layer 0 water concentrations are ordered", + "target_and_save": { + "variables": { + "wilting_point_water_concentration": "soil_1.soil_layers.0.wilting_point_water_concentration", + "field_capacity_water_concentration": "soil_1.soil_layers.0.field_capacity_water_concentration", + "saturation_point_water_concentration": "soil_1.soil_layers.0.saturation_point_water_concentration" + } + }, + "rules": [ + { + "left_hand": { + "ordered_variables": ["field_capacity_water_concentration"] + }, + "right_hand": { + "ordered_variables": ["wilting_point_water_concentration"] + }, + "relationship": "greater_or_equal_to" + }, + { + "left_hand": { + "ordered_variables": ["saturation_point_water_concentration"] + }, + "right_hand": { + "ordered_variables": ["field_capacity_water_concentration"] + }, + "relationship": "greater_or_equal_to" + } + ] + }, + { + "description": "soil_1 layer 1 water concentrations are ordered", + "target_and_save": { + "variables": { + "wilting_point_water_concentration": "soil_1.soil_layers.1.wilting_point_water_concentration", + "field_capacity_water_concentration": "soil_1.soil_layers.1.field_capacity_water_concentration", + "saturation_point_water_concentration": "soil_1.soil_layers.1.saturation_point_water_concentration" + } + }, + "rules": [ + { + "left_hand": { + "ordered_variables": ["field_capacity_water_concentration"] + }, + "right_hand": { + "ordered_variables": ["wilting_point_water_concentration"] + }, + "relationship": "greater_or_equal_to" + }, + { + "left_hand": { + "ordered_variables": ["saturation_point_water_concentration"] + }, + "right_hand": { + "ordered_variables": ["field_capacity_water_concentration"] + }, + "relationship": "greater_or_equal_to" + } + ] + }, + { + "description": "soil_1 layer 2 water concentrations are ordered", + "target_and_save": { + "variables": { + "wilting_point_water_concentration": "soil_1.soil_layers.2.wilting_point_water_concentration", + "field_capacity_water_concentration": "soil_1.soil_layers.2.field_capacity_water_concentration", + "saturation_point_water_concentration": "soil_1.soil_layers.2.saturation_point_water_concentration" + } + }, + "rules": [ + { + "left_hand": { + "ordered_variables": ["field_capacity_water_concentration"] + }, + "right_hand": { + "ordered_variables": ["wilting_point_water_concentration"] + }, + "relationship": "greater_or_equal_to" + }, + { + "left_hand": { + "ordered_variables": ["saturation_point_water_concentration"] + }, + "right_hand": { + "ordered_variables": ["field_capacity_water_concentration"] + }, + "relationship": "greater_or_equal_to" + } + ] + }, + { + "description": "soil_2 layer 0 water concentrations are ordered", + "target_and_save": { + "variables": { + "wilting_point_water_concentration": "soil_2.soil_layers.0.wilting_point_water_concentration", + "field_capacity_water_concentration": "soil_2.soil_layers.0.field_capacity_water_concentration", + "saturation_point_water_concentration": "soil_2.soil_layers.0.saturation_point_water_concentration" + } + }, + "rules": [ + { + "left_hand": { + "ordered_variables": ["field_capacity_water_concentration"] + }, + "right_hand": { + "ordered_variables": ["wilting_point_water_concentration"] + }, + "relationship": "greater_or_equal_to" + }, + { + "left_hand": { + "ordered_variables": ["saturation_point_water_concentration"] + }, + "right_hand": { + "ordered_variables": ["field_capacity_water_concentration"] + }, + "relationship": "greater_or_equal_to" + } + ] + }, + { + "description": "soil_2 layer 1 water concentrations are ordered", + "target_and_save": { + "variables": { + "wilting_point_water_concentration": "soil_2.soil_layers.1.wilting_point_water_concentration", + "field_capacity_water_concentration": "soil_2.soil_layers.1.field_capacity_water_concentration", + "saturation_point_water_concentration": "soil_2.soil_layers.1.saturation_point_water_concentration" + } + }, + "rules": [ + { + "left_hand": { + "ordered_variables": ["field_capacity_water_concentration"] + }, + "right_hand": { + "ordered_variables": ["wilting_point_water_concentration"] + }, + "relationship": "greater_or_equal_to" + }, + { + "left_hand": { + "ordered_variables": ["saturation_point_water_concentration"] + }, + "right_hand": { + "ordered_variables": ["field_capacity_water_concentration"] + }, + "relationship": "greater_or_equal_to" + } + ] + }, + { + "description": "soil_2 layer 2 water concentrations are ordered", + "target_and_save": { + "variables": { + "wilting_point_water_concentration": "soil_2.soil_layers.2.wilting_point_water_concentration", + "field_capacity_water_concentration": "soil_2.soil_layers.2.field_capacity_water_concentration", + "saturation_point_water_concentration": "soil_2.soil_layers.2.saturation_point_water_concentration" + } + }, + "rules": [ + { + "left_hand": { + "ordered_variables": ["field_capacity_water_concentration"] + }, + "right_hand": { + "ordered_variables": ["wilting_point_water_concentration"] + }, + "relationship": "greater_or_equal_to" + }, + { + "left_hand": { + "ordered_variables": ["saturation_point_water_concentration"] + }, + "right_hand": { + "ordered_variables": ["field_capacity_water_concentration"] + }, + "relationship": "greater_or_equal_to" + } + ] + }, + { + "description": "soil_2 layer 3 water concentrations are ordered", + "target_and_save": { + "variables": { + "wilting_point_water_concentration": "soil_2.soil_layers.3.wilting_point_water_concentration", + "field_capacity_water_concentration": "soil_2.soil_layers.3.field_capacity_water_concentration", + "saturation_point_water_concentration": "soil_2.soil_layers.3.saturation_point_water_concentration" + } + }, + "rules": [ + { + "left_hand": { + "ordered_variables": ["field_capacity_water_concentration"] + }, + "right_hand": { + "ordered_variables": ["wilting_point_water_concentration"] + }, + "relationship": "greater_or_equal_to" + }, + { + "left_hand": { + "ordered_variables": ["saturation_point_water_concentration"] + }, + "right_hand": { + "ordered_variables": ["field_capacity_water_concentration"] + }, + "relationship": "greater_or_equal_to" + } + ] } ] } From 7965cbc5d8c19e2717d46cf411125b87e6f087d9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 31 Mar 2026 14:10:58 +0000 Subject: [PATCH 05/27] Apply Black Formatting From d70ad035c90333a0fea83408b6bd942062327dba Mon Sep 17 00:00:00 2001 From: Matthew Liu Date: Mon, 6 Apr 2026 15:40:22 +0900 Subject: [PATCH 06/27] Added length comparison and tests --- RUFAS/data_validator.py | 23 +- input/data/tasks/example_freestall_task.json | 2 +- ...on => crop_and_soil_cross_validation.json} | 280 ++++++++++++++++++ tests/test_data_validator.py | 96 +++++- 4 files changed, 397 insertions(+), 4 deletions(-) rename input/metadata/cross_validation/{crop_cross_validation.json => crop_and_soil_cross_validation.json} (72%) diff --git a/RUFAS/data_validator.py b/RUFAS/data_validator.py index b61834568f..1575de76ad 100644 --- a/RUFAS/data_validator.py +++ b/RUFAS/data_validator.py @@ -1747,6 +1747,8 @@ def __init__(self) -> None: "is_of_type": lambda left, right, eager_termination: self._evaluate_is_type(left, right, eager_termination), "is_null": lambda left, _right, _eager_termination: self._evaluate_is_null(left), "regex": lambda left, right, _eager_termination: self._evaluate_regex(left, right), + "is_equal_length": lambda left, right, _eager_termination: self._evaluate_equal_data_length( + left, right, _eager_termination), } def cross_validate_data( @@ -1834,7 +1836,6 @@ def _get_alias_value(self, alias_name: str, eager_termination: bool, relationshi } ) if eager_termination: - print(self._alias_pool) raise ValueError(f"Cross-validation error: Unknown alias name: {alias_name}") return value @@ -2170,6 +2171,25 @@ def _validate_relationship(self, relationship: Any, eager_termination: bool) -> else: return True + def _evaluate_equal_data_length(self, left_hand_value: Any, right_hand_value: Any, eager_termination: bool) -> bool: + """Evaluates if data lengths matches.""" + if not (isinstance(left_hand_value, list) and isinstance(right_hand_value, list)): + self._event_logs.append( + { + "error": "Invalid data length validation", + "message": f"Both data have to be list type to validate their length.", + "info_map": { + "class": self.__class__.__name__, + "function": self._evaluate_equal_data_length.__name__, + }, + } + ) + if eager_termination: + raise ValueError("Cross-validation error: Invalid type comparison.") + return False + else: + return len(left_hand_value) == len(right_hand_value) + def _evaluate_equal_condition(self, left_hand_value: Any, right_hand_value: Any) -> bool: """Evaluates equal condition.""" return bool(left_hand_value == right_hand_value) @@ -2184,7 +2204,6 @@ def _evaluate_is_null(self, left_hand_value: Any) -> bool: def _evaluate_is_type(self, left_hand_value: Any, data_type: Any, eager_termination: bool) -> bool: """Evaluates the if_type condition""" - # TODO: Remove these type checks when cross validation inputs' validation is implemented - issue #2615 if not isinstance(data_type[0], str): self._event_logs.append( { diff --git a/input/data/tasks/example_freestall_task.json b/input/data/tasks/example_freestall_task.json index 3c110d77aa..601e062c6a 100644 --- a/input/data/tasks/example_freestall_task.json +++ b/input/data/tasks/example_freestall_task.json @@ -9,7 +9,7 @@ "random_seed": 42, "exclude_info_maps": false, "cross_validation_file_paths": [ - "input/metadata/cross_validation/field_cross_validation.json" + "input/metadata/cross_validation/crop_and_soil_cross_validation.json" ] } ] diff --git a/input/metadata/cross_validation/crop_cross_validation.json b/input/metadata/cross_validation/crop_and_soil_cross_validation.json similarity index 72% rename from input/metadata/cross_validation/crop_cross_validation.json rename to input/metadata/cross_validation/crop_and_soil_cross_validation.json index a838d5b335..a1d50ea6a2 100644 --- a/input/metadata/cross_validation/crop_cross_validation.json +++ b/input/metadata/cross_validation/crop_and_soil_cross_validation.json @@ -681,6 +681,286 @@ "relationship": "greater_or_equal_to" } ] + }, + { + "description": "Corn-Alf-Silage crop schedules have equal planting and harvesting year/day lengths", + "target_and_save": { + "variables": { + "planting_years": "Corn-Alf-Silage.crop_schedules.0.planting_years", + "planting_days": "Corn-Alf-Silage.crop_schedules.0.planting_days", + "harvest_years": "Corn-Alf-Silage.crop_schedules.0.harvest_years", + "harvest_days": "Corn-Alf-Silage.crop_schedules.0.harvest_days" + } + }, + "rules": [ + { + "left_hand": { + "ordered_variables": ["planting_years"], + "apply_to": "individual" + }, + "right_hand": { + "ordered_variables": ["planting_days"], + "apply_to": "individual" + }, + "relationship": "is_equal_length" + }, + { + "left_hand": { + "ordered_variables": ["harvest_years"], + "apply_to": "individual" + }, + "right_hand": { + "ordered_variables": ["harvest_days"], + "apply_to": "individual" + }, + "relationship": "is_equal_length" + } + ] + }, + { + "description": "Corn-Alf-Silage crop schedules have equal planting and harvesting year/day lengths", + "target_and_save": { + "variables": { + "planting_years": "Corn-Alf-Silage.crop_schedules.1.planting_years", + "planting_days": "Corn-Alf-Silage.crop_schedules.1.planting_days", + "harvest_years": "Corn-Alf-Silage.crop_schedules.1.harvest_years", + "harvest_days": "Corn-Alf-Silage.crop_schedules.1.harvest_days" + } + }, + "rules": [ + { + "left_hand": { + "ordered_variables": ["planting_years"], + "apply_to": "individual" + }, + "right_hand": { + "ordered_variables": ["planting_days"], + "apply_to": "individual" + }, + "relationship": "is_equal_length" + }, + { + "left_hand": { + "ordered_variables": ["harvest_years"], + "apply_to": "individual" + }, + "right_hand": { + "ordered_variables": ["harvest_days"], + "apply_to": "individual" + }, + "relationship": "is_equal_length" + } + ] + }, + { + "description": "Corn-Alf-Silage crop schedules have equal planting and harvesting year/day lengths", + "target_and_save": { + "variables": { + "planting_years": "Corn-Alf-Silage.crop_schedules.2.planting_years", + "planting_days": "Corn-Alf-Silage.crop_schedules.2.planting_days", + "harvest_years": "Corn-Alf-Silage.crop_schedules.2.harvest_years", + "harvest_days": "Corn-Alf-Silage.crop_schedules.2.harvest_days" + } + }, + "rules": [ + { + "left_hand": { + "ordered_variables": ["planting_years"], + "apply_to": "individual" + }, + "right_hand": { + "ordered_variables": ["planting_days"], + "apply_to": "individual" + }, + "relationship": "is_equal_length" + }, + { + "left_hand": { + "ordered_variables": ["harvest_years"], + "apply_to": "individual" + }, + "right_hand": { + "ordered_variables": ["harvest_days"], + "apply_to": "individual" + }, + "relationship": "is_equal_length" + } + ] + }, + { + "description": "Corn-Alf-Silage crop schedules have equal planting and harvesting year/day lengths", + "target_and_save": { + "variables": { + "planting_years": "Corn-Alf-Silage.crop_schedules.3.planting_years", + "planting_days": "Corn-Alf-Silage.crop_schedules.3.planting_days", + "harvest_years": "Corn-Alf-Silage.crop_schedules.3.harvest_years", + "harvest_days": "Corn-Alf-Silage.crop_schedules.3.harvest_days" + } + }, + "rules": [ + { + "left_hand": { + "ordered_variables": ["planting_years"], + "apply_to": "individual" + }, + "right_hand": { + "ordered_variables": ["planting_days"], + "apply_to": "individual" + }, + "relationship": "is_equal_length" + }, + { + "left_hand": { + "ordered_variables": ["harvest_years"], + "apply_to": "individual" + }, + "right_hand": { + "ordered_variables": ["harvest_days"], + "apply_to": "individual" + }, + "relationship": "is_equal_length" + } + ] + }, + { + "description": "CornGrain-AlfHay crop schedules have equal planting and harvesting year/day lengths", + "target_and_save": { + "variables": { + "planting_years": "CornGrain-AlfHay.crop_schedules.0.planting_years", + "planting_days": "CornGrain-AlfHay.crop_schedules.0.planting_days", + "harvest_years": "CornGrain-AlfHay.crop_schedules.0.harvest_years", + "harvest_days": "CornGrain-AlfHay.crop_schedules.0.harvest_days" + } + }, + "rules": [ + { + "left_hand": { + "ordered_variables": ["planting_years"], + "apply_to": "individual" + }, + "right_hand": { + "ordered_variables": ["planting_days"], + "apply_to": "individual" + }, + "relationship": "is_equal_length" + }, + { + "left_hand": { + "ordered_variables": ["harvest_years"], + "apply_to": "individual" + }, + "right_hand": { + "ordered_variables": ["harvest_days"], + "apply_to": "individual" + }, + "relationship": "is_equal_length" + } + ] + }, + { + "description": "CornGrain-AlfHay crop schedules have equal planting and harvesting year/day lengths", + "target_and_save": { + "variables": { + "planting_years": "CornGrain-AlfHay.crop_schedules.1.planting_years", + "planting_days": "CornGrain-AlfHay.crop_schedules.1.planting_days", + "harvest_years": "CornGrain-AlfHay.crop_schedules.1.harvest_years", + "harvest_days": "CornGrain-AlfHay.crop_schedules.1.harvest_days" + } + }, + "rules": [ + { + "left_hand": { + "ordered_variables": ["planting_years"], + "apply_to": "individual" + }, + "right_hand": { + "ordered_variables": ["planting_days"], + "apply_to": "individual" + }, + "relationship": "is_equal_length" + }, + { + "left_hand": { + "ordered_variables": ["harvest_years"], + "apply_to": "individual" + }, + "right_hand": { + "ordered_variables": ["harvest_days"], + "apply_to": "individual" + }, + "relationship": "is_equal_length" + } + ] + }, + { + "description": "CornGrain-AlfHay crop schedules have equal planting and harvesting year/day lengths", + "target_and_save": { + "variables": { + "planting_years": "CornGrain-AlfHay.crop_schedules.2.planting_years", + "planting_days": "CornGrain-AlfHay.crop_schedules.2.planting_days", + "harvest_years": "CornGrain-AlfHay.crop_schedules.2.harvest_years", + "harvest_days": "CornGrain-AlfHay.crop_schedules.2.harvest_days" + } + }, + "rules": [ + { + "left_hand": { + "ordered_variables": ["planting_years"], + "apply_to": "individual" + }, + "right_hand": { + "ordered_variables": ["planting_days"], + "apply_to": "individual" + }, + "relationship": "is_equal_length" + }, + { + "left_hand": { + "ordered_variables": ["harvest_years"], + "apply_to": "individual" + }, + "right_hand": { + "ordered_variables": ["harvest_days"], + "apply_to": "individual" + }, + "relationship": "is_equal_length" + } + ] + }, + { + "description": "CornGrain-AlfHay crop schedules have equal planting and harvesting year/day lengths", + "target_and_save": { + "variables": { + "planting_years": "CornGrain-AlfHay.crop_schedules.3.planting_years", + "planting_days": "CornGrain-AlfHay.crop_schedules.3.planting_days", + "harvest_years": "CornGrain-AlfHay.crop_schedules.3.harvest_years", + "harvest_days": "CornGrain-AlfHay.crop_schedules.3.harvest_days" + } + }, + "rules": [ + { + "left_hand": { + "ordered_variables": ["planting_years"], + "apply_to": "individual" + }, + "right_hand": { + "ordered_variables": ["planting_days"], + "apply_to": "individual" + }, + "relationship": "is_equal_length" + }, + { + "left_hand": { + "ordered_variables": ["harvest_years"], + "apply_to": "individual" + }, + "right_hand": { + "ordered_variables": ["harvest_days"], + "apply_to": "individual" + }, + "relationship": "is_equal_length" + } + ] } ] } diff --git a/tests/test_data_validator.py b/tests/test_data_validator.py index 8b8b7a2ebc..e57bf40cd0 100644 --- a/tests/test_data_validator.py +++ b/tests/test_data_validator.py @@ -1,3 +1,4 @@ +import json from pathlib import Path from typing import Dict, Any, List, Union, Optional, Type @@ -2951,7 +2952,7 @@ def test_evaluate_expression_apply_to_group( @pytest.mark.parametrize( "relationship", - ["equal", "greater", "greater_or_equal_to", "not_equal", "is_of_type", "regex"], + ["equal", "greater", "greater_or_equal_to", "not_equal", "is_of_type", "regex", "is_equal_length"], ) @pytest.mark.parametrize("eager_termination", [True, False]) def test_validate_relationship_valid_values(relationship: str, eager_termination: bool) -> None: @@ -3123,6 +3124,63 @@ def test_evaluate_regex_fullmatch(text: str, pattern: str, expected: bool) -> No assert cv._evaluate_regex(text, pattern) is expected +@pytest.mark.parametrize( + "left,right,expected", + [ + ([1], [2], True), + ([1, 2], [3], False), + ([], [], True), + ], +) +@pytest.mark.parametrize("eager_termination", [True, False]) +def test_evaluate_equal_data_length_with_lists( + left: list[Any], right: list[Any], expected: bool, eager_termination: bool +) -> None: + """List lengths are compared directly with no event log on valid inputs.""" + cv = CrossValidator() + + assert cv._evaluate_equal_data_length(left, right, eager_termination) is expected + assert cv._event_logs == [] + + +@pytest.mark.parametrize( + "left,right", + [ + ("abc", [1, 2, 3]), + ([1, 2], {"a": 1}), + (1, 2), + ], +) +@pytest.mark.parametrize("eager_termination", [True, False]) +def test_evaluate_equal_data_length_invalid_types(left: Any, right: Any, eager_termination: bool) -> None: + """Non-list inputs should log and optionally raise.""" + cv = CrossValidator() + + if eager_termination: + with pytest.raises(ValueError, match=r"Cross-validation error: Invalid type comparison\."): + cv._evaluate_equal_data_length(left, right, eager_termination=True) + assert len(cv._event_logs) == 1 + else: + assert cv._evaluate_equal_data_length(left, right, eager_termination=False) is False + assert len(cv._event_logs) == 1 + + +@pytest.mark.parametrize("eager_termination", [True, False]) +def test_evaluate_condition_equal_length_branch(mocker: MockerFixture, eager_termination: bool) -> None: + """'is_equal_length' should dispatch to _evaluate_equal_data_length with eager flag preserved.""" + cv = CrossValidator() + mocker.patch.object(cv, "_validate_condition_clause", return_value=True) + mocker.patch.object(cv, "_evaluate_expression", side_effect=[([1, 2], True), ([3, 4], True)]) + mock_equal_length = mocker.patch.object(cv, "_evaluate_equal_data_length", return_value=True) + + result = cv._evaluate_condition( + {"relationship": "is_equal_length", "left_hand": {}, "right_hand": {}}, eager_termination + ) + + assert result is True + mock_equal_length.assert_called_once_with([1, 2], [3, 4], eager_termination) + + @pytest.mark.parametrize("eager_termination", [True, False]) def test_evaluate_condition_short_circuits_when_validation_fails( mocker: MockerFixture, eager_termination: bool @@ -3349,3 +3407,39 @@ def test_log_missing_condition_clause_field_only() -> None: e = v._event_logs[0] assert e["error"] == "Missing required condition clause field" assert e["message"] == "Missing the left hand field in condition clause." + + +def test_crop_and_soil_cross_validation_includes_crop_schedule_length_rules() -> None: + """The combined cross-validation file should enforce planting/harvest day-year length equality.""" + cv_path = Path("input/metadata/cross_validation/crop_and_soil_cross_validation.json") + + with cv_path.open() as file: + data = json.load(file) + + blocks = data["cross-validation"] + schedule_blocks = [block for block in blocks if "crop schedules have equal planting" in block["description"]] + + assert len(schedule_blocks) == 8 + + expected_paths = { + "Corn-Alf-Silage.crop_schedules.0", + "Corn-Alf-Silage.crop_schedules.1", + "Corn-Alf-Silage.crop_schedules.2", + "Corn-Alf-Silage.crop_schedules.3", + "CornGrain-AlfHay.crop_schedules.0", + "CornGrain-AlfHay.crop_schedules.1", + "CornGrain-AlfHay.crop_schedules.2", + "CornGrain-AlfHay.crop_schedules.3", + } + + actual_paths = { + block["target_and_save"]["variables"]["planting_years"].rsplit(".planting_years", 1)[0] for block in schedule_blocks + } + assert actual_paths == expected_paths + + for block in schedule_blocks: + assert [rule["relationship"] for rule in block["rules"]] == ["is_equal_length", "is_equal_length"] + assert block["rules"][0]["left_hand"]["ordered_variables"] == ["planting_years"] + assert block["rules"][0]["right_hand"]["ordered_variables"] == ["planting_days"] + assert block["rules"][1]["left_hand"]["ordered_variables"] == ["harvest_years"] + assert block["rules"][1]["right_hand"]["ordered_variables"] == ["harvest_days"] From c8ecd973dfd2f6696669f58cbc3a9eb09a816196 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 6 Apr 2026 06:42:51 +0000 Subject: [PATCH 07/27] Apply Black Formatting --- RUFAS/data_validator.py | 3 ++- tests/test_data_validator.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/RUFAS/data_validator.py b/RUFAS/data_validator.py index 1575de76ad..a93613ef4e 100644 --- a/RUFAS/data_validator.py +++ b/RUFAS/data_validator.py @@ -1748,7 +1748,8 @@ def __init__(self) -> None: "is_null": lambda left, _right, _eager_termination: self._evaluate_is_null(left), "regex": lambda left, right, _eager_termination: self._evaluate_regex(left, right), "is_equal_length": lambda left, right, _eager_termination: self._evaluate_equal_data_length( - left, right, _eager_termination), + left, right, _eager_termination + ), } def cross_validate_data( diff --git a/tests/test_data_validator.py b/tests/test_data_validator.py index e57bf40cd0..d9f5060605 100644 --- a/tests/test_data_validator.py +++ b/tests/test_data_validator.py @@ -3433,7 +3433,8 @@ def test_crop_and_soil_cross_validation_includes_crop_schedule_length_rules() -> } actual_paths = { - block["target_and_save"]["variables"]["planting_years"].rsplit(".planting_years", 1)[0] for block in schedule_blocks + block["target_and_save"]["variables"]["planting_years"].rsplit(".planting_years", 1)[0] + for block in schedule_blocks } assert actual_paths == expected_paths From 7477e2eca4f0233c52832fac165537e62942e052 Mon Sep 17 00:00:00 2001 From: Matthew Liu Date: Mon, 6 Apr 2026 19:46:58 +0900 Subject: [PATCH 08/27] Deleted extra tests --- tests/test_cross_validation_crop_paths.py | 35 ----------------------- 1 file changed, 35 deletions(-) delete mode 100644 tests/test_cross_validation_crop_paths.py diff --git a/tests/test_cross_validation_crop_paths.py b/tests/test_cross_validation_crop_paths.py deleted file mode 100644 index 976eaf4901..0000000000 --- a/tests/test_cross_validation_crop_paths.py +++ /dev/null @@ -1,35 +0,0 @@ -from RUFAS.input_manager import InputManager - - -def test_extract_target_and_save_block_reads_nested_crop_configuration_paths() -> None: - """Cross-validation paths should match the nested pool shape for crop configurations.""" - im = InputManager() - setattr( - im, - "_InputManager__pool", - { - "crop_configurations": { - "crop_configurations": [ - { - "minimum_temperature": 4.0, - "optimal_temperature": 25.0, - } - ] - } - }, - ) - - result = im._extract_target_and_save_block( - { - "variables": { - "minimum_temperature": "crop_configurations.crop_configurations.0.minimum_temperature", - "optimal_temperature": "crop_configurations.crop_configurations.0.optimal_temperature", - } - }, - eager_termination=True, - ) - - assert result == { - "minimum_temperature": 4.0, - "optimal_temperature": 25.0, - } From 359318a8782a8d94efd4cc2e1382adc5d19272f5 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 6 Apr 2026 10:50:42 +0000 Subject: [PATCH 09/27] Apply Black Formatting From 3a194a05312d2fcb95ecaefc334bce6616893dfc Mon Sep 17 00:00:00 2001 From: Matthew Liu Date: Mon, 6 Apr 2026 19:53:34 +0900 Subject: [PATCH 10/27] Fixed failing tests --- .../digestive_system/manure_excretion_calculator.py | 8 ++++---- .../test_manure_excretion_calculator.py | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/RUFAS/biophysical/animal/digestive_system/manure_excretion_calculator.py b/RUFAS/biophysical/animal/digestive_system/manure_excretion_calculator.py index caae6ff92d..e5a956cc32 100644 --- a/RUFAS/biophysical/animal/digestive_system/manure_excretion_calculator.py +++ b/RUFAS/biophysical/animal/digestive_system/manure_excretion_calculator.py @@ -39,10 +39,10 @@ def _track_and_warn_dmi_threshold( dmi_effective : float Effective DMI after applying the minimum bound (kg/day). """ - stats = ManureExcretionCalculator._dmi_below_min_stats[kind] + stats = ManureExcretionCalculator._dmi_below_min_stats[dmi_kind] stats["n_total"] += 1 - if kind == "lact": + if dmi_kind == "lact": floor = AnimalModuleConstants.MINIMUM_DMI_LACT_FOR_MANURE_VS else: floor = AnimalModuleConstants.MINIMUM_DMI_DRY_FOR_MANURE_VS @@ -455,7 +455,7 @@ def _calculate_lactating_cow_manure( dry_matter_intake = dmi_predicted dry_matter_intake = max(dry_matter_intake, AnimalModuleConstants.MINIMUM_DMI_LACT) ManureExcretionCalculator._track_and_warn_dmi_threshold( - kind="lact", + dmi_kind="lact", dmi_effective=dry_matter_intake, ) ash_diet_content = nutrient_amounts.ash_supply @@ -624,7 +624,7 @@ def _calculate_dry_cow_manure( dry_matter_intake = dmi_predicted dry_matter_intake = max(dry_matter_intake, AnimalModuleConstants.MINIMUM_DMI_DRY) ManureExcretionCalculator._track_and_warn_dmi_threshold( - kind="dry", + dmi_kind="dry", dmi_effective=dry_matter_intake, ) crude_protein_concentration = nutrient_amounts.crude_protein_percentage diff --git a/tests/test_biophysical/test_animal/test_digestive_system/test_manure_excretion_calculator.py b/tests/test_biophysical/test_animal/test_digestive_system/test_manure_excretion_calculator.py index 68a0958d68..c7c271126e 100644 --- a/tests/test_biophysical/test_animal/test_digestive_system/test_manure_excretion_calculator.py +++ b/tests/test_biophysical/test_animal/test_digestive_system/test_manure_excretion_calculator.py @@ -324,15 +324,15 @@ def test_track_and_warn_dmi_threshold_tracks_below_min_counts(mocker: MockerFixt mocker.patch.object(AnimalModuleConstants, "MINIMUM_DMI_DRY_FOR_MANURE_VS", 6.5) ManureExcretionCalculator._track_and_warn_dmi_threshold( - kind="lact", + dmi_kind="lact", dmi_effective=6.0, ) ManureExcretionCalculator._track_and_warn_dmi_threshold( - kind="lact", + dmi_kind="lact", dmi_effective=8.0, ) ManureExcretionCalculator._track_and_warn_dmi_threshold( - kind="dry", + dmi_kind="dry", dmi_effective=6.0, ) From 9542c96d4b0233eba53890a6ab408784d087bb40 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 6 Apr 2026 10:55:23 +0000 Subject: [PATCH 11/27] Apply Black Formatting From 567a665fbac8189b3370273d5b1cf238ed4e7746 Mon Sep 17 00:00:00 2001 From: matthew7838 Date: Mon, 6 Apr 2026 10:59:49 +0000 Subject: [PATCH 12/27] Update badges on README --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a477ec13bb..2370fb4686 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ [![Flake8](https://img.shields.io/badge/Flake8-failed-red)](https://github.com/RuminantFarmSystems/MASM/actions/workflows/combined_format_lint_test_mypy.yml) -[![Pytest](https://img.shields.io/badge/Pytest-failed-red)](https://github.com/RuminantFarmSystems/MASM/actions/workflows/combined_format_lint_test_mypy.yml) -[![Coverage](https://img.shields.io/badge/Coverage-%25-red)](https://github.com/RuminantFarmSystems/MASM/actions/workflows/combined_format_lint_test_mypy.yml) -[![Mypy](https://img.shields.io/badge/Mypy-1205%20errors-red)](https://github.com/RuminantFarmSystems/MASM/actions/workflows/combined_format_lint_test_mypy.yml) +[![Pytest](https://img.shields.io/badge/Pytest-passed-brightgreen)](https://github.com/RuminantFarmSystems/MASM/actions/workflows/combined_format_lint_test_mypy.yml) +[![Coverage](https://img.shields.io/badge/Coverage-99%25-brightgreen)](https://github.com/RuminantFarmSystems/MASM/actions/workflows/combined_format_lint_test_mypy.yml) +[![Mypy](https://img.shields.io/badge/Mypy-1198%20errors-red)](https://github.com/RuminantFarmSystems/MASM/actions/workflows/combined_format_lint_test_mypy.yml) # RuFaS: Ruminant Farm Systems From b43737b6b44645e1b72c50c09c9e2f5080ad62e0 Mon Sep 17 00:00:00 2001 From: Matthew Liu Date: Mon, 6 Apr 2026 19:59:53 +0900 Subject: [PATCH 13/27] Fixed flake8 --- RUFAS/data_validator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RUFAS/data_validator.py b/RUFAS/data_validator.py index a93613ef4e..17e466986e 100644 --- a/RUFAS/data_validator.py +++ b/RUFAS/data_validator.py @@ -2178,7 +2178,7 @@ def _evaluate_equal_data_length(self, left_hand_value: Any, right_hand_value: An self._event_logs.append( { "error": "Invalid data length validation", - "message": f"Both data have to be list type to validate their length.", + "message": "Both data have to be list type to validate their length.", "info_map": { "class": self.__class__.__name__, "function": self._evaluate_equal_data_length.__name__, From e43e768dd5c462503406fd82c0cfd33a8f6e12f6 Mon Sep 17 00:00:00 2001 From: Matthew Liu Date: Mon, 6 Apr 2026 20:01:46 +0900 Subject: [PATCH 14/27] Updated changelog.md --- changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog.md b/changelog.md index 49ec24dc6b..adaec94f12 100644 --- a/changelog.md +++ b/changelog.md @@ -58,6 +58,7 @@ v1.0.0 - [2921](https://github.com/RuminantFarmSystems/RuFaS/pull/2921) - [minor change] [OutputManager][NoInputChange] [NoOutputChange] Override incorrect fill type for complex data structure data padding. - [2924](https://github.com/RuminantFarmSystems/RuFaS/pull/2924) - [minor change] [NoInputChange] [NoOutputChange] Updated advance purchase allowance to prevent excessive warnings for example run. - [2929](https://github.com/RuminantFarmSystems/RuFaS/pull/2929) - [minor change] [GraphGenerator] [NoInputChange] [NoOutputChange] Sanitizes non-numerical data sent to graph generator to allow graphing to occur despite. +- [2916](https://github.com/RuminantFarmSystems/RuFaS/pull/2916) - [minor change] [Corss Validation] [NoInputChange] [NoOutputChange] Added cross validation rules for the Crop and Soil module. ### v1.0.0 From 5e895277e0d5897dd53f75883f9f951c617dda87 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 6 Apr 2026 11:03:30 +0000 Subject: [PATCH 15/27] Apply Black Formatting From c9deb91e1355431e956fe761708d75c9a6f598fd Mon Sep 17 00:00:00 2001 From: matthew7838 Date: Mon, 6 Apr 2026 11:07:55 +0000 Subject: [PATCH 16/27] Update badges on README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2370fb4686..32553e061d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![Flake8](https://img.shields.io/badge/Flake8-failed-red)](https://github.com/RuminantFarmSystems/MASM/actions/workflows/combined_format_lint_test_mypy.yml) +[![Flake8](https://img.shields.io/badge/Flake8-passed-brightgreen)](https://github.com/RuminantFarmSystems/MASM/actions/workflows/combined_format_lint_test_mypy.yml) [![Pytest](https://img.shields.io/badge/Pytest-passed-brightgreen)](https://github.com/RuminantFarmSystems/MASM/actions/workflows/combined_format_lint_test_mypy.yml) [![Coverage](https://img.shields.io/badge/Coverage-99%25-brightgreen)](https://github.com/RuminantFarmSystems/MASM/actions/workflows/combined_format_lint_test_mypy.yml) [![Mypy](https://img.shields.io/badge/Mypy-1198%20errors-red)](https://github.com/RuminantFarmSystems/MASM/actions/workflows/combined_format_lint_test_mypy.yml) From 1fbe6132a6d20426eabb7a71342e3329d43a36d2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 7 Apr 2026 08:59:23 +0000 Subject: [PATCH 17/27] Apply Black Formatting From 575a16e66f5450d7a4c08a12e0bb9127c6e8af2c Mon Sep 17 00:00:00 2001 From: matthew7838 Date: Tue, 7 Apr 2026 09:03:49 +0000 Subject: [PATCH 18/27] Update badges on README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 32553e061d..3e1485b21f 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ [![Flake8](https://img.shields.io/badge/Flake8-passed-brightgreen)](https://github.com/RuminantFarmSystems/MASM/actions/workflows/combined_format_lint_test_mypy.yml) [![Pytest](https://img.shields.io/badge/Pytest-passed-brightgreen)](https://github.com/RuminantFarmSystems/MASM/actions/workflows/combined_format_lint_test_mypy.yml) [![Coverage](https://img.shields.io/badge/Coverage-99%25-brightgreen)](https://github.com/RuminantFarmSystems/MASM/actions/workflows/combined_format_lint_test_mypy.yml) -[![Mypy](https://img.shields.io/badge/Mypy-1198%20errors-red)](https://github.com/RuminantFarmSystems/MASM/actions/workflows/combined_format_lint_test_mypy.yml) +[![Mypy](https://img.shields.io/badge/Mypy-1191%20errors-red)](https://github.com/RuminantFarmSystems/MASM/actions/workflows/combined_format_lint_test_mypy.yml) # RuFaS: Ruminant Farm Systems From f3cd2271117c8d3f806f1c8046c564c128341f10 Mon Sep 17 00:00:00 2001 From: Matthew Liu Date: Tue, 7 Apr 2026 18:59:56 +0900 Subject: [PATCH 19/27] Addressed PR comments --- input/data/tasks/example_freestall_task.json | 2 +- .../crop_and_soil_cross_validation.json | 1048 +++++++++++++---- .../field_cross_validation.json | 350 ------ tests/test_data_validator.py | 5 +- 4 files changed, 812 insertions(+), 593 deletions(-) delete mode 100644 input/metadata/cross_validation/field_cross_validation.json diff --git a/input/data/tasks/example_freestall_task.json b/input/data/tasks/example_freestall_task.json index 601e062c6a..e36254fe3a 100644 --- a/input/data/tasks/example_freestall_task.json +++ b/input/data/tasks/example_freestall_task.json @@ -9,7 +9,7 @@ "random_seed": 42, "exclude_info_maps": false, "cross_validation_file_paths": [ - "input/metadata/cross_validation/crop_and_soil_cross_validation.json" + "input/metadata/cross_validation/example_cross_validation.json" ] } ] diff --git a/input/metadata/cross_validation/crop_and_soil_cross_validation.json b/input/metadata/cross_validation/crop_and_soil_cross_validation.json index a1d50ea6a2..f9d61dea89 100644 --- a/input/metadata/cross_validation/crop_and_soil_cross_validation.json +++ b/input/metadata/cross_validation/crop_and_soil_cross_validation.json @@ -4,28 +4,36 @@ "description": "alfalfa_silage temperature and harvest index bounds", "target_and_save": { "variables": { - "minimum_temperature": "crop_configurations.crop_configurations.0.minimum_temperature", - "optimal_temperature": "crop_configurations.crop_configurations.0.optimal_temperature", - "minimum_harvest_index": "crop_configurations.crop_configurations.0.minimum_harvest_index", - "optimal_harvest_index": "crop_configurations.crop_configurations.0.optimal_harvest_index" + "alfalfa_silage_minimum_temperature": "crop_configurations.crop_configurations.0.minimum_temperature", + "alfalfa_silage_optimal_temperature": "crop_configurations.crop_configurations.0.optimal_temperature", + "alfalfa_silage_minimum_harvest_index": "crop_configurations.crop_configurations.0.minimum_harvest_index", + "alfalfa_silage_optimal_harvest_index": "crop_configurations.crop_configurations.0.optimal_harvest_index" } }, "rules": [ { "left_hand": { - "ordered_variables": ["optimal_temperature"] + "ordered_variables": [ + "alfalfa_silage_optimal_temperature" + ] }, "right_hand": { - "ordered_variables": ["minimum_temperature"] + "ordered_variables": [ + "alfalfa_silage_minimum_temperature" + ] }, "relationship": "greater_or_equal_to" }, { "left_hand": { - "ordered_variables": ["optimal_harvest_index"] + "ordered_variables": [ + "alfalfa_silage_optimal_harvest_index" + ] }, "right_hand": { - "ordered_variables": ["minimum_harvest_index"] + "ordered_variables": [ + "alfalfa_silage_minimum_harvest_index" + ] }, "relationship": "greater_or_equal_to" } @@ -35,28 +43,36 @@ "description": "alfalfa_baleage temperature and harvest index bounds", "target_and_save": { "variables": { - "minimum_temperature": "crop_configurations.crop_configurations.1.minimum_temperature", - "optimal_temperature": "crop_configurations.crop_configurations.1.optimal_temperature", - "minimum_harvest_index": "crop_configurations.crop_configurations.1.minimum_harvest_index", - "optimal_harvest_index": "crop_configurations.crop_configurations.1.optimal_harvest_index" + "alfalfa_baleage_minimum_temperature": "crop_configurations.crop_configurations.1.minimum_temperature", + "alfalfa_baleage_optimal_temperature": "crop_configurations.crop_configurations.1.optimal_temperature", + "alfalfa_baleage_minimum_harvest_index": "crop_configurations.crop_configurations.1.minimum_harvest_index", + "alfalfa_baleage_optimal_harvest_index": "crop_configurations.crop_configurations.1.optimal_harvest_index" } }, "rules": [ { "left_hand": { - "ordered_variables": ["optimal_temperature"] + "ordered_variables": [ + "alfalfa_baleage_optimal_temperature" + ] }, "right_hand": { - "ordered_variables": ["minimum_temperature"] + "ordered_variables": [ + "alfalfa_baleage_minimum_temperature" + ] }, "relationship": "greater_or_equal_to" }, { "left_hand": { - "ordered_variables": ["optimal_harvest_index"] + "ordered_variables": [ + "alfalfa_baleage_optimal_harvest_index" + ] }, "right_hand": { - "ordered_variables": ["minimum_harvest_index"] + "ordered_variables": [ + "alfalfa_baleage_minimum_harvest_index" + ] }, "relationship": "greater_or_equal_to" } @@ -66,28 +82,36 @@ "description": "alfalfa_hay temperature and harvest index bounds", "target_and_save": { "variables": { - "minimum_temperature": "crop_configurations.crop_configurations.2.minimum_temperature", - "optimal_temperature": "crop_configurations.crop_configurations.2.optimal_temperature", - "minimum_harvest_index": "crop_configurations.crop_configurations.2.minimum_harvest_index", - "optimal_harvest_index": "crop_configurations.crop_configurations.2.optimal_harvest_index" + "alfalfa_hay_minimum_temperature": "crop_configurations.crop_configurations.2.minimum_temperature", + "alfalfa_hay_optimal_temperature": "crop_configurations.crop_configurations.2.optimal_temperature", + "alfalfa_hay_minimum_harvest_index": "crop_configurations.crop_configurations.2.minimum_harvest_index", + "alfalfa_hay_optimal_harvest_index": "crop_configurations.crop_configurations.2.optimal_harvest_index" } }, "rules": [ { "left_hand": { - "ordered_variables": ["optimal_temperature"] + "ordered_variables": [ + "alfalfa_hay_optimal_temperature" + ] }, "right_hand": { - "ordered_variables": ["minimum_temperature"] + "ordered_variables": [ + "alfalfa_hay_minimum_temperature" + ] }, "relationship": "greater_or_equal_to" }, { "left_hand": { - "ordered_variables": ["optimal_harvest_index"] + "ordered_variables": [ + "alfalfa_hay_optimal_harvest_index" + ] }, "right_hand": { - "ordered_variables": ["minimum_harvest_index"] + "ordered_variables": [ + "alfalfa_hay_minimum_harvest_index" + ] }, "relationship": "greater_or_equal_to" } @@ -97,28 +121,36 @@ "description": "cereal_rye_grain temperature and harvest index bounds", "target_and_save": { "variables": { - "minimum_temperature": "crop_configurations.crop_configurations.3.minimum_temperature", - "optimal_temperature": "crop_configurations.crop_configurations.3.optimal_temperature", - "minimum_harvest_index": "crop_configurations.crop_configurations.3.minimum_harvest_index", - "optimal_harvest_index": "crop_configurations.crop_configurations.3.optimal_harvest_index" + "cereal_rye_grain_minimum_temperature": "crop_configurations.crop_configurations.3.minimum_temperature", + "cereal_rye_grain_optimal_temperature": "crop_configurations.crop_configurations.3.optimal_temperature", + "cereal_rye_grain_minimum_harvest_index": "crop_configurations.crop_configurations.3.minimum_harvest_index", + "cereal_rye_grain_optimal_harvest_index": "crop_configurations.crop_configurations.3.optimal_harvest_index" } }, "rules": [ { "left_hand": { - "ordered_variables": ["optimal_temperature"] + "ordered_variables": [ + "cereal_rye_grain_optimal_temperature" + ] }, "right_hand": { - "ordered_variables": ["minimum_temperature"] + "ordered_variables": [ + "cereal_rye_grain_minimum_temperature" + ] }, "relationship": "greater_or_equal_to" }, { "left_hand": { - "ordered_variables": ["optimal_harvest_index"] + "ordered_variables": [ + "cereal_rye_grain_optimal_harvest_index" + ] }, "right_hand": { - "ordered_variables": ["minimum_harvest_index"] + "ordered_variables": [ + "cereal_rye_grain_minimum_harvest_index" + ] }, "relationship": "greater_or_equal_to" } @@ -128,28 +160,36 @@ "description": "cereal_rye_silage temperature and harvest index bounds", "target_and_save": { "variables": { - "minimum_temperature": "crop_configurations.crop_configurations.4.minimum_temperature", - "optimal_temperature": "crop_configurations.crop_configurations.4.optimal_temperature", - "minimum_harvest_index": "crop_configurations.crop_configurations.4.minimum_harvest_index", - "optimal_harvest_index": "crop_configurations.crop_configurations.4.optimal_harvest_index" + "cereal_rye_silage_minimum_temperature": "crop_configurations.crop_configurations.4.minimum_temperature", + "cereal_rye_silage_optimal_temperature": "crop_configurations.crop_configurations.4.optimal_temperature", + "cereal_rye_silage_minimum_harvest_index": "crop_configurations.crop_configurations.4.minimum_harvest_index", + "cereal_rye_silage_optimal_harvest_index": "crop_configurations.crop_configurations.4.optimal_harvest_index" } }, "rules": [ { "left_hand": { - "ordered_variables": ["optimal_temperature"] + "ordered_variables": [ + "cereal_rye_silage_optimal_temperature" + ] }, "right_hand": { - "ordered_variables": ["minimum_temperature"] + "ordered_variables": [ + "cereal_rye_silage_minimum_temperature" + ] }, "relationship": "greater_or_equal_to" }, { "left_hand": { - "ordered_variables": ["optimal_harvest_index"] + "ordered_variables": [ + "cereal_rye_silage_optimal_harvest_index" + ] }, "right_hand": { - "ordered_variables": ["minimum_harvest_index"] + "ordered_variables": [ + "cereal_rye_silage_minimum_harvest_index" + ] }, "relationship": "greater_or_equal_to" } @@ -159,28 +199,36 @@ "description": "cereal_rye_baleage temperature and harvest index bounds", "target_and_save": { "variables": { - "minimum_temperature": "crop_configurations.crop_configurations.5.minimum_temperature", - "optimal_temperature": "crop_configurations.crop_configurations.5.optimal_temperature", - "minimum_harvest_index": "crop_configurations.crop_configurations.5.minimum_harvest_index", - "optimal_harvest_index": "crop_configurations.crop_configurations.5.optimal_harvest_index" + "cereal_rye_baleage_minimum_temperature": "crop_configurations.crop_configurations.5.minimum_temperature", + "cereal_rye_baleage_optimal_temperature": "crop_configurations.crop_configurations.5.optimal_temperature", + "cereal_rye_baleage_minimum_harvest_index": "crop_configurations.crop_configurations.5.minimum_harvest_index", + "cereal_rye_baleage_optimal_harvest_index": "crop_configurations.crop_configurations.5.optimal_harvest_index" } }, "rules": [ { "left_hand": { - "ordered_variables": ["optimal_temperature"] + "ordered_variables": [ + "cereal_rye_baleage_optimal_temperature" + ] }, "right_hand": { - "ordered_variables": ["minimum_temperature"] + "ordered_variables": [ + "cereal_rye_baleage_minimum_temperature" + ] }, "relationship": "greater_or_equal_to" }, { "left_hand": { - "ordered_variables": ["optimal_harvest_index"] + "ordered_variables": [ + "cereal_rye_baleage_optimal_harvest_index" + ] }, "right_hand": { - "ordered_variables": ["minimum_harvest_index"] + "ordered_variables": [ + "cereal_rye_baleage_minimum_harvest_index" + ] }, "relationship": "greater_or_equal_to" } @@ -190,28 +238,36 @@ "description": "cereal_rye_hay temperature and harvest index bounds", "target_and_save": { "variables": { - "minimum_temperature": "crop_configurations.crop_configurations.6.minimum_temperature", - "optimal_temperature": "crop_configurations.crop_configurations.6.optimal_temperature", - "minimum_harvest_index": "crop_configurations.crop_configurations.6.minimum_harvest_index", - "optimal_harvest_index": "crop_configurations.crop_configurations.6.optimal_harvest_index" + "cereal_rye_hay_minimum_temperature": "crop_configurations.crop_configurations.6.minimum_temperature", + "cereal_rye_hay_optimal_temperature": "crop_configurations.crop_configurations.6.optimal_temperature", + "cereal_rye_hay_minimum_harvest_index": "crop_configurations.crop_configurations.6.minimum_harvest_index", + "cereal_rye_hay_optimal_harvest_index": "crop_configurations.crop_configurations.6.optimal_harvest_index" } }, "rules": [ { "left_hand": { - "ordered_variables": ["optimal_temperature"] + "ordered_variables": [ + "cereal_rye_hay_optimal_temperature" + ] }, "right_hand": { - "ordered_variables": ["minimum_temperature"] + "ordered_variables": [ + "cereal_rye_hay_minimum_temperature" + ] }, "relationship": "greater_or_equal_to" }, { "left_hand": { - "ordered_variables": ["optimal_harvest_index"] + "ordered_variables": [ + "cereal_rye_hay_optimal_harvest_index" + ] }, "right_hand": { - "ordered_variables": ["minimum_harvest_index"] + "ordered_variables": [ + "cereal_rye_hay_minimum_harvest_index" + ] }, "relationship": "greater_or_equal_to" } @@ -221,28 +277,36 @@ "description": "corn_grain temperature and harvest index bounds", "target_and_save": { "variables": { - "minimum_temperature": "crop_configurations.crop_configurations.7.minimum_temperature", - "optimal_temperature": "crop_configurations.crop_configurations.7.optimal_temperature", - "minimum_harvest_index": "crop_configurations.crop_configurations.7.minimum_harvest_index", - "optimal_harvest_index": "crop_configurations.crop_configurations.7.optimal_harvest_index" + "corn_grain_minimum_temperature": "crop_configurations.crop_configurations.7.minimum_temperature", + "corn_grain_optimal_temperature": "crop_configurations.crop_configurations.7.optimal_temperature", + "corn_grain_minimum_harvest_index": "crop_configurations.crop_configurations.7.minimum_harvest_index", + "corn_grain_optimal_harvest_index": "crop_configurations.crop_configurations.7.optimal_harvest_index" } }, "rules": [ { "left_hand": { - "ordered_variables": ["optimal_temperature"] + "ordered_variables": [ + "corn_grain_optimal_temperature" + ] }, "right_hand": { - "ordered_variables": ["minimum_temperature"] + "ordered_variables": [ + "corn_grain_minimum_temperature" + ] }, "relationship": "greater_or_equal_to" }, { "left_hand": { - "ordered_variables": ["optimal_harvest_index"] + "ordered_variables": [ + "corn_grain_optimal_harvest_index" + ] }, "right_hand": { - "ordered_variables": ["minimum_harvest_index"] + "ordered_variables": [ + "corn_grain_minimum_harvest_index" + ] }, "relationship": "greater_or_equal_to" } @@ -252,28 +316,36 @@ "description": "corn_silage temperature and harvest index bounds", "target_and_save": { "variables": { - "minimum_temperature": "crop_configurations.crop_configurations.8.minimum_temperature", - "optimal_temperature": "crop_configurations.crop_configurations.8.optimal_temperature", - "minimum_harvest_index": "crop_configurations.crop_configurations.8.minimum_harvest_index", - "optimal_harvest_index": "crop_configurations.crop_configurations.8.optimal_harvest_index" + "corn_silage_minimum_temperature": "crop_configurations.crop_configurations.8.minimum_temperature", + "corn_silage_optimal_temperature": "crop_configurations.crop_configurations.8.optimal_temperature", + "corn_silage_minimum_harvest_index": "crop_configurations.crop_configurations.8.minimum_harvest_index", + "corn_silage_optimal_harvest_index": "crop_configurations.crop_configurations.8.optimal_harvest_index" } }, "rules": [ { "left_hand": { - "ordered_variables": ["optimal_temperature"] + "ordered_variables": [ + "corn_silage_optimal_temperature" + ] }, "right_hand": { - "ordered_variables": ["minimum_temperature"] + "ordered_variables": [ + "corn_silage_minimum_temperature" + ] }, "relationship": "greater_or_equal_to" }, { "left_hand": { - "ordered_variables": ["optimal_harvest_index"] + "ordered_variables": [ + "corn_silage_optimal_harvest_index" + ] }, "right_hand": { - "ordered_variables": ["minimum_harvest_index"] + "ordered_variables": [ + "corn_silage_minimum_harvest_index" + ] }, "relationship": "greater_or_equal_to" } @@ -283,28 +355,36 @@ "description": "soybean_grain temperature and harvest index bounds", "target_and_save": { "variables": { - "minimum_temperature": "crop_configurations.crop_configurations.9.minimum_temperature", - "optimal_temperature": "crop_configurations.crop_configurations.9.optimal_temperature", - "minimum_harvest_index": "crop_configurations.crop_configurations.9.minimum_harvest_index", - "optimal_harvest_index": "crop_configurations.crop_configurations.9.optimal_harvest_index" + "soybean_grain_minimum_temperature": "crop_configurations.crop_configurations.9.minimum_temperature", + "soybean_grain_optimal_temperature": "crop_configurations.crop_configurations.9.optimal_temperature", + "soybean_grain_minimum_harvest_index": "crop_configurations.crop_configurations.9.minimum_harvest_index", + "soybean_grain_optimal_harvest_index": "crop_configurations.crop_configurations.9.optimal_harvest_index" } }, "rules": [ { "left_hand": { - "ordered_variables": ["optimal_temperature"] + "ordered_variables": [ + "soybean_grain_optimal_temperature" + ] }, "right_hand": { - "ordered_variables": ["minimum_temperature"] + "ordered_variables": [ + "soybean_grain_minimum_temperature" + ] }, "relationship": "greater_or_equal_to" }, { "left_hand": { - "ordered_variables": ["optimal_harvest_index"] + "ordered_variables": [ + "soybean_grain_optimal_harvest_index" + ] }, "right_hand": { - "ordered_variables": ["minimum_harvest_index"] + "ordered_variables": [ + "soybean_grain_minimum_harvest_index" + ] }, "relationship": "greater_or_equal_to" } @@ -314,28 +394,36 @@ "description": "soybean_hay temperature and harvest index bounds", "target_and_save": { "variables": { - "minimum_temperature": "crop_configurations.crop_configurations.10.minimum_temperature", - "optimal_temperature": "crop_configurations.crop_configurations.10.optimal_temperature", - "minimum_harvest_index": "crop_configurations.crop_configurations.10.minimum_harvest_index", - "optimal_harvest_index": "crop_configurations.crop_configurations.10.optimal_harvest_index" + "soybean_hay_minimum_temperature": "crop_configurations.crop_configurations.10.minimum_temperature", + "soybean_hay_optimal_temperature": "crop_configurations.crop_configurations.10.optimal_temperature", + "soybean_hay_minimum_harvest_index": "crop_configurations.crop_configurations.10.minimum_harvest_index", + "soybean_hay_optimal_harvest_index": "crop_configurations.crop_configurations.10.optimal_harvest_index" } }, "rules": [ { "left_hand": { - "ordered_variables": ["optimal_temperature"] + "ordered_variables": [ + "soybean_hay_optimal_temperature" + ] }, "right_hand": { - "ordered_variables": ["minimum_temperature"] + "ordered_variables": [ + "soybean_hay_minimum_temperature" + ] }, "relationship": "greater_or_equal_to" }, { "left_hand": { - "ordered_variables": ["optimal_harvest_index"] + "ordered_variables": [ + "soybean_hay_optimal_harvest_index" + ] }, "right_hand": { - "ordered_variables": ["minimum_harvest_index"] + "ordered_variables": [ + "soybean_hay_minimum_harvest_index" + ] }, "relationship": "greater_or_equal_to" } @@ -345,28 +433,36 @@ "description": "tall_fescue_silage temperature and harvest index bounds", "target_and_save": { "variables": { - "minimum_temperature": "crop_configurations.crop_configurations.11.minimum_temperature", - "optimal_temperature": "crop_configurations.crop_configurations.11.optimal_temperature", - "minimum_harvest_index": "crop_configurations.crop_configurations.11.minimum_harvest_index", - "optimal_harvest_index": "crop_configurations.crop_configurations.11.optimal_harvest_index" + "tall_fescue_silage_minimum_temperature": "crop_configurations.crop_configurations.11.minimum_temperature", + "tall_fescue_silage_optimal_temperature": "crop_configurations.crop_configurations.11.optimal_temperature", + "tall_fescue_silage_minimum_harvest_index": "crop_configurations.crop_configurations.11.minimum_harvest_index", + "tall_fescue_silage_optimal_harvest_index": "crop_configurations.crop_configurations.11.optimal_harvest_index" } }, "rules": [ { "left_hand": { - "ordered_variables": ["optimal_temperature"] + "ordered_variables": [ + "tall_fescue_silage_optimal_temperature" + ] }, "right_hand": { - "ordered_variables": ["minimum_temperature"] + "ordered_variables": [ + "tall_fescue_silage_minimum_temperature" + ] }, "relationship": "greater_or_equal_to" }, { "left_hand": { - "ordered_variables": ["optimal_harvest_index"] + "ordered_variables": [ + "tall_fescue_silage_optimal_harvest_index" + ] }, "right_hand": { - "ordered_variables": ["minimum_harvest_index"] + "ordered_variables": [ + "tall_fescue_silage_minimum_harvest_index" + ] }, "relationship": "greater_or_equal_to" } @@ -376,28 +472,36 @@ "description": "tall_fescue_baleage temperature and harvest index bounds", "target_and_save": { "variables": { - "minimum_temperature": "crop_configurations.crop_configurations.12.minimum_temperature", - "optimal_temperature": "crop_configurations.crop_configurations.12.optimal_temperature", - "minimum_harvest_index": "crop_configurations.crop_configurations.12.minimum_harvest_index", - "optimal_harvest_index": "crop_configurations.crop_configurations.12.optimal_harvest_index" + "tall_fescue_baleage_minimum_temperature": "crop_configurations.crop_configurations.12.minimum_temperature", + "tall_fescue_baleage_optimal_temperature": "crop_configurations.crop_configurations.12.optimal_temperature", + "tall_fescue_baleage_minimum_harvest_index": "crop_configurations.crop_configurations.12.minimum_harvest_index", + "tall_fescue_baleage_optimal_harvest_index": "crop_configurations.crop_configurations.12.optimal_harvest_index" } }, "rules": [ { "left_hand": { - "ordered_variables": ["optimal_temperature"] + "ordered_variables": [ + "tall_fescue_baleage_optimal_temperature" + ] }, "right_hand": { - "ordered_variables": ["minimum_temperature"] + "ordered_variables": [ + "tall_fescue_baleage_minimum_temperature" + ] }, "relationship": "greater_or_equal_to" }, { "left_hand": { - "ordered_variables": ["optimal_harvest_index"] + "ordered_variables": [ + "tall_fescue_baleage_optimal_harvest_index" + ] }, "right_hand": { - "ordered_variables": ["minimum_harvest_index"] + "ordered_variables": [ + "tall_fescue_baleage_minimum_harvest_index" + ] }, "relationship": "greater_or_equal_to" } @@ -407,28 +511,36 @@ "description": "tall_fescue_hay temperature and harvest index bounds", "target_and_save": { "variables": { - "minimum_temperature": "crop_configurations.crop_configurations.13.minimum_temperature", - "optimal_temperature": "crop_configurations.crop_configurations.13.optimal_temperature", - "minimum_harvest_index": "crop_configurations.crop_configurations.13.minimum_harvest_index", - "optimal_harvest_index": "crop_configurations.crop_configurations.13.optimal_harvest_index" + "tall_fescue_hay_minimum_temperature": "crop_configurations.crop_configurations.13.minimum_temperature", + "tall_fescue_hay_optimal_temperature": "crop_configurations.crop_configurations.13.optimal_temperature", + "tall_fescue_hay_minimum_harvest_index": "crop_configurations.crop_configurations.13.minimum_harvest_index", + "tall_fescue_hay_optimal_harvest_index": "crop_configurations.crop_configurations.13.optimal_harvest_index" } }, "rules": [ { "left_hand": { - "ordered_variables": ["optimal_temperature"] + "ordered_variables": [ + "tall_fescue_hay_optimal_temperature" + ] }, "right_hand": { - "ordered_variables": ["minimum_temperature"] + "ordered_variables": [ + "tall_fescue_hay_minimum_temperature" + ] }, "relationship": "greater_or_equal_to" }, { "left_hand": { - "ordered_variables": ["optimal_harvest_index"] + "ordered_variables": [ + "tall_fescue_hay_optimal_harvest_index" + ] }, "right_hand": { - "ordered_variables": ["minimum_harvest_index"] + "ordered_variables": [ + "tall_fescue_hay_minimum_harvest_index" + ] }, "relationship": "greater_or_equal_to" } @@ -438,28 +550,36 @@ "description": "triticale_grain temperature and harvest index bounds", "target_and_save": { "variables": { - "minimum_temperature": "crop_configurations.crop_configurations.14.minimum_temperature", - "optimal_temperature": "crop_configurations.crop_configurations.14.optimal_temperature", - "minimum_harvest_index": "crop_configurations.crop_configurations.14.minimum_harvest_index", - "optimal_harvest_index": "crop_configurations.crop_configurations.14.optimal_harvest_index" + "triticale_grain_minimum_temperature": "crop_configurations.crop_configurations.14.minimum_temperature", + "triticale_grain_optimal_temperature": "crop_configurations.crop_configurations.14.optimal_temperature", + "triticale_grain_minimum_harvest_index": "crop_configurations.crop_configurations.14.minimum_harvest_index", + "triticale_grain_optimal_harvest_index": "crop_configurations.crop_configurations.14.optimal_harvest_index" } }, "rules": [ { "left_hand": { - "ordered_variables": ["optimal_temperature"] + "ordered_variables": [ + "triticale_grain_optimal_temperature" + ] }, "right_hand": { - "ordered_variables": ["minimum_temperature"] + "ordered_variables": [ + "triticale_grain_minimum_temperature" + ] }, "relationship": "greater_or_equal_to" }, { "left_hand": { - "ordered_variables": ["optimal_harvest_index"] + "ordered_variables": [ + "triticale_grain_optimal_harvest_index" + ] }, "right_hand": { - "ordered_variables": ["minimum_harvest_index"] + "ordered_variables": [ + "triticale_grain_minimum_harvest_index" + ] }, "relationship": "greater_or_equal_to" } @@ -469,28 +589,36 @@ "description": "triticale_silage temperature and harvest index bounds", "target_and_save": { "variables": { - "minimum_temperature": "crop_configurations.crop_configurations.15.minimum_temperature", - "optimal_temperature": "crop_configurations.crop_configurations.15.optimal_temperature", - "minimum_harvest_index": "crop_configurations.crop_configurations.15.minimum_harvest_index", - "optimal_harvest_index": "crop_configurations.crop_configurations.15.optimal_harvest_index" + "triticale_silage_minimum_temperature": "crop_configurations.crop_configurations.15.minimum_temperature", + "triticale_silage_optimal_temperature": "crop_configurations.crop_configurations.15.optimal_temperature", + "triticale_silage_minimum_harvest_index": "crop_configurations.crop_configurations.15.minimum_harvest_index", + "triticale_silage_optimal_harvest_index": "crop_configurations.crop_configurations.15.optimal_harvest_index" } }, "rules": [ { "left_hand": { - "ordered_variables": ["optimal_temperature"] + "ordered_variables": [ + "triticale_silage_optimal_temperature" + ] }, "right_hand": { - "ordered_variables": ["minimum_temperature"] + "ordered_variables": [ + "triticale_silage_minimum_temperature" + ] }, "relationship": "greater_or_equal_to" }, { "left_hand": { - "ordered_variables": ["optimal_harvest_index"] + "ordered_variables": [ + "triticale_silage_optimal_harvest_index" + ] }, "right_hand": { - "ordered_variables": ["minimum_harvest_index"] + "ordered_variables": [ + "triticale_silage_minimum_harvest_index" + ] }, "relationship": "greater_or_equal_to" } @@ -500,28 +628,36 @@ "description": "triticale_baleage temperature and harvest index bounds", "target_and_save": { "variables": { - "minimum_temperature": "crop_configurations.crop_configurations.16.minimum_temperature", - "optimal_temperature": "crop_configurations.crop_configurations.16.optimal_temperature", - "minimum_harvest_index": "crop_configurations.crop_configurations.16.minimum_harvest_index", - "optimal_harvest_index": "crop_configurations.crop_configurations.16.optimal_harvest_index" + "triticale_baleage_minimum_temperature": "crop_configurations.crop_configurations.16.minimum_temperature", + "triticale_baleage_optimal_temperature": "crop_configurations.crop_configurations.16.optimal_temperature", + "triticale_baleage_minimum_harvest_index": "crop_configurations.crop_configurations.16.minimum_harvest_index", + "triticale_baleage_optimal_harvest_index": "crop_configurations.crop_configurations.16.optimal_harvest_index" } }, "rules": [ { "left_hand": { - "ordered_variables": ["optimal_temperature"] + "ordered_variables": [ + "triticale_baleage_optimal_temperature" + ] }, "right_hand": { - "ordered_variables": ["minimum_temperature"] + "ordered_variables": [ + "triticale_baleage_minimum_temperature" + ] }, "relationship": "greater_or_equal_to" }, { "left_hand": { - "ordered_variables": ["optimal_harvest_index"] + "ordered_variables": [ + "triticale_baleage_optimal_harvest_index" + ] }, "right_hand": { - "ordered_variables": ["minimum_harvest_index"] + "ordered_variables": [ + "triticale_baleage_minimum_harvest_index" + ] }, "relationship": "greater_or_equal_to" } @@ -531,28 +667,36 @@ "description": "triticale_hay temperature and harvest index bounds", "target_and_save": { "variables": { - "minimum_temperature": "crop_configurations.crop_configurations.17.minimum_temperature", - "optimal_temperature": "crop_configurations.crop_configurations.17.optimal_temperature", - "minimum_harvest_index": "crop_configurations.crop_configurations.17.minimum_harvest_index", - "optimal_harvest_index": "crop_configurations.crop_configurations.17.optimal_harvest_index" + "triticale_hay_minimum_temperature": "crop_configurations.crop_configurations.17.minimum_temperature", + "triticale_hay_optimal_temperature": "crop_configurations.crop_configurations.17.optimal_temperature", + "triticale_hay_minimum_harvest_index": "crop_configurations.crop_configurations.17.minimum_harvest_index", + "triticale_hay_optimal_harvest_index": "crop_configurations.crop_configurations.17.optimal_harvest_index" } }, "rules": [ { "left_hand": { - "ordered_variables": ["optimal_temperature"] + "ordered_variables": [ + "triticale_hay_optimal_temperature" + ] }, "right_hand": { - "ordered_variables": ["minimum_temperature"] + "ordered_variables": [ + "triticale_hay_minimum_temperature" + ] }, "relationship": "greater_or_equal_to" }, { "left_hand": { - "ordered_variables": ["optimal_harvest_index"] + "ordered_variables": [ + "triticale_hay_optimal_harvest_index" + ] }, "right_hand": { - "ordered_variables": ["minimum_harvest_index"] + "ordered_variables": [ + "triticale_hay_minimum_harvest_index" + ] }, "relationship": "greater_or_equal_to" } @@ -562,28 +706,36 @@ "description": "winter_wheat_grain temperature and harvest index bounds", "target_and_save": { "variables": { - "minimum_temperature": "crop_configurations.crop_configurations.18.minimum_temperature", - "optimal_temperature": "crop_configurations.crop_configurations.18.optimal_temperature", - "minimum_harvest_index": "crop_configurations.crop_configurations.18.minimum_harvest_index", - "optimal_harvest_index": "crop_configurations.crop_configurations.18.optimal_harvest_index" + "winter_wheat_grain_minimum_temperature": "crop_configurations.crop_configurations.18.minimum_temperature", + "winter_wheat_grain_optimal_temperature": "crop_configurations.crop_configurations.18.optimal_temperature", + "winter_wheat_grain_minimum_harvest_index": "crop_configurations.crop_configurations.18.minimum_harvest_index", + "winter_wheat_grain_optimal_harvest_index": "crop_configurations.crop_configurations.18.optimal_harvest_index" } }, "rules": [ { "left_hand": { - "ordered_variables": ["optimal_temperature"] + "ordered_variables": [ + "winter_wheat_grain_optimal_temperature" + ] }, "right_hand": { - "ordered_variables": ["minimum_temperature"] + "ordered_variables": [ + "winter_wheat_grain_minimum_temperature" + ] }, "relationship": "greater_or_equal_to" }, { "left_hand": { - "ordered_variables": ["optimal_harvest_index"] + "ordered_variables": [ + "winter_wheat_grain_optimal_harvest_index" + ] }, "right_hand": { - "ordered_variables": ["minimum_harvest_index"] + "ordered_variables": [ + "winter_wheat_grain_minimum_harvest_index" + ] }, "relationship": "greater_or_equal_to" } @@ -593,28 +745,36 @@ "description": "winter_wheat_silage temperature and harvest index bounds", "target_and_save": { "variables": { - "minimum_temperature": "crop_configurations.crop_configurations.19.minimum_temperature", - "optimal_temperature": "crop_configurations.crop_configurations.19.optimal_temperature", - "minimum_harvest_index": "crop_configurations.crop_configurations.19.minimum_harvest_index", - "optimal_harvest_index": "crop_configurations.crop_configurations.19.optimal_harvest_index" + "winter_wheat_silage_minimum_temperature": "crop_configurations.crop_configurations.19.minimum_temperature", + "winter_wheat_silage_optimal_temperature": "crop_configurations.crop_configurations.19.optimal_temperature", + "winter_wheat_silage_minimum_harvest_index": "crop_configurations.crop_configurations.19.minimum_harvest_index", + "winter_wheat_silage_optimal_harvest_index": "crop_configurations.crop_configurations.19.optimal_harvest_index" } }, "rules": [ { "left_hand": { - "ordered_variables": ["optimal_temperature"] + "ordered_variables": [ + "winter_wheat_silage_optimal_temperature" + ] }, "right_hand": { - "ordered_variables": ["minimum_temperature"] + "ordered_variables": [ + "winter_wheat_silage_minimum_temperature" + ] }, "relationship": "greater_or_equal_to" }, { "left_hand": { - "ordered_variables": ["optimal_harvest_index"] + "ordered_variables": [ + "winter_wheat_silage_optimal_harvest_index" + ] }, "right_hand": { - "ordered_variables": ["minimum_harvest_index"] + "ordered_variables": [ + "winter_wheat_silage_minimum_harvest_index" + ] }, "relationship": "greater_or_equal_to" } @@ -624,28 +784,36 @@ "description": "winter_wheat_baleage temperature and harvest index bounds", "target_and_save": { "variables": { - "minimum_temperature": "crop_configurations.crop_configurations.20.minimum_temperature", - "optimal_temperature": "crop_configurations.crop_configurations.20.optimal_temperature", - "minimum_harvest_index": "crop_configurations.crop_configurations.20.minimum_harvest_index", - "optimal_harvest_index": "crop_configurations.crop_configurations.20.optimal_harvest_index" + "winter_wheat_baleage_minimum_temperature": "crop_configurations.crop_configurations.20.minimum_temperature", + "winter_wheat_baleage_optimal_temperature": "crop_configurations.crop_configurations.20.optimal_temperature", + "winter_wheat_baleage_minimum_harvest_index": "crop_configurations.crop_configurations.20.minimum_harvest_index", + "winter_wheat_baleage_optimal_harvest_index": "crop_configurations.crop_configurations.20.optimal_harvest_index" } }, "rules": [ { "left_hand": { - "ordered_variables": ["optimal_temperature"] + "ordered_variables": [ + "winter_wheat_baleage_optimal_temperature" + ] }, "right_hand": { - "ordered_variables": ["minimum_temperature"] + "ordered_variables": [ + "winter_wheat_baleage_minimum_temperature" + ] }, "relationship": "greater_or_equal_to" }, { "left_hand": { - "ordered_variables": ["optimal_harvest_index"] + "ordered_variables": [ + "winter_wheat_baleage_optimal_harvest_index" + ] }, "right_hand": { - "ordered_variables": ["minimum_harvest_index"] + "ordered_variables": [ + "winter_wheat_baleage_minimum_harvest_index" + ] }, "relationship": "greater_or_equal_to" } @@ -655,28 +823,36 @@ "description": "winter_wheat_hay temperature and harvest index bounds", "target_and_save": { "variables": { - "minimum_temperature": "crop_configurations.crop_configurations.21.minimum_temperature", - "optimal_temperature": "crop_configurations.crop_configurations.21.optimal_temperature", - "minimum_harvest_index": "crop_configurations.crop_configurations.21.minimum_harvest_index", - "optimal_harvest_index": "crop_configurations.crop_configurations.21.optimal_harvest_index" + "winter_wheat_hay_minimum_temperature": "crop_configurations.crop_configurations.21.minimum_temperature", + "winter_wheat_hay_optimal_temperature": "crop_configurations.crop_configurations.21.optimal_temperature", + "winter_wheat_hay_minimum_harvest_index": "crop_configurations.crop_configurations.21.minimum_harvest_index", + "winter_wheat_hay_optimal_harvest_index": "crop_configurations.crop_configurations.21.optimal_harvest_index" } }, "rules": [ { "left_hand": { - "ordered_variables": ["optimal_temperature"] + "ordered_variables": [ + "winter_wheat_hay_optimal_temperature" + ] }, "right_hand": { - "ordered_variables": ["minimum_temperature"] + "ordered_variables": [ + "winter_wheat_hay_minimum_temperature" + ] }, "relationship": "greater_or_equal_to" }, { "left_hand": { - "ordered_variables": ["optimal_harvest_index"] + "ordered_variables": [ + "winter_wheat_hay_optimal_harvest_index" + ] }, "right_hand": { - "ordered_variables": ["minimum_harvest_index"] + "ordered_variables": [ + "winter_wheat_hay_minimum_harvest_index" + ] }, "relationship": "greater_or_equal_to" } @@ -686,31 +862,39 @@ "description": "Corn-Alf-Silage crop schedules have equal planting and harvesting year/day lengths", "target_and_save": { "variables": { - "planting_years": "Corn-Alf-Silage.crop_schedules.0.planting_years", - "planting_days": "Corn-Alf-Silage.crop_schedules.0.planting_days", - "harvest_years": "Corn-Alf-Silage.crop_schedules.0.harvest_years", - "harvest_days": "Corn-Alf-Silage.crop_schedules.0.harvest_days" + "Corn-Alf-Silage_schedule_0_planting_years": "Corn-Alf-Silage.crop_schedules.0.planting_years", + "Corn-Alf-Silage_schedule_0_planting_days": "Corn-Alf-Silage.crop_schedules.0.planting_days", + "Corn-Alf-Silage_schedule_0_harvest_years": "Corn-Alf-Silage.crop_schedules.0.harvest_years", + "Corn-Alf-Silage_schedule_0_harvest_days": "Corn-Alf-Silage.crop_schedules.0.harvest_days" } }, "rules": [ { "left_hand": { - "ordered_variables": ["planting_years"], + "ordered_variables": [ + "Corn-Alf-Silage_schedule_0_planting_years" + ], "apply_to": "individual" }, "right_hand": { - "ordered_variables": ["planting_days"], + "ordered_variables": [ + "Corn-Alf-Silage_schedule_0_planting_days" + ], "apply_to": "individual" }, "relationship": "is_equal_length" }, { "left_hand": { - "ordered_variables": ["harvest_years"], + "ordered_variables": [ + "Corn-Alf-Silage_schedule_0_harvest_years" + ], "apply_to": "individual" }, "right_hand": { - "ordered_variables": ["harvest_days"], + "ordered_variables": [ + "Corn-Alf-Silage_schedule_0_harvest_days" + ], "apply_to": "individual" }, "relationship": "is_equal_length" @@ -721,31 +905,39 @@ "description": "Corn-Alf-Silage crop schedules have equal planting and harvesting year/day lengths", "target_and_save": { "variables": { - "planting_years": "Corn-Alf-Silage.crop_schedules.1.planting_years", - "planting_days": "Corn-Alf-Silage.crop_schedules.1.planting_days", - "harvest_years": "Corn-Alf-Silage.crop_schedules.1.harvest_years", - "harvest_days": "Corn-Alf-Silage.crop_schedules.1.harvest_days" + "Corn-Alf-Silage_schedule_1_planting_years": "Corn-Alf-Silage.crop_schedules.1.planting_years", + "Corn-Alf-Silage_schedule_1_planting_days": "Corn-Alf-Silage.crop_schedules.1.planting_days", + "Corn-Alf-Silage_schedule_1_harvest_years": "Corn-Alf-Silage.crop_schedules.1.harvest_years", + "Corn-Alf-Silage_schedule_1_harvest_days": "Corn-Alf-Silage.crop_schedules.1.harvest_days" } }, "rules": [ { "left_hand": { - "ordered_variables": ["planting_years"], + "ordered_variables": [ + "Corn-Alf-Silage_schedule_1_planting_years" + ], "apply_to": "individual" }, "right_hand": { - "ordered_variables": ["planting_days"], + "ordered_variables": [ + "Corn-Alf-Silage_schedule_1_planting_days" + ], "apply_to": "individual" }, "relationship": "is_equal_length" }, { "left_hand": { - "ordered_variables": ["harvest_years"], + "ordered_variables": [ + "Corn-Alf-Silage_schedule_1_harvest_years" + ], "apply_to": "individual" }, "right_hand": { - "ordered_variables": ["harvest_days"], + "ordered_variables": [ + "Corn-Alf-Silage_schedule_1_harvest_days" + ], "apply_to": "individual" }, "relationship": "is_equal_length" @@ -756,31 +948,39 @@ "description": "Corn-Alf-Silage crop schedules have equal planting and harvesting year/day lengths", "target_and_save": { "variables": { - "planting_years": "Corn-Alf-Silage.crop_schedules.2.planting_years", - "planting_days": "Corn-Alf-Silage.crop_schedules.2.planting_days", - "harvest_years": "Corn-Alf-Silage.crop_schedules.2.harvest_years", - "harvest_days": "Corn-Alf-Silage.crop_schedules.2.harvest_days" + "Corn-Alf-Silage_schedule_2_planting_years": "Corn-Alf-Silage.crop_schedules.2.planting_years", + "Corn-Alf-Silage_schedule_2_planting_days": "Corn-Alf-Silage.crop_schedules.2.planting_days", + "Corn-Alf-Silage_schedule_2_harvest_years": "Corn-Alf-Silage.crop_schedules.2.harvest_years", + "Corn-Alf-Silage_schedule_2_harvest_days": "Corn-Alf-Silage.crop_schedules.2.harvest_days" } }, "rules": [ { "left_hand": { - "ordered_variables": ["planting_years"], + "ordered_variables": [ + "Corn-Alf-Silage_schedule_2_planting_years" + ], "apply_to": "individual" }, "right_hand": { - "ordered_variables": ["planting_days"], + "ordered_variables": [ + "Corn-Alf-Silage_schedule_2_planting_days" + ], "apply_to": "individual" }, "relationship": "is_equal_length" }, { "left_hand": { - "ordered_variables": ["harvest_years"], + "ordered_variables": [ + "Corn-Alf-Silage_schedule_2_harvest_years" + ], "apply_to": "individual" }, "right_hand": { - "ordered_variables": ["harvest_days"], + "ordered_variables": [ + "Corn-Alf-Silage_schedule_2_harvest_days" + ], "apply_to": "individual" }, "relationship": "is_equal_length" @@ -791,31 +991,39 @@ "description": "Corn-Alf-Silage crop schedules have equal planting and harvesting year/day lengths", "target_and_save": { "variables": { - "planting_years": "Corn-Alf-Silage.crop_schedules.3.planting_years", - "planting_days": "Corn-Alf-Silage.crop_schedules.3.planting_days", - "harvest_years": "Corn-Alf-Silage.crop_schedules.3.harvest_years", - "harvest_days": "Corn-Alf-Silage.crop_schedules.3.harvest_days" + "Corn-Alf-Silage_schedule_3_planting_years": "Corn-Alf-Silage.crop_schedules.3.planting_years", + "Corn-Alf-Silage_schedule_3_planting_days": "Corn-Alf-Silage.crop_schedules.3.planting_days", + "Corn-Alf-Silage_schedule_3_harvest_years": "Corn-Alf-Silage.crop_schedules.3.harvest_years", + "Corn-Alf-Silage_schedule_3_harvest_days": "Corn-Alf-Silage.crop_schedules.3.harvest_days" } }, "rules": [ { "left_hand": { - "ordered_variables": ["planting_years"], + "ordered_variables": [ + "Corn-Alf-Silage_schedule_3_planting_years" + ], "apply_to": "individual" }, "right_hand": { - "ordered_variables": ["planting_days"], + "ordered_variables": [ + "Corn-Alf-Silage_schedule_3_planting_days" + ], "apply_to": "individual" }, "relationship": "is_equal_length" }, { "left_hand": { - "ordered_variables": ["harvest_years"], + "ordered_variables": [ + "Corn-Alf-Silage_schedule_3_harvest_years" + ], "apply_to": "individual" }, "right_hand": { - "ordered_variables": ["harvest_days"], + "ordered_variables": [ + "Corn-Alf-Silage_schedule_3_harvest_days" + ], "apply_to": "individual" }, "relationship": "is_equal_length" @@ -826,31 +1034,39 @@ "description": "CornGrain-AlfHay crop schedules have equal planting and harvesting year/day lengths", "target_and_save": { "variables": { - "planting_years": "CornGrain-AlfHay.crop_schedules.0.planting_years", - "planting_days": "CornGrain-AlfHay.crop_schedules.0.planting_days", - "harvest_years": "CornGrain-AlfHay.crop_schedules.0.harvest_years", - "harvest_days": "CornGrain-AlfHay.crop_schedules.0.harvest_days" + "CornGrain-AlfHay_schedule_0_planting_years": "CornGrain-AlfHay.crop_schedules.0.planting_years", + "CornGrain-AlfHay_schedule_0_planting_days": "CornGrain-AlfHay.crop_schedules.0.planting_days", + "CornGrain-AlfHay_schedule_0_harvest_years": "CornGrain-AlfHay.crop_schedules.0.harvest_years", + "CornGrain-AlfHay_schedule_0_harvest_days": "CornGrain-AlfHay.crop_schedules.0.harvest_days" } }, "rules": [ { "left_hand": { - "ordered_variables": ["planting_years"], + "ordered_variables": [ + "CornGrain-AlfHay_schedule_0_planting_years" + ], "apply_to": "individual" }, "right_hand": { - "ordered_variables": ["planting_days"], + "ordered_variables": [ + "CornGrain-AlfHay_schedule_0_planting_days" + ], "apply_to": "individual" }, "relationship": "is_equal_length" }, { "left_hand": { - "ordered_variables": ["harvest_years"], + "ordered_variables": [ + "CornGrain-AlfHay_schedule_0_harvest_years" + ], "apply_to": "individual" }, "right_hand": { - "ordered_variables": ["harvest_days"], + "ordered_variables": [ + "CornGrain-AlfHay_schedule_0_harvest_days" + ], "apply_to": "individual" }, "relationship": "is_equal_length" @@ -861,31 +1077,39 @@ "description": "CornGrain-AlfHay crop schedules have equal planting and harvesting year/day lengths", "target_and_save": { "variables": { - "planting_years": "CornGrain-AlfHay.crop_schedules.1.planting_years", - "planting_days": "CornGrain-AlfHay.crop_schedules.1.planting_days", - "harvest_years": "CornGrain-AlfHay.crop_schedules.1.harvest_years", - "harvest_days": "CornGrain-AlfHay.crop_schedules.1.harvest_days" + "CornGrain-AlfHay_schedule_1_planting_years": "CornGrain-AlfHay.crop_schedules.1.planting_years", + "CornGrain-AlfHay_schedule_1_planting_days": "CornGrain-AlfHay.crop_schedules.1.planting_days", + "CornGrain-AlfHay_schedule_1_harvest_years": "CornGrain-AlfHay.crop_schedules.1.harvest_years", + "CornGrain-AlfHay_schedule_1_harvest_days": "CornGrain-AlfHay.crop_schedules.1.harvest_days" } }, "rules": [ { "left_hand": { - "ordered_variables": ["planting_years"], + "ordered_variables": [ + "CornGrain-AlfHay_schedule_1_planting_years" + ], "apply_to": "individual" }, "right_hand": { - "ordered_variables": ["planting_days"], + "ordered_variables": [ + "CornGrain-AlfHay_schedule_1_planting_days" + ], "apply_to": "individual" }, "relationship": "is_equal_length" }, { "left_hand": { - "ordered_variables": ["harvest_years"], + "ordered_variables": [ + "CornGrain-AlfHay_schedule_1_harvest_years" + ], "apply_to": "individual" }, "right_hand": { - "ordered_variables": ["harvest_days"], + "ordered_variables": [ + "CornGrain-AlfHay_schedule_1_harvest_days" + ], "apply_to": "individual" }, "relationship": "is_equal_length" @@ -896,31 +1120,39 @@ "description": "CornGrain-AlfHay crop schedules have equal planting and harvesting year/day lengths", "target_and_save": { "variables": { - "planting_years": "CornGrain-AlfHay.crop_schedules.2.planting_years", - "planting_days": "CornGrain-AlfHay.crop_schedules.2.planting_days", - "harvest_years": "CornGrain-AlfHay.crop_schedules.2.harvest_years", - "harvest_days": "CornGrain-AlfHay.crop_schedules.2.harvest_days" + "CornGrain-AlfHay_schedule_2_planting_years": "CornGrain-AlfHay.crop_schedules.2.planting_years", + "CornGrain-AlfHay_schedule_2_planting_days": "CornGrain-AlfHay.crop_schedules.2.planting_days", + "CornGrain-AlfHay_schedule_2_harvest_years": "CornGrain-AlfHay.crop_schedules.2.harvest_years", + "CornGrain-AlfHay_schedule_2_harvest_days": "CornGrain-AlfHay.crop_schedules.2.harvest_days" } }, "rules": [ { "left_hand": { - "ordered_variables": ["planting_years"], + "ordered_variables": [ + "CornGrain-AlfHay_schedule_2_planting_years" + ], "apply_to": "individual" }, "right_hand": { - "ordered_variables": ["planting_days"], + "ordered_variables": [ + "CornGrain-AlfHay_schedule_2_planting_days" + ], "apply_to": "individual" }, "relationship": "is_equal_length" }, { "left_hand": { - "ordered_variables": ["harvest_years"], + "ordered_variables": [ + "CornGrain-AlfHay_schedule_2_harvest_years" + ], "apply_to": "individual" }, "right_hand": { - "ordered_variables": ["harvest_days"], + "ordered_variables": [ + "CornGrain-AlfHay_schedule_2_harvest_days" + ], "apply_to": "individual" }, "relationship": "is_equal_length" @@ -931,36 +1163,372 @@ "description": "CornGrain-AlfHay crop schedules have equal planting and harvesting year/day lengths", "target_and_save": { "variables": { - "planting_years": "CornGrain-AlfHay.crop_schedules.3.planting_years", - "planting_days": "CornGrain-AlfHay.crop_schedules.3.planting_days", - "harvest_years": "CornGrain-AlfHay.crop_schedules.3.harvest_years", - "harvest_days": "CornGrain-AlfHay.crop_schedules.3.harvest_days" + "CornGrain-AlfHay_schedule_3_planting_years": "CornGrain-AlfHay.crop_schedules.3.planting_years", + "CornGrain-AlfHay_schedule_3_planting_days": "CornGrain-AlfHay.crop_schedules.3.planting_days", + "CornGrain-AlfHay_schedule_3_harvest_years": "CornGrain-AlfHay.crop_schedules.3.harvest_years", + "CornGrain-AlfHay_schedule_3_harvest_days": "CornGrain-AlfHay.crop_schedules.3.harvest_days" } }, "rules": [ { "left_hand": { - "ordered_variables": ["planting_years"], + "ordered_variables": [ + "CornGrain-AlfHay_schedule_3_planting_years" + ], "apply_to": "individual" }, "right_hand": { - "ordered_variables": ["planting_days"], + "ordered_variables": [ + "CornGrain-AlfHay_schedule_3_planting_days" + ], "apply_to": "individual" }, "relationship": "is_equal_length" }, { "left_hand": { - "ordered_variables": ["harvest_years"], + "ordered_variables": [ + "CornGrain-AlfHay_schedule_3_harvest_years" + ], "apply_to": "individual" }, "right_hand": { - "ordered_variables": ["harvest_days"], + "ordered_variables": [ + "CornGrain-AlfHay_schedule_3_harvest_days" + ], "apply_to": "individual" }, "relationship": "is_equal_length" } ] + }, + { + "description": "field_1 watering amount must be zero when watering interval is zero", + "target_and_save": { + "variables": { + "watering_amount_in_liters": "field_1.watering_amount_in_liters", + "watering_interval": "field_1.watering_interval" + }, + "constants": { + "zero": 0 + } + }, + "apply_when": [ + { + "left_hand": { + "ordered_variables": ["watering_interval"] + }, + "right_hand": { + "ordered_variables": ["zero"] + }, + "relationship": "equal" + } + ], + "rules": [ + { + "left_hand": { + "ordered_variables": ["watering_amount_in_liters"] + }, + "right_hand": { + "ordered_variables": ["zero"] + }, + "relationship": "equal" + } + ] + }, + { + "description": "field_1 watering interval must be greater than zero when watering amount is positive", + "apply_when": [ + { + "left_hand": { + "ordered_variables": ["watering_amount_in_liters"] + }, + "right_hand": { + "ordered_variables": ["zero"] + }, + "relationship": "greater" + } + ], + "rules": [ + { + "left_hand": { + "ordered_variables": ["watering_interval"] + }, + "right_hand": { + "ordered_variables": ["zero"] + }, + "relationship": "greater" + } + ] + }, + { + "description": "field_2 watering amount must be zero when watering interval is zero", + "target_and_save": { + "variables": { + "watering_amount_in_liters": "field_2.watering_amount_in_liters", + "watering_interval": "field_2.watering_interval" + }, + "constants": { + "zero": 0 + } + }, + "apply_when": [ + { + "left_hand": { + "ordered_variables": ["watering_interval"] + }, + "right_hand": { + "ordered_variables": ["zero"] + }, + "relationship": "equal" + } + ], + "rules": [ + { + "left_hand": { + "ordered_variables": ["watering_amount_in_liters"] + }, + "right_hand": { + "ordered_variables": ["zero"] + }, + "relationship": "equal" + } + ] + }, + { + "description": "field_2 watering interval must be greater than zero when watering amount is positive", + "apply_when": [ + { + "left_hand": { + "ordered_variables": ["watering_amount_in_liters"] + }, + "right_hand": { + "ordered_variables": ["zero"] + }, + "relationship": "greater" + } + ], + "rules": [ + { + "left_hand": { + "ordered_variables": ["watering_interval"] + }, + "right_hand": { + "ordered_variables": ["zero"] + }, + "relationship": "greater" + } + ] + }, + { + "description": "soil_1 layer 0 water concentrations are ordered", + "target_and_save": { + "variables": { + "wilting_point_water_concentration": "soil_1.soil_layers.0.wilting_point_water_concentration", + "field_capacity_water_concentration": "soil_1.soil_layers.0.field_capacity_water_concentration", + "saturation_point_water_concentration": "soil_1.soil_layers.0.saturation_point_water_concentration" + } + }, + "rules": [ + { + "left_hand": { + "ordered_variables": ["field_capacity_water_concentration"] + }, + "right_hand": { + "ordered_variables": ["wilting_point_water_concentration"] + }, + "relationship": "greater_or_equal_to" + }, + { + "left_hand": { + "ordered_variables": ["saturation_point_water_concentration"] + }, + "right_hand": { + "ordered_variables": ["field_capacity_water_concentration"] + }, + "relationship": "greater_or_equal_to" + } + ] + }, + { + "description": "soil_1 layer 1 water concentrations are ordered", + "target_and_save": { + "variables": { + "wilting_point_water_concentration": "soil_1.soil_layers.1.wilting_point_water_concentration", + "field_capacity_water_concentration": "soil_1.soil_layers.1.field_capacity_water_concentration", + "saturation_point_water_concentration": "soil_1.soil_layers.1.saturation_point_water_concentration" + } + }, + "rules": [ + { + "left_hand": { + "ordered_variables": ["field_capacity_water_concentration"] + }, + "right_hand": { + "ordered_variables": ["wilting_point_water_concentration"] + }, + "relationship": "greater_or_equal_to" + }, + { + "left_hand": { + "ordered_variables": ["saturation_point_water_concentration"] + }, + "right_hand": { + "ordered_variables": ["field_capacity_water_concentration"] + }, + "relationship": "greater_or_equal_to" + } + ] + }, + { + "description": "soil_1 layer 2 water concentrations are ordered", + "target_and_save": { + "variables": { + "wilting_point_water_concentration": "soil_1.soil_layers.2.wilting_point_water_concentration", + "field_capacity_water_concentration": "soil_1.soil_layers.2.field_capacity_water_concentration", + "saturation_point_water_concentration": "soil_1.soil_layers.2.saturation_point_water_concentration" + } + }, + "rules": [ + { + "left_hand": { + "ordered_variables": ["field_capacity_water_concentration"] + }, + "right_hand": { + "ordered_variables": ["wilting_point_water_concentration"] + }, + "relationship": "greater_or_equal_to" + }, + { + "left_hand": { + "ordered_variables": ["saturation_point_water_concentration"] + }, + "right_hand": { + "ordered_variables": ["field_capacity_water_concentration"] + }, + "relationship": "greater_or_equal_to" + } + ] + }, + { + "description": "soil_2 layer 0 water concentrations are ordered", + "target_and_save": { + "variables": { + "wilting_point_water_concentration": "soil_2.soil_layers.0.wilting_point_water_concentration", + "field_capacity_water_concentration": "soil_2.soil_layers.0.field_capacity_water_concentration", + "saturation_point_water_concentration": "soil_2.soil_layers.0.saturation_point_water_concentration" + } + }, + "rules": [ + { + "left_hand": { + "ordered_variables": ["field_capacity_water_concentration"] + }, + "right_hand": { + "ordered_variables": ["wilting_point_water_concentration"] + }, + "relationship": "greater_or_equal_to" + }, + { + "left_hand": { + "ordered_variables": ["saturation_point_water_concentration"] + }, + "right_hand": { + "ordered_variables": ["field_capacity_water_concentration"] + }, + "relationship": "greater_or_equal_to" + } + ] + }, + { + "description": "soil_2 layer 1 water concentrations are ordered", + "target_and_save": { + "variables": { + "wilting_point_water_concentration": "soil_2.soil_layers.1.wilting_point_water_concentration", + "field_capacity_water_concentration": "soil_2.soil_layers.1.field_capacity_water_concentration", + "saturation_point_water_concentration": "soil_2.soil_layers.1.saturation_point_water_concentration" + } + }, + "rules": [ + { + "left_hand": { + "ordered_variables": ["field_capacity_water_concentration"] + }, + "right_hand": { + "ordered_variables": ["wilting_point_water_concentration"] + }, + "relationship": "greater_or_equal_to" + }, + { + "left_hand": { + "ordered_variables": ["saturation_point_water_concentration"] + }, + "right_hand": { + "ordered_variables": ["field_capacity_water_concentration"] + }, + "relationship": "greater_or_equal_to" + } + ] + }, + { + "description": "soil_2 layer 2 water concentrations are ordered", + "target_and_save": { + "variables": { + "wilting_point_water_concentration": "soil_2.soil_layers.2.wilting_point_water_concentration", + "field_capacity_water_concentration": "soil_2.soil_layers.2.field_capacity_water_concentration", + "saturation_point_water_concentration": "soil_2.soil_layers.2.saturation_point_water_concentration" + } + }, + "rules": [ + { + "left_hand": { + "ordered_variables": ["field_capacity_water_concentration"] + }, + "right_hand": { + "ordered_variables": ["wilting_point_water_concentration"] + }, + "relationship": "greater_or_equal_to" + }, + { + "left_hand": { + "ordered_variables": ["saturation_point_water_concentration"] + }, + "right_hand": { + "ordered_variables": ["field_capacity_water_concentration"] + }, + "relationship": "greater_or_equal_to" + } + ] + }, + { + "description": "soil_2 layer 3 water concentrations are ordered", + "target_and_save": { + "variables": { + "wilting_point_water_concentration": "soil_2.soil_layers.3.wilting_point_water_concentration", + "field_capacity_water_concentration": "soil_2.soil_layers.3.field_capacity_water_concentration", + "saturation_point_water_concentration": "soil_2.soil_layers.3.saturation_point_water_concentration" + } + }, + "rules": [ + { + "left_hand": { + "ordered_variables": ["field_capacity_water_concentration"] + }, + "right_hand": { + "ordered_variables": ["wilting_point_water_concentration"] + }, + "relationship": "greater_or_equal_to" + }, + { + "left_hand": { + "ordered_variables": ["saturation_point_water_concentration"] + }, + "right_hand": { + "ordered_variables": ["field_capacity_water_concentration"] + }, + "relationship": "greater_or_equal_to" + } + ] } ] } diff --git a/input/metadata/cross_validation/field_cross_validation.json b/input/metadata/cross_validation/field_cross_validation.json deleted file mode 100644 index 67025d625d..0000000000 --- a/input/metadata/cross_validation/field_cross_validation.json +++ /dev/null @@ -1,350 +0,0 @@ -{ - "cross-validation": [ - { - "description": "field_1 watering amount must be zero when watering interval is zero", - "target_and_save": { - "variables": { - "watering_amount_in_liters": "field_1.watering_amount_in_liters", - "watering_interval": "field_1.watering_interval" - }, - "constants": { - "zero": 0 - } - }, - "apply_when": [ - { - "left_hand": { - "ordered_variables": ["watering_interval"] - }, - "right_hand": { - "ordered_variables": ["zero"] - }, - "relationship": "equal" - } - ], - "rules": [ - { - "left_hand": { - "ordered_variables": ["watering_amount_in_liters"] - }, - "right_hand": { - "ordered_variables": ["zero"] - }, - "relationship": "equal" - } - ] - }, - { - "description": "field_1 watering interval must be greater than zero when watering amount is positive", - "target_and_save": { - "variables": { - "watering_amount_in_liters": "field_1.watering_amount_in_liters", - "watering_interval": "field_1.watering_interval" - }, - "constants": { - "zero": 0 - } - }, - "apply_when": [ - { - "left_hand": { - "ordered_variables": ["watering_amount_in_liters"] - }, - "right_hand": { - "ordered_variables": ["zero"] - }, - "relationship": "greater" - } - ], - "rules": [ - { - "left_hand": { - "ordered_variables": ["watering_interval"] - }, - "right_hand": { - "ordered_variables": ["zero"] - }, - "relationship": "greater" - } - ] - }, - { - "description": "field_2 watering amount must be zero when watering interval is zero", - "target_and_save": { - "variables": { - "watering_amount_in_liters": "field_2.watering_amount_in_liters", - "watering_interval": "field_2.watering_interval" - }, - "constants": { - "zero": 0 - } - }, - "apply_when": [ - { - "left_hand": { - "ordered_variables": ["watering_interval"] - }, - "right_hand": { - "ordered_variables": ["zero"] - }, - "relationship": "equal" - } - ], - "rules": [ - { - "left_hand": { - "ordered_variables": ["watering_amount_in_liters"] - }, - "right_hand": { - "ordered_variables": ["zero"] - }, - "relationship": "equal" - } - ] - }, - { - "description": "field_2 watering interval must be greater than zero when watering amount is positive", - "target_and_save": { - "variables": { - "watering_amount_in_liters": "field_2.watering_amount_in_liters", - "watering_interval": "field_2.watering_interval" - }, - "constants": { - "zero": 0 - } - }, - "apply_when": [ - { - "left_hand": { - "ordered_variables": ["watering_amount_in_liters"] - }, - "right_hand": { - "ordered_variables": ["zero"] - }, - "relationship": "greater" - } - ], - "rules": [ - { - "left_hand": { - "ordered_variables": ["watering_interval"] - }, - "right_hand": { - "ordered_variables": ["zero"] - }, - "relationship": "greater" - } - ] - }, - { - "description": "soil_1 layer 0 water concentrations are ordered", - "target_and_save": { - "variables": { - "wilting_point_water_concentration": "soil_1.soil_layers.0.wilting_point_water_concentration", - "field_capacity_water_concentration": "soil_1.soil_layers.0.field_capacity_water_concentration", - "saturation_point_water_concentration": "soil_1.soil_layers.0.saturation_point_water_concentration" - } - }, - "rules": [ - { - "left_hand": { - "ordered_variables": ["field_capacity_water_concentration"] - }, - "right_hand": { - "ordered_variables": ["wilting_point_water_concentration"] - }, - "relationship": "greater_or_equal_to" - }, - { - "left_hand": { - "ordered_variables": ["saturation_point_water_concentration"] - }, - "right_hand": { - "ordered_variables": ["field_capacity_water_concentration"] - }, - "relationship": "greater_or_equal_to" - } - ] - }, - { - "description": "soil_1 layer 1 water concentrations are ordered", - "target_and_save": { - "variables": { - "wilting_point_water_concentration": "soil_1.soil_layers.1.wilting_point_water_concentration", - "field_capacity_water_concentration": "soil_1.soil_layers.1.field_capacity_water_concentration", - "saturation_point_water_concentration": "soil_1.soil_layers.1.saturation_point_water_concentration" - } - }, - "rules": [ - { - "left_hand": { - "ordered_variables": ["field_capacity_water_concentration"] - }, - "right_hand": { - "ordered_variables": ["wilting_point_water_concentration"] - }, - "relationship": "greater_or_equal_to" - }, - { - "left_hand": { - "ordered_variables": ["saturation_point_water_concentration"] - }, - "right_hand": { - "ordered_variables": ["field_capacity_water_concentration"] - }, - "relationship": "greater_or_equal_to" - } - ] - }, - { - "description": "soil_1 layer 2 water concentrations are ordered", - "target_and_save": { - "variables": { - "wilting_point_water_concentration": "soil_1.soil_layers.2.wilting_point_water_concentration", - "field_capacity_water_concentration": "soil_1.soil_layers.2.field_capacity_water_concentration", - "saturation_point_water_concentration": "soil_1.soil_layers.2.saturation_point_water_concentration" - } - }, - "rules": [ - { - "left_hand": { - "ordered_variables": ["field_capacity_water_concentration"] - }, - "right_hand": { - "ordered_variables": ["wilting_point_water_concentration"] - }, - "relationship": "greater_or_equal_to" - }, - { - "left_hand": { - "ordered_variables": ["saturation_point_water_concentration"] - }, - "right_hand": { - "ordered_variables": ["field_capacity_water_concentration"] - }, - "relationship": "greater_or_equal_to" - } - ] - }, - { - "description": "soil_2 layer 0 water concentrations are ordered", - "target_and_save": { - "variables": { - "wilting_point_water_concentration": "soil_2.soil_layers.0.wilting_point_water_concentration", - "field_capacity_water_concentration": "soil_2.soil_layers.0.field_capacity_water_concentration", - "saturation_point_water_concentration": "soil_2.soil_layers.0.saturation_point_water_concentration" - } - }, - "rules": [ - { - "left_hand": { - "ordered_variables": ["field_capacity_water_concentration"] - }, - "right_hand": { - "ordered_variables": ["wilting_point_water_concentration"] - }, - "relationship": "greater_or_equal_to" - }, - { - "left_hand": { - "ordered_variables": ["saturation_point_water_concentration"] - }, - "right_hand": { - "ordered_variables": ["field_capacity_water_concentration"] - }, - "relationship": "greater_or_equal_to" - } - ] - }, - { - "description": "soil_2 layer 1 water concentrations are ordered", - "target_and_save": { - "variables": { - "wilting_point_water_concentration": "soil_2.soil_layers.1.wilting_point_water_concentration", - "field_capacity_water_concentration": "soil_2.soil_layers.1.field_capacity_water_concentration", - "saturation_point_water_concentration": "soil_2.soil_layers.1.saturation_point_water_concentration" - } - }, - "rules": [ - { - "left_hand": { - "ordered_variables": ["field_capacity_water_concentration"] - }, - "right_hand": { - "ordered_variables": ["wilting_point_water_concentration"] - }, - "relationship": "greater_or_equal_to" - }, - { - "left_hand": { - "ordered_variables": ["saturation_point_water_concentration"] - }, - "right_hand": { - "ordered_variables": ["field_capacity_water_concentration"] - }, - "relationship": "greater_or_equal_to" - } - ] - }, - { - "description": "soil_2 layer 2 water concentrations are ordered", - "target_and_save": { - "variables": { - "wilting_point_water_concentration": "soil_2.soil_layers.2.wilting_point_water_concentration", - "field_capacity_water_concentration": "soil_2.soil_layers.2.field_capacity_water_concentration", - "saturation_point_water_concentration": "soil_2.soil_layers.2.saturation_point_water_concentration" - } - }, - "rules": [ - { - "left_hand": { - "ordered_variables": ["field_capacity_water_concentration"] - }, - "right_hand": { - "ordered_variables": ["wilting_point_water_concentration"] - }, - "relationship": "greater_or_equal_to" - }, - { - "left_hand": { - "ordered_variables": ["saturation_point_water_concentration"] - }, - "right_hand": { - "ordered_variables": ["field_capacity_water_concentration"] - }, - "relationship": "greater_or_equal_to" - } - ] - }, - { - "description": "soil_2 layer 3 water concentrations are ordered", - "target_and_save": { - "variables": { - "wilting_point_water_concentration": "soil_2.soil_layers.3.wilting_point_water_concentration", - "field_capacity_water_concentration": "soil_2.soil_layers.3.field_capacity_water_concentration", - "saturation_point_water_concentration": "soil_2.soil_layers.3.saturation_point_water_concentration" - } - }, - "rules": [ - { - "left_hand": { - "ordered_variables": ["field_capacity_water_concentration"] - }, - "right_hand": { - "ordered_variables": ["wilting_point_water_concentration"] - }, - "relationship": "greater_or_equal_to" - }, - { - "left_hand": { - "ordered_variables": ["saturation_point_water_concentration"] - }, - "right_hand": { - "ordered_variables": ["field_capacity_water_concentration"] - }, - "relationship": "greater_or_equal_to" - } - ] - } - ] -} diff --git a/tests/test_data_validator.py b/tests/test_data_validator.py index d9f5060605..0d14f06595 100644 --- a/tests/test_data_validator.py +++ b/tests/test_data_validator.py @@ -3127,9 +3127,10 @@ def test_evaluate_regex_fullmatch(text: str, pattern: str, expected: bool) -> No @pytest.mark.parametrize( "left,right,expected", [ - ([1], [2], True), - ([1, 2], [3], False), ([], [], True), + ([1, 2, 3, 4], [3, 2, 1, 4], True), # longer lists + ([1, 2, 3, 4], [1, 1, 5], False), # left longer than right + ([1, 1, 5], [1, 2, 3, 4], False), # right longer than left ], ) @pytest.mark.parametrize("eager_termination", [True, False]) From c1afdf02ed0404017eb32aedd7db787a7825ccde Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 7 Apr 2026 10:01:47 +0000 Subject: [PATCH 20/27] Apply Black Formatting --- tests/test_data_validator.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_data_validator.py b/tests/test_data_validator.py index 0d14f06595..ee52b66398 100644 --- a/tests/test_data_validator.py +++ b/tests/test_data_validator.py @@ -3128,9 +3128,9 @@ def test_evaluate_regex_fullmatch(text: str, pattern: str, expected: bool) -> No "left,right,expected", [ ([], [], True), - ([1, 2, 3, 4], [3, 2, 1, 4], True), # longer lists - ([1, 2, 3, 4], [1, 1, 5], False), # left longer than right - ([1, 1, 5], [1, 2, 3, 4], False), # right longer than left + ([1, 2, 3, 4], [3, 2, 1, 4], True), # longer lists + ([1, 2, 3, 4], [1, 1, 5], False), # left longer than right + ([1, 1, 5], [1, 2, 3, 4], False), # right longer than left ], ) @pytest.mark.parametrize("eager_termination", [True, False]) From 205a6466e26be0118572e556d29100c48befd091 Mon Sep 17 00:00:00 2001 From: matthew7838 Date: Tue, 7 Apr 2026 10:05:45 +0000 Subject: [PATCH 21/27] Update badges on README --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3e1485b21f..45655ee08b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ [![Flake8](https://img.shields.io/badge/Flake8-passed-brightgreen)](https://github.com/RuminantFarmSystems/MASM/actions/workflows/combined_format_lint_test_mypy.yml) -[![Pytest](https://img.shields.io/badge/Pytest-passed-brightgreen)](https://github.com/RuminantFarmSystems/MASM/actions/workflows/combined_format_lint_test_mypy.yml) -[![Coverage](https://img.shields.io/badge/Coverage-99%25-brightgreen)](https://github.com/RuminantFarmSystems/MASM/actions/workflows/combined_format_lint_test_mypy.yml) +[![Pytest](https://img.shields.io/badge/Pytest-failed-red)](https://github.com/RuminantFarmSystems/MASM/actions/workflows/combined_format_lint_test_mypy.yml) +[![Coverage](https://img.shields.io/badge/Coverage-%25-red)](https://github.com/RuminantFarmSystems/MASM/actions/workflows/combined_format_lint_test_mypy.yml) [![Mypy](https://img.shields.io/badge/Mypy-1191%20errors-red)](https://github.com/RuminantFarmSystems/MASM/actions/workflows/combined_format_lint_test_mypy.yml) From e946297d0df1efc3780c12940a33d4732111b3d2 Mon Sep 17 00:00:00 2001 From: Matthew Liu Date: Tue, 7 Apr 2026 22:31:57 +0900 Subject: [PATCH 22/27] Removed unnecessary test --- tests/test_data_validator.py | 37 ------------------------------------ 1 file changed, 37 deletions(-) diff --git a/tests/test_data_validator.py b/tests/test_data_validator.py index ee52b66398..9a87bbd3ab 100644 --- a/tests/test_data_validator.py +++ b/tests/test_data_validator.py @@ -3408,40 +3408,3 @@ def test_log_missing_condition_clause_field_only() -> None: e = v._event_logs[0] assert e["error"] == "Missing required condition clause field" assert e["message"] == "Missing the left hand field in condition clause." - - -def test_crop_and_soil_cross_validation_includes_crop_schedule_length_rules() -> None: - """The combined cross-validation file should enforce planting/harvest day-year length equality.""" - cv_path = Path("input/metadata/cross_validation/crop_and_soil_cross_validation.json") - - with cv_path.open() as file: - data = json.load(file) - - blocks = data["cross-validation"] - schedule_blocks = [block for block in blocks if "crop schedules have equal planting" in block["description"]] - - assert len(schedule_blocks) == 8 - - expected_paths = { - "Corn-Alf-Silage.crop_schedules.0", - "Corn-Alf-Silage.crop_schedules.1", - "Corn-Alf-Silage.crop_schedules.2", - "Corn-Alf-Silage.crop_schedules.3", - "CornGrain-AlfHay.crop_schedules.0", - "CornGrain-AlfHay.crop_schedules.1", - "CornGrain-AlfHay.crop_schedules.2", - "CornGrain-AlfHay.crop_schedules.3", - } - - actual_paths = { - block["target_and_save"]["variables"]["planting_years"].rsplit(".planting_years", 1)[0] - for block in schedule_blocks - } - assert actual_paths == expected_paths - - for block in schedule_blocks: - assert [rule["relationship"] for rule in block["rules"]] == ["is_equal_length", "is_equal_length"] - assert block["rules"][0]["left_hand"]["ordered_variables"] == ["planting_years"] - assert block["rules"][0]["right_hand"]["ordered_variables"] == ["planting_days"] - assert block["rules"][1]["left_hand"]["ordered_variables"] == ["harvest_years"] - assert block["rules"][1]["right_hand"]["ordered_variables"] == ["harvest_days"] From 400b4bf7cdcb2f841abeda328da654ea1c98ba11 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 7 Apr 2026 13:33:42 +0000 Subject: [PATCH 23/27] Apply Black Formatting From 124f810d44eba49167bfcbfb6868261e7508e777 Mon Sep 17 00:00:00 2001 From: matthew7838 Date: Tue, 7 Apr 2026 13:38:07 +0000 Subject: [PATCH 24/27] Update badges on README --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 45655ee08b..bee0aa5174 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -[![Flake8](https://img.shields.io/badge/Flake8-passed-brightgreen)](https://github.com/RuminantFarmSystems/MASM/actions/workflows/combined_format_lint_test_mypy.yml) -[![Pytest](https://img.shields.io/badge/Pytest-failed-red)](https://github.com/RuminantFarmSystems/MASM/actions/workflows/combined_format_lint_test_mypy.yml) -[![Coverage](https://img.shields.io/badge/Coverage-%25-red)](https://github.com/RuminantFarmSystems/MASM/actions/workflows/combined_format_lint_test_mypy.yml) +[![Flake8](https://img.shields.io/badge/Flake8-failed-red)](https://github.com/RuminantFarmSystems/MASM/actions/workflows/combined_format_lint_test_mypy.yml) +[![Pytest](https://img.shields.io/badge/Pytest-passed-brightgreen)](https://github.com/RuminantFarmSystems/MASM/actions/workflows/combined_format_lint_test_mypy.yml) +[![Coverage](https://img.shields.io/badge/Coverage-99%25-brightgreen)](https://github.com/RuminantFarmSystems/MASM/actions/workflows/combined_format_lint_test_mypy.yml) [![Mypy](https://img.shields.io/badge/Mypy-1191%20errors-red)](https://github.com/RuminantFarmSystems/MASM/actions/workflows/combined_format_lint_test_mypy.yml) From 3586492a181f3c008a165e7398612bb48452e147 Mon Sep 17 00:00:00 2001 From: Matthew Liu Date: Tue, 7 Apr 2026 22:43:23 +0900 Subject: [PATCH 25/27] Removed flake8 errors --- tests/test_data_validator.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_data_validator.py b/tests/test_data_validator.py index 9a87bbd3ab..f4685adec6 100644 --- a/tests/test_data_validator.py +++ b/tests/test_data_validator.py @@ -1,4 +1,3 @@ -import json from pathlib import Path from typing import Dict, Any, List, Union, Optional, Type From 516e03afa8e44c1abf07c52ecd74ec48205098c3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 7 Apr 2026 13:45:23 +0000 Subject: [PATCH 26/27] Apply Black Formatting From f691b20caccb7b0658ef9d8379c768ed2cf85da0 Mon Sep 17 00:00:00 2001 From: matthew7838 Date: Tue, 7 Apr 2026 13:50:03 +0000 Subject: [PATCH 27/27] Update badges on README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bee0aa5174..3e1485b21f 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![Flake8](https://img.shields.io/badge/Flake8-failed-red)](https://github.com/RuminantFarmSystems/MASM/actions/workflows/combined_format_lint_test_mypy.yml) +[![Flake8](https://img.shields.io/badge/Flake8-passed-brightgreen)](https://github.com/RuminantFarmSystems/MASM/actions/workflows/combined_format_lint_test_mypy.yml) [![Pytest](https://img.shields.io/badge/Pytest-passed-brightgreen)](https://github.com/RuminantFarmSystems/MASM/actions/workflows/combined_format_lint_test_mypy.yml) [![Coverage](https://img.shields.io/badge/Coverage-99%25-brightgreen)](https://github.com/RuminantFarmSystems/MASM/actions/workflows/combined_format_lint_test_mypy.yml) [![Mypy](https://img.shields.io/badge/Mypy-1191%20errors-red)](https://github.com/RuminantFarmSystems/MASM/actions/workflows/combined_format_lint_test_mypy.yml)