From 4f37fcbe0b2eeb8bf14da6e9d39f7530e5c7695c Mon Sep 17 00:00:00 2001 From: Thomas aus Welkers <14850347+tpd-opitz@users.noreply.github.com> Date: Sun, 29 Mar 2026 11:05:06 +0000 Subject: [PATCH 1/6] introduce weekdays to fixed_hours tariff --- .../flexible_tariffs/fixed_hours/config.py | 2 + .../flexible_tariffs/fixed_hours/tariff.py | 40 ++++++++++++--- .../fixed_hours/tariff_test.py | 50 +++++++++++++++++++ 3 files changed, 85 insertions(+), 7 deletions(-) diff --git a/packages/modules/electricity_pricing/flexible_tariffs/fixed_hours/config.py b/packages/modules/electricity_pricing/flexible_tariffs/fixed_hours/config.py index 4e39618144..dbf78dcd0d 100644 --- a/packages/modules/electricity_pricing/flexible_tariffs/fixed_hours/config.py +++ b/packages/modules/electricity_pricing/flexible_tariffs/fixed_hours/config.py @@ -12,6 +12,7 @@ class FixedHoursTariffConfiguration: "price": 0.20, "active_times": { "dates": [("01-01", "31-03"), ("01-07", "30-09")], # applicable date ranges (day-month) + "weekdays": [0, 1, 2, 3, 4], # active on weekdays (0=Monday, ..., 6=Sunday) "times": [("08:00", "12:00"), ("18:00", "22:00")] # active times during the day } }, @@ -20,6 +21,7 @@ class FixedHoursTariffConfiguration: "price": 0.05, "active_times": { "dates": [("01-04", "30-06"), ("01-10", "31-12")], # applicable date ranges (day-month) + "weekdays": [5, 6], # active on weekends (0=Monday, ..., 6=Sunday) "times": [("00:00", "06:00"), ("22:00", "23:59")] # active times during the day } } diff --git a/packages/modules/electricity_pricing/flexible_tariffs/fixed_hours/tariff.py b/packages/modules/electricity_pricing/flexible_tariffs/fixed_hours/tariff.py index 9754a7281a..23649602e5 100644 --- a/packages/modules/electricity_pricing/flexible_tariffs/fixed_hours/tariff.py +++ b/packages/modules/electricity_pricing/flexible_tariffs/fixed_hours/tariff.py @@ -3,6 +3,7 @@ import datetime import time from typing import List, Tuple, Dict +from dataclasses import dataclass from modules.electricity_pricing.flexible_tariffs.fixed_hours.config import (FixedHoursTariff, FixedHoursTariffConfiguration) @@ -12,6 +13,14 @@ log = logging.getLogger(__name__) +@dataclass +class TimeSlot: + start_time: datetime.time + end_time: datetime.time + weekdays: List[int] + dates: List[Tuple[str, str]] + + def _to_time(time_str: str) -> datetime.time: if time_str == "24:00": return datetime.time(23, 59, 59) @@ -25,17 +34,31 @@ def _to_date(date_str: str, time_slot: datetime.datetime) -> datetime.date: return date +def _date_ranges_overlap(slot1: TimeSlot, slot2: TimeSlot) -> bool: + def _single_range_overlap(range1: Tuple[str, str], range2: Tuple[str, str]) -> bool: + start1, end1 = _to_date(range1[0], datetime.datetime.now()), _to_date(range1[1], datetime.datetime.now()) + start2, end2 = _to_date(range2[0], datetime.datetime.now()), _to_date(range2[1], datetime.datetime.now()) + return start1 <= end2 and end1 >= start2 + return any(_single_range_overlap(r1, r2) for r1 in slot1.dates for r2 in slot2.dates) + + def _validate_tariff_times(config: FixedHoursTariffConfiguration) -> None: - time_slots: List[Tuple[datetime.time, datetime.time, List[Tuple[str, str]]]] = [] + time_slots: List[TimeSlot] = [] for tariff in config.tariffs: for start, end in tariff["active_times"]["times"]: start_time = _to_time(start) end_time = _to_time(end) - for existing_start, existing_end, existing_dates in time_slots: - if (start_time < existing_end and end_time > existing_start and - any(start <= existing_end and end >= existing_start for start, end in existing_dates)): - raise ValueError(f"Overlapping time window detected: {start} - {end} in tariff '{tariff['name']}'") - time_slots.append((start_time, end_time, tariff["active_times"]["dates"])) + weekdays = tariff["active_times"]["weekdays"] + dates = tariff["active_times"]["dates"] + current_slot = TimeSlot(start_time, end_time, weekdays, dates) + for existing in time_slots: + if (start_time < existing.end_time and end_time > existing.start_time and + not set(weekdays).isdisjoint(existing.weekdays) and + _date_ranges_overlap(current_slot, existing)): + overlapping_weekdays = set(weekdays) & set(existing.weekdays) + raise ValueError(f"Overlapping time window detected: {start} - {end}" + f" on weekdays {sorted(overlapping_weekdays)} in tariff '{tariff['name']}'") + time_slots.append(current_slot) def _fetch(config: FixedHoursTariffConfiguration) -> TariffState: @@ -52,8 +75,11 @@ def _fetch_price_for_time_slot(config: FixedHoursTariffConfiguration, (_to_date(start, time_slot), _to_date(end, time_slot)) for start, end in tariff["active_times"]["dates"] ] + # If no weekdays specified, assume all days are active + weekdays = tariff["active_times"]["weekdays"] or range(7) if (any(start <= time_slot.time() < end for start, end in active_times) and - any(start <= time_slot.date() <= end for start, end in active_dates)): + any(start <= time_slot.date() <= end for start, end in active_dates) and + time_slot.weekday() in weekdays): return tariff["price"] return config.default_price diff --git a/packages/modules/electricity_pricing/flexible_tariffs/fixed_hours/tariff_test.py b/packages/modules/electricity_pricing/flexible_tariffs/fixed_hours/tariff_test.py index ae4c01d6a1..f07ba9e90a 100644 --- a/packages/modules/electricity_pricing/flexible_tariffs/fixed_hours/tariff_test.py +++ b/packages/modules/electricity_pricing/flexible_tariffs/fixed_hours/tariff_test.py @@ -133,6 +133,56 @@ class TestValidateTariffTimes: True, id="adjacent_times", ), + # Overlapping times but different weekdays + pytest.param( + [ + { + "name": "tariff1", + "price": 100, + "active_times": { + "dates": [("01-01", "31-12")], + "times": [("08:00", "12:00")], + "weekdays": [0, 1, 2, 3, 4], # active on weekdays + }, + }, + { + "name": "tariff2", + "price": 200, + "active_times": { + "dates": [("01-01", "31-12")], + "times": [("08:00", "12:00")], + "weekdays": [5, 6], # active on weekends + }, + }, + ], + True, + id="differing weekdays allows overlap", + ), + # Overlapping times one day overlapping + pytest.param( + [ + { + "name": "tariff1", + "price": 100, + "active_times": { + "dates": [("01-01", "31-12")], + "times": [("08:00", "12:00")], + "weekdays": [0, 1, 2, 3, 4], # active on weekdays + }, + }, + { + "name": "tariff2", + "price": 200, + "active_times": { + "dates": [("01-01", "31-12")], + "times": [("08:00", "12:00")], + "weekdays": [4, 5, 6], # active on weekends + }, + }, + ], + False, + id="differing weekdays one day overlaps", + ), # Empty tariffs pytest.param([], True, id="empty_tariffs"), ], From dddbbde53a0ee81cbcd95cea5378f43e66e151c2 Mon Sep 17 00:00:00 2001 From: Thomas aus Welkers <14850347+tpd-opitz@users.noreply.github.com> Date: Wed, 1 Apr 2026 20:23:16 +0200 Subject: [PATCH 2/6] update datastore to version 123 --- packages/helpermodules/update_config.py | 16 ++++++++++- .../helpermodules/upgrade_datastore_124.json | 27 +++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 packages/helpermodules/upgrade_datastore_124.json diff --git a/packages/helpermodules/update_config.py b/packages/helpermodules/update_config.py index 7e108667ba..4f4affd7d9 100644 --- a/packages/helpermodules/update_config.py +++ b/packages/helpermodules/update_config.py @@ -58,7 +58,7 @@ class UpdateConfig: - DATASTORE_VERSION = 132 + DATASTORE_VERSION = 133 valid_topic = [ "^openWB/bat/config/bat_control_activated$", @@ -3406,3 +3406,17 @@ def upgrade(topic: str, payload) -> None: return {topic: payload} self._loop_all_received_topics(upgrade) self._append_datastore_version(132) + + def upgrade_datastore_133(self) -> None: + def upgrade(topic: str, payload) -> None: + if "openWB/optional/ep/grid_fee/provider" == topic: + provider = decode_payload(payload) + if provider["type"] == "fixed_hours": + if provider["configuration"]: + if provider["configuration"]["tariffs"]: + for tariff in provider["configuration"]["tariffs"] or []: + if tariff["weekdays"] is None: + tariff["weekdays"] = list(range(7)) + return {topic: provider} + self._loop_all_received_topics(upgrade) + self._append_datastore_version(133) diff --git a/packages/helpermodules/upgrade_datastore_124.json b/packages/helpermodules/upgrade_datastore_124.json new file mode 100644 index 0000000000..bd7817ddae --- /dev/null +++ b/packages/helpermodules/upgrade_datastore_124.json @@ -0,0 +1,27 @@ +[ + { + "openWB/optional/ep/grid_fee/provider": { + "type": "fixed_hours", + "name": "Grid Fee Provider", + "configuration": { + "tariffs": [ + { + "start": "00:00", + "end": "06:00", + "price": 0.1 + }, + { + "start": "06:00", + "end": "22:00", + "price": 0.2 + }, + { + "start": "22:00", + "end": "00:00", + "price": 0.15 + } + ] + } + } + } +] \ No newline at end of file From 20f2c5f6961b8fc90075af145325849c52b8a18c Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Thu, 9 Jul 2026 09:06:05 +0200 Subject: [PATCH 3/6] fix pytest --- .../flexible_tariffs/fixed_hours/tariff_test.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/modules/electricity_pricing/flexible_tariffs/fixed_hours/tariff_test.py b/packages/modules/electricity_pricing/flexible_tariffs/fixed_hours/tariff_test.py index f07ba9e90a..2febc8a30e 100644 --- a/packages/modules/electricity_pricing/flexible_tariffs/fixed_hours/tariff_test.py +++ b/packages/modules/electricity_pricing/flexible_tariffs/fixed_hours/tariff_test.py @@ -96,6 +96,7 @@ class TestValidateTariffTimes: "active_times": { "dates": [("01-01", "31-12")], "times": [("08:00", "12:00")], + "weekdays": [0, 1, 2, 3, 4, 5, 6], }, }, { @@ -104,6 +105,7 @@ class TestValidateTariffTimes: "active_times": { "dates": [("01-01", "31-12")], "times": [("14:00", "18:00")], + "weekdays": [0, 1, 2, 3, 4, 5, 6], }, }, ], @@ -119,6 +121,7 @@ class TestValidateTariffTimes: "active_times": { "dates": [("01-01", "31-12")], "times": [("08:00", "12:00")], + "weekdays": [0, 1, 2, 3, 4, 5, 6], }, }, { @@ -127,6 +130,7 @@ class TestValidateTariffTimes: "active_times": { "dates": [("01-01", "31-12")], "times": [("12:00", "14:00")], + "weekdays": [0, 1, 2, 3, 4, 5, 6], }, }, ], @@ -216,6 +220,7 @@ class TestFetch: "active_times": { "dates": [("01-01", "31-12")], "times": [("00:00", "24:00")], + "weekdays": [0, 1, 2, 3, 4, 5, 6], }, } ], @@ -232,6 +237,7 @@ class TestFetch: "active_times": { "dates": [("01-01", "31-12")], "times": [("00:00", "24:00")], + "weekdays": [0, 1, 2, 3, 4, 5, 6], }, } ], @@ -273,6 +279,7 @@ def test_fetch_price_configurations(self, default_price, tariffs, expected_price "active_times": { "dates": [("01-01", "31-12")], "times": [("00:00", "24:00")], + "weekdays": [0, 1, 2, 3, 4, 5, 6], }, } ], @@ -323,6 +330,7 @@ class TestCreateElectricityTariff: "active_times": { "dates": [("01-01", "31-12")], "times": [("00:00", "24:00")], + "weekdays": [0, 1, 2, 3, 4, 5, 6], }, } ], @@ -339,6 +347,7 @@ class TestCreateElectricityTariff: "active_times": { "dates": [("01-01", "31-12")], "times": [("00:00", "24:00")], + "weekdays": [0, 1, 2, 3, 4, 5, 6], }, } ], @@ -354,6 +363,7 @@ class TestCreateElectricityTariff: "active_times": { "dates": [("01-01", "01-01")], "times": [("00:00", "00:00")], + "weekdays": [0, 1, 2, 3, 4, 5, 6], }, } ], From ecd829c84b67e6a2cb0f66ae28313e5eeb8eb8d3 Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Tue, 14 Jul 2026 10:43:11 +0200 Subject: [PATCH 4/6] grid fee and flexible tariff --- packages/helpermodules/update_config.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/helpermodules/update_config.py b/packages/helpermodules/update_config.py index 4f4affd7d9..398e34b7a0 100644 --- a/packages/helpermodules/update_config.py +++ b/packages/helpermodules/update_config.py @@ -3409,7 +3409,8 @@ def upgrade(topic: str, payload) -> None: def upgrade_datastore_133(self) -> None: def upgrade(topic: str, payload) -> None: - if "openWB/optional/ep/grid_fee/provider" == topic: + if ("openWB/optional/ep/flexible_tariff/provider" == topic or + "openWB/optional/ep/grid_fee/provider" == topic): provider = decode_payload(payload) if provider["type"] == "fixed_hours": if provider["configuration"]: From e75ca8c2b41b6b488ea3419c0de0011382fd31db Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Tue, 14 Jul 2026 11:05:14 +0200 Subject: [PATCH 5/6] fix --- packages/helpermodules/update_config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/helpermodules/update_config.py b/packages/helpermodules/update_config.py index 398e34b7a0..f85dd8c8f0 100644 --- a/packages/helpermodules/update_config.py +++ b/packages/helpermodules/update_config.py @@ -3416,7 +3416,7 @@ def upgrade(topic: str, payload) -> None: if provider["configuration"]: if provider["configuration"]["tariffs"]: for tariff in provider["configuration"]["tariffs"] or []: - if tariff["weekdays"] is None: + if tariff.get("weekdays") is None: tariff["weekdays"] = list(range(7)) return {topic: provider} self._loop_all_received_topics(upgrade) From 6ea012045084f49e09e9db412b3bfe8f0fc35a9b Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Tue, 14 Jul 2026 11:16:13 +0200 Subject: [PATCH 6/6] remove obsolet file --- .../helpermodules/upgrade_datastore_124.json | 27 ------------------- 1 file changed, 27 deletions(-) delete mode 100644 packages/helpermodules/upgrade_datastore_124.json diff --git a/packages/helpermodules/upgrade_datastore_124.json b/packages/helpermodules/upgrade_datastore_124.json deleted file mode 100644 index bd7817ddae..0000000000 --- a/packages/helpermodules/upgrade_datastore_124.json +++ /dev/null @@ -1,27 +0,0 @@ -[ - { - "openWB/optional/ep/grid_fee/provider": { - "type": "fixed_hours", - "name": "Grid Fee Provider", - "configuration": { - "tariffs": [ - { - "start": "00:00", - "end": "06:00", - "price": 0.1 - }, - { - "start": "06:00", - "end": "22:00", - "price": 0.2 - }, - { - "start": "22:00", - "end": "00:00", - "price": 0.15 - } - ] - } - } - } -] \ No newline at end of file