From 698ebf039d238cb401b2c7aaab559bf0f38e5225 Mon Sep 17 00:00:00 2001 From: Matthew Liu Date: Thu, 23 Jul 2026 23:34:53 +0900 Subject: [PATCH 01/11] Implementation --- RUFAS/biophysical/field/crop/crop.py | 7 +- RUFAS/biophysical/field/crop/crop_data.py | 4 ++ .../field/crop/crop_data_factory.py | 3 +- .../biophysical/field/crop/crop_management.py | 14 ++-- RUFAS/biophysical/field/field/field.py | 4 +- RUFAS/input/metadata/properties/default.json | 5 ++ changelog_WIP.md | 1 + .../default_crop_configs.json | 38 ++++++++++ .../crop_tests/test_crop_data.py | 5 ++ .../crop_tests/test_crop_data_factory.py | 48 +++++++++++++ .../crop_tests/test_crop_management.py | 70 +++++++++++++++++++ 11 files changed, 188 insertions(+), 11 deletions(-) diff --git a/RUFAS/biophysical/field/crop/crop.py b/RUFAS/biophysical/field/crop/crop.py index 224ab3bb05..a35798845c 100644 --- a/RUFAS/biophysical/field/crop/crop.py +++ b/RUFAS/biophysical/field/crop/crop.py @@ -233,7 +233,7 @@ def manage_crop_harvest( field_size: float, time: RufasTime, soil_data: SoilData, - ) -> HarvestedCrop: + ) -> HarvestedCrop | None: """Wrapper function for the Crop's CropManagement harvesting operation. Parameters @@ -251,8 +251,9 @@ def manage_crop_harvest( Returns ------- - HarvestedCrop - A harvested crop data structure. + HarvestedCrop | None + A harvested crop data structure, or ``None`` when the operation collects no yield (a kill-only operation + or a crop configured to deposit all residue into the field). """ return self._crop_management.manage_harvest(harvest_operation, field_name, field_size, time, soil_data) diff --git a/RUFAS/biophysical/field/crop/crop_data.py b/RUFAS/biophysical/field/crop/crop_data.py index 75bb7fe35e..76302ba924 100644 --- a/RUFAS/biophysical/field/crop/crop_data.py +++ b/RUFAS/biophysical/field/crop/crop_data.py @@ -69,6 +69,9 @@ class CropData: grain. use_heat_scheduling : bool If heat unit scheduling is used for harvesting. + deposit_all_residue_at_harvest : bool, default False + If True, harvest operations collect no yield and deposit all cut biomass into the field as residue instead of + removing it as harvested yield. harvest_heat_fraction : float Fraction of potential heat units for optimal growth stage for harvest. optimal_harvest_index : float @@ -235,6 +238,7 @@ class CropData: planting_day: int = 100 lignin_dry_matter_percentage: float = 1.518 use_heat_scheduling: bool = False + deposit_all_residue_at_harvest: bool = False harvest_heat_fraction: float = 1.10 optimal_harvest_index: float = 0.5 minimum_harvest_index: float = 0.3 diff --git a/RUFAS/biophysical/field/crop/crop_data_factory.py b/RUFAS/biophysical/field/crop/crop_data_factory.py index f7dd033b11..06c0bc81ed 100644 --- a/RUFAS/biophysical/field/crop/crop_data_factory.py +++ b/RUFAS/biophysical/field/crop/crop_data_factory.py @@ -1,4 +1,4 @@ -from typing import Any, TypedDict +from typing import Any, NotRequired, TypedDict from RUFAS.input_manager import InputManager from RUFAS.output_manager import OutputManager @@ -15,6 +15,7 @@ class CropConfiguration(TypedDict): name: str plant_category: PlantCategory is_nitrogen_fixer: bool + deposit_all_residue_at_harvest: NotRequired[bool] minimum_temperature: float optimal_temperature: float potential_heat_units: float diff --git a/RUFAS/biophysical/field/crop/crop_management.py b/RUFAS/biophysical/field/crop/crop_management.py index 08810fe7b8..f405d17fd2 100644 --- a/RUFAS/biophysical/field/crop/crop_management.py +++ b/RUFAS/biophysical/field/crop/crop_management.py @@ -118,7 +118,7 @@ def manage_harvest( field_size: float, time: RufasTime, soil_data: SoilData, - ) -> HarvestedCrop: + ) -> HarvestedCrop | None: """ Executes the harvest operation passed on the crop that contains this module. @@ -137,17 +137,21 @@ def manage_harvest( Returns ------- - HarvestedCrop + HarvestedCrop | None The harvested crop data structure containing mass and nutritional information associated with the - harvest's yield. + harvest's yield. This is ``None`` when no yield is collected, i.e. for a kill-only operation or for a crop + configured to deposit all residue into the field (see ``CropData.deposit_all_residue_at_harvest``). """ self.determine_harvest_index() harvested_crop = None if harvest_operation in (HarvestOperation.HARVEST_KILL, HarvestOperation.HARVEST_ONLY): - self.cut_crop(collected_fraction=self.harvest_efficiency) - harvested_crop = self._get_harvested_crop(time, field_size, field_name) + if self.data.deposit_all_residue_at_harvest: + self.cut_crop(collected_fraction=0.0) + else: + self.cut_crop(collected_fraction=self.harvest_efficiency) + harvested_crop = self._get_harvested_crop(time, field_size, field_name) if harvest_operation in (HarvestOperation.KILL_ONLY, HarvestOperation.HARVEST_KILL): self.kill() diff --git a/RUFAS/biophysical/field/field/field.py b/RUFAS/biophysical/field/field/field.py index 1991fd1416..bc0c6bbf47 100644 --- a/RUFAS/biophysical/field/field/field.py +++ b/RUFAS/biophysical/field/field/field.py @@ -1253,7 +1253,7 @@ def _harvest_heat_scheduled_crops(self, rainfall: float, time: RufasTime) -> lis harvested_crops = [] for crop in self.crops: if crop.should_harvest_based_on_heat(): - harvested_crop: HarvestedCrop = crop.manage_crop_harvest( + harvested_crop: HarvestedCrop | None = crop.manage_crop_harvest( HarvestOperation.HARVEST_ONLY, self.field_data.name, self.field_data.field_size, @@ -1447,7 +1447,7 @@ def _harvest_crop( harvested_crops = [] for crop in crops_to_be_harvested: - harvested_crop: HarvestedCrop = crop.manage_crop_harvest( + harvested_crop: HarvestedCrop | None = crop.manage_crop_harvest( harvest_operation, self.field_data.name, self.field_data.field_size, diff --git a/RUFAS/input/metadata/properties/default.json b/RUFAS/input/metadata/properties/default.json index 20974295b0..84b3976cef 100644 --- a/RUFAS/input/metadata/properties/default.json +++ b/RUFAS/input/metadata/properties/default.json @@ -2647,6 +2647,11 @@ "type": "bool", "description": "Whether the crop configuration is a nitrogen fixer." }, + "deposit_all_residue_at_harvest": { + "type": "bool", + "description": "Whether harvest operations deposit all cut biomass into the field as residue instead of removing it as harvested yield. Set to true for grazed crops such as pastures, where forage removal is handled by grazing rather than by the harvest operation.", + "default": false + }, "minimum_temperature": { "type": "number", "description": "Minimum temperature for plant growth (Celsius)." diff --git a/changelog_WIP.md b/changelog_WIP.md index b4e785a034..b0480ca71b 100644 --- a/changelog_WIP.md +++ b/changelog_WIP.md @@ -105,3 +105,4 @@ This **WIP Changelog** records development changes in progress and not yet inclu - [3148](https://github.com/RuminantFarmSystems/RuFaS/pull/3148) - [minor change] [TractorDataset] [NoInputChange] [NoOutputChange] Updates and improves tractor dataset input validation. - [2972](https://github.com/RuminantFarmSystems/RuFaS/pull/2972) - [minor change] [OutputManager] [NoInputChange] [NoOutputChange] Adds an `is_daily_variable` info_map flag so OutputManager can distinguish variables reported daily from those reported on another cadence, and renames `_variables_usage_counter` to `_filtered_variable_key_counter` to clarify it counts filter selections rather than reports. - [3128](https://github.com/RuminantFarmSystems/RuFaS/pull/3128) - [minor change] [Animal] [NoInputChange] [NoOutputChange] Clamps `Growth._calculate_pregnant_heifer_target_daily_growth()` to `AnimalModuleConstants.MINIMUM_HEIFER_DAILY_GROWTH_RATE`, preventing negative target daily gain for pregnant heifers near or at term. +- [PR_NUMBER](https://github.com/RuminantFarmSystems/RuFaS/pull/PR_NUMBER) - [minor change] [Crop and Soil] [Properties] [InputChange] [NoOutputChange] Adds a `pasture` crop configuration (modeled after tall fescue) for grazing and a `deposit_all_residue_at_harvest` crop-configuration flag; when the flag is set, harvest operations collect no yield and deposit all cut biomass into the field as residue rather than removing it as harvested yield. Implements #3131. diff --git a/input/data/crop_configurations/default_crop_configs.json b/input/data/crop_configurations/default_crop_configs.json index e3100ef048..206035b367 100644 --- a/input/data/crop_configurations/default_crop_configs.json +++ b/input/data/crop_configurations/default_crop_configs.json @@ -518,6 +518,44 @@ "yield_nitrogen_fraction": 0.021248, "yield_phosphorus_fraction": 0.00281 }, + { + "name": "pasture", + "plant_category": "perennial", + "is_nitrogen_fixer": false, + "deposit_all_residue_at_harvest": true, + "minimum_temperature": 0.0, + "optimal_temperature": 15.0, + "potential_heat_units": 800.0, + "max_leaf_area_index": 4.0, + "first_heat_fraction_point": 0.15, + "first_leaf_fraction_point": 0.01, + "second_heat_fraction_point": 0.50, + "second_leaf_fraction_point": 0.95, + "senescent_heat_fraction": 0.80, + "light_use_efficiency": 30.0, + "emergence_nitrogen_fraction": 0.0560, + "half_mature_nitrogen_fraction": 0.0210, + "mature_nitrogen_fraction": 0.0120, + "emergence_phosphorus_fraction": 0.0099, + "half_mature_phosphorus_fraction": 0.0022, + "mature_phosphorus_fraction": 0.0019, + "max_root_depth": 2000.0, + "root_distribution_param_da": 137.0, + "root_distribution_param_c": -1.144, + "optimal_harvest_index": 0.85, + "minimum_harvest_index": 0.37, + "dry_matter_percentage": 88.331, + "lignin_dry_matter_percentage": 4.167, + "crude_protein_percent_at_harvest": 13.28, + "non_protein_nitrogen_at_harvest": 4.056, + "starch_at_harvest": 2.217, + "adf_at_harvest": 35.524, + "ndf_at_harvest": 58.007, + "sugar_at_harvest": 15.235, + "ash_at_harvest": 8.567, + "yield_nitrogen_fraction": 0.021248, + "yield_phosphorus_fraction": 0.00281 + }, { "name": "triticale_grain", "plant_category": "cool_annual", diff --git a/tests/test_biophysical/test_crop_soil_field/crop_tests/test_crop_data.py b/tests/test_biophysical/test_crop_soil_field/crop_tests/test_crop_data.py index 793a07d2d7..859ea68931 100644 --- a/tests/test_biophysical/test_crop_soil_field/crop_tests/test_crop_data.py +++ b/tests/test_biophysical/test_crop_soil_field/crop_tests/test_crop_data.py @@ -12,6 +12,11 @@ def mock_crop_data() -> CropData: return CropData(**SAMPLE_CROP_CONFIGURATION) +def test_deposit_all_residue_at_harvest_defaults_false(mock_crop_data: CropData) -> None: + """The deposit-all-residue flag defaults to False so ordinary crops are unaffected.""" + assert mock_crop_data.deposit_all_residue_at_harvest is False + + @pytest.mark.parametrize("frac,expect", [(0, False), (0.5, False), (1, True), (1.5, True)]) def test_is_mature_property(mock_crop_data: CropData, frac: float, expect: bool) -> None: """Check that the is_mature property is properly assigning maturity by heat fraction.""" diff --git a/tests/test_biophysical/test_crop_soil_field/crop_tests/test_crop_data_factory.py b/tests/test_biophysical/test_crop_soil_field/crop_tests/test_crop_data_factory.py index a4df3c4d40..184aa632fd 100644 --- a/tests/test_biophysical/test_crop_soil_field/crop_tests/test_crop_data_factory.py +++ b/tests/test_biophysical/test_crop_soil_field/crop_tests/test_crop_data_factory.py @@ -292,6 +292,54 @@ def test_create_crop_data() -> None: assert actual_value == expected_value +def test_create_crop_data_preserves_deposit_all_residue_flag() -> None: + """Test that a crop configuration's deposit_all_residue_at_harvest flag is carried into the created CropData.""" + CropDataFactory._crop_configurations = { + "pasture": CropConfiguration( + name="pasture", + plant_category=PlantCategory.PERENNIAL, + is_nitrogen_fixer=False, + deposit_all_residue_at_harvest=True, + minimum_temperature=0.0, + optimal_temperature=15.0, + potential_heat_units=800.0, + max_leaf_area_index=4.0, + first_heat_fraction_point=0.15, + first_leaf_fraction_point=0.01, + second_heat_fraction_point=0.50, + second_leaf_fraction_point=0.95, + senescent_heat_fraction=0.80, + light_use_efficiency=30.0, + emergence_nitrogen_fraction=0.0560, + half_mature_nitrogen_fraction=0.0210, + mature_nitrogen_fraction=0.0120, + emergence_phosphorus_fraction=0.0099, + half_mature_phosphorus_fraction=0.0022, + mature_phosphorus_fraction=0.0019, + max_root_depth=2000.0, + root_distribution_param_da=137.0, + root_distribution_param_c=-1.144, + optimal_harvest_index=0.85, + minimum_harvest_index=0.37, + dry_matter_percentage=88.331, + lignin_dry_matter_percentage=4.167, + crude_protein_percent_at_harvest=13.28, + non_protein_nitrogen_at_harvest=4.056, + starch_at_harvest=2.217, + adf_at_harvest=35.524, + ndf_at_harvest=58.007, + sugar_at_harvest=15.235, + ash_at_harvest=8.567, + yield_nitrogen_fraction=0.021248, + yield_phosphorus_fraction=0.00281, + ) + } + + actual = CropDataFactory.create_crop_data("pasture") + + assert actual.deposit_all_residue_at_harvest is True + + def test_crop_crop_data_error() -> None: """Test that CropDataFactory raises an error when trying to create an unavailable configuration.""" with pytest.raises(ValueError): diff --git a/tests/test_biophysical/test_crop_soil_field/crop_tests/test_crop_management.py b/tests/test_biophysical/test_crop_soil_field/crop_tests/test_crop_management.py index bce3eed594..5b44cac083 100644 --- a/tests/test_biophysical/test_crop_soil_field/crop_tests/test_crop_management.py +++ b/tests/test_biophysical/test_crop_soil_field/crop_tests/test_crop_management.py @@ -234,6 +234,76 @@ def test_manage_harvest( assert actual is None +@pytest.mark.parametrize( + "harvest_op,expect_kill", + [ + (HarvestOperation.HARVEST_ONLY, False), + (HarvestOperation.HARVEST_KILL, True), + ], +) +def test_manage_harvest_deposit_all_residue( + mocker: MockerFixture, + crop_manager: CropManagement, + mock_time: RufasTime, + harvest_op: HarvestOperation, + expect_kill: bool, +) -> None: + """When a crop is flagged to deposit all residue (e.g. a grazed pasture), a harvest collects no yield, produces no + harvested crop for feed storage, and cuts the crop with a collected fraction of zero so all cut biomass is left in + the field as residue.""" + crop_manager.data.deposit_all_residue_at_harvest = True + field_size = 3.0 + soil_data = SoilData(field_size=field_size) + + mocker.patch.object(crop_manager, "determine_harvest_index") + cut_crop = mocker.patch.object(crop_manager, "cut_crop") + get_crop = mocker.patch.object(crop_manager, "_get_harvested_crop") + kill = mocker.patch.object(crop_manager, "kill", wraps=crop_manager.kill) + record_yield = mocker.patch.object(crop_manager, "_record_yield") + transfer_residue = mocker.patch.object(crop_manager, "_transfer_residue") + + actual = crop_manager.manage_harvest(harvest_op, "pasture_field", field_size, mock_time, soil_data) + + # No yield is collected and no harvested crop is produced for feed storage. + cut_crop.assert_called_once_with(collected_fraction=0.0) + get_crop.assert_not_called() + assert actual is None + + kill.assert_called_once() if expect_kill else kill.assert_not_called() + record_yield.assert_called_once() + transfer_residue.assert_called_once_with(soil_data, expect_kill) + + +def test_manage_harvest_deposit_all_residue_leaves_biomass_in_field( + mocker: MockerFixture, mock_crop_data: CropData, mock_time: RufasTime +) -> None: + """End-to-end check that a harvest of a deposit-all-residue crop removes no yield and instead deposits the cut + biomass into the field's surface soil layer as residue.""" + mock_crop_data.deposit_all_residue_at_harvest = True + mock_crop_data.biomass = 5000.0 + mock_crop_data.above_ground_biomass = 4000.0 + mock_crop_data.root_biomass = 1000.0 + mock_crop_data.leaf_area_index = 3.0 + crop_manager = CropManagement(crop_data=mock_crop_data) + + field_size = 2.0 + soil_data = SoilData(field_size=field_size) + soil_data.soil_layers[0].plant_residue = 0.0 + + # _record_yield only reports to the OutputManager and is not under test here. + mocker.patch.object(crop_manager, "_record_yield") + + harvested = crop_manager.manage_harvest( + HarvestOperation.HARVEST_ONLY, "pasture_field", field_size, mock_time, soil_data + ) + + assert harvested is None + assert crop_manager.dry_matter_yield_collected == 0.0 + assert crop_manager.wet_yield_collected == 0.0 + # All cut biomass is deposited into the field's surface layer as residue. + assert soil_data.soil_layers[0].plant_residue > 0.0 + + @pytest.mark.parametrize( "efficiency,harvest,override,should_fail", [ From b7ae4870705b7b47c5760b69439553c42f7a161e Mon Sep 17 00:00:00 2001 From: Matthew Liu Date: Fri, 24 Jul 2026 17:19:41 +0900 Subject: [PATCH 02/11] Updated changelog_WIP.md --- changelog_WIP.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog_WIP.md b/changelog_WIP.md index b0480ca71b..658b1f3cd2 100644 --- a/changelog_WIP.md +++ b/changelog_WIP.md @@ -105,4 +105,4 @@ This **WIP Changelog** records development changes in progress and not yet inclu - [3148](https://github.com/RuminantFarmSystems/RuFaS/pull/3148) - [minor change] [TractorDataset] [NoInputChange] [NoOutputChange] Updates and improves tractor dataset input validation. - [2972](https://github.com/RuminantFarmSystems/RuFaS/pull/2972) - [minor change] [OutputManager] [NoInputChange] [NoOutputChange] Adds an `is_daily_variable` info_map flag so OutputManager can distinguish variables reported daily from those reported on another cadence, and renames `_variables_usage_counter` to `_filtered_variable_key_counter` to clarify it counts filter selections rather than reports. - [3128](https://github.com/RuminantFarmSystems/RuFaS/pull/3128) - [minor change] [Animal] [NoInputChange] [NoOutputChange] Clamps `Growth._calculate_pregnant_heifer_target_daily_growth()` to `AnimalModuleConstants.MINIMUM_HEIFER_DAILY_GROWTH_RATE`, preventing negative target daily gain for pregnant heifers near or at term. -- [PR_NUMBER](https://github.com/RuminantFarmSystems/RuFaS/pull/PR_NUMBER) - [minor change] [Crop and Soil] [Properties] [InputChange] [NoOutputChange] Adds a `pasture` crop configuration (modeled after tall fescue) for grazing and a `deposit_all_residue_at_harvest` crop-configuration flag; when the flag is set, harvest operations collect no yield and deposit all cut biomass into the field as residue rather than removing it as harvested yield. Implements #3131. +- [3163](https://github.com/RuminantFarmSystems/RuFaS/pull/3163) - [minor change] [Crop and Soil] [Properties] [InputChange] [NoOutputChange] Adds a `pasture` crop configuration (modeled after tall fescue) for grazing and a `deposit_all_residue_at_harvest` crop-configuration flag. From 6d785a4849dd7730fbd3613498a0d1b6d647fec7 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 24 Jul 2026 08:21:45 +0000 Subject: [PATCH 03/11] Apply Black Formatting From 508437023f305e4ecf8bcffd43baefd8a7b6b08c Mon Sep 17 00:00:00 2001 From: matthew7838 Date: Fri, 24 Jul 2026 08:26:45 +0000 Subject: [PATCH 04/11] Update badges on README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3802776dce..bb9e2481f4 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-1136%20errors-red)](https://github.com/RuminantFarmSystems/MASM/actions/workflows/combined_format_lint_test_mypy.yml) +[![Mypy](https://img.shields.io/badge/Mypy-1137%20errors-red)](https://github.com/RuminantFarmSystems/MASM/actions/workflows/combined_format_lint_test_mypy.yml) # RuFaS: Ruminant Farm Systems From 5f3bc4cd69e4366e83dc1bcf94f443909c02933f Mon Sep 17 00:00:00 2001 From: Matthew Liu Date: Fri, 24 Jul 2026 19:04:25 +0900 Subject: [PATCH 05/11] Removed the extra mypy error --- .../test_crop_soil_field/crop_tests/test_crop_management.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/test_biophysical/test_crop_soil_field/crop_tests/test_crop_management.py b/tests/test_biophysical/test_crop_soil_field/crop_tests/test_crop_management.py index 5b44cac083..97599f9840 100644 --- a/tests/test_biophysical/test_crop_soil_field/crop_tests/test_crop_management.py +++ b/tests/test_biophysical/test_crop_soil_field/crop_tests/test_crop_management.py @@ -288,7 +288,9 @@ def test_manage_harvest_deposit_all_residue_leaves_biomass_in_field( field_size = 2.0 soil_data = SoilData(field_size=field_size) - soil_data.soil_layers[0].plant_residue = 0.0 + assert soil_data.soil_layers is not None + surface_layer = soil_data.soil_layers[0] + surface_layer.plant_residue = 0.0 # _record_yield only reports to the OutputManager and is not under test here. mocker.patch.object(crop_manager, "_record_yield") @@ -301,7 +303,7 @@ def test_manage_harvest_deposit_all_residue_leaves_biomass_in_field( assert crop_manager.dry_matter_yield_collected == 0.0 assert crop_manager.wet_yield_collected == 0.0 # All cut biomass is deposited into the field's surface layer as residue. - assert soil_data.soil_layers[0].plant_residue > 0.0 + assert surface_layer.plant_residue > 0.0 @pytest.mark.parametrize( From cc37ae15bdb74cd636635f1f0d0cf8d5dc523733 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 24 Jul 2026 10:06:40 +0000 Subject: [PATCH 06/11] Apply Black Formatting From c549c8ef47e5fc95642e781662cfe768e26d05c3 Mon Sep 17 00:00:00 2001 From: matthew7838 Date: Fri, 24 Jul 2026 10:11:47 +0000 Subject: [PATCH 07/11] Update badges on README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bb9e2481f4..c378c18f0c 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-1137%20errors-red)](https://github.com/RuminantFarmSystems/MASM/actions/workflows/combined_format_lint_test_mypy.yml) +[![Mypy](https://img.shields.io/badge/Mypy-1135%20errors-red)](https://github.com/RuminantFarmSystems/MASM/actions/workflows/combined_format_lint_test_mypy.yml) # RuFaS: Ruminant Farm Systems From 9c5b3ce17899dc556fbe8fd845a3b5b9d9ff3596 Mon Sep 17 00:00:00 2001 From: Matthew Liu Date: Tue, 28 Jul 2026 12:51:01 +0900 Subject: [PATCH 08/11] Addressed Niko's comment to remove the inline comments on tests --- .../test_crop_soil_field/crop_tests/test_crop_management.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/test_biophysical/test_crop_soil_field/crop_tests/test_crop_management.py b/tests/test_biophysical/test_crop_soil_field/crop_tests/test_crop_management.py index 97599f9840..6a3ceb7346 100644 --- a/tests/test_biophysical/test_crop_soil_field/crop_tests/test_crop_management.py +++ b/tests/test_biophysical/test_crop_soil_field/crop_tests/test_crop_management.py @@ -264,7 +264,6 @@ def test_manage_harvest_deposit_all_residue( actual = crop_manager.manage_harvest(harvest_op, "pasture_field", field_size, mock_time, soil_data) - # No yield is collected and no harvested crop is produced for feed storage. cut_crop.assert_called_once_with(collected_fraction=0.0) get_crop.assert_not_called() assert actual is None @@ -292,7 +291,6 @@ def test_manage_harvest_deposit_all_residue_leaves_biomass_in_field( surface_layer = soil_data.soil_layers[0] surface_layer.plant_residue = 0.0 - # _record_yield only reports to the OutputManager and is not under test here. mocker.patch.object(crop_manager, "_record_yield") harvested = crop_manager.manage_harvest( @@ -302,7 +300,6 @@ def test_manage_harvest_deposit_all_residue_leaves_biomass_in_field( assert harvested is None assert crop_manager.dry_matter_yield_collected == 0.0 assert crop_manager.wet_yield_collected == 0.0 - # All cut biomass is deposited into the field's surface layer as residue. assert surface_layer.plant_residue > 0.0 From 4e38734ff91a91be97343824e486eb07a4be3862 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 28 Jul 2026 03:53:11 +0000 Subject: [PATCH 09/11] Apply Black Formatting From 27cfdbfed343e989d03fc220660e519d8757d903 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 29 Jul 2026 05:31:44 +0000 Subject: [PATCH 10/11] Apply Black Formatting From 2b9ad225df8031244fdc8620f301c5247b5f1566 Mon Sep 17 00:00:00 2001 From: matthew7838 Date: Wed, 29 Jul 2026 05:35:51 +0000 Subject: [PATCH 11/11] Update badges on README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c378c18f0c..10de5cb740 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-1135%20errors-red)](https://github.com/RuminantFarmSystems/MASM/actions/workflows/combined_format_lint_test_mypy.yml) +[![Mypy](https://img.shields.io/badge/Mypy-1134%20errors-red)](https://github.com/RuminantFarmSystems/MASM/actions/workflows/combined_format_lint_test_mypy.yml) # RuFaS: Ruminant Farm Systems