diff --git a/docs/_generated/configelecprice.md b/docs/_generated/configelecprice.md index bc912d3..9110848 100644 --- a/docs/_generated/configelecprice.md +++ b/docs/_generated/configelecprice.md @@ -8,8 +8,9 @@ | Name | Environment Variable | Type | Read-Only | Default | Description | | ---- | -------------------- | ---- | --------- | ------- | ----------- | | charges_kwh | `EOS_ELECPRICE__CHARGES_KWH` | `Optional[float]` | `rw` | `None` | Electricity price charges [€/kWh]. Will be added to variable market price. | +| elecpriceimport | `EOS_ELECPRICE__ELECPRICEIMPORT` | `ElecPriceImportCommonSettings` | `rw` | `required` | Import provider settings. | +| energycharts | `EOS_ELECPRICE__ENERGYCHARTS` | `ElecPriceEnergyChartsCommonSettings` | `rw` | `required` | Energy Charts provider settings. | | provider | `EOS_ELECPRICE__PROVIDER` | `Optional[str]` | `rw` | `None` | Electricity price provider id of provider to be used. | -| provider_settings | `EOS_ELECPRICE__PROVIDER_SETTINGS` | `ElecPriceCommonProviderSettings` | `rw` | `required` | Provider settings | | vat_rate | `EOS_ELECPRICE__VAT_RATE` | `Optional[float]` | `rw` | `1.19` | VAT rate factor applied to electricity price when charges are used. | ::: @@ -25,8 +26,41 @@ "provider": "ElecPriceAkkudoktor", "charges_kwh": 0.21, "vat_rate": 1.19, - "provider_settings": { - "ElecPriceImport": null + "elecpriceimport": { + "import_file_path": null, + "import_json": null + }, + "energycharts": { + "bidding_zone": "DE-LU" + } + } + } +``` + + +### Common settings for Energy Charts electricity price provider + + +:::{table} elecprice::energycharts +:widths: 10 10 5 5 30 +:align: left + +| Name | Type | Read-Only | Default | Description | +| ---- | ---- | --------- | ------- | ----------- | +| bidding_zone | `` | `rw` | `EnergyChartsBiddingZones.DE_LU` | Bidding Zone: 'AT', 'BE', 'CH', 'CZ', 'DE-LU', 'DE-AT-LU', 'DK1', 'DK2', 'FR', 'HU', 'IT-NORTH', 'NL', 'NO2', 'PL', 'SE4' or 'SI' | +::: + + + +**Example Input/Output** + + + +```json + { + "elecprice": { + "energycharts": { + "bidding_zone": "AT" } } } @@ -36,7 +70,7 @@ ### Common settings for elecprice data import from file or JSON String -:::{table} elecprice::provider_settings::ElecPriceImport +:::{table} elecprice::elecpriceimport :widths: 10 10 5 5 30 :align: left @@ -55,40 +89,9 @@ ```json { "elecprice": { - "provider_settings": { - "ElecPriceImport": { - "import_file_path": null, - "import_json": "{\"elecprice_marketprice_wh\": [0.0003384, 0.0003318, 0.0003284]}" - } - } - } - } -``` - - -### Electricity Price Prediction Provider Configuration - - -:::{table} elecprice::provider_settings -:widths: 10 10 5 5 30 -:align: left - -| Name | Type | Read-Only | Default | Description | -| ---- | ---- | --------- | ------- | ----------- | -| ElecPriceImport | `Optional[akkudoktoreos.prediction.elecpriceimport.ElecPriceImportCommonSettings]` | `rw` | `None` | ElecPriceImport settings | -::: - - - -**Example Input/Output** - - - -```json - { - "elecprice": { - "provider_settings": { - "ElecPriceImport": null + "elecpriceimport": { + "import_file_path": null, + "import_json": "{\"elecprice_marketprice_wh\": [0.0003384, 0.0003318, 0.0003284]}" } } } diff --git a/docs/_generated/configexample.md b/docs/_generated/configexample.md index 3789e8a..389a3ac 100644 --- a/docs/_generated/configexample.md +++ b/docs/_generated/configexample.md @@ -71,8 +71,12 @@ "provider": "ElecPriceAkkudoktor", "charges_kwh": 0.21, "vat_rate": 1.19, - "provider_settings": { - "ElecPriceImport": null + "elecpriceimport": { + "import_file_path": null, + "import_json": null + }, + "energycharts": { + "bidding_zone": "DE-LU" } }, "ems": { diff --git a/docs/akkudoktoreos/prediction.md b/docs/akkudoktoreos/prediction.md index e4d0163..d40aedc 100644 --- a/docs/akkudoktoreos/prediction.md +++ b/docs/akkudoktoreos/prediction.md @@ -124,8 +124,9 @@ Configuration options: - `charges_kwh`: Electricity price charges (€/kWh). - `vat_rate`: VAT rate factor applied to electricity price when charges are used (default: 1.19). - - `provider_settings.import_file_path`: Path to the file to import electricity price forecast data from. - - `provider_settings.import_json`: JSON string, dictionary of electricity price forecast value lists. + - `elecpriceimport.import_file_path`: Path to the file to import electricity price forecast data from. + - `elecpriceimport.import_json`: JSON string, dictionary of electricity price forecast value lists. + - `energycharts.bidding_zone`: Bidding zone Energy Charts shall provide price data for. ### ElecPriceAkkudoktor Provider diff --git a/openapi.json b/openapi.json index a51c8c3..fe1b1fe 100644 --- a/openapi.json +++ b/openapi.json @@ -2469,7 +2469,10 @@ "$ref": "#/components/schemas/ElecPriceCommonSettings-Output", "default": { "vat_rate": 1.19, - "provider_settings": {} + "elecpriceimport": {}, + "energycharts": { + "bidding_zone": "DE-LU" + } } }, "feedintariff": { @@ -2975,27 +2978,6 @@ "title": "DevicesCommonSettings", "description": "Base configuration for devices simulation settings." }, - "ElecPriceCommonProviderSettings": { - "properties": { - "ElecPriceImport": { - "anyOf": [ - { - "$ref": "#/components/schemas/ElecPriceImportCommonSettings" - }, - { - "type": "null" - } - ], - "description": "ElecPriceImport settings", - "examples": [ - null - ] - } - }, - "type": "object", - "title": "ElecPriceCommonProviderSettings", - "description": "Electricity Price Prediction Provider Configuration." - }, "ElecPriceCommonSettings-Input": { "properties": { "provider": { @@ -3046,12 +3028,13 @@ 1.19 ] }, - "provider_settings": { - "$ref": "#/components/schemas/ElecPriceCommonProviderSettings", - "description": "Provider settings", - "examples": [ - {} - ] + "elecpriceimport": { + "$ref": "#/components/schemas/ElecPriceImportCommonSettings", + "description": "Import provider settings." + }, + "energycharts": { + "$ref": "#/components/schemas/ElecPriceEnergyChartsCommonSettings", + "description": "Energy Charts provider settings." } }, "type": "object", @@ -3108,18 +3091,34 @@ 1.19 ] }, - "provider_settings": { - "$ref": "#/components/schemas/ElecPriceCommonProviderSettings", - "description": "Provider settings", - "examples": [ - {} - ] + "elecpriceimport": { + "$ref": "#/components/schemas/ElecPriceImportCommonSettings", + "description": "Import provider settings." + }, + "energycharts": { + "$ref": "#/components/schemas/ElecPriceEnergyChartsCommonSettings", + "description": "Energy Charts provider settings." } }, "type": "object", "title": "ElecPriceCommonSettings", "description": "Electricity Price Prediction Configuration." }, + "ElecPriceEnergyChartsCommonSettings": { + "properties": { + "bidding_zone": { + "$ref": "#/components/schemas/EnergyChartsBiddingZones", + "description": "Bidding Zone: 'AT', 'BE', 'CH', 'CZ', 'DE-LU', 'DE-AT-LU', 'DK1', 'DK2', 'FR', 'HU', 'IT-NORTH', 'NL', 'NO2', 'PL', 'SE4' or 'SI'", + "default": "DE-LU", + "examples": [ + "AT" + ] + } + }, + "type": "object", + "title": "ElecPriceEnergyChartsCommonSettings", + "description": "Common settings for Energy Charts electricity price provider." + }, "ElecPriceImportCommonSettings": { "properties": { "import_file_path": { @@ -3375,6 +3374,29 @@ "title": "ElectricVehicleResult", "description": "Result class containing information related to the electric vehicle's charging and discharging behavior." }, + "EnergyChartsBiddingZones": { + "type": "string", + "enum": [ + "AT", + "BE", + "CH", + "CZ", + "DE-LU", + "DE-AT-LU", + "DK1", + "DK2", + "FR", + "HU", + "IT-NORTH", + "NL", + "NO2", + "PL", + "SE4", + "SI" + ], + "title": "EnergyChartsBiddingZones", + "description": "Energy Charts Bidding Zones." + }, "EnergyManagementCommonSettings": { "properties": { "startup_delay": { diff --git a/src/akkudoktoreos/config/configmigrate.py b/src/akkudoktoreos/config/configmigrate.py index ebbc4ae..e4a3d6b 100644 --- a/src/akkudoktoreos/config/configmigrate.py +++ b/src/akkudoktoreos/config/configmigrate.py @@ -21,11 +21,14 @@ if TYPE_CHECKING: # - tuple[str, Callable[[Any], Any]] (new path + transform) # - None (drop) MIGRATION_MAP: Dict[str, Union[str, Tuple[str, Callable[[Any], Any]], None]] = { - # 0.1.0 -> 0.2.0 + # 0.2.0 -> 0.2.0+dev + "elecprice/provider_settings/ElecPriceImport/import_file_path": "elecprice/elecpriceimport/import_file_path", + "elecprice/provider_settings/ElecPriceImport/import_json": "elecprice/elecpriceimport/import_json", + # 0.1.0 -> 0.2.0+dev "devices/batteries/0/initial_soc_percentage": None, "devices/electric_vehicles/0/initial_soc_percentage": None, - "elecprice/provider_settings/import_file_path": "elecprice/provider_settings/ElecPriceImport/import_file_path", - "elecprice/provider_settings/import_json": "elecprice/provider_settings/ElecPriceImport/import_json", + "elecprice/provider_settings/import_file_path": "elecprice/elecpriceimport/import_file_path", + "elecprice/provider_settings/import_json": "elecprice/elecpriceimport/import_json", "load/provider_settings/import_file_path": "load/provider_settings/LoadImport/import_file_path", "load/provider_settings/import_json": "load/provider_settings/LoadImport/import_json", "load/provider_settings/loadakkudoktor_year_energy": "load/provider_settings/LoadAkkudoktor/loadakkudoktor_year_energy_kwh", diff --git a/src/akkudoktoreos/prediction/elecprice.py b/src/akkudoktoreos/prediction/elecprice.py index 7d8c20f..a67c3cf 100644 --- a/src/akkudoktoreos/prediction/elecprice.py +++ b/src/akkudoktoreos/prediction/elecprice.py @@ -4,6 +4,9 @@ from pydantic import Field, field_validator from akkudoktoreos.config.configabc import SettingsBaseModel from akkudoktoreos.prediction.elecpriceabc import ElecPriceProvider +from akkudoktoreos.prediction.elecpriceenergycharts import ( + ElecPriceEnergyChartsCommonSettings, +) from akkudoktoreos.prediction.elecpriceimport import ElecPriceImportCommonSettings from akkudoktoreos.prediction.prediction import get_prediction @@ -17,15 +20,6 @@ elecprice_providers = [ ] -class ElecPriceCommonProviderSettings(SettingsBaseModel): - """Electricity Price Prediction Provider Configuration.""" - - ElecPriceImport: Optional[ElecPriceImportCommonSettings] = Field( - default=None, - json_schema_extra={"description": "ElecPriceImport settings", "examples": [None]}, - ) - - class ElecPriceCommonSettings(SettingsBaseModel): """Electricity Price Prediction Configuration.""" @@ -53,17 +47,14 @@ class ElecPriceCommonSettings(SettingsBaseModel): }, ) - provider_settings: ElecPriceCommonProviderSettings = Field( - default_factory=ElecPriceCommonProviderSettings, - json_schema_extra={ - "description": "Provider settings", - "examples": [ - # Example 1: Empty/default settings (all providers None) - { - "ElecPriceImport": None, - }, - ], - }, + elecpriceimport: ElecPriceImportCommonSettings = Field( + default_factory=ElecPriceImportCommonSettings, + json_schema_extra={"description": "Import provider settings."}, + ) + + energycharts: ElecPriceEnergyChartsCommonSettings = Field( + default_factory=ElecPriceEnergyChartsCommonSettings, + json_schema_extra={"description": "Energy Charts provider settings."}, ) # Validators diff --git a/src/akkudoktoreos/prediction/elecpriceenergycharts.py b/src/akkudoktoreos/prediction/elecpriceenergycharts.py index 016d5da..334a711 100644 --- a/src/akkudoktoreos/prediction/elecpriceenergycharts.py +++ b/src/akkudoktoreos/prediction/elecpriceenergycharts.py @@ -7,21 +7,44 @@ format, enabling consistent access to forecasted and historical electricity pric """ from datetime import datetime +from enum import Enum from typing import Any, List, Optional, Union import numpy as np import pandas as pd import requests from loguru import logger -from pydantic import ValidationError +from pydantic import Field, ValidationError from statsmodels.tsa.holtwinters import ExponentialSmoothing +from akkudoktoreos.config.configabc import SettingsBaseModel from akkudoktoreos.core.cache import cache_in_file from akkudoktoreos.core.pydantic import PydanticBaseModel from akkudoktoreos.prediction.elecpriceabc import ElecPriceProvider from akkudoktoreos.utils.datetimeutil import to_datetime, to_duration +class EnergyChartsBiddingZones(str, Enum): + """Energy Charts Bidding Zones.""" + + AT = "AT" + BE = "BE" + CH = "CH" + CZ = "CZ" + DE_LU = "DE-LU" + DE_AT_LU = "DE-AT-LU" + DK1 = "DK1" + DK2 = "DK2" + FR = "FR" + HU = "HU" + IT_North = "IT-NORTH" + NL = "NL" + NO2 = "NO2" + PL = "PL" + SE4 = "SE4" + SI = "SI" + + class EnergyChartsElecPrice(PydanticBaseModel): license_info: str unix_seconds: List[int] @@ -30,6 +53,21 @@ class EnergyChartsElecPrice(PydanticBaseModel): deprecated: bool +class ElecPriceEnergyChartsCommonSettings(SettingsBaseModel): + """Common settings for Energy Charts electricity price provider.""" + + bidding_zone: EnergyChartsBiddingZones = Field( + default=EnergyChartsBiddingZones.DE_LU, + json_schema_extra={ + "description": ( + "Bidding Zone: 'AT', 'BE', 'CH', 'CZ', 'DE-LU', 'DE-AT-LU', 'DK1', 'DK2', 'FR', " + "'HU', 'IT-NORTH', 'NL', 'NO2', 'PL', 'SE4' or 'SI'" + ), + "examples": ["AT"], + }, + ) + + class ElecPriceEnergyCharts(ElecPriceProvider): """Fetch and process electricity price forecast data from Energy-Charts. @@ -95,7 +133,8 @@ class ElecPriceEnergyCharts(ElecPriceProvider): ) last_date = to_datetime(self.end_datetime, as_string="YYYY-MM-DD") - url = f"{source}/price?bzn=DE-LU&start={start_date}&end={last_date}" + bidding_zone = str(self.config.elecprice.energycharts.bidding_zone) + url = f"{source}/price?bzn={bidding_zone}&start={start_date}&end={last_date}" response = requests.get(url, timeout=30) logger.debug(f"Response from {url}: {response}") response.raise_for_status() # Raise an error for bad responses diff --git a/src/akkudoktoreos/prediction/elecpriceimport.py b/src/akkudoktoreos/prediction/elecpriceimport.py index 65d89c7..5ac2687 100644 --- a/src/akkudoktoreos/prediction/elecpriceimport.py +++ b/src/akkudoktoreos/prediction/elecpriceimport.py @@ -9,7 +9,6 @@ format, enabling consistent access to forecasted and historical elecprice attrib from pathlib import Path from typing import Optional, Union -from loguru import logger from pydantic import Field, field_validator from akkudoktoreos.config.configabc import SettingsBaseModel @@ -65,16 +64,13 @@ class ElecPriceImport(ElecPriceProvider, PredictionImportProvider): return "ElecPriceImport" def _update_data(self, force_update: Optional[bool] = False) -> None: - if self.config.elecprice.provider_settings.ElecPriceImport is None: - logger.debug(f"{self.provider_id()} data update without provider settings.") - return - if self.config.elecprice.provider_settings.ElecPriceImport.import_file_path: + if self.config.elecprice.elecpriceimport.import_file_path: self.import_from_file( - self.config.elecprice.provider_settings.ElecPriceImport.import_file_path, + self.config.elecprice.elecpriceimport.import_file_path, key_prefix="elecprice", ) - if self.config.elecprice.provider_settings.ElecPriceImport.import_json: + if self.config.elecprice.elecpriceimport.import_json: self.import_from_json( - self.config.elecprice.provider_settings.ElecPriceImport.import_json, + self.config.elecprice.elecpriceimport.import_json, key_prefix="elecprice", ) diff --git a/tests/test_elecpriceimport.py b/tests/test_elecpriceimport.py index 6f44596..09f20e1 100644 --- a/tests/test_elecpriceimport.py +++ b/tests/test_elecpriceimport.py @@ -18,11 +18,9 @@ def provider(sample_import_1_json, config_eos): settings = { "elecprice": { "provider": "ElecPriceImport", - "provider_settings": { - "ElecPriceImport": { - "import_file_path": str(FILE_TESTDATA_ELECPRICEIMPORT_1_JSON), - "import_json": json.dumps(sample_import_1_json), - }, + "elecpriceimport": { + "import_file_path": str(FILE_TESTDATA_ELECPRICEIMPORT_1_JSON), + "import_json": json.dumps(sample_import_1_json), }, } } @@ -56,10 +54,8 @@ def test_invalid_provider(provider, config_eos): settings = { "elecprice": { "provider": "", - "provider_settings": { - "ElecPriceImport": { - "import_file_path": str(FILE_TESTDATA_ELECPRICEIMPORT_1_JSON), - }, + "elecpriceimport": { + "import_file_path": str(FILE_TESTDATA_ELECPRICEIMPORT_1_JSON), }, } } @@ -90,11 +86,11 @@ def test_import(provider, sample_import_1_json, start_datetime, from_file, confi ems_eos = get_ems() ems_eos.set_start_datetime(to_datetime(start_datetime, in_timezone="Europe/Berlin")) if from_file: - config_eos.elecprice.provider_settings.ElecPriceImport.import_json = None - assert config_eos.elecprice.provider_settings.ElecPriceImport.import_json is None + config_eos.elecprice.elecpriceimport.import_json = None + assert config_eos.elecprice.elecpriceimport.import_json is None else: - config_eos.elecprice.provider_settings.ElecPriceImport.import_file_path = None - assert config_eos.elecprice.provider_settings.ElecPriceImport.import_file_path is None + config_eos.elecprice.elecpriceimport.import_file_path = None + assert config_eos.elecprice.elecpriceimport.import_file_path is None provider.clear() # Call the method