openstef-beam 4.0.0a0__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 (106) hide show
  1. openstef_beam-4.0.0a0/.gitignore +127 -0
  2. openstef_beam-4.0.0a0/PKG-INFO +34 -0
  3. openstef_beam-4.0.0a0/README.md +7 -0
  4. openstef_beam-4.0.0a0/pyproject.toml +50 -0
  5. openstef_beam-4.0.0a0/src/openstef_beam/__init__.py +42 -0
  6. openstef_beam-4.0.0a0/src/openstef_beam/analysis/__init__.py +31 -0
  7. openstef_beam-4.0.0a0/src/openstef_beam/analysis/analysis_pipeline.py +187 -0
  8. openstef_beam-4.0.0a0/src/openstef_beam/analysis/models/__init__.py +24 -0
  9. openstef_beam-4.0.0a0/src/openstef_beam/analysis/models/target_metadata.py +97 -0
  10. openstef_beam-4.0.0a0/src/openstef_beam/analysis/models/visualization_aggregation.py +60 -0
  11. openstef_beam-4.0.0a0/src/openstef_beam/analysis/models/visualization_output.py +93 -0
  12. openstef_beam-4.0.0a0/src/openstef_beam/analysis/plots/__init__.py +27 -0
  13. openstef_beam-4.0.0a0/src/openstef_beam/analysis/plots/forecast_time_series_plotter.py +674 -0
  14. openstef_beam-4.0.0a0/src/openstef_beam/analysis/plots/grouped_target_metric_plotter.py +173 -0
  15. openstef_beam-4.0.0a0/src/openstef_beam/analysis/plots/precision_recall_curve_plotter.py +149 -0
  16. openstef_beam-4.0.0a0/src/openstef_beam/analysis/plots/quantile_calibration_box_plotter.py +196 -0
  17. openstef_beam-4.0.0a0/src/openstef_beam/analysis/plots/quantile_probability_plotter.py +152 -0
  18. openstef_beam-4.0.0a0/src/openstef_beam/analysis/plots/summary_table_plotter.py +79 -0
  19. openstef_beam-4.0.0a0/src/openstef_beam/analysis/plots/windowed_metric_plotter.py +140 -0
  20. openstef_beam-4.0.0a0/src/openstef_beam/analysis/visualizations/__init__.py +33 -0
  21. openstef_beam-4.0.0a0/src/openstef_beam/analysis/visualizations/base.py +250 -0
  22. openstef_beam-4.0.0a0/src/openstef_beam/analysis/visualizations/grouped_target_metric_visualization.py +349 -0
  23. openstef_beam-4.0.0a0/src/openstef_beam/analysis/visualizations/precision_recall_curve_visualization.py +192 -0
  24. openstef_beam-4.0.0a0/src/openstef_beam/analysis/visualizations/quantile_calibration_box_visualization.py +239 -0
  25. openstef_beam-4.0.0a0/src/openstef_beam/analysis/visualizations/quantile_probability_visualization.py +220 -0
  26. openstef_beam-4.0.0a0/src/openstef_beam/analysis/visualizations/summary_table_visualization.py +309 -0
  27. openstef_beam-4.0.0a0/src/openstef_beam/analysis/visualizations/timeseries_visualization.py +150 -0
  28. openstef_beam-4.0.0a0/src/openstef_beam/analysis/visualizations/windowed_metric_visualization.py +398 -0
  29. openstef_beam-4.0.0a0/src/openstef_beam/backtesting/__init__.py +22 -0
  30. openstef_beam-4.0.0a0/src/openstef_beam/backtesting/backtest_callback.py +9 -0
  31. openstef_beam-4.0.0a0/src/openstef_beam/backtesting/backtest_event.py +57 -0
  32. openstef_beam-4.0.0a0/src/openstef_beam/backtesting/backtest_event_generator.py +207 -0
  33. openstef_beam-4.0.0a0/src/openstef_beam/backtesting/backtest_forecaster/__init__.py +29 -0
  34. openstef_beam-4.0.0a0/src/openstef_beam/backtesting/backtest_forecaster/dummy_forecaster.py +73 -0
  35. openstef_beam-4.0.0a0/src/openstef_beam/backtesting/backtest_forecaster/mixins.py +232 -0
  36. openstef_beam-4.0.0a0/src/openstef_beam/backtesting/backtest_forecaster/openstef4_backtest_forecaster.py +127 -0
  37. openstef_beam-4.0.0a0/src/openstef_beam/backtesting/backtest_pipeline.py +301 -0
  38. openstef_beam-4.0.0a0/src/openstef_beam/backtesting/restricted_horizon_timeseries.py +72 -0
  39. openstef_beam-4.0.0a0/src/openstef_beam/benchmarking/__init__.py +54 -0
  40. openstef_beam-4.0.0a0/src/openstef_beam/benchmarking/benchmark_comparison_pipeline.py +249 -0
  41. openstef_beam-4.0.0a0/src/openstef_beam/benchmarking/benchmark_pipeline.py +404 -0
  42. openstef_beam-4.0.0a0/src/openstef_beam/benchmarking/benchmarks/__init__.py +14 -0
  43. openstef_beam-4.0.0a0/src/openstef_beam/benchmarking/benchmarks/liander2024.py +239 -0
  44. openstef_beam-4.0.0a0/src/openstef_beam/benchmarking/callbacks/__init__.py +10 -0
  45. openstef_beam-4.0.0a0/src/openstef_beam/benchmarking/callbacks/base.py +228 -0
  46. openstef_beam-4.0.0a0/src/openstef_beam/benchmarking/callbacks/strict_execution_callback.py +39 -0
  47. openstef_beam-4.0.0a0/src/openstef_beam/benchmarking/models/__init__.py +9 -0
  48. openstef_beam-4.0.0a0/src/openstef_beam/benchmarking/models/benchmark_target.py +116 -0
  49. openstef_beam-4.0.0a0/src/openstef_beam/benchmarking/storage/__init__.py +16 -0
  50. openstef_beam-4.0.0a0/src/openstef_beam/benchmarking/storage/base.py +252 -0
  51. openstef_beam-4.0.0a0/src/openstef_beam/benchmarking/storage/local_storage.py +162 -0
  52. openstef_beam-4.0.0a0/src/openstef_beam/benchmarking/storage/s3_storage.py +193 -0
  53. openstef_beam-4.0.0a0/src/openstef_beam/benchmarking/target_provider.py +403 -0
  54. openstef_beam-4.0.0a0/src/openstef_beam/evaluation/__init__.py +42 -0
  55. openstef_beam-4.0.0a0/src/openstef_beam/evaluation/evaluation_pipeline.py +294 -0
  56. openstef_beam-4.0.0a0/src/openstef_beam/evaluation/metric_providers.py +620 -0
  57. openstef_beam-4.0.0a0/src/openstef_beam/evaluation/models/__init__.py +21 -0
  58. openstef_beam-4.0.0a0/src/openstef_beam/evaluation/models/report.py +150 -0
  59. openstef_beam-4.0.0a0/src/openstef_beam/evaluation/models/subset.py +83 -0
  60. openstef_beam-4.0.0a0/src/openstef_beam/evaluation/models/window.py +106 -0
  61. openstef_beam-4.0.0a0/src/openstef_beam/evaluation/window_iterators.py +95 -0
  62. openstef_beam-4.0.0a0/src/openstef_beam/metrics/__init__.py +53 -0
  63. openstef_beam-4.0.0a0/src/openstef_beam/metrics/metrics_deterministic.py +535 -0
  64. openstef_beam-4.0.0a0/src/openstef_beam/metrics/metrics_probabilistic.py +272 -0
  65. openstef_beam-4.0.0a0/tests/__init__.py +3 -0
  66. openstef_beam-4.0.0a0/tests/unit/__init__.py +3 -0
  67. openstef_beam-4.0.0a0/tests/unit/analysis/__init__.py +0 -0
  68. openstef_beam-4.0.0a0/tests/unit/analysis/plots/__init__.py +0 -0
  69. openstef_beam-4.0.0a0/tests/unit/analysis/plots/test_forecast_time_series_plotter.py +467 -0
  70. openstef_beam-4.0.0a0/tests/unit/analysis/plots/test_grouped_target_metric_plotter.py +114 -0
  71. openstef_beam-4.0.0a0/tests/unit/analysis/plots/test_precision_recall_curve_plotter.py +100 -0
  72. openstef_beam-4.0.0a0/tests/unit/analysis/plots/test_quantile_calibration_box_plotter.py +114 -0
  73. openstef_beam-4.0.0a0/tests/unit/analysis/plots/test_quantile_probability_plotter.py +124 -0
  74. openstef_beam-4.0.0a0/tests/unit/analysis/plots/test_summary_table_plotter.py +25 -0
  75. openstef_beam-4.0.0a0/tests/unit/analysis/plots/test_windowed_metric_plotter.py +91 -0
  76. openstef_beam-4.0.0a0/tests/unit/analysis/test_analysis_pipeline.py +296 -0
  77. openstef_beam-4.0.0a0/tests/unit/analysis/visualizations/__init__.py +0 -0
  78. openstef_beam-4.0.0a0/tests/unit/analysis/visualizations/conftest.py +194 -0
  79. openstef_beam-4.0.0a0/tests/unit/analysis/visualizations/test_grouped_target_metric_visualization.py +468 -0
  80. openstef_beam-4.0.0a0/tests/unit/analysis/visualizations/test_precision_recall_curve_visualization.py +217 -0
  81. openstef_beam-4.0.0a0/tests/unit/analysis/visualizations/test_quantile_calibration_box_visualization.py +282 -0
  82. openstef_beam-4.0.0a0/tests/unit/analysis/visualizations/test_quantile_probability_visualization.py +241 -0
  83. openstef_beam-4.0.0a0/tests/unit/analysis/visualizations/test_summary_table_visualization.py +186 -0
  84. openstef_beam-4.0.0a0/tests/unit/analysis/visualizations/test_timeseries_visualization.py +165 -0
  85. openstef_beam-4.0.0a0/tests/unit/analysis/visualizations/test_windowed_metric_visualization.py +493 -0
  86. openstef_beam-4.0.0a0/tests/unit/backtesting/__init__.py +0 -0
  87. openstef_beam-4.0.0a0/tests/unit/backtesting/test_backtest_event_generator.py +220 -0
  88. openstef_beam-4.0.0a0/tests/unit/backtesting/test_backtest_pipeline.py +383 -0
  89. openstef_beam-4.0.0a0/tests/unit/backtesting/test_batch_prediction.py +212 -0
  90. openstef_beam-4.0.0a0/tests/unit/benchmarking/__init__.py +0 -0
  91. openstef_beam-4.0.0a0/tests/unit/benchmarking/storage/__init__.py +0 -0
  92. openstef_beam-4.0.0a0/tests/unit/benchmarking/storage/test_local_storage.py +300 -0
  93. openstef_beam-4.0.0a0/tests/unit/benchmarking/storage/test_s3_storage.py +287 -0
  94. openstef_beam-4.0.0a0/tests/unit/benchmarking/test_benchmark_pipeline.py +443 -0
  95. openstef_beam-4.0.0a0/tests/unit/benchmarking/test_target_provider.py +158 -0
  96. openstef_beam-4.0.0a0/tests/unit/evaluation/__init__.py +3 -0
  97. openstef_beam-4.0.0a0/tests/unit/evaluation/models/__init__.py +3 -0
  98. openstef_beam-4.0.0a0/tests/unit/evaluation/models/test_window.py +72 -0
  99. openstef_beam-4.0.0a0/tests/unit/evaluation/test_evaluation_pipeline.py +153 -0
  100. openstef_beam-4.0.0a0/tests/unit/evaluation/test_metric_provider.py +123 -0
  101. openstef_beam-4.0.0a0/tests/unit/evaluation/test_window_iterators.py +0 -0
  102. openstef_beam-4.0.0a0/tests/unit/metrics/__init__.py +0 -0
  103. openstef_beam-4.0.0a0/tests/unit/metrics/test_metrics_deterministic.py +548 -0
  104. openstef_beam-4.0.0a0/tests/unit/metrics/test_metrics_probabilistic.py +186 -0
  105. openstef_beam-4.0.0a0/tests/utils/__init__.py +0 -0
  106. openstef_beam-4.0.0a0/tests/utils/mocks.py +109 -0
