From df75197ca2f0a8203d007adfe91de2a726f9bf40 Mon Sep 17 00:00:00 2001 From: Niko <70217952+ew3361zh@users.noreply.github.com> Date: Tue, 4 Aug 2026 16:18:17 -0400 Subject: [PATCH 1/4] roi calculation updates --- RUFAS/EEE/economics/dcfror.py | 20 ++--- RUFAS/EEE/economics/partial_budget.py | 109 +++++++++++++++++++++++++ RUFAS/input_manager.py | 8 +- input/data/EEE/economic_inputs.json | 9 ++ input/metadata/properties/default.json | 24 ++++++ 5 files changed, 154 insertions(+), 16 deletions(-) diff --git a/RUFAS/EEE/economics/dcfror.py b/RUFAS/EEE/economics/dcfror.py index a7cb5f365b..59a4154050 100644 --- a/RUFAS/EEE/economics/dcfror.py +++ b/RUFAS/EEE/economics/dcfror.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Any, Dict, Iterable, Tuple +from typing import Any, Iterable import numpy as np import pandas as pd @@ -22,7 +22,7 @@ def __init__(self) -> None: self.om = OutputManager() self.inputs = self._load_inputs() - def _load_inputs(self) -> Dict[str, Any]: + def _load_inputs(self) -> dict[str, Any]: info_map = {"class": self.__class__.__name__, "function": self._load_inputs.__name__} try: @@ -50,7 +50,7 @@ def _get_input(path: str) -> Any: cost_capital_multiple = _get_input("economic_inputs.capital_costs.capital_cost_breakdown") - inputs: Dict[str, Any] = { + inputs: dict[str, Any] = { "cost_capital_multiple": cost_capital_multiple, "cost_operational_units": _get_input("economic_inputs.cashflow_inputs.operating_units"), "cost_operational_unit_cost": _get_input("economic_inputs.cashflow_inputs.operating_unit_costs"), @@ -85,7 +85,7 @@ def _get_input(path: str) -> Any: self.om.add_error("MissingInputKey", f"Missing input key: {str(e)}", info_map) raise - def calculate(self, override_inputs: Dict[str, Any] | None = None) -> None: + def calculate(self, override_inputs: dict[str, Any] | None = None) -> None: """Run the DCFROR cash-flow model and export summary outputs. Parameters @@ -131,7 +131,7 @@ def calculate(self, override_inputs: Dict[str, Any] | None = None) -> None: self.om.add_error("DCFRORCalculationFailed", f"DCFROR calculation failed: {exc}", info_map) raise - def _prepare_costs(self, input_dict: Dict[str, Any]) -> Dict[str, Any]: + def _prepare_costs(self, input_dict: dict[str, Any]) -> dict[str, Any]: """Build capital, operating-cost, and revenue schedules from inputs.""" info_map = {"class": self.__class__.__name__, "function": self._prepare_costs.__name__} @@ -267,7 +267,7 @@ def _get_digester_config(self) -> list: except (KeyError, TypeError): return [] - def _prepare_financing(self, input_dict: Dict[str, Any], project_term: int) -> Dict[str, Any]: + def _prepare_financing(self, input_dict: dict[str, Any], project_term: int) -> dict[str, Any]: """Normalise financing inputs and enforce valid borrowing shares.""" depreciation_rate = input_dict["depreciation_rate"] depreciation_rate = depreciation_rate[~np.isnan(depreciation_rate)] @@ -320,7 +320,7 @@ def _compute_cash_flows( operating_costs: np.ndarray, tax_rate: float, internal_rate_of_return: float, - ) -> Tuple[np.ndarray, pd.DataFrame]: + ) -> tuple[np.ndarray, pd.DataFrame]: """Build the annual cash flow table using Equations 7–26.""" years = EconomicEquations.construct_timeline(construction_term, loan_term, project_term) @@ -497,15 +497,11 @@ def _export_results( cash_flow_df.loc[construction_mask, "NPVCapitalPlusInterest"].to_numpy(), ) - positive_benefits = float(CF[CF > 0].sum()) - investment_costs = float(-CF[CF < 0].sum()) - roi = EconomicMetrics.calculate_roi(positive_benefits, investment_costs) payback = EconomicMetrics.calculate_payback_period(CF) net_cash_flow = EconomicMetrics.calculate_net_annual_cash_flow(revenue, operating_costs) mpsp = EconomicMetrics.calculate_mpsp(capital_cost + operating_costs.sum(), revenue.sum()) self.om.add_variable("econ_dcfror_npv", npv, {**info_map, "units": MeasurementUnits.DOLLARS}) - self.om.add_variable("econ_dcfror_roi", roi, {**info_map, "units": MeasurementUnits.UNITLESS}) self.om.add_variable( "econ_dcfror_payback_period", payback, @@ -532,7 +528,7 @@ def goal_seek( self, variable_name: str, target_npv: float = 0.0, - bounds: Tuple[float, float] = (0.01, 100.0), + bounds: tuple[float, float] = (0.01, 100.0), tol: float = 1e-6, max_iter: int = 100, ) -> float: diff --git a/RUFAS/EEE/economics/partial_budget.py b/RUFAS/EEE/economics/partial_budget.py index b62bba8b2e..438e227948 100644 --- a/RUFAS/EEE/economics/partial_budget.py +++ b/RUFAS/EEE/economics/partial_budget.py @@ -2,12 +2,16 @@ from __future__ import annotations +import ast +from pathlib import Path +import re from typing import Any, Dict import numpy as np import pandas as pd import math +from RUFAS.EEE.economics.metrics import EconomicMetrics from RUFAS.input_manager import InputManager from RUFAS.output_manager import OutputManager from RUFAS.units import MeasurementUnits @@ -244,6 +248,17 @@ def calculate_partial_budget(self, preprocessed_data: Dict[str, Dict[str, Dict[s self.om.add_variable("econ_pba_net_annual_cash_flow", net_annual_cash_flow.tolist(), info_map) self.om.add_variable("econ_pba_summary", result_df.to_dict(orient="list"), info_map) self.om.add_log("PartialBudget", "Partial budget analysis completed.", info_map) + + should_run_roi_comparison: bool = self.im.get_data("economic_inputs.roi.compare_roi") + if should_run_roi_comparison: + revenue = float(revenue_total.item()) + costs = float(cost_total.item()) + current_simulation_roi = EconomicMetrics.calculate_roi(benefits=revenue, costs=costs) + comparison_roi_path_data: list[dict[str, str]] = \ + self.im.get_data("economic_inputs.roi.roi_comparison_paths") + for comparison in comparison_roi_path_data: + self._run_roi_comparison(comparison, current_simulation_roi) + return else: @@ -280,6 +295,100 @@ def calculate_partial_budget(self, preprocessed_data: Dict[str, Dict[str, Dict[s self.om.add_variable("econ_pba_summary", result_df.to_dict(orient="list"), info_map) self.om.add_log("PartialBudget", "Partial budget analysis completed.", info_map) + def _run_roi_comparison( + self, + comparison_roi_data: dict[str, str], + current_simulation_roi: float, + ) -> None: + """Compare a previous simulation ROI with the current simulation ROI.""" + info_map = { + "class": self.__class__.__name__, + "function": self._run_roi_comparison.__name__, + "units": MeasurementUnits.DOLLARS + } + + comparison_path = Path(comparison_roi_data["address"]) + comparison_pool = self.im.load_data_from_csv(comparison_path) + + comparison_revenues = self._extract_numeric_values( + comparison_pool, + r"\.RevenueTotal$", + ) + comparison_costs = self._extract_numeric_values( + comparison_pool, + r"\.CostTotal$", + ) + + if len(comparison_revenues) != len(comparison_costs): + raise ValueError( + "Comparison revenue and cost data must contain the same " + "number of values." + ) + + comparison_roi_name = comparison_roi_data["name"] + + for index, (comparison_revenue, comparison_cost) in enumerate( + zip(comparison_revenues, comparison_costs, strict=True) + ): + comparison_roi = EconomicMetrics.calculate_roi( + benefits=comparison_revenue, + costs=comparison_cost, + ) + roi_delta = current_simulation_roi - comparison_roi + + output_name = f"roi_delta_for_{comparison_roi_name}" + if len(comparison_revenues) > 1: + output_name = f"{output_name}_{index}" + + self.om.add_variable( + output_name, + roi_delta, + info_map, + ) + + def _extract_numeric_values( + self, + data: dict[str, Any], + column_pattern: str, + ) -> list[float]: + """Extract numeric values from the single column matching a pattern.""" + matching_columns = [ + column_name + for column_name in data + if re.search(column_pattern, column_name) + ] + + if len(matching_columns) != 1: + error_message = ( + f"In prepping ROI data, expected exactly one column matching {column_pattern!r}, " + f"but found {matching_columns}." + ) + self.om.add_error( + "ROI comparison error", + error_message, + { + "class": self.__class__.__name__, + "function": self._extract_numeric_values.__name__ + } + ) + raise ValueError(error_message) + + column_values = data[matching_columns[0]] + extracted_values: list[float] = [] + + for value in column_values: + if isinstance(value, str): + parsed_value = ast.literal_eval(value) + else: + parsed_value = value + + if isinstance(parsed_value, (list, tuple)): + extracted_values.extend(float(item) for item in parsed_value) + else: + extracted_values.append(float(parsed_value)) + + return extracted_values + def has_partial_budget_activity( self, preprocessed_data: Dict[str, Dict[str, Dict[str, Any]]] | None = None ) -> bool: diff --git a/RUFAS/input_manager.py b/RUFAS/input_manager.py index 6bcddeba32..f74397bfdc 100644 --- a/RUFAS/input_manager.py +++ b/RUFAS/input_manager.py @@ -545,7 +545,7 @@ def _runtime_data_loader_map(self) -> dict[str, Callable[[Path], dict[str, Any]] """Helper function for runtime data mapping.""" return { "json": self._load_data_from_json, - "csv": self._load_data_from_csv, + "csv": self.load_data_from_csv, } def _process_runtime_file( @@ -889,7 +889,7 @@ def _load_data_from_json(self, file_path: Path) -> dict[str, Any]: self.om.add_error(f"Unexpected error when loading file at path {file_path}: {e}", str(e), info_map) raise - def _load_data_from_csv(self, file_path: Path) -> dict[str, Any]: + def load_data_from_csv(self, file_path: Path) -> dict[str, Any]: """ Loads data from input csv file. @@ -917,7 +917,7 @@ def _load_data_from_csv(self, file_path: Path) -> dict[str, Any]: """ info_map = { "class": self.__class__.__name__, - "function": self._load_data_from_csv.__name__, + "function": self.load_data_from_csv.__name__, } self.om.add_log("open_csv_file", f"Attempting to open {file_path}.", info_map) try: @@ -972,7 +972,7 @@ def _populate_pool(self, input_root: Path, eager_termination: bool) -> bool: self.input_root = input_root data_type_to_loader_map: dict[str, Callable[[Path], dict[str, Any]]] = { "json": self._load_data_from_json, - "csv": self._load_data_from_csv, + "csv": self.load_data_from_csv, } valid_data = True for file_blob_key, file_details in self.__metadata["files"].items(): diff --git a/input/data/EEE/economic_inputs.json b/input/data/EEE/economic_inputs.json index 03da1e21da..7f27fa5a3f 100644 --- a/input/data/EEE/economic_inputs.json +++ b/input/data/EEE/economic_inputs.json @@ -189,5 +189,14 @@ "Cost": 15000.0 } ] + }, + "roi": { + "compare_roi": true, + "roi_comparison_paths": [ + { + "name": "base_freestall_costs_and_revenue", + "address": "output/reports/freestall_report_report_econ.json_04-Aug-2026_Tue_09-01-28.csv" + } + ] } } diff --git a/input/metadata/properties/default.json b/input/metadata/properties/default.json index 8517249e61..5c0cc64b53 100644 --- a/input/metadata/properties/default.json +++ b/input/metadata/properties/default.json @@ -3535,6 +3535,30 @@ } } } + }, + "roi": { + "type": "object", + "description": "Inputs related to return on investment calculations.", + "compare_roi": { + "type": "bool", + "description": "If true, triggers loading previous simulation roi results for comparison to current simulation roi.", + "default": false + }, + "roi_comparison_paths": { + "type": "array", + "description": "The list of saved roi output addresses the user wants to compare to the roi for the current simulation.", + "properties": { + "type": "object", + "name": { + "type": "string", + "description": "The user-specified name for the roi being used for comparison to the current simulation roi." + }, + "address": { + "type": "string", + "description": "The user-specified directory location of the saved roi comparison result." + } + } + } } }, "emissions_properties": { From bdf508c6a8146760e50a0a9b965a9e487e2390e0 Mon Sep 17 00:00:00 2001 From: Niko <70217952+ew3361zh@users.noreply.github.com> Date: Wed, 5 Aug 2026 14:12:02 -0400 Subject: [PATCH 2/4] fixes unit tests --- tests/test_EEE/test_partial_budget_outputs.py | 72 ++++++++++--------- tests/test_input_manager.py | 25 ++++--- 2 files changed, 52 insertions(+), 45 deletions(-) diff --git a/tests/test_EEE/test_partial_budget_outputs.py b/tests/test_EEE/test_partial_budget_outputs.py index 7a252d5083..174d3d6e1b 100644 --- a/tests/test_EEE/test_partial_budget_outputs.py +++ b/tests/test_EEE/test_partial_budget_outputs.py @@ -1,4 +1,5 @@ import pytest +from pytest_mock import MockerFixture from RUFAS.EEE.economics import partial_budget @@ -65,41 +66,48 @@ def test_partial_budget_exports_all_series(monkeypatch: pytest.MonkeyPatch) -> N def test_partial_budget_exports_net_annual_cash_flow_for_single_scenario( - monkeypatch: pytest.MonkeyPatch, -) -> None: - preprocessed = { - "Section": { - "Revenue": { - "Milk": { - "flow_type": "revenue", - "line_item_values_by_scenario": {"baseline": 120.0}, - } - }, - "Costs": { - "Feed": { - "flow_type": "cost", - "line_item_values_by_scenario": {"baseline": 80.0}, - } - }, + monkeypatch: pytest.MonkeyPatch, + mocker: MockerFixture, + ) -> None: + preprocessed = { + "Section": { + "Revenue": { + "Milk": { + "flow_type": "revenue", + "line_item_values_by_scenario": {"baseline": 120.0}, + } + }, + "Costs": { + "Feed": { + "flow_type": "cost", + "line_item_values_by_scenario": {"baseline": 80.0}, + } + }, + } } - } - dummy_im = object() - dummy_om = DummyOutputManager() + dummy_im = mocker.Mock() + dummy_im.get_data.return_value = False - monkeypatch.setattr(partial_budget, "InputManager", lambda: dummy_im) - monkeypatch.setattr(partial_budget, "OutputManager", lambda: dummy_om) + dummy_om = DummyOutputManager() - pb = partial_budget.PartialBudget() - pb.calculate_partial_budget(preprocessed) + monkeypatch.setattr(partial_budget, "InputManager", lambda: dummy_im) + monkeypatch.setattr(partial_budget, "OutputManager", lambda: dummy_om) - exported = {name: value for name, value, _ in dummy_om.variables} + pb = partial_budget.PartialBudget() + pb.calculate_partial_budget(preprocessed) - assert exported["econ_pba_net_annual_cash_flow"] == [40.0] - assert exported["econ_pba_revenue_total"] == [120.0] - assert exported["econ_pba_cost_total"] == [80.0] - assert exported["econ_pba_additional_revenue"] == [0.0] - assert exported["econ_pba_reduced_costs"] == [0.0] - assert exported["econ_pba_additional_costs"] == [0.0] - assert exported["econ_pba_reduced_revenue"] == [0.0] - assert "econ_pba_summary" in exported + exported = {name: value for name, value, _ in dummy_om.variables} + + assert exported["econ_pba_net_annual_cash_flow"] == [40.0] + assert exported["econ_pba_revenue_total"] == [120.0] + assert exported["econ_pba_cost_total"] == [80.0] + assert exported["econ_pba_additional_revenue"] == [0.0] + assert exported["econ_pba_reduced_costs"] == [0.0] + assert exported["econ_pba_additional_costs"] == [0.0] + assert exported["econ_pba_reduced_revenue"] == [0.0] + assert "econ_pba_summary" in exported + + dummy_im.get_data.assert_called_once_with( + "economic_inputs.roi.compare_roi" + ) diff --git a/tests/test_input_manager.py b/tests/test_input_manager.py index 693bb7625c..11145685f5 100644 --- a/tests/test_input_manager.py +++ b/tests/test_input_manager.py @@ -47,7 +47,7 @@ def input_manager_original_method_states( "_load_metadata": mock_input_manager._load_metadata, "_load_properties": mock_input_manager._load_properties, "_load_data_from_json": mock_input_manager._load_data_from_json, - "_load_data_from_csv": mock_input_manager._load_data_from_csv, + "load_data_from_csv": mock_input_manager.load_data_from_csv, "_populate_pool": mock_input_manager._populate_pool, "get_data": mock_input_manager.get_data, "get_metadata": mock_input_manager.get_metadata, @@ -344,16 +344,15 @@ def test_load_data_from_json_invalid_data_raises_error( def test_load_data_from_csv( mock_input_manager: InputManager, ) -> None: - """Unit test for function _load_data_from_csv with valid csv file in file input_manager.py""" + """Unit test for function load_data_from_csv with valid csv file in file input_manager.py""" dummy_csv_data = "key1,key2\na,1\nb,2\n" dummy_expected_data = {"key1": ["a", "b"], "key2": [1, 2]} file_path = Path("path/to/csv/file") with patch("builtins.open", mock_open(read_data=dummy_csv_data)): with patch("RUFAS.output_manager.OutputManager.add_log") as add_log: - result_data = mock_input_manager._load_data_from_csv(file_path) - - assert result_data == dummy_expected_data - assert add_log.call_count == 2 + result_data = mock_input_manager.load_data_from_csv(file_path) + assert result_data == dummy_expected_data + assert add_log.call_count == 2 def test_load_data_from_csv_missing_file_raises_error( @@ -363,7 +362,7 @@ def test_load_data_from_csv_missing_file_raises_error( with patch("builtins.open", side_effect=FileNotFoundError): with patch("RUFAS.output_manager.OutputManager.add_log") as add_log: with pytest.raises(FileNotFoundError): - mock_input_manager._load_data_from_csv(Path("non_existent_file.csv")) + mock_input_manager.load_data_from_csv(Path("non_existent_file.csv")) assert add_log.call_count == 1 @@ -375,7 +374,7 @@ def test_load_data_from_csv_invalid_data_raises_error( with patch("RUFAS.output_manager.OutputManager.add_log") as add_log: with patch("pandas.read_csv", side_effect=pd.errors.ParserError("Invalid CSV")): with pytest.raises(pd.errors.ParserError): - mock_input_manager._load_data_from_csv(Path("dummy_file.csv")) + mock_input_manager.load_data_from_csv(Path("dummy_file.csv")) assert add_log.call_count == 1 @@ -731,7 +730,7 @@ def test_populate_pool_valid( input_manager, "_load_data_from_json", side_effect=lambda _: {"element1": "value1", "element2": "value2"} ) mocker.patch.object( - input_manager, "_load_data_from_csv", side_effect=lambda _: {"element3": "value3", "element4": "value4"} + input_manager, "load_data_from_csv", side_effect=lambda _: {"element3": "value3", "element4": "value4"} ) mocker.patch.object(DataValidator, "validate_data_by_type", side_effect=lambda *args, **kwargs: True) mocker.patch.object(OutputManager, "add_log") @@ -761,7 +760,7 @@ def test_populate_pool_invalid( input_manager, "_load_data_from_json", side_effect=lambda _: {"element1": "value1", "element2": "value2"} ) mocker.patch.object( - input_manager, "_load_data_from_csv", side_effect=lambda _: {"element3": "value3", "element4": "value4"} + input_manager, "load_data_from_csv", side_effect=lambda _: {"element3": "value3", "element4": "value4"} ) mocker.patch.object(DataValidator, "validate_data_by_type", side_effect=lambda *args, **kwargs: False) mocker.patch.object(OutputManager, "add_log") @@ -792,7 +791,7 @@ def test_populate_pool_partial_invalid( input_manager, "_load_data_from_json", side_effect=lambda _: {"element1": "value1", "element2": "value2"} ) mocker.patch.object( - input_manager, "_load_data_from_csv", side_effect=lambda _: {"element3": "value3", "element4": "value4"} + input_manager, "load_data_from_csv", side_effect=lambda _: {"element3": "value3", "element4": "value4"} ) mocker.patch.object(DataValidator, "validate_data_by_type", side_effect=[True, False, True, False]) mocker.patch.object(OutputManager, "add_log") @@ -827,7 +826,7 @@ def test_populate_pool_eager_termination( input_manager, "_load_data_from_json", side_effect=lambda _: {"element1": "value1", "element2": "value2"} ) mocker.patch.object( - input_manager, "_load_data_from_csv", side_effect=lambda _: {"element3": "value3", "element4": "value4"} + input_manager, "load_data_from_csv", side_effect=lambda _: {"element3": "value3", "element4": "value4"} ) mocker.patch.object(DataValidator, "validate_data_by_type", side_effect=lambda *args, **kwargs: False) mocker.patch.object(OutputManager, "add_log") @@ -3677,7 +3676,7 @@ def test_load_runtime_metadata_success(mock_input_manager: InputManager, mocker: mock_input_manager.data_validator, "validate_metadata", return_value=(True, "") ) mock_input_manager.input_root = tmp_path - mocked_loader = mocker.patch.object(mock_input_manager, "_load_data_from_csv", return_value={"value": [1]}) + mocked_loader = mocker.patch.object(mock_input_manager, "load_data_from_csv", return_value={"value": [1]}) mocked_add = mocker.patch.object(mock_input_manager, "add_runtime_variable_to_pool", return_value=True) metadata_exists_spy = mocker.spy(mock_input_manager, "_metadata_properties_exist") From f483c8387dbbc59b2093e4d46b9253ad5374deae Mon Sep 17 00:00:00 2001 From: Niko <70217952+ew3361zh@users.noreply.github.com> Date: Wed, 5 Aug 2026 15:12:22 -0400 Subject: [PATCH 3/4] docstrings --- RUFAS/EEE/economics/partial_budget.py | 49 +++++++++++++++++-- tests/test_EEE/test_partial_budget_outputs.py | 49 ------------------- 2 files changed, 44 insertions(+), 54 deletions(-) diff --git a/RUFAS/EEE/economics/partial_budget.py b/RUFAS/EEE/economics/partial_budget.py index 438e227948..9f55935e42 100644 --- a/RUFAS/EEE/economics/partial_budget.py +++ b/RUFAS/EEE/economics/partial_budget.py @@ -300,7 +300,22 @@ def _run_roi_comparison( comparison_roi_data: dict[str, str], current_simulation_roi: float, ) -> None: - """Compare a previous simulation ROI with the current simulation ROI.""" + """ + Compare a previous simulation ROI with the current simulation ROI. + + Parameters + ---------- + comparison_roi_data : dict[str, str] + A dictionary containing the user specified locations and names for comparison roi data. + current_simulation_roi : float + The roi calculated for the current simulation. + + Raises + ------ + ValueError + If the comparison data has a different number of revenues and costs. + + """ info_map = { "class": self.__class__.__name__, "function": self._run_roi_comparison.__name__, @@ -320,10 +335,13 @@ def _run_roi_comparison( ) if len(comparison_revenues) != len(comparison_costs): - raise ValueError( - "Comparison revenue and cost data must contain the same " - "number of values." + error_message = "Comparison revenue and cost data must contain the same number of values." + self.om.add_error( + "ROI comparison error", + error_message, + info_map ) + raise ValueError(error_message) comparison_roi_name = comparison_roi_data["name"] @@ -351,7 +369,28 @@ def _extract_numeric_values( data: dict[str, Any], column_pattern: str, ) -> list[float]: - """Extract numeric values from the single column matching a pattern.""" + """ + Helper function for _run_roi_comparison(). + Extract numeric values from the single column matching a pattern. + + Parameters + ---------- + data : dict[str, Any] + The data structure from which the numeric values are extracted. + column_pattern : str + The regex pattern used to search the column name for the desired variable. + + Returns + ------- + list[float] + A list of floats extracted from the pattern-matched column of data in the data structure. + + Raises + ------ + ValueError + If there are multiple columns of matched data where we're only expecting one. + + """ matching_columns = [ column_name for column_name in data diff --git a/tests/test_EEE/test_partial_budget_outputs.py b/tests/test_EEE/test_partial_budget_outputs.py index 174d3d6e1b..745187990a 100644 --- a/tests/test_EEE/test_partial_budget_outputs.py +++ b/tests/test_EEE/test_partial_budget_outputs.py @@ -1,5 +1,4 @@ import pytest -from pytest_mock import MockerFixture from RUFAS.EEE.economics import partial_budget @@ -63,51 +62,3 @@ def test_partial_budget_exports_all_series(monkeypatch: pytest.MonkeyPatch) -> N assert exported["econ_pba_net_change"] == pytest.approx([7.0]) assert exported["econ_pba_cumulative_net_change"] == pytest.approx([7.0]) assert "econ_pba_summary" in exported - - -def test_partial_budget_exports_net_annual_cash_flow_for_single_scenario( - monkeypatch: pytest.MonkeyPatch, - mocker: MockerFixture, - ) -> None: - preprocessed = { - "Section": { - "Revenue": { - "Milk": { - "flow_type": "revenue", - "line_item_values_by_scenario": {"baseline": 120.0}, - } - }, - "Costs": { - "Feed": { - "flow_type": "cost", - "line_item_values_by_scenario": {"baseline": 80.0}, - } - }, - } - } - - dummy_im = mocker.Mock() - dummy_im.get_data.return_value = False - - dummy_om = DummyOutputManager() - - monkeypatch.setattr(partial_budget, "InputManager", lambda: dummy_im) - monkeypatch.setattr(partial_budget, "OutputManager", lambda: dummy_om) - - pb = partial_budget.PartialBudget() - pb.calculate_partial_budget(preprocessed) - - exported = {name: value for name, value, _ in dummy_om.variables} - - assert exported["econ_pba_net_annual_cash_flow"] == [40.0] - assert exported["econ_pba_revenue_total"] == [120.0] - assert exported["econ_pba_cost_total"] == [80.0] - assert exported["econ_pba_additional_revenue"] == [0.0] - assert exported["econ_pba_reduced_costs"] == [0.0] - assert exported["econ_pba_additional_costs"] == [0.0] - assert exported["econ_pba_reduced_revenue"] == [0.0] - assert "econ_pba_summary" in exported - - dummy_im.get_data.assert_called_once_with( - "economic_inputs.roi.compare_roi" - ) From fadddeccb86abe5d1826f944c9400c9d3c3942ab Mon Sep 17 00:00:00 2001 From: Niko <70217952+ew3361zh@users.noreply.github.com> Date: Wed, 5 Aug 2026 15:15:31 -0400 Subject: [PATCH 4/4] updating compare_roi input to false --- input/data/EEE/economic_inputs.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/input/data/EEE/economic_inputs.json b/input/data/EEE/economic_inputs.json index 7f27fa5a3f..cebc696ab1 100644 --- a/input/data/EEE/economic_inputs.json +++ b/input/data/EEE/economic_inputs.json @@ -191,7 +191,7 @@ ] }, "roi": { - "compare_roi": true, + "compare_roi": false, "roi_comparison_paths": [ { "name": "base_freestall_costs_and_revenue",