diff --git a/packages/modules/common/configurable_tariff.py b/packages/modules/common/configurable_tariff.py index 37102716c0..9093badf26 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 @@ -64,7 +65,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." @@ -79,18 +80,28 @@ def __calculate_next_query_time(self) -> 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 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 += 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.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:" 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() - if last_known_timestamp < datetime.fromtimestamp(int(max(tariff_state.prices.keys()))): - self.__calculate_next_query_time() + 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 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..6f06829450 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, 17, 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, 12, 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