openstef-beam 4.0.0__py3-none-any.whl

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 (64) hide show
  1. openstef_beam/__init__.py +43 -0
  2. openstef_beam/analysis/__init__.py +32 -0
  3. openstef_beam/analysis/analysis_pipeline.py +194 -0
  4. openstef_beam/analysis/models/__init__.py +24 -0
  5. openstef_beam/analysis/models/target_metadata.py +97 -0
  6. openstef_beam/analysis/models/visualization_aggregation.py +61 -0
  7. openstef_beam/analysis/models/visualization_output.py +93 -0
  8. openstef_beam/analysis/plots/__init__.py +27 -0
  9. openstef_beam/analysis/plots/forecast_time_series_plotter.py +683 -0
  10. openstef_beam/analysis/plots/grouped_target_metric_plotter.py +173 -0
  11. openstef_beam/analysis/plots/precision_recall_curve_plotter.py +151 -0
  12. openstef_beam/analysis/plots/quantile_calibration_box_plotter.py +196 -0
  13. openstef_beam/analysis/plots/quantile_probability_plotter.py +153 -0
  14. openstef_beam/analysis/plots/summary_table_plotter.py +80 -0
  15. openstef_beam/analysis/plots/windowed_metric_plotter.py +141 -0
  16. openstef_beam/analysis/visualizations/__init__.py +33 -0
  17. openstef_beam/analysis/visualizations/base.py +250 -0
  18. openstef_beam/analysis/visualizations/grouped_target_metric_visualization.py +351 -0
  19. openstef_beam/analysis/visualizations/precision_recall_curve_visualization.py +195 -0
  20. openstef_beam/analysis/visualizations/quantile_calibration_box_visualization.py +240 -0
  21. openstef_beam/analysis/visualizations/quantile_probability_visualization.py +221 -0
  22. openstef_beam/analysis/visualizations/summary_table_visualization.py +311 -0
  23. openstef_beam/analysis/visualizations/timeseries_visualization.py +155 -0
  24. openstef_beam/analysis/visualizations/windowed_metric_visualization.py +429 -0
  25. openstef_beam/backtesting/__init__.py +23 -0
  26. openstef_beam/backtesting/backtest_callback.py +9 -0
  27. openstef_beam/backtesting/backtest_event.py +57 -0
  28. openstef_beam/backtesting/backtest_event_generator.py +207 -0
  29. openstef_beam/backtesting/backtest_forecaster/__init__.py +25 -0
  30. openstef_beam/backtesting/backtest_forecaster/dummy_forecaster.py +73 -0
  31. openstef_beam/backtesting/backtest_forecaster/mixins.py +236 -0
  32. openstef_beam/backtesting/backtest_pipeline.py +301 -0
  33. openstef_beam/backtesting/restricted_horizon_timeseries.py +72 -0
  34. openstef_beam/benchmarking/__init__.py +55 -0
  35. openstef_beam/benchmarking/baselines/__init__.py +17 -0
  36. openstef_beam/benchmarking/baselines/openstef4.py +243 -0
  37. openstef_beam/benchmarking/benchmark_comparison_pipeline.py +255 -0
  38. openstef_beam/benchmarking/benchmark_pipeline.py +420 -0
  39. openstef_beam/benchmarking/benchmarks/__init__.py +14 -0
  40. openstef_beam/benchmarking/benchmarks/liander2024.py +239 -0
  41. openstef_beam/benchmarking/callbacks/__init__.py +10 -0
  42. openstef_beam/benchmarking/callbacks/base.py +230 -0
  43. openstef_beam/benchmarking/callbacks/strict_execution_callback.py +39 -0
  44. openstef_beam/benchmarking/models/__init__.py +9 -0
  45. openstef_beam/benchmarking/models/benchmark_target.py +116 -0
  46. openstef_beam/benchmarking/storage/__init__.py +16 -0
  47. openstef_beam/benchmarking/storage/base.py +254 -0
  48. openstef_beam/benchmarking/storage/local_storage.py +163 -0
  49. openstef_beam/benchmarking/storage/s3_storage.py +193 -0
  50. openstef_beam/benchmarking/target_provider.py +412 -0
  51. openstef_beam/evaluation/__init__.py +43 -0
  52. openstef_beam/evaluation/evaluation_pipeline.py +316 -0
  53. openstef_beam/evaluation/metric_providers.py +753 -0
  54. openstef_beam/evaluation/models/__init__.py +21 -0
  55. openstef_beam/evaluation/models/report.py +150 -0
  56. openstef_beam/evaluation/models/subset.py +100 -0
  57. openstef_beam/evaluation/models/window.py +132 -0
  58. openstef_beam/evaluation/window_iterators.py +117 -0
  59. openstef_beam/metrics/__init__.py +58 -0
  60. openstef_beam/metrics/metrics_deterministic.py +625 -0
  61. openstef_beam/metrics/metrics_probabilistic.py +273 -0
  62. openstef_beam-4.0.0.dist-info/METADATA +39 -0
  63. openstef_beam-4.0.0.dist-info/RECORD +64 -0
  64. openstef_beam-4.0.0.dist-info/WHEEL +4 -0
