fix: provider settings in top level field of configuration (#1161)

Configuration for some providers was given in the sub-field `provider_settings`
combining the settings of several providers. Pydantic does not understand
this very well and the configuration became cumbersome, especially in EOSdash.

All provider settings from the `provider-settings` sub-field are now lifted
to the top level field of the configuration.

The changes are automatically migrated in the configuration.

Signed-off-by: Bobby Noelte <b0661n0e17e@gmail.com>
This commit is contained in:
Bobby Noelte
2026-07-20 14:37:03 +02:00
committed by GitHub
parent 845191c0d0
commit 3bb0e02aed
44 changed files with 798 additions and 1061 deletions

View File

@@ -48,49 +48,105 @@ MIGRATION_MAP: Dict[
None, # drop
],
] = {
# 0.2.0.dev -> 0.2.0.dev
# adapter
# =======
# - HomeAssistant
"adapter/homeassistant/optimization_solution_entity_ids": (
"adapter/homeassistant/solution_entity_ids",
lambda v: v if isinstance(v, list) else None,
),
"general/data_folder_path": ("general/data_folder_path", _KEEP_DEFAULT),
# 0.2.0 -> 0.2.0+dev
# - NodeRed
# devices
# =======
# - batteries
"devices/batteries/0/initial_soc_percentage": None,
# - electric_vehicles
"devices/electric_vehicles/0/initial_soc_percentage": None,
# elecprice
# =========
# - ElecPriceAkkudoktor
# - ElecPriceEnergyCharts
# - ElecPriceFixed
# - ElecPriceImport
"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",
# feedintariff
# ============
# - FeedInTariffFixed
"feedintariff/provider_settings/FeedInTariffFixed/feed_in_tariff_kwh": "feedintariff/feedintarifffixed/feed_in_tariff_kwh",
# - FeedInTariffImport
"feedintariff/provider_settings/FeedInTariffImport/import_file_path": "feedintariff/feedintariffimport/import_file_path",
"feedintariff/provider_settings/FeedInTariffImport/import_json": "feedintariff/feedintariffimport/import_json",
# general
# =======
"general/data_folder_path": ("general/data_folder_path", _KEEP_DEFAULT),
# load
# ====
# - LoadAkkudoktor
"load/provider_settings/LoadAkkudoktor/loadakkudoktor_year_energy_kwh": "load/loadakkudoktor/loadakkudoktor_year_energy_kwh",
"load/provider_settings/loadakkudoktor_year_energy": "load/loadakkudoktor/loadakkudoktor_year_energy_kwh",
# - LoadVrm
"load/loadvrm/load_vrm_idsite": "load/vrm/site_id",
"load/loadvrm/load_vrm_token": "load/vrm/token",
"load/provider_settings/load_vrm_idsite": "load/vrm/site_id",
"load/provider_settings/load_vrm_token": "load/vrm/token",
"load/provider_settings/LoadVrm/load_vrm_idsite": "load/vrm/site_id",
"load/provider_settings/LoadVrm/load_vrm_token": "load/vrm/token",
# - LoadImport
"load/provider_settings/LoadImport/import_file_path": "load/loadimport/import_file_path",
"load/provider_settings/LoadImport/import_json": "load/loadimport/import_json",
"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
# =======
"logging/level": "logging/console_level",
"logging/root_level": None,
# measurement
# ===========
"measurement/load0_name": "measurement/load_emr_keys/0",
"measurement/load1_name": "measurement/load_emr_keys/1",
"measurement/load2_name": "measurement/load_emr_keys/2",
"measurement/load3_name": "measurement/load_emr_keys/3",
"measurement/load4_name": "measurement/load_emr_keys/4",
# optimization
# ============
"optimization/ev_available_charge_rates_percent": (
"devices/electric_vehicles/0/charge_rates",
lambda v: [x / 100 for x in v],
),
"optimization/hours": "optimization/horizon_hours",
"optimization/penalty": ("optimization/genetic/penalties/ev_soc_miss", lambda v: float(v)),
"pvforecast/provider_settings/import_file_path": "pvforecast/provider_settings/PVForecastImport/import_file_path",
"pvforecast/provider_settings/import_json": "pvforecast/provider_settings/PVForecastImport/import_json",
"pvforecast/provider_settings/load_vrm_idsite": "pvforecast/provider_settings/PVForecastVrm/load_vrm_idsite",
"pvforecast/provider_settings/load_vrm_token": "pvforecast/provider_settings/PVForecastVrm/load_vrm_token",
"weather/provider_settings/import_file_path": "weather/provider_settings/WeatherImport/import_file_path",
"weather/provider_settings/import_json": "weather/provider_settings/WeatherImport/import_json",
# pvforecast
# ==========
# - PVForecastAkkudoktor
# - PVForecastVrm
"pvforecast/provider_settings/PVForecastVrm/pvforecast_vrm_token": "pvforecast/vrm/token",
"pvforecast/provider_settings/PVForecastVrm/pvforecast_vrm_idsite": "pvforecast/vrm/site_id",
"pvforecast/provider_settings/load_vrm_idsite": "pvforecast/vrm/site_id",
"pvforecast/provider_settings/load_vrm_token": "pvforecast/vrm/token",
# - PVForecastPVNode
"pvforecast/provider_settings/PVForecastPVNode/api_key": "pvforecast/pvnode/api_key",
"pvforecast/provider_settings/PVForecastPVNode/site_id": "pvforecast/pvnode/site_id",
"pvforecast/provider_settings/PVForecastPVNode/forecast_days": "pvforecast/pvnode/forecast_days",
# - PVForecastForecastSolar
"pvforecast/provider_settings/PVForecastForecastSolar/api_key": "pvforecast/forecastsolar/api_key",
# - PVForecastSolcast
"pvforecast/provider_settings/PVForecastSolcast/api_key": "pvforecast/solcast/api_key",
"pvforecast/provider_settings/PVForecastSolcast/site_id": "pvforecast/solcast/site_id",
# - PVForecastImport
"pvforecast/provider_settings/PVForecastImport/import_file_path": "pvforecast/pvforecastimport/import_file_path",
"pvforecast/provider_settings/PVForecastImport/import_json": "pvforecast/pvforecastimport/import_json",
"pvforecast/provider_settings/import_file_path": "pvforecast/pvforecastimport/import_file_path",
"pvforecast/provider_settings/import_json": "pvforecast/pvforecastimport/import_json",
# weather
# =======
# - WeatherImport
"weather/provider_settings/WeatherImport/import_file_path": "weather/weatherimport/import_file_path",
"weather/provider_settings/WeatherImport/import_json": "weather/weatherimport/import_json",
"weather/provider_settings/import_file_path": "weather/weatherimport/import_file_path",
"weather/provider_settings/import_json": "weather/weatherimport/import_json",
}
# -----------------------------

