openstef-beam 4.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. openstef_beam-4.0.0/.gitignore +146 -0
  2. openstef_beam-4.0.0/PKG-INFO +39 -0
  3. openstef_beam-4.0.0/README.md +7 -0
  4. openstef_beam-4.0.0/pyproject.toml +57 -0
  5. openstef_beam-4.0.0/src/openstef_beam/__init__.py +43 -0
  6. openstef_beam-4.0.0/src/openstef_beam/analysis/__init__.py +32 -0
  7. openstef_beam-4.0.0/src/openstef_beam/analysis/analysis_pipeline.py +194 -0
  8. openstef_beam-4.0.0/src/openstef_beam/analysis/models/__init__.py +24 -0
  9. openstef_beam-4.0.0/src/openstef_beam/analysis/models/target_metadata.py +97 -0
  10. openstef_beam-4.0.0/src/openstef_beam/analysis/models/visualization_aggregation.py +61 -0
  11. openstef_beam-4.0.0/src/openstef_beam/analysis/models/visualization_output.py +93 -0
  12. openstef_beam-4.0.0/src/openstef_beam/analysis/plots/__init__.py +27 -0
  13. openstef_beam-4.0.0/src/openstef_beam/analysis/plots/forecast_time_series_plotter.py +683 -0
  14. openstef_beam-4.0.0/src/openstef_beam/analysis/plots/grouped_target_metric_plotter.py +173 -0
  15. openstef_beam-4.0.0/src/openstef_beam/analysis/plots/precision_recall_curve_plotter.py +151 -0
  16. openstef_beam-4.0.0/src/openstef_beam/analysis/plots/quantile_calibration_box_plotter.py +196 -0
  17. openstef_beam-4.0.0/src/openstef_beam/analysis/plots/quantile_probability_plotter.py +153 -0
  18. openstef_beam-4.0.0/src/openstef_beam/analysis/plots/summary_table_plotter.py +80 -0
  19. openstef_beam-4.0.0/src/openstef_beam/analysis/plots/windowed_metric_plotter.py +141 -0
  20. openstef_beam-4.0.0/src/openstef_beam/analysis/visualizations/__init__.py +33 -0
  21. openstef_beam-4.0.0/src/openstef_beam/analysis/visualizations/base.py +250 -0
  22. openstef_beam-4.0.0/src/openstef_beam/analysis/visualizations/grouped_target_metric_visualization.py +351 -0
  23. openstef_beam-4.0.0/src/openstef_beam/analysis/visualizations/precision_recall_curve_visualization.py +195 -0
  24. openstef_beam-4.0.0/src/openstef_beam/analysis/visualizations/quantile_calibration_box_visualization.py +240 -0
  25. openstef_beam-4.0.0/src/openstef_beam/analysis/visualizations/quantile_probability_visualization.py +221 -0
  26. openstef_beam-4.0.0/src/openstef_beam/analysis/visualizations/summary_table_visualization.py +311 -0
  27. openstef_beam-4.0.0/src/openstef_beam/analysis/visualizations/timeseries_visualization.py +155 -0
  28. openstef_beam-4.0.0/src/openstef_beam/analysis/visualizations/windowed_metric_visualization.py +429 -0
  29. openstef_beam-4.0.0/src/openstef_beam/backtesting/__init__.py +23 -0
  30. openstef_beam-4.0.0/src/openstef_beam/backtesting/backtest_callback.py +9 -0
  31. openstef_beam-4.0.0/src/openstef_beam/backtesting/backtest_event.py +57 -0
  32. openstef_beam-4.0.0/src/openstef_beam/backtesting/backtest_event_generator.py +207 -0
  33. openstef_beam-4.0.0/src/openstef_beam/backtesting/backtest_forecaster/__init__.py +25 -0
  34. openstef_beam-4.0.0/src/openstef_beam/backtesting/backtest_forecaster/dummy_forecaster.py +73 -0
  35. openstef_beam-4.0.0/src/openstef_beam/backtesting/backtest_forecaster/mixins.py +236 -0
  36. openstef_beam-4.0.0/src/openstef_beam/backtesting/backtest_pipeline.py +301 -0
  37. openstef_beam-4.0.0/src/openstef_beam/backtesting/restricted_horizon_timeseries.py +72 -0
  38. openstef_beam-4.0.0/src/openstef_beam/benchmarking/__init__.py +55 -0
  39. openstef_beam-4.0.0/src/openstef_beam/benchmarking/baselines/__init__.py +17 -0
  40. openstef_beam-4.0.0/src/openstef_beam/benchmarking/baselines/openstef4.py +243 -0
  41. openstef_beam-4.0.0/src/openstef_beam/benchmarking/benchmark_comparison_pipeline.py +255 -0
  42. openstef_beam-4.0.0/src/openstef_beam/benchmarking/benchmark_pipeline.py +420 -0
  43. openstef_beam-4.0.0/src/openstef_beam/benchmarking/benchmarks/__init__.py +14 -0
  44. openstef_beam-4.0.0/src/openstef_beam/benchmarking/benchmarks/liander2024.py +239 -0
  45. openstef_beam-4.0.0/src/openstef_beam/benchmarking/callbacks/__init__.py +10 -0
  46. openstef_beam-4.0.0/src/openstef_beam/benchmarking/callbacks/base.py +230 -0
  47. openstef_beam-4.0.0/src/openstef_beam/benchmarking/callbacks/strict_execution_callback.py +39 -0
  48. openstef_beam-4.0.0/src/openstef_beam/benchmarking/models/__init__.py +9 -0
  49. openstef_beam-4.0.0/src/openstef_beam/benchmarking/models/benchmark_target.py +116 -0
  50. openstef_beam-4.0.0/src/openstef_beam/benchmarking/storage/__init__.py +16 -0
  51. openstef_beam-4.0.0/src/openstef_beam/benchmarking/storage/base.py +254 -0
  52. openstef_beam-4.0.0/src/openstef_beam/benchmarking/storage/local_storage.py +163 -0
  53. openstef_beam-4.0.0/src/openstef_beam/benchmarking/storage/s3_storage.py +193 -0
  54. openstef_beam-4.0.0/src/openstef_beam/benchmarking/target_provider.py +412 -0
  55. openstef_beam-4.0.0/src/openstef_beam/evaluation/__init__.py +43 -0
  56. openstef_beam-4.0.0/src/openstef_beam/evaluation/evaluation_pipeline.py +316 -0
  57. openstef_beam-4.0.0/src/openstef_beam/evaluation/metric_providers.py +753 -0
  58. openstef_beam-4.0.0/src/openstef_beam/evaluation/models/__init__.py +21 -0
  59. openstef_beam-4.0.0/src/openstef_beam/evaluation/models/report.py +150 -0
  60. openstef_beam-4.0.0/src/openstef_beam/evaluation/models/subset.py +100 -0
  61. openstef_beam-4.0.0/src/openstef_beam/evaluation/models/window.py +132 -0
  62. openstef_beam-4.0.0/src/openstef_beam/evaluation/window_iterators.py +117 -0
  63. openstef_beam-4.0.0/src/openstef_beam/metrics/__init__.py +58 -0
  64. openstef_beam-4.0.0/src/openstef_beam/metrics/metrics_deterministic.py +625 -0
  65. openstef_beam-4.0.0/src/openstef_beam/metrics/metrics_probabilistic.py +273 -0
  66. openstef_beam-4.0.0/tests/__init__.py +3 -0
  67. openstef_beam-4.0.0/tests/unit/__init__.py +3 -0
  68. openstef_beam-4.0.0/tests/unit/analysis/__init__.py +0 -0
  69. openstef_beam-4.0.0/tests/unit/analysis/plots/__init__.py +0 -0
  70. openstef_beam-4.0.0/tests/unit/analysis/plots/test_forecast_time_series_plotter.py +467 -0
  71. openstef_beam-4.0.0/tests/unit/analysis/plots/test_grouped_target_metric_plotter.py +114 -0
  72. openstef_beam-4.0.0/tests/unit/analysis/plots/test_precision_recall_curve_plotter.py +100 -0
  73. openstef_beam-4.0.0/tests/unit/analysis/plots/test_quantile_calibration_box_plotter.py +114 -0
  74. openstef_beam-4.0.0/tests/unit/analysis/plots/test_quantile_probability_plotter.py +124 -0
  75. openstef_beam-4.0.0/tests/unit/analysis/plots/test_summary_table_plotter.py +25 -0
  76. openstef_beam-4.0.0/tests/unit/analysis/plots/test_windowed_metric_plotter.py +91 -0
  77. openstef_beam-4.0.0/tests/unit/analysis/test_analysis_pipeline.py +296 -0
  78. openstef_beam-4.0.0/tests/unit/analysis/visualizations/__init__.py +0 -0
  79. openstef_beam-4.0.0/tests/unit/analysis/visualizations/conftest.py +194 -0
  80. openstef_beam-4.0.0/tests/unit/analysis/visualizations/test_grouped_target_metric_visualization.py +468 -0
  81. openstef_beam-4.0.0/tests/unit/analysis/visualizations/test_precision_recall_curve_visualization.py +217 -0
  82. openstef_beam-4.0.0/tests/unit/analysis/visualizations/test_quantile_calibration_box_visualization.py +282 -0
  83. openstef_beam-4.0.0/tests/unit/analysis/visualizations/test_quantile_probability_visualization.py +241 -0
  84. openstef_beam-4.0.0/tests/unit/analysis/visualizations/test_summary_table_visualization.py +186 -0
  85. openstef_beam-4.0.0/tests/unit/analysis/visualizations/test_timeseries_visualization.py +162 -0
  86. openstef_beam-4.0.0/tests/unit/analysis/visualizations/test_windowed_metric_visualization.py +496 -0
  87. openstef_beam-4.0.0/tests/unit/backtesting/__init__.py +0 -0
  88. openstef_beam-4.0.0/tests/unit/backtesting/test_backtest_event_generator.py +220 -0
  89. openstef_beam-4.0.0/tests/unit/backtesting/test_backtest_pipeline.py +382 -0
  90. openstef_beam-4.0.0/tests/unit/backtesting/test_batch_prediction.py +212 -0
  91. openstef_beam-4.0.0/tests/unit/benchmarking/__init__.py +0 -0
  92. openstef_beam-4.0.0/tests/unit/benchmarking/baselines/__init__.py +3 -0
  93. openstef_beam-4.0.0/tests/unit/benchmarking/baselines/test_openstef4.py +182 -0
  94. openstef_beam-4.0.0/tests/unit/benchmarking/storage/__init__.py +0 -0
  95. openstef_beam-4.0.0/tests/unit/benchmarking/storage/test_local_storage.py +300 -0
  96. openstef_beam-4.0.0/tests/unit/benchmarking/storage/test_s3_storage.py +287 -0
  97. openstef_beam-4.0.0/tests/unit/benchmarking/test_benchmark_pipeline.py +443 -0
  98. openstef_beam-4.0.0/tests/unit/benchmarking/test_target_provider.py +158 -0
  99. openstef_beam-4.0.0/tests/unit/evaluation/__init__.py +3 -0
  100. openstef_beam-4.0.0/tests/unit/evaluation/models/__init__.py +3 -0
  101. openstef_beam-4.0.0/tests/unit/evaluation/models/test_window.py +72 -0
  102. openstef_beam-4.0.0/tests/unit/evaluation/test_evaluation_pipeline.py +153 -0
  103. openstef_beam-4.0.0/tests/unit/evaluation/test_metric_provider.py +123 -0
  104. openstef_beam-4.0.0/tests/unit/evaluation/test_window_iterators.py +87 -0
  105. openstef_beam-4.0.0/tests/unit/metrics/__init__.py +0 -0
  106. openstef_beam-4.0.0/tests/unit/metrics/test_metrics_deterministic.py +590 -0
  107. openstef_beam-4.0.0/tests/unit/metrics/test_metrics_probabilistic.py +186 -0
  108. openstef_beam-4.0.0/tests/utils/__init__.py +0 -0
  109. openstef_beam-4.0.0/tests/utils/mocks.py +109 -0
