mirror of
https://github.com/Akkudoktor-EOS/EOS.git
synced 2026-03-09 08:06:17 +00:00
fix: Improve provider update error handling and add VRM provider settings validation (#887)
Some checks failed
Bump Version / Bump Version Workflow (push) Has been cancelled
docker-build / platform-excludes (push) Has been cancelled
pre-commit / pre-commit (push) Has been cancelled
Run Pytest on Pull Request / test (push) Has been cancelled
docker-build / build (push) Has been cancelled
docker-build / merge (push) Has been cancelled
Close stale pull requests/issues / Find Stale issues and PRs (push) Has been cancelled
Some checks failed
Bump Version / Bump Version Workflow (push) Has been cancelled
docker-build / platform-excludes (push) Has been cancelled
pre-commit / pre-commit (push) Has been cancelled
Run Pytest on Pull Request / test (push) Has been cancelled
docker-build / build (push) Has been cancelled
docker-build / merge (push) Has been cancelled
Close stale pull requests/issues / Find Stale issues and PRs (push) Has been cancelled
* fix: improve error handling for provider updates Distinguishes failures of active providers from inactive ones. Propagates errors only for enabled providers, allowing execution to continue if a non-active provider fails, which avoids unnecessary interruptions and improves robustness. * fix: add provider settings validation for forecast requests Prevents potential runtime errors by checking if provider settings are configured before accessing forecast credentials. Raises a clear error when settings are missing to help with debugging misconfigurations. * refactor(load): move provider settings to top-level fields Transitions load provider settings from a nested "provider_settings" object with provider-specific keys to dedicated top-level fields.\n\nRemoves the legacy "provider_settings" mapping and updates migration logic to ensure backward compatibility with existing configurations. * docs: update version numbers and documantation --------- Co-authored-by: Normann <github@koldrack.com>
This commit is contained in:
committed by
GitHub
parent
2ca9c930e5
commit
04420e66ab
@@ -43,16 +43,21 @@ MIGRATION_MAP: Dict[
|
||||
# 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",
|
||||
"load/provider_settings/LoadAkkudoktor/loadakkudoktor_year_energy_kwh": "load/loadakkudoktor/loadakkudoktor_year_energy_kwh",
|
||||
"load/provider_settings/LoadVrm/load_vrm_idsite": "load/loadvrm/load_vrm_idsite",
|
||||
"load/provider_settings/LoadVrm/load_vrm_token": "load/loadvrm/load_vrm_token",
|
||||
"load/provider_settings/LoadImport/import_file_path": "load/loadimport/import_file_path",
|
||||
"load/provider_settings/LoadImport/import_json": "load/loadimport/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/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",
|
||||
"load/provider_settings/load_vrm_idsite": "load/provider_settings/LoadVrm/load_vrm_idsite",
|
||||
"load/provider_settings/load_vrm_token": "load/provider_settings/LoadVrm/load_vrm_token",
|
||||
"load/provider_settings/import_file_path": "load/loadimport/import_file_path",
|
||||
"load/provider_settings/import_json": "load/loadimport/import_json",
|
||||
"load/provider_settings/loadakkudoktor_year_energy": "load/loadakkudoktor/loadakkudoktor_year_energy_kwh",
|
||||
"load/provider_settings/load_vrm_idsite": "load/loadvrm/load_vrm_idsite",
|
||||
"load/provider_settings/load_vrm_token": "load/loadvrm/load_vrm_token",
|
||||
"logging/level": "logging/console_level",
|
||||
"logging/root_level": None,
|
||||
"measurement/load0_name": "measurement/load_emr_keys/0",
|
||||
|
||||
@@ -1982,8 +1982,14 @@ class DataContainer(SingletonMixin, DataABC, MutableMapping):
|
||||
provider.update_data(force_enable=force_enable, force_update=force_update)
|
||||
except Exception as ex:
|
||||
error = f"Provider {provider.provider_id()} fails on update - enabled={provider.enabled()}, force_enable={force_enable}, force_update={force_update}: {ex}"
|
||||
logger.error(error)
|
||||
raise RuntimeError(error)
|
||||
if provider.enabled():
|
||||
# The active provider failed — this is a real error worth propagating.
|
||||
logger.error(error)
|
||||
raise RuntimeError(error)
|
||||
else:
|
||||
# A non-active provider failed (e.g. missing config while force_enable=True).
|
||||
# Log as warning and continue so the remaining providers still run.
|
||||
logger.warning(error)
|
||||
|
||||
def key_to_series(
|
||||
self,
|
||||
|
||||
@@ -337,10 +337,8 @@ class GeneticOptimizationParameters(
|
||||
{
|
||||
"load": {
|
||||
"provider": "LoadAkkudoktor",
|
||||
"provider_settings": {
|
||||
"LoadAkkudoktor": {
|
||||
"loadakkudoktor_year_energy_kwh": "3000",
|
||||
},
|
||||
"loadakkudoktor": {
|
||||
"loadakkudoktor_year_energy_kwh": "3000",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -28,21 +28,6 @@ def load_providers() -> list[str]:
|
||||
]
|
||||
|
||||
|
||||
class LoadCommonProviderSettings(SettingsBaseModel):
|
||||
"""Load Prediction Provider Configuration."""
|
||||
|
||||
LoadAkkudoktor: Optional[LoadAkkudoktorCommonSettings] = Field(
|
||||
default=None,
|
||||
json_schema_extra={"description": "LoadAkkudoktor settings", "examples": [None]},
|
||||
)
|
||||
LoadVrm: Optional[LoadVrmCommonSettings] = Field(
|
||||
default=None, json_schema_extra={"description": "LoadVrm settings", "examples": [None]}
|
||||
)
|
||||
LoadImport: Optional[LoadImportCommonSettings] = Field(
|
||||
default=None, json_schema_extra={"description": "LoadImport settings", "examples": [None]}
|
||||
)
|
||||
|
||||
|
||||
class LoadCommonSettings(SettingsBaseModel):
|
||||
"""Load Prediction Configuration."""
|
||||
|
||||
@@ -54,19 +39,19 @@ class LoadCommonSettings(SettingsBaseModel):
|
||||
},
|
||||
)
|
||||
|
||||
provider_settings: LoadCommonProviderSettings = Field(
|
||||
default_factory=LoadCommonProviderSettings,
|
||||
json_schema_extra={
|
||||
"description": "Provider settings",
|
||||
"examples": [
|
||||
# Example 1: Empty/default settings (all providers None)
|
||||
{
|
||||
"LoadAkkudoktor": None,
|
||||
"LoadVrm": None,
|
||||
"LoadImport": None,
|
||||
},
|
||||
],
|
||||
},
|
||||
loadakkudoktor: LoadAkkudoktorCommonSettings = Field(
|
||||
default_factory=LoadAkkudoktorCommonSettings,
|
||||
json_schema_extra={"description": "LoadAkkudoktor provider settings."},
|
||||
)
|
||||
|
||||
loadvrm: LoadVrmCommonSettings = Field(
|
||||
default_factory=LoadVrmCommonSettings,
|
||||
json_schema_extra={"description": "LoadVrm provider settings."},
|
||||
)
|
||||
|
||||
loadimport: LoadImportCommonSettings = Field(
|
||||
default_factory=LoadImportCommonSettings,
|
||||
json_schema_extra={"description": "LoadImport provider settings."},
|
||||
)
|
||||
|
||||
@computed_field # type: ignore[prop-decorator]
|
||||
|
||||
@@ -56,9 +56,7 @@ class LoadAkkudoktor(LoadProvider):
|
||||
)
|
||||
# Calculate values in W by relative profile data and yearly consumption given in kWh
|
||||
data_year_energy = (
|
||||
profile_data
|
||||
* self.config.load.provider_settings.LoadAkkudoktor.loadakkudoktor_year_energy_kwh
|
||||
* 1000
|
||||
profile_data * self.config.load.loadakkudoktor.loadakkudoktor_year_energy_kwh * 1000
|
||||
)
|
||||
except FileNotFoundError:
|
||||
error_msg = f"Error: File {load_file} not found."
|
||||
|
||||
@@ -9,7 +9,6 @@ format, enabling consistent access to forecasted and historical load attributes.
|
||||
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
|
||||
@@ -64,14 +63,7 @@ class LoadImport(LoadProvider, PredictionImportProvider):
|
||||
return "LoadImport"
|
||||
|
||||
def _update_data(self, force_update: Optional[bool] = False) -> None:
|
||||
if self.config.load.provider_settings.LoadImport is None:
|
||||
logger.debug(f"{self.provider_id()} data update without provider settings.")
|
||||
return
|
||||
if self.config.load.provider_settings.LoadImport.import_file_path:
|
||||
self.import_from_file(
|
||||
self.config.provider_settings.LoadImport.import_file_path, key_prefix="load"
|
||||
)
|
||||
if self.config.load.provider_settings.LoadImport.import_json:
|
||||
self.import_from_json(
|
||||
self.config.load.provider_settings.LoadImport.import_json, key_prefix="load"
|
||||
)
|
||||
if self.config.load.loadimport.import_file_path:
|
||||
self.import_from_file(self.config.load.loadimport.import_file_path, key_prefix="load")
|
||||
if self.config.load.loadimport.import_json:
|
||||
self.import_from_json(self.config.load.loadimport.import_json, key_prefix="load")
|
||||
|
||||
@@ -62,8 +62,9 @@ class LoadVrm(LoadProvider):
|
||||
def _request_forecast(self, start_ts: int, end_ts: int) -> VrmForecastResponse:
|
||||
"""Fetch forecast data from Victron VRM API."""
|
||||
base_url = "https://vrmapi.victronenergy.com/v2/installations"
|
||||
installation_id = self.config.load.provider_settings.LoadVrm.load_vrm_idsite
|
||||
api_token = self.config.load.provider_settings.LoadVrm.load_vrm_token
|
||||
vrm_settings = self.config.load.loadvrm
|
||||
installation_id = vrm_settings.load_vrm_idsite
|
||||
api_token = vrm_settings.load_vrm_token
|
||||
|
||||
url = f"{base_url}/{installation_id}/stats?type=forecast&start={start_ts}&end={end_ts}&interval=hours"
|
||||
headers = {"X-Authorization": f"Token {api_token}", "Content-Type": "application/json"}
|
||||
@@ -85,6 +86,9 @@ class LoadVrm(LoadProvider):
|
||||
|
||||
def _update_data(self, force_update: Optional[bool] = False) -> None:
|
||||
"""Fetch and store VRM load forecast as loadforecast_power_w and related values."""
|
||||
if self.enabled is False:
|
||||
logger.info("LoadVrm is disabled, skipping update.")
|
||||
return
|
||||
start_date = self.ems_start_datetime.start_of("day")
|
||||
end_date = self.ems_start_datetime.add(hours=self.config.prediction.hours)
|
||||
start_ts = int(start_date.timestamp())
|
||||
|
||||
@@ -85,6 +85,9 @@ class PVForecastVrm(PVForecastProvider):
|
||||
|
||||
def _update_data(self, force_update: Optional[bool] = False) -> None:
|
||||
"""Update forecast data in the PVForecastDataRecord format."""
|
||||
if self.enabled is False:
|
||||
logger.info("PVForecastVrm is disabled, skipping update.")
|
||||
return
|
||||
start_date = self.ems_start_datetime.start_of("day")
|
||||
end_date = self.ems_start_datetime.add(hours=self.config.prediction.hours)
|
||||
start_ts = int(start_date.timestamp())
|
||||
|
||||
@@ -154,9 +154,7 @@ def LoadForecast(predictions: pd.DataFrame, config: dict, date_time_tz: str, dar
|
||||
source = ColumnDataSource(predictions)
|
||||
provider = config["load"]["provider"]
|
||||
if provider == "LoadAkkudoktorAdjusted":
|
||||
year_energy = config["load"]["provider_settings"]["LoadAkkudoktor"][
|
||||
"loadakkudoktor_year_energy_kwh"
|
||||
]
|
||||
year_energy = config["load"]["loadakkudoktor"]["loadakkudoktor_year_energy_kwh"]
|
||||
provider = f"{provider}, {year_energy} kWh"
|
||||
|
||||
plot = figure(
|
||||
|
||||
@@ -54,7 +54,7 @@ from akkudoktoreos.optimization.genetic.geneticparams import (
|
||||
from akkudoktoreos.optimization.genetic.geneticsolution import GeneticSolution
|
||||
from akkudoktoreos.optimization.optimization import OptimizationSolution
|
||||
from akkudoktoreos.prediction.elecprice import ElecPriceCommonSettings
|
||||
from akkudoktoreos.prediction.load import LoadCommonProviderSettings, LoadCommonSettings
|
||||
from akkudoktoreos.prediction.load import LoadCommonSettings
|
||||
from akkudoktoreos.prediction.loadakkudoktor import LoadAkkudoktorCommonSettings
|
||||
from akkudoktoreos.prediction.pvforecast import PVForecastCommonSettings
|
||||
from akkudoktoreos.server.rest.cli import cli_apply_args_to_config, cli_parse_args
|
||||
@@ -1074,10 +1074,8 @@ async def fastapi_gesamtlast(request: GesamtlastRequest) -> list[float]:
|
||||
},
|
||||
"load": {
|
||||
"provider": "LoadAkkudoktorAdjusted",
|
||||
"provider_settings": {
|
||||
"LoadAkkudoktor": {
|
||||
"loadakkudoktor_year_energy_kwh": request.year_energy,
|
||||
},
|
||||
"loadakkudoktor": {
|
||||
"loadakkudoktor_year_energy_kwh": request.year_energy,
|
||||
},
|
||||
},
|
||||
"measurement": {
|
||||
@@ -1174,10 +1172,8 @@ async def fastapi_gesamtlast_simple(year_energy: float) -> list[float]:
|
||||
settings = SettingsEOS(
|
||||
load=LoadCommonSettings(
|
||||
provider="LoadAkkudoktor",
|
||||
provider_settings=LoadCommonProviderSettings(
|
||||
LoadAkkudoktor=LoadAkkudoktorCommonSettings(
|
||||
loadakkudoktor_year_energy_kwh=year_energy / 1000, # Convert to kWh
|
||||
),
|
||||
loadakkudoktor=LoadAkkudoktorCommonSettings(
|
||||
loadakkudoktor_year_energy_kwh=year_energy / 1000, # Convert to kWh
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user