feat: add bidding zone to energy charts price prediction (#765)

Energy charts supports bidding zones. Allow to specifiy the bidding zone in the configuration.

Extend and simplify ElecPrice configuration structure and setup config migration to automatically
update the configuration file.

Signed-off-by: Bobby Noelte <b0661n0e17e@gmail.com>
This commit is contained in:
Bobby Noelte
2025-11-16 13:26:18 +01:00
committed by GitHub
parent edff649a5e
commit 4c2997dbd6
9 changed files with 177 additions and 122 deletions

View File

@@ -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",

View File

@@ -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

View File

@@ -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

View File

@@ -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",
)