Source code for openstef_models.models.forecasting.flatliner_forecaster

# SPDX-FileCopyrightText: 2025 Contributors to the OpenSTEF project <openstef@lfenergy.org>
#
# SPDX-License-Identifier: MPL-2.0

"""Simple constant zero forecasting model.

Provides basic forecasting model that predict constant flatliner zero values. It can be used
when a flatline (non-)zero measurement is observed in the past and expected in the future.
"""

from typing import override

import pandas as pd
from pydantic import Field, PrivateAttr

from openstef_core.datasets.validated_datasets import ForecastDataset, ForecastInputDataset, TimeSeriesDataset
from openstef_core.mixins.predictor import HyperParams
from openstef_models.explainability.mixins import ContributionsMixin, ExplainableForecaster
from openstef_models.models.forecasting.forecaster import Forecaster

MODEL_CODE_VERSION = 1


[docs] class FlatlinerForecaster(Forecaster, ExplainableForecaster, ContributionsMixin): """Flatliner forecaster that predicts a flatline of zeros or median. A simple forecasting model that always predicts zero (or the median of historical load measurements if configured) for all horizons and quantiles. Invariants: - Configuration quantiles determine the number of prediction outputs - Zeros (or median values) are predicted for all horizons and quantiles Example: >>> from openstef_core.types import LeadTime, Quantile >>> from datetime import timedelta >>> forecaster = FlatlinerForecaster( ... quantiles=[Quantile(0.5), Quantile(0.1), Quantile(0.9)], ... horizons=[LeadTime(timedelta(hours=1)), LeadTime(timedelta(hours=2))], ... ) >>> forecaster.fit(training_data) # doctest: +SKIP >>> predictions = forecaster.predict(test_data) # doctest: +SKIP See Also: Forecaster: Base class for forecasting models that predict multiple horizons. """ predict_median: bool = Field( default=False, description="If True, predict the median of load measurements instead of zero.", ) hyperparams: HyperParams = Field( default_factory=HyperParams, description="Model hyperparameters (no tuning parameters for flatliner).", ) _median_value: float | None = PrivateAttr(default=None) @property @override def hparams(self) -> HyperParams: return self.hyperparams @property @override def is_fitted(self) -> bool: if self.predict_median: return self._median_value is not None return True
[docs] @override def fit( self, data: ForecastInputDataset, data_val: ForecastInputDataset | None = None, ) -> None: if self.predict_median: self._median_value = float(data.target_series.median())
[docs] @override def predict(self, data: ForecastInputDataset) -> ForecastDataset: forecast_index = data.create_forecast_range(horizon=self.max_horizon) prediction_value = self._median_value if self.predict_median else 0.0 return ForecastDataset( data=pd.DataFrame( data={quantile.format(): prediction_value for quantile in self.quantiles}, index=forecast_index, ), sample_interval=data.sample_interval, target_column=data.target_column, )
@property @override def feature_importances(self) -> pd.DataFrame: columns = [quantile.format() for quantile in self.quantiles] return pd.DataFrame( data=[[0.0] * len(columns)], index=["load"], columns=columns, )
[docs] @override def predict_contributions(self, data: ForecastInputDataset) -> TimeSeriesDataset: """Return zero contributions since flatliner has no features.""" input_data = data.input_data(start=data.forecast_start) contribs_df = pd.DataFrame(0.0, index=input_data.index, columns=["bias"]) return TimeSeriesDataset(data=contribs_df, sample_interval=data.sample_interval)