openstef 3.4.10__py3-none-any.whl → 3.4.29__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. openstef/app_settings.py +19 -0
  2. openstef/data_classes/data_prep.py +1 -1
  3. openstef/data_classes/prediction_job.py +12 -8
  4. openstef/enums.py +3 -7
  5. openstef/exceptions.py +1 -1
  6. openstef/feature_engineering/apply_features.py +0 -6
  7. openstef/feature_engineering/data_preparation.py +12 -5
  8. openstef/feature_engineering/feature_applicator.py +1 -5
  9. openstef/feature_engineering/general.py +14 -0
  10. openstef/feature_engineering/missing_values_transformer.py +99 -0
  11. openstef/feature_engineering/weather_features.py +7 -0
  12. openstef/metrics/figure.py +3 -0
  13. openstef/metrics/metrics.py +58 -1
  14. openstef/metrics/reporter.py +7 -0
  15. openstef/model/confidence_interval_applicator.py +28 -3
  16. openstef/model/model_creator.py +36 -27
  17. openstef/model/objective.py +11 -28
  18. openstef/model/objective_creator.py +4 -3
  19. openstef/model/regressors/arima.py +1 -1
  20. openstef/model/regressors/dazls.py +35 -96
  21. openstef/model/regressors/flatliner.py +100 -0
  22. openstef/model/regressors/linear_quantile.py +247 -0
  23. openstef/model/regressors/xgb_multioutput_quantile.py +261 -0
  24. openstef/model/regressors/xgb_quantile.py +3 -0
  25. openstef/model/serializer.py +10 -0
  26. openstef/model_selection/model_selection.py +3 -0
  27. openstef/monitoring/performance_meter.py +1 -2
  28. openstef/monitoring/teams.py +11 -0
  29. openstef/pipeline/create_basecase_forecast.py +11 -1
  30. openstef/pipeline/create_component_forecast.py +11 -22
  31. openstef/pipeline/create_forecast.py +20 -1
  32. openstef/pipeline/optimize_hyperparameters.py +18 -16
  33. openstef/pipeline/train_create_forecast_backtest.py +11 -1
  34. openstef/pipeline/train_model.py +23 -7
  35. openstef/pipeline/utils.py +3 -0
  36. openstef/postprocessing/postprocessing.py +29 -0
  37. openstef/settings.py +15 -0
  38. openstef/tasks/calculate_kpi.py +20 -17
  39. openstef/tasks/create_basecase_forecast.py +13 -5
  40. openstef/tasks/create_components_forecast.py +20 -4
  41. openstef/tasks/create_forecast.py +5 -2
  42. openstef/tasks/split_forecast.py +7 -0
  43. openstef/tasks/train_model.py +7 -5
  44. openstef/tasks/utils/taskcontext.py +7 -0
  45. openstef/validation/validation.py +27 -2
  46. {openstef-3.4.10.dist-info → openstef-3.4.29.dist-info}/METADATA +34 -38
  47. openstef-3.4.29.dist-info/RECORD +91 -0
  48. {openstef-3.4.10.dist-info → openstef-3.4.29.dist-info}/WHEEL +1 -1
  49. openstef/data/dazls_model_3.4.0/dazls_stored_3.4.0_adaptation_model.z +0 -0
  50. openstef/data/dazls_model_3.4.0/dazls_stored_3.4.0_adaptation_model.z.license +0 -3
  51. openstef/data/dazls_model_3.4.0/dazls_stored_3.4.0_adaptation_model_features.z +0 -0
  52. openstef/data/dazls_model_3.4.0/dazls_stored_3.4.0_adaptation_model_features.z.license +0 -3
  53. openstef/data/dazls_model_3.4.0/dazls_stored_3.4.0_adaptation_model_scaler.z +0 -0
  54. openstef/data/dazls_model_3.4.0/dazls_stored_3.4.0_adaptation_model_scaler.z.license +0 -3
  55. openstef/data/dazls_model_3.4.0/dazls_stored_3.4.0_domain_model.z +0 -0
  56. openstef/data/dazls_model_3.4.0/dazls_stored_3.4.0_domain_model.z.license +0 -3
  57. openstef/data/dazls_model_3.4.0/dazls_stored_3.4.0_domain_model_features.z +0 -2
  58. openstef/data/dazls_model_3.4.0/dazls_stored_3.4.0_domain_model_features.z.license +0 -3
  59. openstef/data/dazls_model_3.4.0/dazls_stored_3.4.0_domain_model_scaler.z +0 -0
  60. openstef/data/dazls_model_3.4.0/dazls_stored_3.4.0_domain_model_scaler.z.license +0 -3
  61. openstef/data/dazls_model_3.4.0/dazls_stored_3.4.0_target.z +0 -0
  62. openstef/data/dazls_model_3.4.0/dazls_stored_3.4.0_target.z.license +0 -3
  63. openstef/data/dazls_model_3.4.0/dazls_stored_3.4.0_target_scaler.z +0 -6
  64. openstef/data/dazls_model_3.4.0/dazls_stored_3.4.0_target_scaler.z.license +0 -3
  65. openstef/feature_engineering/historic_features.py +0 -40
  66. openstef/model/regressors/proloaf.py +0 -281
  67. openstef/tasks/run_tracy.py +0 -145
  68. openstef-3.4.10.dist-info/RECORD +0 -104
  69. {openstef-3.4.10.dist-info → openstef-3.4.29.dist-info}/LICENSE +0 -0
  70. {openstef-3.4.10.dist-info → openstef-3.4.29.dist-info}/top_level.txt +0 -0
