Skip to content

feat(tuning): Bayesian hyperparameter tuning via Optuna integration#851

Open
egordm wants to merge 63 commits intorelease/v4.0.0from
refactor/tuning-module-split
Open

feat(tuning): Bayesian hyperparameter tuning via Optuna integration#851
egordm wants to merge 63 commits intorelease/v4.0.0from
refactor/tuning-module-split

Conversation

@egordm
Copy link
Copy Markdown
Collaborator

@egordm egordm commented Mar 27, 2026

New feature: Bayesian hyperparameter tuning with Optuna

This PR introduces a first-class hyperparameter tuning API to OpenSTEF, powered by Optuna. The design goal is to keep search space declarations co-located with the model definition — no separate config files, no extra boilerplate.


How it works

Step 1 — declare the search space inline

Pass a FloatRange, IntRange, or CategoricalRange directly in the HyperParams constructor to mark fields for tuning. Everything else keeps its default value.

from openstef_core.mixins.param_ranges import FloatRange, IntRange
from openstef_models.models.forecasting.xgboost_forecaster import XGBoostHyperParams

config = ForecastingWorkflowConfig(
    model_id="my_forecast",
    model="xgboost",
    xgboost_hyperparams=XGBoostHyperParams(
        learning_rate=FloatRange(0.01, 0.3, log=True, tune=True),
        n_estimators=IntRange(50, 500, tune=True),
        max_depth=IntRange(3, 10, tune=True),
    ),
)

Step 2 — run the tuner

from openstef_models.integrations.optuna import HyperparameterTuner

tuner = HyperparameterTuner(
    config=config,
    train_dataset=dataset,
    create_workflow=create_forecasting_workflow,
    target_quantile=Q(0.5),
    metric_name="R2",
    n_trials=50,
)
result = tuner.fit_with_tuning()

Step 3 — use the result

result.best_config   # ForecastingWorkflowConfig with tuned values applied
result.workflow      # Fitted workflow, ready to generate forecasts
result.study         # Full optuna.Study for analysis and visualisation

What's included

Component Package Description
FloatRange, IntRange, CategoricalRange openstef-core Frozen dataclasses that encode tuning search spaces as field annotations
HyperParams.get_search_space() openstef-core Resolves instance overrides against class-level defaults and returns only tune=True fields
get_tuning_range(field_info) openstef-core Public helper that extracts a TuningRange from a Pydantic FieldInfo's metadata
HyperparameterTuner openstef-models Optuna-backed tuner: TPE sampling, median pruning, configurable direction/parallelism
TuningResult openstef-models Typed result container: best_config, study, and fitted workflow
XGBoostHyperParams, GBLinearHyperParams openstef-models All fields annotated with class-level default ranges; tuning opt-in per instantiation

The HyperparameterTuner is extensible: override _evaluate_trial, _create_study, or suggest_value to customise sampling, cross-validation, or pruning behaviour.


Installation

Optuna is an optional dependency. Install via:

pip install openstef-models[tuning]
# or with uv:
uv add openstef-models[tuning]

Tutorial

examples/tutorials/hyperparameter_tuning_with_optuna.ipynb walks through the full workflow:

  • Downloading the Liander benchmark dataset
  • Declaring an inline search space
  • Running a 20-trial study
  • Inspecting trial history and hyperparameter importances
  • Training the final model with the best config

Testing

# All tests (includes 36 targeted tuning tests)
uv run poe tests

# Targeted subset
uv run pytest packages/openstef-models/tests/unit/integrations/optuna/
uv run pytest packages/openstef-core/tests/unit/mixins/

fleur-petit and others added 30 commits March 26, 2026 16:39
Signed-off-by: Fleur Petit <fleur.petit@alliander.com>
- Using Annotated fields to set search ranges on XGBoost and GBLinear hyperparameters;
- try_optuna.ipynb to explore hyperparameter tuning with optuna.

Signed-off-by: Fleur Petit <fleur.petit@alliander.com>
Signed-off-by: Fleur Petit <fleur.petit@alliander.com>
…#811)

* feature(STEF-2717): save last valid rolling aggregate during training

* add backwards compatibility for RollingAggregatesAdder and SampleWeighter

* remove backwards compatibility for SampleWeighter (other branch)

* remove unused imports

Signed-off-by: Fleur Petit <fleur.petit@alliander.com>
Signed-off-by: Fleur Petit <fleur.petit@alliander.com>
* wip

* wip

* Add median model

* Fix linting

* added and fixed unit tests

* ran linting

* ran format

* fix linting and formatting

* fix more linting and formatting

* fix type check

* Implemented comments

* moved sample interval check to TimeseriesDataset and updated tests accordingly

* fix doc test

* implemented fixes after comments

* fix typing issue

