merge: refresh configuration foundation with current main

This commit is contained in:
Andreas
2026-09-16 13:19:51 +02:00
2 changed files with 237 additions and 25 deletions
@@ -98,6 +98,35 @@ class ElecPriceEnergyCharts(ElecPriceProvider):
"""Return the unique identifier for the Energy-Charts provider."""
return "ElecPriceEnergyCharts"
def _coverage_resolution_seconds(self, source_series: pd.Series) -> int:
"""Infer interval coverage from original prices without changing forecast resolution.
The caller excludes timestamps beyond ``highest_orig_datetime``. Within the last
24 hours, four equal spacings among the final five differences establish a recent
cadence. This recognizes five consecutive points at a new resolution while tolerating
one exceptional gap. Only positive intervals dividing one hour are supported, as in
the shared resolution helper.
Ambiguous or insufficient agreement falls back to the median resolution of these
24 hours; fewer than two distinct timestamps fall back to hourly coverage.
"""
if source_series.empty:
return 3600
recent_series = source_series.sort_index()
recent_series = recent_series[
recent_series.index >= recent_series.index[-1] - pd.Timedelta(hours=24)
]
index = pd.DatetimeIndex(recent_series.index).drop_duplicates()
deltas = index.to_series().diff().dropna().dt.total_seconds().tail(5)
counts = deltas.value_counts()
if not counts.empty and counts.iloc[0] >= 4:
resolution = float(counts.index[0])
if resolution > 0 and 3600 % resolution == 0:
return int(resolution)
return self._resolution_seconds(recent_series)
def _has_complete_published_horizon(
self, *, now: pd.Timestamp, resolution_seconds: int
) -> bool:
@@ -247,12 +276,21 @@ class ElecPriceEnergyCharts(ElecPriceProvider):
# Determine if update is needed and what start date is really necessary
needs_update = False
raw_series: Optional[pd.Series] = None
if self.highest_orig_datetime:
raw_history = await self.key_to_raw_series(
source_end = to_datetime(self.highest_orig_datetime).add(seconds=1)
raw_series = await self.key_to_raw_series(
key="elecprice_marketprice_raw_wh",
start_datetime=start_datetime,
end_datetime=gross_start_datetime,
end_datetime=max(gross_start_datetime, source_end),
)
# Preserve the history window even during an outage when the latest original
# timestamp precedes EMS start. Reuse this read for coverage and forecasting,
# but exclude the extrapolated tail from both resolution estimates.
raw_history = raw_series[
(raw_series.index >= pd.Timestamp(start_datetime))
& (raw_series.index < pd.Timestamp(gross_start_datetime))
]
raw_series = raw_series[raw_series.index <= pd.Timestamp(self.highest_orig_datetime)]
if raw_history.empty:
# We need the default start date (35 days in past)
@@ -271,16 +309,7 @@ class ElecPriceEnergyCharts(ElecPriceProvider):
# Use default start date in case of forced update
needs_update = True
else:
# The latest source data may have a different resolution than
# the history before ems_start_datetime. Use its final 24 hours
# so older, finer intervals cannot dominate the median, and
# exclude the predicted tail.
source_series = await self.key_to_raw_series(
key="elecprice_marketprice_raw_wh",
start_datetime=to_datetime(self.highest_orig_datetime).subtract(hours=24),
end_datetime=to_datetime(self.highest_orig_datetime).add(seconds=1),
)
source_resolution_seconds = self._resolution_seconds(source_series)
source_resolution_seconds = self._coverage_resolution_seconds(raw_series)
if not self._has_complete_published_horizon(
now=now, resolution_seconds=source_resolution_seconds
):
@@ -311,6 +340,8 @@ class ElecPriceEnergyCharts(ElecPriceProvider):
raise ValueError("No Energy-Charts electricity price data available")
self.highest_orig_datetime = to_datetime(series_data.index.max())
await self.key_from_series("elecprice_marketprice_raw_wh", series_data)
# Reload after a successful fetch so prediction sees the new source data.
raw_series = None
# Newly fetched data widens the window that needs its gross
# (fee-inclusive) values recomputed.
gross_start_datetime = to_datetime(series_data.index.min())
@@ -335,10 +366,11 @@ class ElecPriceEnergyCharts(ElecPriceProvider):
logger.error(error_msg)
raise ValueError(error_msg)
raw_series = await self.key_to_raw_series(
key="elecprice_marketprice_raw_wh",
end_datetime=to_datetime(self.highest_orig_datetime).add(seconds=1),
)
if raw_series is None:
raw_series = await self.key_to_raw_series(
key="elecprice_marketprice_raw_wh",
end_datetime=to_datetime(self.highest_orig_datetime).add(seconds=1),
)
resolution_seconds = self._resolution_seconds(raw_series)
slots_per_hour = 3600 // resolution_seconds