@@ -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"]
@@ -0,0 +1,61 @@
1
+ # SPDX-FileCopyrightText: 2025 Contributors to the OpenSTEF project <openstef@lfenergy.org>
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
+
25
+ - NONE ("none"): Single run, single target - individual performance analysis
26
+ - TARGET ("target"): Single run, per target - cross-target comparison (e.g., RMAE per target)
27
+ - GROUP ("group"): Single run, multiple targets - cross-group comparison (e.g., RMAE per group)
28
+ - RUN ("run"): Multiple runs, per target - model comparison on same target for all targets
29
+ - RUN_AND_GROUP ("run_and_group"): Multiple runs, multiple targets - comparison matrix
30
+ """
31
+
32
+ NONE = "none"
33
+ TARGET = "target"
34
+ GROUP = "group"
35
+ RUN_AND_NONE = "run_and_none"
36
+ RUN_AND_TARGET = "run_and_target"
37
+ RUN_AND_GROUP = "run_and_group"
38
+
39
+
40
+ class AnalysisScope(BaseConfig):
41
+ """Defines the scope context for analysis operations.
42
+
43
+ Specifies which targets, groups, and runs are included in an analysis,
44
+ along with the aggregation level for grouping data.
45
+ """
46
+
47
+ target_name: TargetName | None = None
48
+ group_name: GroupName | None = None
49
+ run_name: RunName | None = None
50
+ aggregation: AnalysisAggregation
51
+
52
+ def __hash__(self) -> int:
53
+ """Enable using AnalysisScope as a dictionary key.
54
+
55
+ Returns:
56
+ Hash value based on all scope fields.
57
+ """
58
+ return hash((self.target_name, self.group_name, self.run_name, self.aggregation))
59
+
60
+
61
+ __all__ = ["AnalysisAggregation", "AnalysisScope"]
@@ -0,0 +1,93 @@
1
+ # SPDX-FileCopyrightText: 2025 Contributors to the OpenSTEF project <openstef@lfenergy.org>
2
+ #
3
+ # SPDX-License-Identifier: MPL-2.0
4
+
5
+ """Output models for analysis visualizations.
6
+
7
+ This module defines the output structures for generated visualizations,
8
+ including individual visualization outputs and aggregated analysis results.
9
+ """
10
+
11
+ from pathlib import Path
12
+ from typing import Any
13
+
14
+ import plotly.graph_objects as go
15
+
16
+ from openstef_beam.analysis.models.visualization_aggregation import AnalysisScope
17
+ from openstef_beam.evaluation.models import Filtering
18
+ from openstef_core.base_model import BaseModel
19
+
20
+
21
+ class VisualizationOutput:
22
+ """A generated visualization from evaluation data.
23
+
24
+ Represents a single chart, plot, or visual analysis that can be saved as HTML.
25
+ Contains either a Plotly figure object for interactive visualizations or raw
26
+ HTML content for static displays.
27
+
28
+ Example:
29
+ Creating a visualization from a Plotly figure
30
+
31
+ >>> import plotly.graph_objects as go
32
+ >>> fig = go.Figure(data=go.Scatter(x=[1, 2, 3], y=[4, 5, 6]))
33
+ >>> viz = VisualizationOutput("my_chart", figure=fig)
34
+ >>> viz.get_file_name()
35
+ 'my_chart.html'
36
+ """
37
+
38
+ def __init__(self, name: str, figure: go.Figure | None = None, html: str | None = None):
39
+ """Initialize visualization output with either a figure or HTML content.
40
+
41
+ Args:
42
+ name: Name identifier for the visualization.
43
+ figure: Plotly figure object, if available.
44
+ html: Raw HTML string content, if available.
45
+
46
+ Raises:
47
+ ValueError: If neither figure nor html is provided.
48
+ """
49
+ if figure is None and html is None:
50
+ raise ValueError("Either a plotly figure or an HTML string must be provided.")
51
+ self.name = name
52
+ self.figure = figure
53
+ self.html = html
54
+
55
+ def write_html(self, file_path: Path, **kwargs: dict[str, Any]) -> None:
56
+ """Write the plotly figure or HTML string to an HTML file.
57
+
58
+ Raises:
59
+ ValueError: If neither figure nor HTML content is available.
60
+ """
61
+ if self.figure is not None:
62
+ self.figure.write_html(file_path, include_plotlyjs="cdn", **kwargs) # type: ignore[reportUnknownMemberType]
63
+ elif self.html is not None:
64
+ file_path.write_text(self.html, encoding="utf-8")
65
+ else:
66
+ raise ValueError("No figure or HTML to write.")
67
+
68
+ def get_file_name(self) -> str:
69
+ """Return the file name for the visualization."""
70
+ return f"{self.name}.html"
71
+
72
+
73
+ class AnalysisOutput(BaseModel):
74
+ """Container for analysis results from the benchmarking pipeline.
75
+
76
+ Holds all visualizations generated for a specific analysis scope, organized
77
+ by lead time filtering conditions. This allows comparing model performance
78
+ across different forecasting horizons (e.g., 1-hour vs 24-hour ahead predictions).
79
+
80
+ The output structure enables systematic organization of results from benchmark
81
+ runs, making it easy to generate reports that compare multiple models across
82
+ various lead times and targets.
83
+
84
+ Attributes:
85
+ scope: Analysis context defining what was analyzed (targets, runs, aggregation)
86
+ visualizations: Generated charts and plots grouped by lead time filtering
87
+ """
88
+
89
+ scope: AnalysisScope
90
+ visualizations: dict[Filtering, list[VisualizationOutput]]
91
+
92
+
93
+ __all__ = ["AnalysisOutput", "VisualizationOutput"]
@@ -0,0 +1,27 @@
1
+ # SPDX-FileCopyrightText: 2025 Contributors to the OpenSTEF project <openstef@lfenergy.org>
2
+ #
3
+ # SPDX-License-Identifier: MPL-2.0
4
+
5
+ """Plotting components for generating visualizations from evaluation data.
6
+
7
+ This package provides specialized plotters for different types of analysis
8
+ visualizations, including time series, metrics, and statistical plots.
9
+ """
10
+
11
+ from .forecast_time_series_plotter import ForecastTimeSeriesPlotter
12
+ from .grouped_target_metric_plotter import GroupedTargetMetricPlotter
13
+ from .precision_recall_curve_plotter import PrecisionRecallCurvePlotter
14
+ from .quantile_calibration_box_plotter import QuantileCalibrationBoxPlotter
15
+ from .quantile_probability_plotter import QuantileProbabilityPlotter
16
+ from .summary_table_plotter import SummaryTablePlotter
17
+ from .windowed_metric_plotter import WindowedMetricPlotter
18
+
19
+ __all__ = [
20
+ "ForecastTimeSeriesPlotter",
21
+ "GroupedTargetMetricPlotter",
22
+ "PrecisionRecallCurvePlotter",
23
+ "QuantileCalibrationBoxPlotter",
24
+ "QuantileProbabilityPlotter",
25
+ "SummaryTablePlotter",
26
+ "WindowedMetricPlotter",
27
+ ]