@@ -23,6 +23,7 @@ from openstef.model.regressors.regressor import OpenstfRegressor
23
23
  from openstef.model.serializer import MLflowSerializer
24
24
  from openstef.model.standard_deviation_generator import StandardDeviationGenerator
25
25
  from openstef.model_selection.model_selection import split_data_train_validation_test
26
+ from openstef.settings import Settings
26
27
  from openstef.validation import validation
27
28
 
28
29
  DEFAULT_TRAIN_HORIZONS_HOURS: list[float] = [0.25, 47.0]
@@ -31,6 +32,11 @@ MAXIMUM_MODEL_AGE: int = 7
31
32
  DEFAULT_EARLY_STOPPING_ROUNDS: int = 10
32
33
  PENALTY_FACTOR_OLD_MODEL: float = 1.2
33
34
 
35
+ structlog.configure(
36
+ wrapper_class=structlog.make_filtering_bound_logger(
37
+ logging.getLevelName(Settings.log_level)
38
+ )
39
+ )
34
40
  logger = structlog.get_logger(__name__)
35
41
 
36
42
 
@@ -60,6 +66,13 @@ def train_model_pipeline(
60
66
  - The validation dataset with forecasts
61
67
  - The test dataset with forecasts
62
68
 
69
+ Raises:
70
+ InputDataInsufficientError: when input data is insufficient.
71
+ InputDataWrongColumnOrderError: when input data has a invalid column order.
72
+ 'load' column should be first and 'horizon' column last.
73
+ OldModelHigherScoreError: When old model is better than new model.
74
+ SkipSaveTrainingForecasts: If old model is better or younger than `MAXIMUM_MODEL_AGE`, the model is not saved.
75
+
63
76
  """
64
77
  # Initialize serializer
65
78
  serializer = MLflowSerializer(mlflow_tracking_uri=mlflow_tracking_uri)
@@ -164,6 +177,7 @@ def train_model_pipeline_core(
164
177
  InputDataInsufficientError: when input data is insufficient.
165
178
  InputDataWrongColumnOrderError: when input data has a invalid column order.
166
179
  OldModelHigherScoreError: When old model is better than new model.
180
+ InputDataOngoingZeroFlatlinerError: when all recent load measurements are zero.
167
181
 
168
182
  Returns:
169
183
  - Fitted_model (OpenstfRegressor)
@@ -172,8 +186,6 @@ def train_model_pipeline_core(
172
186
  - Datasets (tuple[pd.DataFrmae, pd.DataFrame, pd.Dataframe): The train, validation and test sets
173
187
 
174
188
  """
175
- logger = structlog.get_logger(__name__)
176
-
177
189
  # Call common pipeline
178
190
  (
179
191
  model,
@@ -257,6 +269,8 @@ def train_pipeline_common(
257
269
  Raises:
258
270
  InputDataInsufficientError: when input data is insufficient.
259
271
  InputDataWrongColumnOrderError: when input data has a invalid column order.
272
+ 'load' column should be first and 'horizon' column last.
273
+ InputDataOngoingZeroFlatlinerError: when all recent load measurements are zero.
260
274
 
261
275
  """
262
276
  data_with_features = train_pipeline_step_compute_features(
@@ -346,12 +360,9 @@ def train_pipeline_step_compute_features(
346
360
  InputDataInsufficientError: when input data is insufficient.
347
361
  InputDataWrongColumnOrderError: when input data has a invalid column order.
348
362
  ValueError: when the horizon is a string and the corresponding column in not in the input data
363
+ InputDataOngoingZeroFlatlinerError: when all recent load measurements are zero.
349
364
 
350
365
  """
351
- if pj["model"] == "proloaf":
352
- # proloaf is only able to train with one horizon
353
- horizons = [horizons[0]]
354
-
355
366
  if input_data.empty:
356
367
  raise InputDataInsufficientError("Input dataframe is empty")
357
368
  elif "load" not in input_data.columns:
@@ -423,6 +434,10 @@ def train_pipeline_step_train_model(
423
434
  Returns:
424
435
  The trained model
425
436
 
437
+ Raises:
438
+ NotImplementedError: When using invalid model type in the prediction job.
439
+ InputDataWrongColumnOrderError: When 'load' column is not first and 'horizon' column is not last.
440
+
426
441
  """
427
442
  # Test if first column is "load" and last column is "horizon"
428
443
  if train_data.columns[0] != "load" or train_data.columns[-1] != "horizon":
@@ -435,6 +450,7 @@ def train_pipeline_step_train_model(
435
450
  model = ModelCreator.create_model(
436
451
  pj["model"],
437
452
  quantiles=pj["quantiles"],
453
+ **(pj.model_kwargs or {}),
438
454
  )
439
455
 
440
456
  # split x and y data
@@ -523,7 +539,7 @@ def train_pipeline_step_split_data(
523
539
  if pj.train_split_func is None:
524
540
  split_func = split_data_train_validation_test
525
541
  split_args = {
526
- "stratification_min_max": pj["model"] != "proloaf",
542
+ "stratification_min_max": True,
527
543
  "back_test": backtest,
528
544
  }
529
545
  else:
@@ -27,6 +27,9 @@ def generate_forecast_datetime_range(
27
27
  Returns:
28
28
  Start and end datetimes of the forecast range.
29
29
 
30
+ Raises:
31
+ ValueError: If the target column does not have null values.
32
+
30
33
  """
31
34
  # By labeling the True/False values (based on the isnull() statement) as clusters,
32
35
  # we find what True value belongs to what cluster and the amount of True clusters.
@@ -1,6 +1,7 @@
1
1
  # SPDX-FileCopyrightText: 2017-2023 Contributors to the OpenSTEF project <korte.termijn.prognoses@alliander.com> # noqa E501>
2
2
  #
3
3
  # SPDX-License-Identifier: MPL-2.0
4
+ import logging
4
5
  from enum import Enum
5
6
 
6
7
  import numpy as np
@@ -10,6 +11,7 @@ import structlog
10
11
  from openstef.data_classes.prediction_job import PredictionJobDataClass
11
12
  from openstef.enums import ForecastType
12
13
  from openstef.feature_engineering import weather_features
14
+ from openstef.settings import Settings
13
15
 
14
16
  # this is the default for "Lagerwey100"
15
17
  TURBINE_DATA = {
@@ -219,6 +221,11 @@ def add_prediction_job_properties_to_forecast(
219
221
  Dataframe with added metadata.
220
222
 
221
223
  """
224
+ structlog.configure(
225
+ wrapper_class=structlog.make_filtering_bound_logger(
226
+ logging.getLevelName(Settings.log_level)
227
+ )
228
+ )
222
229
  logger = structlog.get_logger(__name__)
223
230
 
224
231
  logger.info("Postproces in preparation of storing")
@@ -244,3 +251,25 @@ def add_prediction_job_properties_to_forecast(
244
251
  forecast["algtype"] = algorithm_type
245
252
 
246
253
  return forecast
254
+
255
+
256
+ def sort_quantiles(
257
+ forecast: pd.DataFrame, quantile_col_start="quantile_P"
258
+ ) -> pd.DataFrame:
259
+ """Sort quantile values so quantiles do not cross.
260
+
261
+ This function assumes that all quantile columns start with 'quantile_P' For more academic details on why this is
262
+ mathematically sounds, please refer to Quantile and Probability Curves Without Crossing (Chernozhukov, 2010)
263
+
264
+ """
265
+ p_columns = [col for col in forecast.columns if col.startswith(quantile_col_start)]
266
+
267
+ if len(p_columns) == 0:
268
+ return forecast
269
+
270
+ # sort the columns
271
+ p_columns = np.sort(p_columns)
272
+
273
+ forecast.loc[:, p_columns] = forecast[p_columns].apply(sorted, axis=1).to_list()
274
+
275
+ return forecast
openstef/settings.py ADDED
@@ -0,0 +1,15 @@
1
+ # SPDX-FileCopyrightText: 2017-2023 Contributors to the OpenSTEF project <korte.termijn.prognoses@alliander.com> # noqa E501>
2
+ #
3
+ # SPDX-License-Identifier: MPL-2.0
4
+
5
+ from functools import lru_cache
6
+
7
+ from openstef.app_settings import AppSettings
8
+
9
+
10
+ @lru_cache
11
+ def _get_app_settings() -> AppSettings:
12
+ return AppSettings()
13
+
14
+
15
+ Settings = _get_app_settings()
@@ -18,6 +18,8 @@ Example:
18
18
  $ python calculate_kpi.py
19
19
 
20
20
  """
21
+ import logging
22
+
21
23
  # Import builtins
22
24
  from datetime import datetime, timedelta
23
25
  from pathlib import Path
@@ -30,6 +32,7 @@ from openstef.data_classes.prediction_job import PredictionJobDataClass
30
32
  from openstef.enums import MLModelType
31
33
  from openstef.exceptions import NoPredictedLoadError, NoRealisedLoadError
32
34
  from openstef.metrics import metrics
35
+ from openstef.settings import Settings
33
36
  from openstef.tasks.utils.predictionjobloop import PredictionJobLoop
34
37
  from openstef.tasks.utils.taskcontext import TaskContext
35
38
  from openstef.validation import validation
@@ -69,6 +72,8 @@ def check_kpi_task(
69
72
  context: TaskContext,
70
73
  start_time: datetime,
71
74
  end_time: datetime,
75
+ threshold_optimizing=THRESHOLD_OPTIMIZING,
76
+ threshold_retraining=THRESHOLD_RETRAINING,
72
77
  ) -> None:
73
78
  # Apply default parameters if none are provided
74
79
  if start_time is None:
@@ -99,28 +104,21 @@ def check_kpi_task(
99
104
 
100
105
  # Add pid to the list of pids that should be retrained or optimized if
101
106
  # performance is insufficient
102
- if kpis["47.0h"]["rMAE"] > THRESHOLD_RETRAINING:
107
+ if kpis["47.0h"]["rMAE"] > threshold_retraining:
103
108
  context.logger.warning(
104
109
  "Need to retrain model, retraining threshold rMAE 47h exceeded",
105
110
  t_ahead="47.0h",
106
111
  rMAE=kpis["47.0h"]["rMAE"],
107
- retraining_threshold=THRESHOLD_RETRAINING,
112
+ retraining_threshold=threshold_retraining,
108
113
  )
109
- function_name = "train_model"
110
-
111
- context.logger.info("Adding tracy job", function=function_name)
112
- context.database.ktp_api.add_tracy_job(pj["id"], function=function_name)
113
114
 
114
- if kpis["47.0h"]["rMAE"] > THRESHOLD_OPTIMIZING:
115
+ if kpis["47.0h"]["rMAE"] > threshold_optimizing:
115
116
  context.logger.warning(
116
117
  "Need to optimize hyperparameters, optimizing threshold rMAE 47h exceeded",
117
118
  t_ahead="47.0h",
118
119
  rMAE=kpis["47.0h"]["rMAE"],
119
- optimizing_threshold=THRESHOLD_OPTIMIZING,
120
+ optimizing_threshold=threshold_optimizing,
120
121
  )
121
- function_name = "optimize_hyperparameters"
122
- context.logger.info("Adding tracy job", function=function_name)
123
- context.database.ktp_api.add_tracy_job(pj["id"], function=function_name)
124
122
 
125
123
 
126
124
  def calc_kpi_for_specific_pid(
@@ -160,7 +158,12 @@ def calc_kpi_for_specific_pid(
160
158
  COMPLETENESS_REALISED_THRESHOLDS = 0.7
161
159
  COMPLETENESS_PREDICTED_LOAD_THRESHOLD = 0.7
162
160
 
163
- log = structlog.get_logger(__name__)
161
+ structlog.configure(
162
+ wrapper_class=structlog.make_filtering_bound_logger(
163
+ logging.getLevelName(Settings.log_level)
164
+ )
165
+ )
166
+ logger = structlog.get_logger(__name__)
164
167
 
165
168
  # If predicted is empty
166
169
  if len(predicted_load) == 0:
@@ -194,9 +197,9 @@ def calc_kpi_for_specific_pid(
194
197
 
195
198
  # Raise exception in case of constant load
196
199
  if combined.load.nunique() == 1:
197
- structlog.get_logger(__name__).warning(
200
+ logger.warning(
198
201
  "The load is constant! KPIs will still be calculated, but relative metrics"
199
- " will be nan"
202
+ " will be nan."
200
203
  )
201
204
 
202
205
  # Define output dictonary
@@ -213,7 +216,7 @@ def calc_kpi_for_specific_pid(
213
216
  date = pd.to_datetime(end_time)
214
217
 
215
218
  # Calculate model metrics and add them to the output dictionary
216
- log.info("Start calculating kpis")
219
+ logger.info("Start calculating kpis")
217
220
  for hor_cols in hor_list:
218
221
  t_ahead_h = hor_cols[0].split("_")[1]
219
222
  fc = combined[hor_cols[0]] # load predictions
@@ -272,7 +275,7 @@ def calc_kpi_for_specific_pid(
272
275
  )
273
276
 
274
277
  if completeness_realised < COMPLETENESS_REALISED_THRESHOLDS:
275
- log.warning(
278
+ logger.warning(
276
279
  "Completeness realised load too low",
277
280
  prediction_id=pid,
278
281
  start_time=start_time,
@@ -282,7 +285,7 @@ def calc_kpi_for_specific_pid(
282
285
  )
283
286
  set_incomplete_kpi_to_nan(kpis, t_ahead_h)
284
287
  if completeness_predicted_load.any() < COMPLETENESS_PREDICTED_LOAD_THRESHOLD:
285
- log.warning(
288
+ logger.warning(
286
289
  "Completeness predicted load of specific horizon too low",
287
290
  prediction_id=pid,
288
291
  horizon=t_ahead_h,
@@ -32,7 +32,10 @@ T_AHEAD_DAYS: int = 14
32
32
 
33
33
 
34
34
  def create_basecase_forecast_task(
35
- pj: PredictionJobDataClass, context: TaskContext
35
+ pj: PredictionJobDataClass,
36
+ context: TaskContext,
37
+ t_behind_days=T_BEHIND_DAYS,
38
+ t_ahead_days=T_AHEAD_DAYS,
36
39
  ) -> None:
37
40
  """Top level task that creates a basecase forecast.
38
41
 
@@ -41,6 +44,8 @@ def create_basecase_forecast_task(
41
44
  Args:
42
45
  pj: Prediction job
43
46
  context: Contect object that holds a config manager and a database connection
47
+ t_behind_days: number of days included as history. This is used to generated lagged features for the to-be-forecasted period
48
+ t_ahead_days: number of days a basecase forecast is created for
44
49
 
45
50
  """
46
51
  # Check pipeline types
@@ -63,8 +68,8 @@ def create_basecase_forecast_task(
63
68
  return
64
69
 
65
70
  # Define datetime range for input data
66
- datetime_start = datetime.utcnow() - timedelta(days=T_BEHIND_DAYS)
67
- datetime_end = datetime.utcnow() + timedelta(days=T_AHEAD_DAYS)
71
+ datetime_start = datetime.utcnow() - timedelta(days=t_behind_days)
72
+ datetime_end = datetime.utcnow() + timedelta(days=t_ahead_days)
68
73
 
69
74
  # Retrieve input data
70
75
  input_data = context.database.get_model_input(
@@ -77,11 +82,14 @@ def create_basecase_forecast_task(
77
82
  # Make basecase forecast using the corresponding pipeline
78
83
  basecase_forecast = create_basecase_forecast_pipeline(pj, input_data)
79
84
 
80
- # Do not store basecase forecasts for moments within next 48 hours.
85
+ # Do not store basecase forecasts for moments within the prediction job's horizon.
81
86
  # Those should be updated by regular forecast process.
82
87
  basecase_forecast = basecase_forecast.loc[
83
88
  basecase_forecast.index
84
- > (pd.to_datetime(datetime.utcnow(), utc=True) + timedelta(hours=48)),
89
+ > (
90
+ pd.to_datetime(datetime.utcnow(), utc=True)
91
+ + timedelta(minutes=pj.horizon_minutes)
92
+ ),
85
93
  :,
86
94
  ]
87
95
 
@@ -21,11 +21,12 @@ Example:
21
21
  $ python create_components_forecast.py
22
22
 
23
23
  """
24
+ import logging
24
25
  from datetime import datetime, timedelta, timezone
25
26
  from pathlib import Path
26
27
 
27
- import structlog
28
28
  import pandas as pd
29
+ import structlog
29
30
 
30
31
  from openstef.data_classes.prediction_job import PredictionJobDataClass
31
32
  from openstef.enums import MLModelType
@@ -33,6 +34,7 @@ from openstef.exceptions import ComponentForecastTooShortHorizonError
33
34
  from openstef.pipeline.create_component_forecast import (
34
35
  create_components_forecast_pipeline,
35
36
  )
37
+ from openstef.settings import Settings
36
38
  from openstef.tasks.utils.predictionjobloop import PredictionJobLoop
37
39
  from openstef.tasks.utils.taskcontext import TaskContext
38
40
 
@@ -41,7 +43,10 @@ T_AHEAD_DAYS = 3
41
43
 
42
44
 
43
45
  def create_components_forecast_task(
44
- pj: PredictionJobDataClass, context: TaskContext
46
+ pj: PredictionJobDataClass,
47
+ context: TaskContext,
48
+ t_behind_days: int = T_BEHIND_DAYS,
49
+ t_ahead_days: int = T_AHEAD_DAYS,
45
50
  ) -> None:
46
51
  """Top level task that creates a components forecast.
47
52
 
@@ -50,8 +55,19 @@ def create_components_forecast_task(
50
55
  Args:
51
56
  pj: Prediction job
52
57
  context: Contect object that holds a config manager and a database connection
58
+ t_behind_days: number of days in the past that the component forecast is created for
59
+ t_ahead_days: number of days in the future that the component forecast is created for
60
+
61
+ Raises:
62
+ ComponentForecastTooShortHorizonError: If the forecast horizon is too short
63
+ (less than 30 minutes in advance)
53
64
 
54
65
  """
66
+ structlog.configure(
67
+ wrapper_class=structlog.make_filtering_bound_logger(
68
+ logging.getLevelName(Settings.log_level)
69
+ )
70
+ )
55
71
  logger = structlog.get_logger(__name__)
56
72
  if pj["train_components"] == 0:
57
73
  context.logger.info(
@@ -60,8 +76,8 @@ def create_components_forecast_task(
60
76
  return
61
77
 
62
78
  # Define datetime range for input data
63
- datetime_start = datetime.utcnow() - timedelta(days=T_BEHIND_DAYS)
64
- datetime_end = datetime.utcnow() + timedelta(days=T_AHEAD_DAYS)
79
+ datetime_start = datetime.utcnow() - timedelta(days=t_behind_days)
80
+ datetime_end = datetime.utcnow() + timedelta(days=t_ahead_days)
65
81
 
66
82
  logger.info(
67
83
  "Get predicted load", datetime_start=datetime_start, datetime_end=datetime_end
@@ -34,7 +34,9 @@ from openstef.validation.validation import detect_ongoing_zero_flatliner
34
34
  T_BEHIND_DAYS: int = 14
35
35
 
36
36
 
37
- def create_forecast_task(pj: PredictionJobDataClass, context: TaskContext) -> None:
37
+ def create_forecast_task(
38
+ pj: PredictionJobDataClass, context: TaskContext, t_behind_days: int = T_BEHIND_DAYS
39
+ ) -> None:
38
40
  """Top level task that creates a forecast.
39
41
 
40
42
  On this task level all database and context manager dependencies are resolved.
@@ -45,6 +47,7 @@ def create_forecast_task(pj: PredictionJobDataClass, context: TaskContext) -> No
45
47
  Args:
46
48
  pj: Prediction job
47
49
  context: Contect object that holds a config manager and a database connection
50
+ t_behind_days: number of days included as history. This is used to generated lagged features for the to-be-forecasted period
48
51
 
49
52
  """
50
53
  # Check pipeline types
@@ -70,7 +73,7 @@ def create_forecast_task(pj: PredictionJobDataClass, context: TaskContext) -> No
70
73
  mlflow_tracking_uri = context.config.paths_mlflow_tracking_uri
71
74
 
72
75
  # Define datetime range for input data
73
- datetime_start = datetime.utcnow() - timedelta(days=T_BEHIND_DAYS)
76
+ datetime_start = datetime.utcnow() - timedelta(days=t_behind_days)
74
77
  datetime_end = datetime.utcnow() + timedelta(seconds=pj.horizon_minutes * 60)
75
78
 
76
79
  # Retrieve input data
@@ -22,6 +22,7 @@ Example:
22
22
  $ python split_forecast.py
23
23
 
24
24
  """
25
+ import logging
25
26
  from datetime import datetime
26
27
  from pathlib import Path
27
28
 
@@ -33,6 +34,7 @@ import structlog
33
34
  import openstef.monitoring.teams as monitoring
34
35
  from openstef.data_classes.prediction_job import PredictionJobDataClass
35
36
  from openstef.enums import MLModelType
37
+ from openstef.settings import Settings
36
38
  from openstef.tasks.utils.predictionjobloop import PredictionJobLoop
37
39
  from openstef.tasks.utils.taskcontext import TaskContext
38
40
 
@@ -70,6 +72,11 @@ def split_forecast_task(
70
72
  Energy splitting coefficients.
71
73
 
72
74
  """
75
+ structlog.configure(
76
+ wrapper_class=structlog.make_filtering_bound_logger(
77
+ logging.getLevelName(Settings.log_level)
78
+ )
79
+ )
73
80
  logger = structlog.get_logger(__name__)
74
81
 
75
82
  logger.info("Start splitting energy", pid=pj["id"])
@@ -23,22 +23,20 @@ from datetime import datetime, timedelta
23
23
  from pathlib import Path
24
24
 
25
25
  from openstef.data_classes.prediction_job import PredictionJobDataClass
26
-
27
26
  from openstef.enums import MLModelType, PipelineType
28
27
  from openstef.exceptions import (
29
- SkipSaveTrainingForecasts,
30
28
  InputDataOngoingZeroFlatlinerError,
29
+ SkipSaveTrainingForecasts,
31
30
  )
31
+ from openstef.model.serializer import MLflowSerializer
32
32
  from openstef.pipeline.train_model import (
33
+ MAXIMUM_MODEL_AGE,
33
34
  train_model_pipeline,
34
35
  train_pipeline_step_load_model,
35
- MAXIMUM_MODEL_AGE,
36
36
  )
37
37
  from openstef.tasks.utils.predictionjobloop import PredictionJobLoop
38
38
  from openstef.tasks.utils.taskcontext import TaskContext
39
39
 
40
- from openstef.model.serializer import MLflowSerializer
41
-
42
40
  TRAINING_PERIOD_DAYS: int = 120
43
41
  DEFAULT_CHECK_MODEL_AGE: bool = True
44
42
 
@@ -65,6 +63,10 @@ def train_model_task(
65
63
  datetime_start: Start
66
64
  datetime_end: End
67
65
 
66
+ Raises:
67
+ SkipSaveTrainingForecasts: If old model is better or too young, you don't need to save the traing forcast.
68
+ InputDataOngoingZeroFlatlinerError: If all recent load measurements are zero.
69
+
68
70
  """
69
71
  # Check pipeline types
70
72
  if PipelineType.TRAIN not in pj.pipelines_to_run:
@@ -1,6 +1,7 @@
1
1
  # SPDX-FileCopyrightText: 2017-2023 Contributors to the OpenSTEF project <korte.termijn.prognoses@alliander.com> # noqa E501>
2
2
  #
3
3
  # SPDX-License-Identifier: MPL-2.0
4
+ import logging
4
5
  import traceback
5
6
  from typing import Callable
6
7
 
@@ -9,6 +10,7 @@ import structlog
9
10
  from openstef.exceptions import PredictionJobException
10
11
  from openstef.monitoring.performance_meter import PerformanceMeter
11
12
  from openstef.monitoring.teams import post_teams
13
+ from openstef.settings import Settings
12
14
 
13
15
 
14
16
  class TaskContext:
@@ -62,6 +64,11 @@ class TaskContext:
62
64
  self.database = database
63
65
 
64
66
  def __enter__(self):
67
+ structlog.configure(
68
+ wrapper_class=structlog.make_filtering_bound_logger(
69
+ logging.getLevelName(Settings.log_level)
70
+ )
71
+ )
65
72
  self.logger = structlog.get_logger(__name__).bind(task=self.name)
66
73
 
67
74
  self.perf_meter = PerformanceMeter(self.logger)
@@ -1,17 +1,19 @@
1
1
  # SPDX-FileCopyrightText: 2017-2023 Contributors to the OpenSTEF project <korte.termijn.prognoses@alliander.com> # noqa E501>
2
2
  #
3
3
  # SPDX-License-Identifier: MPL-2.0
4
+ import logging
5
+ import math
4
6
  from datetime import datetime, timedelta
5
7
  from typing import Union
6
8
 
7
- import math
8
9
  import numpy as np
9
10
  import pandas as pd
10
11
  import structlog
11
12
 
12
13
  from openstef.exceptions import InputDataOngoingZeroFlatlinerError
13
- from openstef.preprocessing.preprocessing import replace_repeated_values_with_nan
14
14
  from openstef.model.regressors.regressor import OpenstfRegressor
15
+ from openstef.preprocessing.preprocessing import replace_repeated_values_with_nan
16
+ from openstef.settings import Settings
15
17
 
16
18
 
17
19
  def validate(
@@ -37,7 +39,15 @@ def validate(
37
39
  Returns:
38
40
  Dataframe where repeated values are set to None
39
41
 
42
+ Raises:
43
+ InputDataOngoingZeroFlatlinerError: If all recent load measurements are zero.
44
+
40
45
  """
46
+ structlog.configure(
47
+ wrapper_class=structlog.make_filtering_bound_logger(
48
+ logging.getLevelName(Settings.log_level)
49
+ )
50
+ )
41
51
  logger = structlog.get_logger(__name__)
42
52
 
43
53
  if not isinstance(data.index, pd.DatetimeIndex):
@@ -81,6 +91,11 @@ def validate(
81
91
 
82
92
 
83
93
  def drop_target_na(data: pd.DataFrame) -> pd.DataFrame:
94
+ structlog.configure(
95
+ wrapper_class=structlog.make_filtering_bound_logger(
96
+ logging.getLevelName(Settings.log_level)
97
+ )
98
+ )
84
99
  logger = structlog.get_logger(__name__)
85
100
  len_original = len(data)
86
101
  # Remove where load is NA, NaN features are preserved
@@ -119,6 +134,11 @@ def is_data_sufficient(
119
134
  else:
120
135
  weights = model.feature_importance_dataframe
121
136
 
137
+ structlog.configure(
138
+ wrapper_class=structlog.make_filtering_bound_logger(
139
+ logging.getLevelName(Settings.log_level)
140
+ )
141
+ )
122
142
  logger = structlog.get_logger(__name__)
123
143
  # Set output variable
124
144
  is_sufficient = True
@@ -251,6 +271,11 @@ def calc_completeness_dataframe(
251
271
  Dataframe with fraction of completeness per column
252
272
 
253
273
  """
274
+ structlog.configure(
275
+ wrapper_class=structlog.make_filtering_bound_logger(
276
+ logging.getLevelName(Settings.log_level)
277
+ )
278
+ )
254
279
  logger = structlog.get_logger(__name__)
255
280
 
256
281
  if homogenise and isinstance(df.index, pd.DatetimeIndex) and len(df) > 0: