fix: surface DVhub feed-in tariff update failures (#1357)

* fix: surface DVhub feed-in tariff update failures

* test: annotate DVhub sample records for mypy

* fix: require real feed-in tariff coverage in GENETIC0

* test: satisfy GENETIC0 tariff coverage checks

* test: patch DVhub storage method on its class
This commit is contained in:
Normann
2026-10-11 08:39:01 +02:00
committed by GitHub
parent fca07a0c62
commit e203a81666
5 changed files with 184 additions and 61 deletions
@@ -8,6 +8,7 @@ It also provides a method to assemble these parameters from predictions,
forecasts, and fallback defaults, preparing them for optimization runs.
"""
import math
from typing import Optional, Union
from loguru import logger
@@ -36,6 +37,7 @@ from akkudoktoreos.devices.genetic0.genetic0homeappliance import (
)
from akkudoktoreos.devices.genetic0.genetic0inverter import Genetic0InverterParameters
from akkudoktoreos.optimization.genetic0.genetic0abc import Genetic0ParametersBaseModel
from akkudoktoreos.optimization.genetic.forecast import bounded_forecast_array
from akkudoktoreos.utils.datetimeutil import to_duration
# Do not import directly from akkudoktoreos.core.coreabc
@@ -506,41 +508,28 @@ class Genetic0OptimizationParameters(
)
# Retry
continue
try:
array = await cls.prediction.key_to_array(
key="feed_in_tariff_wh",
start_datetime=parameter_start_datetime,
end_datetime=parameter_end_datetime,
interval=interval,
fill_method="ffill",
tariff_values = await bounded_forecast_array(
cls.prediction,
key="feed_in_tariff_wh",
start_datetime=parameter_start_datetime,
end_datetime=parameter_end_datetime,
interval=interval,
)
first = int(
(ems.start_datetime - parameter_start_datetime).total_seconds()
/ interval.total_seconds()
)
last = first + cls.config.optimization.genetic0.horizon_hours
if last > len(tariff_values) or not all(
math.isfinite(value) for value in tariff_values[first:last]
):
logger.error(
"Missing feed-in tariff within the GENETIC0 control horizon; "
"canceling optimization with provider {}.",
cls.config.feedintariff.provider,
)
feed_in_tariff_wh = array.tolist()
except Exception as e:
logger.info(
"No feed in tariff forecast data available - defaulting to demo data. Parameter preparation attempt {}: {}",
attempt,
e,
)
cls.config.merge_settings_from_dict(
{
"feedintariff": {
"provider": "FeedInTariffFixed",
"feedintarifffixed": {
"feed_in_tariff_amt_kwh": {
"windows": [
{
"start_time": "00:00",
"duration": "24 hours",
"value": 0.078,
},
],
},
},
},
}
)
# Retry
continue
return None
feed_in_tariff_wh = tariff_values.tolist()
# Add device data
@@ -42,8 +42,8 @@ class FeedInTariffDvhubOnline(FeedInTariffProvider):
market price is stored as ``feed_in_tariff_wh`` (EUR/Wh) — like
``FeedInTariffEnergyCharts`` this intentionally adds no import charges or
VAT, so the series is the direct-marketing revenue the optimizer needs.
Slots beyond the published day-ahead horizon are left to the consumer's
forward-fill (same behaviour as ``FeedInTariffImport``).
Only published slots are stored. Automatic optimization requires real
tariff coverage for the GENETIC or GENETIC0 control horizon.
"""
highest_orig_datetime: Optional[datetime] = None
@@ -124,24 +124,11 @@ class FeedInTariffDvhubOnline(FeedInTariffProvider):
)
end_date = to_datetime(self.end_datetime, as_string="YYYY-MM-DD")
try:
dvhub_data = self._request_forecast(
start_date=start_date, end_date=end_date, force_update=force_update
) # type: ignore[call-arg]
series_data = self._parse_data(dvhub_data)
if series_data.empty:
raise ValueError("No dvhub.online feed-in tariff data available")
self.highest_orig_datetime = series_data.index.max()
await self.key_from_series("feed_in_tariff_wh", series_data)
except Exception as exc:
if self.highest_orig_datetime is None:
# Cold start: nothing to fall back to — a failed fetch is fatal.
raise
# Transient outage with existing history: keep the history and let
# downstream forward-fill cover the remaining slots.
logger.warning(
"dvhub.online feed-in tariff update failed ({}); keeping existing "
"history until {}.",
exc,
self.highest_orig_datetime,
)
dvhub_data = self._request_forecast(
start_date=start_date, end_date=end_date, force_update=force_update
) # type: ignore[call-arg]
series_data = self._parse_data(dvhub_data)
if series_data.empty:
raise ValueError("No dvhub.online feed-in tariff data available")
await self.key_from_series("feed_in_tariff_wh", series_data)
self.highest_orig_datetime = series_data.index.max()