openstef-models 4.0.1__tar.gz → 4.1.1.dev0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) hide show
  1. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/.gitignore +5 -4
  2. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/PKG-INFO +3 -3
  3. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/pyproject.toml +15 -12
  4. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/explainability/plotters/contributions_plotter.py +7 -7
  5. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/integrations/__init__.py +1 -1
  6. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/integrations/joblib/joblib_model_serializer.py +2 -2
  7. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/integrations/mlflow/mlflow_storage.py +8 -0
  8. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/integrations/mlflow/mlflow_storage_callback.py +5 -5
  9. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/integrations/optuna/tuner.py +5 -4
  10. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/models/component_splitting/constant_component_splitter.py +2 -1
  11. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/models/component_splitting/linear_component_splitter.py +2 -2
  12. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/models/forecasting/base_case_forecaster.py +2 -2
  13. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/models/forecasting/constant_quantile_forecaster.py +1 -1
  14. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/models/forecasting/gblinear_forecaster.py +3 -1
  15. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/models/forecasting/lgbm_forecaster.py +5 -3
  16. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/models/forecasting/lgbmlinear_forecaster.py +4 -3
  17. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/models/forecasting/median_forecaster.py +5 -7
  18. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/models/forecasting/xgboost_forecaster.py +3 -1
  19. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/models/forecasting_model.py +75 -6
  20. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/presets/forecasting_workflow.py +6 -4
  21. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/transforms/general/dimensionality_reducer.py +2 -2
  22. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/transforms/general/empty_feature_remover.py +2 -2
  23. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/transforms/general/imputer.py +1 -1
  24. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/transforms/general/nan_dropper.py +1 -1
  25. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/transforms/general/sample_weighter.py +1 -25
  26. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/transforms/general/scaler.py +1 -1
  27. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/transforms/general/shifter.py +2 -2
  28. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/transforms/postprocessing/confidence_interval_applicator.py +4 -4
  29. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/transforms/postprocessing/isotonic_quantile_calibrator.py +3 -3
  30. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/transforms/time_domain/cyclic_features_adder.py +1 -1
  31. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/transforms/time_domain/datetime_features_adder.py +15 -9
  32. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/transforms/time_domain/holiday_features_adder.py +7 -5
  33. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/transforms/time_domain/lags_adder.py +3 -12
  34. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/transforms/time_domain/rolling_aggregates_adder.py +7 -11
  35. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/transforms/time_domain/versioned_lags_adder.py +7 -3
  36. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/transforms/validation/flatline_checker.py +1 -1
  37. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/transforms/weather_domain/atmosphere_derived_features_adder.py +1 -1
  38. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/transforms/weather_domain/daylight_feature_adder.py +1 -1
  39. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/transforms/weather_domain/radiation_derived_features_adder.py +5 -5
  40. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/utils/data_split.py +5 -5
  41. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/utils/evaluation_functions.py +2 -2
  42. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/utils/feature_selection.py +1 -12
  43. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/utils/loss_functions.py +2 -2
  44. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/utils/multi_quantile_regressor.py +4 -4
  45. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/workflows/custom_forecasting_workflow.py +61 -0
  46. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/explainability/plotters/test_contributions_plotter.py +2 -2
  47. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/integrations/mlflow/test_mlflow_storage.py +29 -9
  48. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/integrations/mlflow/test_mlflow_storage_callback.py +6 -5
  49. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/integrations/optuna/test_tuner.py +11 -9
  50. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/models/forecasting/test_base_case_forecaster.py +20 -0
  51. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/models/forecasting/test_constant_quantile_forecaster.py +26 -0
  52. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/models/forecasting/test_lgbm_forecaster.py +1 -1
  53. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/models/forecasting/test_lgbmlinear_forecaster.py +1 -1
  54. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/models/forecasting/test_median_forecaster.py +6 -6
  55. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/models/test_forecasting_model.py +163 -4
  56. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/transforms/general/test_imputer.py +1 -1
  57. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/transforms/general/test_outlier_handler.py +3 -2
  58. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/transforms/general/test_scaler.py +1 -1
  59. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/transforms/postprocessing/test_isotonic_quantile_calibrator.py +1 -1
  60. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/transforms/time_domain/test_datetime_features_adder.py +29 -0
  61. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/transforms/time_domain/test_rolling_aggregates_adder.py +42 -0
  62. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/transforms/time_domain/test_versioned_lags_adder.py +53 -43
  63. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/utils/test_data_split.py +1 -1
  64. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/utils/test_multi_quantile_regressor.py +2 -2
  65. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/workflows/callbacks/test_model_performance_callback.py +12 -6
  66. openstef_models-4.1.1.dev0/tests/unit/workflows/test_custom_forecasting_workflow.py +134 -0
  67. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/README.md +0 -0
  68. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/__init__.py +0 -0
  69. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/explainability/__init__.py +0 -0
  70. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/explainability/mixins.py +0 -0
  71. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/explainability/plotters/__init__.py +0 -0
  72. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/explainability/plotters/feature_importance_plotter.py +0 -0
  73. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/integrations/joblib/__init__.py +0 -0
  74. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/integrations/mlflow/__init__.py +0 -0
  75. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/integrations/optuna/__init__.py +0 -0
  76. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/mixins/__init__.py +0 -0
  77. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/mixins/callbacks.py +0 -0
  78. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/mixins/model_serializer.py +0 -0
  79. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/models/__init__.py +0 -0
  80. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/models/component_splitting/__init__.py +0 -0
  81. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/models/component_splitting/component_splitter.py +0 -0
  82. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/models/component_splitting/linear_component_splitter_model/linear_component_splitter_model.z +0 -0
  83. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/models/component_splitting/linear_component_splitter_model/linear_component_splitter_model.z.license +0 -0
  84. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/models/component_splitting_model.py +0 -0
  85. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/models/forecasting/__init__.py +0 -0
  86. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/models/forecasting/flatliner_forecaster.py +0 -0
  87. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/models/forecasting/forecaster.py +0 -0
  88. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/presets/__init__.py +0 -0
  89. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/testing.py +0 -0
  90. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/transforms/__init__.py +0 -0
  91. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/transforms/energy_domain/__init__.py +0 -0
  92. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/transforms/energy_domain/wind_power_feature_adder.py +0 -0
  93. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/transforms/general/__init__.py +0 -0
  94. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/transforms/general/flagger.py +0 -0
  95. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/transforms/general/outlier_handler.py +0 -0
  96. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/transforms/general/selector.py +0 -0
  97. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/transforms/postprocessing/__init__.py +0 -0
  98. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/transforms/postprocessing/quantile_sorter.py +0 -0
  99. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/transforms/time_domain/__init__.py +0 -0
  100. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/transforms/validation/__init__.py +0 -0
  101. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/transforms/validation/completeness_checker.py +0 -0
  102. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/transforms/validation/input_consistency_checker.py +0 -0
  103. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/transforms/weather_domain/__init__.py +0 -0
  104. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/utils/__init__.py +0 -0
  105. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/utils/xgboost.py +0 -0
  106. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/workflows/__init__.py +0 -0
  107. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/workflows/callbacks/__init__.py +0 -0
  108. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/workflows/callbacks/data_save.py +0 -0
  109. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/workflows/callbacks/model_performance_callback.py +0 -0
  110. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/src/openstef_models/workflows/custom_component_split_workflow.py +0 -0
  111. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/__init__.py +0 -0
  112. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/integration/__init__.py +0 -0
  113. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/integration/test_integration.py +0 -0
  114. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/__init__.py +0 -0
  115. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/explainability/__init__.py +0 -0
  116. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/explainability/plotters/__init__.py +0 -0
  117. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/integrations/__init__.py +0 -0
  118. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/integrations/joblib/__init__.py +0 -0
  119. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/integrations/joblib/test_joblib_model_serializer.py +0 -0
  120. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/integrations/mlflow/__init__.py +0 -0
  121. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/integrations/optuna/__init__.py +0 -0
  122. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/models/__init__.py +0 -0
  123. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/models/component_splitting/__init__.py +0 -0
  124. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/models/component_splitting/test_constant_component_splitter.py +0 -0
  125. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/models/component_splitting/test_linear_component_splitter.py +0 -0
  126. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/models/forecasting/__init__.py +0 -0
  127. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/models/forecasting/conftest.py +0 -0
  128. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/models/forecasting/test_flatliner_forecaster.py +0 -0
  129. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/models/forecasting/test_gblinear_forecaster.py +0 -0
  130. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/models/forecasting/test_xgboost_forecaster.py +0 -0
  131. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/test_example.py +0 -0
  132. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/transforms/__init__.py +0 -0
  133. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/transforms/energy_domain/__init__.py +0 -0
  134. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/transforms/energy_domain/test_wind_power_feature_adder.py +0 -0
  135. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/transforms/general/__init__.py +0 -0
  136. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/transforms/general/test_dimensionality_reducer.py +0 -0
  137. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/transforms/general/test_empty_feature_remover.py +0 -0
  138. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/transforms/general/test_flagger.py +0 -0
  139. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/transforms/general/test_nan_dropper.py +0 -0
  140. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/transforms/general/test_sample_weighter.py +0 -0
  141. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/transforms/general/test_selector.py +0 -0
  142. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/transforms/general/test_shifter.py +0 -0
  143. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/transforms/postprocessing/__init__.py +0 -0
  144. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/transforms/postprocessing/test_confidence_interval_applicator.py +0 -0
  145. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/transforms/postprocessing/test_quantile_sorter.py +0 -0
  146. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/transforms/time_domain/__init__.py +0 -0
  147. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/transforms/time_domain/test_cyclic_features_adder.py +0 -0
  148. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/transforms/time_domain/test_holiday_features_adder.py +0 -0
  149. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/transforms/time_domain/test_lags_adder.py +0 -0
  150. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/transforms/validation/__init__.py +0 -0
  151. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/transforms/validation/test_completeness_checker.py +0 -0
  152. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/transforms/validation/test_flatline_checker.py +0 -0
  153. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/transforms/validation/test_input_consistency_checker.py +0 -0
  154. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/transforms/weather_domain/__init__.py +0 -0
  155. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/transforms/weather_domain/test_atmosphere_derived_features_adder.py +0 -0
  156. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/transforms/weather_domain/test_daylight_feature_adder.py +0 -0
  157. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/transforms/weather_domain/test_radiation_derived_featuers_adder.py +0 -0
  158. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/utils/__init__.py +0 -0
  159. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/utils/test_feature_selection.py +0 -0
  160. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/utils/test_loss_functions.py +0 -0
  161. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/workflows/__init__.py +0 -0
  162. {openstef_models-4.0.1 → openstef_models-4.1.1.dev0}/tests/unit/workflows/callbacks/__init__.py +0 -0
@@ -35,10 +35,6 @@ MANIFEST
35
35
  # Ruff
36
36
  .ruff_cache/
37
37
 
38
- # Pyright
39
- .pyright/
40
- # pyright-report/
41
-
42
38
  # Test, coverage, tox
43
39
  .pytest_cache/
44
40
  .coverage
@@ -69,6 +65,8 @@ docs/_build/
69
65
  docs/source/api/generated/
70
66
  docs/source/tutorials/
71
67
  docs/source/benchmarks/
68
+ # Community health files materialized from OpenSTEF/.github at build time
69
+ docs/source/contribute/_community/
72
70
  docs/source/user_guide/**/quick_start_tutorial.py
73
71
  docs/source/user_guide/**/feature_engineering_tutorial.py
74
72
  docs/source/user_guide/**/datasets_tutorial.py
@@ -135,6 +133,9 @@ benchmark_results*/
135
133
  # Local dataset files
136
134
  liander_dataset/
137
135
 
136
+ # Deployment example run artifacts (MLflow store, forecasts, dataset, Celery/Airflow state)
137
+ openstef_deployment_runs/
138
+
138
139
  # Mlflow
139
140
  /mlflow
140
141
  /mlflow_artifacts_local
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openstef-models
3
- Version: 4.0.1
3
+ Version: 4.1.1.dev0
4
4
  Summary: Core models for OpenSTEF
5
5
  Project-URL: Documentation, https://openstef.github.io/openstef/index.html
6
6
  Project-URL: Homepage, https://lfenergy.org/projects/openstef/
@@ -18,8 +18,8 @@ Classifier: Programming Language :: Python :: 3.14
18
18
  Requires-Python: <4.0,>=3.12
19
19
  Requires-Dist: holidays>=0.79
20
20
  Requires-Dist: mlflow-skinny<4,>=3
21
- Requires-Dist: openstef-beam<5,>=4.0.0.dev0
22
- Requires-Dist: openstef-core<5,>=4.0.0.dev0
21
+ Requires-Dist: openstef-beam<5,>=4
22
+ Requires-Dist: openstef-core<5,>=4
23
23
  Requires-Dist: pvlib>=0.13
24
24
  Requires-Dist: pycountry>=24.6.1
25
25
  Requires-Dist: scikit-learn<1.8,>=1.7.1
@@ -1,15 +1,13 @@
1
1
  # SPDX-FileCopyrightText: 2025 Contributors to the OpenSTEF project <openstef@lfenergy.org>
2
2
  #
3
3
  # SPDX-License-Identifier: MPL-2.0
4
-
5
4
  [build-system]
6
5
  build-backend = "hatchling.build"
7
-
8
6
  requires = [ "hatchling" ]
9
7
 
10
8
  [project]
11
9
  name = "openstef-models"
12
- version = "4.0.1"
10
+ version = "4.1.1.dev0"
13
11
  description = "Core models for OpenSTEF"
14
12
  readme = "README.md"
15
13
  keywords = [ "energy", "forecasting", "machinelearning" ]
@@ -26,34 +24,39 @@ classifiers = [
26
24
  "Programming Language :: Python :: 3.13",
27
25
  "Programming Language :: Python :: 3.14",
28
26
  ]
29
-
30
27
  dependencies = [
31
28
  "holidays>=0.79",
32
29
  "mlflow-skinny>=3,<4",
33
- "openstef-beam>=4.0.0.dev0,<5",
34
- "openstef-core>=4.0.0.dev0,<5",
30
+ "openstef-beam>=4,<5",
31
+ "openstef-core>=4,<5",
35
32
  "pvlib>=0.13",
36
33
  "pycountry>=24.6.1",
37
34
  "scikit-learn>=1.7.1,<1.8",
38
35
  "scipy>=1.16.3,<2",
39
36
  ]
40
-
41
37
  optional-dependencies.lgbm = [
42
38
  "lightgbm>=4.6",
43
39
  ]
44
-
45
40
  optional-dependencies.tuning = [ "optuna>=4.7" ]
46
-
47
41
  optional-dependencies.xgb-cpu = [
48
42
  "xgboost>=3,<4; sys_platform=='darwin'",
49
43
  "xgboost-cpu>=3,<4; sys_platform=='linux' or sys_platform=='win32'",
50
44
  ]
51
-
52
45
  optional-dependencies.xgb-gpu = [ "xgboost>=3,<4" ]
53
46
  urls.Documentation = "https://openstef.github.io/openstef/index.html"
54
47
  urls.Homepage = "https://lfenergy.org/projects/openstef/"
55
48
  urls.Issues = "https://github.com/OpenSTEF/openstef/issues"
56
49
  urls.Repository = "https://github.com/OpenSTEF/openstef"
57
50
 
58
- [tool.hatch.build.targets.wheel]
59
- packages = [ "src/openstef_models" ]
51
+ [tool.hatch]
52
+ build.targets.wheel.packages = [ "src/openstef_models" ]
53
+
54
+ [tool.uv]
55
+ # xgb-cpu installs xgboost-cpu (Linux/Windows) and xgb-gpu installs the full
56
+ # xgboost; both provide the same import, so exactly one may be installed.
57
+ conflicts = [
58
+ [
59
+ { extra = "xgb-cpu" },
60
+ { extra = "xgb-gpu" },
61
+ ],
62
+ ]
@@ -9,9 +9,9 @@ from __future__ import annotations
9
9
  from typing import TYPE_CHECKING
10
10
 
11
11
  import plotly.graph_objects as go
12
- from plotly.subplots import make_subplots # pyright: ignore[reportUnknownVariableType]
12
+ from plotly.subplots import make_subplots
13
13
 
14
- from openstef_core.datasets import TimeSeriesDataset # noqa: TC001 # runtime needed for pyright
14
+ from openstef_core.datasets import TimeSeriesDataset # noqa: TC001
15
15
 
16
16
  if TYPE_CHECKING:
17
17
  import pandas as pd