Signed-off-by: Fleur Petit <fleur.petit@alliander.com>
…ions (#813)

When rolling_aggregate_features is empty (e.g., ato_regions, grid_losses),
the RollingAggregatesAdder transform is no longer added to the pipeline.
Previously it was always added, and fit() would crash with
ValueError: No objects to concatenate when calling pandas rolling().agg([]).

Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>
Signed-off-by: Fleur Petit <fleur.petit@alliander.com>
The bare 'mlflow' pattern matched anywhere in the tree, which caused
hatchling to exclude packages/openstef-models/src/openstef_models/integrations/mlflow/
from the built wheel. Root-anchoring with '/mlflow' limits the match to
only the top-level mlflow directory (local MLflow data).

Broken since v4.0.0.a17 (commit 1bcf71d).

Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>
Signed-off-by: Fleur Petit <fleur.petit@alliander.com>
* Added Lightgbm, LightGBM Linear Trees and Hybrid Stacking Forecasters

* Fixed small issues

* Ruff compliance

* fixed quality checks

* Fixed last issues, Signed-off-by: Lars van Someren <lars.vansomeren@sia-partners.com>

* fixed comments

* Refactor LightGBM to LGBM

* Update LGBM and LGBMLinear defaults, fixed comments

* Fixed comments

* Added SkopsModelSerializer

* Fixed issues

* Gitignore optimization and dev sandbox

* Added MultiQuantileAdapter Class

* small fix

* Hybrid V2

* Small fix

* Squashed commit of the following:

commit 37089b8
Author: Egor Dmitriev <egor.dmitriev@alliander.com>
Date:   Mon Nov 17 15:29:59 2025 +0100

    fix(#728): Fixed parallelism stability issues, and gblinear feature pipeline. (#752)

    * fix(STEF-2475): Added loky as default option for parallelism since fork causes instabilities for xgboost results.

    Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>

    * fix(STEF-2475): Added better support for flatliners and predicting when data is sparse.

    Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>

    * fix(STEF-2475): Feature handing improvements for gblinear. Like imputation, nan dropping, and checking if features are available.

    Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>

    * fix(#728): Added checks on metrics to gracefully handle empty data. Added flatline filtering during evalution.

    Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>

    * fix(#728): Updated xgboost to skip scaling on empty prediction.

    Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>

    * fix(STEF-2475): Added parallelism parameters.

    Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>

    ---------

    Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>

commit a85a3f7
Author: Egor Dmitriev <egor.dmitriev@alliander.com>
Date:   Fri Nov 14 14:31:34 2025 +0100

    fix(STEF-2475): Fixed rolling aggregate adder by adding forward filling and stating support for only one horizon. (#750)

    Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>

commit 4f0c664
Author: Egor Dmitriev <egor.dmitriev@alliander.com>
Date:   Thu Nov 13 16:54:15 2025 +0100

    feature: Disabled data cutoff by default to be consistent with openstef 3.  And other minor improvements. (#748)

commit 493126e
Author: Egor Dmitriev <egor.dmitriev@alliander.com>
Date:   Thu Nov 13 16:12:35 2025 +0100

    fix(STEF-2475) fix and refactor backtesting iction in context of backtestforecasting config for clarity. Added more colors. Fixed data split function to handle 0.0 splits. (#747)

    * fix: Fixed data collation during backtesting. Renamed horizon to prediction in context of backtestforecasting config for clarity. Added more colors. Fixed data split function to handle 0.0 splits.

    * fix: Formatting.

    Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>

    * fix: Formatting.

    Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>

    ---------

    Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>

commit 6b1da44
Author: Egor Dmitriev <egor.dmitriev@alliander.com>
Date:   Thu Nov 13 16:05:32 2025 +0100

    feature: forecaster hyperparams and eval metrics (#746)

    * feature(#729) Removed to_state and from_state methods in favor of builtin python state saving functions.

    Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>

    * feature(#729): Fixed issue where generic transform pipeline could not be serialized.

    Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>

    * feature(#729): Added more state saving tests

    Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>

    * feature(#729): Added more state saving tests

    Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>

    * feature(#729): Added more state saving tests

    Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>

    * feature: standardized objective function. Added custom evaluation functions for forecasters.

    * fix: Formatting.

    Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>

    ---------

    Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>

* set silence

* small fix

* Fix final learner

* fixed lgbm efficiency

* updated lgbm linear params

* Fixed type and quality issues

* First Version Sample Weighting Approach

Signed-off-by: Lars van Someren <lvsom1@gmail.com>

* MetaForecasterClass

Signed-off-by: Lars van Someren <lvsom1@gmail.com>

* Research/v4.1.0 additional forecasters (#765)

* Added Lightgbm, LightGBM Linear Trees and Hybrid Stacking Forecasters

* Fixed small issues

* Ruff compliance

* fixed quality checks

* Fixed last issues, Signed-off-by: Lars van Someren <lars.vansomeren@sia-partners.com>

* fixed comments

* Refactor LightGBM to LGBM

* Update LGBM and LGBMLinear defaults, fixed comments

* Fixed comments

* Added SkopsModelSerializer

* Fixed issues

* Gitignore optimization and dev sandbox

* Added MultiQuantileAdapter Class

* small fix

* Hybrid V2

* Small fix

* Squashed commit of the following:

commit 37089b8
Author: Egor Dmitriev <egor.dmitriev@alliander.com>
Date:   Mon Nov 17 15:29:59 2025 +0100

    fix(#728): Fixed parallelism stability issues, and gblinear feature pipeline. (#752)

    * fix(STEF-2475): Added loky as default option for parallelism since fork causes instabilities for xgboost results.

    Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>

    * fix(STEF-2475): Added better support for flatliners and predicting when data is sparse.

    Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>

    * fix(STEF-2475): Feature handing improvements for gblinear. Like imputation, nan dropping, and checking if features are available.

    Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>

    * fix(#728): Added checks on metrics to gracefully handle empty data. Added flatline filtering during evalution.

    Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>

    * fix(#728): Updated xgboost to skip scaling on empty prediction.

    Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>

    * fix(STEF-2475): Added parallelism parameters.

    Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>

    ---------

    Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>

commit a85a3f7
Author: Egor Dmitriev <egor.dmitriev@alliander.com>
Date:   Fri Nov 14 14:31:34 2025 +0100

    fix(STEF-2475): Fixed rolling aggregate adder by adding forward filling and stating support for only one horizon. (#750)

    Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>

commit 4f0c664
Author: Egor Dmitriev <egor.dmitriev@alliander.com>
Date:   Thu Nov 13 16:54:15 2025 +0100

    feature: Disabled data cutoff by default to be consistent with openstef 3.  And other minor improvements. (#748)

commit 493126e
Author: Egor Dmitriev <egor.dmitriev@alliander.com>
Date:   Thu Nov 13 16:12:35 2025 +0100

    fix(STEF-2475) fix and refactor backtesting iction in context of backtestforecasting config for clarity. Added more colors. Fixed data split function to handle 0.0 splits. (#747)

    * fix: Fixed data collation during backtesting. Renamed horizon to prediction in context of backtestforecasting config for clarity. Added more colors. Fixed data split function to handle 0.0 splits.

    * fix: Formatting.

    Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>

    * fix: Formatting.

    Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>

    ---------

    Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>

commit 6b1da44
Author: Egor Dmitriev <egor.dmitriev@alliander.com>
Date:   Thu Nov 13 16:05:32 2025 +0100

    feature: forecaster hyperparams and eval metrics (#746)

    * feature(#729) Removed to_state and from_state methods in favor of builtin python state saving functions.

    Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>

    * feature(#729): Fixed issue where generic transform pipeline could not be serialized.

    Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>

    * feature(#729): Added more state saving tests

    Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>

    * feature(#729): Added more state saving tests

    Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>

    * feature(#729): Added more state saving tests

    Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>

    * feature: standardized objective function. Added custom evaluation functions for forecasters.

    * fix: Formatting.

    Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>

    ---------

    Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>

* set silence

* small fix

* Fix final learner

* fixed lgbm efficiency

* updated lgbm linear params

* Fixed type and quality issues

* remove depricated files

Signed-off-by: Lars van Someren <lvsom1@gmail.com>

* change: Fixed dependencies to align more with the current release.

Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>

* change: Style fixes.

Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>

---------

Signed-off-by: Lars van Someren <lvsom1@gmail.com>
Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>
Co-authored-by: Egor Dmitriev <egor.dmitriev@alliander.com>

* fix merge issue

Signed-off-by: Lars van Someren <lvsom1@gmail.com>

* Fixed type Issues

Signed-off-by: Lars van Someren <lvsom1@gmail.com>

* Introduced openstef_metalearning

Signed-off-by: Lars van Someren <lvsom1@gmail.com>

* ResidualForecaster + refactoring

Signed-off-by: Lars van Someren <lvsom1@gmail.com>

* Testing and fixes on Learned Weights Forecaster

Signed-off-by: Lars van Someren <lvsom1@gmail.com>

* FinalLearner PreProcessor

Signed-off-by: Lars van Someren <lvsom1@gmail.com>

* Fixed benchmark references

Signed-off-by: Lars van Someren <lvsom1@gmail.com>

* Added additional Feature logic to StackingForecaster

Signed-off-by: Lars van Someren <lvsom1@gmail.com>

* added example to openstef Meta

Signed-off-by: Lars van Someren <lvsom1@gmail.com>

* RulesForecaster with dummy features

Signed-off-by: Lars van Someren <lvsom1@gmail.com>

* Updated feature specification

Signed-off-by: Lars van Someren <lvsom1@gmail.com>

* entered flagger feature in new architecture

* Fix sample weights

Signed-off-by: Lars van Someren <lvsom1@gmail.com>

* Fixes

Signed-off-by: Lars van Someren <lvsom1@gmail.com>

* PR compliant

Signed-off-by: Lars van Someren <lvsom1@gmail.com>

* Ensemble Forecast Dataset

Signed-off-by: Lars van Someren <lvsom1@gmail.com>

* Make PR compliant

Signed-off-by: Lars van Someren <lvsom1@gmail.com>

* fixed toml

Signed-off-by: Lars van Someren <lvsom1@gmail.com>

* Really fixed the TOML

Signed-off-by: Lars van Someren <lvsom1@gmail.com>

* Renamed FinalLearner to Forecast Combiner. Eliminated redundant classes

Signed-off-by: Lars van Someren <lvsom1@gmail.com>

* fixed small issues

Signed-off-by: Lars van Someren <lvsom1@gmail.com>

* Major Refactor, Working Version

Signed-off-by: Lars van Someren <lvsom1@gmail.com>

* Fixed tests

Signed-off-by: Lars van Someren <lvsom1@gmail.com>

* Prepared TODOs for Florian

Signed-off-by: Lars van Someren <lvsom1@gmail.com>

* Small fix

Signed-off-by: Lars van Someren <lvsom1@gmail.com>

* Made PR Compliant

Signed-off-by: Lars van Someren <lvsom1@gmail.com>

* BugFix

Signed-off-by: Lars van Someren <lvsom1@gmail.com>

* fixes

Signed-off-by: Lars van Someren <lvsom1@gmail.com>

* bug fixes

Signed-off-by: Lars van Someren <lvsom1@gmail.com>

* added learned weights contributions

* Added Feature Contributions

Residual Forecaster and Stacking Forecaster can now predict model contributions. Regular forecasters (EXCEPT LGBM Linear) can predict feature contributions

* Bugfixes

Signed-off-by: Lars van Someren <lvsom1@gmail.com>

* fixes

Signed-off-by: Lars van Someren <lvsom1@gmail.com>

* Squashed commit of the following:

commit 6f88d72
Author: Lars van Someren <lvsom1@gmail.com>
Date:   Mon Dec 8 09:46:57 2025 +0100

    Bugfixes

    Signed-off-by: Lars van Someren <lvsom1@gmail.com>

commit b44fd92
Author: Lars van Someren <lvsom1@gmail.com>
Date:   Thu Dec 4 14:39:31 2025 +0100

    bug fixes

    Signed-off-by: Lars van Someren <lvsom1@gmail.com>

commit e212448
Author: Lars van Someren <lvsom1@gmail.com>
Date:   Thu Dec 4 12:38:24 2025 +0100

    fixes

    Signed-off-by: Lars van Someren <lvsom1@gmail.com>

commit eb775e4
Author: Lars van Someren <lvsom1@gmail.com>
Date:   Thu Dec 4 11:40:44 2025 +0100

    BugFix

    Signed-off-by: Lars van Someren <lvsom1@gmail.com>

commit c33ce93
Author: Lars van Someren <lvsom1@gmail.com>
Date:   Wed Dec 3 14:15:06 2025 +0100

    Made PR Compliant

    Signed-off-by: Lars van Someren <lvsom1@gmail.com>

* Fixes

Signed-off-by: Lars van Someren <lvsom1@gmail.com>

* fixed tests

Signed-off-by: Lars van Someren <lvsom1@gmail.com>

* small fix

Signed-off-by: Lars van Someren <lvsom1@gmail.com>

* Stacking Bugfix

Signed-off-by: Lars van Someren <lvsom1@gmail.com>

* Added hard Forecast Selection

Signed-off-by: Lars van Someren <lvsom1@gmail.com>

* Improved data handling in EnsembleForecasting model, correct data splitting and Model Fit Result. Validation and test data can now be fully used

Signed-off-by: Lars van Someren <lvsom1@gmail.com>

* Migrated Flagger and Selector to OpenSTEF Models transforms

Signed-off-by: Lars van Someren <lvsom1@gmail.com>

* Fixed restore target Forecast Combiner

Signed-off-by: Lars van Someren <lvsom1@gmail.com>

* Streamline logging statements, Fix quality

Signed-off-by: Lars van Someren <lvsom1@gmail.com>

* Resolved comments, fixed bug

Signed-off-by: Lars van Someren <lvsom1@gmail.com>

* Moved example

Signed-off-by: Lars van Someren <lvsom1@gmail.com>

* Bring 4.1 up to date with release
Squashed commit of the following:

commit 6d140bc
Author: Lars Schilders <123180911+lschilders@users.noreply.github.com>
Date:   Wed Dec 17 10:33:19 2025 +0100

    feature: add regex pattern matching in FeatureSelection and fix combine bug (#787)

commit 32a42bb
Author: Lars Schilders <123180911+lschilders@users.noreply.github.com>
Date:   Tue Dec 16 13:50:40 2025 +0100

    feature: Selector transform (#786)

    * feature: add Selector transform

    * add ForecastInputDataset testcases

    * add selected_features to presets

    * add doctest

commit d3977b1
Author: Egor Dmitriev <egor.dmitriev@alliander.com>
Date:   Mon Dec 15 09:17:38 2025 +0100

    feature: added tutorials for basic functionality. Added convenience method for simple openstef baselines. (#785)

    * feature: Added tutorial start.

    Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>

    * feature: Added example notebooks. First draft.

    Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>

    * chore(examples): add examples workspace project and register it in workspace; update lock

    * fix(lint): add missing docstrings in baselines package (D104, D103)

    * chore(examples): add examples workspace project and register it in workspace; update lock

    * chore(examples): Updated text in examples.

    ---------

    Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>

commit 8a4097c
Author: Bart Pleiter <bart.pleiter@alliander.com>
Date:   Wed Dec 10 16:19:48 2025 +0100

    fix: exclude stdev column from quantile column checking. (#783)

    * fix: exclude stdev column from quantile column checking.

    Signed-off-by: Bart Pleiter <bart.pleiter@alliander.com>

    * fix: duplicate removed.

    Signed-off-by: Bart Pleiter <bart.pleiter@alliander.com>

    * fix: type

    Signed-off-by: Bart Pleiter <bart.pleiter@alliander.com>

    ---------

    Signed-off-by: Bart Pleiter <bart.pleiter@alliander.com>

commit 43987fc
Author: Egor Dmitriev <egor.dmitriev@alliander.com>
Date:   Wed Dec 10 10:24:32 2025 +0100

    fix(STEF-2549): Added none check for model end date from mlflow. Added experiment tags. (#782)

    Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>

commit 1891009
Author: Lars Schilders <123180911+lschilders@users.noreply.github.com>
Date:   Tue Dec 9 14:40:40 2025 +0100

    feature: check for model config change and skip model selection (#781)

    * feature: check for model config change and skip model selection

    * changed checking model compatibility

    * check for tag compatibility only

    * fix tests

    * rename new methods in callback

commit c37ac92
Author: Lars Schilders <123180911+lschilders@users.noreply.github.com>
Date:   Tue Dec 9 09:22:58 2025 +0100

    fix: clip values of wind and solar components to below 0 (#779)

    * fix: clip values of wind and solar components to below 0

    * add test for not all components zero

commit 3eb7e69
Author: Egor Dmitriev <egor.dmitriev@alliander.com>
Date:   Mon Dec 8 15:55:16 2025 +0100

    feat(mlflow): suppress MLflow emoji URL logs (#780)

    * feat(mlflow): suppress MLflow emoji URL logs

    Add MLFLOW_SUPPRESS_PRINTING_URL_TO_STDOUT=true environment variable
    to prevent MLflow from printing 'View run...' messages with emojis
    that don't comply with ECS JSON logging format.

    Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>

    * feature: Style fixes.

    Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>

    ---------

    Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>

commit eca628e
Author: Lars Schilders <123180911+lschilders@users.noreply.github.com>
Date:   Fri Dec 5 16:27:53 2025 +0100

    feature: nonzero flatliner preset (#777)

    * add predict_nonzero_flatliner to presets

    * remove redundant validate_required_columns

commit 61e1699
Author: Lars Schilders <123180911+lschilders@users.noreply.github.com>
Date:   Fri Dec 5 15:37:05 2025 +0100

    feature: add standard devation column to ForecastDataset and add it in ConfidenceIntervalApplicator (#778)

    * feature: add standard devation column to ForecastDataset and add it in ConfidenceIntervalApplicator

    * simplify code for adding column

commit 4f70d00
Author: Lars Schilders <123180911+lschilders@users.noreply.github.com>
Date:   Fri Dec 5 10:41:23 2025 +0100

    chore: change radiation unit to Wm-2 (#776)

    * chore: change expected radiation unit to W/m-2

    * change values in test for radiation features adder

    * fix docs for dni/gti unit

    * formatting

commit 71ac428
Author: Bart Pleiter <bart.pleiter@alliander.com>
Date:   Wed Dec 3 09:45:34 2025 +0100

    feature: added use_median option to flatliner forecaster so it predic… (#773)

    * feature: added use_median option to flatliner forecaster so it predicts the median of the training data.

    Signed-off-by: Bart Pleiter <bart.pleiter@alliander.com>

    * feature: improved naming to predict_median.

    Signed-off-by: Bart Pleiter <bart.pleiter@alliander.com>

    ---------

    Signed-off-by: Bart Pleiter <bart.pleiter@alliander.com>

commit 45ca37f
Author: Lars Schilders <123180911+lschilders@users.noreply.github.com>
Date:   Wed Nov 26 15:45:07 2025 +0100

    fix: fixes in EvaluationPipeline and TimeSeriesPlotter (#769)

    * Remove target column from predictions to avoid duplication for lead_times

    * get sample_interval class attr

commit ee41442
Author: Egor Dmitriev <egor.dmitriev@alliander.com>
Date:   Wed Nov 26 14:01:16 2025 +0100

    fix: Improved mlflow to use run names and load proper models for reuse. Fixed time series plotter to use correct sample interval paramter. (#768)

    * feature: Improved mlflow to use run names and load proper models for reuse. Fixed time series plotter to use correct sample interval paramter.

    Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>

    * feature(STEF-2551): Fixed path. Changed run_name to step_name in backtester.

    Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>

    ---------

    Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>

commit 7deb69e
Author: Bart Pleiter <bart.pleiter@alliander.com>
Date:   Fri Nov 21 14:42:54 2025 +0100

    chore: replaced alliander emails with lfenergy email. (#767)

    Signed-off-by: Bart Pleiter <bart.pleiter@alliander.com>

Signed-off-by: Lars van Someren <lvsom1@gmail.com>

* Integrated changes to beam structure

Signed-off-by: Lars van Someren <lvsom1@gmail.com>

* make PR compliant

Signed-off-by: Lars van Someren <lvsom1@gmail.com>

* Fix when aggregation functions empty.

Signed-off-by: Marnix van Lieshout <marnix.van.lieshout@alliander.com>

* Improve naming

Signed-off-by: Marnix van Lieshout <marnix.van.lieshout@alliander.com>

* Cleaning up

Signed-off-by: Marnix van Lieshout <marnix.van.lieshout@alliander.com>

* Change how config's are made and used for combiner and stacking models.

Signed-off-by: Marnix van Lieshout <marnix.van.lieshout@alliander.com>

* Cleaning up

Signed-off-by: Marnix van Lieshout <marnix.van.lieshout@alliander.com>

* Move skops code to separate branch

Signed-off-by: Marnix van Lieshout <marnix.van.lieshout@alliander.com>

* Move rules combiner code to separate branch

Signed-off-by: Marnix van Lieshout <marnix.van.lieshout@alliander.com>

* Cleaning up

Signed-off-by: Marnix van Lieshout <marnix.van.lieshout@alliander.com>

* Cleaning up

Signed-off-by: Marnix van Lieshout <marnix.van.lieshout@alliander.com>

* Improve hyperparam naming

Signed-off-by: Marnix van Lieshout <marnix.van.lieshout@alliander.com>

* Remove openstef-meta dependency in openstef-models

Signed-off-by: Marnix van Lieshout <marnix.van.lieshout@alliander.com>

* Separating openstef-models and openstef-meta WIP

Signed-off-by: Marnix van Lieshout <marnix.van.lieshout@alliander.com>

* Move residual model to separate branch

Signed-off-by: Marnix van Lieshout <marnix.van.lieshout@alliander.com>

* Rename regression tests to integration

Signed-off-by: Marnix van Lieshout <marnix.van.lieshout@alliander.com>

* Move forecaster back for now

Signed-off-by: Marnix van Lieshout <marnix.van.lieshout@alliander.com>

* Use sample weight config in models

Signed-off-by: Marnix van Lieshout <marnix.van.lieshout@alliander.com>

* Add importances to median forecaster

Signed-off-by: Marnix van Lieshout <marnix.van.lieshout@alliander.com>

* Rename for consistency

Signed-off-by: Marnix van Lieshout <marnix.van.lieshout@alliander.com>

* Move base mlflow callback to separate file

Signed-off-by: Marnix van Lieshout <marnix.van.lieshout@alliander.com>

* Clean up docstring

Signed-off-by: Marnix van Lieshout <marnix.van.lieshout@alliander.com>

* Add explainability to combiner models

Signed-off-by: Marnix van Lieshout <marnix.van.lieshout@alliander.com>

* Make combiners explainable

Signed-off-by: Marnix van Lieshout <marnix.van.lieshout@alliander.com>

* Delete empty folder

Signed-off-by: Marnix van Lieshout <marnix.van.lieshout@alliander.com>

* Use protocols

Signed-off-by: Marnix van Lieshout <marnix.van.lieshout@alliander.com>

* Rename test file

Signed-off-by: Marnix van Lieshout <marnix.van.lieshout@alliander.com>

* Reset pinball_losses changes

Signed-off-by: Marnix van Lieshout <marnix.van.lieshout@alliander.com>

* Fix linting issues

Signed-off-by: Marnix van Lieshout <marnix.van.lieshout@alliander.com>

* Add base models to tags MLflow

Signed-off-by: Marnix van Lieshout <marnix.van.lieshout@alliander.com>

* Improve what is stored in MLFlow

Signed-off-by: Marnix van Lieshout <marnix.van.lieshout@alliander.com>

* Formatting

Signed-off-by: Marnix van Lieshout <marnix.van.lieshout@alliander.com>

* refactor(STEF-2702): Meta rework v2 — flatten configs, abstract hparams, clean up presets (#818)

* fix(STEF-2802): skip RollingAggregatesAdder when no aggregation functions (#813)

When rolling_aggregate_features is empty (e.g., ato_regions, grid_losses),
the RollingAggregatesAdder transform is no longer added to the pipeline.
Previously it was always added, and fit() would crash with
ValueError: No objects to concatenate when calling pandas rolling().agg([]).

Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>

* fix(STEF-2802): anchor mlflow gitignore patterns to repo root (#814)

The bare 'mlflow' pattern matched anywhere in the tree, which caused
hatchling to exclude packages/openstef-models/src/openstef_models/integrations/mlflow/
from the built wheel. Root-anchoring with '/mlflow' limits the match to
only the top-level mlflow directory (local MLflow data).

Broken since v4.0.0.a17 (commit 1bcf71d).

Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>

* refactor(STEF-2702): extract BaseForecastingModel and ContributionsMixin

- Extract BaseForecastingModel as abstract base with hyperparams property
- Create ContributionsMixin with predict_contributions -> TimeSeriesDataset
- Remove predict_contributions from ExplainableForecaster (now ContributionsMixin)
- Implement ContributionsMixin on all ML forecasters (XGBoost, GBLinear, LGBM, LGBMLinear)
- Implement ContributionsMixin on baseline forecasters (flatliner, median, constant_median, base_case)
- Defer lightgbm imports to __init__() with MissingExtraError guard
- Inline _get_primary_hyperparams into model.hyperparams property
- Update mlflow callback and custom workflow to use BaseForecastingModel
- Update tests for new predict_contributions signature

* refactor(STEF-2702): flatten combiners, rearchitect StackingCombiner, prune low-value tests

- ForecastCombiner IS its config (BaseConfig + Predictor + ABC)
- Remove ForecastCombinerConfig entirely
- WeightsCombiner/StackingCombiner: flatten fields, use PrivateAttr for mutable state
- StackingCombiner: accept meta_forecaster template, clone via config.model_copy()
- Remove factory chain (forecaster_class -> Config -> forecaster_from_config)
- Add hyperparams field to ForecastCombiner base (no more getattr reflection)
- Extract _prepare_input helper, simplify predict/contributions/importances
- Delete test_forecast_combiner.py (6 tests testing pydantic/stdlib only)
- Remove test_initialization from combiner tests (covered by fit_predict)
- Remove test_init_uses_defaults (tested pydantic defaults)
- All 469 tests pass across core+models+meta

* refactor(STEF-2702): flatten forecasters into BaseConfig, add abstract hparams property

* refactor(STEF-2702): column separator, lightgbm optional, move classification to combiner

- D5: Move get_best_forecaster_labels/pinball classification from
  EnsembleForecastDataset to WeightsCombiner._classify_best_forecaster
- D6: Change ensemble column separator from '_' to '__' (ENSEMBLE_COLUMN_SEP),
  add forecaster name validation, remove dead get_quantile_feature_name
- Phase 1.5: Make lightgbm optional dependency (optional-dependencies.lgbm),
  add MissingExtraError guards to combiner get_classifier methods
- Phase 3.2: Replace isinstance(model, EnsembleForecastingModel) with
  polymorphic try/except NotImplementedError in beam backtest

* refactor(STEF-2702): abstract hparams on ForecastCombiner, fix all lint/type errors, add inline docs

* refactor(STEF-2702): rename EnsembleWorkflowConfig to EnsembleForecastingWorkflowConfig

* refactor(STEF-2702): clean up forecasting_workflow preset, extract helpers, prefix internals

* fix(STEF-2702): address PR review — remove D1 reference, rename fixture

* feat(STEF-2702): validate horizons consistency at EnsembleForecastingModel construction

* fix(STEF-2702): regenerate lockfile against PyPI (remove JFrog URLs)

---------

Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>

---------

Signed-off-by: Lars van Someren <lvsom1@gmail.com>
Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>
Signed-off-by: Marnix van Lieshout <marnix.van.lieshout@alliander.com>
Co-authored-by: Egor Dmitriev <egor.dmitriev@alliander.com>
Co-authored-by: floriangoethals <floriangoehtals@gmail.com>
Co-authored-by: Marnix van Lieshout <marnix.van.lieshout@alliander.com>
Signed-off-by: Fleur Petit <fleur.petit@alliander.com>
Signed-off-by: Marnix van Lieshout <marnix.van.lieshout@alliander.com>
Signed-off-by: Fleur Petit <fleur.petit@alliander.com>
* refactor(STEF-2702): push polymorphism into models, eliminate isinstance protocols

- SubsetMetric.to_flat_dict() for metrics self-serialization
- ModelFitResult.metrics_to_flat_dict() with component_fit_results override
- BaseForecastingModel: component_hyperparams, get_explainable_components()
- ForecastCombiner extends ExplainableForecaster (gains plot_feature_importances)
- EnsembleForecastingModel/EnsembleModelFitResult override all polymorphic methods
- MLflow callback: delete 3 protocols, 5 isinstance checks -> polymorphic calls
- Fix penalty bug: lower_is_better now multiplies (was dividing)

* refactor(STEF-2702): extract normalize_to_unit_sum, dedup feature importances

- Add normalize_to_unit_sum() pipe-compatible utility in openstef_core.utils.pandas
- Replace duplicated normalization logic in 4 forecasters (xgboost, gblinear, lgbm, lgbmlinear)
- All now use weights_df.pipe(normalize_to_unit_sum)

* fix: make AvailableAt.__str__() Windows-safe by removing colon

Change DnTHH:MM format to DnTHHMM (e.g. D-1T0600 instead of D-1T06:00).
Colons are illegal in Windows file paths, breaking benchmark output directories.

from_string() now accepts both formats for backward compatibility.

* feat(STEF-2702): make openstef-models/meta optional for beam baselines

* refactor(STEF-2702): replace workflow factory with template + model_copy

- Replace workflow_factory Callable with workflow_template field
- Add with_run_name() method for type-safe deep copy
- Add kind discriminator to ForecastingWorkflowConfig (single) and
  EnsembleForecastingWorkflowConfig (ensemble)
- Simplify factory: use kind-based narrowing, raise MissingExtraError
- Delete WorkflowCreationContext (no longer needed)
- Remove both pyright: ignore[reportArgumentType] suppressions

* test(STEF-2702): add template pattern smoke tests for OpenSTEF4BacktestForecaster

- test_fit_does_not_mutate_template: verifies template immutability after fit
- test_fit_then_predict_returns_forecast: e2e smoke test for fit→predict path

* chore(STEF-2702): add openstef-meta to release pipeline

- Add missing [build-system] section to openstef-meta pyproject.toml
- Add openstef-meta to poe version task (version bump + root pinning)
- Fix baselines extra version range: >=4.0.0.dev0,<5 (was >=0.0.1,<1)
- Add openstef-meta to root [all] optional extra
- Add openstef-meta to licensecheck ignore_packages

* refactor(STEF-2702): extend test utility, address review comments

- Add include_atmosphere/price/available_at options to
  create_synthetic_forecasting_dataset for realistic test data
- Simplify test fixture to use the shared utility
- Remove verbose parenthetical comments per review feedback

Signed-off-by: Fleur Petit <fleur.petit@alliander.com>
…ty (#826)

- Add normalize_tracking_uri() helper that converts local file paths
    to file:/// URIs, handling relative paths, absolute POSIX paths,
    and Windows drive-letter paths (e.g. D:\mlflow)
  - Apply normalization in MLFlowStorage.model_post_init() so all users
    get correct URIs regardless of how they construct tracking_uri
  - Add parametrized tests covering 8 URI variants including a mock-based
    Windows drive letter test

Signed-off-by: Fleur Petit <fleur.petit@alliander.com>
Signed-off-by: lschilders <lars.schilders@alliander.com>
Signed-off-by: Fleur Petit <fleur.petit@alliander.com>
* Add run_name. Set default weights. Add confidence interval applicator postprocessing.

Signed-off-by: Marnix van Lieshout <marnix.van.lieshout@alliander.com>

* Add lgbm extra for openstef-models dep in openstef-meta

Signed-off-by: Marnix van Lieshout <marnix.van.lieshout@alliander.com>

* No sample weights standard for lgbm

Signed-off-by: Marnix van Lieshout <marnix.van.lieshout@alliander.com>

---------

Signed-off-by: Marnix van Lieshout <marnix.van.lieshout@alliander.com>
Signed-off-by: Fleur Petit <fleur.petit@alliander.com>
* Add run_name. Set default weights. Add confidence interval applicator postprocessing.

Signed-off-by: Marnix van Lieshout <marnix.van.lieshout@alliander.com>

* Add lgbm extra for openstef-models dep in openstef-meta

Signed-off-by: Marnix van Lieshout <marnix.van.lieshout@alliander.com>

* No sample weights standard for lgbm

Signed-off-by: Marnix van Lieshout <marnix.van.lieshout@alliander.com>

* transform -> fit_transform

Signed-off-by: Marnix van Lieshout <marnix.van.lieshout@alliander.com>

* Split postprocessing into common, per-forecaster and combiner

Signed-off-by: Marnix van Lieshout <marnix.van.lieshout@alliander.com>

---------

Signed-off-by: Marnix van Lieshout <marnix.van.lieshout@alliander.com>
Signed-off-by: Fleur Petit <fleur.petit@alliander.com>
* feature: add apply() to AvailableAt and tz-aware option

* feature: refactor AvailableAt to use day_offset and time_of_day rather than lag_from_day

* feature: (de)serialization support for tz-aware AvailableAt

* feature: improved docstring

* feature: add tests for apply_index

Signed-off-by: lschilders <lars.schilders@alliander.com>

---------

Signed-off-by: lschilders <lars.schilders@alliander.com>
Signed-off-by: Fleur Petit <fleur.petit@alliander.com>
…ply_index()) (#832)

Signed-off-by: Fleur Petit <fleur.petit@alliander.com>
Signed-off-by: Marnix van Lieshout <marnix.van.lieshout@alliander.com>
Signed-off-by: Fleur Petit <fleur.petit@alliander.com>
…833)

* fix: timezone handling for offset in AvailableAt apply() (matching apply_index())

* feature: add new metric NMAE (normalized MAE)

* add MAEProvider

* make allow_nan configurable

* refactored NMAE into rMAE with norm_value

Signed-off-by: lschilders <lars.schilders@alliander.com>

* formatting

Signed-off-by: lschilders <lars.schilders@alliander.com>

* add allow_nan to rMAE

---------

Signed-off-by: lschilders <lars.schilders@alliander.com>
Signed-off-by: Fleur Petit <fleur.petit@alliander.com>
* Update workflows

Signed-off-by: Marnix van Lieshout <marnix.van.lieshout@alliander.com>

* Revert doc change

Signed-off-by: Marnix van Lieshout <marnix.van.lieshout@alliander.com>

---------

Signed-off-by: Marnix van Lieshout <marnix.van.lieshout@alliander.com>
Signed-off-by: Fleur Petit <fleur.petit@alliander.com>
* fix(STEF-2854): handle InsufficientlyCompleteError during backtest training

OpenSTEF4BacktestForecaster.fit() now catches InsufficientlyCompleteError
alongside FlatlinerDetectedError. When a training window has insufficient
non-NaN data, the training event is skipped and the previous model is
retained instead of crashing the entire target backtest.

Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>

* test(STEF-2854): replace mock with real NaN data in insufficient-data test

Use all-NaN load data with model_reuse_enable=False to trigger
InsufficientlyCompleteError naturally instead of patching workflow.fit.

Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>

* fix(STEF-2854): return None from predict() when no model fitted

When the first fit fails due to InsufficientlyCompleteError, _workflow
stays None. predict() now returns None (like flatliner) instead of
raising NotFittedError, letting the benchmark pipeline skip gracefully.

Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>

* fix(STEF-2854): make WindowedMetricVisualization robust to missing data

Skip runs/targets with no windowed metrics instead of raising ValueError.
Returns an HTML placeholder when all items in a visualization are empty.

Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>

* fix(STEF-2854): fix combiner label/weight shape mismatch and Quantile serialization

Two fixes:
1. learned_weights_combiner.py: Filter labels to match combined_data
   index after inner join drops rows from additional_features.
   Fixes ValueError: operands could not be broadcast together.
2. types.py: Add Pydantic serializer to Quantile to suppress
   PydanticSerializationUnexpectedValue warnings.

Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>

Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>

* fix(STEF-2854): add Pydantic serializer to Quantile to suppress warnings

Quantile.__get_pydantic_core_schema__ only defined a validator but no
serializer. When Quantile values appear as dict keys in a union type
(e.g., QuantileOrGlobal = Quantile | Literal['global']), Pydantic emits
PydanticSerializationUnexpectedValue warnings during model_dump_json().

Add a plain_serializer_function_ser_schema(float) so Pydantic knows
how to serialize Quantile as a float, preventing the warning.

Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>
Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>

* fix(STEF-2854): raise InsufficientlyCompleteError for empty datasets in train/test split

chronological_train_test_split crashed with IndexError when the dataset
had fewer than 2 unique timestamps. This happens during ensemble backtest
when a base forecaster's preprocessed data is empty. Now raises
InsufficientlyCompleteError which is caught by the backtest harness.

Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>

* fix(STEF-2854): raise InsufficientlyCompleteError on empty combiner data after inner join

Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>

* feat(STEF-2854): add strict parameter to BenchmarkComparisonPipeline.run()

Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>

* fix(STEF-2854): use 'global' subdirectory for RUN_AND_GROUP analysis scope

The RUN_AND_GROUP scope was saving directly to the base analysis dir,
making has_analysis_output fail to locate it and colliding with group-level
outputs. Store in a 'global' subdirectory to match the group-level pattern
(group_name/global).

Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>

* fix(STEF-2854): renormalize ensemble weights when base model predictions are NaN

When a base model cannot predict certain timestamps (e.g. gblinear limited
to 2-day weather horizon while lgbm predicts 7 days), the combiner must
redistribute the missing model's weight proportionally to the remaining
models.

Previously, pandas sum(axis=1, skipna=True) silently dropped the NaN
model's weight contribution, causing predictions to be systematically
scaled down by ~35% for timestamps beyond the weather horizon.

Now weights are reindexed to match predictions, zeroed where predictions
are NaN, and the weighted sum is divided by the available weight total.
When all models are NaN, the result is 0 (matching prior behavior).

Includes regression test with seeded data verifying no NaN propagation
and no systematic downscaling.

Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>
Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>

* refactor(STEF-2854): extract nan_aware_weighted_mean helper

Extract NaN-aware weight renormalization into a reusable helper in
openstef_core.utils.pandas and use it in learned_weights_combiner.
Removes type: ignore comments from _predict_quantile.

Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>

* feat(STEF-2854): add skip_analysis param to BenchmarkPipeline.run()

Allows skipping per-target and global analysis steps when running
benchmarks. Useful when analysis will be run separately later
via the comparison pipeline.

Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>

* feat(STEF-2854): add filterings override to AnalysisConfig

Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>

* fix(STEF-2854): resolve ruff lint warnings

Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>

* fix(STEF-2854): resolve pyright type errors in modified files

Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>

---------

Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>
Signed-off-by: Fleur Petit <fleur.petit@alliander.com>
* add shifter transform and tests

Signed-off-by: MentReeze <ment.reeze@alliander.com>

* added shifter to forecasting workflow

Signed-off-by: MentReeze <ment.reeze@alliander.com>

* changed averaging to aggregated, simplified edge handling and removed cast to datetimeindex

Signed-off-by: MentReeze <ment.reeze@alliander.com>

* changed to list of shifts

Signed-off-by: MentReeze <ment.reeze@alliander.com>

* small fix: removed redundant list()

Signed-off-by: MentReeze <ment.reeze@alliander.com>

---------

Signed-off-by: MentReeze <ment.reeze@alliander.com>
Signed-off-by: Fleur Petit <fleur.petit@alliander.com>
…e workflow.

Signed-off-by: Fleur Petit <fleur.petit@alliander.com>
Signed-off-by: Fleur Petit <fleur.petit@alliander.com>
…casters

Signed-off-by: Fleur Petit <fleur.petit@alliander.com>
Signed-off-by: Marnix van Lieshout <marnix.van.lieshout@alliander.com>
Signed-off-by: Fleur Petit <fleur.petit@alliander.com>
* refactor(STEF-2702): push polymorphism into models, eliminate isinstance protocols

- SubsetMetric.to_flat_dict() for metrics self-serialization
- ModelFitResult.metrics_to_flat_dict() with component_fit_results override
- BaseForecastingModel: component_hyperparams, get_explainable_components()
- ForecastCombiner extends ExplainableForecaster (gains plot_feature_importances)
- EnsembleForecastingModel/EnsembleModelFitResult override all polymorphic methods
- MLflow callback: delete 3 protocols, 5 isinstance checks -> polymorphic calls
- Fix penalty bug: lower_is_better now multiplies (was dividing)

* refactor(STEF-2702): extract normalize_to_unit_sum, dedup feature importances

- Add normalize_to_unit_sum() pipe-compatible utility in openstef_core.utils.pandas
- Replace duplicated normalization logic in 4 forecasters (xgboost, gblinear, lgbm, lgbmlinear)
- All now use weights_df.pipe(normalize_to_unit_sum)

* fix: make AvailableAt.__str__() Windows-safe by removing colon

Change DnTHH:MM format to DnTHHMM (e.g. D-1T0600 instead of D-1T06:00).
Colons are illegal in Windows file paths, breaking benchmark output directories.

from_string() now accepts both formats for backward compatibility.

* feat(STEF-2702): make openstef-models/meta optional for beam baselines

* refactor(STEF-2702): replace workflow factory with template + model_copy

- Replace workflow_factory Callable with workflow_template field
- Add with_run_name() method for type-safe deep copy
- Add kind discriminator to ForecastingWorkflowConfig (single) and
  EnsembleForecastingWorkflowConfig (ensemble)
- Simplify factory: use kind-based narrowing, raise MissingExtraError
- Delete WorkflowCreationContext (no longer needed)
- Remove both pyright: ignore[reportArgumentType] suppressions

* test(STEF-2702): add template pattern smoke tests for OpenSTEF4BacktestForecaster

- test_fit_does_not_mutate_template: verifies template immutability after fit
- test_fit_then_predict_returns_forecast: e2e smoke test for fit→predict path

* chore(STEF-2702): add openstef-meta to release pipeline

- Add missing [build-system] section to openstef-meta pyproject.toml
- Add openstef-meta to poe version task (version bump + root pinning)
- Fix baselines extra version range: >=4.0.0.dev0,<5 (was >=0.0.1,<1)
- Add openstef-meta to root [all] optional extra
- Add openstef-meta to licensecheck ignore_packages

* refactor(STEF-2702): extend test utility, address review comments

- Add include_atmosphere/price/available_at options to
  create_synthetic_forecasting_dataset for realistic test data
- Simplify test fixture to use the shared utility
- Remove verbose parenthetical comments per review feedback

Signed-off-by: Fleur Petit <fleur.petit@alliander.com>
* Add run_name. Set default weights. Add confidence interval applicator postprocessing.

Signed-off-by: Marnix van Lieshout <marnix.van.lieshout@alliander.com>

* Add lgbm extra for openstef-models dep in openstef-meta

Signed-off-by: Marnix van Lieshout <marnix.van.lieshout@alliander.com>

* No sample weights standard for lgbm

Signed-off-by: Marnix van Lieshout <marnix.van.lieshout@alliander.com>

---------

Signed-off-by: Marnix van Lieshout <marnix.van.lieshout@alliander.com>
Signed-off-by: Fleur Petit <fleur.petit@alliander.com>
* Add run_name. Set default weights. Add confidence interval applicator postprocessing.

Signed-off-by: Marnix van Lieshout <marnix.van.lieshout@alliander.com>

* Add lgbm extra for openstef-models dep in openstef-meta

Signed-off-by: Marnix van Lieshout <marnix.van.lieshout@alliander.com>

* No sample weights standard for lgbm

Signed-off-by: Marnix van Lieshout <marnix.van.lieshout@alliander.com>

* transform -> fit_transform

Signed-off-by: Marnix van Lieshout <marnix.van.lieshout@alliander.com>

* Split postprocessing into common, per-forecaster and combiner

Signed-off-by: Marnix van Lieshout <marnix.van.lieshout@alliander.com>

---------

Signed-off-by: Marnix van Lieshout <marnix.van.lieshout@alliander.com>
Signed-off-by: Fleur Petit <fleur.petit@alliander.com>
…ter-tuning

Signed-off-by: Fleur Petit <fleur.petit@alliander.com>
egordm added 3 commits March 27, 2026 13:47
- Remove banner comments from tuner.py
- Inline _build_hp_updates into both callers (_evaluate_trial, _reconstruct_best_config)
- Raise ValueError with available metrics when metric_name is invalid
- Fix all pyright errors in optuna tutorial (add missing config cell, guards)
- Extract shared data download helper (examples/tutorials/_data.py)
- Update both tutorials to use load_liander_dataset() helper
- Add PLC2701/S101 to tutorial ruff per-file-ignores

Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>
- Make methods @staticmethod where possible (PLR6301)
- Use defaultdict and comprehensions for cleaner code
- Simplify TuningResult to (best_config, study), remove workflow/fit_result
- Make TuningResult generic to preserve config type
- Add n_jobs field for parallel Optuna trials
- Validate metric_name against MetricProvider.metric_names at tuner init
- Add metric_names property to all MetricProvider subclasses
- Inline defaults in data.py, rename _data.py → data.py (PLC2701)
- Convert forecasting tutorial to jupytext paired format
- Fix all pyright type errors in tutorials with proper casts
- Add inline comments explaining complex logic

Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>
Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>
@egordm egordm changed the base branch from main to release/v4.0.0 March 27, 2026 13:33
egordm added 2 commits March 27, 2026 14:34
Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>
Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>
@egordm
Copy link
Copy Markdown
Collaborator Author

egordm commented Mar 27, 2026

Made with improvements for #840

…ning-module-split

# Conflicts:
#	examples/tutorials/forecasting_with_workflow_presets.ipynb
@egordm egordm changed the title Refactor tuning module Refactor hyperparameter tuning into a modular Optuna integration Mar 27, 2026
…unes.

Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>
@egordm egordm changed the title Refactor hyperparameter tuning into a modular Optuna integration feat(tuning): Bayesian hyperparameter tuning via Optuna integration Mar 27, 2026
egordm added 4 commits March 27, 2026 15:29
…, strip mlflow from tuning trials

Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>
…dFunction suppress and data.py comments

Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>
Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>
Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>
@egordm egordm marked this pull request as ready for review March 27, 2026 14:40
@egordm egordm requested a review from a team March 27, 2026 14:40
egordm added 2 commits March 27, 2026 15:42
Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>
…e task and CI step

Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>
bartpleiter
bartpleiter previously approved these changes Mar 27, 2026
Copy link
Copy Markdown
Collaborator

@bartpleiter bartpleiter left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice feature! Some minor comment comments. Also the sonar suggestions of comparing with floats is good to take a look at.

Comment on lines +159 to +161
target: str = "mv_feeder/OS Gorredijk",
repo_id: str = "OpenSTEF/liander2024-energy-forecasting-benchmark",
local_dir: Path = Path("./liander_dataset"),
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the Liander 2024 dataset is now being used in multiple parts of the code base (examples, openstef-beam, and now openstef-core as well). Does it make sense to have an "appsetting" like config somewhere with paths? In case we want to change the dataset in the future I want to prevent having to search though all code in all packages.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure about the app setting. It may be a bit overkill for constants. But a constants module would work.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Made constant. Other things can use it.

…ove redundant comments, extract LIANDER_DATASET_REPO_ID constant

Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>
egordm added 2 commits March 27, 2026 16:25
…lt.workflow is already fitted

Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>
Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>
@egordm
Copy link
Copy Markdown
Collaborator Author

egordm commented Mar 27, 2026

TODO:

  • Check what happens if you have set searching range and accidentally run fit. If it gracefully errors out.
  • Double check that first run actually uses openstef defaults and not something random.

egordm added 3 commits March 27, 2026 16:54
Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>
Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>
Signed-off-by: Egor Dmitriev <egor.dmitriev@alliander.com>
@sonarqubecloud
Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
6.3% Duplication on New Code (required ≤ 3%)
C Reliability Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants