From cca3874587bdc2233217ee9cf8f0e431c6cbe8ff Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Tue, 2 Jun 2026 12:14:11 +0200 Subject: [PATCH 1/5] electricity pricing_: if last price key is before next calculated query time set last price key as query time --- .../modules/common/configurable_tariff.py | 12 +++- .../common/configurable_tariff_test.py | 60 +++++++++++++++++-- 2 files changed, 65 insertions(+), 7 deletions(-) diff --git a/packages/modules/common/configurable_tariff.py b/packages/modules/common/configurable_tariff.py index 37102716c0..0fd283ce18 100644 --- a/packages/modules/common/configurable_tariff.py +++ b/packages/modules/common/configurable_tariff.py @@ -64,7 +64,7 @@ def __update_et_provider_data(self) -> tuple[TariffState, int]: tariff_state = self._remove_outdated_prices(tariff_state, timeslot_length_seconds) return tariff_state, timeslot_length_seconds - def __calculate_next_query_time(self) -> None: + def _calculate_next_query_time(self, latest_price_timestamp: datetime) -> None: now = datetime.now() current_hour = now.hour + 1 # next full hour log.debug(f"Aktuelle Tarif Update Stunde: {current_hour} Uhr." @@ -81,6 +81,10 @@ def __calculate_next_query_time(self) -> None: timedelta(days=day_offset, minutes=random.randint(1, 7) * -5)) if next_query_time <= now: next_query_time += timedelta(hours=1) + if next_query_time < latest_price_timestamp: + # Falls die Preise nicht bis zum nächsten geplanten Abruf gültig sind, + # den Abruf auf die Gültigkeitsdauer der Preise setzen + next_query_time = latest_price_timestamp self.get.next_query_time = int(next_query_time.timestamp()) log.debug(f"Nächster Abruf der {self.tariff_type} Strompreise geplant für:" f" {next_query_time} (Unix Timestamp: {self.get.next_query_time})") @@ -89,8 +93,10 @@ def __calculate_next_query_time(self) -> None: def __call_component_updater(self) -> TariffState: last_known_timestamp = datetime.fromtimestamp(int(max(self.get.prices.keys()) if self.get.prices else 0)) tariff_state = self._component_updater() - if last_known_timestamp < datetime.fromtimestamp(int(max(tariff_state.prices.keys()))): - self.__calculate_next_query_time() + latest_price_timestamp = datetime.fromtimestamp( + int(max(tariff_state.prices.keys()) if tariff_state.prices else 0)) + if last_known_timestamp < latest_price_timestamp: + self._calculate_next_query_time(latest_price_timestamp) return tariff_state def __log_and_publish_progress(self, timeslot_length_seconds, tariff_state): diff --git a/packages/modules/common/configurable_tariff_test.py b/packages/modules/common/configurable_tariff_test.py index b5f274e162..414bbe9030 100644 --- a/packages/modules/common/configurable_tariff_test.py +++ b/packages/modules/common/configurable_tariff_test.py @@ -1,8 +1,11 @@ +from datetime import datetime, tzinfo +from typing import Optional from unittest.mock import Mock from helpermodules import timecheck import pytest from modules.common.component_state import TariffState +from modules.common import configurable_tariff from modules.common.configurable_tariff import ConfigurableFlexibleTariff from modules.electricity_pricing.flexible_tariffs.awattar.config import AwattarTariff @@ -94,9 +97,8 @@ def test_remove_outdated_prices( monkeypatch.setattr(timecheck, "create_timestamp", Mock(return_value=now)) # test - result = tariff._remove_outdated_prices( - tariff_state, time_slot_seconds[1] - time_slot_seconds[0] - ) + # pyright: ignore[reportPrivateUsage] + result = tariff._remove_outdated_prices(tariff_state, time_slot_seconds[1] - time_slot_seconds[0]) # assert assert result.prices == expected.prices @@ -117,4 +119,54 @@ def test_accept_no_prices_at_start(monkeypatch): monkeypatch.setattr(timecheck, "create_timestamp", Mock(return_value=5)) # test - do not fail - tariff._remove_outdated_prices(TariffState(), 1) + tariff._remove_outdated_prices(TariffState(), 1) # pyright: ignore[reportPrivateUsage] + + +def test_calculate_next_query_time(monkeypatch: pytest.MonkeyPatch): + # setup + fixed_now = datetime(2022, 5, 16, 10, 10, 0) + + class FixedDateTime(datetime): + @classmethod + def now(cls, tz: Optional[tzinfo] = None): + return fixed_now if tz is None else fixed_now.replace(tzinfo=tz) + + monkeypatch.setattr(configurable_tariff, "datetime", FixedDateTime) + monkeypatch.setattr(configurable_tariff.random, "randint", Mock(return_value=1)) + + tariff = ConfigurableFlexibleTariff(AwattarTariff(), Mock(return_value=Mock())) + tariff.tariff_update_hours = [14] + + latest_price_timestamp = datetime(2022, 5, 16, 13, 55, 0) + + # execution + tariff._calculate_next_query_time(latest_price_timestamp) # pyright: ignore[reportPrivateUsage] + + # evaluation + expected_timestamp = int(datetime(2022, 5, 16, 13, 55, 0).timestamp()) + assert tariff.get.next_query_time == expected_timestamp + + +def test_calculate_next_query_time_uses_latest_price_timestamp(monkeypatch: pytest.MonkeyPatch): + # setup + fixed_now = datetime(2022, 5, 16, 10, 10, 0) + + class FixedDateTime(datetime): + @classmethod + def now(cls, tz: Optional[tzinfo] = None): + return fixed_now if tz is None else fixed_now.replace(tzinfo=tz) + + monkeypatch.setattr(configurable_tariff, "datetime", FixedDateTime) + monkeypatch.setattr(configurable_tariff.random, "randint", Mock(return_value=1)) + + tariff = ConfigurableFlexibleTariff(AwattarTariff(), Mock(return_value=Mock())) + tariff.tariff_update_hours = [14] + + latest_price_timestamp = datetime(2022, 5, 16, 14, 30, 0) + + # execution + tariff._calculate_next_query_time(latest_price_timestamp) # pyright: ignore[reportPrivateUsage] + + # evaluation + expected_timestamp = int(latest_price_timestamp.timestamp()) + assert tariff.get.next_query_time == expected_timestamp From cf35fabfd5ef61d02e2afc6a107a07c4ceda8499 Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Wed, 3 Jun 2026 09:11:21 +0200 Subject: [PATCH 2/5] fix --- packages/modules/common/configurable_tariff.py | 4 +++- packages/modules/common/configurable_tariff_test.py | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/modules/common/configurable_tariff.py b/packages/modules/common/configurable_tariff.py index 0fd283ce18..2943ba6ce3 100644 --- a/packages/modules/common/configurable_tariff.py +++ b/packages/modules/common/configurable_tariff.py @@ -81,9 +81,11 @@ def _calculate_next_query_time(self, latest_price_timestamp: datetime) -> None: timedelta(days=day_offset, minutes=random.randint(1, 7) * -5)) if next_query_time <= now: next_query_time += timedelta(hours=1) - if next_query_time < latest_price_timestamp: + if latest_price_timestamp < next_query_time: # Falls die Preise nicht bis zum nächsten geplanten Abruf gültig sind, # den Abruf auf die Gültigkeitsdauer der Preise setzen + log.debug(f"Die Preise sind nur bis {latest_price_timestamp} gültig, " + f"setze nächsten Abruf auf dieses Datum.") next_query_time = latest_price_timestamp self.get.next_query_time = int(next_query_time.timestamp()) log.debug(f"Nächster Abruf der {self.tariff_type} Strompreise geplant für:" diff --git a/packages/modules/common/configurable_tariff_test.py b/packages/modules/common/configurable_tariff_test.py index 414bbe9030..6f06829450 100644 --- a/packages/modules/common/configurable_tariff_test.py +++ b/packages/modules/common/configurable_tariff_test.py @@ -137,7 +137,7 @@ def now(cls, tz: Optional[tzinfo] = None): tariff = ConfigurableFlexibleTariff(AwattarTariff(), Mock(return_value=Mock())) tariff.tariff_update_hours = [14] - latest_price_timestamp = datetime(2022, 5, 16, 13, 55, 0) + latest_price_timestamp = datetime(2022, 5, 17, 13, 55, 0) # execution tariff._calculate_next_query_time(latest_price_timestamp) # pyright: ignore[reportPrivateUsage] @@ -162,7 +162,7 @@ def now(cls, tz: Optional[tzinfo] = None): tariff = ConfigurableFlexibleTariff(AwattarTariff(), Mock(return_value=Mock())) tariff.tariff_update_hours = [14] - latest_price_timestamp = datetime(2022, 5, 16, 14, 30, 0) + latest_price_timestamp = datetime(2022, 5, 16, 12, 30, 0) # execution tariff._calculate_next_query_time(latest_price_timestamp) # pyright: ignore[reportPrivateUsage] From c2a3943589ebd223a98700b6ac7ccb85ff8911ff Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Wed, 3 Jun 2026 09:23:16 +0200 Subject: [PATCH 3/5] review --- packages/modules/common/configurable_tariff.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/modules/common/configurable_tariff.py b/packages/modules/common/configurable_tariff.py index 2943ba6ce3..0198b81e74 100644 --- a/packages/modules/common/configurable_tariff.py +++ b/packages/modules/common/configurable_tariff.py @@ -79,24 +79,23 @@ def _calculate_next_query_time(self, latest_price_timestamp: datetime) -> None: # reduce serverload on their site by trying early and randomizing query time minutes and seconds next_query_time = (now.replace(hour=next_hour, minute=0, second=0, microsecond=0) + timedelta(days=day_offset, minutes=random.randint(1, 7) * -5)) - if next_query_time <= now: - next_query_time += timedelta(hours=1) if latest_price_timestamp < next_query_time: # Falls die Preise nicht bis zum nächsten geplanten Abruf gültig sind, # den Abruf auf die Gültigkeitsdauer der Preise setzen log.debug(f"Die Preise sind nur bis {latest_price_timestamp} gültig, " f"setze nächsten Abruf auf dieses Datum.") next_query_time = latest_price_timestamp + if next_query_time <= now: + next_query_time = now + timedelta(hours=1) self.get.next_query_time = int(next_query_time.timestamp()) log.debug(f"Nächster Abruf der {self.tariff_type} Strompreise geplant für:" f" {next_query_time} (Unix Timestamp: {self.get.next_query_time})") Pub().pub(f"openWB/set/optional/ep/{self.tariff_type}/get/next_query_time", int(next_query_time.timestamp())) def __call_component_updater(self) -> TariffState: - last_known_timestamp = datetime.fromtimestamp(int(max(self.get.prices.keys()) if self.get.prices else 0)) + last_known_timestamp = datetime.fromtimestamp(max((int(ts) for ts in self.get.prices.keys()), default=0)) tariff_state = self._component_updater() - latest_price_timestamp = datetime.fromtimestamp( - int(max(tariff_state.prices.keys()) if tariff_state.prices else 0)) + latest_price_timestamp = datetime.fromtimestamp(max((int(ts) for ts in tariff_state.prices.keys()), default=0)) if last_known_timestamp < latest_price_timestamp: self._calculate_next_query_time(latest_price_timestamp) return tariff_state From a8e4412d8f36260bda88e8126be50b4e6f3e0501 Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Wed, 3 Jun 2026 09:44:37 +0200 Subject: [PATCH 4/5] review --- packages/modules/common/configurable_tariff.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/modules/common/configurable_tariff.py b/packages/modules/common/configurable_tariff.py index 0198b81e74..81793c65c4 100644 --- a/packages/modules/common/configurable_tariff.py +++ b/packages/modules/common/configurable_tariff.py @@ -1,4 +1,5 @@ from datetime import datetime, timedelta +import math import random from typing import TypeVar, Generic, Callable from helpermodules import timecheck @@ -86,7 +87,10 @@ def _calculate_next_query_time(self, latest_price_timestamp: datetime) -> None: f"setze nächsten Abruf auf dieses Datum.") next_query_time = latest_price_timestamp if next_query_time <= now: - next_query_time = now + timedelta(hours=1) + # Zeit um so viele volle Stunden erhöhen, bis sie in der Zukunft liegt, Minute-Offset bleibt erhalten + delta_seconds = (now - next_query_time).total_seconds() + hours_to_add = max(1, math.ceil(delta_seconds / ONE_HOUR_SECONDS)) + next_query_time = next_query_time + timedelta(hours=hours_to_add) self.get.next_query_time = int(next_query_time.timestamp()) log.debug(f"Nächster Abruf der {self.tariff_type} Strompreise geplant für:" f" {next_query_time} (Unix Timestamp: {self.get.next_query_time})") From eccafdfb513e0df2b61b53036fcd407ca13aa436 Mon Sep 17 00:00:00 2001 From: LKuemmel <76958050+LKuemmel@users.noreply.github.com> Date: Wed, 3 Jun 2026 09:50:03 +0200 Subject: [PATCH 5/5] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- packages/modules/common/configurable_tariff.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/modules/common/configurable_tariff.py b/packages/modules/common/configurable_tariff.py index 81793c65c4..9093badf26 100644 --- a/packages/modules/common/configurable_tariff.py +++ b/packages/modules/common/configurable_tariff.py @@ -89,7 +89,7 @@ def _calculate_next_query_time(self, latest_price_timestamp: datetime) -> None: if next_query_time <= now: # Zeit um so viele volle Stunden erhöhen, bis sie in der Zukunft liegt, Minute-Offset bleibt erhalten delta_seconds = (now - next_query_time).total_seconds() - hours_to_add = max(1, math.ceil(delta_seconds / ONE_HOUR_SECONDS)) + hours_to_add = max(1, math.floor(delta_seconds / ONE_HOUR_SECONDS) + 1) next_query_time = next_query_time + timedelta(hours=hours_to_add) self.get.next_query_time = int(next_query_time.timestamp()) log.debug(f"Nächster Abruf der {self.tariff_type} Strompreise geplant für:"