From 9462fafd0a6904016c6ab99fbe791525db21cbf7 Mon Sep 17 00:00:00 2001 From: Niko <70217952+ew3361zh@users.noreply.github.com> Date: Mon, 30 Mar 2026 18:11:03 -0400 Subject: [PATCH 1/4] adds sim day to several reported vars for c&s module --- RUFAS/EEE/emissions.py | 2 ++ RUFAS/biophysical/field/field/field.py | 5 +++-- RUFAS/biophysical/field/manager/field_manager.py | 1 + RUFAS/simulation_engine.py | 3 ++- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/RUFAS/EEE/emissions.py b/RUFAS/EEE/emissions.py index 8615a859fa..5a1c8bfdbf 100644 --- a/RUFAS/EEE/emissions.py +++ b/RUFAS/EEE/emissions.py @@ -167,6 +167,7 @@ def check_available_purchased_feed_data(self, available_feed_ids: list[int]) -> def calculate_purchased_feed_emissions( self, purchased_feeds: dict[int, float], + simulation_day: int, ) -> None: """ Calculates the emissions from purchased feeds and land use changes and reports them to OutputManager. @@ -191,6 +192,7 @@ def calculate_purchased_feed_emissions( "class": self.__class__.__name__, "function": self.calculate_purchased_feed_emissions.__name__, "units": MeasurementUnits.KILOGRAMS_CARBON_DIOXIDE_PER_KILOGRAM_DRY_MATTER, + "simulation_day": simulation_day } self.om.add_variable("purchased_feed_emissions", purchased_feed_emissions, info_map) self.om.add_variable("land_use_change_emissions", land_use_change_emissions, info_map) diff --git a/RUFAS/biophysical/field/field/field.py b/RUFAS/biophysical/field/field/field.py index dd2ac686a5..6a4125dfdc 100644 --- a/RUFAS/biophysical/field/field/field.py +++ b/RUFAS/biophysical/field/field/field.py @@ -1469,7 +1469,7 @@ def _cycle_water(self, current_conditions: CurrentDayConditions, time: RufasTime of processes. This is necessary because there is not necessarily one correct order for processes to run in. """ - manure_water = self._get_manure_water() + manure_water = self._get_manure_water(time) watering_amount = self._determine_watering_amount( rainfall=current_conditions.rainfall, manure_water=manure_water, @@ -1609,7 +1609,7 @@ def _determine_watering_amount( else: return 0.0 - def _get_manure_water(self) -> float: + def _get_manure_water(self, time: RufasTime) -> float: """ Grabs water from a manure application and records it, if any. @@ -1626,6 +1626,7 @@ def _get_manure_water(self) -> float: "function": self._get_manure_water.__name__, "suffix": f"field='{self.field_data.name}'", "units": MeasurementUnits.MILLIMETERS, + "simulation_day": time.simulation_day } self.om.add_variable("manure_water", manure_water, info_map) diff --git a/RUFAS/biophysical/field/manager/field_manager.py b/RUFAS/biophysical/field/manager/field_manager.py index b94ff5f30e..9775a479b6 100644 --- a/RUFAS/biophysical/field/manager/field_manager.py +++ b/RUFAS/biophysical/field/manager/field_manager.py @@ -98,6 +98,7 @@ def daily_update_routine( "function": self.daily_update_routine.__name__, "suffix": f"field='{field.field_data.name}'", "units": MeasurementUnits.HOURS, + "simulation_day": time.simulation_day } self.om.add_variable("daylength", current_conditions.daylength, info_map) manure_applications_for_field = [ diff --git a/RUFAS/simulation_engine.py b/RUFAS/simulation_engine.py index 8e70d48fae..652f258b98 100644 --- a/RUFAS/simulation_engine.py +++ b/RUFAS/simulation_engine.py @@ -435,7 +435,8 @@ def _report_daily_records(self, daily_purchased_feeds_fed: dict[int, float] | No If no purchased feed was fed, this will be None. """ if daily_purchased_feeds_fed: - self.emissions_estimator.calculate_purchased_feed_emissions(daily_purchased_feeds_fed) + self.emissions_estimator.calculate_purchased_feed_emissions(daily_purchased_feeds_fed, + self.time.simulation_day) self.time.record_time() self.weather.record_weather(self.time) From 5d70365e194ea379010e1cb4c570410a7a0309e7 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 30 Mar 2026 22:14:06 +0000 Subject: [PATCH 2/4] Apply Black Formatting --- RUFAS/EEE/emissions.py | 2 +- RUFAS/biophysical/field/field/field.py | 2 +- RUFAS/biophysical/field/manager/field_manager.py | 2 +- RUFAS/simulation_engine.py | 5 +++-- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/RUFAS/EEE/emissions.py b/RUFAS/EEE/emissions.py index 5a1c8bfdbf..debc7073c1 100644 --- a/RUFAS/EEE/emissions.py +++ b/RUFAS/EEE/emissions.py @@ -192,7 +192,7 @@ def calculate_purchased_feed_emissions( "class": self.__class__.__name__, "function": self.calculate_purchased_feed_emissions.__name__, "units": MeasurementUnits.KILOGRAMS_CARBON_DIOXIDE_PER_KILOGRAM_DRY_MATTER, - "simulation_day": simulation_day + "simulation_day": simulation_day, } self.om.add_variable("purchased_feed_emissions", purchased_feed_emissions, info_map) self.om.add_variable("land_use_change_emissions", land_use_change_emissions, info_map) diff --git a/RUFAS/biophysical/field/field/field.py b/RUFAS/biophysical/field/field/field.py index 6a4125dfdc..4a445eef71 100644 --- a/RUFAS/biophysical/field/field/field.py +++ b/RUFAS/biophysical/field/field/field.py @@ -1626,7 +1626,7 @@ def _get_manure_water(self, time: RufasTime) -> float: "function": self._get_manure_water.__name__, "suffix": f"field='{self.field_data.name}'", "units": MeasurementUnits.MILLIMETERS, - "simulation_day": time.simulation_day + "simulation_day": time.simulation_day, } self.om.add_variable("manure_water", manure_water, info_map) diff --git a/RUFAS/biophysical/field/manager/field_manager.py b/RUFAS/biophysical/field/manager/field_manager.py index 9775a479b6..79616c4811 100644 --- a/RUFAS/biophysical/field/manager/field_manager.py +++ b/RUFAS/biophysical/field/manager/field_manager.py @@ -98,7 +98,7 @@ def daily_update_routine( "function": self.daily_update_routine.__name__, "suffix": f"field='{field.field_data.name}'", "units": MeasurementUnits.HOURS, - "simulation_day": time.simulation_day + "simulation_day": time.simulation_day, } self.om.add_variable("daylength", current_conditions.daylength, info_map) manure_applications_for_field = [ diff --git a/RUFAS/simulation_engine.py b/RUFAS/simulation_engine.py index 652f258b98..9be9122f9c 100644 --- a/RUFAS/simulation_engine.py +++ b/RUFAS/simulation_engine.py @@ -435,8 +435,9 @@ def _report_daily_records(self, daily_purchased_feeds_fed: dict[int, float] | No If no purchased feed was fed, this will be None. """ if daily_purchased_feeds_fed: - self.emissions_estimator.calculate_purchased_feed_emissions(daily_purchased_feeds_fed, - self.time.simulation_day) + self.emissions_estimator.calculate_purchased_feed_emissions( + daily_purchased_feeds_fed, self.time.simulation_day + ) self.time.record_time() self.weather.record_weather(self.time) From c08daa2cd4aafc164360248f29045c32454197c7 Mon Sep 17 00:00:00 2001 From: ew3361zh Date: Mon, 30 Mar 2026 22:18:25 +0000 Subject: [PATCH 3/4] Update badges on README --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 204cda5a20..8c086898fb 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-1195%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-1199%20errors-red)](https://github.com/RuminantFarmSystems/MASM/actions/workflows/combined_format_lint_test_mypy.yml) # RuFaS: Ruminant Farm Systems From 90b7531597cb29d85809f60205d02154987b12e4 Mon Sep 17 00:00:00 2001 From: Clay Morrow <25436787+morrowcj@users.noreply.github.com> Date: Fri, 3 Apr 2026 10:26:56 -0500 Subject: [PATCH 4/4] intial testing --- tests/test_output_manager.py | 64 ++++++++++++++++++++++++++++++--- tests/test_simulation_engine.py | 19 ++++++++-- tests/test_time.py | 9 ----- 3 files changed, 76 insertions(+), 16 deletions(-) diff --git a/tests/test_output_manager.py b/tests/test_output_manager.py index f95d207ec3..9a5bb7e922 100644 --- a/tests/test_output_manager.py +++ b/tests/test_output_manager.py @@ -34,7 +34,6 @@ def mock_output_manager() -> OutputManager: output_manager = OutputManager() return output_manager - @pytest.mark.parametrize( "is_end_to_end_testing_run, expected_prefixes", [ @@ -874,20 +873,22 @@ def test_add_log( @pytest.mark.parametrize( - "name, value, info_map, first_map, expected_exception", + "name, value, info_map, first_map, overwrite_simulation_day, current_simulation_day, expected_exception", [ # Case 1: Everything correct, no exception should be raised - ("var1", 100, {"class": "TestClass", "function": "test_function", "units": "kg"}, True, None), + ("var1", 100, {"class": "TestClass", "function": "test_function", "units": "kg"}, True, True, 220, None), # Case 1.5: Everything correct, no exception should be raised, only first info map should be recorded. - ("var1", 100, {"class": "TestClass", "function": "test_function", "units": "kg"}, False, None), + ("var1", 100, {"class": "TestClass", "function": "test_function", "units": "kg"}, False, True, 220, None), # Case 2: 'units' key missing, should raise KeyError - ("var2", 200, {"class": "TestClass", "function": "test_function"}, True, KeyError), + ("var2", 200, {"class": "TestClass", "function": "test_function"}, True, True, 220, KeyError), # Case 3: Value is a dict, should process sub-keys ( "var3", {"sub1": 10, "sub2": 20}, {"class": "TestClass", "function": "test_function", "units": "kg"}, True, + True, + 220, None, ), # Case 4: 'units' is a dict, but lengths do not match with value, should raise KeyError @@ -896,6 +897,8 @@ def test_add_log( [1, 2, 3], {"class": "TestClass", "function": "test_function", "units": {"key1": "kg", "key2": "g"}}, True, + True, + 220, KeyError, ), # Case 5: 'units' is a dict, lengths match with value, no exception @@ -904,6 +907,8 @@ def test_add_log( [1, 2], {"class": "TestClass", "function": "test_function", "units": {"key1": "kg", "key2": "g"}}, True, + True, + 220, None, ), # Case 6: 'units' is a dict, lengths do not match with value (empty value), no exception @@ -912,8 +917,49 @@ def test_add_log( {}, {"class": "TestClass", "function": "test_function", "units": {"key1": "kg", "key2": "g"}}, True, + True, + 220, + None, + ), + # Case 7: don't overwrite included "simulation_day" + ( + "var6", + "one day only", + { + "class": "TestClass", "function": "test_function", + "units": {"key1": "kg", "key2": "g"}, "simulation_day": 379 + }, + True, + False, + 220, + None, + ), + # Case 8: do overwrite included simulation day + ( + "var6", + "one day only", + { + "class": "TestClass", "function": "test_function", + "units": {"key1": "kg", "key2": "g"}, "simulation_day": 379 + }, + True, + True, + 220, None, ), + # Case 9: no simulation day provided in info map + ( + "var6", + "one day only", + { + "class": "TestClass", "function": "test_function", + "units": {"key1": "kg", "key2": "g"} + }, + True, + True, + 220, + None, + ) ], ) def test_add_variable( @@ -921,6 +967,8 @@ def test_add_variable( value: Any, info_map: dict[str, Any], first_map: bool, + overwrite_simulation_day: bool, + current_simulation_day: int, expected_exception: Type[BaseException] | None, mocker: MockerFixture, ) -> None: @@ -934,14 +982,20 @@ def test_add_variable( mocker.patch.object(output_manager, "_generate_key", return_value="key_with_prefix") patched_add_to_pool = mocker.patch.object(output_manager, "_add_to_pool") mocker.patch.dict(output_manager._variables_usage_counter, {}, clear=True) + mocker.patch("RUFAS.rufas_time.RufasTime.simulation_day", return_value=current_simulation_day) if expected_exception: with pytest.raises(expected_exception): output_manager.add_variable(name, value, info_map, first_map) else: # Act + mapped_simday = info_map["simulation_day"] | None output_manager.add_variable(name, value, info_map, first_map) # Assert + if overwrite_simulation_day or "simulation_day" not in info_map.keys: + assert info_map["simulation_day"] == current_simulation_day + else: + assert info_map["simulation_day"] == mapped_simday patched_add_to_pool.assert_called_once_with( output_manager.variables_pool, "key_with_prefix", diff --git a/tests/test_simulation_engine.py b/tests/test_simulation_engine.py index 01f710d2b1..4005fd3e80 100644 --- a/tests/test_simulation_engine.py +++ b/tests/test_simulation_engine.py @@ -1,11 +1,11 @@ from datetime import date, datetime, timedelta -from typing import cast +from typing import cast, Dict, Any from RUFAS.EEE.emissions import EmissionsEstimator from RUFAS.data_structures.animal_to_manure_connection import ManureStream from RUFAS.data_structures.crop_soil_to_feed_storage_connection import HarvestedCrop import pytest -from unittest.mock import MagicMock, PropertyMock, call +from unittest.mock import MagicMock, PropertyMock, call, patch from pytest_mock import MockerFixture @@ -36,6 +36,21 @@ from RUFAS.rufas_time import RufasTime from RUFAS.weather import Weather +@pytest.fixture +def mock_time_config() -> Dict[str, Any]: + config = { + "start_date": "1999:2", + "end_date": "2000:1", + } + return config + +def test_record_time(mock_time_config: Dict[str, Any], mocker: MockerFixture) -> None: + """Tests that RufasTime instances correctly add current time information to the OutputManager.""" + mocker.patch("RUFAS.input_manager.InputManager.get_data", return_value=mock_time_config) + time = RufasTime() + with patch("RUFAS.output_manager.OutputManager.add_variable") as add_var: + time.record_time() + assert add_var.call_count == 4 def test_simulation_type_enum_values() -> None: """Unit test for SimulationType enum values.""" diff --git a/tests/test_time.py b/tests/test_time.py index 419406effe..ede129d67b 100644 --- a/tests/test_time.py +++ b/tests/test_time.py @@ -136,15 +136,6 @@ def test_year_end_day(mock_config: Dict[str, Any], mocker: MockerFixture) -> Non time.advance() -def test_record_time(mock_config: Dict[str, Any], mocker: MockerFixture) -> None: - """Tests that RufasTime instances correctly add current time information to the OutputManager.""" - mocker.patch("RUFAS.input_manager.InputManager.get_data", return_value=mock_config) - time = RufasTime() - with patch("RUFAS.output_manager.OutputManager.add_variable") as add_var: - time.record_time() - assert add_var.call_count == 4 - - @pytest.mark.parametrize( "start_date_str, simulation_day, expected_date", [