From f41148a99d8c9a73668cdf09f1499c028f39e7f6 Mon Sep 17 00:00:00 2001 From: Matthew Liu Date: Fri, 23 Jan 2026 14:16:19 +0900 Subject: [PATCH 01/16] Usage of ME305 implementation --- RUFAS/biophysical/animal/herd_manager.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/RUFAS/biophysical/animal/herd_manager.py b/RUFAS/biophysical/animal/herd_manager.py index 881469b8d9..c740747c78 100644 --- a/RUFAS/biophysical/animal/herd_manager.py +++ b/RUFAS/biophysical/animal/herd_manager.py @@ -711,10 +711,10 @@ def _get_cow_removal_index(self, removed_animal: list[Animal]) -> int | None: # Priority 1: DNB cows by lowest daily milk if dnb_indices: - return min(dnb_indices, key=lambda i: self.cows[i].milk_production.daily_milk_produced) + return min(dnb_indices, key=lambda i: self.cows[i].mature_equivalent_milking_prediction_305_day) # Priority 2: Non-DNB cows by lowest daily milk (qualified by DIM) - return min(non_dnb_indices, key=lambda i: self.cows[i].milk_production.daily_milk_produced) + return min(non_dnb_indices, key=lambda i: self.cows[i].mature_equivalent_milking_prediction_305_day) def _record_sold_cow_stats(self, removed_cow: Animal, simulation_day: int) -> None: """Updates herd statistics and metadata for a sold cow.""" @@ -753,8 +753,6 @@ def _check_if_cows_need_to_be_sold(self, simulation_day: int, removed_animal: li return animals_removed - # estimated_production = self.cows[index].mature_equivalent_milking_prediction_305_day - def _check_if_replacement_heifers_needed(self, time: RufasTime) -> list[Animal]: """ Checks if replacement heifers are needed to maintain the herd size. From 90114ebc5e53ebce3683ed27ed44e623aaff2542 Mon Sep 17 00:00:00 2001 From: Matthew Liu Date: Sat, 24 Jan 2026 16:50:30 +0900 Subject: [PATCH 02/16] Fixed simulation engine call --- RUFAS/simulation_engine.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RUFAS/simulation_engine.py b/RUFAS/simulation_engine.py index b86471a52f..99349d3533 100644 --- a/RUFAS/simulation_engine.py +++ b/RUFAS/simulation_engine.py @@ -127,7 +127,7 @@ def _daily_simulation(self) -> None: is_time_to_reformulate_ration = self.time.current_date.date() == self.next_ration_reformulation if is_time_to_reformulate_ration: self._formulate_ration() - # self.herd_manager.update_milk_305_day_yield_predictions() + self.herd_manager.update_milk_305_day_yield_predictions() requested_feed = self.herd_manager.collect_daily_feed_request() self.feed_manager.report_feed_storage_levels(self.time.simulation_day, "daily_storage_levels") From af3ac0011a96eb17bdf8ed3e7f01a028612b0cd1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 16 Apr 2026 13:02:19 +0000 Subject: [PATCH 03/16] Apply Black Formatting From 20e47aa9015eb9b6c88925fb66c540ff9fe9b5c9 Mon Sep 17 00:00:00 2001 From: matthew7838 Date: Thu, 16 Apr 2026 13:06:37 +0000 Subject: [PATCH 04/16] Update badges on README --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 17507f42e9..c97d414880 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-1190%20errors-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-1192%20errors-red)](https://github.com/RuminantFarmSystems/MASM/actions/workflows/combined_format_lint_test_mypy.yml) # RuFaS: Ruminant Farm Systems From 320c6782936323e64628a79818a332e21663e174 Mon Sep 17 00:00:00 2001 From: Matthew Liu Date: Thu, 16 Apr 2026 22:44:37 +0900 Subject: [PATCH 05/16] Reimplement the ME 305 changes that got covered after base branch merge --- RUFAS/biophysical/animal/animal.py | 22 +++++++++++++++++++ RUFAS/biophysical/animal/herd_manager.py | 19 ---------------- .../animal/milk/lactation_curve.py | 3 ++- .../animal/milk/milk_production.py | 21 +++++++++++++++--- 4 files changed, 42 insertions(+), 23 deletions(-) diff --git a/RUFAS/biophysical/animal/animal.py b/RUFAS/biophysical/animal/animal.py index 95e438af37..3492697320 100644 --- a/RUFAS/biophysical/animal/animal.py +++ b/RUFAS/biophysical/animal/animal.py @@ -201,6 +201,8 @@ def __init__( self.nutrition_supply: NutritionSupply = NutritionSupply.make_empty_nutrition_supply() self.nutrition_supply.dry_matter = AnimalModuleConstants.DEFAULT_DRY_MATTER_INTAKE self.previous_nutrition_supply: NutritionSupply | None = None + self.milk_yield_305_day: float = 0.0 + self.mature_equivalent_milking_prediction_305_day: float = 0.0 self._days_in_milk: int = 0 self._milk_production_output_days_in_milk: int = 0 @@ -2359,3 +2361,23 @@ def calculate_nutrition_requirements( ) return requirements + + def update_mature_equivalent_305_days_milk_production(self) -> None: + if self.days_in_milk < 305: + self.mature_equivalent_milking_prediction_305_day = self.milk_production.calculate_305_day_milk_yield( + self.milk_production.wood_l, + self.milk_production.wood_m, + self.milk_production.wood_n, + self.milk_production.milk_production_history, + self.days_in_milk, + ) + else: + self.mature_equivalent_milking_prediction_305_day = ( + self.milk_production.current_lactation_305_day_milk_produced + ) + + parity_factor = {1: 1.25, 2: 1.18}.get(self.calves, 1.0) + + self.mature_equivalent_milking_prediction_305_day = ( + self.mature_equivalent_milking_prediction_305_day * parity_factor + ) \ No newline at end of file diff --git a/RUFAS/biophysical/animal/herd_manager.py b/RUFAS/biophysical/animal/herd_manager.py index 2fed6d5432..d8b5a0d060 100644 --- a/RUFAS/biophysical/animal/herd_manager.py +++ b/RUFAS/biophysical/animal/herd_manager.py @@ -724,25 +724,6 @@ def _get_cow_removal_index(self, removed_animal: list[Animal]) -> int | None: return min(eligible_indices, key=lambda i: self.cows[i].mature_equivalent_milking_prediction_305_day) - def _record_sold_cow_stats(self, removed_cow: Animal, simulation_day: int) -> None: - """Updates herd statistics and metadata for a sold cow.""" - removed_cow.sold_at_day = simulation_day - self.herd_statistics.sold_cows_info.append( - SoldAnimalTypedDict( - id=removed_cow.id, - animal_type=removed_cow.animal_type.value, - sold_at_day=removed_cow.sold_at_day, - body_weight=removed_cow.body_weight, - cull_reason="NA", - days_in_milk=removed_cow.days_in_milk, - parity="NA", - ) - ) - self.herd_statistics.cow_num -= 1 - self.herd_statistics.sold_cow_oversupply_num += 1 - self.herd_statistics.cow_herd_exit_num -= 1 - self.herd_statistics.sold_cow_num += 1 - def _check_if_cows_need_to_be_sold(self, simulation_day: int, removed_animal: list[Animal]) -> list[Animal]: """Checks if surplus cows need to be sold based on herd size.""" animals_removed: list[Animal] = [] diff --git a/RUFAS/biophysical/animal/milk/lactation_curve.py b/RUFAS/biophysical/animal/milk/lactation_curve.py index 204c459d25..f0449ef01a 100644 --- a/RUFAS/biophysical/animal/milk/lactation_curve.py +++ b/RUFAS/biophysical/animal/milk/lactation_curve.py @@ -377,7 +377,8 @@ def _estimate_305_day_milk_yield_by_parity( @staticmethod def _calculate_305_day_milk_yield_error(l_param: float, m_param: float, n_param: float, milk_yield: float) -> float: """Calculates absolute difference between an estimated 305 day milk yield and a predetermined one.""" - return abs(MilkProduction.calc_305_day_milk_yield(l_param, m_param, n_param) - milk_yield) + return abs( + MilkProduction.calculate_305_day_milk_yield(l_param, m_param, n_param, None) - milk_yield) @classmethod def _fit_wood_l_param_to_milk_yield( diff --git a/RUFAS/biophysical/animal/milk/milk_production.py b/RUFAS/biophysical/animal/milk/milk_production.py index 9adebbbb25..e6596b565d 100644 --- a/RUFAS/biophysical/animal/milk/milk_production.py +++ b/RUFAS/biophysical/animal/milk/milk_production.py @@ -260,7 +260,13 @@ def calculate_daily_milk_production(days_in_milk: int, l_param: float, m_param: return l_param * np.power(days_in_milk, m_param) * np.exp(-1 * n_param * days_in_milk) @staticmethod - def calc_305_day_milk_yield(l_param: float, m_param: float, n_param: float) -> float: + def calculate_305_day_milk_yield( + l_param: float, + m_param: float, + n_param: float, + milking_history: list[MilkProductionRecord] | None, + days_in_milk: int = 0, + ) -> float: """ Calculates the total milk yield from day 1 to day 305 of the lactation. @@ -276,6 +282,10 @@ def calc_305_day_milk_yield(l_param: float, m_param: float, n_param: float) -> f Wood's lactation curve parameter m. n_param: float Wood's lactation curve parameter n. + days_in_milk : int + Days in milk. + milking_history : + The milk production history if the animal. Returns ------- @@ -283,9 +293,14 @@ def calc_305_day_milk_yield(l_param: float, m_param: float, n_param: float) -> f 305 day milk yield for a cow with the given lactation curve (kg). """ + production_history_sum = 0 + if 305 > days_in_milk > 0 and milking_history is not None: + production_history_sum = sum(history["milk_production"] for history in milking_history[:days_in_milk]) - result, _ = quad(MilkProduction.calculate_daily_milk_production, 1, 305, args=(l_param, m_param, n_param)) - return result + result, _ = quad( + MilkProduction.calculate_daily_milk_production, days_in_milk + 1, 305, args=(l_param, m_param, n_param) + ) + return result + production_history_sum def _get_milk_production_adjustment(self) -> float: """ From cb677e9b7e6e265dd8cc752f3d2601f9461f3e1e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 16 Apr 2026 13:46:36 +0000 Subject: [PATCH 06/16] Apply Black Formatting --- RUFAS/biophysical/animal/animal.py | 2 +- RUFAS/biophysical/animal/milk/lactation_curve.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/RUFAS/biophysical/animal/animal.py b/RUFAS/biophysical/animal/animal.py index 3492697320..a151afb675 100644 --- a/RUFAS/biophysical/animal/animal.py +++ b/RUFAS/biophysical/animal/animal.py @@ -2380,4 +2380,4 @@ def update_mature_equivalent_305_days_milk_production(self) -> None: self.mature_equivalent_milking_prediction_305_day = ( self.mature_equivalent_milking_prediction_305_day * parity_factor - ) \ No newline at end of file + ) diff --git a/RUFAS/biophysical/animal/milk/lactation_curve.py b/RUFAS/biophysical/animal/milk/lactation_curve.py index f0449ef01a..343f8791d8 100644 --- a/RUFAS/biophysical/animal/milk/lactation_curve.py +++ b/RUFAS/biophysical/animal/milk/lactation_curve.py @@ -377,8 +377,7 @@ def _estimate_305_day_milk_yield_by_parity( @staticmethod def _calculate_305_day_milk_yield_error(l_param: float, m_param: float, n_param: float, milk_yield: float) -> float: """Calculates absolute difference between an estimated 305 day milk yield and a predetermined one.""" - return abs( - MilkProduction.calculate_305_day_milk_yield(l_param, m_param, n_param, None) - milk_yield) + return abs(MilkProduction.calculate_305_day_milk_yield(l_param, m_param, n_param, None) - milk_yield) @classmethod def _fit_wood_l_param_to_milk_yield( From 5d129e7d9bc7c4b4515e2a1e47312a41fe321063 Mon Sep 17 00:00:00 2001 From: matthew7838 Date: Thu, 16 Apr 2026 13:50:47 +0000 Subject: [PATCH 07/16] Update badges on README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c97d414880..ac545c76d1 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-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-1192%20errors-red)](https://github.com/RuminantFarmSystems/MASM/actions/workflows/combined_format_lint_test_mypy.yml) +[![Mypy](https://img.shields.io/badge/Mypy-1193%20errors-red)](https://github.com/RuminantFarmSystems/MASM/actions/workflows/combined_format_lint_test_mypy.yml) # RuFaS: Ruminant Farm Systems From fb461e9b8af0c06589fb39bfaa156ae2a7e796d6 Mon Sep 17 00:00:00 2001 From: Matthew Liu Date: Thu, 16 Apr 2026 23:26:15 +0900 Subject: [PATCH 08/16] Updated logic break --- RUFAS/biophysical/animal/herd_manager.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/RUFAS/biophysical/animal/herd_manager.py b/RUFAS/biophysical/animal/herd_manager.py index d8b5a0d060..3ff221ea8d 100644 --- a/RUFAS/biophysical/animal/herd_manager.py +++ b/RUFAS/biophysical/animal/herd_manager.py @@ -2070,3 +2070,7 @@ def _update_total_enteric_methane(self, digestive_outputs: list[dict[AnimalType, self.herd_statistics.total_enteric_methane[animal_type] = { k: float(current_totals.get(k, 0) + new_emissions.get(k, 0)) for k in all_keys } + + def update_milk_305_day_yield_predictions(self) -> None: + for cow in self.cows: + cow.update_mature_equivalent_305_days_milk_production() From a3017fc628d9ab2024a50880d797c757b48881a8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 18 Apr 2026 07:09:18 +0000 Subject: [PATCH 09/16] Apply Black Formatting --- RUFAS/biophysical/animal/animal.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RUFAS/biophysical/animal/animal.py b/RUFAS/biophysical/animal/animal.py index 796266cca8..f7a0bf4c03 100644 --- a/RUFAS/biophysical/animal/animal.py +++ b/RUFAS/biophysical/animal/animal.py @@ -2485,7 +2485,7 @@ def update_mature_equivalent_305_days_milk_production(self) -> None: self.mature_equivalent_milking_prediction_305_day = ( self.mature_equivalent_milking_prediction_305_day * parity_factor ) - + def update_genetic_history(self, simulation_day: int) -> None: """ Updates the genetic history record for the animal on the given simulation day. From cff95c68660948c2f49f5b43a32c142bedb62d09 Mon Sep 17 00:00:00 2001 From: matthew7838 Date: Sat, 18 Apr 2026 07:13:40 +0000 Subject: [PATCH 10/16] Update badges on README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ac545c76d1..9608f749ae 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-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-1193%20errors-red)](https://github.com/RuminantFarmSystems/MASM/actions/workflows/combined_format_lint_test_mypy.yml) +[![Mypy](https://img.shields.io/badge/Mypy-1183%20errors-red)](https://github.com/RuminantFarmSystems/MASM/actions/workflows/combined_format_lint_test_mypy.yml) # RuFaS: Ruminant Farm Systems From 7cc9a7368ea15d615e119017a64226e9d37ecc12 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 18 Apr 2026 07:38:01 +0000 Subject: [PATCH 11/16] Apply Black Formatting From e8038e33d2957365850247bb8b986a9dc784bcdf Mon Sep 17 00:00:00 2001 From: Matthew Liu Date: Sat, 18 Apr 2026 16:46:51 +0900 Subject: [PATCH 12/16] Fixed broken tests --- .../animal/milk/milk_production.py | 5 +- .../test_animal/test_animal/test_animal.py | 47 +++++++++++++++++++ .../test_herd_manager/pytest_fixtures.py | 6 +++ .../test_herd_manager_daily_routines.py | 1 + .../test_herd_manager_misc.py | 15 ++++++ .../test_milk_production.py | 31 ++++++++++-- 6 files changed, 101 insertions(+), 4 deletions(-) diff --git a/RUFAS/biophysical/animal/milk/milk_production.py b/RUFAS/biophysical/animal/milk/milk_production.py index e6596b565d..93de4b0581 100644 --- a/RUFAS/biophysical/animal/milk/milk_production.py +++ b/RUFAS/biophysical/animal/milk/milk_production.py @@ -295,7 +295,10 @@ def calculate_305_day_milk_yield( """ production_history_sum = 0 if 305 > days_in_milk > 0 and milking_history is not None: - production_history_sum = sum(history["milk_production"] for history in milking_history[:days_in_milk]) + current_lactation_history = [ + history for history in milking_history if 0 < history["days_in_milk"] <= days_in_milk + ][-days_in_milk:] + production_history_sum = sum(history["milk_production"] for history in current_lactation_history) result, _ = quad( MilkProduction.calculate_daily_milk_production, days_in_milk + 1, 305, args=(l_param, m_param, n_param) diff --git a/tests/test_biophysical/test_animal/test_animal/test_animal.py b/tests/test_biophysical/test_animal/test_animal/test_animal.py index e4d24cf112..31081c1fdb 100644 --- a/tests/test_biophysical/test_animal/test_animal/test_animal.py +++ b/tests/test_biophysical/test_animal/test_animal/test_animal.py @@ -59,6 +59,53 @@ from RUFAS.rufas_time import RufasTime +def _make_cow_for_305_day_milk_prediction( + days_in_milk: int, calves: int, current_lactation_305_day_milk_produced: float = 0.0 +) -> Animal: + cow = Animal.__new__(Animal) + cow.animal_type = AnimalType.LAC_COW + cow.days_in_milk = days_in_milk + cow.reproduction = MagicMock() + cow.reproduction.calves = calves + cow.milk_production = MagicMock() + cow.milk_production.wood_l = 1.0 + cow.milk_production.wood_m = 2.0 + cow.milk_production.wood_n = 3.0 + cow.milk_production.milk_production_history = [] + cow.milk_production.current_lactation_305_day_milk_produced = current_lactation_305_day_milk_produced + cow.mature_equivalent_milking_prediction_305_day = 0.0 + return cow + + +@pytest.mark.parametrize("calves, parity_factor", [(1, 1.25), (2, 1.18), (3, 1.0)]) +def test_update_mature_equivalent_305_days_milk_production_for_partial_lactation( + calves: int, parity_factor: float +) -> None: + """Test mature-equivalent 305-day milk prediction for cows before day 305.""" + cow = _make_cow_for_305_day_milk_prediction(days_in_milk=120, calves=calves) + cow.milk_production.calculate_305_day_milk_yield.return_value = 8000.0 + + cow.update_mature_equivalent_305_days_milk_production() + + cow.milk_production.calculate_305_day_milk_yield.assert_called_once_with(1.0, 2.0, 3.0, [], 120) + assert cow.mature_equivalent_milking_prediction_305_day == pytest.approx(8000.0 * parity_factor) + + +@pytest.mark.parametrize("calves, parity_factor", [(1, 1.25), (2, 1.18), (3, 1.0)]) +def test_update_mature_equivalent_305_days_milk_production_for_completed_lactation( + calves: int, parity_factor: float +) -> None: + """Test mature-equivalent 305-day milk prediction for cows at or after day 305.""" + cow = _make_cow_for_305_day_milk_prediction( + days_in_milk=305, calves=calves, current_lactation_305_day_milk_produced=9000.0 + ) + + cow.update_mature_equivalent_305_days_milk_production() + + cow.milk_production.calculate_305_day_milk_yield.assert_not_called() + assert cow.mature_equivalent_milking_prediction_305_day == pytest.approx(9000.0 * parity_factor) + + def mock_submodule_init(mocker: MockerFixture) -> None: mocker.patch("RUFAS.biophysical.animal.growth.growth.Growth", return_value=MagicMock(auto_spec=Growth)) diff --git a/tests/test_biophysical/test_animal/test_herd_manager/pytest_fixtures.py b/tests/test_biophysical/test_animal/test_herd_manager/pytest_fixtures.py index 877a358001..7a3e9e48bc 100644 --- a/tests/test_biophysical/test_animal/test_herd_manager/pytest_fixtures.py +++ b/tests/test_biophysical/test_animal/test_herd_manager/pytest_fixtures.py @@ -527,6 +527,7 @@ def mock_animal( ED_days: int = 0, breeding_to_preg_time: int = 0, daily_milk_produced: float = 0.0, + mature_equivalent_milking_prediction_305_day: float | None = None, milk_fat_content: float = 0.0, milk_protein_content: float = 0.0, sold_at_day: int | None = None, @@ -578,6 +579,11 @@ def mock_animal( animal.milk_production.daily_milk_produced = daily_milk_produced animal.milk_production.fat_content = milk_fat_content animal.milk_production.true_protein_content = milk_protein_content + animal.mature_equivalent_milking_prediction_305_day = ( + daily_milk_produced + if mature_equivalent_milking_prediction_305_day is None + else mature_equivalent_milking_prediction_305_day + ) return animal diff --git a/tests/test_biophysical/test_animal/test_herd_manager/test_herd_manager_daily_routines.py b/tests/test_biophysical/test_animal/test_herd_manager/test_herd_manager_daily_routines.py index d1ed978627..bc9e9353b1 100644 --- a/tests/test_biophysical/test_animal/test_herd_manager/test_herd_manager_daily_routines.py +++ b/tests/test_biophysical/test_animal/test_herd_manager/test_herd_manager_daily_routines.py @@ -504,6 +504,7 @@ def _create_sortable_mock_cow( cow.milk_production = MagicMock() cow.milk_production.daily_milk_produced = daily_milk + cow.mature_equivalent_milking_prediction_305_day = daily_milk cow.days_in_milk = days_in_milk cow.days_in_pregnancy = days_in_pregnancy diff --git a/tests/test_biophysical/test_animal/test_herd_manager/test_herd_manager_misc.py b/tests/test_biophysical/test_animal/test_herd_manager/test_herd_manager_misc.py index 94d1f57d4d..3fc44c8df2 100644 --- a/tests/test_biophysical/test_animal/test_herd_manager/test_herd_manager_misc.py +++ b/tests/test_biophysical/test_animal/test_herd_manager/test_herd_manager_misc.py @@ -66,6 +66,21 @@ def test_collect_daily_feed_request(herd_manager: HerdManager) -> None: assert result == expected_total_requested_feed +def test_update_milk_305_day_yield_predictions(herd_manager: HerdManager) -> None: + """Unit test for update_milk_305_day_yield_predictions().""" + cows = [ + mock_animal(AnimalType.LAC_COW, id=1), + mock_animal(AnimalType.LAC_COW, id=2), + mock_animal(AnimalType.DRY_COW, id=3), + ] + herd_manager.cows = cows + + herd_manager.update_milk_305_day_yield_predictions() + + for cow in cows: + cow.update_mature_equivalent_305_days_milk_production.assert_called_once_with() + + def test_print_herd_snapshot(herd_manager: HerdManager, mocker: MockerFixture) -> None: """Unit test for print_herd_snapshot()""" mock_print = mocker.patch("builtins.print") diff --git a/tests/test_biophysical/test_animal/test_milk_production/test_milk_production.py b/tests/test_biophysical/test_animal/test_milk_production/test_milk_production.py index 5b400ef12f..af5d20878a 100644 --- a/tests/test_biophysical/test_animal/test_milk_production/test_milk_production.py +++ b/tests/test_biophysical/test_animal/test_milk_production/test_milk_production.py @@ -358,9 +358,34 @@ def test_calculate_daily_milk_production( (0.3, 0.4, 0.5, quad(MilkProduction.calculate_daily_milk_production, 1, 305, args=(0.3, 0.4, 0.5))[0]), ], ) -def test_calc_305_day_milk_yield(l_param: float, m_param: float, n_param: float, expected: float) -> None: - """Test the calc_305_day_milk_yield method of the MilkProduction class.""" - assert MilkProduction.calc_305_day_milk_yield(l_param, m_param, n_param) == pytest.approx(expected, rel=1e-6) +def test_calculate_305_day_milk_yield(l_param: float, m_param: float, n_param: float, expected: float) -> None: + """Test the calculate_305_day_milk_yield method of the MilkProduction class.""" + assert MilkProduction.calculate_305_day_milk_yield(l_param, m_param, n_param, None) == pytest.approx( + expected, rel=1e-6 + ) + + +def test_calculate_305_day_milk_yield_uses_current_lactation_history() -> None: + """Test that partial 305-day milk yield projections use current lactation records.""" + milk_production_history = [ + {"simulation_day": 1, "days_in_milk": 1, "milk_production": 100.0, "days_born": 1000}, + {"simulation_day": 2, "days_in_milk": 2, "milk_production": 100.0, "days_born": 1001}, + {"simulation_day": 3, "days_in_milk": 0, "milk_production": 0.0, "days_born": 1002}, + {"simulation_day": 4, "days_in_milk": 1, "milk_production": 10.0, "days_born": 1003}, + {"simulation_day": 5, "days_in_milk": 2, "milk_production": 20.0, "days_born": 1004}, + ] + l_param = 0.1 + m_param = 0.2 + n_param = 0.3 + expected_projected_yield = quad( + MilkProduction.calculate_daily_milk_production, 3, 305, args=(l_param, m_param, n_param) + )[0] + + result = MilkProduction.calculate_305_day_milk_yield( + l_param, m_param, n_param, milk_production_history, days_in_milk=2 + ) + + assert result == pytest.approx(30.0 + expected_projected_yield, rel=1e-6) @pytest.mark.parametrize( From 1f255686b7e6126f3d866f1a8ef05ddfbd25ed8c Mon Sep 17 00:00:00 2001 From: Matthew Liu Date: Sat, 18 Apr 2026 17:05:22 +0900 Subject: [PATCH 13/16] Revert milk history changes --- RUFAS/biophysical/animal/milk/milk_production.py | 5 +---- .../test_milk_production/test_milk_production.py | 6 +++--- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/RUFAS/biophysical/animal/milk/milk_production.py b/RUFAS/biophysical/animal/milk/milk_production.py index 93de4b0581..e6596b565d 100644 --- a/RUFAS/biophysical/animal/milk/milk_production.py +++ b/RUFAS/biophysical/animal/milk/milk_production.py @@ -295,10 +295,7 @@ def calculate_305_day_milk_yield( """ production_history_sum = 0 if 305 > days_in_milk > 0 and milking_history is not None: - current_lactation_history = [ - history for history in milking_history if 0 < history["days_in_milk"] <= days_in_milk - ][-days_in_milk:] - production_history_sum = sum(history["milk_production"] for history in current_lactation_history) + production_history_sum = sum(history["milk_production"] for history in milking_history[:days_in_milk]) result, _ = quad( MilkProduction.calculate_daily_milk_production, days_in_milk + 1, 305, args=(l_param, m_param, n_param) diff --git a/tests/test_biophysical/test_animal/test_milk_production/test_milk_production.py b/tests/test_biophysical/test_animal/test_milk_production/test_milk_production.py index af5d20878a..550d28b18e 100644 --- a/tests/test_biophysical/test_animal/test_milk_production/test_milk_production.py +++ b/tests/test_biophysical/test_animal/test_milk_production/test_milk_production.py @@ -365,8 +365,8 @@ def test_calculate_305_day_milk_yield(l_param: float, m_param: float, n_param: f ) -def test_calculate_305_day_milk_yield_uses_current_lactation_history() -> None: - """Test that partial 305-day milk yield projections use current lactation records.""" +def test_calculate_305_day_milk_yield_uses_available_history() -> None: + """Test that partial 305-day milk yield projections include recorded production history.""" milk_production_history = [ {"simulation_day": 1, "days_in_milk": 1, "milk_production": 100.0, "days_born": 1000}, {"simulation_day": 2, "days_in_milk": 2, "milk_production": 100.0, "days_born": 1001}, @@ -385,7 +385,7 @@ def test_calculate_305_day_milk_yield_uses_current_lactation_history() -> None: l_param, m_param, n_param, milk_production_history, days_in_milk=2 ) - assert result == pytest.approx(30.0 + expected_projected_yield, rel=1e-6) + assert result == pytest.approx(200.0 + expected_projected_yield, rel=1e-6) @pytest.mark.parametrize( From 76335dd329f92e0d01e984fd562f917a93294ae2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 18 Apr 2026 08:07:05 +0000 Subject: [PATCH 14/16] Apply Black Formatting From 059d82d0a91bdeedcb56e5094ccaa489ad51a730 Mon Sep 17 00:00:00 2001 From: matthew7838 Date: Sat, 18 Apr 2026 08:11:38 +0000 Subject: [PATCH 15/16] Update badges on README --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9608f749ae..4eb5a2d199 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-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-1183%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-1186%20errors-red)](https://github.com/RuminantFarmSystems/MASM/actions/workflows/combined_format_lint_test_mypy.yml) # RuFaS: Ruminant Farm Systems From 465e8e02ec8877f872589c309cf102a7308141e0 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 18 Apr 2026 08:15:17 +0000 Subject: [PATCH 16/16] Apply Black Formatting