View File

@@ -392,10 +392,8 @@ class GeneticOptimizationParameters(
{
"feedintariff": {
"provider": "FeedInTariffFixed",
"provider_settings": {
"FeedInTariffFixed": {
"feed_in_tariff_kwh": 0.078,
},
"feedintarifffixed": {
"feed_in_tariff_kwh": 0.078,
},
},
}

View File

@@ -70,7 +70,7 @@ class ElecPriceCommonSettings(SettingsBaseModel):
elecpriceimport: ElecPriceImportCommonSettings = Field(
default_factory=ElecPriceImportCommonSettings,
json_schema_extra={"description": "Import provider settings."},
json_schema_extra={"description": "Electricity price import provider settings."},
)
energycharts: ElecPriceEnergyChartsCommonSettings = Field(

View File

@@ -25,19 +25,6 @@ def feedintariff_provider_ids() -> list[str]:
]
class FeedInTariffCommonProviderSettings(SettingsBaseModel):
"""Feed In Tariff Prediction Provider Configuration."""
FeedInTariffFixed: Optional[FeedInTariffFixedCommonSettings] = Field(
default=None,
json_schema_extra={"description": "FeedInTariffFixed settings", "examples": [None]},
)
FeedInTariffImport: Optional[FeedInTariffImportCommonSettings] = Field(
default=None,
json_schema_extra={"description": "FeedInTariffImport settings", "examples": [None]},
)
class FeedInTariffCommonSettings(SettingsBaseModel):
"""Feed In Tariff Prediction Configuration."""
@@ -49,18 +36,14 @@ class FeedInTariffCommonSettings(SettingsBaseModel):
},
)
provider_settings: FeedInTariffCommonProviderSettings = Field(
default_factory=FeedInTariffCommonProviderSettings,
json_schema_extra={
"description": "Provider settings",
"examples": [
# Example 1: Empty/default settings (all providers None)
{
"FeedInTariffFixed": None,
"FeedInTariffImport": None,
},
],
},
feedintarifffixed: FeedInTariffFixedCommonSettings = Field(
default_factory=FeedInTariffFixedCommonSettings,
json_schema_extra={"description": "Fixed feed in tariff provider settings."},
)
feedintariffimport: FeedInTariffImportCommonSettings = Field(
default_factory=FeedInTariffImportCommonSettings,
json_schema_extra={"description": "Feed in tarif import provider settings."},
)
@computed_field # type: ignore[prop-decorator]

View File

@@ -37,9 +37,7 @@ class FeedInTariffFixed(FeedInTariffProvider):
async def _update_data(self, force_update: Optional[bool] = False) -> None:
error_msg = "Feed in tariff not provided"
try:
feed_in_tariff = (
self.config.feedintariff.provider_settings.FeedInTariffFixed.feed_in_tariff_kwh
)
feed_in_tariff = self.config.feedintariff.feedintarifffixed.feed_in_tariff_kwh
except Exception:
logger.exception(error_msg)
raise ValueError(error_msg)

View File

@@ -67,16 +67,16 @@ class FeedInTariffImport(FeedInTariffProvider, PredictionImportProvider):
async def _update_data(self, force_update: Optional[bool] = False) -> None:
# Both _sequence_lock and _record_lock are already held by the caller.
# Use internal sync methods only — never await public async counterparts.
if self.config.feedintariff.provider_settings.FeedInTariffImport is None:
if self.config.feedintariff.feedintariffimport is None:
logger.debug(f"{self.provider_id()} data update without provider settings.")
return
if self.config.feedintariff.provider_settings.FeedInTariffImport.import_file_path:
if self.config.feedintariff.feedintariffimport.import_file_path:
await self._import_from_file(
self.config.provider_settings.FeedInTariffImport.import_file_path,
self.config.feedintariffimport.import_file_path,
key_prefix="feedintariff",
)
if self.config.feedintariff.provider_settings.FeedInTariffImport.import_json:
if self.config.feedintariff.feedintariffimport.import_json:
await self._import_from_json(
self.config.feedintariff.provider_settings.FeedInTariffImport.import_json,
self.config.feedintariff.feedintariffimport.import_json,
key_prefix="feedintariff",
)

View File

@@ -44,16 +44,16 @@ class LoadCommonSettings(SettingsBaseModel):
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."},
)
vrm: LoadVrmCommonSettings = Field(
default_factory=LoadVrmCommonSettings,
json_schema_extra={"description": "Victron Remote Management (VRM) provider settings."},
)
@computed_field # type: ignore[prop-decorator]
@property
def providers(self) -> list[str]:

