From 40cb830ad7134c08238c513918396af0bd4cf41e Mon Sep 17 00:00:00 2001 From: Egor Dmitriev Date: Wed, 4 Mar 2026 16:14:55 +0100 Subject: [PATCH 1/8] refactor(STEF-2702): push polymorphism into models, eliminate isinstance protocols - SubsetMetric.to_flat_dict() for metrics self-serialization - ModelFitResult.metrics_to_flat_dict() with component_fit_results override - BaseForecastingModel: component_hyperparams, get_explainable_components() - ForecastCombiner extends ExplainableForecaster (gains plot_feature_importances) - EnsembleForecastingModel/EnsembleModelFitResult override all polymorphic methods - MLflow callback: delete 3 protocols, 5 isinstance checks -> polymorphic calls - Fix penalty bug: lower_is_better now multiplies (was dividing) --- .../openstef_beam/evaluation/models/subset.py | 17 ++ .../models/ensemble_forecasting_model.py | 98 ++++++---- .../forecast_combiners/forecast_combiner.py | 9 +- .../integrations/mlflow/__init__.py | 6 - .../mlflow/mlflow_storage_callback.py | 178 +++++------------- .../models/forecasting_model.py | 71 ++++++- 6 files changed, 192 insertions(+), 187 deletions(-) diff --git a/packages/openstef-beam/src/openstef_beam/evaluation/models/subset.py b/packages/openstef-beam/src/openstef_beam/evaluation/models/subset.py index 16a5e7f63..1731f3fcd 100644 --- a/packages/openstef-beam/src/openstef_beam/evaluation/models/subset.py +++ b/packages/openstef-beam/src/openstef_beam/evaluation/models/subset.py @@ -65,6 +65,23 @@ def get_metric(self, quantile: QuantileOrGlobal, metric_name: str) -> FloatOrNan """ return self.metrics.get(quantile, {}).get(metric_name) + def to_flat_dict(self, prefix: str = "") -> dict[str, float]: + """Flatten metrics into a single dict suitable for logging (e.g. MLflow). + + Each key is ``{prefix}{quantile}_{metric_name}``. + + Args: + prefix: String prepended to every key. + + Returns: + Flat mapping of metric names to values. + """ + return { + f"{prefix}{quantile}_{metric_name}": value + for quantile, metrics_dict in self.metrics.items() + for metric_name, value in metrics_dict.items() + } + def merge_quantile_metrics(metrics_list: list[QuantileMetricsDict]) -> QuantileMetricsDict: """Merge multiple quantile metrics dictionaries into a single one. diff --git a/packages/openstef-meta/src/openstef_meta/models/ensemble_forecasting_model.py b/packages/openstef-meta/src/openstef_meta/models/ensemble_forecasting_model.py index f107dd586..d0c6e655a 100644 --- a/packages/openstef-meta/src/openstef_meta/models/ensemble_forecasting_model.py +++ b/packages/openstef-meta/src/openstef_meta/models/ensemble_forecasting_model.py @@ -28,6 +28,7 @@ from openstef_core.mixins import HyperParams, TransformPipeline from openstef_core.types import LeadTime, Quantile from openstef_meta.models.forecast_combiners.forecast_combiner import ForecastCombiner +from openstef_models.explainability.mixins import ExplainableForecaster from openstef_models.models.forecasting.forecaster import Forecaster from openstef_models.models.forecasting_model import BaseForecastingModel, ModelFitResult, restore_target @@ -43,6 +44,18 @@ class EnsembleModelFitResult(ModelFitResult): forecaster_fit_results: dict[str, ModelFitResult] = Field(description="ModelFitResult for each base forecaster") + @override + def metrics_to_flat_dict(self) -> dict[str, float]: + result = super().metrics_to_flat_dict() + for name, child in self.forecaster_fit_results.items(): + result.update({f"{name}_{k}": v for k, v in child.metrics_to_flat_dict().items()}) + return result + + @property + @override + def component_fit_results(self) -> dict[str, ModelFitResult]: + return self.forecaster_fit_results + class EnsembleForecastingModel(BaseForecastingModel): """Ensemble forecasting pipeline: common preprocessing -> N forecasters -> combiner. @@ -124,8 +137,12 @@ def _validate_horizons_consistent(self) -> Self: Validated model instance. Raises: - ValueError: If any forecaster's horizons differ from the combiner's. + ValueError: If forecasters dict is empty or any forecaster's horizons differ from the combiner's. """ + if not self.forecasters: + msg = "At least one forecaster is required." + raise ValueError(msg) + expected = sorted(self.combiner.horizons) for name, forecaster in self.forecasters.items(): if sorted(forecaster.horizons) != expected: @@ -161,6 +178,23 @@ def hyperparams(self) -> HyperParams: def is_fitted(self) -> bool: return all(f.is_fitted for f in self.forecasters.values()) and self.combiner.is_fitted + @property + @override + def component_hyperparams(self) -> dict[str, HyperParams]: + return {name: f.hparams for name, f in self.forecasters.items()} + + @override + def get_explainable_components(self) -> dict[str, ExplainableForecaster]: + components: dict[str, ExplainableForecaster] = { + name: forecaster + for name, forecaster in self.forecasters.items() + if isinstance(forecaster, ExplainableForecaster) + } + # ForecastCombiner is always ExplainableForecaster, but skip if importances are empty + if not self.combiner.feature_importances.empty: + components["combiner"] = self.combiner + return components + @property def forecaster_names(self) -> list[str]: """Returns the names of the underlying forecasters.""" @@ -231,6 +265,8 @@ def _combine_datasets( ) def _transform_combiner_data(self, data: TimeSeriesDataset) -> ForecastInputDataset | None: + # Returns None when no combiner preprocessing is configured, signalling the combiner + # should work without additional features. if len(self.combiner_preprocessing.transforms) == 0: return None combiner_data = self.combiner_preprocessing.transform(data) @@ -242,7 +278,8 @@ def _fit_prepare_combiner_data( data_val: TimeSeriesDataset | None = None, data_test: TimeSeriesDataset | None = None, ) -> tuple[ForecastInputDataset | None, ForecastInputDataset | None, ForecastInputDataset | None]: - + # Fits combiner preprocessing on train data and transforms all splits. + # Returns (None, None, None) when no combiner preprocessing is configured. if len(self.combiner_preprocessing.transforms) == 0: return None, None, None self.combiner_preprocessing.fit(data=data) @@ -281,7 +318,8 @@ def _fit_forecasters( EnsembleForecastDataset | None, dict[str, ModelFitResult], ]: - + # Fits common + per-forecaster preprocessing, trains each forecaster, + # and bundles their in-sample predictions into EnsembleForecastDatasets. predictions_train: dict[str, ForecastDataset] = {} predictions_val: dict[str, ForecastDataset | None] = {} predictions_test: dict[str, ForecastDataset | None] = {} @@ -291,10 +329,8 @@ def _fit_forecasters( self.preprocessing.fit(data=data) data_transformed = self.preprocessing.transform(data=data) # Fit per-forecaster transforms on the common-preprocessed output (not raw data) - [ + for name in self.model_specific_preprocessing: self.model_specific_preprocessing[name].fit(data=data_transformed) - for name in self.model_specific_preprocessing - ] logger.debug("Completed fitting preprocessing pipelines.") # Fit the forecasters @@ -414,6 +450,7 @@ def _fit_forecaster( return prediction_train, prediction_val, prediction_test, result def _predict_forecaster(self, input_data: ForecastInputDataset, forecaster_name: str) -> ForecastDataset: + # Postprocessing is applied per-forecaster so the combiner sees final-scale predictions. logger.debug("Predicting forecaster '%s'.", forecaster_name) prediction_raw = self.forecasters[forecaster_name].predict(data=input_data) # Apply postprocessing per-forecaster so the combiner sees final-scale predictions @@ -445,7 +482,7 @@ def prepare_forecaster_input( """Prepare input data for a specific base forecaster. Applies common preprocessing, then model-specific preprocessing, restores - the target column, and trims history. + the target column, and trims history via the shared base ``prepare_input``. Args: data: Raw time series dataset. @@ -456,29 +493,32 @@ def prepare_forecaster_input( Processed forecast input dataset ready for the named forecaster. """ logger.debug("Preparing input data for forecaster '%s'.", forecaster_name) - input_data = self.preprocessing.transform(data=data) + # Apply model-specific preprocessing on top of the common pipeline if forecaster_name in self.model_specific_preprocessing: logger.debug("Applying model-specific preprocessing for forecaster '%s'.", forecaster_name) - input_data = self.model_specific_preprocessing[forecaster_name].transform(data=input_data) - input_data = restore_target(dataset=input_data, original_dataset=data, target_column=self.target_column) - - # Cut away input history to avoid training on incomplete data - input_data_start = cast("pd.Series[pd.Timestamp]", input_data.index).min().to_pydatetime() - input_data_cutoff = input_data_start + self.cutoff_history - if forecast_start is not None and forecast_start < input_data_cutoff: - input_data_cutoff = forecast_start - self._logger.warning( - "Forecast start %s is after input data start + cutoff history %s. Using forecast start as cutoff.", - forecast_start, - input_data_cutoff, + preprocessed = self.preprocessing.transform(data=data) + preprocessed = self.model_specific_preprocessing[forecaster_name].transform(data=preprocessed) + preprocessed = restore_target(dataset=preprocessed, original_dataset=data, target_column=self.target_column) + # Apply cutoff and create ForecastInputDataset (same logic as base prepare_input) + input_data_start = cast("pd.Series[pd.Timestamp]", preprocessed.index).min().to_pydatetime() + input_data_cutoff = input_data_start + self.cutoff_history + if forecast_start is not None and forecast_start < input_data_cutoff: + input_data_cutoff = forecast_start + self._logger.warning( + "Forecast start %s is before input data start + cutoff history %s. Using forecast start as cutoff.", + forecast_start, + input_data_cutoff, + ) + preprocessed = preprocessed.filter_by_range(start=input_data_cutoff) + + return ForecastInputDataset.from_timeseries( + dataset=preprocessed, + target_column=self.target_column, + forecast_start=forecast_start, ) - input_data = input_data.filter_by_range(start=input_data_cutoff) - return ForecastInputDataset.from_timeseries( - dataset=input_data, - target_column=self.target_column, - forecast_start=forecast_start, - ) + # No model-specific preprocessing — delegate entirely to shared base method + return self.prepare_input(data=data, forecast_start=forecast_start) def _predict_transform_combiner( self, ensemble_dataset: EnsembleForecastDataset, original_data: TimeSeriesDataset @@ -506,7 +546,6 @@ def _fit_combiner( val_ensemble_dataset: EnsembleForecastDataset | None = None, test_ensemble_dataset: EnsembleForecastDataset | None = None, ) -> ModelFitResult: - # Prepare additional features for the combiner (e.g. sample weights) — split separately from ensemble data features_train, features_val, features_test = self._fit_prepare_combiner_data( data=data, data_val=data_val, data_test=data_test @@ -556,11 +595,6 @@ def _predict_contributions_combiner( features = self._transform_combiner_data(data=original_data) return self.combiner.predict_contributions(ensemble_dataset, additional_features=features) - @override - def _predict(self, input_data: ForecastInputDataset) -> ForecastDataset: - msg = "EnsembleForecastingModel does not support single-input _predict; use predict() instead." - raise NotImplementedError(msg) - @override def predict(self, data: TimeSeriesDataset, forecast_start: datetime | None = None) -> ForecastDataset: """Generate forecasts for the provided dataset. diff --git a/packages/openstef-meta/src/openstef_meta/models/forecast_combiners/forecast_combiner.py b/packages/openstef-meta/src/openstef_meta/models/forecast_combiners/forecast_combiner.py index a2ca8fb5a..b3244088f 100644 --- a/packages/openstef-meta/src/openstef_meta/models/forecast_combiners/forecast_combiner.py +++ b/packages/openstef-meta/src/openstef_meta/models/forecast_combiners/forecast_combiner.py @@ -10,7 +10,6 @@ from abc import ABC, abstractmethod from typing import Self -import pandas as pd from pydantic import ConfigDict, Field from openstef_core.base_model import BaseConfig @@ -19,9 +18,10 @@ from openstef_core.mixins import Predictor from openstef_core.mixins.predictor import HyperParams from openstef_core.types import LeadTime, Quantile +from openstef_models.explainability.mixins import ExplainableForecaster -class ForecastCombiner(BaseConfig, Predictor[EnsembleForecastDataset, ForecastDataset], ABC): +class ForecastCombiner(BaseConfig, Predictor[EnsembleForecastDataset, ForecastDataset], ExplainableForecaster, ABC): """Combines base Forecaster predictions for each quantile into final predictions. Subclasses implement specific combination strategies (stacking, learned weights, @@ -127,8 +127,3 @@ def predict_contributions( Returns: TimeSeriesDataset where columns are features/models and rows are timesteps. """ - - @property - @abstractmethod - def feature_importances(self) -> pd.DataFrame: - """Aggregate feature importances from the combiner's internal models.""" diff --git a/packages/openstef-models/src/openstef_models/integrations/mlflow/__init__.py b/packages/openstef-models/src/openstef_models/integrations/mlflow/__init__.py index b95ebd898..3de518393 100644 --- a/packages/openstef-models/src/openstef_models/integrations/mlflow/__init__.py +++ b/packages/openstef-models/src/openstef_models/integrations/mlflow/__init__.py @@ -17,16 +17,10 @@ from .mlflow_storage import MLFlowStorage from .mlflow_storage_callback import ( - EnsembleFitResult, - EnsembleModel, - ExplainableEnsembleModel, MLFlowStorageCallback, ) __all__ = [ - "EnsembleFitResult", - "EnsembleModel", - "ExplainableEnsembleModel", "MLFlowStorage", "MLFlowStorageCallback", ] diff --git a/packages/openstef-models/src/openstef_models/integrations/mlflow/mlflow_storage_callback.py b/packages/openstef-models/src/openstef_models/integrations/mlflow/mlflow_storage_callback.py index 6eefe3ae2..1601390d0 100644 --- a/packages/openstef-models/src/openstef_models/integrations/mlflow/mlflow_storage_callback.py +++ b/packages/openstef-models/src/openstef_models/integrations/mlflow/mlflow_storage_callback.py @@ -5,25 +5,29 @@ """MLflow integration for tracking and storing forecasting workflows. Provides a single callback for logging model training runs, artifacts, -and metrics to MLflow. Supports both single-model (ForecastingModel) and -ensemble (EnsembleForecastingModel) workflows via protocol-based dispatch. - -Ensemble-specific behavior is enabled automatically when the model satisfies -the ``EnsembleModel`` and ``ExplainableEnsembleModel`` protocols, and when the -fit result satisfies ``EnsembleFitResult``: - -- Logs combiner hyperparameters as the primary hyperparams -- Logs per-forecaster hyperparameters with name-prefixed keys -- Stores per-forecaster training data as separate artifacts -- Logs per-forecaster evaluation metrics with name-prefixed keys -- Stores feature importance plots for each explainable forecaster component -- Stores combiner feature importance plots +and metrics to MLflow. The callback is model-agnostic — it delegates to +polymorphic methods on ``BaseForecastingModel`` and ``ModelFitResult`` so +it works unchanged for both single-model and ensemble workflows. + +Key behaviours: + +- Logs model hyperparameters, plus per-component hyperparameters via + ``model.component_hyperparams`` (e.g. per-forecaster in an ensemble). +- Stores training data, plus per-component datasets via + ``result.component_fit_results``. +- Collects evaluation metrics via ``result.metrics_to_flat_dict()``; + subclasses embed child metrics automatically. +- Stores feature-importance plots for every explainable component + exposed by ``model.get_explainable_components()``. +- Supports model reuse (skip re-fit if a recent run exists) and + model selection (keep the better model based on a configurable metric + with a bias-towards-newer penalty). """ import logging from datetime import UTC, datetime, timedelta from pathlib import Path -from typing import Any, Protocol, cast, override, runtime_checkable +from typing import Any, cast, override from mlflow.entities import Run from pydantic import Field, PrivateAttr @@ -41,52 +45,20 @@ SkipFitting, ) from openstef_core.types import Q, QuantileOrGlobal -from openstef_models.explainability import ExplainableForecaster from openstef_models.integrations.mlflow.mlflow_storage import MLFlowStorage from openstef_models.mixins.callbacks import WorkflowContext -from openstef_models.models.forecasting.forecaster import Forecaster -from openstef_models.models.forecasting_model import BaseForecastingModel, ForecastingModel, ModelFitResult +from openstef_models.models.forecasting_model import BaseForecastingModel, ModelFitResult from openstef_models.workflows.custom_forecasting_workflow import ( CustomForecastingWorkflow, ForecastingCallback, ) -@runtime_checkable -class EnsembleModel(Protocol): - """Protocol for ensemble models with multiple base forecasters.""" - - @property - def forecasters(self) -> dict[str, Forecaster]: - """Return a dictionary of forecasters keyed by name.""" - ... - - -@runtime_checkable -class ExplainableEnsembleModel(Protocol): - """Protocol for ensemble models with an explainable forecast combiner.""" - - @property - def combiner(self) -> ExplainableForecaster: - """Return the explainable forecast combiner.""" - ... - - -@runtime_checkable -class EnsembleFitResult(Protocol): - """Protocol for fit results that contain per-forecaster results.""" - - @property - def forecaster_fit_results(self) -> dict[str, ModelFitResult]: - """Return per-forecaster fit results.""" - ... - - class MLFlowStorageCallback(BaseConfig, ForecastingCallback): """MLFlow callback for logging forecasting workflow events. - Handles both single-model and ensemble workflows via protocol-based - dispatch. + Model-agnostic: delegates to polymorphic methods on the model and fit result + for child hyperparams, child data, metrics, and feature importances. """ storage: MLFlowStorage = Field(default_factory=MLFlowStorage) @@ -161,19 +133,23 @@ def on_fit_end( run_id: str = run.info.run_id self._logger.info("Created MLflow run %s for model %s", run_id, context.workflow.model_id) - # Log per-forecaster hyperparams for ensemble models - if isinstance(context.workflow.model, EnsembleModel): - self._log_forecaster_hyperparams(context.workflow.model, run_id) + # Log per-component hyperparams (e.g. per-forecaster in an ensemble) + for name, hparams in context.workflow.model.component_hyperparams.items(): + prefixed = {f"{name}.{k}": str(v) for k, v in hparams.model_dump().items()} + self.storage.log_hyperparams(run_id=run_id, params=prefixed) - # Store the model input and per-forecaster data + # Store the model input data run_path = self.storage.get_artifacts_path(model_id=context.workflow.model_id, run_id=run_id) data_path = run_path / self.storage.data_path data_path.mkdir(parents=True, exist_ok=True) result.input_dataset.to_parquet(path=data_path / "data.parquet") self._logger.info("Stored training data at %s for run %s", data_path, run_id) - if isinstance(result, EnsembleFitResult): - self._store_forecaster_data(result.forecaster_fit_results, data_path) + # Store per-component training data (e.g. per-forecaster in an ensemble) + for name, component_result in result.component_fit_results.items(): + component_path = data_path / name + component_path.mkdir(parents=True, exist_ok=True) + component_result.input_dataset.to_parquet(path=component_path / "data.parquet") # Store feature importance plots if self.store_feature_importance_plot: @@ -187,8 +163,8 @@ def on_fit_end( ) self._logger.info("Stored trained model for run %s", run_id) - # Format the metrics for MLflow - metrics = self._collect_metrics(result) + # Format the metrics for MLflow (includes child metrics via polymorphism) + metrics = result.metrics_to_flat_dict() # Mark the run as finished self.storage.finalize_run(model_id=context.workflow.model_id, run_id=run_id, metrics=metrics) @@ -267,65 +243,14 @@ def _run_model_selection(self, workflow: CustomForecastingWorkflow, result: Mode ) raise SkipFitting("New model did not improve monitored metric, skipping re-fit.") - def _log_forecaster_hyperparams(self, model: EnsembleModel, run_id: str) -> None: - """Log per-forecaster hyperparameters to the run.""" - for name, forecaster in model.forecasters.items(): - prefixed_params = {f"{name}.{k}": str(v) for k, v in forecaster.hparams.model_dump().items()} - self.storage.log_hyperparams(run_id=run_id, params=prefixed_params) - self._logger.debug("Logged hyperparams for forecaster '%s' in run %s", name, run_id) - - def _store_forecaster_data(self, forecaster_fit_results: dict[str, ModelFitResult], data_path: Path) -> None: - """Store per-forecaster training data as separate parquet files.""" - for name, forecaster_result in forecaster_fit_results.items(): - forecaster_data_path = data_path / name - forecaster_data_path.mkdir(parents=True, exist_ok=True) - forecaster_result.input_dataset.to_parquet(path=forecaster_data_path / "data.parquet") - self._logger.debug("Stored training data for forecaster '%s' at %s", name, forecaster_data_path) - - def _collect_metrics(self, result: ModelFitResult) -> dict[str, float]: - """Collect all metrics from the fit result, including per-forecaster metrics for ensembles. - - Returns: - Flat dictionary mapping metric names to values, including per-forecaster prefixed metrics. - """ - metrics = self.metrics_to_dict(metrics=result.metrics_full, prefix="full_") - metrics.update(self.metrics_to_dict(metrics=result.metrics_train, prefix="train_")) - if result.metrics_val is not None: - metrics.update(self.metrics_to_dict(metrics=result.metrics_val, prefix="val_")) - if result.metrics_test is not None: - metrics.update(self.metrics_to_dict(metrics=result.metrics_test, prefix="test_")) - - if isinstance(result, EnsembleFitResult): - for name, forecaster_result in result.forecaster_fit_results.items(): - metrics.update(self.metrics_to_dict(metrics=forecaster_result.metrics_full, prefix=f"{name}_full_")) - metrics.update(self.metrics_to_dict(metrics=forecaster_result.metrics_train, prefix=f"{name}_train_")) - if forecaster_result.metrics_val is not None: - metrics.update(self.metrics_to_dict(metrics=forecaster_result.metrics_val, prefix=f"{name}_val_")) - if forecaster_result.metrics_test is not None: - metrics.update(self.metrics_to_dict(metrics=forecaster_result.metrics_test, prefix=f"{name}_test_")) - - return metrics - @staticmethod def _store_feature_importances(model: BaseForecastingModel, data_path: Path) -> None: - """Store feature importance plots for all explainable components of the model.""" - if isinstance(model, EnsembleModel): - # Ensemble model: store per-forecaster feature importances - for name, forecaster in model.forecasters.items(): - if isinstance(forecaster, ExplainableForecaster): - fig = forecaster.plot_feature_importances() - fig.write_html(data_path / f"feature_importances_{name}.html") # pyright: ignore[reportUnknownMemberType] - elif isinstance(model, ForecastingModel) and isinstance(model.forecaster, ExplainableForecaster): - # Single model: store feature importance - fig = model.forecaster.plot_feature_importances() - fig.write_html(data_path / "feature_importances.html") # pyright: ignore[reportUnknownMemberType] - - # Store combiner feature importances (if model has an explainable combiner) - if isinstance(model, ExplainableEnsembleModel): - combiner_fi = model.combiner.feature_importances - if not combiner_fi.empty: - fig = model.combiner.plot_feature_importances() - fig.write_html(data_path / "feature_importances_combiner.html") # pyright: ignore[reportUnknownMemberType] + for name, component in model.get_explainable_components().items(): + if component.feature_importances.empty: + continue + suffix = f"_{name}" if name else "" + fig = component.plot_feature_importances() + fig.write_html(data_path / f"feature_importances{suffix}.html") # pyright: ignore[reportUnknownMemberType] def _find_run(self, model_id: str, run_name: str | None) -> Run | None: """Find an MLflow run by model_id and optional run_name. @@ -441,35 +366,18 @@ def _check_is_new_model_better( quantile, ) + # Penalty biases selection towards newer models: + # higher_is_better: lower the bar by dividing old metric by penalty + # lower_is_better: raise the bar by multiplying old metric by penalty match direction: case "higher_is_better" if new_metric >= old_metric / self.model_selection_old_model_penalty: return True - case "lower_is_better" if new_metric <= old_metric / self.model_selection_old_model_penalty: + case "lower_is_better" if new_metric <= old_metric * self.model_selection_old_model_penalty: return True case _: return False - @staticmethod - def metrics_to_dict(metrics: SubsetMetric, prefix: str) -> dict[str, float]: - """Convert SubsetMetric to a flat dictionary for MLflow logging. - - Args: - metrics: The metrics to convert. - prefix: Prefix to add to each metric key (e.g. "full_", "train_"). - - Returns: - Flat dictionary mapping metric names to values. - """ - return { - f"{prefix}{quantile}_{metric_name}": value - for quantile, metrics_dict in metrics.metrics.items() - for metric_name, value in metrics_dict.items() - } - __all__ = [ - "EnsembleFitResult", - "EnsembleModel", - "ExplainableEnsembleModel", "MLFlowStorageCallback", ] diff --git a/packages/openstef-models/src/openstef_models/models/forecasting_model.py b/packages/openstef-models/src/openstef_models/models/forecasting_model.py index 7ca93fdb9..2aa922032 100644 --- a/packages/openstef-models/src/openstef_models/models/forecasting_model.py +++ b/packages/openstef-models/src/openstef_models/models/forecasting_model.py @@ -31,7 +31,7 @@ from openstef_core.exceptions import InsufficientlyCompleteError, NotFittedError from openstef_core.mixins import HyperParams, Predictor, TransformPipeline from openstef_core.types import LeadTime, Quantile -from openstef_models.explainability.mixins import ContributionsMixin +from openstef_models.explainability.mixins import ContributionsMixin, ExplainableForecaster from openstef_models.models.forecasting.forecaster import Forecaster from openstef_models.utils.data_split import DataSplitter @@ -62,14 +62,43 @@ class ModelFitResult(BaseModel): ) metrics_full: SubsetMetric = Field(description="Evaluation metrics computed on the full original dataset.") + def metrics_to_flat_dict(self) -> dict[str, float]: + """Flatten all split metrics into a single dict for logging. + + Keys are prefixed with ``full_``, ``train_``, ``val_``, ``test_`` respectively. + Subclasses with child results (e.g. per-forecaster) should override to include + them. + + Returns: + Flat mapping of metric names to values. + """ + result = self.metrics_full.to_flat_dict(prefix="full_") + result.update(self.metrics_train.to_flat_dict(prefix="train_")) + if self.metrics_val is not None: + result.update(self.metrics_val.to_flat_dict(prefix="val_")) + if self.metrics_test is not None: + result.update(self.metrics_test.to_flat_dict(prefix="test_")) + return result + + @property + def component_fit_results(self) -> dict[str, "ModelFitResult"]: + """Per-component fit results (e.g. per-forecaster in an ensemble). + + Returns: + Empty dict by default; ensemble subclasses override. + """ + return {} + class BaseForecastingModel(BaseModel, Predictor[TimeSeriesDataset, ForecastDataset]): """Abstract base for forecasting models (single-forecaster and ensemble). Provides the shared pipeline skeleton: preprocessing -> predict -> postprocessing, data preparation, scoring, and evaluation. Concrete subclasses must implement the - abstract hooks ``fit``, ``_predict``, ``is_fitted``, ``quantiles``, and - ``max_horizon``. + abstract hooks ``fit``, ``is_fitted``, ``quantiles``, and ``max_horizon``. + Subclasses following the single-input template method pattern should also override + ``_predict``; those with a different predict flow (e.g. ensemble) can override + ``predict()`` directly. Important: The ``cutoff_history`` parameter is crucial when using lag-based features in @@ -139,6 +168,26 @@ def hyperparams(self) -> HyperParams: """ return HyperParams() + @property + def component_hyperparams(self) -> dict[str, HyperParams]: + """Per-component hyperparameters (e.g. per-forecaster in an ensemble). + + Returns: + Empty dict by default; ensemble subclasses override. + """ + return {} + + def get_explainable_components(self) -> dict[str, ExplainableForecaster]: # noqa: PLR6301 + """Return named components that support feature-importance plotting. + + Keys are used as filename suffixes; an empty key means no suffix. + Override in subclasses to expose forecasters and/or combiners. + + Returns: + Empty dict by default. + """ + return {} + @abstractmethod @override def fit( @@ -158,13 +207,15 @@ def fit( Result containing training details and metrics. """ - @abstractmethod def _predict(self, input_data: ForecastInputDataset) -> ForecastDataset: """Generate raw predictions from preprocessed input data. - Subclasses implement the actual prediction logic (single-forecaster - delegation or ensemble aggregation). + Subclasses that follow the single-input template method pattern implement this. + Subclasses that require a different predict flow (e.g. ensemble) should override + ``predict()`` directly and may leave this unimplemented. """ + msg = f"{type(self).__name__} does not implement _predict; override predict() instead." + raise NotImplementedError(msg) def prepare_input( self, @@ -193,7 +244,7 @@ def prepare_input( if forecast_start is not None and forecast_start < input_data_cutoff: input_data_cutoff = forecast_start self._logger.warning( - "Forecast start %s is after input data start + cutoff history %s. Using forecast start as cutoff.", + "Forecast start %s is before input data start + cutoff history %s. Using forecast start as cutoff.", forecast_start, input_data_cutoff, ) @@ -365,6 +416,12 @@ def hyperparams(self) -> HyperParams: def is_fitted(self) -> bool: return self.forecaster.is_fitted + @override + def get_explainable_components(self) -> dict[str, ExplainableForecaster]: + if isinstance(self.forecaster, ExplainableForecaster): + return {"": self.forecaster} + return {} + @override def fit( self, From e3a58ed1f9553c03e8561586daa23839ef717dfe Mon Sep 17 00:00:00 2001 From: Egor Dmitriev Date: Thu, 5 Mar 2026 09:46:03 +0100 Subject: [PATCH 2/8] refactor(STEF-2702): extract normalize_to_unit_sum, dedup feature importances - Add normalize_to_unit_sum() pipe-compatible utility in openstef_core.utils.pandas - Replace duplicated normalization logic in 4 forecasters (xgboost, gblinear, lgbm, lgbmlinear) - All now use weights_df.pipe(normalize_to_unit_sum) --- .../src/openstef_core/utils/pandas.py | 15 +++++++++++++++ .../models/forecasting/gblinear_forecaster.py | 6 ++---- .../models/forecasting/lgbm_forecaster.py | 6 ++---- .../models/forecasting/lgbmlinear_forecaster.py | 6 ++---- .../models/forecasting/xgboost_forecaster.py | 6 ++---- 5 files changed, 23 insertions(+), 16 deletions(-) diff --git a/packages/openstef-core/src/openstef_core/utils/pandas.py b/packages/openstef-core/src/openstef_core/utils/pandas.py index f714962e7..9466c353e 100644 --- a/packages/openstef-core/src/openstef_core/utils/pandas.py +++ b/packages/openstef-core/src/openstef_core/utils/pandas.py @@ -46,6 +46,21 @@ def unsafe_sorted_range_slice_idxs( return int(start_idx), int(end_idx) +def normalize_to_unit_sum(df: pd.DataFrame) -> pd.DataFrame: + """Normalize each column so absolute values sum to 1.0. + + Pipe-compatible: ``df.pipe(normalize_to_unit_sum)``. + + Columns that sum to zero are left as zeros (no NaN). + + Returns: + DataFrame with the same shape, each column normalized to unit sum. + """ + abs_values = df.abs() + totals = abs_values.sum(axis=0).replace(to_replace=0, value=1.0) # pyright: ignore[reportUnknownMemberType] + return abs_values / totals + + def combine_timeseries_indexes(indexes: Sequence[pd.DatetimeIndex]) -> pd.DatetimeIndex: """Combine multiple datetime indexes into a single sorted index. diff --git a/packages/openstef-models/src/openstef_models/models/forecasting/gblinear_forecaster.py b/packages/openstef-models/src/openstef_models/models/forecasting/gblinear_forecaster.py index de18234e7..b181b7bd5 100644 --- a/packages/openstef-models/src/openstef_models/models/forecasting/gblinear_forecaster.py +++ b/packages/openstef-models/src/openstef_models/models/forecasting/gblinear_forecaster.py @@ -23,6 +23,7 @@ from openstef_core.datasets.validated_datasets import ForecastDataset, ForecastInputDataset from openstef_core.exceptions import InputValidationError, MissingExtraError, NotFittedError from openstef_core.mixins.predictor import HyperParams +from openstef_core.utils.pandas import normalize_to_unit_sum from openstef_models.explainability.mixins import ContributionsMixin, ExplainableForecaster from openstef_models.models.forecasting.forecaster import Forecaster from openstef_models.utils.evaluation_functions import EvaluationFunctionType, get_evaluation_function @@ -344,7 +345,4 @@ def feature_importances(self) -> pd.DataFrame: weights_df.index.name = "feature_name" weights_df.columns.name = "quantiles" - weights_abs = weights_df.abs() - total = weights_abs.sum(axis=0).replace(to_replace=0, value=1.0) # pyright: ignore[reportUnknownMemberType] - - return weights_abs / total + return weights_df.pipe(normalize_to_unit_sum) diff --git a/packages/openstef-models/src/openstef_models/models/forecasting/lgbm_forecaster.py b/packages/openstef-models/src/openstef_models/models/forecasting/lgbm_forecaster.py index 4c5aea656..ffba97309 100644 --- a/packages/openstef-models/src/openstef_models/models/forecasting/lgbm_forecaster.py +++ b/packages/openstef-models/src/openstef_models/models/forecasting/lgbm_forecaster.py @@ -21,6 +21,7 @@ NotFittedError, ) from openstef_core.mixins import HyperParams +from openstef_core.utils.pandas import normalize_to_unit_sum from openstef_models.explainability.mixins import ContributionsMixin, ExplainableForecaster from openstef_models.models.forecasting.forecaster import Forecaster from openstef_models.utils.multi_quantile_regressor import MultiQuantileRegressor @@ -334,10 +335,7 @@ def feature_importances(self) -> pd.DataFrame: weights_df.index.name = "feature_name" weights_df.columns.name = "quantiles" - weights_abs = weights_df.abs() - total = weights_abs.sum(axis=0).replace(to_replace=0, value=1.0) # pyright: ignore[reportUnknownMemberType] - - return weights_abs / total + return weights_df.pipe(normalize_to_unit_sum) __all__ = ["LGBMForecaster", "LGBMHyperParams"] diff --git a/packages/openstef-models/src/openstef_models/models/forecasting/lgbmlinear_forecaster.py b/packages/openstef-models/src/openstef_models/models/forecasting/lgbmlinear_forecaster.py index 204cabd3f..a315e702b 100644 --- a/packages/openstef-models/src/openstef_models/models/forecasting/lgbmlinear_forecaster.py +++ b/packages/openstef-models/src/openstef_models/models/forecasting/lgbmlinear_forecaster.py @@ -20,6 +20,7 @@ NotFittedError, ) from openstef_core.mixins import HyperParams +from openstef_core.utils.pandas import normalize_to_unit_sum from openstef_models.explainability.mixins import ContributionsMixin, ExplainableForecaster from openstef_models.models.forecasting.forecaster import Forecaster from openstef_models.utils.multi_quantile_regressor import MultiQuantileRegressor @@ -336,10 +337,7 @@ def feature_importances(self) -> pd.DataFrame: weights_df.index.name = "feature_name" weights_df.columns.name = "quantiles" - weights_abs = weights_df.abs() - total = weights_abs.sum(axis=0).replace(to_replace=0, value=1.0) # pyright: ignore[reportUnknownMemberType] - - return weights_abs / total + return weights_df.pipe(normalize_to_unit_sum) __all__ = ["LGBMLinearForecaster", "LGBMLinearHyperParams"] diff --git a/packages/openstef-models/src/openstef_models/models/forecasting/xgboost_forecaster.py b/packages/openstef-models/src/openstef_models/models/forecasting/xgboost_forecaster.py index 40b43101f..87a956378 100644 --- a/packages/openstef-models/src/openstef_models/models/forecasting/xgboost_forecaster.py +++ b/packages/openstef-models/src/openstef_models/models/forecasting/xgboost_forecaster.py @@ -19,6 +19,7 @@ from openstef_core.datasets import ForecastDataset, ForecastInputDataset, TimeSeriesDataset from openstef_core.exceptions import MissingExtraError, NotFittedError from openstef_core.mixins import HyperParams +from openstef_core.utils.pandas import normalize_to_unit_sum from openstef_models.explainability.mixins import ContributionsMixin, ExplainableForecaster from openstef_models.models.forecasting.forecaster import Forecaster from openstef_models.utils.evaluation_functions import EvaluationFunctionType, get_evaluation_function @@ -417,10 +418,7 @@ def feature_importances(self) -> pd.DataFrame: weights_df.index.name = "feature_name" weights_df.columns.name = "quantiles" - weights_abs = weights_df.abs() - total = weights_abs.sum(axis=0).replace(to_replace=0, value=1.0) # pyright: ignore[reportUnknownMemberType] - - return weights_abs / total + return weights_df.pipe(normalize_to_unit_sum) __all__ = ["XGBoostForecaster", "XGBoostHyperParams"] From ca36fa7a3a87410ee5fdda07fd1737c702aea70d Mon Sep 17 00:00:00 2001 From: Egor Dmitriev Date: Wed, 4 Mar 2026 12:01:18 +0100 Subject: [PATCH 3/8] fix: make AvailableAt.__str__() Windows-safe by removing colon Change DnTHH:MM format to DnTHHMM (e.g. D-1T0600 instead of D-1T06:00). Colons are illegal in Windows file paths, breaking benchmark output directories. from_string() now accepts both formats for backward compatibility. --- .../openstef-core/src/openstef_core/types.py | 21 ++++++++++--------- .../openstef-core/tests/unit/test_types.py | 4 ++-- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/packages/openstef-core/src/openstef_core/types.py b/packages/openstef-core/src/openstef_core/types.py index 6c989c84d..fa01686e6 100644 --- a/packages/openstef-core/src/openstef_core/types.py +++ b/packages/openstef-core/src/openstef_core/types.py @@ -119,11 +119,12 @@ def to_hours(self) -> float: class AvailableAt(PydanticStringPrimitive): """Represents a time point available relative to a reference day. - Uses a specialized string format 'DnTHH:MM' where: + Uses a specialized string format 'DnTHHMM' where: - n is the day offset (negative indicates prior days) - - HH:MM is the time of day + - HHMM is the time of day - For example, 'D-1T06:00' means "6:00 AM on the previous day". + For example, 'D-1T0600' means "6:00 AM on the previous day". + The legacy 'DnTHH:MM' format (with colon) is also accepted by from_string(). Example: Creating and using availability times: @@ -132,7 +133,7 @@ class AvailableAt(PydanticStringPrimitive): >>> # Available at 6 AM on the previous day >>> at = AvailableAt(timedelta(hours=18)) # 18 hours before day end >>> str(at) - 'D-1T06:00' + 'D-1T0600' >>> # Available at midnight of the current day >>> AvailableAt.from_string('D0T00:00').lag_from_day datetime.timedelta(0) @@ -143,21 +144,21 @@ def __init__(self, lag_from_day: timedelta): self.lag_from_day = lag_from_day def __str__(self) -> str: - """Converts to string in 'DnTHH:MM' format. + """Converts to string in 'DnTHHMM' format (Windows-safe, no colon). Returns: - String representation in 'DnTHH:MM' format. + String representation in 'DnTHHMM' format. """ lag_days = -int(self.lag_from_day / timedelta(days=1)) - 1 time = timedelta(hours=24) - (self.lag_from_day % timedelta(days=1)) - return f"D{lag_days}T{time.seconds // 3600:02}:{(time.seconds // 60) % 60:02}" + return f"D{lag_days}T{time.seconds // 3600:02}{(time.seconds // 60) % 60:02}" @classmethod def from_string(cls, s: str) -> Self: - """Creates an instance from a string in 'DnTHH:MM' format. + """Creates an instance from a string in 'DnTHHMM' or 'DnTHH:MM' format. Args: - s: String in 'DnTHH:MM' format to parse. + s: String in 'DnTHHMM' or 'DnTHH:MM' format to parse. Returns: AvailableAt instance parsed from the string. @@ -165,7 +166,7 @@ def from_string(cls, s: str) -> Self: Raises: ValueError: If the string format is invalid. """ - match = re.match(r"D(-?\d+)T(\d{2}):(\d{2})", s) + match = re.match(r"D(-?\d+)T(\d{2}):?(\d{2})", s) if not match: error_message = f"Cannot convert {s} to {cls.__name__}" raise ValueError(error_message) diff --git a/packages/openstef-core/tests/unit/test_types.py b/packages/openstef-core/tests/unit/test_types.py index b1f3af49f..0cbb40077 100644 --- a/packages/openstef-core/tests/unit/test_types.py +++ b/packages/openstef-core/tests/unit/test_types.py @@ -48,8 +48,8 @@ def test_lead_time_from_string_roundtrip(input_delta: timedelta): @pytest.mark.parametrize( ("lag_from_day", "expected_string"), [ - pytest.param(timedelta(hours=18), "D-1T06:00", id="D-1T06:00"), - pytest.param(timedelta(hours=12 + 24), "D-2T12:00", id="D-2T12:00"), + pytest.param(timedelta(hours=18), "D-1T0600", id="D-1T0600"), + pytest.param(timedelta(hours=12 + 24), "D-2T1200", id="D-2T1200"), ], ) def test_available_at_str(lag_from_day: timedelta, expected_string: str): From a59fd14f376719024ec7dc0e1191085b00bcccdc Mon Sep 17 00:00:00 2001 From: Egor Dmitriev Date: Thu, 5 Mar 2026 12:10:58 +0100 Subject: [PATCH 4/8] feat(STEF-2702): make openstef-models/meta optional for beam baselines --- ...liander_2024_benchmark_xgboost_gblinear.py | 2 +- examples/benchmarks/liander_2024_ensemble.py | 2 +- packages/openstef-beam/pyproject.toml | 7 + .../benchmarking/baselines/__init__.py | 21 +- .../benchmarking/baselines/openstef4.py | 92 +-- .../workflows/callbacks/__init__.py | 9 + .../workflows/callbacks/data_save.py | 108 ++++ uv.lock | 577 +++++++++--------- 8 files changed, 477 insertions(+), 341 deletions(-) create mode 100644 packages/openstef-models/src/openstef_models/workflows/callbacks/__init__.py create mode 100644 packages/openstef-models/src/openstef_models/workflows/callbacks/data_save.py diff --git a/examples/benchmarks/liander_2024_benchmark_xgboost_gblinear.py b/examples/benchmarks/liander_2024_benchmark_xgboost_gblinear.py index 7ce3e33e1..9ff296c5d 100644 --- a/examples/benchmarks/liander_2024_benchmark_xgboost_gblinear.py +++ b/examples/benchmarks/liander_2024_benchmark_xgboost_gblinear.py @@ -20,7 +20,7 @@ import multiprocessing from pathlib import Path -from openstef_beam.benchmarking.baselines import ( +from openstef_beam.benchmarking.baselines.openstef4 import ( create_openstef4_preset_backtest_forecaster, ) from openstef_beam.benchmarking.benchmarks.liander2024 import Liander2024Category, create_liander2024_benchmark_runner diff --git a/examples/benchmarks/liander_2024_ensemble.py b/examples/benchmarks/liander_2024_ensemble.py index c00d2c5df..5760d35e6 100644 --- a/examples/benchmarks/liander_2024_ensemble.py +++ b/examples/benchmarks/liander_2024_ensemble.py @@ -23,7 +23,7 @@ from pathlib import Path from openstef_beam.backtesting.backtest_forecaster import BacktestForecasterConfig -from openstef_beam.benchmarking.baselines import ( +from openstef_beam.benchmarking.baselines.openstef4 import ( create_openstef4_preset_backtest_forecaster, ) from openstef_beam.benchmarking.benchmarks.liander2024 import Liander2024Category, create_liander2024_benchmark_runner diff --git a/packages/openstef-beam/pyproject.toml b/packages/openstef-beam/pyproject.toml index 5c33fb373..cdcf3610b 100644 --- a/packages/openstef-beam/pyproject.toml +++ b/packages/openstef-beam/pyproject.toml @@ -36,8 +36,13 @@ dependencies = [ ] optional-dependencies.all = [ + "openstef-beam[baselines]", "s3fs>=2025.5.1", ] +optional-dependencies.baselines = [ + "openstef-meta>=0.0.1,<1", + "openstef-models>=4.0.0.dev0,<5", +] urls.Documentation = "https://openstef.github.io/openstef/index.html" urls.Homepage = "https://lfenergy.org/projects/openstef/" urls.Issues = "https://github.com/OpenSTEF/openstef/issues" @@ -48,3 +53,5 @@ packages = [ "src/openstef_beam" ] [tool.uv.sources] openstef-core = { workspace = true } +openstef-models = { workspace = true } +openstef-meta = { workspace = true } diff --git a/packages/openstef-beam/src/openstef_beam/benchmarking/baselines/__init__.py b/packages/openstef-beam/src/openstef_beam/benchmarking/baselines/__init__.py index 19124fc22..6bf2d8d07 100644 --- a/packages/openstef-beam/src/openstef_beam/benchmarking/baselines/__init__.py +++ b/packages/openstef-beam/src/openstef_beam/benchmarking/baselines/__init__.py @@ -1,20 +1,17 @@ """Benchmarks baselines used by the OpenSTEF Beam benchmarking utilities. This package exposes baseline forecasters for use in backtesting. +The OpenSTEF v4 baselines require ``openstef-models`` and ``openstef-meta``, +available via the ``baselines`` extra: ``pip install openstef-beam[baselines]``. + +Import directly from the submodule:: + + from openstef_beam.benchmarking.baselines.openstef4 import ( + OpenSTEF4BacktestForecaster, + create_openstef4_preset_backtest_forecaster, + ) """ # SPDX-FileCopyrightText: 2025 Contributors to the OpenSTEF project # # SPDX-License-Identifier: MPL-2.0 - -from openstef_beam.benchmarking.baselines.openstef4 import ( - OpenSTEF4BacktestForecaster, - WorkflowCreationContext, - create_openstef4_preset_backtest_forecaster, -) - -__all__ = [ - "OpenSTEF4BacktestForecaster", - "WorkflowCreationContext", - "create_openstef4_preset_backtest_forecaster", -] diff --git a/packages/openstef-beam/src/openstef_beam/benchmarking/baselines/openstef4.py b/packages/openstef-beam/src/openstef_beam/benchmarking/baselines/openstef4.py index f6ecb67db..006dcc33e 100644 --- a/packages/openstef-beam/src/openstef_beam/benchmarking/baselines/openstef4.py +++ b/packages/openstef-beam/src/openstef_beam/benchmarking/baselines/openstef4.py @@ -2,16 +2,18 @@ # # SPDX-License-Identifier: MPL-2.0 -"""OpenSTEF 4.0 forecaster for backtesting pipelines.""" +"""OpenSTEF 4.0 forecaster for backtesting pipelines. + +Requires the ``baselines`` extra: ``pip install openstef-beam[baselines]``. +""" import logging from collections.abc import Callable from datetime import timedelta from functools import partial from pathlib import Path -from typing import Any, cast, override +from typing import TYPE_CHECKING, Any, cast, override -import pandas as pd from pydantic import Field, PrivateAttr from pydantic_extra_types.coordinate import Coordinate @@ -31,12 +33,17 @@ from openstef_core.datasets import TimeSeriesDataset from openstef_core.exceptions import FlatlinerDetectedError, NotFittedError from openstef_core.types import Q -from openstef_meta.presets import EnsembleForecastingWorkflowConfig, create_ensemble_forecasting_workflow -from openstef_models.presets import ForecastingWorkflowConfig +from openstef_models.presets import ForecastingWorkflowConfig, create_forecasting_workflow +from openstef_models.presets.forecasting_workflow import LocationConfig +from openstef_models.workflows.callbacks.data_save import DataSaveCallback from openstef_models.workflows.custom_forecasting_workflow import ( CustomForecastingWorkflow, + ForecastingCallback, ) +if TYPE_CHECKING: + from openstef_meta.presets import EnsembleForecastingWorkflowConfig + class WorkflowCreationContext(BaseConfig): """Context information for workflow execution within backtesting.""" @@ -71,6 +78,10 @@ class OpenSTEF4BacktestForecaster(BaseModel, BacktestForecasterMixin): default=False, description="When True, saves base forecaster prediction contributions for ensemble models", ) + extra_callbacks: list[ForecastingCallback] = Field( + default_factory=list[ForecastingCallback], + description="Additional callbacks to inject into workflows created by the factory.", + ) _workflow: CustomForecastingWorkflow | None = PrivateAttr(default=None) _is_flatliner_detected: bool = PrivateAttr(default=False) @@ -80,7 +91,16 @@ class OpenSTEF4BacktestForecaster(BaseModel, BacktestForecasterMixin): @override def model_post_init(self, context: Any) -> None: if self.debug or self.contributions: - self.cache_dir.mkdir(parents=True, exist_ok=True) + self.extra_callbacks.append( + DataSaveCallback( + cache_dir=self.cache_dir, + save_training_data=self.debug, + save_prepared_data=self.debug, + save_predict_data=self.debug, + save_forecast=self.debug, + save_contributions=self.contributions, + ) + ) @property @override @@ -96,6 +116,7 @@ def fit(self, data: RestrictedHorizonVersionedTimeSeries) -> None: # Create a new workflow for this training cycle context = WorkflowCreationContext(step_name=data.horizon.isoformat()) workflow = self.workflow_factory(context) + workflow.callbacks.extend(self.extra_callbacks) # Extract the dataset for training training_data = data.get_window( @@ -104,10 +125,6 @@ def fit(self, data: RestrictedHorizonVersionedTimeSeries) -> None: available_before=data.horizon, ) - if self.debug: - id_str = data.horizon.strftime("%Y%m%d%H%M%S") - training_data.to_parquet(path=self.cache_dir / f"debug_{id_str}_training.parquet") - try: # Use the workflow's fit method workflow.fit(data=training_data) @@ -119,12 +136,6 @@ def fit(self, data: RestrictedHorizonVersionedTimeSeries) -> None: self._workflow = workflow - if self.debug: - id_str = data.horizon.strftime("%Y%m%d%H%M%S") - self._workflow.model.prepare_input(training_data).to_parquet( - path=self.cache_dir / f"debug_{id_str}_prepared_training.parquet" - ) - @override def predict(self, data: RestrictedHorizonVersionedTimeSeries) -> TimeSeriesDataset | None: if self._is_flatliner_detected: @@ -150,42 +161,33 @@ def predict(self, data: RestrictedHorizonVersionedTimeSeries) -> TimeSeriesDatas self._logger.info("Flatliner detected during prediction") return None - if self.debug: - id_str = data.horizon.strftime("%Y%m%d%H%M%S") - predict_data.to_parquet(path=self.cache_dir / f"debug_{id_str}_predict.parquet") - forecast.to_parquet(path=self.cache_dir / f"debug_{id_str}_forecast.parquet") - - if self.contributions: - id_str = data.horizon.strftime("%Y%m%d%H%M%S") - try: - contributions = self._workflow.model.predict_contributions(predict_data, forecast_start=data.horizon) - except NotImplementedError: - pass - else: - df = pd.concat([contributions.data, forecast.data.drop(columns=["load"])], axis=1) - df.to_parquet(path=self.cache_dir / f"contrib_{id_str}_predict.parquet") return forecast -class OpenSTEF4PresetBacktestForecaster(OpenSTEF4BacktestForecaster): - pass - - def _preset_target_forecaster_factory( - base_config: ForecastingWorkflowConfig | EnsembleForecastingWorkflowConfig, + base_config: "ForecastingWorkflowConfig | EnsembleForecastingWorkflowConfig", backtest_config: BacktestForecasterConfig, cache_dir: Path, context: BenchmarkContext, target: BenchmarkTarget, ) -> OpenSTEF4BacktestForecaster: - from openstef_models.presets import create_forecasting_workflow # noqa: PLC0415 - from openstef_models.presets.forecasting_workflow import LocationConfig # noqa: PLC0415 + # Try to import ensemble support (openstef-meta is optional) + ensemble_config_cls: type | None = None + create_ensemble: Callable[..., CustomForecastingWorkflow] | None = None + try: + from openstef_meta.presets import ( # noqa: PLC0415 + EnsembleForecastingWorkflowConfig, + create_ensemble_forecasting_workflow, + ) + + ensemble_config_cls = EnsembleForecastingWorkflowConfig + create_ensemble = create_ensemble_forecasting_workflow + except ImportError: + pass - # Factory function that creates a forecaster for a given target. prefix = context.run_name def _create_workflow(context: WorkflowCreationContext) -> CustomForecastingWorkflow: - # Create a new workflow instance with fresh model. location = LocationConfig( name=target.name, description=target.description, @@ -201,10 +203,14 @@ def _create_workflow(context: WorkflowCreationContext) -> CustomForecastingWorkf "run_name": context.step_name, } - if isinstance(base_config, EnsembleForecastingWorkflowConfig): - return create_ensemble_forecasting_workflow(config=base_config.model_copy(update=update)) + if ( # fmt: skip + create_ensemble is not None + and ensemble_config_cls is not None + and isinstance(base_config, ensemble_config_cls) + ): + return create_ensemble(config=base_config.model_copy(update=update)) # pyright: ignore[reportArgumentType] - return create_forecasting_workflow(config=base_config.model_copy(update=update)) + return create_forecasting_workflow(config=base_config.model_copy(update=update)) # pyright: ignore[reportArgumentType] return OpenSTEF4BacktestForecaster( config=backtest_config, @@ -215,7 +221,7 @@ def _create_workflow(context: WorkflowCreationContext) -> CustomForecastingWorkf def create_openstef4_preset_backtest_forecaster( - workflow_config: ForecastingWorkflowConfig | EnsembleForecastingWorkflowConfig, + workflow_config: "ForecastingWorkflowConfig | EnsembleForecastingWorkflowConfig", backtest_config: BacktestForecasterConfig | None = None, cache_dir: Path = Path("cache"), ) -> ForecasterFactory[BenchmarkTarget]: diff --git a/packages/openstef-models/src/openstef_models/workflows/callbacks/__init__.py b/packages/openstef-models/src/openstef_models/workflows/callbacks/__init__.py new file mode 100644 index 000000000..57a25eb92 --- /dev/null +++ b/packages/openstef-models/src/openstef_models/workflows/callbacks/__init__.py @@ -0,0 +1,9 @@ +# SPDX-FileCopyrightText: 2025 Contributors to the OpenSTEF project +# +# SPDX-License-Identifier: MPL-2.0 + +"""Workflow callbacks for data capture, persistence, and debugging.""" + +from openstef_models.workflows.callbacks.data_save import DataSaveCallback + +__all__ = ["DataSaveCallback"] diff --git a/packages/openstef-models/src/openstef_models/workflows/callbacks/data_save.py b/packages/openstef-models/src/openstef_models/workflows/callbacks/data_save.py new file mode 100644 index 000000000..50c9065b1 --- /dev/null +++ b/packages/openstef-models/src/openstef_models/workflows/callbacks/data_save.py @@ -0,0 +1,108 @@ +# SPDX-FileCopyrightText: 2025 Contributors to the OpenSTEF project +# +# SPDX-License-Identifier: MPL-2.0 + +"""Data-saving callback for forecasting workflows. + +Saves intermediate datasets (training data, prepared inputs, forecasts, +contributions) to parquet files. Useful for debugging, backtesting analysis, +and inspecting model behaviour. +""" + +import logging +from pathlib import Path +from typing import Any, override + +import pandas as pd +from pydantic import Field + +from openstef_core.base_model import BaseConfig +from openstef_core.datasets import TimeSeriesDataset, VersionedTimeSeriesDataset +from openstef_core.datasets.validated_datasets import ForecastDataset +from openstef_models.mixins.callbacks import WorkflowContext +from openstef_models.models.forecasting_model import ModelFitResult +from openstef_models.workflows.custom_forecasting_workflow import ( + CustomForecastingWorkflow, + ForecastingCallback, +) + +_logger = logging.getLogger(__name__) + + +class DataSaveCallback(BaseConfig, ForecastingCallback): + """Saves intermediate datasets to parquet files during workflow execution. + + Toggle individual outputs via the boolean fields. All paths use + ``workflow.run_name`` as an identifier in the filename. + """ + + cache_dir: Path = Field(description="Directory to write parquet files to.") + save_training_data: bool = Field(default=True, description="Save raw training data on fit.") + save_prepared_data: bool = Field(default=True, description="Save preprocessed training data on fit.") + save_predict_data: bool = Field(default=True, description="Save prediction input data on predict.") + save_forecast: bool = Field(default=True, description="Save forecast output on predict.") + save_contributions: bool = Field(default=False, description="Save prediction contributions on predict.") + + @override + def model_post_init(self, context: Any) -> None: + self.cache_dir.mkdir(parents=True, exist_ok=True) + + @override + def on_fit_start( + self, + context: WorkflowContext[CustomForecastingWorkflow], + data: VersionedTimeSeriesDataset | TimeSeriesDataset, + ) -> None: + if self.save_prepared_data: + # Stash training data so on_fit_end can call prepare_input with it + context.data["_datasave_training_data"] = data + + if self.save_training_data: + run_name = context.workflow.run_name or "step" + data.to_parquet(path=self.cache_dir / f"debug_{run_name}_training.parquet") + + @override + def on_fit_end( + self, + context: WorkflowContext[CustomForecastingWorkflow], + result: ModelFitResult, + ) -> None: + if not self.save_prepared_data: + return + + training_data = context.data.pop("_datasave_training_data", None) + if not isinstance(training_data, TimeSeriesDataset): + return + + run_name = context.workflow.run_name or "step" + prepared = context.workflow.model.prepare_input(training_data) + prepared.to_parquet(path=self.cache_dir / f"debug_{run_name}_prepared_training.parquet") + + @override + def on_predict_end( + self, + context: WorkflowContext[CustomForecastingWorkflow], + data: VersionedTimeSeriesDataset | TimeSeriesDataset, + result: ForecastDataset, + ) -> None: + run_name = context.workflow.run_name or "step" + + if self.save_predict_data: + data.to_parquet(path=self.cache_dir / f"debug_{run_name}_predict.parquet") + + if self.save_forecast: + result.to_parquet(path=self.cache_dir / f"debug_{run_name}_forecast.parquet") + + if self.save_contributions and isinstance(data, TimeSeriesDataset): + try: + contributions = context.workflow.model.predict_contributions( + data, + forecast_start=result.forecast_start, + ) + except NotImplementedError: + return + df = pd.concat([contributions.data, result.data.drop(columns=["load"])], axis=1) + df.to_parquet(path=self.cache_dir / f"contrib_{run_name}_predict.parquet") + + +__all__ = ["DataSaveCallback"] diff --git a/uv.lock b/uv.lock index 46fac5dc4..045f0086d 100644 --- a/uv.lock +++ b/uv.lock @@ -20,7 +20,7 @@ members = [ [[package]] name = "accessible-pygments" version = "0.0.5" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pygments" }, ] @@ -32,7 +32,7 @@ wheels = [ [[package]] name = "aiobotocore" version = "2.25.2" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohttp" }, { name = "aioitertools" }, @@ -50,7 +50,7 @@ wheels = [ [[package]] name = "aiohappyeyeballs" version = "2.6.1" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/26/30/f84a107a9c4331c14b2b586036f40965c128aa4fee4dda5d3d51cb14ad54/aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558", size = 22760, upload-time = "2025-03-12T01:42:48.764Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8", size = 15265, upload-time = "2025-03-12T01:42:47.083Z" }, @@ -59,7 +59,7 @@ wheels = [ [[package]] name = "aiohttp" version = "3.13.2" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohappyeyeballs" }, { name = "aiosignal" }, @@ -144,7 +144,7 @@ wheels = [ [[package]] name = "aioitertools" version = "0.13.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/fd/3c/53c4a17a05fb9ea2313ee1777ff53f5e001aefd5cc85aa2f4c2d982e1e38/aioitertools-0.13.0.tar.gz", hash = "sha256:620bd241acc0bbb9ec819f1ab215866871b4bbd1f73836a55f799200ee86950c", size = 19322, upload-time = "2025-11-06T22:17:07.609Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/10/a1/510b0a7fadc6f43a6ce50152e69dbd86415240835868bb0bd9b5b88b1e06/aioitertools-0.13.0-py3-none-any.whl", hash = "sha256:0be0292b856f08dfac90e31f4739432f4cb6d7520ab9eb73e143f4f2fa5259be", size = 24182, upload-time = "2025-11-06T22:17:06.502Z" }, @@ -153,7 +153,7 @@ wheels = [ [[package]] name = "aiosignal" version = "1.4.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "frozenlist" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, @@ -166,7 +166,7 @@ wheels = [ [[package]] name = "alabaster" version = "1.0.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/a6/f8/d9c74d0daf3f742840fd818d69cfae176fa332022fd44e3469487d5a9420/alabaster-1.0.0.tar.gz", hash = "sha256:c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e", size = 24210, upload-time = "2024-07-26T18:15:03.762Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/7e/b3/6b4067be973ae96ba0d615946e314c5ae35f9f993eca561b356540bb0c2b/alabaster-1.0.0-py3-none-any.whl", hash = "sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b", size = 13929, upload-time = "2024-07-26T18:15:02.05Z" }, @@ -175,7 +175,7 @@ wheels = [ [[package]] name = "annotated-doc" version = "0.0.4" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/57/ba/046ceea27344560984e26a590f90bc7f4a75b06701f653222458922b558c/annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4", size = 7288, upload-time = "2025-11-10T22:07:42.062Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", size = 5303, upload-time = "2025-11-10T22:07:40.673Z" }, @@ -184,7 +184,7 @@ wheels = [ [[package]] name = "annotated-types" version = "0.7.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, @@ -193,7 +193,7 @@ wheels = [ [[package]] name = "antlr4-python3-runtime" version = "4.13.2" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/33/5f/2cdf6f7aca3b20d3f316e9f505292e1f256a32089bd702034c29ebde6242/antlr4_python3_runtime-4.13.2.tar.gz", hash = "sha256:909b647e1d2fc2b70180ac586df3933e38919c85f98ccc656a96cd3f25ef3916", size = 117467, upload-time = "2024-08-03T19:00:12.757Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/89/03/a851e84fcbb85214dc637b6378121ef9a0dd61b4c65264675d8a5c9b1ae7/antlr4_python3_runtime-4.13.2-py3-none-any.whl", hash = "sha256:fe3835eb8d33daece0e799090eda89719dbccee7aa39ef94eed3818cafa5a7e8", size = 144462, upload-time = "2024-08-03T19:00:11.134Z" }, @@ -202,7 +202,7 @@ wheels = [ [[package]] name = "anyio" version = "4.11.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "idna" }, { name = "sniffio" }, @@ -216,7 +216,7 @@ wheels = [ [[package]] name = "appdirs" version = "1.4.4" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/d7/d8/05696357e0311f5b5c316d7b95f46c669dd9c15aaeecbb48c7d0aeb88c40/appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41", size = 13470, upload-time = "2020-05-11T07:59:51.037Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/3b/00/2344469e2084fb287c2e0b57b72910309874c3245463acd6cf5e3db69324/appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128", size = 9566, upload-time = "2020-05-11T07:59:49.499Z" }, @@ -225,7 +225,7 @@ wheels = [ [[package]] name = "appnope" version = "0.1.4" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/35/5d/752690df9ef5b76e169e68d6a129fa6d08a7100ca7f754c89495db3c6019/appnope-0.1.4.tar.gz", hash = "sha256:1de3860566df9caf38f01f86f65e0e13e379af54f9e4bee1e66b48f2efffd1ee", size = 4170, upload-time = "2024-02-06T09:43:11.258Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl", hash = "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c", size = 4321, upload-time = "2024-02-06T09:43:09.663Z" }, @@ -234,7 +234,7 @@ wheels = [ [[package]] name = "argon2-cffi" version = "25.1.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "argon2-cffi-bindings" }, ] @@ -246,7 +246,7 @@ wheels = [ [[package]] name = "argon2-cffi-bindings" version = "25.1.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi" }, ] @@ -277,7 +277,7 @@ wheels = [ [[package]] name = "arrow" version = "1.4.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "python-dateutil" }, { name = "tzdata" }, @@ -290,7 +290,7 @@ wheels = [ [[package]] name = "asttokens" version = "3.0.1" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/be/a5/8e3f9b6771b0b408517c82d97aed8f2036509bc247d46114925e32fe33f0/asttokens-3.0.1.tar.gz", hash = "sha256:71a4ee5de0bde6a31d64f6b13f2293ac190344478f081c3d1bccfcf5eacb0cb7", size = 62308, upload-time = "2025-11-15T16:43:48.578Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl", hash = "sha256:15a3ebc0f43c2d0a50eeafea25e19046c68398e487b9f1f5b517f7c0f40f976a", size = 27047, upload-time = "2025-11-15T16:43:16.109Z" }, @@ -299,7 +299,7 @@ wheels = [ [[package]] name = "async-lru" version = "2.0.5" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/b2/4d/71ec4d3939dc755264f680f6c2b4906423a304c3d18e96853f0a595dfe97/async_lru-2.0.5.tar.gz", hash = "sha256:481d52ccdd27275f42c43a928b4a50c3bfb2d67af4e78b170e3e0bb39c66e5bb", size = 10380, upload-time = "2025-03-16T17:25:36.919Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/03/49/d10027df9fce941cb8184e78a02857af36360d33e1721df81c5ed2179a1a/async_lru-2.0.5-py3-none-any.whl", hash = "sha256:ab95404d8d2605310d345932697371a5f40def0487c03d6d0ad9138de52c9943", size = 6069, upload-time = "2025-03-16T17:25:35.422Z" }, @@ -308,7 +308,7 @@ wheels = [ [[package]] name = "attrs" version = "23.2.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/e3/fc/f800d51204003fa8ae392c4e8278f256206e7a919b708eef054f5f4b650d/attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30", size = 780820, upload-time = "2023-12-31T06:30:32.926Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/e0/44/827b2a91a5816512fcaf3cc4ebc465ccd5d598c45cefa6703fcf4a79018f/attrs-23.2.0-py3-none-any.whl", hash = "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1", size = 60752, upload-time = "2023-12-31T06:30:30.772Z" }, @@ -317,7 +317,7 @@ wheels = [ [[package]] name = "aws-sam-translator" version = "1.101.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "boto3" }, { name = "jsonschema" }, @@ -332,7 +332,7 @@ wheels = [ [[package]] name = "aws-xray-sdk" version = "2.15.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "botocore" }, { name = "wrapt" }, @@ -345,7 +345,7 @@ wheels = [ [[package]] name = "babel" version = "2.17.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852, upload-time = "2025-02-01T15:17:41.026Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537, upload-time = "2025-02-01T15:17:37.39Z" }, @@ -354,7 +354,7 @@ wheels = [ [[package]] name = "beautifulsoup4" version = "4.14.2" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "soupsieve" }, { name = "typing-extensions" }, @@ -367,7 +367,7 @@ wheels = [ [[package]] name = "bleach" version = "6.3.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "webencodings" }, ] @@ -384,7 +384,7 @@ css = [ [[package]] name = "blinker" version = "1.9.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/21/28/9b3f50ce0e048515135495f198351908d99540d69bfdc8c1d15b73dc55ce/blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf", size = 22460, upload-time = "2024-11-08T17:25:47.436Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc", size = 8458, upload-time = "2024-11-08T17:25:46.184Z" }, @@ -393,7 +393,7 @@ wheels = [ [[package]] name = "boolean-py" version = "5.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/c4/cf/85379f13b76f3a69bca86b60237978af17d6aa0bc5998978c3b8cf05abb2/boolean_py-5.0.tar.gz", hash = "sha256:60cbc4bad079753721d32649545505362c754e121570ada4658b852a3a318d95", size = 37047, upload-time = "2025-04-03T10:39:49.734Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/e5/ca/78d423b324b8d77900030fa59c4aa9054261ef0925631cd2501dd015b7b7/boolean_py-5.0-py3-none-any.whl", hash = "sha256:ef28a70bd43115208441b53a045d1549e2f0ec6e3d08a9d142cbc41c1938e8d9", size = 26577, upload-time = "2025-04-03T10:39:48.449Z" }, @@ -402,7 +402,7 @@ wheels = [ [[package]] name = "boto3" version = "1.40.70" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "botocore" }, { name = "jmespath" }, @@ -416,7 +416,7 @@ wheels = [ [[package]] name = "botocore" version = "1.40.70" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jmespath" }, { name = "python-dateutil" }, @@ -430,7 +430,7 @@ wheels = [ [[package]] name = "cachetools" version = "6.2.2" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/fb/44/ca1675be2a83aeee1886ab745b28cda92093066590233cc501890eb8417a/cachetools-6.2.2.tar.gz", hash = "sha256:8e6d266b25e539df852251cfd6f990b4bc3a141db73b939058d809ebd2590fc6", size = 31571, upload-time = "2025-11-13T17:42:51.465Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/e6/46/eb6eca305c77a4489affe1c5d8f4cae82f285d9addd8de4ec084a7184221/cachetools-6.2.2-py3-none-any.whl", hash = "sha256:6c09c98183bf58560c97b2abfcedcbaf6a896a490f534b031b661d3723b45ace", size = 11503, upload-time = "2025-11-13T17:42:50.232Z" }, @@ -439,7 +439,7 @@ wheels = [ [[package]] name = "cattrs" version = "24.1.3" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "attrs" }, ] @@ -451,7 +451,7 @@ wheels = [ [[package]] name = "certifi" version = "2025.11.12" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/a2/8c/58f469717fa48465e4a50c014a0400602d3c437d7c0c468e17ada824da3a/certifi-2025.11.12.tar.gz", hash = "sha256:d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316", size = 160538, upload-time = "2025-11-12T02:54:51.517Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/70/7d/9bc192684cea499815ff478dfcdc13835ddf401365057044fb721ec6bddb/certifi-2025.11.12-py3-none-any.whl", hash = "sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b", size = 159438, upload-time = "2025-11-12T02:54:49.735Z" }, @@ -460,7 +460,7 @@ wheels = [ [[package]] name = "cffi" version = "2.0.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pycparser", marker = "implementation_name != 'PyPy'" }, ] @@ -517,7 +517,7 @@ wheels = [ [[package]] name = "cfn-lint" version = "1.40.4" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aws-sam-translator" }, { name = "jsonpatch" }, @@ -535,7 +535,7 @@ wheels = [ [[package]] name = "charset-normalizer" version = "3.4.4" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/f3/85/1637cd4af66fa687396e757dec650f28025f2a2f5a5531a3208dc0ec43f2/charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", size = 208425, upload-time = "2025-10-14T04:40:53.353Z" }, @@ -592,7 +592,7 @@ wheels = [ [[package]] name = "choreographer" version = "1.2.1" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "logistro" }, { name = "simplejson" }, @@ -605,7 +605,7 @@ wheels = [ [[package]] name = "click" version = "8.3.1" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] @@ -617,7 +617,7 @@ wheels = [ [[package]] name = "cloudpickle" version = "3.1.2" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/27/fb/576f067976d320f5f0114a8d9fa1215425441bb35627b1993e5afd8111e5/cloudpickle-3.1.2.tar.gz", hash = "sha256:7fda9eb655c9c230dab534f1983763de5835249750e85fbcef43aaa30a9a2414", size = 22330, upload-time = "2025-11-03T09:25:26.604Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl", hash = "sha256:9acb47f6afd73f60dc1df93bb801b472f05ff42fa6c84167d25cb206be1fbf4a", size = 22228, upload-time = "2025-11-03T09:25:25.534Z" }, @@ -626,7 +626,7 @@ wheels = [ [[package]] name = "colorama" version = "0.4.6" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, @@ -635,7 +635,7 @@ wheels = [ [[package]] name = "comm" version = "0.2.3" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/4c/13/7d740c5849255756bc17888787313b61fd38a0a8304fc4f073dfc46122aa/comm-0.2.3.tar.gz", hash = "sha256:2dc8048c10962d55d7ad693be1e7045d891b7ce8d999c97963a5e3e99c055971", size = 6319, upload-time = "2025-07-25T14:02:04.452Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl", hash = "sha256:c615d91d75f7f04f095b30d1c1711babd43bdc6419c1be9886a85f2f4e489417", size = 7294, upload-time = "2025-07-25T14:02:02.896Z" }, @@ -644,7 +644,7 @@ wheels = [ [[package]] name = "contourpy" version = "1.3.3" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] @@ -710,7 +710,7 @@ wheels = [ [[package]] name = "coverage" version = "7.11.3" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/d2/59/9698d57a3b11704c7b89b21d69e9d23ecf80d538cabb536c8b63f4a12322/coverage-7.11.3.tar.gz", hash = "sha256:0f59387f5e6edbbffec2281affb71cdc85e0776c1745150a3ab9b6c1d016106b", size = 815210, upload-time = "2025-11-10T00:13:17.18Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/c2/39/af056ec7a27c487e25c7f6b6e51d2ee9821dba1863173ddf4dc2eebef4f7/coverage-7.11.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5b771b59ac0dfb7f139f70c85b42717ef400a6790abb6475ebac1ecee8de782f", size = 216676, upload-time = "2025-11-10T00:11:11.566Z" }, @@ -784,7 +784,7 @@ wheels = [ [[package]] name = "cryptography" version = "46.0.3" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, ] @@ -840,7 +840,7 @@ wheels = [ [[package]] name = "cycler" version = "0.12.1" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/a9/95/a3dbbb5028f35eafb79008e7522a75244477d2838f38cbb722248dabc2a8/cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c", size = 7615, upload-time = "2023-10-07T05:32:18.335Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321, upload-time = "2023-10-07T05:32:16.783Z" }, @@ -849,7 +849,7 @@ wheels = [ [[package]] name = "databricks-sdk" version = "0.73.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "google-auth" }, { name = "protobuf" }, @@ -863,7 +863,7 @@ wheels = [ [[package]] name = "debugpy" version = "1.8.18" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/62/1a/7cb5531840d7ba5d9329644109e62adee41f2f0083d9f8a4039f01de58cf/debugpy-1.8.18.tar.gz", hash = "sha256:02551b1b84a91faadd2db9bc4948873f2398190c95b3cc6f97dc706f43e8c433", size = 1644467, upload-time = "2025-12-10T19:48:07.236Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/83/01/439626e3572a33ac543f25bc1dac1e80bc01c7ce83f3c24dc4441302ca13/debugpy-1.8.18-cp312-cp312-macosx_15_0_universal2.whl", hash = "sha256:530c38114725505a7e4ea95328dbc24aabb9be708c6570623c8163412e6d1d6b", size = 2549961, upload-time = "2025-12-10T19:48:21.73Z" }, @@ -884,7 +884,7 @@ wheels = [ [[package]] name = "decorator" version = "5.2.1" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/43/fa/6d96a0978d19e17b68d634497769987b16c8f4cd0a7a05048bec693caa6b/decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360", size = 56711, upload-time = "2025-02-24T04:41:34.073Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a", size = 9190, upload-time = "2025-02-24T04:41:32.565Z" }, @@ -893,7 +893,7 @@ wheels = [ [[package]] name = "defusedxml" version = "0.7.1" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/0f/d5/c66da9b79e5bdb124974bfe172b4daf3c984ebd9c2a06e2b8a4dc7331c72/defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", size = 75520, upload-time = "2021-03-08T10:59:26.269Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604, upload-time = "2021-03-08T10:59:24.45Z" }, @@ -902,7 +902,7 @@ wheels = [ [[package]] name = "docker" version = "7.1.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pywin32", marker = "sys_platform == 'win32'" }, { name = "requests" }, @@ -916,7 +916,7 @@ wheels = [ [[package]] name = "docutils" version = "0.21.2" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/ae/ed/aefcc8cd0ba62a0560c3c18c33925362d46c6075480bfa4df87b28e169a9/docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f", size = 2204444, upload-time = "2024-04-23T18:57:18.24Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/8f/d7/9322c609343d929e75e7e5e6255e614fcc67572cfd083959cdef3b7aad79/docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2", size = 587408, upload-time = "2024-04-23T18:57:14.835Z" }, @@ -925,7 +925,7 @@ wheels = [ [[package]] name = "dom-toml" version = "2.1.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "domdf-python-tools" }, ] @@ -937,7 +937,7 @@ wheels = [ [[package]] name = "domdf-python-tools" version = "3.10.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "natsort" }, { name = "typing-extensions" }, @@ -950,7 +950,7 @@ wheels = [ [[package]] name = "execnet" version = "2.1.2" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/bf/89/780e11f9588d9e7128a3f87788354c7946a9cbb1401ad38a48c4db9a4f07/execnet-2.1.2.tar.gz", hash = "sha256:63d83bfdd9a23e35b9c6a3261412324f964c2ec8dcd8d3c6916ee9373e0befcd", size = 166622, upload-time = "2025-11-12T09:56:37.75Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl", hash = "sha256:67fba928dd5a544b783f6056f449e5e3931a5c378b128bc18501f7ea79e296ec", size = 40708, upload-time = "2025-11-12T09:56:36.333Z" }, @@ -959,7 +959,7 @@ wheels = [ [[package]] name = "executing" version = "2.2.1" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/cc/28/c14e053b6762b1044f34a13aab6859bbf40456d37d23aa286ac24cfd9a5d/executing-2.2.1.tar.gz", hash = "sha256:3632cc370565f6648cc328b32435bd120a1e4ebb20c77e3fdde9a13cd1e533c4", size = 1129488, upload-time = "2025-09-01T09:48:10.866Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl", hash = "sha256:760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017", size = 28317, upload-time = "2025-09-01T09:48:08.5Z" }, @@ -968,7 +968,7 @@ wheels = [ [[package]] name = "fastapi" version = "0.121.2" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-doc" }, { name = "pydantic" }, @@ -983,7 +983,7 @@ wheels = [ [[package]] name = "fastjsonschema" version = "2.21.2" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/20/b5/23b216d9d985a956623b6bd12d4086b60f0059b27799f23016af04a74ea1/fastjsonschema-2.21.2.tar.gz", hash = "sha256:b1eb43748041c880796cd077f1a07c3d94e93ae84bba5ed36800a33554ae05de", size = 374130, upload-time = "2025-08-14T18:49:36.666Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl", hash = "sha256:1c797122d0a86c5cace2e54bf4e819c36223b552017172f32c5c024a6b77e463", size = 24024, upload-time = "2025-08-14T18:49:34.776Z" }, @@ -992,7 +992,7 @@ wheels = [ [[package]] name = "fhconfparser" version = "2024.1" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "attrs" }, { name = "tomli" }, @@ -1005,7 +1005,7 @@ wheels = [ [[package]] name = "filelock" version = "3.20.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/58/46/0028a82567109b5ef6e4d2a1f04a583fb513e6cf9527fcdd09afd817deeb/filelock-3.20.0.tar.gz", hash = "sha256:711e943b4ec6be42e1d4e6690b48dc175c822967466bb31c0c293f34334c13f4", size = 18922, upload-time = "2025-10-08T18:03:50.056Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/76/91/7216b27286936c16f5b4d0c530087e4a54eead683e6b0b73dd0c64844af6/filelock-3.20.0-py3-none-any.whl", hash = "sha256:339b4732ffda5cd79b13f4e2711a31b0365ce445d95d243bb996273d072546a2", size = 16054, upload-time = "2025-10-08T18:03:48.35Z" }, @@ -1014,7 +1014,7 @@ wheels = [ [[package]] name = "flask" version = "3.1.2" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "blinker" }, { name = "click" }, @@ -1031,7 +1031,7 @@ wheels = [ [[package]] name = "flask-cors" version = "6.0.1" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "flask" }, { name = "werkzeug" }, @@ -1044,7 +1044,7 @@ wheels = [ [[package]] name = "fonttools" version = "4.60.1" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/4b/42/97a13e47a1e51a5a7142475bbcf5107fe3a68fc34aef331c897d5fb98ad0/fonttools-4.60.1.tar.gz", hash = "sha256:ef00af0439ebfee806b25f24c8f92109157ff3fac5731dc7867957812e87b8d9", size = 3559823, upload-time = "2025-09-29T21:13:27.129Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/e3/f7/a10b101b7a6f8836a5adb47f2791f2075d044a6ca123f35985c42edc82d8/fonttools-4.60.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7b0c6d57ab00dae9529f3faf187f2254ea0aa1e04215cf2f1a8ec277c96661bc", size = 2832953, upload-time = "2025-09-29T21:11:39.616Z" }, @@ -1085,7 +1085,7 @@ wheels = [ [[package]] name = "fqdn" version = "1.5.1" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/30/3e/a80a8c077fd798951169626cde3e239adeba7dab75deb3555716415bd9b0/fqdn-1.5.1.tar.gz", hash = "sha256:105ed3677e767fb5ca086a0c1f4bb66ebc3c100be518f0e0d755d9eae164d89f", size = 6015, upload-time = "2021-03-11T07:16:29.08Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/cf/58/8acf1b3e91c58313ce5cb67df61001fc9dcd21be4fadb76c1a2d540e09ed/fqdn-1.5.1-py3-none-any.whl", hash = "sha256:3a179af3761e4df6eb2e026ff9e1a3033d3587bf980a0b1b2e1e5d08d7358014", size = 9121, upload-time = "2021-03-11T07:16:28.351Z" }, @@ -1094,7 +1094,7 @@ wheels = [ [[package]] name = "frozenlist" version = "1.8.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/2d/f5/c831fac6cc817d26fd54c7eaccd04ef7e0288806943f7cc5bbf69f3ac1f0/frozenlist-1.8.0.tar.gz", hash = "sha256:3ede829ed8d842f6cd48fc7081d7a41001a56f1f38603f9d49bf3020d59a31ad", size = 45875, upload-time = "2025-10-06T05:38:17.865Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/69/29/948b9aa87e75820a38650af445d2ef2b6b8a6fab1a23b6bb9e4ef0be2d59/frozenlist-1.8.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:78f7b9e5d6f2fdb88cdde9440dc147259b62b9d3b019924def9f6478be254ac1", size = 87782, upload-time = "2025-10-06T05:36:06.649Z" }, @@ -1183,7 +1183,7 @@ wheels = [ [[package]] name = "fsspec" version = "2025.10.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/24/7f/2747c0d332b9acfa75dc84447a066fdf812b5a6b8d30472b74d309bfe8cb/fsspec-2025.10.0.tar.gz", hash = "sha256:b6789427626f068f9a83ca4e8a3cc050850b6c0f71f99ddb4f542b8266a26a59", size = 309285, upload-time = "2025-10-30T14:58:44.036Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/eb/02/a6b21098b1d5d6249b7c5ab69dde30108a71e4e819d4a9778f1de1d5b70d/fsspec-2025.10.0-py3-none-any.whl", hash = "sha256:7c7712353ae7d875407f97715f0e1ffcc21e33d5b24556cb1e090ae9409ec61d", size = 200966, upload-time = "2025-10-30T14:58:42.53Z" }, @@ -1192,7 +1192,7 @@ wheels = [ [[package]] name = "gitdb" version = "4.0.12" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "smmap" }, ] @@ -1204,7 +1204,7 @@ wheels = [ [[package]] name = "gitpython" version = "3.1.45" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "gitdb" }, ] @@ -1216,7 +1216,7 @@ wheels = [ [[package]] name = "google-auth" version = "2.43.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cachetools" }, { name = "pyasn1-modules" }, @@ -1230,7 +1230,7 @@ wheels = [ [[package]] name = "graphql-core" version = "3.2.7" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/ac/9b/037a640a2983b09aed4a823f9cf1729e6d780b0671f854efa4727a7affbe/graphql_core-3.2.7.tar.gz", hash = "sha256:27b6904bdd3b43f2a0556dad5d579bdfdeab1f38e8e8788e555bdcb586a6f62c", size = 513484, upload-time = "2025-11-01T22:30:40.436Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/0a/14/933037032608787fb92e365883ad6a741c235e0ff992865ec5d904a38f1e/graphql_core-3.2.7-py3-none-any.whl", hash = "sha256:17fc8f3ca4a42913d8e24d9ac9f08deddf0a0b2483076575757f6c412ead2ec0", size = 207262, upload-time = "2025-11-01T22:30:38.912Z" }, @@ -1239,7 +1239,7 @@ wheels = [ [[package]] name = "h11" version = "0.16.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, @@ -1248,7 +1248,7 @@ wheels = [ [[package]] name = "h5py" version = "3.15.1" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] @@ -1283,7 +1283,7 @@ wheels = [ [[package]] name = "hf-xet" version = "1.2.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/5e/6e/0f11bacf08a67f7fb5ee09740f2ca54163863b07b70d579356e9222ce5d8/hf_xet-1.2.0.tar.gz", hash = "sha256:a8c27070ca547293b6890c4bf389f713f80e8c478631432962bb7f4bc0bd7d7f", size = 506020, upload-time = "2025-10-24T19:04:32.129Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/9e/a5/85ef910a0aa034a2abcfadc360ab5ac6f6bc4e9112349bd40ca97551cff0/hf_xet-1.2.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:ceeefcd1b7aed4956ae8499e2199607765fbd1c60510752003b6cc0b8413b649", size = 2861870, upload-time = "2025-10-24T19:04:11.422Z" }, @@ -1312,7 +1312,7 @@ wheels = [ [[package]] name = "holidays" version = "0.84" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "python-dateutil" }, ] @@ -1324,7 +1324,7 @@ wheels = [ [[package]] name = "httpcore" version = "1.0.9" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, { name = "h11" }, @@ -1337,7 +1337,7 @@ wheels = [ [[package]] name = "httpx" version = "0.28.1" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, { name = "certifi" }, @@ -1352,7 +1352,7 @@ wheels = [ [[package]] name = "huggingface-hub" version = "1.2.2" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "filelock" }, { name = "fsspec" }, @@ -1373,7 +1373,7 @@ wheels = [ [[package]] name = "idna" version = "3.11" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, @@ -1382,7 +1382,7 @@ wheels = [ [[package]] name = "imagesize" version = "1.4.1" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/a7/84/62473fb57d61e31fef6e36d64a179c8781605429fd927b5dd608c997be31/imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a", size = 1280026, upload-time = "2022-07-01T12:21:05.687Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/ff/62/85c4c919272577931d407be5ba5d71c20f0b616d31a0befe0ae45bb79abd/imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b", size = 8769, upload-time = "2022-07-01T12:21:02.467Z" }, @@ -1391,7 +1391,7 @@ wheels = [ [[package]] name = "importlib-metadata" version = "8.7.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "zipp" }, ] @@ -1403,7 +1403,7 @@ wheels = [ [[package]] name = "iniconfig" version = "2.3.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, @@ -1412,7 +1412,7 @@ wheels = [ [[package]] name = "ipykernel" version = "7.1.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "appnope", marker = "sys_platform == 'darwin'" }, { name = "comm" }, @@ -1436,7 +1436,7 @@ wheels = [ [[package]] name = "ipython" version = "9.7.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, { name = "decorator" }, @@ -1457,7 +1457,7 @@ wheels = [ [[package]] name = "ipython-pygments-lexers" version = "1.1.1" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pygments" }, ] @@ -1469,7 +1469,7 @@ wheels = [ [[package]] name = "ipywidgets" version = "8.1.8" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "comm" }, { name = "ipython" }, @@ -1485,7 +1485,7 @@ wheels = [ [[package]] name = "isoduration" version = "20.11.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "arrow" }, ] @@ -1497,7 +1497,7 @@ wheels = [ [[package]] name = "itsdangerous" version = "2.2.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/9c/cb/8ac0172223afbccb63986cc25049b154ecfb5e85932587206f42317be31d/itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173", size = 54410, upload-time = "2024-04-16T21:28:15.614Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", size = 16234, upload-time = "2024-04-16T21:28:14.499Z" }, @@ -1506,7 +1506,7 @@ wheels = [ [[package]] name = "jedi" version = "0.19.2" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "parso" }, ] @@ -1518,7 +1518,7 @@ wheels = [ [[package]] name = "jinja2" version = "3.1.6" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markupsafe" }, ] @@ -1530,7 +1530,7 @@ wheels = [ [[package]] name = "jmespath" version = "1.0.1" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/00/2a/e867e8531cf3e36b41201936b7fa7ba7b5702dbef42922193f05c8976cd6/jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe", size = 25843, upload-time = "2022-06-17T18:00:12.224Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/31/b4/b9b800c45527aadd64d5b442f9b932b00648617eb5d63d2c7a6587b7cafc/jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980", size = 20256, upload-time = "2022-06-17T18:00:10.251Z" }, @@ -1539,7 +1539,7 @@ wheels = [ [[package]] name = "joblib" version = "1.5.2" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/e8/5d/447af5ea094b9e4c4054f82e223ada074c552335b9b4b2d14bd9b35a67c4/joblib-1.5.2.tar.gz", hash = "sha256:3faa5c39054b2f03ca547da9b2f52fde67c06240c31853f306aea97f13647b55", size = 331077, upload-time = "2025-08-27T12:15:46.575Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/1e/e8/685f47e0d754320684db4425a0967f7d3fa70126bffd76110b7009a0090f/joblib-1.5.2-py3-none-any.whl", hash = "sha256:4e1f0bdbb987e6d843c70cf43714cb276623def372df3c22fe5266b2670bc241", size = 308396, upload-time = "2025-08-27T12:15:45.188Z" }, @@ -1548,7 +1548,7 @@ wheels = [ [[package]] name = "joserfc" version = "1.4.2" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cryptography" }, ] @@ -1560,7 +1560,7 @@ wheels = [ [[package]] name = "json5" version = "0.12.1" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/12/ae/929aee9619e9eba9015207a9d2c1c54db18311da7eb4dcf6d41ad6f0eb67/json5-0.12.1.tar.gz", hash = "sha256:b2743e77b3242f8d03c143dd975a6ec7c52e2f2afe76ed934e53503dd4ad4990", size = 52191, upload-time = "2025-08-12T19:47:42.583Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/85/e2/05328bd2621be49a6fed9e3030b1e51a2d04537d3f816d211b9cc53c5262/json5-0.12.1-py3-none-any.whl", hash = "sha256:d9c9b3bc34a5f54d43c35e11ef7cb87d8bdd098c6ace87117a7b7e83e705c1d5", size = 36119, upload-time = "2025-08-12T19:47:41.131Z" }, @@ -1569,7 +1569,7 @@ wheels = [ [[package]] name = "jsonpatch" version = "1.33" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jsonpointer" }, ] @@ -1581,7 +1581,7 @@ wheels = [ [[package]] name = "jsonpath-ng" version = "1.7.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "ply" }, ] @@ -1593,7 +1593,7 @@ wheels = [ [[package]] name = "jsonpointer" version = "3.0.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/6a/0a/eebeb1fa92507ea94016a2a790b93c2ae41a7e18778f85471dc54475ed25/jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef", size = 9114, upload-time = "2024-06-10T19:24:42.462Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942", size = 7595, upload-time = "2024-06-10T19:24:40.698Z" }, @@ -1602,7 +1602,7 @@ wheels = [ [[package]] name = "jsonschema" version = "4.25.1" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "attrs" }, { name = "jsonschema-specifications" }, @@ -1630,7 +1630,7 @@ format-nongpl = [ [[package]] name = "jsonschema-path" version = "0.3.4" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pathable" }, { name = "pyyaml" }, @@ -1645,7 +1645,7 @@ wheels = [ [[package]] name = "jsonschema-specifications" version = "2025.9.1" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "referencing" }, ] @@ -1657,7 +1657,7 @@ wheels = [ [[package]] name = "jupyter" version = "1.1.1" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "ipykernel" }, { name = "ipywidgets" }, @@ -1674,7 +1674,7 @@ wheels = [ [[package]] name = "jupyter-client" version = "8.7.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jupyter-core" }, { name = "python-dateutil" }, @@ -1690,7 +1690,7 @@ wheels = [ [[package]] name = "jupyter-console" version = "6.6.3" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "ipykernel" }, { name = "ipython" }, @@ -1709,7 +1709,7 @@ wheels = [ [[package]] name = "jupyter-core" version = "5.9.1" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "platformdirs" }, { name = "traitlets" }, @@ -1722,7 +1722,7 @@ wheels = [ [[package]] name = "jupyter-events" version = "0.12.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jsonschema", extra = ["format-nongpl"] }, { name = "packaging" }, @@ -1741,7 +1741,7 @@ wheels = [ [[package]] name = "jupyter-lsp" version = "2.3.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jupyter-server" }, ] @@ -1753,7 +1753,7 @@ wheels = [ [[package]] name = "jupyter-server" version = "2.17.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, { name = "argon2-cffi" }, @@ -1782,7 +1782,7 @@ wheels = [ [[package]] name = "jupyter-server-terminals" version = "0.5.3" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pywinpty", marker = "os_name == 'nt'" }, { name = "terminado" }, @@ -1795,7 +1795,7 @@ wheels = [ [[package]] name = "jupyterlab" version = "4.5.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "async-lru" }, { name = "httpx" }, @@ -1819,7 +1819,7 @@ wheels = [ [[package]] name = "jupyterlab-pygments" version = "0.3.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/90/51/9187be60d989df97f5f0aba133fa54e7300f17616e065d1ada7d7646b6d6/jupyterlab_pygments-0.3.0.tar.gz", hash = "sha256:721aca4d9029252b11cfa9d185e5b5af4d54772bb8072f9b7036f4170054d35d", size = 512900, upload-time = "2023-11-23T09:26:37.44Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl", hash = "sha256:841a89020971da1d8693f1a99997aefc5dc424bb1b251fd6322462a1b8842780", size = 15884, upload-time = "2023-11-23T09:26:34.325Z" }, @@ -1828,7 +1828,7 @@ wheels = [ [[package]] name = "jupyterlab-server" version = "2.28.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "babel" }, { name = "jinja2" }, @@ -1846,7 +1846,7 @@ wheels = [ [[package]] name = "jupyterlab-widgets" version = "3.0.16" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/26/2d/ef58fed122b268c69c0aa099da20bc67657cdfb2e222688d5731bd5b971d/jupyterlab_widgets-3.0.16.tar.gz", hash = "sha256:423da05071d55cf27a9e602216d35a3a65a3e41cdf9c5d3b643b814ce38c19e0", size = 897423, upload-time = "2025-11-01T21:11:29.724Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl", hash = "sha256:45fa36d9c6422cf2559198e4db481aa243c7a32d9926b500781c830c80f7ecf8", size = 914926, upload-time = "2025-11-01T21:11:28.008Z" }, @@ -1855,7 +1855,7 @@ wheels = [ [[package]] name = "kaleido" version = "1.2.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "choreographer" }, { name = "logistro" }, @@ -1871,7 +1871,7 @@ wheels = [ [[package]] name = "kiwisolver" version = "1.4.9" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/5c/3c/85844f1b0feb11ee581ac23fe5fce65cd049a200c1446708cc1b7f922875/kiwisolver-1.4.9.tar.gz", hash = "sha256:c3b22c26c6fd6811b0ae8363b95ca8ce4ea3c202d3d0975b2914310ceb1bcc4d", size = 97564, upload-time = "2025-08-10T21:27:49.279Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/86/c9/13573a747838aeb1c76e3267620daa054f4152444d1f3d1a2324b78255b5/kiwisolver-1.4.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ac5a486ac389dddcc5bef4f365b6ae3ffff2c433324fb38dd35e3fab7c957999", size = 123686, upload-time = "2025-08-10T21:26:10.034Z" }, @@ -1943,7 +1943,7 @@ wheels = [ [[package]] name = "lark" version = "1.3.1" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/da/34/28fff3ab31ccff1fd4f6c7c7b0ceb2b6968d8ea4950663eadcb5720591a0/lark-1.3.1.tar.gz", hash = "sha256:b426a7a6d6d53189d318f2b6236ab5d6429eaf09259f1ca33eb716eed10d2905", size = 382732, upload-time = "2025-10-27T18:25:56.653Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/82/3d/14ce75ef66813643812f3093ab17e46d3a206942ce7376d31ec2d36229e7/lark-1.3.1-py3-none-any.whl", hash = "sha256:c629b661023a014c37da873b4ff58a817398d12635d3bbb2c5a03be7fe5d1e12", size = 113151, upload-time = "2025-10-27T18:25:54.882Z" }, @@ -1952,7 +1952,7 @@ wheels = [ [[package]] name = "lazy-object-proxy" version = "1.12.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/08/a2/69df9c6ba6d316cfd81fe2381e464db3e6de5db45f8c43c6a23504abf8cb/lazy_object_proxy-1.12.0.tar.gz", hash = "sha256:1f5a462d92fd0cfb82f1fab28b51bfb209fabbe6aabf7f0d51472c0c124c0c61", size = 43681, upload-time = "2025-08-22T13:50:06.783Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/0d/1b/b5f5bd6bda26f1e15cd3232b223892e4498e34ec70a7f4f11c401ac969f1/lazy_object_proxy-1.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8ee0d6027b760a11cc18281e702c0309dd92da458a74b4c15025d7fc490deede", size = 26746, upload-time = "2025-08-22T13:42:37.572Z" }, @@ -1984,7 +1984,7 @@ wheels = [ [[package]] name = "license-expression" version = "30.4.4" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "boolean-py" }, ] @@ -1996,7 +1996,7 @@ wheels = [ [[package]] name = "licensecheck" version = "2025.1.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "appdirs" }, { name = "fhconfparser" }, @@ -2019,7 +2019,7 @@ wheels = [ [[package]] name = "lightgbm" version = "4.6.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, { name = "scipy" }, @@ -2036,7 +2036,7 @@ wheels = [ [[package]] name = "logistro" version = "2.0.1" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/08/90/bfd7a6fab22bdfafe48ed3c4831713cb77b4779d18ade5e248d5dbc0ca22/logistro-2.0.1.tar.gz", hash = "sha256:8446affc82bab2577eb02bfcbcae196ae03129287557287b6a070f70c1985047", size = 8398, upload-time = "2025-11-01T02:41:18.81Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/54/20/6aa79ba3570bddd1bf7e951c6123f806751e58e8cce736bad77b2cf348d7/logistro-2.0.1-py3-none-any.whl", hash = "sha256:06ffa127b9fb4ac8b1972ae6b2a9d7fde57598bf5939cd708f43ec5bba2d31eb", size = 8555, upload-time = "2025-11-01T02:41:17.587Z" }, @@ -2045,7 +2045,7 @@ wheels = [ [[package]] name = "loguru" version = "0.7.3" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, { name = "win32-setctime", marker = "sys_platform == 'win32'" }, @@ -2058,7 +2058,7 @@ wheels = [ [[package]] name = "markdown" version = "3.10" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/7d/ab/7dd27d9d863b3376fcf23a5a13cb5d024aed1db46f963f1b5735ae43b3be/markdown-3.10.tar.gz", hash = "sha256:37062d4f2aa4b2b6b32aefb80faa300f82cc790cb949a35b8caede34f2b68c0e", size = 364931, upload-time = "2025-11-03T19:51:15.007Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/70/81/54e3ce63502cd085a0c556652a4e1b919c45a446bd1e5300e10c44c8c521/markdown-3.10-py3-none-any.whl", hash = "sha256:b5b99d6951e2e4948d939255596523444c0e677c669700b1d17aa4a8a464cb7c", size = 107678, upload-time = "2025-11-03T19:51:13.887Z" }, @@ -2067,7 +2067,7 @@ wheels = [ [[package]] name = "markdown-it-py" version = "3.0.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mdurl" }, ] @@ -2079,7 +2079,7 @@ wheels = [ [[package]] name = "markupsafe" version = "3.0.3" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, @@ -2142,7 +2142,7 @@ wheels = [ [[package]] name = "matplotlib" version = "3.10.7" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "contourpy" }, { name = "cycler" }, @@ -2196,7 +2196,7 @@ wheels = [ [[package]] name = "matplotlib-inline" version = "0.2.1" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "traitlets" }, ] @@ -2208,7 +2208,7 @@ wheels = [ [[package]] name = "mdit-py-plugins" version = "0.5.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markdown-it-py" }, ] @@ -2220,7 +2220,7 @@ wheels = [ [[package]] name = "mdurl" version = "0.1.2" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, @@ -2234,7 +2234,7 @@ source = { git = "https://github.com/microsoft/python-type-stubs.git#692c37c3969 [[package]] name = "mistune" version = "3.1.4" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/d7/02/a7fb8b21d4d55ac93cdcde9d3638da5dd0ebdd3a4fed76c7725e10b81cbe/mistune-3.1.4.tar.gz", hash = "sha256:b5a7f801d389f724ec702840c11d8fc48f2b33519102fc7ee739e8177b672164", size = 94588, upload-time = "2025-08-29T07:20:43.594Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/7a/f0/8282d9641415e9e33df173516226b404d367a0fc55e1a60424a152913abc/mistune-3.1.4-py3-none-any.whl", hash = "sha256:93691da911e5d9d2e23bc54472892aff676df27a75274962ff9edc210364266d", size = 53481, upload-time = "2025-08-29T07:20:42.218Z" }, @@ -2243,7 +2243,7 @@ wheels = [ [[package]] name = "mlflow-skinny" version = "3.6.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cachetools" }, { name = "click" }, @@ -2273,7 +2273,7 @@ wheels = [ [[package]] name = "moto" version = "5.1.16" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "boto3" }, { name = "botocore" }, @@ -2315,7 +2315,7 @@ server = [ [[package]] name = "mpmath" version = "1.3.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f", size = 508106, upload-time = "2023-03-07T16:47:11.061Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198, upload-time = "2023-03-07T16:47:09.197Z" }, @@ -2324,7 +2324,7 @@ wheels = [ [[package]] name = "multidict" version = "6.7.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/80/1e/5492c365f222f907de1039b91f922b93fa4f764c713ee858d235495d8f50/multidict-6.7.0.tar.gz", hash = "sha256:c6e99d9a65ca282e578dfea819cfa9c0a62b2499d8677392e09feaf305e9e6f5", size = 101834, upload-time = "2025-10-06T14:52:30.657Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/c2/9e/9f61ac18d9c8b475889f32ccfa91c9f59363480613fc807b6e3023d6f60b/multidict-6.7.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8a3862568a36d26e650a19bb5cbbba14b71789032aebc0423f8cc5f150730184", size = 76877, upload-time = "2025-10-06T14:49:20.884Z" }, @@ -2423,7 +2423,7 @@ wheels = [ [[package]] name = "myst-parser" version = "4.0.1" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "docutils" }, { name = "jinja2" }, @@ -2440,7 +2440,7 @@ wheels = [ [[package]] name = "narwhals" version = "2.12.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/93/f8/e1c28f24b641871c14ccae7ba6381f3c7827789a06e947ce975ae8a9075a/narwhals-2.12.0.tar.gz", hash = "sha256:075b6d56f3a222613793e025744b129439ecdff9292ea6615dd983af7ba6ea44", size = 590404, upload-time = "2025-11-17T10:53:28.381Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/0b/9a/c6f79de7ba3a0a8473129936b7b90aa461d3d46fec6f1627672b1dccf4e9/narwhals-2.12.0-py3-none-any.whl", hash = "sha256:baeba5d448a30b04c299a696bd9ee5ff73e4742143e06c49ca316b46539a7cbb", size = 425014, upload-time = "2025-11-17T10:53:26.65Z" }, @@ -2449,7 +2449,7 @@ wheels = [ [[package]] name = "natsort" version = "8.4.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/e2/a9/a0c57aee75f77794adaf35322f8b6404cbd0f89ad45c87197a937764b7d0/natsort-8.4.0.tar.gz", hash = "sha256:45312c4a0e5507593da193dedd04abb1469253b601ecaf63445ad80f0a1ea581", size = 76575, upload-time = "2023-06-20T04:17:19.925Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/ef/82/7a9d0550484a62c6da82858ee9419f3dd1ccc9aa1c26a1e43da3ecd20b0d/natsort-8.4.0-py3-none-any.whl", hash = "sha256:4732914fb471f56b5cce04d7bae6f164a592c7712e1c85f9ef585e197299521c", size = 38268, upload-time = "2023-06-20T04:17:17.522Z" }, @@ -2458,7 +2458,7 @@ wheels = [ [[package]] name = "nbclient" version = "0.10.2" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jupyter-client" }, { name = "jupyter-core" }, @@ -2473,7 +2473,7 @@ wheels = [ [[package]] name = "nbconvert" version = "7.16.6" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "beautifulsoup4" }, { name = "bleach", extra = ["css"] }, @@ -2498,7 +2498,7 @@ wheels = [ [[package]] name = "nbformat" version = "5.10.4" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "fastjsonschema" }, { name = "jsonschema" }, @@ -2513,7 +2513,7 @@ wheels = [ [[package]] name = "nest-asyncio" version = "1.6.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/83/f8/51569ac65d696c8ecbee95938f89d4abf00f47d58d48f6fbabfe8f0baefe/nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe", size = 7418, upload-time = "2024-01-21T14:25:19.227Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c", size = 5195, upload-time = "2024-01-21T14:25:17.223Z" }, @@ -2522,7 +2522,7 @@ wheels = [ [[package]] name = "networkx" version = "3.5" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/6c/4f/ccdb8ad3a38e583f214547fd2f7ff1fc160c43a75af88e6aec213404b96a/networkx-3.5.tar.gz", hash = "sha256:d4c6f9cf81f52d69230866796b82afbccdec3db7ae4fbd1b65ea750feed50037", size = 2471065, upload-time = "2025-05-29T11:35:07.804Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/eb/8d/776adee7bbf76365fdd7f2552710282c79a4ead5d2a46408c9043a2b70ba/networkx-3.5-py3-none-any.whl", hash = "sha256:0030d386a9a06dee3565298b4a734b68589749a544acbb6c412dc9e2489ec6ec", size = 2034406, upload-time = "2025-05-29T11:35:04.961Z" }, @@ -2531,7 +2531,7 @@ wheels = [ [[package]] name = "nodeenv" version = "1.9.1" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437, upload-time = "2024-06-04T18:44:11.171Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314, upload-time = "2024-06-04T18:44:08.352Z" }, @@ -2540,7 +2540,7 @@ wheels = [ [[package]] name = "notebook" version = "7.5.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jupyter-server" }, { name = "jupyterlab" }, @@ -2556,7 +2556,7 @@ wheels = [ [[package]] name = "notebook-shim" version = "0.2.4" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jupyter-server" }, ] @@ -2568,7 +2568,7 @@ wheels = [ [[package]] name = "numpy" version = "2.3.5" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/76/65/21b3bc86aac7b8f2862db1e808f1ea22b028e30a225a34a5ede9bf8678f2/numpy-2.3.5.tar.gz", hash = "sha256:784db1dcdab56bf0517743e746dfb0f885fc68d948aba86eeec2cba234bdf1c0", size = 20584950, upload-time = "2025-11-16T22:52:42.067Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/44/37/e669fe6cbb2b96c62f6bbedc6a81c0f3b7362f6a59230b23caa673a85721/numpy-2.3.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:74ae7b798248fe62021dbf3c914245ad45d1a6b0cb4a29ecb4b31d0bfbc4cc3e", size = 16733873, upload-time = "2025-11-16T22:49:49.84Z" }, @@ -2631,7 +2631,7 @@ wheels = [ [[package]] name = "numpy-typing-compat" version = "20250818.2.3" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] @@ -2643,7 +2643,7 @@ wheels = [ [[package]] name = "nvidia-nccl-cu12" version = "2.28.9" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } wheels = [ { url = "https://files.pythonhosted.org/packages/08/c4/120d2dfd92dff2c776d68f361ff8705fdea2ca64e20b612fab0fd3f581ac/nvidia_nccl_cu12-2.28.9-py3-none-manylinux_2_18_aarch64.whl", hash = "sha256:50a36e01c4a090b9f9c47d92cec54964de6b9fcb3362d0e19b8ffc6323c21b60", size = 296766525, upload-time = "2025-11-18T05:49:16.094Z" }, { url = "https://files.pythonhosted.org/packages/4a/4e/44dbb46b3d1b0ec61afda8e84837870f2f9ace33c564317d59b70bc19d3e/nvidia_nccl_cu12-2.28.9-py3-none-manylinux_2_18_x86_64.whl", hash = "sha256:485776daa8447da5da39681af455aa3b2c2586ddcf4af8772495e7c532c7e5ab", size = 296782137, upload-time = "2025-11-18T05:49:34.248Z" }, @@ -2652,7 +2652,7 @@ wheels = [ [[package]] name = "openapi-schema-validator" version = "0.6.3" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jsonschema" }, { name = "jsonschema-specifications" }, @@ -2666,7 +2666,7 @@ wheels = [ [[package]] name = "openapi-spec-validator" version = "0.7.2" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jsonschema" }, { name = "jsonschema-path" }, @@ -2772,19 +2772,28 @@ dependencies = [ [package.optional-dependencies] all = [ + { name = "openstef-meta" }, + { name = "openstef-models" }, { name = "s3fs" }, ] +baselines = [ + { name = "openstef-meta" }, + { name = "openstef-models" }, +] [package.metadata] requires-dist = [ + { name = "openstef-beam", extras = ["baselines"], marker = "extra == 'all'", editable = "packages/openstef-beam" }, { name = "openstef-core", editable = "packages/openstef-core" }, + { name = "openstef-meta", marker = "extra == 'baselines'", editable = "packages/openstef-meta" }, + { name = "openstef-models", marker = "extra == 'baselines'", editable = "packages/openstef-models" }, { name = "plotly", specifier = ">=6.3" }, { name = "pyyaml", specifier = ">=6.0.2" }, { name = "s3fs", marker = "extra == 'all'", specifier = ">=2025.5.1" }, { name = "scoringrules", specifier = ">=0.8" }, { name = "tqdm", specifier = ">=4.67.1" }, ] -provides-extras = ["all"] +provides-extras = ["baselines", "all"] [[package]] name = "openstef-core" @@ -2946,7 +2955,7 @@ provides-extras = ["lgbm", "xgb-cpu", "xgb-gpu"] [[package]] name = "opentelemetry-api" version = "1.38.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "importlib-metadata" }, { name = "typing-extensions" }, @@ -2959,7 +2968,7 @@ wheels = [ [[package]] name = "opentelemetry-proto" version = "1.38.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "protobuf" }, ] @@ -2971,7 +2980,7 @@ wheels = [ [[package]] name = "opentelemetry-sdk" version = "1.38.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "opentelemetry-semantic-conventions" }, @@ -2985,7 +2994,7 @@ wheels = [ [[package]] name = "opentelemetry-semantic-conventions" version = "0.59b0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "typing-extensions" }, @@ -2998,7 +3007,7 @@ wheels = [ [[package]] name = "optype" version = "0.14.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] @@ -3016,7 +3025,7 @@ numpy = [ [[package]] name = "orjson" version = "3.11.5" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/04/b8/333fdb27840f3bf04022d21b654a35f58e15407183aeb16f3b41aa053446/orjson-3.11.5.tar.gz", hash = "sha256:82393ab47b4fe44ffd0a7659fa9cfaacc717eb617c93cde83795f14af5c2e9d5", size = 5972347, upload-time = "2025-12-06T15:55:39.458Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/ef/a4/8052a029029b096a78955eadd68ab594ce2197e24ec50e6b6d2ab3f4e33b/orjson-3.11.5-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:334e5b4bff9ad101237c2d799d9fd45737752929753bf4faf4b207335a416b7d", size = 245347, upload-time = "2025-12-06T15:54:22.061Z" }, @@ -3069,7 +3078,7 @@ wheels = [ [[package]] name = "packaging" version = "25.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, @@ -3078,7 +3087,7 @@ wheels = [ [[package]] name = "pandas" version = "2.3.3" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, { name = "python-dateutil" }, @@ -3125,7 +3134,7 @@ wheels = [ [[package]] name = "pandas-stubs" version = "2.3.2.250926" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, { name = "types-pytz" }, @@ -3138,7 +3147,7 @@ wheels = [ [[package]] name = "pandocfilters" version = "1.5.1" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/70/6f/3dd4940bbe001c06a65f88e36bad298bc7a0de5036115639926b0c5c0458/pandocfilters-1.5.1.tar.gz", hash = "sha256:002b4a555ee4ebc03f8b66307e287fa492e4a77b4ea14d3f934328297bb4939e", size = 8454, upload-time = "2024-01-18T20:08:13.726Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/ef/af/4fbc8cab944db5d21b7e2a5b8e9211a03a79852b1157e2c102fcc61ac440/pandocfilters-1.5.1-py2.py3-none-any.whl", hash = "sha256:93be382804a9cdb0a7267585f157e5d1731bbe5545a85b268d6f5fe6232de2bc", size = 8663, upload-time = "2024-01-18T20:08:11.28Z" }, @@ -3147,7 +3156,7 @@ wheels = [ [[package]] name = "parso" version = "0.8.5" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/d4/de/53e0bcf53d13e005bd8c92e7855142494f41171b34c2536b86187474184d/parso-0.8.5.tar.gz", hash = "sha256:034d7354a9a018bdce352f48b2a8a450f05e9d6ee85db84764e9b6bd96dafe5a", size = 401205, upload-time = "2025-08-23T15:15:28.028Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/16/32/f8e3c85d1d5250232a5d3477a2a28cc291968ff175caeadaf3cc19ce0e4a/parso-0.8.5-py2.py3-none-any.whl", hash = "sha256:646204b5ee239c396d040b90f9e272e9a8017c630092bf59980beb62fd033887", size = 106668, upload-time = "2025-08-23T15:15:25.663Z" }, @@ -3156,7 +3165,7 @@ wheels = [ [[package]] name = "pastel" version = "0.2.1" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/76/f1/4594f5e0fcddb6953e5b8fe00da8c317b8b41b547e2b3ae2da7512943c62/pastel-0.2.1.tar.gz", hash = "sha256:e6581ac04e973cac858828c6202c1e1e81fee1dc7de7683f3e1ffe0bfd8a573d", size = 7555, upload-time = "2020-09-16T19:21:12.43Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/aa/18/a8444036c6dd65ba3624c63b734d3ba95ba63ace513078e1580590075d21/pastel-0.2.1-py2.py3-none-any.whl", hash = "sha256:4349225fcdf6c2bb34d483e523475de5bb04a5c10ef711263452cb37d7dd4364", size = 5955, upload-time = "2020-09-16T19:21:11.409Z" }, @@ -3165,7 +3174,7 @@ wheels = [ [[package]] name = "pathable" version = "0.4.4" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/67/93/8f2c2075b180c12c1e9f6a09d1a985bc2036906b13dff1d8917e395f2048/pathable-0.4.4.tar.gz", hash = "sha256:6905a3cd17804edfac7875b5f6c9142a218c7caef78693c2dbbbfbac186d88b2", size = 8124, upload-time = "2025-01-10T18:43:13.247Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/7d/eb/b6260b31b1a96386c0a880edebe26f89669098acea8e0318bff6adb378fd/pathable-0.4.4-py3-none-any.whl", hash = "sha256:5ae9e94793b6ef5a4cbe0a7ce9dbbefc1eec38df253763fd0aeeacf2762dbbc2", size = 9592, upload-time = "2025-01-10T18:43:11.88Z" }, @@ -3174,7 +3183,7 @@ wheels = [ [[package]] name = "pexpect" version = "4.9.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "ptyprocess" }, ] @@ -3186,7 +3195,7 @@ wheels = [ [[package]] name = "pillow" version = "12.0.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/cace85a1b0c9775a9f8f5d5423c8261c858760e2466c79b2dd184638b056/pillow-12.0.0.tar.gz", hash = "sha256:87d4f8125c9988bfbed67af47dd7a953e2fc7b0cc1e7800ec6d2080d490bb353", size = 47008828, upload-time = "2025-10-15T18:24:14.008Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/2c/90/4fcce2c22caf044e660a198d740e7fbc14395619e3cb1abad12192c0826c/pillow-12.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:53561a4ddc36facb432fae7a9d8afbfaf94795414f5cdc5fc52f28c1dca90371", size = 5249377, upload-time = "2025-10-15T18:22:05.993Z" }, @@ -3255,7 +3264,7 @@ wheels = [ [[package]] name = "platformdirs" version = "4.5.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/61/33/9611380c2bdb1225fdef633e2a9610622310fed35ab11dac9620972ee088/platformdirs-4.5.0.tar.gz", hash = "sha256:70ddccdd7c99fc5942e9fc25636a8b34d04c24b335100223152c2803e4063312", size = 21632, upload-time = "2025-10-08T17:44:48.791Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/73/cb/ac7874b3e5d58441674fb70742e6c374b28b0c7cb988d37d991cde47166c/platformdirs-4.5.0-py3-none-any.whl", hash = "sha256:e578a81bb873cbb89a41fcc904c7ef523cc18284b7e3b3ccf06aca1403b7ebd3", size = 18651, upload-time = "2025-10-08T17:44:47.223Z" }, @@ -3264,7 +3273,7 @@ wheels = [ [[package]] name = "plotly" version = "6.4.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "narwhals" }, { name = "packaging" }, @@ -3277,7 +3286,7 @@ wheels = [ [[package]] name = "pluggy" version = "1.6.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, @@ -3286,7 +3295,7 @@ wheels = [ [[package]] name = "ply" version = "3.11" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/e5/69/882ee5c9d017149285cab114ebeab373308ef0f874fcdac9beb90e0ac4da/ply-3.11.tar.gz", hash = "sha256:00c7c1aaa88358b9c765b6d3000c6eec0ba42abca5351b095321aef446081da3", size = 159130, upload-time = "2018-02-15T19:01:31.097Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl", hash = "sha256:096f9b8350b65ebd2fd1346b12452efe5b9607f7482813ffca50c22722a807ce", size = 49567, upload-time = "2018-02-15T19:01:27.172Z" }, @@ -3295,7 +3304,7 @@ wheels = [ [[package]] name = "poethepoet" version = "0.37.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pastel" }, { name = "pyyaml" }, @@ -3308,7 +3317,7 @@ wheels = [ [[package]] name = "prometheus-client" version = "0.23.1" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/23/53/3edb5d68ecf6b38fcbcc1ad28391117d2a322d9a1a3eff04bfdb184d8c3b/prometheus_client-0.23.1.tar.gz", hash = "sha256:6ae8f9081eaaaf153a2e959d2e6c4f4fb57b12ef76c8c7980202f1e57b48b2ce", size = 80481, upload-time = "2025-09-18T20:47:25.043Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/b8/db/14bafcb4af2139e046d03fd00dea7873e48eafe18b7d2797e73d6681f210/prometheus_client-0.23.1-py3-none-any.whl", hash = "sha256:dd1913e6e76b59cfe44e7a4b83e01afc9873c1bdfd2ed8739f1e76aeca115f99", size = 61145, upload-time = "2025-09-18T20:47:23.875Z" }, @@ -3317,7 +3326,7 @@ wheels = [ [[package]] name = "prompt-toolkit" version = "3.0.52" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "wcwidth" }, ] @@ -3329,7 +3338,7 @@ wheels = [ [[package]] name = "propcache" version = "0.4.1" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/9e/da/e9fc233cf63743258bff22b3dfa7ea5baef7b5bc324af47a0ad89b8ffc6f/propcache-0.4.1.tar.gz", hash = "sha256:f48107a8c637e80362555f37ecf49abe20370e557cc4ab374f04ec4423c97c3d", size = 46442, upload-time = "2025-10-08T19:49:02.291Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/a2/0f/f17b1b2b221d5ca28b4b876e8bb046ac40466513960646bda8e1853cdfa2/propcache-0.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e153e9cd40cc8945138822807139367f256f89c6810c2634a4f6902b52d3b4e2", size = 80061, upload-time = "2025-10-08T19:46:46.075Z" }, @@ -3413,7 +3422,7 @@ wheels = [ [[package]] name = "protobuf" version = "6.33.1" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/0a/03/a1440979a3f74f16cab3b75b0da1a1a7f922d56a8ddea96092391998edc0/protobuf-6.33.1.tar.gz", hash = "sha256:97f65757e8d09870de6fd973aeddb92f85435607235d20b2dfed93405d00c85b", size = 443432, upload-time = "2025-11-13T16:44:18.895Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/06/f1/446a9bbd2c60772ca36556bac8bfde40eceb28d9cc7838755bc41e001d8f/protobuf-6.33.1-cp310-abi3-win32.whl", hash = "sha256:f8d3fdbc966aaab1d05046d0240dd94d40f2a8c62856d41eaa141ff64a79de6b", size = 425593, upload-time = "2025-11-13T16:44:06.275Z" }, @@ -3428,7 +3437,7 @@ wheels = [ [[package]] name = "psutil" version = "7.1.3" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/e1/88/bdd0a41e5857d5d703287598cbf08dad90aed56774ea52ae071bae9071b6/psutil-7.1.3.tar.gz", hash = "sha256:6c86281738d77335af7aec228328e944b30930899ea760ecf33a4dba66be5e74", size = 489059, upload-time = "2025-11-02T12:25:54.619Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/bd/93/0c49e776b8734fef56ec9c5c57f923922f2cf0497d62e0f419465f28f3d0/psutil-7.1.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0005da714eee687b4b8decd3d6cc7c6db36215c9e74e5ad2264b90c3df7d92dc", size = 239751, upload-time = "2025-11-02T12:25:58.161Z" }, @@ -3454,7 +3463,7 @@ wheels = [ [[package]] name = "ptyprocess" version = "0.7.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/20/e5/16ff212c1e452235a90aeb09066144d0c5a6a8c0834397e03f5224495c4e/ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220", size = 70762, upload-time = "2020-12-28T15:15:30.155Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", size = 13993, upload-time = "2020-12-28T15:15:28.35Z" }, @@ -3463,7 +3472,7 @@ wheels = [ [[package]] name = "pure-eval" version = "0.2.3" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/cd/05/0a34433a064256a578f1783a10da6df098ceaa4a57bbeaa96a6c0352786b/pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42", size = 19752, upload-time = "2024-07-21T12:58:21.801Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842, upload-time = "2024-07-21T12:58:20.04Z" }, @@ -3472,7 +3481,7 @@ wheels = [ [[package]] name = "pvlib" version = "0.13.1" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "h5py" }, { name = "numpy" }, @@ -3489,7 +3498,7 @@ wheels = [ [[package]] name = "py-partiql-parser" version = "0.6.3" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/56/7a/a0f6bda783eb4df8e3dfd55973a1ac6d368a89178c300e1b5b91cd181e5e/py_partiql_parser-0.6.3.tar.gz", hash = "sha256:09cecf916ce6e3da2c050f0cb6106166de42c33d34a078ec2eb19377ea70389a", size = 17456, upload-time = "2025-10-18T13:56:13.441Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/c9/33/a7cbfccc39056a5cf8126b7aab4c8bafbedd4f0ca68ae40ecb627a2d2cd3/py_partiql_parser-0.6.3-py2.py3-none-any.whl", hash = "sha256:deb0769c3346179d2f590dcbde556f708cdb929059fb654bad75f4cf6e07f582", size = 23752, upload-time = "2025-10-18T13:56:12.256Z" }, @@ -3498,7 +3507,7 @@ wheels = [ [[package]] name = "pyarrow" version = "22.0.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/30/53/04a7fdc63e6056116c9ddc8b43bc28c12cdd181b85cbeadb79278475f3ae/pyarrow-22.0.0.tar.gz", hash = "sha256:3d600dc583260d845c7d8a6db540339dd883081925da2bd1c5cb808f720b3cd9", size = 1151151, upload-time = "2025-10-24T12:30:00.762Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/af/63/ba23862d69652f85b615ca14ad14f3bcfc5bf1b99ef3f0cd04ff93fdad5a/pyarrow-22.0.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:bea79263d55c24a32b0d79c00a1c58bb2ee5f0757ed95656b01c0fb310c5af3d", size = 34211578, upload-time = "2025-10-24T10:05:21.583Z" }, @@ -3541,7 +3550,7 @@ wheels = [ [[package]] name = "pyasn1" version = "0.6.1" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/ba/e9/01f1a64245b89f039897cb0130016d79f77d52669aae6ee7b159a6c4c018/pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034", size = 145322, upload-time = "2024-09-10T22:41:42.55Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/c8/f1/d6a797abb14f6283c0ddff96bbdd46937f64122b8c925cab503dd37f8214/pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629", size = 83135, upload-time = "2024-09-11T16:00:36.122Z" }, @@ -3550,7 +3559,7 @@ wheels = [ [[package]] name = "pyasn1-modules" version = "0.4.2" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyasn1" }, ] @@ -3562,7 +3571,7 @@ wheels = [ [[package]] name = "pycountry" version = "24.6.1" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/76/57/c389fa68c50590881a75b7883eeb3dc15e9e73a0fdc001cdd45c13290c92/pycountry-24.6.1.tar.gz", hash = "sha256:b61b3faccea67f87d10c1f2b0fc0be714409e8fcdcc1315613174f6466c10221", size = 6043910, upload-time = "2024-06-01T04:12:15.05Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/b1/ec/1fb891d8a2660716aadb2143235481d15ed1cbfe3ad669194690b0604492/pycountry-24.6.1-py3-none-any.whl", hash = "sha256:f1a4fb391cd7214f8eefd39556d740adcc233c778a27f8942c8dca351d6ce06f", size = 6335189, upload-time = "2024-06-01T04:11:49.711Z" }, @@ -3571,7 +3580,7 @@ wheels = [ [[package]] name = "pycparser" version = "2.23" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", size = 173734, upload-time = "2025-09-09T13:23:47.91Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload-time = "2025-09-09T13:23:46.651Z" }, @@ -3580,7 +3589,7 @@ wheels = [ [[package]] name = "pydantic" version = "2.12.4" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-types" }, { name = "pydantic-core" }, @@ -3595,7 +3604,7 @@ wheels = [ [[package]] name = "pydantic-core" version = "2.41.5" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] @@ -3666,7 +3675,7 @@ wheels = [ [[package]] name = "pydantic-extra-types" version = "2.10.6" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pydantic" }, { name = "typing-extensions" }, @@ -3679,7 +3688,7 @@ wheels = [ [[package]] name = "pydata-sphinx-theme" version = "0.16.1" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "accessible-pygments" }, { name = "babel" }, @@ -3697,7 +3706,7 @@ wheels = [ [[package]] name = "pygments" version = "2.19.2" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, @@ -3706,7 +3715,7 @@ wheels = [ [[package]] name = "pyparsing" version = "3.2.5" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/f2/a5/181488fc2b9d093e3972d2a472855aae8a03f000592dbfce716a512b3359/pyparsing-3.2.5.tar.gz", hash = "sha256:2df8d5b7b2802ef88e8d016a2eb9c7aeaa923529cd251ed0fe4608275d4105b6", size = 1099274, upload-time = "2025-09-21T04:11:06.277Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-py3-none-any.whl", hash = "sha256:e38a4f02064cf41fe6593d328d0512495ad1f3d8a91c4f73fc401b3079a59a5e", size = 113890, upload-time = "2025-09-21T04:11:04.117Z" }, @@ -3715,7 +3724,7 @@ wheels = [ [[package]] name = "pyproject-fmt" version = "2.11.1" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "toml-fmt-common" }, ] @@ -3744,7 +3753,7 @@ wheels = [ [[package]] name = "pyright" version = "1.1.407" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "nodeenv" }, { name = "typing-extensions" }, @@ -3757,7 +3766,7 @@ wheels = [ [[package]] name = "pytest" version = "9.0.1" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, { name = "iniconfig" }, @@ -3773,7 +3782,7 @@ wheels = [ [[package]] name = "pytest-cov" version = "7.0.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "coverage" }, { name = "pluggy" }, @@ -3787,7 +3796,7 @@ wheels = [ [[package]] name = "pytest-timeout" version = "2.4.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pytest" }, ] @@ -3799,7 +3808,7 @@ wheels = [ [[package]] name = "pytest-xdist" version = "3.8.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "execnet" }, { name = "pytest" }, @@ -3812,7 +3821,7 @@ wheels = [ [[package]] name = "python-dateutil" version = "2.9.0.post0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "six" }, ] @@ -3824,7 +3833,7 @@ wheels = [ [[package]] name = "python-debian" version = "1.0.1" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "charset-normalizer" }, ] @@ -3836,7 +3845,7 @@ wheels = [ [[package]] name = "python-dotenv" version = "1.2.1" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/f0/26/19cadc79a718c5edbec86fd4919a6b6d3f681039a2f6d66d14be94e75fb9/python_dotenv-1.2.1.tar.gz", hash = "sha256:42667e897e16ab0d66954af0e60a9caa94f0fd4ecf3aaf6d2d260eec1aa36ad6", size = 44221, upload-time = "2025-10-26T15:12:10.434Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/14/1b/a298b06749107c305e1fe0f814c6c74aea7b2f1e10989cb30f544a1b3253/python_dotenv-1.2.1-py3-none-any.whl", hash = "sha256:b81ee9561e9ca4004139c6cbba3a238c32b03e4894671e181b671e8cb8425d61", size = 21230, upload-time = "2025-10-26T15:12:09.109Z" }, @@ -3845,7 +3854,7 @@ wheels = [ [[package]] name = "python-json-logger" version = "4.0.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/29/bf/eca6a3d43db1dae7070f70e160ab20b807627ba953663ba07928cdd3dc58/python_json_logger-4.0.0.tar.gz", hash = "sha256:f58e68eb46e1faed27e0f574a55a0455eecd7b8a5b88b85a784519ba3cff047f", size = 17683, upload-time = "2025-10-06T04:15:18.984Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl", hash = "sha256:af09c9daf6a813aa4cc7180395f50f2a9e5fa056034c9953aec92e381c5ba1e2", size = 15548, upload-time = "2025-10-06T04:15:17.553Z" }, @@ -3854,7 +3863,7 @@ wheels = [ [[package]] name = "python-magic" version = "0.4.27" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/da/db/0b3e28ac047452d079d375ec6798bf76a036a08182dbb39ed38116a49130/python-magic-0.4.27.tar.gz", hash = "sha256:c1ba14b08e4a5f5c31a302b7721239695b2f0f058d125bd5ce1ee36b9d9d3c3b", size = 14677, upload-time = "2022-06-07T20:16:59.508Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/6c/73/9f872cb81fc5c3bb48f7227872c28975f998f3e7c2b1c16e95e6432bbb90/python_magic-0.4.27-py2.py3-none-any.whl", hash = "sha256:c212960ad306f700aa0d01e5d7a325d20548ff97eb9920dcd29513174f0294d3", size = 13840, upload-time = "2022-06-07T20:16:57.763Z" }, @@ -3863,7 +3872,7 @@ wheels = [ [[package]] name = "pytz" version = "2025.2" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884, upload-time = "2025-03-25T02:25:00.538Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" }, @@ -3872,7 +3881,7 @@ wheels = [ [[package]] name = "pywin32" version = "311" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } wheels = [ { url = "https://files.pythonhosted.org/packages/e7/ab/01ea1943d4eba0f850c3c61e78e8dd59757ff815ff3ccd0a84de5f541f42/pywin32-311-cp312-cp312-win32.whl", hash = "sha256:750ec6e621af2b948540032557b10a2d43b0cee2ae9758c54154d711cc852d31", size = 8706543, upload-time = "2025-07-14T20:13:20.765Z" }, { url = "https://files.pythonhosted.org/packages/d1/a8/a0e8d07d4d051ec7502cd58b291ec98dcc0c3fff027caad0470b72cfcc2f/pywin32-311-cp312-cp312-win_amd64.whl", hash = "sha256:b8c095edad5c211ff31c05223658e71bf7116daa0ecf3ad85f3201ea3190d067", size = 9495040, upload-time = "2025-07-14T20:13:22.543Z" }, @@ -3888,7 +3897,7 @@ wheels = [ [[package]] name = "pywinpty" version = "3.0.2" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/f3/bb/a7cc2967c5c4eceb6cc49cfe39447d4bfc56e6c865e7c2249b6eb978935f/pywinpty-3.0.2.tar.gz", hash = "sha256:1505cc4cb248af42cb6285a65c9c2086ee9e7e574078ee60933d5d7fa86fb004", size = 30669, upload-time = "2025-10-03T21:16:29.205Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/02/4e/1098484e042c9485f56f16eb2b69b43b874bd526044ee401512234cf9e04/pywinpty-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:99fdd9b455f0ad6419aba6731a7a0d2f88ced83c3c94a80ff9533d95fa8d8a9e", size = 2050391, upload-time = "2025-10-03T21:19:01.642Z" }, @@ -3901,7 +3910,7 @@ wheels = [ [[package]] name = "pyyaml" version = "6.0.3" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, @@ -3947,7 +3956,7 @@ wheels = [ [[package]] name = "pyzmq" version = "27.1.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "implementation_name == 'pypy'" }, ] @@ -3990,7 +3999,7 @@ wheels = [ [[package]] name = "referencing" version = "0.36.2" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "attrs" }, { name = "rpds-py" }, @@ -4004,7 +4013,7 @@ wheels = [ [[package]] name = "regex" version = "2025.11.3" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/cc/a9/546676f25e573a4cf00fe8e119b78a37b6a8fe2dc95cda877b30889c9c45/regex-2025.11.3.tar.gz", hash = "sha256:1fedc720f9bb2494ce31a58a1631f9c82df6a09b49c19517ea5cc280b4541e01", size = 414669, upload-time = "2025-11-03T21:34:22.089Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/e8/74/18f04cb53e58e3fb107439699bd8375cf5a835eec81084e0bddbd122e4c2/regex-2025.11.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bc8ab71e2e31b16e40868a40a69007bc305e1109bd4658eb6cad007e0bf67c41", size = 489312, upload-time = "2025-11-03T21:31:34.343Z" }, @@ -4082,7 +4091,7 @@ wheels = [ [[package]] name = "requests" version = "2.32.4" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, { name = "charset-normalizer" }, @@ -4097,7 +4106,7 @@ wheels = [ [[package]] name = "requests-cache" version = "1.2.1" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "attrs" }, { name = "cattrs" }, @@ -4114,7 +4123,7 @@ wheels = [ [[package]] name = "requirements-parser" version = "0.13.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "packaging" }, ] @@ -4126,7 +4135,7 @@ wheels = [ [[package]] name = "responses" version = "0.25.8" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyyaml" }, { name = "requests" }, @@ -4140,7 +4149,7 @@ wheels = [ [[package]] name = "reuse" version = "6.2.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "attrs" }, { name = "click" }, @@ -4155,7 +4164,7 @@ sdist = { url = "https://files.pythonhosted.org/packages/05/35/298d9410b3635107c [[package]] name = "rfc3339-validator" version = "0.1.4" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "six" }, ] @@ -4167,7 +4176,7 @@ wheels = [ [[package]] name = "rfc3986-validator" version = "0.1.1" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/da/88/f270de456dd7d11dcc808abfa291ecdd3f45ff44e3b549ffa01b126464d0/rfc3986_validator-0.1.1.tar.gz", hash = "sha256:3d44bde7921b3b9ec3ae4e3adca370438eccebc676456449b145d533b240d055", size = 6760, upload-time = "2019-10-28T16:00:19.144Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/9e/51/17023c0f8f1869d8806b979a2bffa3f861f26a3f1a66b094288323fba52f/rfc3986_validator-0.1.1-py2.py3-none-any.whl", hash = "sha256:2f235c432ef459970b4306369336b9d5dbdda31b510ca1e327636e01f528bfa9", size = 4242, upload-time = "2019-10-28T16:00:13.976Z" }, @@ -4176,7 +4185,7 @@ wheels = [ [[package]] name = "rfc3987-syntax" version = "1.1.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "lark" }, ] @@ -4188,7 +4197,7 @@ wheels = [ [[package]] name = "rich" version = "14.2.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markdown-it-py" }, { name = "pygments" }, @@ -4201,7 +4210,7 @@ wheels = [ [[package]] name = "roman-numerals-py" version = "3.1.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/30/76/48fd56d17c5bdbdf65609abbc67288728a98ed4c02919428d4f52d23b24b/roman_numerals_py-3.1.0.tar.gz", hash = "sha256:be4bf804f083a4ce001b5eb7e3c0862479d10f94c936f6c4e5f250aa5ff5bd2d", size = 9017, upload-time = "2025-02-22T07:34:54.333Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/53/97/d2cbbaa10c9b826af0e10fdf836e1bf344d9f0abb873ebc34d1f49642d3f/roman_numerals_py-3.1.0-py3-none-any.whl", hash = "sha256:9da2ad2fb670bcf24e81070ceb3be72f6c11c440d73bd579fbeca1e9f330954c", size = 7742, upload-time = "2025-02-22T07:34:52.422Z" }, @@ -4210,7 +4219,7 @@ wheels = [ [[package]] name = "rpds-py" version = "0.29.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/98/33/23b3b3419b6a3e0f559c7c0d2ca8fc1b9448382b25245033788785921332/rpds_py-0.29.0.tar.gz", hash = "sha256:fe55fe686908f50154d1dc599232016e50c243b438c3b7432f24e2895b0e5359", size = 69359, upload-time = "2025-11-16T14:50:39.532Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/3c/50/bc0e6e736d94e420df79be4deb5c9476b63165c87bb8f19ef75d100d21b3/rpds_py-0.29.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a0891cfd8db43e085c0ab93ab7e9b0c8fee84780d436d3b266b113e51e79f954", size = 376000, upload-time = "2025-11-16T14:48:19.141Z" }, @@ -4291,7 +4300,7 @@ wheels = [ [[package]] name = "rsa" version = "4.9.1" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyasn1" }, ] @@ -4303,7 +4312,7 @@ wheels = [ [[package]] name = "ruff" version = "0.14.5" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/82/fa/fbb67a5780ae0f704876cb8ac92d6d76da41da4dc72b7ed3565ab18f2f52/ruff-0.14.5.tar.gz", hash = "sha256:8d3b48d7d8aad423d3137af7ab6c8b1e38e4de104800f0d596990f6ada1a9fc1", size = 5615944, upload-time = "2025-11-13T19:58:51.155Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/68/31/c07e9c535248d10836a94e4f4e8c5a31a1beed6f169b31405b227872d4f4/ruff-0.14.5-py3-none-linux_armv6l.whl", hash = "sha256:f3b8248123b586de44a8018bcc9fefe31d23dda57a34e6f0e1e53bd51fd63594", size = 13171630, upload-time = "2025-11-13T19:57:54.894Z" }, @@ -4329,7 +4338,7 @@ wheels = [ [[package]] name = "s3fs" version = "2025.10.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiobotocore" }, { name = "aiohttp" }, @@ -4343,7 +4352,7 @@ wheels = [ [[package]] name = "s3transfer" version = "0.14.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "botocore" }, ] @@ -4355,7 +4364,7 @@ wheels = [ [[package]] name = "scikit-learn" version = "1.7.2" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "joblib" }, { name = "numpy" }, @@ -4389,7 +4398,7 @@ wheels = [ [[package]] name = "scipy" version = "1.16.3" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] @@ -4450,7 +4459,7 @@ wheels = [ [[package]] name = "scipy-stubs" version = "1.16.3.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "optype", extra = ["numpy"] }, ] @@ -4462,7 +4471,7 @@ wheels = [ [[package]] name = "scoringrules" version = "0.8.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, { name = "scipy" }, @@ -4475,7 +4484,7 @@ wheels = [ [[package]] name = "send2trash" version = "1.8.3" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/fd/3a/aec9b02217bb79b87bbc1a21bc6abc51e3d5dcf65c30487ac96c0908c722/Send2Trash-1.8.3.tar.gz", hash = "sha256:b18e7a3966d99871aefeb00cfbcfdced55ce4871194810fc71f4aa484b953abf", size = 17394, upload-time = "2024-04-07T00:01:09.267Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/40/b0/4562db6223154aa4e22f939003cb92514c79f3d4dccca3444253fd17f902/Send2Trash-1.8.3-py3-none-any.whl", hash = "sha256:0c31227e0bd08961c7665474a3d1ef7193929fedda4233843689baa056be46c9", size = 18072, upload-time = "2024-04-07T00:01:07.438Z" }, @@ -4484,7 +4493,7 @@ wheels = [ [[package]] name = "setuptools" version = "80.9.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/18/5d/3bf57dcd21979b887f014ea83c24ae194cfcd12b9e0fda66b957c69d1fca/setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c", size = 1319958, upload-time = "2025-05-27T00:56:51.443Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486, upload-time = "2025-05-27T00:56:49.664Z" }, @@ -4493,7 +4502,7 @@ wheels = [ [[package]] name = "shellingham" version = "1.5.4" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, @@ -4502,7 +4511,7 @@ wheels = [ [[package]] name = "simplejson" version = "3.20.2" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/41/f4/a1ac5ed32f7ed9a088d62a59d410d4c204b3b3815722e2ccfb491fa8251b/simplejson-3.20.2.tar.gz", hash = "sha256:5fe7a6ce14d1c300d80d08695b7f7e633de6cd72c80644021874d985b3393649", size = 85784, upload-time = "2025-09-26T16:29:36.64Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/9d/9e/1a91e7614db0416885eab4136d49b7303de20528860ffdd798ce04d054db/simplejson-3.20.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:4376d5acae0d1e91e78baeba4ee3cf22fbf6509d81539d01b94e0951d28ec2b6", size = 93523, upload-time = "2025-09-26T16:28:00.356Z" }, @@ -4537,7 +4546,7 @@ wheels = [ [[package]] name = "six" version = "1.17.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, @@ -4546,7 +4555,7 @@ wheels = [ [[package]] name = "smmap" version = "5.0.2" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/44/cd/a040c4b3119bbe532e5b0732286f805445375489fceaec1f48306068ee3b/smmap-5.0.2.tar.gz", hash = "sha256:26ea65a03958fa0c8a1c7e8c7a58fdc77221b8910f6be2131affade476898ad5", size = 22329, upload-time = "2025-01-02T07:14:40.909Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/04/be/d09147ad1ec7934636ad912901c5fd7667e1c858e19d355237db0d0cd5e4/smmap-5.0.2-py3-none-any.whl", hash = "sha256:b30115f0def7d7531d22a0fb6502488d879e75b260a9db4d0819cfb25403af5e", size = 24303, upload-time = "2025-01-02T07:14:38.724Z" }, @@ -4555,7 +4564,7 @@ wheels = [ [[package]] name = "sniffio" version = "1.3.1" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, @@ -4564,7 +4573,7 @@ wheels = [ [[package]] name = "snowballstemmer" version = "3.0.1" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/75/a7/9810d872919697c9d01295633f5d574fb416d47e535f258272ca1f01f447/snowballstemmer-3.0.1.tar.gz", hash = "sha256:6d5eeeec8e9f84d4d56b847692bacf79bc2c8e90c7f80ca4444ff8b6f2e52895", size = 105575, upload-time = "2025-05-09T16:34:51.843Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/c8/78/3565d011c61f5a43488987ee32b6f3f656e7f107ac2782dd57bdd7d91d9a/snowballstemmer-3.0.1-py3-none-any.whl", hash = "sha256:6cd7b3897da8d6c9ffb968a6781fa6532dce9c3618a4b127d920dab764a19064", size = 103274, upload-time = "2025-05-09T16:34:50.371Z" }, @@ -4573,7 +4582,7 @@ wheels = [ [[package]] name = "soupsieve" version = "2.8" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/6d/e6/21ccce3262dd4889aa3332e5a119a3491a95e8f60939870a3a035aabac0d/soupsieve-2.8.tar.gz", hash = "sha256:e2dd4a40a628cb5f28f6d4b0db8800b8f581b65bb380b97de22ba5ca8d72572f", size = 103472, upload-time = "2025-08-27T15:39:51.78Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/14/a0/bb38d3b76b8cae341dad93a2dd83ab7462e6dbcdd84d43f54ee60a8dc167/soupsieve-2.8-py3-none-any.whl", hash = "sha256:0cc76456a30e20f5d7f2e14a98a4ae2ee4e5abdc7c5ea0aafe795f344bc7984c", size = 36679, upload-time = "2025-08-27T15:39:50.179Z" }, @@ -4582,7 +4591,7 @@ wheels = [ [[package]] name = "sphinx" version = "8.2.3" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "alabaster" }, { name = "babel" }, @@ -4610,7 +4619,7 @@ wheels = [ [[package]] name = "sphinx-autobuild" version = "2025.8.25" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama" }, { name = "sphinx" }, @@ -4627,7 +4636,7 @@ wheels = [ [[package]] name = "sphinx-autodoc-typehints" version = "3.5.2" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "sphinx" }, ] @@ -4639,7 +4648,7 @@ wheels = [ [[package]] name = "sphinx-copybutton" version = "0.5.2" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "sphinx" }, ] @@ -4651,7 +4660,7 @@ wheels = [ [[package]] name = "sphinx-design" version = "0.6.1" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "sphinx" }, ] @@ -4663,7 +4672,7 @@ wheels = [ [[package]] name = "sphinx-gallery" version = "0.19.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pillow" }, { name = "sphinx" }, @@ -4676,7 +4685,7 @@ wheels = [ [[package]] name = "sphinx-prompt" version = "1.10.1" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, { name = "docutils" }, @@ -4695,7 +4704,7 @@ wheels = [ [[package]] name = "sphinx-pyproject" version = "0.3.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "dom-toml" }, { name = "domdf-python-tools" }, @@ -4708,7 +4717,7 @@ wheels = [ [[package]] name = "sphinxcontrib-applehelp" version = "2.0.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/ba/6e/b837e84a1a704953c62ef8776d45c3e8d759876b4a84fe14eba2859106fe/sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1", size = 20053, upload-time = "2024-07-29T01:09:00.465Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/5d/85/9ebeae2f76e9e77b952f4b274c27238156eae7979c5421fba91a28f4970d/sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5", size = 119300, upload-time = "2024-07-29T01:08:58.99Z" }, @@ -4717,7 +4726,7 @@ wheels = [ [[package]] name = "sphinxcontrib-devhelp" version = "2.0.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/f6/d2/5beee64d3e4e747f316bae86b55943f51e82bb86ecd325883ef65741e7da/sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad", size = 12967, upload-time = "2024-07-29T01:09:23.417Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/35/7a/987e583882f985fe4d7323774889ec58049171828b58c2217e7f79cdf44e/sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2", size = 82530, upload-time = "2024-07-29T01:09:21.945Z" }, @@ -4726,7 +4735,7 @@ wheels = [ [[package]] name = "sphinxcontrib-htmlhelp" version = "2.1.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/43/93/983afd9aa001e5201eab16b5a444ed5b9b0a7a010541e0ddfbbfd0b2470c/sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9", size = 22617, upload-time = "2024-07-29T01:09:37.889Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/0a/7b/18a8c0bcec9182c05a0b3ec2a776bba4ead82750a55ff798e8d406dae604/sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8", size = 98705, upload-time = "2024-07-29T01:09:36.407Z" }, @@ -4735,7 +4744,7 @@ wheels = [ [[package]] name = "sphinxcontrib-jsmath" version = "1.0.1" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/b2/e8/9ed3830aeed71f17c026a07a5097edcf44b692850ef215b161b8ad875729/sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8", size = 5787, upload-time = "2019-01-21T16:10:16.347Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", size = 5071, upload-time = "2019-01-21T16:10:14.333Z" }, @@ -4744,7 +4753,7 @@ wheels = [ [[package]] name = "sphinxcontrib-qthelp" version = "2.0.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/68/bc/9104308fc285eb3e0b31b67688235db556cd5b0ef31d96f30e45f2e51cae/sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab", size = 17165, upload-time = "2024-07-29T01:09:56.435Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/27/83/859ecdd180cacc13b1f7e857abf8582a64552ea7a061057a6c716e790fce/sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb", size = 88743, upload-time = "2024-07-29T01:09:54.885Z" }, @@ -4753,7 +4762,7 @@ wheels = [ [[package]] name = "sphinxcontrib-serializinghtml" version = "2.0.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/3b/44/6716b257b0aa6bfd51a1b31665d1c205fb12cb5ad56de752dfa15657de2f/sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d", size = 16080, upload-time = "2024-07-29T01:10:09.332Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072, upload-time = "2024-07-29T01:10:08.203Z" }, @@ -4762,7 +4771,7 @@ wheels = [ [[package]] name = "sqlparse" version = "0.5.3" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/e5/40/edede8dd6977b0d3da179a342c198ed100dd2aba4be081861ee5911e4da4/sqlparse-0.5.3.tar.gz", hash = "sha256:09f67787f56a0b16ecdbde1bfc7f5d9c3371ca683cfeaa8e6ff60b4807ec9272", size = 84999, upload-time = "2024-12-10T12:05:30.728Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/a9/5c/bfd6bd0bf979426d405cc6e71eceb8701b148b16c21d2dc3c261efc61c7b/sqlparse-0.5.3-py3-none-any.whl", hash = "sha256:cf2196ed3418f3ba5de6af7e82c694a9fbdbfecccdfc72e281548517081f16ca", size = 44415, upload-time = "2024-12-10T12:05:27.824Z" }, @@ -4771,7 +4780,7 @@ wheels = [ [[package]] name = "stack-data" version = "0.6.3" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "asttokens" }, { name = "executing" }, @@ -4785,7 +4794,7 @@ wheels = [ [[package]] name = "starlette" version = "0.49.3" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, @@ -4798,7 +4807,7 @@ wheels = [ [[package]] name = "sympy" version = "1.14.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mpmath" }, ] @@ -4810,7 +4819,7 @@ wheels = [ [[package]] name = "terminado" version = "0.18.1" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "ptyprocess", marker = "os_name != 'nt'" }, { name = "pywinpty", marker = "os_name == 'nt'" }, @@ -4824,7 +4833,7 @@ wheels = [ [[package]] name = "threadpoolctl" version = "3.6.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/b7/4d/08c89e34946fce2aec4fbb45c9016efd5f4d7f24af8e5d93296e935631d8/threadpoolctl-3.6.0.tar.gz", hash = "sha256:8ab8b4aa3491d812b623328249fab5302a68d2d71745c8a4c719a2fcaba9f44e", size = 21274, upload-time = "2025-03-13T13:49:23.031Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/32/d5/f9a850d79b0851d1d4ef6456097579a9005b31fea68726a4ae5f2d82ddd9/threadpoolctl-3.6.0-py3-none-any.whl", hash = "sha256:43a0b8fd5a2928500110039e43a5eed8480b918967083ea48dc3ab9f13c4a7fb", size = 18638, upload-time = "2025-03-13T13:49:21.846Z" }, @@ -4833,7 +4842,7 @@ wheels = [ [[package]] name = "tinycss2" version = "1.4.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "webencodings" }, ] @@ -4845,7 +4854,7 @@ wheels = [ [[package]] name = "toml-fmt-common" version = "1.1.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/ba/ec/94b10890bf99ef0cc547cf4113bff46337c289012e7916ae7adb8f3c470b/toml_fmt_common-1.1.0.tar.gz", hash = "sha256:e4ba8f13e5fe25cfe0bfc60342ad7deb91c741fd31f2e5522e6a51bfbf1427d3", size = 9643, upload-time = "2025-10-08T17:41:14.328Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/4f/3b/40e889a19cf41bd898eedb6dded7c4ba711442555f68dc0cff6275aaa682/toml_fmt_common-1.1.0-py3-none-any.whl", hash = "sha256:92a956c4abf9c14e72d51e4c23149b2596a84ac0c347484e7c36008807e2e0a3", size = 5686, upload-time = "2025-10-08T17:41:13.035Z" }, @@ -4854,7 +4863,7 @@ wheels = [ [[package]] name = "tomli" version = "2.3.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/52/ed/3f73f72945444548f33eba9a87fc7a6e969915e7b1acc8260b30e1f76a2f/tomli-2.3.0.tar.gz", hash = "sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549", size = 17392, upload-time = "2025-10-08T22:01:47.119Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/ff/b7/40f36368fcabc518bb11c8f06379a0fd631985046c038aca08c6d6a43c6e/tomli-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7d86942e56ded512a594786a5ba0a5e521d02529b3826e7761a05138341a2ac", size = 154891, upload-time = "2025-10-08T22:01:09.082Z" }, @@ -4895,7 +4904,7 @@ wheels = [ [[package]] name = "tomlkit" version = "0.13.3" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/cc/18/0bbf3884e9eaa38819ebe46a7bd25dcd56b67434402b66a58c4b8e552575/tomlkit-0.13.3.tar.gz", hash = "sha256:430cf247ee57df2b94ee3fbe588e71d362a941ebb545dec29b53961d61add2a1", size = 185207, upload-time = "2025-06-05T07:13:44.947Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/bd/75/8539d011f6be8e29f339c42e633aae3cb73bffa95dd0f9adec09b9c58e85/tomlkit-0.13.3-py3-none-any.whl", hash = "sha256:c89c649d79ee40629a9fda55f8ace8c6a1b42deb912b2a8fd8d942ddadb606b0", size = 38901, upload-time = "2025-06-05T07:13:43.546Z" }, @@ -4904,7 +4913,7 @@ wheels = [ [[package]] name = "tornado" version = "6.5.3" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/7f/2e/3d22d478f27cb4b41edd4db7f10cd7846d0a28ea443342de3dba97035166/tornado-6.5.3.tar.gz", hash = "sha256:16abdeb0211796ffc73765bc0a20119712d68afeeaf93d1a3f2edf6b3aee8d5a", size = 513348, upload-time = "2025-12-11T04:16:42.225Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/d3/e9/bf22f66e1d5d112c0617974b5ce86666683b32c09b355dfcd59f8d5c8ef6/tornado-6.5.3-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:2dd7d7e8d3e4635447a8afd4987951e3d4e8d1fb9ad1908c54c4002aabab0520", size = 443860, upload-time = "2025-12-11T04:16:26.638Z" }, @@ -4923,7 +4932,7 @@ wheels = [ [[package]] name = "tqdm" version = "4.67.1" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] @@ -4935,7 +4944,7 @@ wheels = [ [[package]] name = "traitlets" version = "5.14.3" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/eb/79/72064e6a701c2183016abbbfedaba506d81e30e232a68c9f0d6f6fcd1574/traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7", size = 161621, upload-time = "2024-04-19T11:11:49.746Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f", size = 85359, upload-time = "2024-04-19T11:11:46.763Z" }, @@ -4944,7 +4953,7 @@ wheels = [ [[package]] name = "typer-slim" version = "0.20.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, { name = "typing-extensions" }, @@ -4957,7 +4966,7 @@ wheels = [ [[package]] name = "types-pytz" version = "2025.2.0.20251108" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/40/ff/c047ddc68c803b46470a357454ef76f4acd8c1088f5cc4891cdd909bfcf6/types_pytz-2025.2.0.20251108.tar.gz", hash = "sha256:fca87917836ae843f07129567b74c1929f1870610681b4c92cb86a3df5817bdb", size = 10961, upload-time = "2025-11-08T02:55:57.001Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/e7/c1/56ef16bf5dcd255155cc736d276efa6ae0a5c26fd685e28f0412a4013c01/types_pytz-2025.2.0.20251108-py3-none-any.whl", hash = "sha256:0f1c9792cab4eb0e46c52f8845c8f77cf1e313cb3d68bf826aa867fe4717d91c", size = 10116, upload-time = "2025-11-08T02:55:56.194Z" }, @@ -4966,7 +4975,7 @@ wheels = [ [[package]] name = "typing-extensions" version = "4.15.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, @@ -4975,7 +4984,7 @@ wheels = [ [[package]] name = "typing-inspection" version = "0.4.2" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] @@ -4987,7 +4996,7 @@ wheels = [ [[package]] name = "tzdata" version = "2025.2" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" }, @@ -4996,7 +5005,7 @@ wheels = [ [[package]] name = "uri-template" version = "1.3.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/31/c7/0336f2bd0bcbada6ccef7aaa25e443c118a704f828a0620c6fa0207c1b64/uri-template-1.3.0.tar.gz", hash = "sha256:0e00f8eb65e18c7de20d595a14336e9f337ead580c70934141624b6d1ffdacc7", size = 21678, upload-time = "2023-06-21T01:49:05.374Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl", hash = "sha256:a44a133ea12d44a0c0f06d7d42a52d71282e77e2f937d8abd5655b8d56fc1363", size = 11140, upload-time = "2023-06-21T01:49:03.467Z" }, @@ -5005,7 +5014,7 @@ wheels = [ [[package]] name = "url-normalize" version = "2.2.1" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "idna" }, ] @@ -5017,7 +5026,7 @@ wheels = [ [[package]] name = "urllib3" version = "2.5.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, @@ -5026,7 +5035,7 @@ wheels = [ [[package]] name = "uv" version = "0.9.9" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/66/4a/dd4d1a772afd0ad4167a932864e145ba3010d2a148e34171070bfcb85528/uv-0.9.9.tar.gz", hash = "sha256:dc5885fda74cec4cf8eea4115a6e0e431462c6c6bf1bd925abd72699d6b54f51", size = 3724446, upload-time = "2025-11-12T18:45:24.863Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/6d/d2/dcf1ee2b977ebbe12c8666b56f49d1138b565fc475c0e80c00c50da6d321/uv-0.9.9-py3-none-linux_armv6l.whl", hash = "sha256:ea700f6e43389a3bd6aa90c02f3010b61ef987c3b025842281a8bd513e26cf3a", size = 20481964, upload-time = "2025-11-12T18:44:21.532Z" }, @@ -5052,7 +5061,7 @@ wheels = [ [[package]] name = "uvicorn" version = "0.38.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, { name = "h11" }, @@ -5065,7 +5074,7 @@ wheels = [ [[package]] name = "watchfiles" version = "1.1.1" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, ] @@ -5135,7 +5144,7 @@ wheels = [ [[package]] name = "wcwidth" version = "0.2.14" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/24/30/6b0809f4510673dc723187aeaf24c7f5459922d01e2f794277a3dfb90345/wcwidth-0.2.14.tar.gz", hash = "sha256:4d478375d31bc5395a3c55c40ccdf3354688364cd61c4f6adacaa9215d0b3605", size = 102293, upload-time = "2025-09-22T16:29:53.023Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl", hash = "sha256:a7bb560c8aee30f9957e5f9895805edd20602f2d7f720186dfd906e82b4982e1", size = 37286, upload-time = "2025-09-22T16:29:51.641Z" }, @@ -5144,7 +5153,7 @@ wheels = [ [[package]] name = "webcolors" version = "25.10.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/1d/7a/eb316761ec35664ea5174709a68bbd3389de60d4a1ebab8808bfc264ed67/webcolors-25.10.0.tar.gz", hash = "sha256:62abae86504f66d0f6364c2a8520de4a0c47b80c03fc3a5f1815fedbef7c19bf", size = 53491, upload-time = "2025-10-31T07:51:03.977Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl", hash = "sha256:032c727334856fc0b968f63daa252a1ac93d33db2f5267756623c210e57a4f1d", size = 14905, upload-time = "2025-10-31T07:51:01.778Z" }, @@ -5153,7 +5162,7 @@ wheels = [ [[package]] name = "webencodings" version = "0.5.1" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/0b/02/ae6ceac1baeda530866a85075641cec12989bd8d31af6d5ab4a3e8c92f47/webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923", size = 9721, upload-time = "2017-04-05T20:21:34.189Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", size = 11774, upload-time = "2017-04-05T20:21:32.581Z" }, @@ -5162,7 +5171,7 @@ wheels = [ [[package]] name = "websocket-client" version = "1.9.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/2c/41/aa4bf9664e4cda14c3b39865b12251e8e7d239f4cd0e3cc1b6c2ccde25c1/websocket_client-1.9.0.tar.gz", hash = "sha256:9e813624b6eb619999a97dc7958469217c3176312b3a16a4bd1bc7e08a46ec98", size = 70576, upload-time = "2025-10-07T21:16:36.495Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/34/db/b10e48aa8fff7407e67470363eac595018441cf32d5e1001567a7aeba5d2/websocket_client-1.9.0-py3-none-any.whl", hash = "sha256:af248a825037ef591efbf6ed20cc5faa03d3b47b9e5a2230a529eeee1c1fc3ef", size = 82616, upload-time = "2025-10-07T21:16:34.951Z" }, @@ -5171,7 +5180,7 @@ wheels = [ [[package]] name = "websockets" version = "15.0.1" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016, upload-time = "2025-03-05T20:03:41.606Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/51/6b/4545a0d843594f5d0771e86463606a3988b5a09ca5123136f8a76580dd63/websockets-15.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3e90baa811a5d73f3ca0bcbf32064d663ed81318ab225ee4f427ad4e26e5aff3", size = 175437, upload-time = "2025-03-05T20:02:16.706Z" }, @@ -5202,7 +5211,7 @@ wheels = [ [[package]] name = "werkzeug" version = "3.1.3" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markupsafe" }, ] @@ -5214,7 +5223,7 @@ wheels = [ [[package]] name = "widgetsnbextension" version = "4.0.15" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/bd/f4/c67440c7fb409a71b7404b7aefcd7569a9c0d6bd071299bf4198ae7a5d95/widgetsnbextension-4.0.15.tar.gz", hash = "sha256:de8610639996f1567952d763a5a41af8af37f2575a41f9852a38f947eb82a3b9", size = 1097402, upload-time = "2025-11-01T21:15:55.178Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/3f/0e/fa3b193432cfc60c93b42f3be03365f5f909d2b3ea410295cf36df739e31/widgetsnbextension-4.0.15-py3-none-any.whl", hash = "sha256:8156704e4346a571d9ce73b84bee86a29906c9abfd7223b7228a28899ccf3366", size = 2196503, upload-time = "2025-11-01T21:15:53.565Z" }, @@ -5223,7 +5232,7 @@ wheels = [ [[package]] name = "win32-setctime" version = "1.2.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/b3/8f/705086c9d734d3b663af0e9bb3d4de6578d08f46b1b101c2442fd9aecaa2/win32_setctime-1.2.0.tar.gz", hash = "sha256:ae1fdf948f5640aae05c511ade119313fb6a30d7eabe25fef9764dca5873c4c0", size = 4867, upload-time = "2024-12-07T15:28:28.314Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/e1/07/c6fe3ad3e685340704d314d765b7912993bcb8dc198f0e7a89382d37974b/win32_setctime-1.2.0-py3-none-any.whl", hash = "sha256:95d644c4e708aba81dc3704a116d8cbc974d70b3bdb8be1d150e36be6e9d1390", size = 4083, upload-time = "2024-12-07T15:28:26.465Z" }, @@ -5232,7 +5241,7 @@ wheels = [ [[package]] name = "wrapt" version = "1.17.3" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/95/8f/aeb76c5b46e273670962298c23e7ddde79916cb74db802131d49a85e4b7d/wrapt-1.17.3.tar.gz", hash = "sha256:f66eb08feaa410fe4eebd17f2a2c8e2e46d3476e9f8c783daa8e09e0faa666d0", size = 55547, upload-time = "2025-08-12T05:53:21.714Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/9f/41/cad1aba93e752f1f9268c77270da3c469883d56e2798e7df6240dcb2287b/wrapt-1.17.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ab232e7fdb44cdfbf55fc3afa31bcdb0d8980b9b95c38b6405df2acb672af0e0", size = 53998, upload-time = "2025-08-12T05:51:47.138Z" }, @@ -5281,7 +5290,7 @@ wheels = [ [[package]] name = "xgboost" version = "3.1.1" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, { name = "nvidia-nccl-cu12", marker = "platform_machine != 'aarch64' and sys_platform == 'linux'" }, @@ -5299,7 +5308,7 @@ wheels = [ [[package]] name = "xgboost-cpu" version = "3.1.1" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, { name = "scipy" }, @@ -5315,7 +5324,7 @@ wheels = [ [[package]] name = "xmltodict" version = "1.0.2" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/6a/aa/917ceeed4dbb80d2f04dbd0c784b7ee7bba8ae5a54837ef0e5e062cd3cfb/xmltodict-1.0.2.tar.gz", hash = "sha256:54306780b7c2175a3967cad1db92f218207e5bc1aba697d887807c0fb68b7649", size = 25725, upload-time = "2025-09-17T21:59:26.459Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/c0/20/69a0e6058bc5ea74892d089d64dfc3a62ba78917ec5e2cfa70f7c92ba3a5/xmltodict-1.0.2-py3-none-any.whl", hash = "sha256:62d0fddb0dcbc9f642745d8bbf4d81fd17d6dfaec5a15b5c1876300aad92af0d", size = 13893, upload-time = "2025-09-17T21:59:24.859Z" }, @@ -5324,7 +5333,7 @@ wheels = [ [[package]] name = "yarl" version = "1.22.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "idna" }, { name = "multidict" }, @@ -5418,7 +5427,7 @@ wheels = [ [[package]] name = "zipp" version = "3.23.0" -source = { registry = "https://pypi.org/simple/" } +source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, From f82a49b8a36175352d37524365d45b5fb19d9469 Mon Sep 17 00:00:00 2001 From: Egor Dmitriev Date: Thu, 5 Mar 2026 12:55:36 +0100 Subject: [PATCH 5/8] refactor(STEF-2702): replace workflow factory with template + model_copy - Replace workflow_factory Callable with workflow_template field - Add with_run_name() method for type-safe deep copy - Add kind discriminator to ForecastingWorkflowConfig (single) and EnsembleForecastingWorkflowConfig (ensemble) - Simplify factory: use kind-based narrowing, raise MissingExtraError - Delete WorkflowCreationContext (no longer needed) - Remove both pyright: ignore[reportArgumentType] suppressions --- .../benchmarking/baselines/openstef4.py | 90 ++++++------------- .../presets/forecasting_workflow.py | 1 + .../presets/forecasting_workflow.py | 1 + .../workflows/custom_forecasting_workflow.py | 5 ++ 4 files changed, 35 insertions(+), 62 deletions(-) diff --git a/packages/openstef-beam/src/openstef_beam/benchmarking/baselines/openstef4.py b/packages/openstef-beam/src/openstef_beam/benchmarking/baselines/openstef4.py index 006dcc33e..d6328e7f2 100644 --- a/packages/openstef-beam/src/openstef_beam/benchmarking/baselines/openstef4.py +++ b/packages/openstef-beam/src/openstef_beam/benchmarking/baselines/openstef4.py @@ -8,7 +8,6 @@ """ import logging -from collections.abc import Callable from datetime import timedelta from functools import partial from pathlib import Path @@ -29,9 +28,9 @@ BenchmarkTarget, ForecasterFactory, ) -from openstef_core.base_model import BaseConfig, BaseModel +from openstef_core.base_model import BaseModel from openstef_core.datasets import TimeSeriesDataset -from openstef_core.exceptions import FlatlinerDetectedError, NotFittedError +from openstef_core.exceptions import FlatlinerDetectedError, MissingExtraError, NotFittedError from openstef_core.types import Q from openstef_models.presets import ForecastingWorkflowConfig, create_forecasting_workflow from openstef_models.presets.forecasting_workflow import LocationConfig @@ -45,15 +44,6 @@ from openstef_meta.presets import EnsembleForecastingWorkflowConfig -class WorkflowCreationContext(BaseConfig): - """Context information for workflow execution within backtesting.""" - - step_name: str | None = Field( - default=None, - description="Name of the current backtesting step.", - ) - - class OpenSTEF4BacktestForecaster(BaseModel, BacktestForecasterMixin): """Forecaster that allows using a ForecastingWorkflow to be used in backtesting, specifically for OpenSTEF4 models. @@ -64,8 +54,8 @@ class OpenSTEF4BacktestForecaster(BaseModel, BacktestForecasterMixin): config: BacktestForecasterConfig = Field( description="Configuration for the backtest forecaster interface", ) - workflow_factory: Callable[[WorkflowCreationContext], CustomForecastingWorkflow] = Field( - description="Factory function that creates a new CustomForecastingWorkflow instance", + workflow_template: CustomForecastingWorkflow = Field( + description="Untrained workflow template; deep-copied for each fit() call", ) cache_dir: Path = Field( description="Directory to use for caching model artifacts during backtesting", @@ -105,17 +95,12 @@ def model_post_init(self, context: Any) -> None: @property @override def quantiles(self) -> list[Q]: - # Create a workflow instance if needed to get quantiles - if self._workflow is None: - self._workflow = self.workflow_factory(WorkflowCreationContext()) - - return self._workflow.model.quantiles + return self.workflow_template.model.quantiles @override def fit(self, data: RestrictedHorizonVersionedTimeSeries) -> None: - # Create a new workflow for this training cycle - context = WorkflowCreationContext(step_name=data.horizon.isoformat()) - workflow = self.workflow_factory(context) + # Deep-copy the template for a fresh model + workflow = self.workflow_template.with_run_name(data.horizon.isoformat()) workflow.callbacks.extend(self.extra_callbacks) # Extract the dataset for training @@ -171,50 +156,32 @@ def _preset_target_forecaster_factory( context: BenchmarkContext, target: BenchmarkTarget, ) -> OpenSTEF4BacktestForecaster: - # Try to import ensemble support (openstef-meta is optional) - ensemble_config_cls: type | None = None - create_ensemble: Callable[..., CustomForecastingWorkflow] | None = None - try: - from openstef_meta.presets import ( # noqa: PLC0415 - EnsembleForecastingWorkflowConfig, - create_ensemble_forecasting_workflow, - ) - - ensemble_config_cls = EnsembleForecastingWorkflowConfig - create_ensemble = create_ensemble_forecasting_workflow - except ImportError: - pass - - prefix = context.run_name - - def _create_workflow(context: WorkflowCreationContext) -> CustomForecastingWorkflow: - location = LocationConfig( - name=target.name, - description=target.description, - coordinate=Coordinate( - latitude=target.latitude, - longitude=target.longitude, - ), - ) - - update = { - "model_id": f"{prefix}_{target.name}", - "location": location, - "run_name": context.step_name, - } + location = LocationConfig( + name=target.name, + description=target.description, + coordinate=Coordinate( + latitude=target.latitude, + longitude=target.longitude, + ), + ) - if ( # fmt: skip - create_ensemble is not None - and ensemble_config_cls is not None - and isinstance(base_config, ensemble_config_cls) - ): - return create_ensemble(config=base_config.model_copy(update=update)) # pyright: ignore[reportArgumentType] + update: dict[str, Any] = { + "model_id": f"{context.run_name}_{target.name}", + "location": location, + } - return create_forecasting_workflow(config=base_config.model_copy(update=update)) # pyright: ignore[reportArgumentType] + if base_config.kind == "ensemble": + try: + from openstef_meta.presets import create_ensemble_forecasting_workflow # noqa: PLC0415 + except ImportError as e: + raise MissingExtraError("openstef-meta") from e + workflow = create_ensemble_forecasting_workflow(config=base_config.model_copy(update=update)) + else: + workflow = create_forecasting_workflow(config=base_config.model_copy(update=update)) return OpenSTEF4BacktestForecaster( config=backtest_config, - workflow_factory=_create_workflow, + workflow_template=workflow, debug=False, cache_dir=cache_dir / f"{context.run_name}_{target.name}", ) @@ -264,6 +231,5 @@ def create_openstef4_preset_backtest_forecaster( __all__ = [ "OpenSTEF4BacktestForecaster", - "WorkflowCreationContext", "create_openstef4_preset_backtest_forecaster", ] diff --git a/packages/openstef-meta/src/openstef_meta/presets/forecasting_workflow.py b/packages/openstef-meta/src/openstef_meta/presets/forecasting_workflow.py index b24c3080b..d10916eba 100644 --- a/packages/openstef-meta/src/openstef_meta/presets/forecasting_workflow.py +++ b/packages/openstef-meta/src/openstef_meta/presets/forecasting_workflow.py @@ -74,6 +74,7 @@ class EnsembleForecastingWorkflowConfig(BaseConfig): """Configuration for ensemble forecasting workflows.""" + kind: Literal["ensemble"] = Field(default="ensemble", description="Discriminator tag for config type.") model_id: ModelIdentifier # Ensemble configuration diff --git a/packages/openstef-models/src/openstef_models/presets/forecasting_workflow.py b/packages/openstef-models/src/openstef_models/presets/forecasting_workflow.py index e7b7ad70b..483955400 100644 --- a/packages/openstef-models/src/openstef_models/presets/forecasting_workflow.py +++ b/packages/openstef-models/src/openstef_models/presets/forecasting_workflow.py @@ -106,6 +106,7 @@ class ForecastingWorkflowConfig(BaseConfig): # PredictionJob hyperparameters, location information, data columns, and feature engineering settings. """ + kind: Literal["single"] = Field(default="single", description="Discriminator tag for config type.") model_id: ModelIdentifier = Field(description="Unique identifier for the forecasting model.") run_name: str | None = Field( default=None, description="Optional name for this workflow run, can be used for versioning." diff --git a/packages/openstef-models/src/openstef_models/workflows/custom_forecasting_workflow.py b/packages/openstef-models/src/openstef_models/workflows/custom_forecasting_workflow.py index 82e7d6a14..ed56ffd10 100644 --- a/packages/openstef-models/src/openstef_models/workflows/custom_forecasting_workflow.py +++ b/packages/openstef-models/src/openstef_models/workflows/custom_forecasting_workflow.py @@ -11,6 +11,7 @@ import logging from datetime import datetime +from typing import Self from pydantic import Field, PrivateAttr @@ -129,6 +130,10 @@ class CustomForecastingWorkflow(BaseModel): _logger: logging.Logger = PrivateAttr(default_factory=lambda: logging.getLogger(__name__)) + def with_run_name(self, run_name: str) -> Self: + """Return a deep copy of this workflow with the given run name.""" + return self.model_copy(deep=True, update={"run_name": run_name}) + def fit( self, data: TimeSeriesDataset, From 4e350c82c4c86cfa6559db2de2702445324a2fd3 Mon Sep 17 00:00:00 2001 From: Egor Dmitriev Date: Thu, 5 Mar 2026 13:01:16 +0100 Subject: [PATCH 6/8] test(STEF-2702): add template pattern smoke tests for OpenSTEF4BacktestForecaster MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - test_fit_does_not_mutate_template: verifies template immutability after fit - test_fit_then_predict_returns_forecast: e2e smoke test for fit→predict path --- .../unit/benchmarking/baselines/__init__.py | 3 + .../benchmarking/baselines/test_openstef4.py | 119 ++++++++++++++++++ 2 files changed, 122 insertions(+) create mode 100644 packages/openstef-beam/tests/unit/benchmarking/baselines/__init__.py create mode 100644 packages/openstef-beam/tests/unit/benchmarking/baselines/test_openstef4.py diff --git a/packages/openstef-beam/tests/unit/benchmarking/baselines/__init__.py b/packages/openstef-beam/tests/unit/benchmarking/baselines/__init__.py new file mode 100644 index 000000000..7b9e0469f --- /dev/null +++ b/packages/openstef-beam/tests/unit/benchmarking/baselines/__init__.py @@ -0,0 +1,3 @@ +# SPDX-FileCopyrightText: 2025 Contributors to the OpenSTEF project +# +# SPDX-License-Identifier: MPL-2.0 diff --git a/packages/openstef-beam/tests/unit/benchmarking/baselines/test_openstef4.py b/packages/openstef-beam/tests/unit/benchmarking/baselines/test_openstef4.py new file mode 100644 index 000000000..611e9b205 --- /dev/null +++ b/packages/openstef-beam/tests/unit/benchmarking/baselines/test_openstef4.py @@ -0,0 +1,119 @@ +# SPDX-FileCopyrightText: 2025 Contributors to the OpenSTEF project +# +# SPDX-License-Identifier: MPL-2.0 + +from datetime import UTC, datetime, timedelta +from pathlib import Path + +import numpy as np +import pandas as pd +import pytest + +from openstef_beam.backtesting.restricted_horizon_timeseries import RestrictedHorizonVersionedTimeSeries +from openstef_beam.benchmarking.baselines.openstef4 import create_openstef4_preset_backtest_forecaster +from openstef_beam.benchmarking.benchmark_pipeline import BenchmarkContext, BenchmarkTarget +from openstef_core.datasets import TimeSeriesDataset, VersionedTimeSeriesDataset +from openstef_core.types import LeadTime, Q +from openstef_models.presets import ForecastingWorkflowConfig + + +@pytest.fixture +def xgboost_config() -> ForecastingWorkflowConfig: + return ForecastingWorkflowConfig( + model_id="test_xgb", + model="xgboost", + horizons=[LeadTime.from_string("PT24H")], + quantiles=[Q(0.5)], + ) + + +@pytest.fixture +def benchmark_target() -> BenchmarkTarget: + now = datetime(2024, 6, 1, tzinfo=UTC) + return BenchmarkTarget( + name="target_1", + description="test", + latitude=52.0, + longitude=5.0, + limit=100.0, + benchmark_start=now - timedelta(days=365), + benchmark_end=now, + train_start=now - timedelta(days=730), + ) + + +@pytest.fixture +def training_data() -> VersionedTimeSeriesDataset: + """Synthetic 15-min data covering 120 days.""" + t0 = datetime(2024, 2, 1, tzinfo=UTC) + n = 96 * 120 + rng = np.random.default_rng(42) + index = pd.date_range(t0, periods=n, freq="15min", tz="UTC") + df = pd.DataFrame( + { + "load": np.sin(np.arange(n) * 2 * np.pi / 96) * 50 + 100 + rng.normal(0, 5, n), + "radiation": np.maximum(0, np.sin(np.arange(n) * 2 * np.pi / 96 - 1)) * 500, + "temperature": 15 + 5 * np.sin(np.arange(n) * 2 * np.pi / 96), + "windspeed": np.abs(rng.normal(5, 2, n)), + "pressure": 1013 + rng.normal(0, 5, n), + "relative_humidity": 70 + rng.normal(0, 10, n), + "day_ahead_electricity_price": 50 + rng.normal(0, 10, n), + "available_at": [t0 + timedelta(minutes=15) * (i + 1) for i in range(n)], + }, + index=index, + ) + return VersionedTimeSeriesDataset([TimeSeriesDataset(df, timedelta(minutes=15))]) + + +def test_fit_does_not_mutate_template( + xgboost_config: ForecastingWorkflowConfig, + benchmark_target: BenchmarkTarget, + training_data: VersionedTimeSeriesDataset, + tmp_path: Path, +): + """fit() should train a deep copy; the template must remain untouched.""" + # Arrange + factory = create_openstef4_preset_backtest_forecaster( + workflow_config=xgboost_config, + cache_dir=tmp_path / "test_no_mutate", + ) + forecaster = factory(BenchmarkContext(run_name="run"), benchmark_target) + template_model_id_before = id(forecaster.workflow_template.model) + + horizon = datetime(2024, 5, 25, tzinfo=UTC) + rhvts = RestrictedHorizonVersionedTimeSeries(dataset=training_data, horizon=horizon) + + # Act + forecaster.fit(rhvts) + + # Assert — template model object identity unchanged + assert id(forecaster.workflow_template.model) == template_model_id_before + assert forecaster.workflow_template.run_name is None + assert forecaster._workflow is not forecaster.workflow_template + assert forecaster._workflow is not None + assert forecaster._workflow.run_name == horizon.isoformat() + + +def test_fit_then_predict_returns_forecast( + xgboost_config: ForecastingWorkflowConfig, + benchmark_target: BenchmarkTarget, + training_data: VersionedTimeSeriesDataset, + tmp_path: Path, +): + """End-to-end: fit then predict should return a ForecastDataset.""" + # Arrange + factory = create_openstef4_preset_backtest_forecaster( + workflow_config=xgboost_config, + cache_dir=tmp_path / "test_e2e", + ) + forecaster = factory(BenchmarkContext(run_name="e2e"), benchmark_target) + horizon = datetime(2024, 5, 25, tzinfo=UTC) + rhvts = RestrictedHorizonVersionedTimeSeries(dataset=training_data, horizon=horizon) + + # Act + forecaster.fit(rhvts) + result = forecaster.predict(rhvts) + + # Assert + assert result is not None + assert len(result.data) > 0 From a186a5b9ca4ed0ab09720756ee3d44ef5e3074e3 Mon Sep 17 00:00:00 2001 From: Egor Dmitriev Date: Thu, 5 Mar 2026 13:21:26 +0100 Subject: [PATCH 7/8] chore(STEF-2702): add openstef-meta to release pipeline - Add missing [build-system] section to openstef-meta pyproject.toml - Add openstef-meta to poe version task (version bump + root pinning) - Fix baselines extra version range: >=4.0.0.dev0,<5 (was >=0.0.1,<1) - Add openstef-meta to root [all] optional extra - Add openstef-meta to licensecheck ignore_packages --- packages/openstef-beam/pyproject.toml | 2 +- .../tests/unit/benchmarking/baselines/test_openstef4.py | 2 ++ packages/openstef-meta/pyproject.toml | 5 +++++ pyproject.toml | 7 +++++-- uv.lock | 4 +++- 5 files changed, 16 insertions(+), 4 deletions(-) diff --git a/packages/openstef-beam/pyproject.toml b/packages/openstef-beam/pyproject.toml index cdcf3610b..0db13d4b9 100644 --- a/packages/openstef-beam/pyproject.toml +++ b/packages/openstef-beam/pyproject.toml @@ -40,7 +40,7 @@ optional-dependencies.all = [ "s3fs>=2025.5.1", ] optional-dependencies.baselines = [ - "openstef-meta>=0.0.1,<1", + "openstef-meta>=4.0.0.dev0,<5", "openstef-models>=4.0.0.dev0,<5", ] urls.Documentation = "https://openstef.github.io/openstef/index.html" diff --git a/packages/openstef-beam/tests/unit/benchmarking/baselines/test_openstef4.py b/packages/openstef-beam/tests/unit/benchmarking/baselines/test_openstef4.py index 611e9b205..81b652dca 100644 --- a/packages/openstef-beam/tests/unit/benchmarking/baselines/test_openstef4.py +++ b/packages/openstef-beam/tests/unit/benchmarking/baselines/test_openstef4.py @@ -89,6 +89,8 @@ def test_fit_does_not_mutate_template( # Assert — template model object identity unchanged assert id(forecaster.workflow_template.model) == template_model_id_before assert forecaster.workflow_template.run_name is None + + # The fitted workflow should be different from the template assert forecaster._workflow is not forecaster.workflow_template assert forecaster._workflow is not None assert forecaster._workflow.run_name == horizon.isoformat() diff --git a/packages/openstef-meta/pyproject.toml b/packages/openstef-meta/pyproject.toml index 0f620e63b..51519ea2c 100644 --- a/packages/openstef-meta/pyproject.toml +++ b/packages/openstef-meta/pyproject.toml @@ -2,6 +2,11 @@ # # SPDX-License-Identifier: MPL-2.0 +[build-system] +build-backend = "hatchling.build" + +requires = [ "hatchling" ] + [project] name = "openstef-meta" version = "0.0.0" diff --git a/pyproject.toml b/pyproject.toml index eb92e37fb..cfe8318ac 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,6 +34,7 @@ dependencies = [ optional-dependencies.all = [ "openstef-beam[all]", "openstef-core", + "openstef-meta", "openstef-models[xgb-cpu]", ] optional-dependencies.beam = [ @@ -321,11 +322,13 @@ sequence = [ { cmd = "uv version --package openstef-beam ${version} --frozen" }, { cmd = "uv version --package openstef-models ${version} --frozen" }, { cmd = "uv version --package openstef-core ${version} --frozen" }, + { cmd = "uv version --package openstef-meta ${version} --frozen" }, # Pin these versions in the root pyproject.toml { cmd = "uv add openstef-beam==${version} --optional beam" }, { cmd = "uv add openstef-models==${version} --optional models" }, { cmd = "uv add openstef-core==${version} --optional core" }, - { cmd = "uv add openstef-models==${version} openstef-beam==${version} openstef-core==${version} --optional all" }, + { cmd = "uv add openstef-meta==${version} --optional models" }, + { cmd = "uv add openstef-models==${version} openstef-beam==${version} openstef-core==${version} openstef-meta==${version} --optional all" }, { cmd = "uv add openstef-models==${version} openstef-core==${version}" }, ] args = [ @@ -357,7 +360,7 @@ cmd = "rm -rf docs/build/html/* docs/source/api/generated" [tool.licensecheck] # Pycountry is LGPL-2.1 according to pypi. # MPL 2.0 lists explicitly that it is compatible with LGPL-2.1 and later, so we ignore it here (due to false positive). -ignore_packages = [ "openstef-beam", "openstef-models", "openstef-core", "pycountry" ] +ignore_packages = [ "openstef-beam", "openstef-meta", "openstef-models", "openstef-core", "pycountry" ] # Note: MPL 2.0 lists explicitly Apache License 2.0 as compatible, so we ignore it here (due to false positive). ignore_licenses = [ "apache software license", "apache license 2.0" ] extras = [ "all" ] diff --git a/uv.lock b/uv.lock index 045f0086d..24fcd4b00 100644 --- a/uv.lock +++ b/uv.lock @@ -2691,6 +2691,7 @@ dependencies = [ all = [ { name = "openstef-beam", extra = ["all"] }, { name = "openstef-core" }, + { name = "openstef-meta" }, { name = "openstef-models", extra = ["xgb-cpu"] }, ] beam = [ @@ -2730,6 +2731,7 @@ requires-dist = [ { name = "openstef-beam", extras = ["all"], marker = "extra == 'all'", editable = "packages/openstef-beam" }, { name = "openstef-core", editable = "packages/openstef-core" }, { name = "openstef-core", marker = "extra == 'all'", editable = "packages/openstef-core" }, + { name = "openstef-meta", marker = "extra == 'all'", editable = "packages/openstef-meta" }, { name = "openstef-meta", marker = "extra == 'models'", editable = "packages/openstef-meta" }, { name = "openstef-models", extras = ["xgb-cpu"], editable = "packages/openstef-models" }, { name = "openstef-models", extras = ["xgb-cpu"], marker = "extra == 'all'", editable = "packages/openstef-models" }, @@ -2793,7 +2795,7 @@ requires-dist = [ { name = "scoringrules", specifier = ">=0.8" }, { name = "tqdm", specifier = ">=4.67.1" }, ] -provides-extras = ["baselines", "all"] +provides-extras = ["all", "baselines"] [[package]] name = "openstef-core" From 1d67a066774f4a5bb9f65c5ca8a874fd81eb55fb Mon Sep 17 00:00:00 2001 From: Egor Dmitriev Date: Thu, 5 Mar 2026 15:24:25 +0100 Subject: [PATCH 8/8] refactor(STEF-2702): extend test utility, address review comments - Add include_atmosphere/price/available_at options to create_synthetic_forecasting_dataset for realistic test data - Simplify test fixture to use the shared utility - Remove verbose parenthetical comments per review feedback --- .../benchmarking/baselines/test_openstef4.py | 30 +++++++------------ .../src/openstef_core/testing.py | 17 +++++++++++ .../models/ensemble_forecasting_model.py | 2 +- .../mlflow/mlflow_storage_callback.py | 6 ++-- 4 files changed, 31 insertions(+), 24 deletions(-) diff --git a/packages/openstef-beam/tests/unit/benchmarking/baselines/test_openstef4.py b/packages/openstef-beam/tests/unit/benchmarking/baselines/test_openstef4.py index 81b652dca..e9b66591a 100644 --- a/packages/openstef-beam/tests/unit/benchmarking/baselines/test_openstef4.py +++ b/packages/openstef-beam/tests/unit/benchmarking/baselines/test_openstef4.py @@ -5,14 +5,13 @@ from datetime import UTC, datetime, timedelta from pathlib import Path -import numpy as np -import pandas as pd import pytest from openstef_beam.backtesting.restricted_horizon_timeseries import RestrictedHorizonVersionedTimeSeries from openstef_beam.benchmarking.baselines.openstef4 import create_openstef4_preset_backtest_forecaster from openstef_beam.benchmarking.benchmark_pipeline import BenchmarkContext, BenchmarkTarget -from openstef_core.datasets import TimeSeriesDataset, VersionedTimeSeriesDataset +from openstef_core.datasets import VersionedTimeSeriesDataset +from openstef_core.testing import create_synthetic_forecasting_dataset from openstef_core.types import LeadTime, Q from openstef_models.presets import ForecastingWorkflowConfig @@ -45,24 +44,15 @@ def benchmark_target() -> BenchmarkTarget: @pytest.fixture def training_data() -> VersionedTimeSeriesDataset: """Synthetic 15-min data covering 120 days.""" - t0 = datetime(2024, 2, 1, tzinfo=UTC) - n = 96 * 120 - rng = np.random.default_rng(42) - index = pd.date_range(t0, periods=n, freq="15min", tz="UTC") - df = pd.DataFrame( - { - "load": np.sin(np.arange(n) * 2 * np.pi / 96) * 50 + 100 + rng.normal(0, 5, n), - "radiation": np.maximum(0, np.sin(np.arange(n) * 2 * np.pi / 96 - 1)) * 500, - "temperature": 15 + 5 * np.sin(np.arange(n) * 2 * np.pi / 96), - "windspeed": np.abs(rng.normal(5, 2, n)), - "pressure": 1013 + rng.normal(0, 5, n), - "relative_humidity": 70 + rng.normal(0, 10, n), - "day_ahead_electricity_price": 50 + rng.normal(0, 10, n), - "available_at": [t0 + timedelta(minutes=15) * (i + 1) for i in range(n)], - }, - index=index, + ts = create_synthetic_forecasting_dataset( + start=datetime(2024, 2, 1, tzinfo=UTC), + length=timedelta(days=120), + sample_interval=timedelta(minutes=15), + include_atmosphere=True, + include_price=True, + include_available_at=True, ) - return VersionedTimeSeriesDataset([TimeSeriesDataset(df, timedelta(minutes=15))]) + return VersionedTimeSeriesDataset([ts]) def test_fit_does_not_mutate_template( diff --git a/packages/openstef-core/src/openstef_core/testing.py b/packages/openstef-core/src/openstef_core/testing.py index e7619fdee..66e98ab15 100644 --- a/packages/openstef-core/src/openstef_core/testing.py +++ b/packages/openstef-core/src/openstef_core/testing.py @@ -84,6 +84,10 @@ def create_synthetic_forecasting_dataset( # noqa: PLR0913, PLR0917 - complex fu radiation_influence: float | None = -0.2, stochastic_influence: float | None = 0.1, other_components: dict[str, float] | None = None, + *, + include_atmosphere: bool = False, + include_price: bool = False, + include_available_at: bool = False, ) -> TimeSeriesDataset: """Create synthetic forecasting dataset for testing. @@ -99,6 +103,9 @@ def create_synthetic_forecasting_dataset( # noqa: PLR0913, PLR0917 - complex fu radiation_influence: Coefficient for radiation component on load. stochastic_influence: Coefficient for random noise component. other_components: Additional components with their influence coefficients. + include_atmosphere: Add ``pressure`` (~1013) and ``relative_humidity`` (~70%) columns. + include_price: Add ``day_ahead_electricity_price`` (~50) column. + include_available_at: Add ``available_at`` column (index + sample_interval). Returns: TimeSeriesDataset containing synthetic load and component data. @@ -124,11 +131,21 @@ def create_synthetic_forecasting_dataset( # noqa: PLR0913, PLR0917 - complex fu load += component * influence components[component_name] = component + extras: dict[str, Any] = {} + if include_atmosphere: + extras["pressure"] = 1013.0 + rng.normal(0, 5, len(timestamps)) + extras["relative_humidity"] = 70.0 + rng.normal(0, 10, len(timestamps)) + if include_price: + extras["day_ahead_electricity_price"] = 50.0 + rng.normal(0, 10, len(timestamps)) + if include_available_at: + extras["available_at"] = timestamps + sample_interval + return TimeSeriesDataset( data=pd.DataFrame( data={ "load": load, **components, + **extras, }, index=timestamps, ), diff --git a/packages/openstef-meta/src/openstef_meta/models/ensemble_forecasting_model.py b/packages/openstef-meta/src/openstef_meta/models/ensemble_forecasting_model.py index d0c6e655a..2d9bf4937 100644 --- a/packages/openstef-meta/src/openstef_meta/models/ensemble_forecasting_model.py +++ b/packages/openstef-meta/src/openstef_meta/models/ensemble_forecasting_model.py @@ -499,7 +499,7 @@ def prepare_forecaster_input( preprocessed = self.preprocessing.transform(data=data) preprocessed = self.model_specific_preprocessing[forecaster_name].transform(data=preprocessed) preprocessed = restore_target(dataset=preprocessed, original_dataset=data, target_column=self.target_column) - # Apply cutoff and create ForecastInputDataset (same logic as base prepare_input) + # Apply cutoff and create ForecastInputDataset input_data_start = cast("pd.Series[pd.Timestamp]", preprocessed.index).min().to_pydatetime() input_data_cutoff = input_data_start + self.cutoff_history if forecast_start is not None and forecast_start < input_data_cutoff: diff --git a/packages/openstef-models/src/openstef_models/integrations/mlflow/mlflow_storage_callback.py b/packages/openstef-models/src/openstef_models/integrations/mlflow/mlflow_storage_callback.py index 1601390d0..558e79bd6 100644 --- a/packages/openstef-models/src/openstef_models/integrations/mlflow/mlflow_storage_callback.py +++ b/packages/openstef-models/src/openstef_models/integrations/mlflow/mlflow_storage_callback.py @@ -133,7 +133,7 @@ def on_fit_end( run_id: str = run.info.run_id self._logger.info("Created MLflow run %s for model %s", run_id, context.workflow.model_id) - # Log per-component hyperparams (e.g. per-forecaster in an ensemble) + # Log per-component hyperparams for name, hparams in context.workflow.model.component_hyperparams.items(): prefixed = {f"{name}.{k}": str(v) for k, v in hparams.model_dump().items()} self.storage.log_hyperparams(run_id=run_id, params=prefixed) @@ -145,7 +145,7 @@ def on_fit_end( result.input_dataset.to_parquet(path=data_path / "data.parquet") self._logger.info("Stored training data at %s for run %s", data_path, run_id) - # Store per-component training data (e.g. per-forecaster in an ensemble) + # Store per-component training data for name, component_result in result.component_fit_results.items(): component_path = data_path / name component_path.mkdir(parents=True, exist_ok=True) @@ -163,7 +163,7 @@ def on_fit_end( ) self._logger.info("Stored trained model for run %s", run_id) - # Format the metrics for MLflow (includes child metrics via polymorphism) + # Format the metrics for MLflow metrics = result.metrics_to_flat_dict() # Mark the run as finished