diff --git a/src/akkudoktoreos/prediction/elecpriceenergycharts.py b/src/akkudoktoreos/prediction/elecpriceenergycharts.py index 12fd1c0d..bc842314 100644 --- a/src/akkudoktoreos/prediction/elecpriceenergycharts.py +++ b/src/akkudoktoreos/prediction/elecpriceenergycharts.py @@ -270,12 +270,22 @@ class ElecPriceEnergyCharts(ElecPriceProvider): elif force_update: # Use default start date in case of forced update needs_update = True - elif not self._has_complete_published_horizon( - now=now, resolution_seconds=resolution_seconds - ): - # We have enough history, but not every expected source interval. - start_datetime = gross_start_datetime - 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) + if not self._has_complete_published_horizon( + now=now, resolution_seconds=source_resolution_seconds + ): + start_datetime = gross_start_datetime + needs_update = True else: needs_update = True diff --git a/tests/test_elecpriceenergycharts.py b/tests/test_elecpriceenergycharts.py index 8c73103c..7b8c63d6 100644 --- a/tests/test_elecpriceenergycharts.py +++ b/tests/test_elecpriceenergycharts.py @@ -159,6 +159,7 @@ class TestElecPriceEnergyCharts: @pytest.mark.asyncio @pytest.mark.parametrize("host_timezone", ["UTC", "Europe/Berlin"]) + @pytest.mark.parametrize("history_interval_minutes", [15, 60]) @pytest.mark.parametrize( ("now", "last_price", "interval_minutes", "needs_update"), [ @@ -180,6 +181,7 @@ class TestElecPriceEnergyCharts: provider: ElecPriceEnergyCharts, set_other_timezone: Callable[[str], str], host_timezone: str, + history_interval_minutes: int, now: str, last_price: str, interval_minutes: int, @@ -196,16 +198,35 @@ class TestElecPriceEnergyCharts: pd.Timestamp(last_price, tz="Europe/Berlin"), in_timezone="Europe/Berlin" ) get_ems().set_start_datetime(start) - raw_index = pd.date_range( + history_index = pd.date_range( start=start.subtract(days=35), + end=start, + freq=f"{history_interval_minutes}min", + inclusive="left", + ) + source_index = pd.date_range( + start=start, end=last_original, freq=f"{interval_minutes}min", ) await provider.key_from_series( - "elecprice_marketprice_raw_wh", pd.Series(0.0001, index=raw_index) + "elecprice_marketprice_raw_wh", + pd.Series(0.0001, index=history_index.append(source_index)), ) provider.highest_orig_datetime = last_original + # Predicted values share the raw key but must not determine source coverage. + # Use enough slots at a different resolution to dominate an unbounded estimate. + predicted_interval_minutes = 60 if interval_minutes == 15 else 15 + predicted_index = pd.date_range( + start=last_original.add(minutes=predicted_interval_minutes), + periods=120, + freq=f"{predicted_interval_minutes}min", + ) + await provider.key_from_series( + "elecprice_marketprice_raw_wh", pd.Series(0.00005, index=predicted_index) + ) + published_end = start.add(days=1 if fixed_now.hour < 14 else 2) response_index = pd.date_range( start=start, end=published_end, freq=f"{interval_minutes}min", inclusive="left"