diff --git a/packages/helpermodules/update_config.py b/packages/helpermodules/update_config.py index a92d0979cb..0987ef9a05 100644 --- a/packages/helpermodules/update_config.py +++ b/packages/helpermodules/update_config.py @@ -57,7 +57,7 @@ class UpdateConfig: - DATASTORE_VERSION = 124 + DATASTORE_VERSION = 125 valid_topic = [ "^openWB/bat/config/bat_control_activated$", @@ -3132,3 +3132,31 @@ def upgrade(topic: str, payload) -> Optional[dict]: return {topic: configuration_payload} self._loop_all_received_topics(upgrade) self._append_datastore_version(124) + + def upgrade_datastore_125(self) -> None: + def upgrade(topic: str, payload) -> None: + if "openWB/optional/ep/flexible_tariff/provider" == topic: + provider = decode_payload(payload) + if provider["type"] == "energycharts": + surcharge = provider["configuration"]["surcharge"] + if surcharge > 0.1: # entspricht mehr als 0.1ct/kWh konfiguriertem Zuschlag + provider["configuration"]["surcharge"] = surcharge / 100000 + return {topic: provider} + 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": + changed = False + for i, tarif in enumerate(provider["configuration"]["tariffs"]): + price = tarif.get("price", 0) + if price > 0.001: # entspricht mehr als 0,1ct/kWh konfiguriertem Tarifpreis + provider["configuration"]["tariffs"][i]["price"] = price / 1000 + changed = True + default_price = provider["configuration"].get("default_price", 0) + if default_price > 0.001: + provider["configuration"]["default_price"] = default_price / 1000 + changed = True + if changed: + return {topic: provider} + self._loop_all_received_topics(upgrade) + self._append_datastore_version(125) diff --git a/packages/helpermodules/update_config_test.py b/packages/helpermodules/update_config_test.py index 7c6a23962e..18e036b162 100644 --- a/packages/helpermodules/update_config_test.py +++ b/packages/helpermodules/update_config_test.py @@ -149,3 +149,87 @@ def test_upgrade_datastore_122(name, monkeypatch): # Assert assert mock_dump.call_args_list[0].args[0] == expected_content assert uc.all_received_topics["openWB/system/datastore_version"] == [122] + + +def test_upgrade_datastore_125_converts_only_tariff_prices_and_persists(mock_pub): + uc = UpdateConfig() + uc.all_received_topics = { + "openWB/optional/ep/flexible_tariff/provider": { + "type": "fixed_hours", + "configuration": { + "tariffs": [ + {"price": 0.15}, + {"price": 0.09}, + ], + "default_price": 0.01, + }, + }, + "openWB/system/datastore_version": [123], + } + + uc.upgrade_datastore_125() + + provider = uc.all_received_topics["openWB/optional/ep/flexible_tariff/provider"] + assert provider["configuration"]["tariffs"][0]["price"] == pytest.approx(0.00015) + assert provider["configuration"]["tariffs"][1]["price"] == pytest.approx(0.00009) + assert provider["configuration"]["default_price"] == pytest.approx(0.00001) + assert uc.all_received_topics["openWB/system/datastore_version"] == [123, 125] + + updated_topics = [call.args[0] for call in mock_pub.pub.call_args_list] + assert "openWB/optional/ep/flexible_tariff/provider" in updated_topics + assert "openWB/system/datastore_version" in updated_topics + + +def test_upgrade_datastore_125_converts_energycharts_surcharge(mock_pub): + uc = UpdateConfig() + uc.all_received_topics = { + "openWB/optional/ep/flexible_tariff/provider": { + "type": "energycharts", + "configuration": { + "surcharge": 10, + }, + }, + "openWB/system/datastore_version": [123], + } + + uc.upgrade_datastore_125() + + provider = uc.all_received_topics["openWB/optional/ep/flexible_tariff/provider"] + assert provider["configuration"]["surcharge"] == pytest.approx(0.0001) + assert uc.all_received_topics["openWB/system/datastore_version"] == [123, 125] + + updated_topics = [call.args[0] for call in mock_pub.pub.call_args_list] + assert "openWB/optional/ep/flexible_tariff/provider" in updated_topics + + +def test_upgrade_datastore_125_is_idempotent_for_already_converted_values(mock_pub): + uc = UpdateConfig() + uc.all_received_topics = { + "openWB/optional/ep/flexible_tariff/provider": { + "type": "energycharts", + "configuration": { + "surcharge": 0.00015, + }, + }, + "openWB/optional/ep/grid_fee/provider": { + "type": "fixed_hours", + "configuration": { + "tariffs": [ + {"price": 0.00006}, + {"price": 0.00099}, + ], + "default_price": 0.00008, + }, + }, + "openWB/system/datastore_version": [123, 124], + } + + expected_flexible = json.loads(json.dumps(uc.all_received_topics["openWB/optional/ep/flexible_tariff/provider"])) + expected_grid_fee = json.loads(json.dumps(uc.all_received_topics["openWB/optional/ep/grid_fee/provider"])) + + uc.upgrade_datastore_125() + + assert uc.all_received_topics["openWB/optional/ep/flexible_tariff/provider"] == expected_flexible + assert uc.all_received_topics["openWB/optional/ep/grid_fee/provider"] == expected_grid_fee + assert uc.all_received_topics["openWB/system/datastore_version"] == [123, 124, 125] + assert mock_pub.pub.call_count == 1 # einmal publishen für Upgrade der Datastore-Version diff --git a/packages/modules/common/store/_tariff.py b/packages/modules/common/store/_tariff.py index 4b9a30f24d..a2ac5e31af 100644 --- a/packages/modules/common/store/_tariff.py +++ b/packages/modules/common/store/_tariff.py @@ -72,11 +72,11 @@ def __reduce_prices(prices: Dict[str, float]) -> Dict[int, float]: def __get_default_grid_fee_price(prices: Dict[str, float]) -> float: if (data.data.optional_data.grid_fee_module is not None and - hasattr(data.data.optional_data.grid_fee_module.config, "default_price") and - data.data.optional_data.grid_fee_module.config.default_price is not None): + hasattr(data.data.optional_data.grid_fee_module.config.configuration, "default_price") and + data.data.optional_data.grid_fee_module.config.configuration.default_price is not None): log.debug("Using default grid fee price from configuration: " + - f"{data.data.optional_data.grid_fee_module.config.default_price}") - return data.data.optional_data.grid_fee_module.config.default_price + f"{data.data.optional_data.grid_fee_module.config.configuration.default_price}") + return data.data.optional_data.grid_fee_module.config.configuration.default_price else: # Fallback: Median der vorhandenen Netzentgelte wird # als Schätzung für das Standard-Netzentgelt verwenden @@ -95,8 +95,8 @@ def __normalize_grid_fee_prices(grid_fee_prices: Dict[int, float], max_timestamp grid_fee_prices = {int(float(k)): v for k, v in grid_fee_prices.items() if int(float(k)) <= max_timestamp} if (data.data.optional_data.flexible_tariff_module is not None and - hasattr(data.data.optional_data.flexible_tariff_module.config, "includes_grid_fee") and - data.data.optional_data.flexible_tariff_module.config.includes_grid_fee is False): + hasattr(data.data.optional_data.flexible_tariff_module.config.configuration, "includes_grid_fee") and + data.data.optional_data.flexible_tariff_module.config.configuration.includes_grid_fee is False): return grid_fee_prices else: # Bei flexiblen Tarifen, die das Netzentgelt _nicht_ enthalten, diff --git a/packages/modules/common/store/_tariff_test.py b/packages/modules/common/store/_tariff_test.py index 4e478c57f4..233c5b86bb 100644 --- a/packages/modules/common/store/_tariff_test.py +++ b/packages/modules/common/store/_tariff_test.py @@ -1,5 +1,6 @@ from datetime import timedelta import datetime +from types import SimpleNamespace from typing import Dict from unittest.mock import Mock @@ -30,12 +31,16 @@ def mock_data() -> None: data.data_init(Mock()) data.data.optional_data = Optional() data.data.optional_data.grid_fee_module = Mock() - data.data.optional_data.grid_fee_module.config = Mock() - data.data.optional_data.grid_fee_module.config.default_price = None + data.data.optional_data.grid_fee_module.config = SimpleNamespace( + configuration=SimpleNamespace(default_price=None) + ) data.data.optional_data.flexible_tariff_module = Mock() - data.data.optional_data.flexible_tariff_module.config = Mock() - data.data.optional_data.flexible_tariff_module.config.default_price = None - data.data.optional_data.flexible_tariff_module.config.includes_grid_fee = True + data.data.optional_data.flexible_tariff_module.config = SimpleNamespace( + configuration=SimpleNamespace( + default_price=None, + includes_grid_fee=True + ) + ) @pytest.mark.parametrize( @@ -146,8 +151,8 @@ def test_sum_prices( flexible_tariff ) data.data.optional_data.data.electricity_pricing.grid_fee.get.prices = grid_fee - data.data.optional_data.flexible_tariff_module.config.includes_grid_fee = includes_grid_fee - data.data.optional_data.grid_fee_module.config.default_price = default_grid_fee_price + data.data.optional_data.flexible_tariff_module.config.configuration.includes_grid_fee = includes_grid_fee + data.data.optional_data.grid_fee_module.config.configuration.default_price = default_grid_fee_price summed = value_Store.sum_prices() assert summed == expected_prices diff --git a/packages/modules/electricity_pricing/flexible_tariffs/energycharts/tariff.py b/packages/modules/electricity_pricing/flexible_tariffs/energycharts/tariff.py index 8ebce91750..5cb3b0ec67 100644 --- a/packages/modules/electricity_pricing/flexible_tariffs/energycharts/tariff.py +++ b/packages/modules/electricity_pricing/flexible_tariffs/energycharts/tariff.py @@ -24,10 +24,10 @@ def create_request_url(config: EnergyChartsTariffConfiguration) -> str: return url -def parse_response(config: EnergyChartsTariffConfiguration, raw_prices: dict) -> Dict[str, float]: - prices: Dict[int, float] = {} +def parse_response(config: EnergyChartsTariffConfiguration, raw_prices: Dict[str, float]) -> Dict[str, float]: + prices: Dict[str, float] = {} for timestamp, price_per_MWh in zip(raw_prices['unix_seconds'], raw_prices['price']): - prices[str(int(timestamp))] = float(price_per_MWh + (config.surcharge*10))/1000000 + prices[str(int(timestamp))] = float(price_per_MWh)/1000000 + config.surcharge log.debug("converted prices: %s : %s", len(prices), prices) return prices 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 2d6885ff35..9754a7281a 100644 --- a/packages/modules/electricity_pricing/flexible_tariffs/fixed_hours/tariff.py +++ b/packages/modules/electricity_pricing/flexible_tariffs/fixed_hours/tariff.py @@ -10,7 +10,6 @@ from modules.common.component_state import TariffState log = logging.getLogger(__name__) -AS_EURO_PER_WH = 1000 def _to_time(time_str: str) -> datetime.time: @@ -69,7 +68,7 @@ def _calculate_price_timeslots(config: FixedHoursTariffConfiguration) -> TariffS for j in range(4): # get prices for quarter hours time_slot = current_hour + datetime.timedelta(hours=i, minutes=j * 15) epoch_time = int(time.mktime(time_slot.timetuple())) - prices[str(epoch_time)] = _fetch_price_for_time_slot(config, time_slot) / AS_EURO_PER_WH + prices[str(epoch_time)] = _fetch_price_for_time_slot(config, time_slot) return TariffState(prices=prices) 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 d31356389b..ae4c01d6a1 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 @@ -155,7 +155,7 @@ class TestFetch: "default_price,tariffs,expected_price", [ # No tariffs, use default price - pytest.param(1500, [], 1.5, id="no_tariffs"), + pytest.param(1500, [], 1500, id="no_tariffs"), # All-day tariff pytest.param( 1500, @@ -169,7 +169,7 @@ class TestFetch: }, } ], - 2.0, + 2000, id="all_day_tariff", ), # Tariff that matches all times @@ -185,13 +185,13 @@ class TestFetch: }, } ], - 2.5, + 2500, id="full_day_tariff", ), # Very low price - pytest.param(500, [], 0.5, id="low_price"), + pytest.param(500, [], 500, id="low_price"), # Very high price - pytest.param(5000, [], 5.0, id="high_price"), + pytest.param(5000, [], 5000, id="high_price"), ], ) def test_fetch_price_configurations(self, default_price, tariffs, expected_price): @@ -205,7 +205,7 @@ def test_fetch_price_configurations(self, default_price, tariffs, expected_price assert isinstance(result, TariffState) assert len(result.prices) > 0 for price in result.prices.values(): - assert isinstance(price, float) + assert isinstance(price, (float, int)) assert price == expected_price @pytest.mark.parametrize( @@ -262,7 +262,7 @@ class TestCreateElectricityTariff: "default_price,tariffs,expected_price", [ # No tariffs, use default - pytest.param(1500, [], 1.5, id="no_tariffs"), + pytest.param(1500, [], 1500, id="no_tariffs"), # With all-day tariff pytest.param( 1500, @@ -276,7 +276,7 @@ class TestCreateElectricityTariff: }, } ], - 2.5, + 2500, id="all_day_tariff", ), # Another tariff configuration @@ -292,7 +292,7 @@ class TestCreateElectricityTariff: }, } ], - 3.5, + 3500, id="premium_tariff", ), pytest.param( @@ -307,7 +307,7 @@ class TestCreateElectricityTariff: }, } ], - 2.0, + 2000, id="not_matching_tariff returns default price", ), ],