@@ -77,7 +77,7 @@ class ContributionsPlotter:
77
77
  vertical_spacing=0.03,
78
78
  )
79
79
 
80
- fig.add_trace( # pyright: ignore[reportUnknownMemberType]
80
+ fig.add_trace(
81
81
  go.Scatter(
82
82
  x=df.index,
83
83
  y=prediction,
@@ -89,9 +89,9 @@ class ContributionsPlotter:
89
89
  row=1,
90
90
  col=1,
91
91
  )
92
- fig.add_trace(heatmap, row=2, col=1) # pyright: ignore[reportUnknownMemberType]
92
+ fig.add_trace(heatmap, row=2, col=1)
93
93
 
94
- fig.update_layout( # pyright: ignore[reportUnknownMemberType]
94
+ fig.update_layout(
95
95
  yaxis_title="Prediction",
96
96
  yaxis2_title="Feature",
97
97
  xaxis2_title="Time",
@@ -150,7 +150,7 @@ class ContributionsPlotter:
150
150
 
151
151
  for feat in top:
152
152
  names.append(feat)
153
- values.append(float(row[feat])) # pyright: ignore[reportArgumentType]
153
+ values.append(float(row[feat]))
154
154
  measures.append("relative")
155
155
 
156
156
  if len(remaining) > 0:
@@ -213,7 +213,7 @@ class ContributionsPlotter:
213
213
 
214
214
  return go.Figure(
215
215
  go.Bar(
216
- x=mean_abs.values, # pyright: ignore[reportArgumentType]
216
+ x=mean_abs.values,
217
217
  y=mean_abs.index.tolist(),
218
218
  orientation="h",
219
219
  marker_color="#1f77b4",
@@ -9,4 +9,4 @@ extend OpenSTEF functionality by integrating with external systems such as
9
9
  monitoring tools, databases, cloud storage, and custom processing pipelines.
10
10
  """
11
11
 
12
- __all__ = ["joblib", "mlflow", "optuna"] # noqa: F822 # pyright: ignore[reportUnsupportedDunderAll] # Sub-packages with optional deps; not imported to avoid missing-extra errors at import time
12
+ __all__ = ["joblib", "mlflow", "optuna"]
@@ -58,11 +58,11 @@ class JoblibModelSerializer(ModelSerializer):
58
58
 
59
59
  @override
60
60
  def serialize(self, model: object, file: BinaryIO) -> None:
61
- joblib.dump(model, file) # type: ignore[reportUnknownMemberType]
61
+ joblib.dump(model, file)
62
62
 
63
63
  @override
64
64
  def deserialize(self, file: BinaryIO) -> object:
65
- return joblib.load(file) # type: ignore[reportUnknownMemberType]
65
+ return joblib.load(file)
66
66
 
67
67
 
68
68
  __all__ = ["JoblibModelSerializer"]
@@ -69,6 +69,13 @@ class MLFlowStorage(BaseConfig):
69
69
  local_artifacts_path: Path = Field(
70
70
  default=Path("./mlflow_artifacts_local"), description="Local path for storing MLflow artifacts before upload."
71
71
  )
72
+ artifact_location: str | None = Field(
73
+ default=None,
74
+ description="Artifact storage location for newly created experiments. Defaults to MLflow's "
75
+ "own default (a ``./mlruns`` directory relative to the working directory). Set this to an "
76
+ "absolute URI (e.g. ``file:///abs/path`` or ``s3://bucket/prefix``) to keep artifacts "
77
+ "self-contained — useful with a database tracking backend such as ``sqlite:///...``.",
78
+ )
72
79
  experiment_name_prefix: str = Field(default="", description="Prefix for MLflow experiment names.")
73
80
  # Artifact subdirectories
74
81
  data_path: str = Field(default="data", description="Subdirectory for storing training data artifacts.")
@@ -119,6 +126,7 @@ class MLFlowStorage(BaseConfig):
119
126
  if experiment is None:
120
127
  experiment_id = self._client.create_experiment(
121
128
  name=f"{self.experiment_name_prefix}{model_id}",
129
+ artifact_location=self.artifact_location,
122
130
  tags=experiment_tags,
123
131
  )
124
132
  else:
@@ -194,7 +194,7 @@ class MLFlowStorageCallback(BaseConfig, ForecastingCallback):
194
194
  )
195
195
  return
196
196
 
197
- context.workflow.model = old_model # pyright: ignore[reportAttributeAccessIssue]
197
+ context.workflow.model = old_model
198
198
  self._logger.info(
199
199
  "Loaded model from MLflow run %s for model %s",
200
200
  run_id,
@@ -209,7 +209,7 @@ class MLFlowStorageCallback(BaseConfig, ForecastingCallback):
209
209
  run_id = cast(str, run.info.run_id)
210
210
 
211
211
  if not self._check_tags_compatible(
212
- run_tags=run.data.tags, # pyright: ignore[reportUnknownMemberType, reportUnknownArgumentType]
212
+ run_tags=run.data.tags,
213
213
  new_tags=workflow.model.tags,
214
214
  run_id=run_id,
215
215
  ):
@@ -233,9 +233,9 @@ class MLFlowStorageCallback(BaseConfig, ForecastingCallback):
233
233
  return
234
234
 
235
235
  if self._check_is_new_model_better(old_metrics=old_metrics, new_metrics=new_metrics):
236
- workflow.model = new_model # pyright: ignore[reportAttributeAccessIssue]
236
+ workflow.model = new_model
237
237
  else:
238
- workflow.model = old_model # pyright: ignore[reportAttributeAccessIssue]
238
+ workflow.model = old_model
239
239
  self._logger.info(
240
240
  "New model did not improve %s metric from previous run %s, reusing old model",
241
241
  self.model_selection_metric,
@@ -250,7 +250,7 @@ class MLFlowStorageCallback(BaseConfig, ForecastingCallback):
250
250
  continue
251
251
  suffix = f"_{name}" if name else ""
252
252
  fig = component.plot_feature_importances()
253
- fig.write_html(data_path / f"feature_importances{suffix}.html") # pyright: ignore[reportUnknownMemberType]
253
+ fig.write_html(data_path / f"feature_importances{suffix}.html")
254
254
 
255
255
  def _find_run(self, model_id: str, run_name: str | None) -> Run | None:
256
256
  """Find an MLflow run by model_id and optional run_name.
@@ -7,7 +7,7 @@
7
7
  from collections import defaultdict
8
8
  from collections.abc import Callable
9
9
  from dataclasses import dataclass
10
- from typing import Any, Literal, NamedTuple, cast
10
+ from typing import Any, Literal, NamedTuple, cast, override
11
11
 
12
12
  from pydantic import ConfigDict, Field, SkipValidation
13
13
 
@@ -16,7 +16,7 @@ try:
16
16
  except ImportError as _err:
17
17
  from openstef_core.exceptions import MissingExtraError
18
18
 
19
- raise MissingExtraError("optuna", "openstef-models[tuning]") from _err
19
+ raise MissingExtraError("optuna", "openstef-models", install_extra="tuning") from _err
20
20
 
21
21
  from openstef_core.base_model import BaseConfig
22
22
  from openstef_core.datasets import TimeSeriesDataset
@@ -65,7 +65,8 @@ class TuningResult[ConfigT: BaseConfig]:
65
65
  study: optuna.Study
66
66
  workflow: CustomForecastingWorkflow
67
67
 
68
- def __repr__(self) -> str: # noqa: D105 # self-explanatory
68
+ @override
69
+ def __repr__(self) -> str: # self-explanatory
69
70
  n = len(self.study.best_params)
70
71
  return f"TuningResult({n} params tuned)" if n else "TuningResult(no tuning)"
71
72
 
@@ -328,7 +329,7 @@ class HyperparameterTuner[ConfigT: BaseConfig](BaseConfig):
328
329
  best_config = self._reconstruct_best_config(
329
330
  config=self.config, model_tuning_info=model_tuning_info, study=study
330
331
  )
331
- return best_config, study # type: ignore[return-value] # ConfigT narrowing not expressible
332
+ return best_config, study # ty: ignore[invalid-return-type] # ConfigT narrowing not expressible
332
333
 
333
334
  def fit_with_tuning(self, *, show_progress_bar: bool = True) -> TuningResult[ConfigT]:
334
335
  """Tune, then fit a final workflow with the best config.
@@ -14,6 +14,7 @@ components like solar, wind, or other energy sources. No training is required
14
14
  since ratios are predefined by the user.
15
15
  """
16
16
 
17
+ import math
17
18
  from typing import Self, override
18
19
 
19
20
  import pandas as pd
@@ -39,7 +40,7 @@ class ConstantComponentSplitterConfig(ComponentSplitterConfig):
39
40
  @field_validator("component_ratios")
40
41
  @classmethod
41
42
  def _validate_component_ratios(cls, value: dict[EnergyComponentType, float]) -> dict[EnergyComponentType, float]:
42
- if sum(value.values()) != 1.0:
43
+ if not math.isclose(sum(value.values()), 1.0):
43
44
  raise ValueError("Component ratios must sum to 1.0.")
44
45
 
45
46
  return value
@@ -101,7 +101,7 @@ class LinearComponentSplitter(ComponentSplitter):
101
101
  """
102
102
  super().__init__()
103
103
  self._config = config
104
- self._model = joblib.load(self.config.linear_model_path) # type: ignore[reportUnknownMemberType]
104
+ self._model = joblib.load(self.config.linear_model_path)
105
105
 
106
106
  @property
107
107
  @override
@@ -148,7 +148,7 @@ class LinearComponentSplitter(ComponentSplitter):
148
148
  )
149
149
 
150
150
  # Drop rows with NaN values
151
- input_df = input_df.dropna() # pyright: ignore[reportUnknownMemberType]
151
+ input_df = input_df.dropna()
152
152
 
153
153
  if input_df.empty:
154
154
  error_msg = "No valid data available for component splitting after dropping NaNs"
@@ -149,7 +149,7 @@ class BaseCaseForecaster(Forecaster, ExplainableForecaster, ContributionsMixin):
149
149
  # Fill missing values with fallback lag if needed
150
150
  if prediction.isna().any():
151
151
  prediction_fallback = target_series.shift(freq=self.hyperparams.fallback_lag).reindex(forecast_index)
152
- prediction = prediction.fillna(prediction_fallback) # pyright: ignore[reportUnknownMemberType]
152
+ prediction = prediction.fillna(prediction_fallback)
153
153
 
154
154
  return ForecastDataset(
155
155
  data=pd.DataFrame(
@@ -171,7 +171,7 @@ class BaseCaseForecaster(Forecaster, ExplainableForecaster, ContributionsMixin):
171
171
  @override
172
172
  def feature_importances(self) -> pd.DataFrame:
173
173
  return pd.DataFrame(
174
- data=[1.0],
174
+ data=1.0,
175
175
  index=["load"],
176
176
  columns=[quantile.format() for quantile in self.quantiles],
177
177
  )
@@ -114,7 +114,7 @@ class ConstantQuantileForecaster(Forecaster, ExplainableForecaster, Contribution
114
114
  @override
115
115
  def feature_importances(self) -> pd.DataFrame:
116
116
  return pd.DataFrame(
117
- data=[1.0],
117
+ data=1.0,
118
118
  index=["load"],
119
119
  columns=[quantile.format() for quantile in self.quantiles],
120
120
  )
@@ -38,7 +38,7 @@ from openstef_models.utils.xgboost import get_median_shap_contribs
38
38
  try:
39
39
  import xgboost as xgb
40
40
  except ImportError as e:
41
- raise MissingExtraError("xgboost", "openstef-models") from e
41
+ raise MissingExtraError("xgboost", "openstef-models", install_extra="xgb-cpu") from e
42
42
 
43
43
 
44
44
  class GBLinearHyperParams(HyperParams):
@@ -186,6 +186,7 @@ class GBLinearForecaster(Forecaster, ExplainableForecaster, ContributionsMixin):
186
186
  def hparams(self) -> GBLinearHyperParams:
187
187
  return self.hyperparams
188
188
 
189
+ @override
189
190
  def model_post_init(self, _context: object, /) -> None:
190
191
  """Initialize the underlying XGBoost gblinear model from configuration."""
191
192
  self._gblinear_model = xgb.XGBRegressor(
@@ -290,6 +291,7 @@ class GBLinearForecaster(Forecaster, ExplainableForecaster, ContributionsMixin):
290
291
  target_column=data.target_column,
291
292
  )
292
293
 
294
+ @override
293
295
  def predict_contributions(self, data: ForecastInputDataset) -> TimeSeriesDataset:
294
296
  """Compute SHAP feature contributions for the median quantile.
295
297
 
@@ -196,6 +196,7 @@ class LGBMForecaster(Forecaster, ExplainableForecaster, ContributionsMixin):
196
196
  def hparams(self) -> LGBMHyperParams:
197
197
  return self.hyperparams
198
198
 
199
+ @override
199
200
  def model_post_init(self, _context: object, /) -> None:
200
201
  """Initialize the underlying LightGBM model from configuration.
201
202
 
@@ -205,7 +206,7 @@ class LGBMForecaster(Forecaster, ExplainableForecaster, ContributionsMixin):
205
206
  try:
206
207
  from lightgbm import LGBMRegressor # noqa: PLC0415
207
208
  except ImportError as e:
208
- raise MissingExtraError("lightgbm", "openstef-models") from e
209
+ raise MissingExtraError("lightgbm", "openstef-models", install_extra="lgbm") from e
209
210
 
210
211
  lgbm_params = {
211
212
  # Core parameters
@@ -234,7 +235,7 @@ class LGBMForecaster(Forecaster, ExplainableForecaster, ContributionsMixin):
234
235
  }
235
236
 
236
237
  self._lgbm_model = MultiQuantileRegressor(
237
- base_learner=LGBMRegressor, # type: ignore
238
+ base_learner=LGBMRegressor,
238
239
  quantile_param="alpha",
239
240
  hyperparams=lgbm_params,
240
241
  quantiles=[float(q) for q in self.quantiles],
@@ -294,6 +295,7 @@ class LGBMForecaster(Forecaster, ExplainableForecaster, ContributionsMixin):
294
295
  target_column=data.target_column,
295
296
  )
296
297
 
298
+ @override
297
299
  def predict_contributions(self, data: ForecastInputDataset) -> TimeSeriesDataset:
298
300
  """Compute SHAP feature contributions for the median quantile.
299
301
 
@@ -317,7 +319,7 @@ class LGBMForecaster(Forecaster, ExplainableForecaster, ContributionsMixin):
317
319
  model: LGBMRegressor = self._lgbm_model.models[median_idx] # type: ignore
318
320
 
319
321
  # Get SHAP contributions from median quantile model (includes bias as last column)
320
- contribs: np.ndarray = model.predict(input_data, pred_contrib=True) # type: ignore
322
+ contribs: np.ndarray = model.predict(input_data, pred_contrib=True)
321
323
 
322
324
  columns = [*input_data.columns, "bias"]
323
325
  contribs_df = pd.DataFrame(contribs, index=input_data.index, columns=columns)
@@ -197,6 +197,7 @@ class LGBMLinearForecaster(Forecaster, ExplainableForecaster, ContributionsMixin
197
197
  def hparams(self) -> LGBMLinearHyperParams:
198
198
  return self.hyperparams
199
199
 
200
+ @override
200
201
  def model_post_init(self, _context: object, /) -> None:
201
202
  """Initialize the underlying LightGBM linear model from configuration.
202
203
 
@@ -206,7 +207,7 @@ class LGBMLinearForecaster(Forecaster, ExplainableForecaster, ContributionsMixin
206
207
  try:
207
208
  from lightgbm import LGBMRegressor # noqa: PLC0415
208
209
  except ImportError as e:
209
- raise MissingExtraError("lightgbm", "openstef-models") from e
210
+ raise MissingExtraError("lightgbm", "openstef-models", install_extra="lgbm") from e
210
211
 
211
212
  lgbmlinear_params = {
212
213
  # Core parameters
@@ -235,7 +236,7 @@ class LGBMLinearForecaster(Forecaster, ExplainableForecaster, ContributionsMixin
235
236
  }
236
237
 
237
238
  self._lgbmlinear_model = MultiQuantileRegressor(
238
- base_learner=LGBMRegressor, # type: ignore
239
+ base_learner=LGBMRegressor,
239
240
  quantile_param="alpha",
240
241
  hyperparams=lgbmlinear_params,
241
242
  quantiles=[float(q) for q in self.quantiles],
@@ -319,7 +320,7 @@ class LGBMLinearForecaster(Forecaster, ExplainableForecaster, ContributionsMixin
319
320
  model: LGBMRegressor = self._lgbmlinear_model.models[median_idx] # type: ignore
320
321
 
321
322
  # Get SHAP contributions from median quantile model (includes bias as last column)
322
- contribs: np.ndarray = model.predict(input_data, pred_contrib=True) # type: ignore
323
+ contribs: np.ndarray = model.predict(input_data, pred_contrib=True)
323
324
 
324
325
  columns = [*input_data.columns, "bias"]
325
326
  contribs_df = pd.DataFrame(contribs, index=input_data.index, columns=columns)
@@ -214,13 +214,11 @@ class MedianForecaster(Forecaster, ExplainableForecaster, ContributionsMixin):
214
214
  for time_step in range(lag_array.shape[0]):
215
215
  # Get the lag features for the current time step.
216
216
  current_lags = lag_array[time_step]
217
- # Calculate the median of the available lag features, ignoring NaNs.
218
- median = np.nanmedian(current_lags) # type: ignore
219
- # If the median calculation resulted in NaN (e.g., all lags were NaN), skip the autoregression step.
220
- if not np.isnan(median): # type: ignore
221
- median = float(median) # type: ignore
222
- else:
217
+ # If all lags are NaN there is no value to predict, so skip the autoregressive step.
218
+ if np.all(np.isnan(current_lags)):
223
219
  continue
220
+ # Calculate the median of the available lag features, ignoring NaNs.
221
+ median = float(np.nanmedian(current_lags))
224
222
 
225
223
  # Store the calculated median in the prediction array.
226
224
  prediction[time_step] = median
@@ -239,7 +237,7 @@ class MedianForecaster(Forecaster, ExplainableForecaster, ContributionsMixin):
239
237
  prediction_df = prediction_df.reindex(input_data.index)
240
238
 
241
239
  return ForecastDataset(
242
- data=prediction_df.dropna().rename(columns={"median": self.quantiles[0].format()}), # type: ignore
240
+ data=prediction_df.dropna().rename(columns={"median": self.quantiles[0].format()}),
243
241
  sample_interval=data.sample_interval,
244
242
  forecast_start=data.forecast_start,
245
243
  target_column=data.target_column,
@@ -34,7 +34,7 @@ from openstef_models.utils.xgboost import get_median_shap_contribs
34
34
  try:
35
35
  import xgboost as xgb
36
36
  except ImportError as e:
37
- raise MissingExtraError("xgboost", "openstef-models") from e
37
+ raise MissingExtraError("xgboost", "openstef-models", install_extra="xgb-cpu") from e
38
38
 
39
39
 
40
40
  class XGBoostHyperParams(HyperParams):
@@ -250,6 +250,7 @@ class XGBoostForecaster(Forecaster, ExplainableForecaster, ContributionsMixin):
250
250
  def hparams(self) -> XGBoostHyperParams:
251
251
  return self.hyperparams
252
252
 
253
+ @override
253
254
  def model_post_init(self, _context: object, /) -> None:
254
255
  """Initialize the underlying XGBoost model from configuration."""
255
256
  self._xgboost_model = xgb.XGBRegressor(
@@ -364,6 +365,7 @@ class XGBoostForecaster(Forecaster, ExplainableForecaster, ContributionsMixin):
364
365
  target_column=data.target_column,
365
366
  )
366
367
 
368
+ @override
367
369
  def predict_contributions(self, data: ForecastInputDataset) -> TimeSeriesDataset:
368
370
  """Compute SHAP feature contributions for the median quantile.
369
371
 
@@ -12,6 +12,7 @@ unified interface while providing consistent data transformation and validation.
12
12
 
13
13
  import logging
14
14
  from abc import abstractmethod
15
+ from collections.abc import Sequence
15
16
  from datetime import datetime, timedelta
16
17
  from functools import partial
17
18
  from typing import Self, cast, override
@@ -29,7 +30,7 @@ from openstef_core.datasets import (
29
30
  TimeSeriesDataset,
30
31
  )
31
32
  from openstef_core.datasets.timeseries_dataset import validate_horizons_present
32
- from openstef_core.exceptions import InsufficientlyCompleteError, NotFittedError
33
+ from openstef_core.exceptions import InsufficientlyCompleteError, NotFittedError, PredictError
33
34
  from openstef_core.mixins import HyperParams, Predictor, TransformPipeline
34
35
  from openstef_core.types import LeadTime, Quantile
35
36
  from openstef_models.explainability.mixins import ContributionsMixin, ExplainableForecaster
@@ -137,7 +138,7 @@ class BaseForecastingModel(BaseModel, Predictor[TimeSeriesDataset, ForecastDatas
137
138
  "Default of 0 assumes no invalid rows are created by preprocessing.",
138
139
  )
139
140
 
140
- evaluation_metrics: list[MetricProvider] = Field(
141
+ evaluation_metrics: list[MetricProvider] = Field( # ty: ignore[invalid-assignment]
141
142
  default_factory=lambda: [R2Provider(), ObservedProbabilityProvider()],
142
143
  description="List of metric providers for evaluating model score.",
143
144
  )
@@ -206,7 +207,7 @@ class BaseForecastingModel(BaseModel, Predictor[TimeSeriesDataset, ForecastDatas
206
207
  raise ValueError(msg)
207
208
  return self
208
209
 
209
- def get_explainable_components(self) -> dict[str, ExplainableForecaster]: # noqa: PLR6301
210
+ def get_explainable_components(self) -> dict[str, ExplainableForecaster]:
210
211
  """Return named components that support feature-importance plotting.
211
212
 
212
213
  Keys are used as filename suffixes; an empty key means no suffix.
@@ -310,6 +311,58 @@ class BaseForecastingModel(BaseModel, Predictor[TimeSeriesDataset, ForecastDatas
310
311
 
311
312
  return self.postprocessing.transform(data=raw_predictions)
312
313
 
314
+ def predict_batch(
315
+ self,
316
+ data: list[TimeSeriesDataset],
317
+ forecast_start: Sequence[datetime],
318
+ ) -> list[ForecastDataset]:
319
+ """Generate forecasts for a batch of inputs with one model instance.
320
+
321
+ Each item is preprocessed independently (keeping its own ``forecast_start``);
322
+ the preprocessed batch is then forecast together via ``_predict_batch`` and
323
+ each raw forecast is postprocessed.
324
+
325
+ Args:
326
+ data: One ``TimeSeriesDataset`` per location/series to forecast.
327
+ forecast_start: One forecast start per item, in input order. Pass
328
+ ``[origin] * len(data)`` to share a single origin across the batch.
329
+
330
+ Returns:
331
+ One ``ForecastDataset`` per input, in input order.
332
+
333
+ Raises:
334
+ NotFittedError: If the model has not been fitted.
335
+ ValueError: If ``forecast_start`` length differs from the batch size.
336
+ """
337
+ if not self.is_fitted:
338
+ raise NotFittedError(type(self).__name__)
339
+ if not data:
340
+ return []
341
+ if len(forecast_start) != len(data):
342
+ msg = f"forecast_start sequence length {len(forecast_start)} != batch size {len(data)}."
343
+ raise ValueError(msg)
344
+
345
+ inputs = [
346
+ self.prepare_input(data=item, forecast_start=start)
347
+ for item, start in zip(data, forecast_start, strict=True)
348
+ ]
349
+ raw = self._predict_batch(input_data=inputs)
350
+ return [self.postprocessing.transform(data=forecast) for forecast in raw]
351
+
352
+ def _predict_batch(self, input_data: list[ForecastInputDataset]) -> list[ForecastDataset]:
353
+ """Generate raw forecasts for a preprocessed batch.
354
+
355
+ The default fallback loops ``_predict`` item by item. Subclasses backed by a
356
+ batch-native forecaster override this to issue a single batched call.
357
+
358
+ Args:
359
+ input_data: One preprocessed ``ForecastInputDataset`` per batch item.
360
+
361
+ Returns:
362
+ One raw ``ForecastDataset`` per input, in input order.
363
+ """
364
+ return [self._predict(input_data=item) for item in input_data]
365
+
313
366
  def score(self, data: TimeSeriesDataset) -> SubsetMetric:
314
367
  """Evaluate model performance on the provided dataset.
315
368
 
@@ -330,7 +383,7 @@ class BaseForecastingModel(BaseModel, Predictor[TimeSeriesDataset, ForecastDatas
330
383
  if prediction.target_series is None:
331
384
  raise ValueError("Prediction dataset must contain target series for scoring.")
332
385
 
333
- prediction = prediction.pipe_pandas(pd.DataFrame.dropna, subset=[self.target_column]) # pyright: ignore[reportUnknownArgumentType, reportUnknownMemberType]
386
+ prediction = prediction.pipe_pandas(pd.DataFrame.dropna, subset=[self.target_column])
334
387
 
335
388
  pipeline = EvaluationPipeline(
336
389
  config=EvaluationConfig(available_ats=[], lead_times=[self.max_horizon]),
@@ -482,7 +535,7 @@ class ForecastingModel(BaseForecastingModel):
482
535
  """
483
536
  validate_horizons_present(data, self.forecaster.horizons)
484
537
 
485
- target_dropna = partial(pd.DataFrame.dropna, subset=[self.target_column]) # pyright: ignore[reportUnknownMemberType]
538
+ target_dropna = partial(pd.DataFrame.dropna, subset=[self.target_column])
486
539
  if data.pipe_pandas(target_dropna).data.empty:
487
540
  msg = (
488
541
  f"No training data available after dropping NaN targets in column '{self.target_column}'. "
@@ -542,6 +595,22 @@ class ForecastingModel(BaseForecastingModel):
542
595
  prediction = self.forecaster.predict(data=input_data)
543
596
  return restore_target(dataset=prediction, original_dataset=input_data, target_column=self.target_column)
544
597
 
598
+ @override
599
+ def _predict_batch(self, input_data: list[ForecastInputDataset]) -> list[ForecastDataset]:
600
+ raw = self.forecaster.predict_batch(data=input_data)
601
+ forecasts: list[ForecastDataset] = []
602
+ for item, forecast in zip(input_data, raw, strict=True):
603
+ if isinstance(forecast, PredictError):
604
+ raise forecast
605
+ forecasts.append(
606
+ restore_target(
607
+ dataset=forecast,
608
+ original_dataset=item,
609
+ target_column=self.target_column,
610
+ )
611
+ )
612
+ return forecasts
613
+
545
614
  @override
546
615
  def predict_contributions(
547
616
  self,
@@ -580,7 +649,7 @@ def restore_target[T: TimeSeriesDataset](
580
649
  target_series = original_dataset.select_features([target_column]).select_version().data[target_column]
581
650
 
582
651
  def _transform_restore_target(df: pd.DataFrame) -> pd.DataFrame:
583
- original_values = df.index.map(target_series) # pyright: ignore[reportUnknownMemberType]
652
+ original_values = df.index.map(target_series) # ty: ignore[invalid-argument-type]
584
653
  restored = pd.Series(original_values, index=df.index, name=target_column)
585
654
 
586
655
  # Preserve NaN values introduced by the OutlierHandler for the target column.