@@ -0,0 +1,146 @@
1
+ # SPDX-FileCopyrightText: 2017-2025 Contributors to the OpenSTEF project <openstef@lfenergy.org> # noqa E501>
2
+ # SPDX-License-Identifier: MPL-2.0
3
+
4
+ # Core
5
+ config.user.yaml
6
+ git-template
7
+ .DS_Store
8
+ tmp
9
+
10
+ # Python bytecode
11
+ __pycache__/
12
+ *.py[cod]
13
+ *$py.class
14
+
15
+ # C extensions
16
+ *.so
17
+
18
+ # Packaging and build artifacts
19
+ .Python
20
+ build/
21
+ dist/
22
+ downloads/
23
+ wheels/
24
+ share/python-wheels/
25
+ sdist/
26
+ .eggs/
27
+ *.egg-info/
28
+ *.egg
29
+ .installed.cfg
30
+ MANIFEST
31
+
32
+ # uv
33
+ .uv/
34
+
35
+ # Ruff
36
+ .ruff_cache/
37
+
38
+ # Pyright
39
+ .pyright/
40
+ # pyright-report/
41
+
42
+ # Test, coverage, tox
43
+ .pytest_cache/
44
+ .coverage
45
+ .coverage.*
46
+ htmlcov/
47
+ .cover/
48
+ cover/
49
+ pytest-report.xml
50
+ .tox/
51
+ .nox/
52
+ coverage.xml
53
+
54
+ # Hypothesis
55
+ .hypothesis/
56
+
57
+ # Cython
58
+ cython_debug/
59
+
60
+ # Type checkers (misc)
61
+ .mypy_cache/
62
+ .dmypy.json
63
+ dmypy.json
64
+ .pytype/
65
+ .pyre/
66
+
67
+ # Sphinx
68
+ docs/_build/
69
+ docs/source/api/generated/
70
+ docs/source/tutorials/
71
+ docs/source/benchmarks/
72
+ docs/source/user_guide/**/quick_start_tutorial.py
73
+ docs/source/user_guide/**/feature_engineering_tutorial.py
74
+ docs/source/user_guide/**/datasets_tutorial.py
75
+ docs/source/user_guide/**/backtesting_tutorial.py
76
+
77
+ # docs/_doctrees/
78
+ # docs/_static_gen/
79
+
80
+ # Editors: JetBrains
81
+ .idea/
82
+
83
+ # Editors: VS Code (allow shared configs)
84
+ .vscode/*
85
+
86
+ # Jupyter / IPython
87
+ .ipynb_checkpoints
88
+ profile_default/
89
+ ipython_config.py
90
+
91
+ # Spyder / Rope
92
+ .spyderproject
93
+ .spyproject
94
+ .ropeproject
95
+
96
+ # Environments
97
+ .env
98
+ .venv
99
+ env/
100
+ venv/
101
+ ENV/
102
+ env.bak/
103
+ venv.bak/
104
+
105
+ # PEP 582
106
+ __pypackages__/
107
+
108
+ # web frameworks / services
109
+ *.sqlite
110
+ *.sqlite3
111
+ *.log
112
+ instance/
113
+ .webassets-cache
114
+ celerybeat-schedule
115
+ celerybeat.pid
116
+ *.sage.py
117
+ tmp/
118
+
119
+ # PyInstaller
120
+ *.manifest
121
+ *.spec
122
+
123
+ # Project outputs
124
+ output/
125
+ prof/
126
+ certificates/
127
+
128
+ # Output artifacts
129
+ *.html
130
+ *.pkl
131
+
132
+ # Benchmark outputs
133
+ benchmark_results*/
134
+
135
+ # Local dataset files
136
+ liander_dataset/
137
+
138
+ # Mlflow
139
+ /mlflow
140
+ /mlflow_artifacts_local
141
+
142
+ .github/instructions
143
+
144
+ # Jupyter notebook cache (myst-nb execution outputs)
145
+ .jupyter_cache/
146
+ docs/build.zip
@@ -0,0 +1,39 @@
1
+ Metadata-Version: 2.4
2
+ Name: openstef-beam
3
+ Version: 4.0.0
4
+ Summary: Backtesting, Evaluation, Analysis and Metrics (BEAM) library for OpenSTEF
5
+ Project-URL: Documentation, https://openstef.github.io/openstef/index.html
6
+ Project-URL: Homepage, https://lfenergy.org/projects/openstef/
7
+ Project-URL: Issues, https://github.com/OpenSTEF/openstef/issues
8
+ Project-URL: Repository, https://github.com/OpenSTEF/openstef
9
+ Author-email: "Alliander N.V" <openstef@lfenergy.org>
10
+ License-Expression: MPL-2.0
11
+ Keywords: energy,forecasting,machinelearning
12
+ Classifier: Development Status :: 5 - Production/Stable
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Programming Language :: Python :: 3 :: Only
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Programming Language :: Python :: 3.14
18
+ Requires-Python: <4.0,>=3.12
19
+ Requires-Dist: openstef-core<5,>=4.0.0.dev0
20
+ Requires-Dist: plotly>=6.3
21
+ Requires-Dist: pyyaml>=6.0.2
22
+ Requires-Dist: scoringrules>=0.8
23
+ Requires-Dist: tqdm>=4.67.1
24
+ Provides-Extra: all
25
+ Requires-Dist: openstef-meta<5,>=4.0.0.dev0; extra == 'all'
26
+ Requires-Dist: openstef-models<5,>=4.0.0.dev0; extra == 'all'
27
+ Requires-Dist: s3fs>=2025.5.1; extra == 'all'
28
+ Provides-Extra: baselines
29
+ Requires-Dist: openstef-meta<5,>=4.0.0.dev0; extra == 'baselines'
30
+ Requires-Dist: openstef-models<5,>=4.0.0.dev0; extra == 'baselines'
31
+ Description-Content-Type: text/markdown
32
+
33
+ <!--
34
+ SPDX-FileCopyrightText: 2025 Contributors to the OpenSTEF project <openstef@lfenergy.org>
35
+
36
+ SPDX-License-Identifier: MPL-2.0
37
+ -->
38
+
39
+ # openstef-beam
@@ -0,0 +1,7 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2025 Contributors to the OpenSTEF project <openstef@lfenergy.org>
3
+
4
+ SPDX-License-Identifier: MPL-2.0
5
+ -->
6
+
7
+ # openstef-beam
@@ -0,0 +1,57 @@
1
+ # SPDX-FileCopyrightText: 2025 Contributors to the OpenSTEF project <openstef@lfenergy.org>
2
+ #
3
+ # SPDX-License-Identifier: MPL-2.0
4
+
5
+ [build-system]
6
+ build-backend = "hatchling.build"
7
+
8
+ requires = [ "hatchling" ]
9
+
10
+ [project]
11
+ name = "openstef-beam"
12
+ version = "4.0.0"
13
+ description = "Backtesting, Evaluation, Analysis and Metrics (BEAM) library for OpenSTEF"
14
+ readme = "README.md"
15
+ keywords = [ "energy", "forecasting", "machinelearning" ]
16
+ license = "MPL-2.0"
17
+ authors = [
18
+ { name = "Alliander N.V", email = "openstef@lfenergy.org" },
19
+ ]
20
+ requires-python = ">=3.12,<4.0"
21
+ classifiers = [
22
+ "Development Status :: 5 - Production/Stable",
23
+ "Intended Audience :: Developers",
24
+ "Programming Language :: Python :: 3 :: Only",
25
+ "Programming Language :: Python :: 3.12",
26
+ "Programming Language :: Python :: 3.13",
27
+ "Programming Language :: Python :: 3.14",
28
+ ]
29
+
30
+ dependencies = [
31
+ "openstef-core>=4.0.0.dev0,<5",
32
+ "plotly>=6.3",
33
+ "pyyaml>=6.0.2",
34
+ "scoringrules>=0.8",
35
+ "tqdm>=4.67.1",
36
+ ]
37
+
38
+ optional-dependencies.all = [
39
+ "openstef-beam[baselines]",
40
+ "s3fs>=2025.5.1",
41
+ ]
42
+ optional-dependencies.baselines = [
43
+ "openstef-meta>=4.0.0.dev0,<5",
44
+ "openstef-models>=4.0.0.dev0,<5",
45
+ ]
46
+ urls.Documentation = "https://openstef.github.io/openstef/index.html"
47
+ urls.Homepage = "https://lfenergy.org/projects/openstef/"
48
+ urls.Issues = "https://github.com/OpenSTEF/openstef/issues"
49
+ urls.Repository = "https://github.com/OpenSTEF/openstef"
50
+
51
+ [tool.hatch.build.targets.wheel]
52
+ packages = [ "src/openstef_beam" ]
53
+
54
+ [tool.uv.sources]
55
+ openstef-core = { workspace = true }
56
+ openstef-models = { workspace = true }
57
+ openstef-meta = { workspace = true }
@@ -0,0 +1,43 @@
1
+ # SPDX-FileCopyrightText: 2017-2025 Contributors to the OpenSTEF project <openstef@lfenergy.org>
2
+ #
3
+ # SPDX-License-Identifier: MPL-2.0
4
+
5
+ """OpenSTEF BEAM: Backtesting, Evaluation, Analysis and Metrics framework.
6
+
7
+ BEAM provides all the tools to test energy forecasting models under realistic conditions.
8
+ Instead of simple validation that can mislead, BEAM simulates real-world scenarios using
9
+ versioned data - ensuring models only use information available at prediction time.
10
+
11
+ Key features:
12
+
13
+ **Real-world simulation**: Uses versioned data to prevent data leakage. Models are
14
+ retrained periodically and can only access historical information, just like in production.
15
+
16
+ **Flexible integration**: Plug in your own forecasting models, create custom metrics,
17
+ design specific visualizations, and select relevant lead times for your use case.
18
+
19
+ **Complete workflow**: Backtesting → Evaluation → Analysis → Benchmarking. From testing
20
+ individual models to comparing multiple approaches across different energy targets.
21
+
22
+ **Lead time analysis**: Evaluate how forecast quality changes from 1-hour to 48-hour
23
+ predictions, critical for operational planning in energy systems.
24
+
25
+ Use BEAM to:
26
+
27
+ - Test models under realistic operational constraints
28
+ - Compare forecasting approaches with versioned data integrity
29
+ - Generate flexible reports tailored to your metrics and visualizations
30
+ - Ensure evaluation results match real-world deployment performance
31
+
32
+ BEAM's versioned data approach and flexible architecture make it the reliable choice
33
+ for energy forecasting model validation that translates to production success.
34
+ """
35
+
36
+ import logging
37
+
38
+ # Set up logging configuration
39
+ root_logger = logging.getLogger(name=__name__)
40
+ if not root_logger.handlers:
41
+ root_logger.addHandler(logging.NullHandler())
42
+
43
+ __all__ = []
@@ -0,0 +1,32 @@
1
+ # SPDX-FileCopyrightText: 2025 Contributors to the OpenSTEF project <openstef@lfenergy.org>
2
+ #
3
+ # SPDX-License-Identifier: MPL-2.0
4
+
5
+ """Turns evaluation results into visualizations and reports for decision making.
6
+
7
+ Numbers and metrics are hard to interpret. This module creates charts, plots, and
8
+ summary reports that help you understand model performance and make decisions about
9
+ which models to deploy. It automatically generates the visualizations most relevant
10
+ for energy forecasting operations.
11
+
12
+ What you get:
13
+
14
+ - Performance charts: See how models compare across different metrics
15
+ - Time series plots: Visualize predictions vs actual load over time
16
+ - Error analysis: Understand when and why models make mistakes
17
+ - Comparison reports: Side-by-side model performance analysis
18
+ """
19
+
20
+ from openstef_beam.analysis import plots, visualizations
21
+ from openstef_beam.analysis.analysis_pipeline import AnalysisConfig, AnalysisPipeline
22
+ from openstef_beam.analysis.models import AnalysisOutput, AnalysisScope, VisualizationOutput
23
+
24
+ __all__ = [
25
+ "AnalysisConfig",
26
+ "AnalysisOutput",
27
+ "AnalysisPipeline",
28
+ "AnalysisScope",
29
+ "VisualizationOutput",
30
+ "plots",
31
+ "visualizations",
32
+ ]
@@ -0,0 +1,194 @@
1
+ # SPDX-FileCopyrightText: 2025 Contributors to the OpenSTEF project <openstef@lfenergy.org>
2
+ #
3
+ # SPDX-License-Identifier: MPL-2.0
4
+
5
+ """Analysis pipeline for generating visualizations from evaluation reports.
6
+
7
+ This module provides the core pipeline that orchestrates visualization generation
8
+ from evaluation reports at different aggregation levels. It integrates with the
9
+ benchmarking framework to provide consistent analysis outputs across benchmark runs.
10
+ """
11
+
12
+ from collections import defaultdict
13
+ from collections.abc import Sequence
14
+
15
+ from pydantic import Field
16
+
17
+ from openstef_beam.analysis.models import (
18
+ AnalysisAggregation,
19
+ AnalysisOutput,
20
+ AnalysisScope,
21
+ GroupName,
22
+ TargetMetadata,
23
+ VisualizationOutput,
24
+ )
25
+ from openstef_beam.analysis.visualizations import ReportTuple, VisualizationProvider
26
+ from openstef_beam.evaluation import EvaluationReport
27
+ from openstef_beam.evaluation.models import Filtering
28
+ from openstef_core.base_model import BaseConfig
29
+ from openstef_core.utils.itertools import groupby
30
+
31
+
32
+ class AnalysisConfig(BaseConfig):
33
+ """Configuration for the analytics pipeline."""
34
+
35
+ visualization_providers: list[VisualizationProvider] = Field(
36
+ default=[], description="List of visualization providers to use for generating analysis outputs"
37
+ )
38
+ filterings: list[Filtering] | None = Field(
39
+ default=None,
40
+ description="When set, only include these filterings (e.g. LeadTime, AvailableAt) in analysis. "
41
+ "None means use all filterings found in the evaluation data.",
42
+ )
43
+
44
+
45
+ class AnalysisPipeline:
46
+ """Orchestrates the generation of visualizations from evaluation reports.
47
+
48
+ The pipeline processes evaluation reports at different aggregation levels:
49
+
50
+ - Individual targets: Creates detailed visualizations for single targets
51
+ - Multiple targets: Creates comparative visualizations across target groups
52
+
53
+ It integrates with the benchmarking framework by being called from BenchmarkPipeline
54
+ after evaluation is complete, ensuring consistent visualization generation across
55
+ all benchmark runs.
56
+ """
57
+
58
+ def __init__(
59
+ self,
60
+ config: AnalysisConfig,
61
+ ) -> None:
62
+ """Initialize the analysis pipeline with configuration.
63
+
64
+ Args:
65
+ config: Analysis configuration containing visualization providers.
66
+ """
67
+ super().__init__()
68
+ self.config = config
69
+
70
+ def _group_by_filtering(
71
+ self,
72
+ reports: Sequence[tuple[TargetMetadata, EvaluationReport]],
73
+ ) -> dict[Filtering, list[ReportTuple]]:
74
+ """Group reports by their lead time filtering conditions.
75
+
76
+ Organizes evaluation reports based on their lead time criteria (e.g.,
77
+ 1-hour ahead vs 24-hour ahead forecasts), enabling comparison of model
78
+ performance across different forecasting horizons.
79
+
80
+ When ``config.filterings`` is set, only subsets matching those filterings are included.
81
+
82
+ Returns:
83
+ Dictionary mapping lead time filtering conditions to lists of report tuples.
84
+ """
85
+ allowed = set(self.config.filterings) if self.config.filterings is not None else None
86
+ return groupby(
87
+ (subset.filtering, (base_metadata.with_filtering(subset.filtering), subset))
88
+ for base_metadata, report in reports
89
+ for subset in report.subset_reports
90
+ if allowed is None or subset.filtering in allowed
91
+ )
92
+
93
+ def run_for_subsets(
94
+ self,
95
+ reports: list[ReportTuple],
96
+ aggregation: AnalysisAggregation,
97
+ ) -> list[VisualizationOutput]:
98
+ """Generate visualizations for a set of evaluation reports at a specific aggregation level.
99
+
100
+ Processes the provided evaluation reports through all configured visualization
101
+ providers that support the requested aggregation level. Only providers that
102
+ declare support for the aggregation are used.
103
+
104
+ Args:
105
+ reports: List of (metadata, evaluation_subset_report) tuples to visualize.
106
+ The metadata provides context about the target and run, while the
107
+ evaluation report contains the metrics and predictions to visualize.
108
+ aggregation: The aggregation level determining how reports are grouped
109
+ and compared in visualizations.
110
+
111
+ Returns:
112
+ List of visualization outputs from all applicable providers. Empty if
113
+ no providers support the requested aggregation level.
114
+ """
115
+ return [
116
+ provider.create(reports=reports, aggregation=aggregation)
117
+ for provider in self.config.visualization_providers
118
+ if aggregation in provider.supported_aggregations
119
+ ]
120
+
121
+ def run_for_reports(
122
+ self,
123
+ reports: Sequence[tuple[TargetMetadata, EvaluationReport]],
124
+ scope: AnalysisScope,
125
+ ) -> AnalysisOutput:
126
+ """Generate visualizations for evaluation reports within a specific scope.
127
+
128
+ Groups reports by lead time filtering conditions and generates visualizations
129
+ for each group using all configured visualization providers that support the
130
+ scope's aggregation level. This enables comparing model performance across
131
+ different forecasting horizons (e.g., short-term vs long-term predictions).
132
+
133
+ Args:
134
+ reports: List of (metadata, evaluation_report) tuples to visualize.
135
+ scope: Analysis scope defining aggregation level and context.
136
+
137
+ Returns:
138
+ Analysis output containing all generated visualizations grouped by
139
+ lead time filtering conditions.
140
+ """
141
+ grouped = self._group_by_filtering(reports)
142
+
143
+ result: dict[Filtering, list[VisualizationOutput]] = defaultdict(list)
144
+ for filtering, subset_reports in grouped.items():
145
+ visualizations = self.run_for_subsets(
146
+ reports=subset_reports,
147
+ aggregation=scope.aggregation,
148
+ )
149
+ result[filtering].extend(visualizations)
150
+
151
+ return AnalysisOutput(
152
+ scope=scope,
153
+ visualizations=result,
154
+ )
155
+
156
+ def run_for_groups(
157
+ self,
158
+ reports: Sequence[tuple[TargetMetadata, EvaluationReport]],
159
+ scope: AnalysisScope,
160
+ ) -> dict[GroupName, AnalysisOutput]:
161
+ """Generate visualizations for multiple target groups at a specific aggregation level.
162
+
163
+ This method processes all evaluation reports, grouping them by their target
164
+ group names and generating visualizations for each group.
165
+
166
+ Args:
167
+ reports: List of (metadata, evaluation_report) tuples to visualize.
168
+ The metadata provides context about the target and run, while the
169
+ evaluation report contains the metrics and predictions to visualize.
170
+ scope: The analytics scope defining how reports are grouped and aggregated.
171
+
172
+ Returns:
173
+ Dictionary mapping group names to their corresponding AnalyticsOutput.
174
+ """
175
+ reports_by_group: dict[GroupName, list[tuple[TargetMetadata, EvaluationReport]]] = groupby(
176
+ (report[0].group_name, report) for report in reports
177
+ )
178
+
179
+ result: dict[GroupName, AnalysisOutput] = {}
180
+ for group_name, group_reports in reports_by_group.items():
181
+ if not group_reports:
182
+ continue
183
+
184
+ result[group_name] = self.run_for_reports(
185
+ reports=group_reports,
186
+ scope=AnalysisScope(
187
+ aggregation=scope.aggregation,
188
+ target_name=scope.target_name,
189
+ run_name=scope.run_name,
190
+ group_name=group_name,
191
+ ),
192
+ )
193
+
194
+ return result
@@ -0,0 +1,24 @@
1
+ # SPDX-FileCopyrightText: 2025 Contributors to the OpenSTEF project <openstef@lfenergy.org>
2
+ #
3
+ # SPDX-License-Identifier: MPL-2.0
4
+
5
+ """Data models for analysis pipeline components.
6
+
7
+ This package provides data models and type definitions for the analysis pipeline,
8
+ including aggregation types, output structures, and metadata containers.
9
+ """
10
+
11
+ from openstef_beam.analysis.models.target_metadata import GroupName, RunName, TargetMetadata, TargetName
12
+ from openstef_beam.analysis.models.visualization_aggregation import AnalysisAggregation
13
+ from openstef_beam.analysis.models.visualization_output import AnalysisOutput, AnalysisScope, VisualizationOutput
14
+
15
+ __all__ = [
16
+ "AnalysisAggregation",
17
+ "AnalysisOutput",
18
+ "AnalysisScope",
19
+ "GroupName",
20
+ "RunName",
21
+ "TargetMetadata",
22
+ "TargetName",
23
+ "VisualizationOutput",
24
+ ]
@@ -0,0 +1,97 @@
1
+ # SPDX-FileCopyrightText: 2025 Contributors to the OpenSTEF project <openstef@lfenergy.org>
2
+ #
3
+ # SPDX-License-Identifier: MPL-2.0
4
+
5
+ """Target metadata models for analysis pipeline.
6
+
7
+ This module defines the metadata structure that carries target information
8
+ through the analysis pipeline, including grouping and filtering context.
9
+ """
10
+
11
+ from typing import Self
12
+
13
+ from pydantic import Field, model_validator
14
+
15
+ from openstef_beam.evaluation.models import Filtering
16
+ from openstef_core.base_model import BaseModel
17
+
18
+ type TargetName = str
19
+ type GroupName = str
20
+ type RunName = str
21
+
22
+
23
+ class TargetMetadata(BaseModel):
24
+ """Metadata for a forecasting target in the analysis pipeline.
25
+
26
+ Contains essential information about a target including its grouping context
27
+ and lead time filtering criteria. Lead time filtering determines which
28
+ predictions are included based on how far ahead they were made (e.g.,
29
+ 1-hour ahead vs 24-hour ahead forecasts).
30
+
31
+ Raises:
32
+ ValueError: When limit constraints are not met (either 'limit' alone or both
33
+ 'upper_limit' and 'lower_limit' must be specified).
34
+ """
35
+
36
+ name: TargetName = Field(description="Name of the target")
37
+ group_name: GroupName = Field(description="Name of the group this target belongs to, used for grouping in reports")
38
+ filtering: Filtering | None = Field(
39
+ description="Lead time filtering criteria - either AvailableAt (data availability time) "
40
+ "or LeadTime (forecast horizon)"
41
+ )
42
+ limit: float | None = Field(default=None, description="Capacity limit of the target in appropriate units")
43
+ upper_limit: float | None = Field(
44
+ default=None, description="Upper capacity limit of the target in appropriate units"
45
+ )
46
+ lower_limit: float | None = Field(
47
+ default=None, description="Lower capacity limit of the target in appropriate units"
48
+ )
49
+ run_name: RunName = Field(description="Name of the run associated with this target")
50
+
51
+ @model_validator(mode="after")
52
+ def validate_limits(self) -> "TargetMetadata":
53
+ """Validate that either limit or both upper_limit and lower_limit are provided.
54
+
55
+ Returns:
56
+ The validated TargetMetadata instance.
57
+
58
+ Raises:
59
+ ValueError: If neither limit nor (upper_limit and lower_limit) are provided,
60
+ or if both limit and (upper_limit or lower_limit) are provided.
61
+ """
62
+ has_limit = self.limit is not None
63
+ has_upper = self.upper_limit is not None
64
+ has_lower = self.lower_limit is not None
65
+
66
+ if has_limit and (has_upper or has_lower):
67
+ raise ValueError(
68
+ "Cannot specify both 'limit' and 'upper_limit'/'lower_limit'. "
69
+ "Use either 'limit' alone or both 'upper_limit' and 'lower_limit'."
70
+ )
71
+
72
+ if not has_limit and not (has_upper and has_lower):
73
+ raise ValueError("Must specify either 'limit' or both 'upper_limit' and 'lower_limit'.")
74
+
75
+ return self
76
+
77
+ def with_filtering(self, filtering: Filtering) -> Self:
78
+ """Returns a copy of the target metadata with different lead time filtering applied.
79
+
80
+ Args:
81
+ filtering: New lead time filtering criteria to apply
82
+
83
+ Returns:
84
+ New TargetMetadata instance with updated lead time filtering
85
+ """
86
+ return type(self)(
87
+ name=self.name,
88
+ group_name=self.group_name,
89
+ filtering=filtering,
90
+ limit=self.limit,
91
+ upper_limit=self.upper_limit,
92
+ lower_limit=self.lower_limit,
93
+ run_name=self.run_name,
94
+ )
95
+
96
+
97
+ __all__ = ["GroupName", "RunName", "TargetMetadata", "TargetName"]