View File

@@ -26,14 +26,14 @@ class VrmForecastResponse(PydanticBaseModel):
class LoadVrmCommonSettings(SettingsBaseModel):
"""Common settings for load forecast VRM API."""
load_vrm_token: str = Field(
token: str = Field(
default="your-token",
json_schema_extra={
"description": "Token for Connecting VRM API",
"description": "Access token for connecting to the Victron Remote Management (VRM) API",
"examples": ["your-token"],
},
)
load_vrm_idsite: int = Field(
site_id: int = Field(
default=12345, json_schema_extra={"description": "VRM-Installation-ID", "examples": [12345]}
)
@@ -62,9 +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"
vrm_settings = self.config.load.loadvrm
installation_id = vrm_settings.load_vrm_idsite
api_token = vrm_settings.load_vrm_token
vrm_settings = self.config.load.vrm
installation_id = vrm_settings.site_id
api_token = vrm_settings.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"}

View File

@@ -180,31 +180,6 @@ class PVForecastPlaneSetting(SettingsBaseModel):
return pvtechchoice
class PVForecastCommonProviderSettings(SettingsBaseModel):
"""PV Forecast Provider Configuration."""
PVForecastImport: Optional[PVForecastImportCommonSettings] = Field(
default=None,
json_schema_extra={"description": "PVForecastImport settings", "examples": [None]},
)
PVForecastVrm: Optional[PVForecastVrmCommonSettings] = Field(
default=None,
json_schema_extra={"description": "PVForecastVrm settings", "examples": [None]},
)
PVForecastPVNode: Optional[PVForecastPVNodeCommonSettings] = Field(
default=None,
json_schema_extra={"description": "PVForecastPVNode settings", "examples": [None]},
)
PVForecastForecastSolar: Optional[PVForecastForecastSolarCommonSettings] = Field(
default=None,
json_schema_extra={"description": "PVForecastForecastSolar settings", "examples": [None]},
)
PVForecastSolcast: Optional[PVForecastSolcastCommonSettings] = Field(
default=None,
json_schema_extra={"description": "PVForecastSolcast settings", "examples": [None]},
)
class PVForecastCommonSettings(SettingsBaseModel):
"""PV Forecast Configuration."""
@@ -221,18 +196,29 @@ class PVForecastCommonSettings(SettingsBaseModel):
},
)
provider_settings: PVForecastCommonProviderSettings = Field(
default_factory=PVForecastCommonProviderSettings,
json_schema_extra={
"description": "Provider settings",
"examples": [
# Example 1: Empty/default settings (all providers None)
{
"PVForecastImport": None,
"PVForecastVrm": None,
},
],
},
pvforecastimport: PVForecastImportCommonSettings = Field(
default_factory=PVForecastImportCommonSettings,
json_schema_extra={"description": "PV forecast import provider settings"},
)
vrm: PVForecastVrmCommonSettings = Field(
default_factory=PVForecastVrmCommonSettings,
json_schema_extra={"description": "Victron Remote Management (VRM) provider settings"},
)
pvnode: PVForecastPVNodeCommonSettings = Field(
default_factory=PVForecastPVNodeCommonSettings,
json_schema_extra={"description": "PVNode provider settings"},
)
forecastsolar: PVForecastForecastSolarCommonSettings = Field(
default_factory=PVForecastForecastSolarCommonSettings,
json_schema_extra={"description": "ForecastSolar provider settings"},
)
solcast: PVForecastSolcastCommonSettings = Field(
default_factory=PVForecastSolcastCommonSettings,
json_schema_extra={"description": "Solcast provider settings"},
)
planes: Optional[list[PVForecastPlaneSetting]] = Field(

View File

@@ -62,7 +62,7 @@ class PVForecastForecastSolar(PVForecastProvider):
@property
def _api_key(self) -> Optional[str]:
settings = self.config.pvforecast.provider_settings.PVForecastForecastSolar
settings = self.config.pvforecast.forecastsolar
return settings.api_key if settings is not None else None
def _to_utc_datetime(self, local_ts: Any, iana_tz: Optional[str]) -> Any:

View File

@@ -67,16 +67,16 @@ class PVForecastImport(PVForecastProvider, PredictionImportProvider):
async def _update_data(self, force_update: Optional[bool] = False) -> None:
# Both _sequence_lock and _record_lock are already held by the caller.
# Use internal sync methods only — never await public async counterparts.
if self.config.pvforecast.provider_settings.PVForecastImport is None:
if self.config.pvforecast.pvforecastimport is None:
logger.debug(f"{self.provider_id()} data update without provider settings.")
return
if self.config.pvforecast.provider_settings.PVForecastImport.import_file_path is not None:
if self.config.pvforecast.pvforecastimport.import_file_path is not None:
await self._import_from_file(
self.config.pvforecast.provider_settings.PVForecastImport.import_file_path,
self.config.pvforecast.pvforecastimport.import_file_path,
key_prefix="pvforecast",
)
if self.config.pvforecast.provider_settings.PVForecastImport.import_json is not None:
if self.config.pvforecast.pvforecastimport.import_json is not None:
await self._import_from_json(
self.config.pvforecast.provider_settings.PVForecastImport.import_json,
self.config.pvforecast.pvforecastimport.import_json,
key_prefix="pvforecast",
)

View File

@@ -16,7 +16,7 @@ null is treated as 0 W so the optimizer's linear resampling does not interpolate
phantom production across the night.
Notes:
- Requires ``pvforecast.provider_settings.PVForecastPVNode.api_key`` (Bearer auth).
- Requires ``pvforecast.pvnode.api_key`` (Bearer auth).
- API: https://api.pvnode.com/v2 (15-minute resolution).
"""
@@ -81,7 +81,7 @@ class PVForecastPVNode(PVForecastProvider):
@property
def _settings(self) -> PVForecastPVNodeCommonSettings:
settings = self.config.pvforecast.provider_settings.PVForecastPVNode
settings = self.config.pvforecast.pvnode
if settings is None:
settings = PVForecastPVNodeCommonSettings()
return settings
@@ -158,7 +158,7 @@ class PVForecastPVNode(PVForecastProvider):
settings = self._settings
api_key = settings.api_key
if not api_key:
raise ValueError("PVForecastPVNode requires pvforecast...PVForecastPVNode.api_key")
raise ValueError("PVForecastPVNode requires pvforecast.pvnode.api_key")
headers = {"Authorization": f"Bearer {api_key}", "Accept": "application/json"}
params = {"forecast_days": str(settings.forecast_days)}
@@ -189,7 +189,7 @@ class PVForecastPVNode(PVForecastProvider):
if latitude is None or longitude is None:
raise ValueError(
"PVForecastPVNode inline mode needs general.latitude/longitude "
"(or set pvforecast...PVForecastPVNode.site_id)"
"(or set pvforecast.pvnode.site_id)"
)
planes = self.config.pvforecast.planes or []
strings = []

View File

@@ -63,7 +63,7 @@ class PVForecastSolcast(PVForecastProvider):
@property
def _settings(self) -> PVForecastSolcastCommonSettings:
settings = self.config.pvforecast.provider_settings.PVForecastSolcast
settings = self.config.pvforecast.solcast
if settings is None:
settings = PVForecastSolcastCommonSettings()
return settings

View File

@@ -1,4 +1,4 @@
"""Retrieves pvforecast data from VRM API."""
"""Retrieves pvforecast data from Victron Remote Management (VRM) API."""
from typing import Any, Optional, Union
@@ -26,14 +26,14 @@ class VrmForecastResponse(PydanticBaseModel):
class PVForecastVrmCommonSettings(SettingsBaseModel):
"""Common settings for PV forecast VRM API."""
pvforecast_vrm_token: str = Field(
token: str = Field(
default="your-token",
json_schema_extra={
"description": "Token for Connecting VRM API",
"description": "Access token for connecting to the Victron Remote Management (VRM) API",
"examples": ["your-token"],
},
)
pvforecast_vrm_idsite: int = Field(
site_id: int = Field(
default=12345, json_schema_extra={"description": "VRM-Installation-ID", "examples": [12345]}
)
@@ -63,8 +63,8 @@ class PVForecastVrm(PVForecastProvider):
def _request_forecast(self, start_ts: int, end_ts: int) -> VrmForecastResponse:
"""Fetch forecast data from Victron VRM API."""
source = "https://vrmapi.victronenergy.com/v2/installations"
id_site = self.config.pvforecast.provider_settings.PVForecastVrm.pvforecast_vrm_idsite
api_token = self.config.pvforecast.provider_settings.PVForecastVrm.pvforecast_vrm_token
id_site = self.config.pvforecast.vrm.site_id
api_token = self.config.pvforecast.vrm.token
headers = {"X-Authorization": f"Token {api_token}", "Content-Type": "application/json"}
url = f"{source}/{id_site}/stats?type=forecast&start={start_ts}&end={end_ts}&interval=hours"
logger.debug(f"Requesting VRM forecast: {url}")

View File

@@ -26,15 +26,6 @@ def weather_provider_ids() -> list[str]:
]
class WeatherCommonProviderSettings(SettingsBaseModel):
"""Weather Forecast Provider Configuration."""
WeatherImport: Optional[WeatherImportCommonSettings] = Field(
default=None,
json_schema_extra={"description": "WeatherImport settings", "examples": [None]},
)
class WeatherCommonSettings(SettingsBaseModel):
"""Weather Forecast Configuration."""
@@ -46,17 +37,9 @@ class WeatherCommonSettings(SettingsBaseModel):
},
)
provider_settings: WeatherCommonProviderSettings = Field(
default_factory=WeatherCommonProviderSettings,
json_schema_extra={
"description": "Provider settings",
"examples": [
# Example 1: Empty/default settings (all providers None)
{
"WeatherImport": None,
},
],
},
weatherimport: WeatherImportCommonSettings = Field(
default_factory=WeatherImportCommonSettings,
json_schema_extra={"description": "Weather import provider settings"},
)
@computed_field # type: ignore[prop-decorator]

View File

@@ -67,16 +67,16 @@ class WeatherImport(WeatherProvider, PredictionImportProvider):
async def _update_data(self, force_update: Optional[bool] = False) -> None:
# Both _sequence_lock and _record_lock are already held by the caller.
# Use internal sync methods only — never await public async counterparts.
if self.config.weather.provider_settings.WeatherImport is None:
if self.config.weather.weatherimport is None:
logger.debug(f"{self.provider_id()} data update without provider settings.")
return
if self.config.weather.provider_settings.WeatherImport.import_file_path:
if self.config.weather.weatherimport.import_file_path:
await self._import_from_file(
self.config.weather.provider_settings.WeatherImport.import_file_path,
self.config.weather.weatherimport.import_file_path,
key_prefix="weather",
)
if self.config.weather.provider_settings.WeatherImport.import_json:
if self.config.weather.weatherimport.import_json:
await self._import_from_json(
self.config.weather.provider_settings.WeatherImport.import_json,
self.config.weather.weatherimport.import_json,
key_prefix="weather",
)

View File

@@ -177,7 +177,7 @@ def LoadForecast(predictions: pd.DataFrame, config: dict, date_time_tz: str, dar
"date_time",
"loadforecast_power_w",
source=source,
legend_label="Load forcast value (adjusted by measurement)",
legend_label="Load forecast value (adjusted by measurement)",
color="red",
)
plot.line(

View File

@@ -1314,7 +1314,7 @@ async def fastapi_gesamtlast(request: GesamtlastRequest) -> list[float]:
detail=f"Can not update predictions: {e}",
)
# Get the forcast starting at start of day
# Get the forecast starting at start of day
start_datetime = to_datetime().start_of("day")
end_datetime = start_datetime.add(days=2)
try:
@@ -1374,7 +1374,7 @@ async def fastapi_gesamtlast_simple(year_energy: float) -> list[float]:
detail=f"Can not update predictions: {e}",
)
# Get the forcast starting at start of day
# Get the forecast starting at start of day
start_datetime = to_datetime().start_of("day")
end_datetime = start_datetime.add(days=2)
try:
@@ -1430,7 +1430,7 @@ async def fastapi_pvforecast() -> ForecastResponse:
detail=f"Can not update predictions: {e}",
)
# Get the forcast starting at start of day
# Get the forecast starting at start of day
start_datetime = to_datetime().start_of("day")
end_datetime = start_datetime.add(days=2)
try: