Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 2 additions & 0 deletions RUFAS/EEE/emissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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)
Expand Down
5 changes: 3 additions & 2 deletions RUFAS/biophysical/field/field/field.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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.

Expand All @@ -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)

Expand Down
1 change: 1 addition & 0 deletions RUFAS/biophysical/field/manager/field_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand Down
4 changes: 3 additions & 1 deletion RUFAS/simulation_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -435,7 +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.emissions_estimator.calculate_purchased_feed_emissions(
daily_purchased_feeds_fed, self.time.simulation_day
)
self.time.record_time()
self.weather.record_weather(self.time)

Expand Down
64 changes: 59 additions & 5 deletions tests/test_output_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
[
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -912,15 +917,58 @@ 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(
name: str,
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:
Expand All @@ -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",
Expand Down
19 changes: 17 additions & 2 deletions tests/test_simulation_engine.py
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -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."""
Expand Down
9 changes: 0 additions & 9 deletions tests/test_time.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
[
Expand Down