@@ -0,0 +1,127 @@
1
+ # SPDX-FileCopyrightText: 2017-2025 Contributors to the OpenSTEF project <short.term.energy.forecasts@alliander.com> # 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
+
71
+ # docs/_doctrees/
72
+ # docs/_static_gen/
73
+
74
+ # Editors: JetBrains
75
+ .idea/
76
+
77
+ # Editors: VS Code (allow shared configs)
78
+ .vscode/*
79
+
80
+ # Jupyter / IPython
81
+ .ipynb_checkpoints
82
+ profile_default/
83
+ ipython_config.py
84
+
85
+ # Spyder / Rope
86
+ .spyderproject
87
+ .spyproject
88
+ .ropeproject
89
+
90
+ # Environments
91
+ .env
92
+ .venv
93
+ env/
94
+ venv/
95
+ ENV/
96
+ env.bak/
97
+ venv.bak/
98
+
99
+ # PEP 582
100
+ __pypackages__/
101
+
102
+ # web frameworks / services
103
+ *.sqlite
104
+ *.sqlite3
105
+ *.log
106
+ instance/
107
+ .webassets-cache
108
+ celerybeat-schedule
109
+ celerybeat.pid
110
+ *.sage.py
111
+ tmp/
112
+
113
+ # PyInstaller
114
+ *.manifest
115
+ *.spec
116
+
117
+ # Project outputs
118
+ output/
119
+ prof/
120
+ certificates/
121
+
122
+ # Output artifacts
123
+ *.html
124
+ *.pkl
125
+
126
+ # Benchmark outputs
127
+ benchmark_results*/
@@ -0,0 +1,34 @@
1
+ Metadata-Version: 2.4
2
+ Name: openstef-beam
3
+ Version: 4.0.0a0
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" <short.term.energy.forecasts@alliander.com>
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: s3fs>=2025.5.1; extra == 'all'
26
+ Description-Content-Type: text/markdown
27
+
28
+ <!--
29
+ SPDX-FileCopyrightText: 2025 Contributors to the OpenSTEF project <short.term.energy.forecasts@alliander.com>
30
+
31
+ SPDX-License-Identifier: MPL-2.0
32
+ -->
33
+
34
+ # openstef-beam
@@ -0,0 +1,7 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2025 Contributors to the OpenSTEF project <short.term.energy.forecasts@alliander.com>
3
+
4
+ SPDX-License-Identifier: MPL-2.0
5
+ -->
6
+
7
+ # openstef-beam
@@ -0,0 +1,50 @@
1
+ # SPDX-FileCopyrightText: 2025 Contributors to the OpenSTEF project <short.term.energy.forecasts@alliander.com>
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.0a0"
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 = "short.term.energy.forecasts@alliander.com" },
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
+ "s3fs>=2025.5.1",
40
+ ]
41
+ urls.Documentation = "https://openstef.github.io/openstef/index.html"
42
+ urls.Homepage = "https://lfenergy.org/projects/openstef/"
43
+ urls.Issues = "https://github.com/OpenSTEF/openstef/issues"
44
+ urls.Repository = "https://github.com/OpenSTEF/openstef"
45
+
46
+ [tool.hatch.build.targets.wheel]
47
+ packages = [ "src/openstef_beam" ]
48
+
49
+ [tool.uv.sources]
50
+ openstef-core = { workspace = true }
@@ -0,0 +1,42 @@
1
+ # SPDX-FileCopyrightText: 2017-2025 Contributors to the OpenSTEF project <short.term.energy.forecasts@alliander.com>
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
+ - Test models under realistic operational constraints
27
+ - Compare forecasting approaches with versioned data integrity
28
+ - Generate flexible reports tailored to your metrics and visualizations
29
+ - Ensure evaluation results match real-world deployment performance
30
+
31
+ BEAM's versioned data approach and flexible architecture make it the reliable choice
32
+ for energy forecasting model validation that translates to production success.
33
+ """
34
+
35
+ import logging
36
+
37
+ # Set up logging configuration
38
+ root_logger = logging.getLogger(name=__name__)
39
+ if not root_logger.handlers:
40
+ root_logger.addHandler(logging.NullHandler())
41
+
42
+ __all__ = []
@@ -0,0 +1,31 @@
1
+ # SPDX-FileCopyrightText: 2025 Contributors to the OpenSTEF project <short.term.energy.forecasts@alliander.com>
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
+ - Performance charts: See how models compare across different metrics
14
+ - Time series plots: Visualize predictions vs actual load over time
15
+ - Error analysis: Understand when and why models make mistakes
16
+ - Comparison reports: Side-by-side model performance analysis
17
+ """
18
+
19
+ from openstef_beam.analysis import plots, visualizations
20
+ from openstef_beam.analysis.analysis_pipeline import AnalysisConfig, AnalysisPipeline
21
+ from openstef_beam.analysis.models import AnalysisOutput, AnalysisScope, VisualizationOutput
22
+
23
+ __all__ = [
24
+ "AnalysisConfig",
25
+ "AnalysisOutput",
26
+ "AnalysisPipeline",
27
+ "AnalysisScope",
28
+ "VisualizationOutput",
29
+ "plots",
30
+ "visualizations",
31
+ ]
@@ -0,0 +1,187 @@
1
+ # SPDX-FileCopyrightText: 2025 Contributors to the OpenSTEF project <short.term.energy.forecasts@alliander.com>
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
+
39
+
40
+ class AnalysisPipeline:
41
+ """Orchestrates the generation of visualizations from evaluation reports.
42
+
43
+ The pipeline processes evaluation reports at different aggregation levels:
44
+ - Individual targets: Creates detailed visualizations for single targets
45
+ - Multiple targets: Creates comparative visualizations across target groups
46
+
47
+ It integrates with the benchmarking framework by being called from BenchmarkPipeline
48
+ after evaluation is complete, ensuring consistent visualization generation across
49
+ all benchmark runs.
50
+ """
51
+
52
+ def __init__(
53
+ self,
54
+ config: AnalysisConfig,
55
+ ) -> None:
56
+ """Initialize the analysis pipeline with configuration.
57
+
58
+ Args:
59
+ config: Analysis configuration containing visualization providers.
60
+ """
61
+ super().__init__()
62
+ self.config = config
63
+
64
+ @staticmethod
65
+ def _group_by_filtering(
66
+ reports: Sequence[tuple[TargetMetadata, EvaluationReport]],
67
+ ) -> dict[Filtering, list[ReportTuple]]:
68
+ """Group reports by their lead time filtering conditions.
69
+
70
+ Organizes evaluation reports based on their lead time criteria (e.g.,
71
+ 1-hour ahead vs 24-hour ahead forecasts), enabling comparison of model
72
+ performance across different forecasting horizons.
73
+
74
+ Returns:
75
+ Dictionary mapping lead time filtering conditions to lists of report tuples.
76
+ """
77
+ return groupby(
78
+ (subset.filtering, (base_metadata.with_filtering(subset.filtering), subset))
79
+ for base_metadata, report in reports
80
+ for subset in report.subset_reports
81
+ )
82
+
83
+ def run_for_subsets(
84
+ self,
85
+ reports: list[ReportTuple],
86
+ aggregation: AnalysisAggregation,
87
+ ) -> list[VisualizationOutput]:
88
+ """Generate visualizations for a set of evaluation reports at a specific aggregation level.
89
+
90
+ Processes the provided evaluation reports through all configured visualization
91
+ providers that support the requested aggregation level. Only providers that
92
+ declare support for the aggregation are used.
93
+
94
+ Args:
95
+ reports: List of (metadata, evaluation_subset_report) tuples to visualize.
96
+ The metadata provides context about the target and run, while the
97
+ evaluation report contains the metrics and predictions to visualize.
98
+ aggregation: The aggregation level determining how reports are grouped
99
+ and compared in visualizations.
100
+
101
+ Returns:
102
+ List of visualization outputs from all applicable providers. Empty if
103
+ no providers support the requested aggregation level.
104
+ """
105
+ return [
106
+ provider.create(
107
+ reports=reports,
108
+ aggregation=aggregation,
109
+ )
110
+ for provider in self.config.visualization_providers
111
+ if aggregation in provider.supported_aggregations
112
+ ]
113
+
114
+ def run_for_reports(
115
+ self,
116
+ reports: Sequence[tuple[TargetMetadata, EvaluationReport]],
117
+ scope: AnalysisScope,
118
+ ) -> AnalysisOutput:
119
+ """Generate visualizations for evaluation reports within a specific scope.
120
+
121
+ Groups reports by lead time filtering conditions and generates visualizations
122
+ for each group using all configured visualization providers that support the
123
+ scope's aggregation level. This enables comparing model performance across
124
+ different forecasting horizons (e.g., short-term vs long-term predictions).
125
+
126
+ Args:
127
+ reports: List of (metadata, evaluation_report) tuples to visualize.
128
+ scope: Analysis scope defining aggregation level and context.
129
+
130
+ Returns:
131
+ Analysis output containing all generated visualizations grouped by
132
+ lead time filtering conditions.
133
+ """
134
+ grouped = self._group_by_filtering(reports)
135
+
136
+ result: dict[Filtering, list[VisualizationOutput]] = defaultdict(list)
137
+ for filtering, subset_reports in grouped.items():
138
+ visualizations = self.run_for_subsets(
139
+ reports=subset_reports,
140
+ aggregation=scope.aggregation,
141
+ )
142
+ result[filtering].extend(visualizations)
143
+
144
+ return AnalysisOutput(
145
+ scope=scope,
146
+ visualizations=result,
147
+ )
148
+
149
+ def run_for_groups(
150
+ self,
151
+ reports: Sequence[tuple[TargetMetadata, EvaluationReport]],
152
+ scope: AnalysisScope,
153
+ ) -> dict[GroupName, AnalysisOutput]:
154
+ """Generate visualizations for multiple target groups at a specific aggregation level.
155
+
156
+ This method processes all evaluation reports, grouping them by their target
157
+ group names and generating visualizations for each group.
158
+
159
+ Args:
160
+ reports: List of (metadata, evaluation_report) tuples to visualize.
161
+ The metadata provides context about the target and run, while the
162
+ evaluation report contains the metrics and predictions to visualize.
163
+ scope: The analytics scope defining how reports are grouped and aggregated.
164
+
165
+ Returns:
166
+ Dictionary mapping group names to their corresponding AnalyticsOutput.
167
+ """
168
+ reports_by_group: dict[GroupName, list[tuple[TargetMetadata, EvaluationReport]]] = groupby(
169
+ (report[0].group_name, report) for report in reports
170
+ )
171
+
172
+ result: dict[GroupName, AnalysisOutput] = {}
173
+ for group_name, group_reports in reports_by_group.items():
174
+ if not group_reports:
175
+ continue
176
+
177
+ result[group_name] = self.run_for_reports(
178
+ reports=group_reports,
179
+ scope=AnalysisScope(
180
+ aggregation=scope.aggregation,
181
+ target_name=scope.target_name,
182
+ run_name=scope.run_name,
183
+ group_name=group_name,
184
+ ),
185
+ )
186
+
187
+ return result
@@ -0,0 +1,24 @@
1
+ # SPDX-FileCopyrightText: 2025 Contributors to the OpenSTEF project <short.term.energy.forecasts@alliander.com>
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 <short.term.energy.forecasts@alliander.com>
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"]
@@ -0,0 +1,60 @@
1
+ # SPDX-FileCopyrightText: 2025 Contributors to the OpenSTEF project <short.term.energy.forecasts@alliander.com>
2
+ #
3
+ # SPDX-License-Identifier: MPL-2.0
4
+
5
+ """Analysis aggregation models for visualization grouping.
6
+
7
+ This module defines aggregation levels and scopes that control how evaluation
8
+ reports are grouped and compared in visualizations.
9
+ """
10
+
11
+ from enum import StrEnum
12
+
13
+ from openstef_beam.analysis.models.target_metadata import GroupName, RunName, TargetName
14
+ from openstef_core.base_model import BaseConfig
15
+
16
+
17
+ class AnalysisAggregation(StrEnum):
18
+ """Defines the aggregation levels for visualizations.
19
+
20
+ Each aggregation level determines how evaluation reports are grouped and
21
+ compared in visualizations, enabling different analytical perspectives.
22
+
23
+ Members:
24
+ - NONE ("none"): Single run, single target - individual performance analysis
25
+ - TARGET ("target"): Single run, per target - cross-target comparison (e.g., RMAE per target)
26
+ - GROUP ("group"): Single run, multiple targets - cross-group comparison (e.g., RMAE per group)
27
+ - RUN ("run"): Multiple runs, per target - model comparison on same target for all targets
28
+ - RUN_AND_GROUP ("run_and_group"): Multiple runs, multiple targets - comparison matrix
29
+ """
30
+
31
+ NONE = "none"
32
+ TARGET = "target"
33
+ GROUP = "group"
34
+ RUN_AND_NONE = "run_and_none"
35
+ RUN_AND_TARGET = "run_and_target"
36
+ RUN_AND_GROUP = "run_and_group"
37
+
38
+
39
+ class AnalysisScope(BaseConfig):
40
+ """Defines the scope context for analysis operations.
41
+
42
+ Specifies which targets, groups, and runs are included in an analysis,
43
+ along with the aggregation level for grouping data.
44
+ """
45
+
46
+ target_name: TargetName | None = None
47
+ group_name: GroupName | None = None
48
+ run_name: RunName | None = None
49
+ aggregation: AnalysisAggregation
50
+
51
+ def __hash__(self) -> int:
52
+ """Enable using AnalysisScope as a dictionary key.
53
+
54
+ Returns:
55
+ Hash value based on all scope fields.
56
+ """
57
+ return hash((self.target_name, self.group_name, self.run_name, self.aggregation))
58
+
59
+
60
+ __all__ = ["AnalysisAggregation", "AnalysisScope"]