From 674b3bd698e237f897e6516765e6b1c76f2f8c3a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 27 Feb 2026 09:27:16 +0000 Subject: [PATCH 1/3] Initial plan From 018ebfed9fdb661aaf624ce5546e6242e83259a1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 27 Feb 2026 09:31:51 +0000 Subject: [PATCH 2/3] fix: reduce PV failsafe minimum from 18 to 12 hours The failsafe in ForecastSolarBaseclass was too aggressive when running between 0:00-2:00 AM since forecast data may not yet cover 18 hours ahead. Reduced minimum to 12 hours (12 intervals for 60-min resolution, 48 intervals for 15-min resolution). Fixes: interval count was also wrong for 15-min resolution (72 != 18*4). 48 slots at 15-min = exactly 12 hours. Co-authored-by: MaStr <1036501+MaStr@users.noreply.github.com> --- src/batcontrol/forecastsolar/baseclass.py | 8 ++++---- tests/batcontrol/forecastsolar/test_baseclass.py | 2 +- .../forecastsolar/test_baseclass_alignment.py | 12 ++++++------ 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/batcontrol/forecastsolar/baseclass.py b/src/batcontrol/forecastsolar/baseclass.py index dfdb2c9..767e369 100644 --- a/src/batcontrol/forecastsolar/baseclass.py +++ b/src/batcontrol/forecastsolar/baseclass.py @@ -151,15 +151,15 @@ def get_forecast(self) -> dict[int, float]: # Validate minimum forecast length if self.target_resolution == 60: - min_intervals = 18 # 18 hours + min_intervals = 12 # 12 hours else: # 15 minutes - min_intervals = 72 # 18 hours * 4 = 72 intervals + min_intervals = 48 # 12 hours * 4 = 48 intervals max_interval = max(current_aligned_forecast.keys()) if current_aligned_forecast else 0 if max_interval < min_intervals: - logger.error('Less than 18 hours of forecast data. Got %d intervals, need %d.', + logger.error('Less than 12 hours of forecast data. Got %d intervals, need %d.', max_interval, min_intervals) - raise RuntimeError('Less than 18 hours of forecast data.') + raise RuntimeError('Less than 12 hours of forecast data.') return current_aligned_forecast diff --git a/tests/batcontrol/forecastsolar/test_baseclass.py b/tests/batcontrol/forecastsolar/test_baseclass.py index 4264cc7..d778f59 100644 --- a/tests/batcontrol/forecastsolar/test_baseclass.py +++ b/tests/batcontrol/forecastsolar/test_baseclass.py @@ -341,7 +341,7 @@ def mock_forecast(): mock_forecast_func=mock_forecast ) - with pytest.raises(RuntimeError, match="Less than 18 hours"): + with pytest.raises(RuntimeError, match="Less than 12 hours"): instance.get_forecast() def test_base_class_not_implemented_errors(self, single_installation, timezone): diff --git a/tests/batcontrol/forecastsolar/test_baseclass_alignment.py b/tests/batcontrol/forecastsolar/test_baseclass_alignment.py index 779fdf3..40617b9 100644 --- a/tests/batcontrol/forecastsolar/test_baseclass_alignment.py +++ b/tests/batcontrol/forecastsolar/test_baseclass_alignment.py @@ -317,7 +317,7 @@ def test_minimum_forecast_validation_hourly(self, pvinstallations, timezone): mock_datetime.timezone = datetime.timezone with patch.object(provider, 'refresh_data'): - with pytest.raises(RuntimeError, match="Less than 18 hours"): + with pytest.raises(RuntimeError, match="Less than 12 hours"): provider.get_forecast() def test_minimum_forecast_validation_15min(self, pvinstallations, timezone): @@ -327,8 +327,8 @@ def test_minimum_forecast_validation_15min(self, pvinstallations, timezone): target_resolution=15, native_resolution=15 ) - # Set insufficient data (less than 72 intervals = 18 hours) - data_15min = {i: 250 for i in range(50)} + # Set insufficient data (less than 48 intervals = 12 hours) + data_15min = {i: 250 for i in range(40)} provider.set_mock_data(data_15min) mock_time = datetime.datetime(2024, 1, 1, 10, 0, 0, tzinfo=timezone) @@ -338,7 +338,7 @@ def test_minimum_forecast_validation_15min(self, pvinstallations, timezone): mock_datetime.timezone = datetime.timezone with patch.object(provider, 'refresh_data'): - with pytest.raises(RuntimeError, match="Less than 18 hours"): + with pytest.raises(RuntimeError, match="Less than 12 hours"): provider.get_forecast() @@ -394,8 +394,8 @@ def test_scenario_1020_with_15min(self, pvinstallations, timezone): assert 0 in result assert result[0] > 0 # Should have some solar production - # Result should have many intervals (at least 72 for 18 hours) - assert len(result) >= 72 + # Result should have many intervals (at least 48 for 12 hours) + assert len(result) >= 48 def test_scenario_matching_doc_example(self, pvinstallations, timezone): """ From 31debe2ff73e489ab0a8b8e17f13835897c7ea5a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 27 Feb 2026 10:51:15 +0000 Subject: [PATCH 3/3] fix: use len() instead of max key for forecast interval count check Using max(keys()) gives the largest 0-based index, which is one less than the actual interval count and causes a spurious failure when data has exactly the required number of intervals. Switch to len() which correctly reflects the actual count. Co-authored-by: MaStr <1036501+MaStr@users.noreply.github.com> --- src/batcontrol/forecastsolar/baseclass.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/batcontrol/forecastsolar/baseclass.py b/src/batcontrol/forecastsolar/baseclass.py index 767e369..5035fb2 100644 --- a/src/batcontrol/forecastsolar/baseclass.py +++ b/src/batcontrol/forecastsolar/baseclass.py @@ -155,10 +155,10 @@ def get_forecast(self) -> dict[int, float]: else: # 15 minutes min_intervals = 48 # 12 hours * 4 = 48 intervals - max_interval = max(current_aligned_forecast.keys()) if current_aligned_forecast else 0 - if max_interval < min_intervals: + num_intervals = len(current_aligned_forecast) + if num_intervals < min_intervals: logger.error('Less than 12 hours of forecast data. Got %d intervals, need %d.', - max_interval, min_intervals) + num_intervals, min_intervals) raise RuntimeError('Less than 12 hours of forecast data.') return current_aligned_forecast