Skip to content

Commit f774851

Browse files
Lars800egordmfloriangoethalsMvLieshout
authored
feature: Added (openstef-meta) meta models package (#771)
* 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 <[email protected]> * 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 <[email protected]> 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 <[email protected]> * fix(STEF-2475): Added better support for flatliners and predicting when data is sparse. Signed-off-by: Egor Dmitriev <[email protected]> * fix(STEF-2475): Feature handing improvements for gblinear. Like imputation, nan dropping, and checking if features are available. Signed-off-by: Egor Dmitriev <[email protected]> * fix(#728): Added checks on metrics to gracefully handle empty data. Added flatline filtering during evalution. Signed-off-by: Egor Dmitriev <[email protected]> * fix(#728): Updated xgboost to skip scaling on empty prediction. Signed-off-by: Egor Dmitriev <[email protected]> * fix(STEF-2475): Added parallelism parameters. Signed-off-by: Egor Dmitriev <[email protected]> --------- Signed-off-by: Egor Dmitriev <[email protected]> commit a85a3f7 Author: Egor Dmitriev <[email protected]> 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 <[email protected]> commit 4f0c664 Author: Egor Dmitriev <[email protected]> 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 <[email protected]> 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 <[email protected]> * fix: Formatting. Signed-off-by: Egor Dmitriev <[email protected]> --------- Signed-off-by: Egor Dmitriev <[email protected]> commit 6b1da44 Author: Egor Dmitriev <[email protected]> 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 <[email protected]> * feature(#729): Fixed issue where generic transform pipeline could not be serialized. Signed-off-by: Egor Dmitriev <[email protected]> * feature(#729): Added more state saving tests Signed-off-by: Egor Dmitriev <[email protected]> * feature(#729): Added more state saving tests Signed-off-by: Egor Dmitriev <[email protected]> * feature(#729): Added more state saving tests Signed-off-by: Egor Dmitriev <[email protected]> * feature: standardized objective function. Added custom evaluation functions for forecasters. * fix: Formatting. Signed-off-by: Egor Dmitriev <[email protected]> --------- Signed-off-by: Egor Dmitriev <[email protected]> * 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 <[email protected]> * MetaForecasterClass Signed-off-by: Lars van Someren <[email protected]> * 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 <[email protected]> * 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 <[email protected]> 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 <[email protected]> * fix(STEF-2475): Added better support for flatliners and predicting when data is sparse. Signed-off-by: Egor Dmitriev <[email protected]> * fix(STEF-2475): Feature handing improvements for gblinear. Like imputation, nan dropping, and checking if features are available. Signed-off-by: Egor Dmitriev <[email protected]> * fix(#728): Added checks on metrics to gracefully handle empty data. Added flatline filtering during evalution. Signed-off-by: Egor Dmitriev <[email protected]> * fix(#728): Updated xgboost to skip scaling on empty prediction. Signed-off-by: Egor Dmitriev <[email protected]> * fix(STEF-2475): Added parallelism parameters. Signed-off-by: Egor Dmitriev <[email protected]> --------- Signed-off-by: Egor Dmitriev <[email protected]> commit a85a3f7 Author: Egor Dmitriev <[email protected]> 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 <[email protected]> commit 4f0c664 Author: Egor Dmitriev <[email protected]> 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 <[email protected]> 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 <[email protected]> * fix: Formatting. Signed-off-by: Egor Dmitriev <[email protected]> --------- Signed-off-by: Egor Dmitriev <[email protected]> commit 6b1da44 Author: Egor Dmitriev <[email protected]> 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 <[email protected]> * feature(#729): Fixed issue where generic transform pipeline could not be serialized. Signed-off-by: Egor Dmitriev <[email protected]> * feature(#729): Added more state saving tests Signed-off-by: Egor Dmitriev <[email protected]> * feature(#729): Added more state saving tests Signed-off-by: Egor Dmitriev <[email protected]> * feature(#729): Added more state saving tests Signed-off-by: Egor Dmitriev <[email protected]> * feature: standardized objective function. Added custom evaluation functions for forecasters. * fix: Formatting. Signed-off-by: Egor Dmitriev <[email protected]> --------- Signed-off-by: Egor Dmitriev <[email protected]> * 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 <[email protected]> * change: Fixed dependencies to align more with the current release. Signed-off-by: Egor Dmitriev <[email protected]> * change: Style fixes. Signed-off-by: Egor Dmitriev <[email protected]> --------- Signed-off-by: Lars van Someren <[email protected]> Signed-off-by: Egor Dmitriev <[email protected]> Co-authored-by: Egor Dmitriev <[email protected]> * fix merge issue Signed-off-by: Lars van Someren <[email protected]> * Fixed type Issues Signed-off-by: Lars van Someren <[email protected]> * Introduced openstef_metalearning Signed-off-by: Lars van Someren <[email protected]> * ResidualForecaster + refactoring Signed-off-by: Lars van Someren <[email protected]> * Testing and fixes on Learned Weights Forecaster Signed-off-by: Lars van Someren <[email protected]> * FinalLearner PreProcessor Signed-off-by: Lars van Someren <[email protected]> * Fixed benchmark references Signed-off-by: Lars van Someren <[email protected]> * Added additional Feature logic to StackingForecaster Signed-off-by: Lars van Someren <[email protected]> * added example to openstef Meta Signed-off-by: Lars van Someren <[email protected]> * RulesForecaster with dummy features Signed-off-by: Lars van Someren <[email protected]> * Updated feature specification Signed-off-by: Lars van Someren <[email protected]> * entered flagger feature in new architecture * Fix sample weights Signed-off-by: Lars van Someren <[email protected]> * Fixes Signed-off-by: Lars van Someren <[email protected]> * PR compliant Signed-off-by: Lars van Someren <[email protected]> * Ensemble Forecast Dataset Signed-off-by: Lars van Someren <[email protected]> * Make PR compliant Signed-off-by: Lars van Someren <[email protected]> * fixed toml Signed-off-by: Lars van Someren <[email protected]> * Really fixed the TOML Signed-off-by: Lars van Someren <[email protected]> * Renamed FinalLearner to Forecast Combiner. Eliminated redundant classes Signed-off-by: Lars van Someren <[email protected]> * fixed small issues Signed-off-by: Lars van Someren <[email protected]> * Major Refactor, Working Version Signed-off-by: Lars van Someren <[email protected]> * Fixed tests Signed-off-by: Lars van Someren <[email protected]> * Prepared TODOs for Florian Signed-off-by: Lars van Someren <[email protected]> * Small fix Signed-off-by: Lars van Someren <[email protected]> * Made PR Compliant Signed-off-by: Lars van Someren <[email protected]> * BugFix Signed-off-by: Lars van Someren <[email protected]> * fixes Signed-off-by: Lars van Someren <[email protected]> * bug fixes Signed-off-by: Lars van Someren <[email protected]> * 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 <[email protected]> * fixes Signed-off-by: Lars van Someren <[email protected]> * Squashed commit of the following: commit 6f88d72 Author: Lars van Someren <[email protected]> Date: Mon Dec 8 09:46:57 2025 +0100 Bugfixes Signed-off-by: Lars van Someren <[email protected]> commit b44fd92 Author: Lars van Someren <[email protected]> Date: Thu Dec 4 14:39:31 2025 +0100 bug fixes Signed-off-by: Lars van Someren <[email protected]> commit e212448 Author: Lars van Someren <[email protected]> Date: Thu Dec 4 12:38:24 2025 +0100 fixes Signed-off-by: Lars van Someren <[email protected]> commit eb775e4 Author: Lars van Someren <[email protected]> Date: Thu Dec 4 11:40:44 2025 +0100 BugFix Signed-off-by: Lars van Someren <[email protected]> commit c33ce93 Author: Lars van Someren <[email protected]> Date: Wed Dec 3 14:15:06 2025 +0100 Made PR Compliant Signed-off-by: Lars van Someren <[email protected]> * Fixes Signed-off-by: Lars van Someren <[email protected]> * fixed tests Signed-off-by: Lars van Someren <[email protected]> * small fix Signed-off-by: Lars van Someren <[email protected]> * Stacking Bugfix Signed-off-by: Lars van Someren <[email protected]> * Added hard Forecast Selection Signed-off-by: Lars van Someren <[email protected]> * 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 <[email protected]> * Migrated Flagger and Selector to OpenSTEF Models transforms Signed-off-by: Lars van Someren <[email protected]> * Fixed restore target Forecast Combiner Signed-off-by: Lars van Someren <[email protected]> * Streamline logging statements, Fix quality Signed-off-by: Lars van Someren <[email protected]> * Resolved comments, fixed bug Signed-off-by: Lars van Someren <[email protected]> * Moved example Signed-off-by: Lars van Someren <[email protected]> * Bring 4.1 up to date with release Squashed commit of the following: commit 6d140bc Author: Lars Schilders <[email protected]> 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 <[email protected]> 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 <[email protected]> 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 <[email protected]> * feature: Added example notebooks. First draft. Signed-off-by: Egor Dmitriev <[email protected]> * 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 <[email protected]> commit 8a4097c Author: Bart Pleiter <[email protected]> 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 <[email protected]> * fix: duplicate removed. Signed-off-by: Bart Pleiter <[email protected]> * fix: type Signed-off-by: Bart Pleiter <[email protected]> --------- Signed-off-by: Bart Pleiter <[email protected]> commit 43987fc Author: Egor Dmitriev <[email protected]> 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 <[email protected]> commit 1891009 Author: Lars Schilders <[email protected]> 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 <[email protected]> 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 <[email protected]> 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 <[email protected]> * feature: Style fixes. Signed-off-by: Egor Dmitriev <[email protected]> --------- Signed-off-by: Egor Dmitriev <[email protected]> commit eca628e Author: Lars Schilders <[email protected]> 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 <[email protected]> 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 <[email protected]> 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 <[email protected]> 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 <[email protected]> * feature: improved naming to predict_median. Signed-off-by: Bart Pleiter <[email protected]> --------- Signed-off-by: Bart Pleiter <[email protected]> commit 45ca37f Author: Lars Schilders <[email protected]> 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 <[email protected]> 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 <[email protected]> * feature(STEF-2551): Fixed path. Changed run_name to step_name in backtester. Signed-off-by: Egor Dmitriev <[email protected]> --------- Signed-off-by: Egor Dmitriev <[email protected]> commit 7deb69e Author: Bart Pleiter <[email protected]> Date: Fri Nov 21 14:42:54 2025 +0100 chore: replaced alliander emails with lfenergy email. (#767) Signed-off-by: Bart Pleiter <[email protected]> Signed-off-by: Lars van Someren <[email protected]> * Integrated changes to beam structure Signed-off-by: Lars van Someren <[email protected]> * make PR compliant Signed-off-by: Lars van Someren <[email protected]> * Fix when aggregation functions empty. Signed-off-by: Marnix van Lieshout <[email protected]> * Improve naming Signed-off-by: Marnix van Lieshout <[email protected]> * Cleaning up Signed-off-by: Marnix van Lieshout <[email protected]> * Change how config's are made and used for combiner and stacking models. Signed-off-by: Marnix van Lieshout <[email protected]> * Cleaning up Signed-off-by: Marnix van Lieshout <[email protected]> * Move skops code to separate branch Signed-off-by: Marnix van Lieshout <[email protected]> * Move rules combiner code to separate branch Signed-off-by: Marnix van Lieshout <[email protected]> * Cleaning up Signed-off-by: Marnix van Lieshout <[email protected]> * Cleaning up Signed-off-by: Marnix van Lieshout <[email protected]> * Improve hyperparam naming Signed-off-by: Marnix van Lieshout <[email protected]> * Remove openstef-meta dependency in openstef-models Signed-off-by: Marnix van Lieshout <[email protected]> * Separating openstef-models and openstef-meta WIP Signed-off-by: Marnix van Lieshout <[email protected]> * Move residual model to separate branch Signed-off-by: Marnix van Lieshout <[email protected]> * Rename regression tests to integration Signed-off-by: Marnix van Lieshout <[email protected]> * Move forecaster back for now Signed-off-by: Marnix van Lieshout <[email protected]> * Use sample weight config in models Signed-off-by: Marnix van Lieshout <[email protected]> * Add importances to median forecaster Signed-off-by: Marnix van Lieshout <[email protected]> * Rename for consistency Signed-off-by: Marnix van Lieshout <[email protected]> * Move base mlflow callback to separate file Signed-off-by: Marnix van Lieshout <[email protected]> * Clean up docstring Signed-off-by: Marnix van Lieshout <[email protected]> * Add explainability to combiner models Signed-off-by: Marnix van Lieshout <[email protected]> * Make combiners explainable Signed-off-by: Marnix van Lieshout <[email protected]> * Delete empty folder Signed-off-by: Marnix van Lieshout <[email protected]> * Use protocols Signed-off-by: Marnix van Lieshout <[email protected]> * Rename test file Signed-off-by: Marnix van Lieshout <[email protected]> * Reset pinball_losses changes Signed-off-by: Marnix van Lieshout <[email protected]> * Fix linting issues Signed-off-by: Marnix van Lieshout <[email protected]> * Add base models to tags MLflow Signed-off-by: Marnix van Lieshout <[email protected]> * Improve what is stored in MLFlow Signed-off-by: Marnix van Lieshout <[email protected]> * Formatting Signed-off-by: Marnix van Lieshout <[email protected]> * 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 <[email protected]> * 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 <[email protected]> * 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 <[email protected]> --------- Signed-off-by: Lars van Someren <[email protected]> Signed-off-by: Egor Dmitriev <[email protected]> Signed-off-by: Marnix van Lieshout <[email protected]> Co-authored-by: Egor Dmitriev <[email protected]> Co-authored-by: floriangoethals <[email protected]> Co-authored-by: Marnix van Lieshout <[email protected]>
1 parent 9271c05 commit f774851

74 files changed

Lines changed: 6562 additions & 1437 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
"""Liander 2024 Benchmark Example.
2+
3+
====================================
4+
5+
This example demonstrates how to set up and run the Liander 2024 STEF benchmark using OpenSTEF BEAM.
6+
The benchmark will evaluate XGBoost and GBLinear models on the dataset from HuggingFace.
7+
"""
8+
9+
# SPDX-FileCopyrightText: 2025 Contributors to the OpenSTEF project <[email protected]>
10+
#
11+
# SPDX-License-Identifier: MPL-2.0
12+
13+
import os
14+
import time
15+
16+
os.environ["OMP_NUM_THREADS"] = "1" # Set OMP_NUM_THREADS to 1 to avoid issues with parallel execution and xgboost
17+
os.environ["OPENBLAS_NUM_THREADS"] = "1"
18+
os.environ["MKL_NUM_THREADS"] = "1"
19+
20+
import logging
21+
import multiprocessing
22+
from datetime import timedelta
23+
from pathlib import Path
24+
25+
from openstef_beam.backtesting.backtest_forecaster import BacktestForecasterConfig
26+
from openstef_beam.benchmarking.baselines import (
27+
create_openstef4_preset_backtest_forecaster,
28+
)
29+
from openstef_beam.benchmarking.benchmarks.liander2024 import Liander2024Category, create_liander2024_benchmark_runner
30+
from openstef_beam.benchmarking.callbacks.strict_execution_callback import StrictExecutionCallback
31+
from openstef_beam.benchmarking.storage.local_storage import LocalBenchmarkStorage
32+
from openstef_core.types import LeadTime, Q
33+
from openstef_meta.presets import (
34+
EnsembleForecastingWorkflowConfig,
35+
)
36+
from openstef_models.integrations.mlflow.mlflow_storage import MLFlowStorage
37+
from openstef_models.transforms.general import SampleWeightConfig
38+
39+
logging.basicConfig(level=logging.INFO, format="[%(asctime)s][%(levelname)s] %(message)s")
40+
41+
OUTPUT_PATH = Path("./benchmark_results")
42+
43+
N_PROCESSES = 1 if True else multiprocessing.cpu_count() # Amount of parallel processes to use for the benchmark
44+
45+
ensemble_type = "learned_weights" # "stacking", "learned_weights" or "rules"
46+
base_models = ["lgbm", "gblinear"] # combination of "lgbm", "gblinear", "xgboost" and "lgbm_linear"
47+
combiner_model = (
48+
"lgbm" # "lgbm", "xgboost", "rf" or "logistic" for learned weights combiner, gblinear for stacking combiner
49+
)
50+
51+
model = "Ensemble_" + "_".join(base_models) + "_" + ensemble_type + "_" + combiner_model
52+
53+
# Model configuration
54+
FORECAST_HORIZONS = [LeadTime.from_string("PT36H")] # Forecast horizon(s)
55+
PREDICTION_QUANTILES = [
56+
Q(0.05),
57+
Q(0.1),
58+
Q(0.3),
59+
Q(0.5),
60+
Q(0.7),
61+
Q(0.9),
62+
Q(0.95),
63+
] # Quantiles for probabilistic forecasts
64+
65+
BENCHMARK_FILTER: list[Liander2024Category] | None = None
66+
67+
USE_MLFLOW_STORAGE = True
68+
69+
if USE_MLFLOW_STORAGE:
70+
storage = MLFlowStorage(
71+
tracking_uri=str(OUTPUT_PATH / "mlflow_artifacts"),
72+
local_artifacts_path=OUTPUT_PATH / "mlflow_tracking_artifacts",
73+
)
74+
else:
75+
storage = None
76+
77+
workflow_config = EnsembleForecastingWorkflowConfig(
78+
model_id="common_model_",
79+
ensemble_type=ensemble_type,
80+
base_models=base_models, # type: ignore
81+
combiner_model=combiner_model,
82+
horizons=FORECAST_HORIZONS,
83+
quantiles=PREDICTION_QUANTILES,
84+
model_reuse_enable=False,
85+
mlflow_storage=None,
86+
radiation_column="shortwave_radiation",
87+
rolling_aggregate_features=["mean", "median", "max", "min"],
88+
wind_speed_column="wind_speed_80m",
89+
pressure_column="surface_pressure",
90+
temperature_column="temperature_2m",
91+
relative_humidity_column="relative_humidity_2m",
92+
energy_price_column="EPEX_NL",
93+
forecaster_sample_weights={
94+
"gblinear": SampleWeightConfig(method="exponential", weight_exponent=1.0),
95+
"lgbm": SampleWeightConfig(weight_exponent=0.0),
96+
"xgboost": SampleWeightConfig(weight_exponent=0.0),
97+
"lgbm_linear": SampleWeightConfig(weight_exponent=0.0),
98+
},
99+
)
100+
101+
102+
# Create the backtest configuration
103+
backtest_config = BacktestForecasterConfig(
104+
requires_training=True,
105+
predict_length=timedelta(days=7),
106+
predict_min_length=timedelta(minutes=15),
107+
predict_context_length=timedelta(days=14), # Context needed for lag features
108+
predict_context_min_coverage=0.5,
109+
training_context_length=timedelta(days=90), # Three months of training data
110+
training_context_min_coverage=0.5,
111+
predict_sample_interval=timedelta(minutes=15),
112+
)
113+
114+
115+
if __name__ == "__main__":
116+
start_time = time.time()
117+
create_liander2024_benchmark_runner(
118+
storage=LocalBenchmarkStorage(base_path=OUTPUT_PATH / model),
119+
data_dir=None, # Path("../data/liander2024-energy-forecasting-benchmark"),
120+
callbacks=[StrictExecutionCallback()],
121+
).run(
122+
forecaster_factory=create_openstef4_preset_backtest_forecaster(
123+
workflow_config=workflow_config,
124+
cache_dir=OUTPUT_PATH / "cache",
125+
),
126+
run_name=model,
127+
n_processes=N_PROCESSES,
128+
filter_args=BENCHMARK_FILTER,
129+
)
130+
131+
end_time = time.time()
132+
print(f"Benchmark completed in {end_time - start_time:.2f} seconds.")

examples/examples/configuring_model_pipeline_example.py

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@
4545
from openstef_models.integrations.mlflow.mlflow_storage import MLFlowStorage
4646
from openstef_models.models.forecasting.gblinear_forecaster import (
4747
GBLinearForecaster,
48-
GBLinearForecasterConfig,
4948
GBLinearHyperParams,
5049
)
5150
from openstef_models.models.forecasting_model import ForecastingModel
@@ -88,15 +87,13 @@
8887
],
8988
),
9089
forecaster=GBLinearForecaster(
91-
config=GBLinearForecasterConfig(
92-
horizons=[LeadTime.from_string("PT36H")],
93-
quantiles=[Q(0.5), Q(0.1), Q(0.9)],
94-
hyperparams=GBLinearHyperParams(
95-
n_steps=1000,
96-
learning_rate=0.3,
97-
),
98-
verbosity=True,
99-
)
90+
horizons=[LeadTime.from_string("PT36H")],
91+
quantiles=[Q(0.5), Q(0.1), Q(0.9)],
92+
hyperparams=GBLinearHyperParams(
93+
n_steps=1000,
94+
learning_rate=0.3,
95+
),
96+
verbosity=True,
10097
),
10198
target_column="load",
10299
tags={

examples/examples/isotonic_calibration_example.py

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
from openstef_core.types import LeadTime, Q
2828
from openstef_models.models.forecasting.gblinear_forecaster import (
2929
GBLinearForecaster,
30-
GBLinearForecasterConfig,
3130
GBLinearHyperParams,
3231
)
3332
from openstef_models.models.forecasting_model import ForecastingModel
@@ -53,12 +52,10 @@
5352
# Step 2: Configure model without calibration (for comparison)
5453
model_uncalibrated = ForecastingModel(
5554
forecaster=GBLinearForecaster(
56-
config=GBLinearForecasterConfig(
57-
horizons=[LeadTime.from_string("PT1H")],
58-
quantiles=[Q(0.1), Q(0.5), Q(0.9)],
59-
hyperparams=GBLinearHyperParams(n_steps=100),
60-
verbosity=0,
61-
)
55+
horizons=[LeadTime.from_string("PT1H")],
56+
quantiles=[Q(0.1), Q(0.5), Q(0.9)],
57+
hyperparams=GBLinearHyperParams(n_steps=100),
58+
verbosity=0,
6259
),
6360
target_column="load",
6461
)
@@ -70,12 +67,10 @@
7067
# Step 3: Configure model with windowed isotonic quantile calibration
7168
model_calibrated = ForecastingModel(
7269
forecaster=GBLinearForecaster(
73-
config=GBLinearForecasterConfig(
74-
horizons=[LeadTime.from_string("PT1H")],
75-
quantiles=[Q(0.1), Q(0.5), Q(0.9)],
76-
hyperparams=GBLinearHyperParams(n_steps=100),
77-
verbosity=0,
78-
)
70+
horizons=[LeadTime.from_string("PT1H")],
71+
quantiles=[Q(0.1), Q(0.5), Q(0.9)],
72+
hyperparams=GBLinearHyperParams(n_steps=100),
73+
verbosity=0,
7974
),
8075
postprocessing=TransformPipeline(
8176
transforms=[

packages/openstef-beam/src/openstef_beam/benchmarking/baselines/openstef4.py

Lines changed: 62 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,31 @@
1111
from pathlib import Path
1212
from typing import Any, cast, override
1313

14+
import pandas as pd
1415
from pydantic import Field, PrivateAttr
1516
from pydantic_extra_types.coordinate import Coordinate
1617

17-
from openstef_beam.backtesting.backtest_forecaster.mixins import BacktestForecasterConfig, BacktestForecasterMixin
18-
from openstef_beam.backtesting.restricted_horizon_timeseries import RestrictedHorizonVersionedTimeSeries
19-
from openstef_beam.benchmarking.benchmark_pipeline import BenchmarkContext, BenchmarkTarget, ForecasterFactory
18+
from openstef_beam.backtesting.backtest_forecaster.mixins import (
19+
BacktestForecasterConfig,
20+
BacktestForecasterMixin,
21+
)
22+
from openstef_beam.backtesting.restricted_horizon_timeseries import (
23+
RestrictedHorizonVersionedTimeSeries,
24+
)
25+
from openstef_beam.benchmarking.benchmark_pipeline import (
26+
BenchmarkContext,
27+
BenchmarkTarget,
28+
ForecasterFactory,
29+
)
2030
from openstef_core.base_model import BaseConfig, BaseModel
2131
from openstef_core.datasets import TimeSeriesDataset
2232
from openstef_core.exceptions import FlatlinerDetectedError, NotFittedError
2333
from openstef_core.types import Q
34+
from openstef_meta.presets import EnsembleForecastingWorkflowConfig, create_ensemble_forecasting_workflow
2435
from openstef_models.presets import ForecastingWorkflowConfig
25-
from openstef_models.workflows.custom_forecasting_workflow import CustomForecastingWorkflow
36+
from openstef_models.workflows.custom_forecasting_workflow import (
37+
CustomForecastingWorkflow,
38+
)
2639

2740

2841
class WorkflowCreationContext(BaseConfig):
@@ -54,6 +67,10 @@ class OpenSTEF4BacktestForecaster(BaseModel, BacktestForecasterMixin):
5467
default=False,
5568
description="When True, saves intermediate input data for debugging",
5669
)
70+
contributions: bool = Field(
71+
default=False,
72+
description="When True, saves base forecaster prediction contributions for ensemble models",
73+
)
5774

5875
_workflow: CustomForecastingWorkflow | None = PrivateAttr(default=None)
5976
_is_flatliner_detected: bool = PrivateAttr(default=False)
@@ -62,7 +79,7 @@ class OpenSTEF4BacktestForecaster(BaseModel, BacktestForecasterMixin):
6279

6380
@override
6481
def model_post_init(self, context: Any) -> None:
65-
if self.debug:
82+
if self.debug or self.contributions:
6683
self.cache_dir.mkdir(parents=True, exist_ok=True)
6784

6885
@property
@@ -71,8 +88,8 @@ def quantiles(self) -> list[Q]:
7188
# Create a workflow instance if needed to get quantiles
7289
if self._workflow is None:
7390
self._workflow = self.workflow_factory(WorkflowCreationContext())
74-
# Extract quantiles from the workflow's model
75-
return self._workflow.model.forecaster.config.quantiles
91+
92+
return self._workflow.model.quantiles
7693

7794
@override
7895
def fit(self, data: RestrictedHorizonVersionedTimeSeries) -> None:
@@ -82,7 +99,9 @@ def fit(self, data: RestrictedHorizonVersionedTimeSeries) -> None:
8299

83100
# Extract the dataset for training
84101
training_data = data.get_window(
85-
start=data.horizon - self.config.training_context_length, end=data.horizon, available_before=data.horizon
102+
start=data.horizon - self.config.training_context_length,
103+
end=data.horizon,
104+
available_before=data.horizon,
86105
)
87106

88107
if self.debug:
@@ -102,7 +121,7 @@ def fit(self, data: RestrictedHorizonVersionedTimeSeries) -> None:
102121

103122
if self.debug:
104123
id_str = data.horizon.strftime("%Y%m%d%H%M%S")
105-
self._workflow.model.prepare_input(training_data).to_parquet( # pyright: ignore[reportPrivateUsage]
124+
self._workflow.model.prepare_input(training_data).to_parquet(
106125
path=self.cache_dir / f"debug_{id_str}_prepared_training.parquet"
107126
)
108127

@@ -136,6 +155,15 @@ def predict(self, data: RestrictedHorizonVersionedTimeSeries) -> TimeSeriesDatas
136155
predict_data.to_parquet(path=self.cache_dir / f"debug_{id_str}_predict.parquet")
137156
forecast.to_parquet(path=self.cache_dir / f"debug_{id_str}_forecast.parquet")
138157

158+
if self.contributions:
159+
id_str = data.horizon.strftime("%Y%m%d%H%M%S")
160+
try:
161+
contributions = self._workflow.model.predict_contributions(predict_data, forecast_start=data.horizon)
162+
except NotImplementedError:
163+
pass
164+
else:
165+
df = pd.concat([contributions.data, forecast.data.drop(columns=["load"])], axis=1)
166+
df.to_parquet(path=self.cache_dir / f"contrib_{id_str}_predict.parquet")
139167
return forecast
140168

141169

@@ -144,7 +172,7 @@ class OpenSTEF4PresetBacktestForecaster(OpenSTEF4BacktestForecaster):
144172

145173

146174
def _preset_target_forecaster_factory(
147-
base_config: ForecastingWorkflowConfig,
175+
base_config: ForecastingWorkflowConfig | EnsembleForecastingWorkflowConfig,
148176
backtest_config: BacktestForecasterConfig,
149177
cache_dir: Path,
150178
context: BenchmarkContext,
@@ -158,23 +186,26 @@ def _preset_target_forecaster_factory(
158186

159187
def _create_workflow(context: WorkflowCreationContext) -> CustomForecastingWorkflow:
160188
# Create a new workflow instance with fresh model.
161-
return create_forecasting_workflow(
162-
config=base_config.model_copy(
163-
update={
164-
"model_id": f"{prefix}_{target.name}",
165-
"run_name": context.step_name,
166-
"location": LocationConfig(
167-
name=target.name,
168-
description=target.description,
169-
coordinate=Coordinate(
170-
latitude=target.latitude,
171-
longitude=target.longitude,
172-
),
173-
),
174-
}
175-
)
189+
location = LocationConfig(
190+
name=target.name,
191+
description=target.description,
192+
coordinate=Coordinate(
193+
latitude=target.latitude,
194+
longitude=target.longitude,
195+
),
176196
)
177197

198+
update = {
199+
"model_id": f"{prefix}_{target.name}",
200+
"location": location,
201+
"run_name": context.step_name,
202+
}
203+
204+
if isinstance(base_config, EnsembleForecastingWorkflowConfig):
205+
return create_ensemble_forecasting_workflow(config=base_config.model_copy(update=update))
206+
207+
return create_forecasting_workflow(config=base_config.model_copy(update=update))
208+
178209
return OpenSTEF4BacktestForecaster(
179210
config=backtest_config,
180211
workflow_factory=_create_workflow,
@@ -184,7 +215,7 @@ def _create_workflow(context: WorkflowCreationContext) -> CustomForecastingWorkf
184215

185216

186217
def create_openstef4_preset_backtest_forecaster(
187-
workflow_config: ForecastingWorkflowConfig,
218+
workflow_config: ForecastingWorkflowConfig | EnsembleForecastingWorkflowConfig,
188219
backtest_config: BacktestForecasterConfig | None = None,
189220
cache_dir: Path = Path("cache"),
190221
) -> ForecasterFactory[BenchmarkTarget]:
@@ -225,4 +256,8 @@ def create_openstef4_preset_backtest_forecaster(
225256
)
226257

227258

228-
__all__ = ["OpenSTEF4BacktestForecaster", "WorkflowCreationContext", "create_openstef4_preset_backtest_forecaster"]
259+
__all__ = [
260+
"OpenSTEF4BacktestForecaster",
261+
"WorkflowCreationContext",
262+
"create_openstef4_preset_backtest_forecaster",
263+
]

packages/openstef-core/src/openstef_core/datasets/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,15 @@
1919
from openstef_core.datasets.timeseries_dataset import TimeSeriesDataset, validate_horizons_present
2020
from openstef_core.datasets.validated_datasets import (
2121
EnergyComponentDataset,
22+
EnsembleForecastDataset,
2223
ForecastDataset,
2324
ForecastInputDataset,
2425
)
2526
from openstef_core.datasets.versioned_timeseries_dataset import VersionedTimeSeriesDataset
2627

2728
__all__ = [
2829
"EnergyComponentDataset",
30+
"EnsembleForecastDataset",
2931
"ForecastDataset",
3032
"ForecastInputDataset",
3133
"TimeSeriesDataset",

0 commit comments

Comments
 (0)