oracle-ads 2.12.11__py3-none-any.whl → 2.13.1__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 (83) hide show
  1. ads/aqua/__init__.py +7 -1
  2. ads/aqua/app.py +41 -27
  3. ads/aqua/client/client.py +48 -11
  4. ads/aqua/common/entities.py +28 -1
  5. ads/aqua/common/enums.py +32 -21
  6. ads/aqua/common/errors.py +3 -4
  7. ads/aqua/common/utils.py +10 -15
  8. ads/aqua/config/container_config.py +203 -0
  9. ads/aqua/config/evaluation/evaluation_service_config.py +5 -181
  10. ads/aqua/constants.py +1 -1
  11. ads/aqua/evaluation/constants.py +7 -7
  12. ads/aqua/evaluation/errors.py +3 -4
  13. ads/aqua/evaluation/evaluation.py +4 -4
  14. ads/aqua/extension/base_handler.py +4 -0
  15. ads/aqua/extension/model_handler.py +41 -27
  16. ads/aqua/extension/models/ws_models.py +5 -6
  17. ads/aqua/finetuning/constants.py +3 -3
  18. ads/aqua/finetuning/finetuning.py +2 -3
  19. ads/aqua/model/constants.py +7 -7
  20. ads/aqua/model/entities.py +2 -3
  21. ads/aqua/model/enums.py +4 -5
  22. ads/aqua/model/model.py +46 -29
  23. ads/aqua/modeldeployment/deployment.py +6 -14
  24. ads/aqua/modeldeployment/entities.py +5 -3
  25. ads/aqua/server/__init__.py +4 -0
  26. ads/aqua/server/__main__.py +24 -0
  27. ads/aqua/server/app.py +47 -0
  28. ads/aqua/server/aqua_spec.yml +1291 -0
  29. ads/aqua/ui.py +5 -199
  30. ads/common/auth.py +50 -28
  31. ads/common/extended_enum.py +52 -44
  32. ads/common/utils.py +91 -11
  33. ads/config.py +3 -0
  34. ads/llm/__init__.py +12 -8
  35. ads/llm/langchain/plugins/embeddings/__init__.py +4 -0
  36. ads/llm/langchain/plugins/embeddings/oci_data_science_model_deployment_endpoint.py +184 -0
  37. ads/llm/langchain/plugins/llms/oci_data_science_model_deployment_endpoint.py +32 -23
  38. ads/model/artifact_downloader.py +6 -4
  39. ads/model/common/utils.py +15 -3
  40. ads/model/datascience_model.py +422 -71
  41. ads/model/generic_model.py +3 -3
  42. ads/model/model_metadata.py +70 -24
  43. ads/model/model_version_set.py +5 -3
  44. ads/model/service/oci_datascience_model.py +487 -17
  45. ads/opctl/anomaly_detection.py +11 -0
  46. ads/opctl/backend/marketplace/helm_helper.py +13 -14
  47. ads/opctl/cli.py +4 -5
  48. ads/opctl/cmds.py +28 -32
  49. ads/opctl/config/merger.py +8 -11
  50. ads/opctl/config/resolver.py +25 -30
  51. ads/opctl/forecast.py +11 -0
  52. ads/opctl/operator/cli.py +9 -9
  53. ads/opctl/operator/common/backend_factory.py +56 -60
  54. ads/opctl/operator/common/const.py +5 -5
  55. ads/opctl/operator/common/utils.py +16 -0
  56. ads/opctl/operator/lowcode/anomaly/const.py +8 -9
  57. ads/opctl/operator/lowcode/common/data.py +5 -2
  58. ads/opctl/operator/lowcode/common/transformations.py +2 -12
  59. ads/opctl/operator/lowcode/feature_store_marketplace/operator_utils.py +43 -48
  60. ads/opctl/operator/lowcode/forecast/__main__.py +5 -5
  61. ads/opctl/operator/lowcode/forecast/const.py +6 -6
  62. ads/opctl/operator/lowcode/forecast/model/arima.py +6 -3
  63. ads/opctl/operator/lowcode/forecast/model/automlx.py +61 -31
  64. ads/opctl/operator/lowcode/forecast/model/base_model.py +66 -40
  65. ads/opctl/operator/lowcode/forecast/model/forecast_datasets.py +79 -13
  66. ads/opctl/operator/lowcode/forecast/model/neuralprophet.py +5 -2
  67. ads/opctl/operator/lowcode/forecast/model/prophet.py +28 -15
  68. ads/opctl/operator/lowcode/forecast/model_evaluator.py +13 -15
  69. ads/opctl/operator/lowcode/forecast/schema.yaml +1 -1
  70. ads/opctl/operator/lowcode/forecast/whatifserve/deployment_manager.py +7 -0
  71. ads/opctl/operator/lowcode/forecast/whatifserve/score.py +19 -11
  72. ads/opctl/operator/lowcode/pii/constant.py +6 -7
  73. ads/opctl/operator/lowcode/recommender/constant.py +12 -7
  74. ads/opctl/operator/runtime/marketplace_runtime.py +4 -10
  75. ads/opctl/operator/runtime/runtime.py +4 -6
  76. ads/pipeline/ads_pipeline_run.py +13 -25
  77. ads/pipeline/visualizer/graph_renderer.py +3 -4
  78. {oracle_ads-2.12.11.dist-info → oracle_ads-2.13.1.dist-info}/METADATA +18 -15
  79. {oracle_ads-2.12.11.dist-info → oracle_ads-2.13.1.dist-info}/RECORD +82 -74
  80. {oracle_ads-2.12.11.dist-info → oracle_ads-2.13.1.dist-info}/WHEEL +1 -1
  81. ads/aqua/config/evaluation/evaluation_service_model_config.py +0 -8
  82. {oracle_ads-2.12.11.dist-info → oracle_ads-2.13.1.dist-info}/entry_points.txt +0 -0
  83. {oracle_ads-2.12.11.dist-info → oracle_ads-2.13.1.dist-info/licenses}/LICENSE.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env python
2
2
 
3
- # Copyright (c) 2023, 2024 Oracle and/or its affiliates.
3
+ # Copyright (c) 2023, 2025 Oracle and/or its affiliates.
4
4
  # Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/
5
5
 
6
6
  import logging
@@ -19,6 +19,7 @@ import report_creator as rc
19
19
  from ads.common.decorator.runtime_dependency import runtime_dependency
20
20
  from ads.common.object_storage_details import ObjectStorageDetails
21
21
  from ads.opctl import logger
22
+ from ads.opctl.operator.lowcode.common.const import DataColumns
22
23
  from ads.opctl.operator.lowcode.common.utils import (
23
24
  datetime_to_seconds,
24
25
  disable_print,
@@ -28,7 +29,6 @@ from ads.opctl.operator.lowcode.common.utils import (
28
29
  seconds_to_datetime,
29
30
  write_data,
30
31
  )
31
- from ads.opctl.operator.lowcode.common.const import DataColumns
32
32
  from ads.opctl.operator.lowcode.forecast.model.forecast_datasets import TestData
33
33
  from ads.opctl.operator.lowcode.forecast.utils import (
34
34
  _build_metrics_df,
@@ -49,10 +49,9 @@ from ..const import (
49
49
  SpeedAccuracyMode,
50
50
  SupportedMetrics,
51
51
  SupportedModels,
52
- BACKTEST_REPORT_NAME,
53
52
  )
54
53
  from ..operator_config import ForecastOperatorConfig, ForecastOperatorSpec
55
- from .forecast_datasets import ForecastDatasets
54
+ from .forecast_datasets import ForecastDatasets, ForecastResults
56
55
 
57
56
  logging.getLogger("report_creator").setLevel(logging.WARNING)
58
57
 
@@ -121,27 +120,30 @@ class ForecastOperatorBaseModel(ABC):
121
120
 
122
121
  # Generate metrics
123
122
  summary_metrics = None
124
- test_data = None
123
+ test_data = self.datasets.test_data
125
124
  self.eval_metrics = None
126
125
 
127
126
  if self.spec.generate_report or self.spec.generate_metrics:
128
127
  self.eval_metrics = self.generate_train_metrics()
129
128
  if not self.target_cat_col:
130
- self.eval_metrics.rename({"Series 1": self.original_target_column},
131
- axis=1, inplace=True)
129
+ self.eval_metrics.rename(
130
+ {"Series 1": self.original_target_column}, axis=1, inplace=True
131
+ )
132
132
 
133
- if self.spec.test_data:
133
+ if self.datasets.test_data is not None:
134
134
  try:
135
135
  (
136
136
  self.test_eval_metrics,
137
- summary_metrics,
138
- test_data,
137
+ summary_metrics
139
138
  ) = self._test_evaluate_metrics(
140
139
  elapsed_time=elapsed_time,
141
140
  )
142
141
  if not self.target_cat_col:
143
- self.test_eval_metrics.rename({"Series 1": self.original_target_column},
144
- axis=1, inplace=True)
142
+ self.test_eval_metrics.rename(
143
+ {"Series 1": self.original_target_column},
144
+ axis=1,
145
+ inplace=True,
146
+ )
145
147
  except Exception:
146
148
  logger.warn("Unable to generate Test Metrics.")
147
149
  logger.debug(f"Full Traceback: {traceback.format_exc()}")
@@ -223,17 +225,23 @@ class ForecastOperatorBaseModel(ABC):
223
225
  rc.Block(
224
226
  first_10_title,
225
227
  # series_subtext,
226
- rc.Select(blocks=first_5_rows_blocks) if self.target_cat_col else first_5_rows_blocks[0],
228
+ rc.Select(blocks=first_5_rows_blocks)
229
+ if self.target_cat_col
230
+ else first_5_rows_blocks[0],
227
231
  ),
228
232
  rc.Block(
229
233
  last_10_title,
230
234
  # series_subtext,
231
- rc.Select(blocks=last_5_rows_blocks) if self.target_cat_col else last_5_rows_blocks[0],
235
+ rc.Select(blocks=last_5_rows_blocks)
236
+ if self.target_cat_col
237
+ else last_5_rows_blocks[0],
232
238
  ),
233
239
  rc.Block(
234
240
  summary_title,
235
241
  # series_subtext,
236
- rc.Select(blocks=data_summary_blocks) if self.target_cat_col else data_summary_blocks[0],
242
+ rc.Select(blocks=data_summary_blocks)
243
+ if self.target_cat_col
244
+ else data_summary_blocks[0],
237
245
  ),
238
246
  rc.Separator(),
239
247
  )
@@ -308,7 +316,7 @@ class ForecastOperatorBaseModel(ABC):
308
316
  horizon=self.spec.horizon,
309
317
  test_data=test_data,
310
318
  ci_interval_width=self.spec.confidence_interval_width,
311
- target_category_column=self.target_cat_col
319
+ target_category_column=self.target_cat_col,
312
320
  )
313
321
  if (
314
322
  series_name is not None
@@ -341,17 +349,18 @@ class ForecastOperatorBaseModel(ABC):
341
349
  )
342
350
 
343
351
  # save the report and result CSV
344
- self._save_report(
352
+ return self._save_report(
345
353
  report_sections=report_sections,
346
354
  result_df=result_df,
347
355
  metrics_df=self.eval_metrics,
348
356
  test_metrics_df=self.test_eval_metrics,
357
+ test_data=test_data,
349
358
  )
350
359
 
351
360
  def _test_evaluate_metrics(self, elapsed_time=0):
352
361
  total_metrics = pd.DataFrame()
353
362
  summary_metrics = pd.DataFrame()
354
- data = TestData(self.spec)
363
+ data = self.datasets.test_data
355
364
 
356
365
  # Generate y_pred and y_true for each series
357
366
  for s_id in self.forecast_output.list_series_ids():
@@ -388,7 +397,7 @@ class ForecastOperatorBaseModel(ABC):
388
397
  total_metrics = pd.concat([total_metrics, metrics_df], axis=1)
389
398
 
390
399
  if total_metrics.empty:
391
- return total_metrics, summary_metrics, data
400
+ return total_metrics, summary_metrics
392
401
 
393
402
  summary_metrics = pd.DataFrame(
394
403
  {
@@ -454,7 +463,7 @@ class ForecastOperatorBaseModel(ABC):
454
463
  ]
455
464
  summary_metrics = summary_metrics[new_column_order]
456
465
 
457
- return total_metrics, summary_metrics, data
466
+ return total_metrics, summary_metrics
458
467
 
459
468
  def _save_report(
460
469
  self,
@@ -462,10 +471,12 @@ class ForecastOperatorBaseModel(ABC):
462
471
  result_df: pd.DataFrame,
463
472
  metrics_df: pd.DataFrame,
464
473
  test_metrics_df: pd.DataFrame,
474
+ test_data: pd.DataFrame,
465
475
  ):
466
476
  """Saves resulting reports to the given folder."""
467
477
 
468
478
  unique_output_dir = self.spec.output_directory.url
479
+ results = ForecastResults()
469
480
 
470
481
  if ObjectStorageDetails.is_oci_path(unique_output_dir):
471
482
  storage_options = default_signer()
@@ -491,13 +502,23 @@ class ForecastOperatorBaseModel(ABC):
491
502
  f2.write(f1.read())
492
503
 
493
504
  # forecast csv report
494
- result_df = result_df if self.target_cat_col else result_df.drop(DataColumns.Series, axis=1)
505
+ # todo: add test data into forecast.csv
506
+ # if self.spec.test_data is not None:
507
+ # test_data_dict = test_data.get_dict_by_series()
508
+ # for series_id, test_data_values in test_data_dict.items():
509
+ # result_df[DataColumns.Series] = test_data_values[]
510
+ result_df = (
511
+ result_df
512
+ if self.target_cat_col
513
+ else result_df.drop(DataColumns.Series, axis=1)
514
+ )
495
515
  write_data(
496
516
  data=result_df,
497
517
  filename=os.path.join(unique_output_dir, self.spec.forecast_filename),
498
518
  format="csv",
499
519
  storage_options=storage_options,
500
520
  )
521
+ results.set_forecast(result_df)
501
522
 
502
523
  # metrics csv report
503
524
  if self.spec.generate_metrics:
@@ -507,10 +528,11 @@ class ForecastOperatorBaseModel(ABC):
507
528
  else "Series 1"
508
529
  )
509
530
  if metrics_df is not None:
531
+ metrics_df_formatted = metrics_df.reset_index().rename(
532
+ {"index": "metrics", "Series 1": metrics_col_name}, axis=1
533
+ )
510
534
  write_data(
511
- data=metrics_df.reset_index().rename(
512
- {"index": "metrics", "Series 1": metrics_col_name}, axis=1
513
- ),
535
+ data=metrics_df_formatted,
514
536
  filename=os.path.join(
515
537
  unique_output_dir, self.spec.metrics_filename
516
538
  ),
@@ -518,18 +540,20 @@ class ForecastOperatorBaseModel(ABC):
518
540
  storage_options=storage_options,
519
541
  index=False,
520
542
  )
543
+ results.set_metrics(metrics_df_formatted)
521
544
  else:
522
545
  logger.warn(
523
546
  f"Attempted to generate the {self.spec.metrics_filename} file with the training metrics, however the training metrics could not be properly generated."
524
547
  )
525
548
 
526
549
  # test_metrics csv report
527
- if self.spec.test_data is not None:
550
+ if self.datasets.test_data is not None:
528
551
  if test_metrics_df is not None:
552
+ test_metrics_df_formatted = test_metrics_df.reset_index().rename(
553
+ {"index": "metrics", "Series 1": metrics_col_name}, axis=1
554
+ )
529
555
  write_data(
530
- data=test_metrics_df.reset_index().rename(
531
- {"index": "metrics", "Series 1": metrics_col_name}, axis=1
532
- ),
556
+ data=test_metrics_df_formatted,
533
557
  filename=os.path.join(
534
558
  unique_output_dir, self.spec.test_metrics_filename
535
559
  ),
@@ -537,6 +561,7 @@ class ForecastOperatorBaseModel(ABC):
537
561
  storage_options=storage_options,
538
562
  index=False,
539
563
  )
564
+ results.set_test_metrics(test_metrics_df_formatted)
540
565
  else:
541
566
  logger.warn(
542
567
  f"Attempted to generate the {self.spec.test_metrics_filename} file with the test metrics, however the test metrics could not be properly generated."
@@ -544,7 +569,7 @@ class ForecastOperatorBaseModel(ABC):
544
569
  # explanations csv reports
545
570
  if self.spec.generate_explanations:
546
571
  try:
547
- if self.formatted_global_explanation is not None:
572
+ if not self.formatted_global_explanation.empty:
548
573
  write_data(
549
574
  data=self.formatted_global_explanation,
550
575
  filename=os.path.join(
@@ -554,12 +579,13 @@ class ForecastOperatorBaseModel(ABC):
554
579
  storage_options=storage_options,
555
580
  index=True,
556
581
  )
582
+ results.set_global_explanations(self.formatted_global_explanation)
557
583
  else:
558
584
  logger.warn(
559
585
  f"Attempted to generate global explanations for the {self.spec.global_explanation_filename} file, but an issue occured in formatting the explanations."
560
586
  )
561
587
 
562
- if self.formatted_local_explanation is not None:
588
+ if not self.formatted_local_explanation.empty:
563
589
  write_data(
564
590
  data=self.formatted_local_explanation,
565
591
  filename=os.path.join(
@@ -569,6 +595,7 @@ class ForecastOperatorBaseModel(ABC):
569
595
  storage_options=storage_options,
570
596
  index=True,
571
597
  )
598
+ results.set_local_explanations(self.formatted_local_explanation)
572
599
  else:
573
600
  logger.warn(
574
601
  f"Attempted to generate local explanations for the {self.spec.local_explanation_filename} file, but an issue occured in formatting the explanations."
@@ -589,10 +616,12 @@ class ForecastOperatorBaseModel(ABC):
589
616
  index=True,
590
617
  indent=4,
591
618
  )
619
+ results.set_model_parameters(self.model_parameters)
592
620
 
593
621
  # model pickle
594
622
  if self.spec.generate_model_pickle:
595
623
  self._save_model(unique_output_dir, storage_options)
624
+ results.set_models(self.models)
596
625
 
597
626
  logger.info(
598
627
  f"The outputs have been successfully "
@@ -612,8 +641,10 @@ class ForecastOperatorBaseModel(ABC):
612
641
  index=True,
613
642
  indent=4,
614
643
  )
644
+ results.set_errors_dict(self.errors_dict)
615
645
  else:
616
646
  logger.info("All modeling completed successfully.")
647
+ return results
617
648
 
618
649
  def preprocess(self, df, series_id):
619
650
  """The method that needs to be implemented on the particular model level."""
@@ -667,7 +698,10 @@ class ForecastOperatorBaseModel(ABC):
667
698
  )
668
699
 
669
700
  def _validate_automlx_explanation_mode(self):
670
- if self.spec.model != SupportedModels.AutoMLX and self.spec.explanations_accuracy_mode == SpeedAccuracyMode.AUTOMLX:
701
+ if (
702
+ self.spec.model != SupportedModels.AutoMLX
703
+ and self.spec.explanations_accuracy_mode == SpeedAccuracyMode.AUTOMLX
704
+ ):
671
705
  raise ValueError(
672
706
  "AUTOMLX explanation accuracy mode is only supported for AutoMLX models. "
673
707
  "Please select mode other than AUTOMLX from the available explanations_accuracy_mode options"
@@ -738,14 +772,6 @@ class ForecastOperatorBaseModel(ABC):
738
772
  logger.warn(
739
773
  "No explanations generated. Ensure that additional data has been provided."
740
774
  )
741
- elif (
742
- self.spec.model == SupportedModels.AutoMLX
743
- and self.spec.explanations_accuracy_mode
744
- == SpeedAccuracyMode.AUTOMLX
745
- ):
746
- logger.warning(
747
- "Global explanations not available for AutoMLX models with inherent explainability"
748
- )
749
775
  else:
750
776
  self.global_explanation[s_id] = dict(
751
777
  zip(
@@ -794,7 +820,7 @@ class ForecastOperatorBaseModel(ABC):
794
820
  def get_explain_predict_fn(self, series_id, fcst_col_name="yhat"):
795
821
  def _custom_predict(
796
822
  data,
797
- model=self.models[series_id],
823
+ model=self.models[series_id]["model"],
798
824
  dt_column_name=self.datasets._datetime_column_name,
799
825
  ):
800
826
  """
@@ -1,8 +1,10 @@
1
1
  #!/usr/bin/env python
2
2
 
3
- # Copyright (c) 2023, 2024 Oracle and/or its affiliates.
3
+ # Copyright (c) 2023, 2025 Oracle and/or its affiliates.
4
4
  # Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/
5
5
 
6
+ from typing import Dict, List
7
+
6
8
  import pandas as pd
7
9
 
8
10
  from ads.opctl import logger
@@ -21,8 +23,8 @@ from ..operator_config import ForecastOperatorConfig
21
23
 
22
24
 
23
25
  class HistoricalData(AbstractData):
24
- def __init__(self, spec: dict):
25
- super().__init__(spec=spec, name="historical_data")
26
+ def __init__(self, spec, historical_data = None):
27
+ super().__init__(spec=spec, name="historical_data", data=historical_data)
26
28
 
27
29
  def _ingest_data(self, spec):
28
30
  try:
@@ -50,8 +52,11 @@ class HistoricalData(AbstractData):
50
52
 
51
53
 
52
54
  class AdditionalData(AbstractData):
53
- def __init__(self, spec, historical_data):
54
- if spec.additional_data is not None:
55
+ def __init__(self, spec, historical_data, additional_data=None):
56
+ if additional_data is not None:
57
+ super().__init__(spec=spec, name="additional_data", data=additional_data)
58
+ self.additional_regressors = list(self.data.columns)
59
+ elif spec.additional_data is not None:
55
60
  super().__init__(spec=spec, name="additional_data")
56
61
  add_dates = self.data.index.get_level_values(0).unique().tolist()
57
62
  add_dates.sort()
@@ -108,14 +113,15 @@ class AdditionalData(AbstractData):
108
113
 
109
114
 
110
115
  class TestData(AbstractData):
111
- def __init__(self, spec):
112
- super().__init__(spec=spec, name="test_data")
116
+ def __init__(self, spec, test_data):
117
+ if test_data is not None or spec.test_data is not None:
118
+ super().__init__(spec=spec, name="test_data", data=test_data)
113
119
  self.dt_column_name = spec.datetime_column.name
114
120
  self.target_name = spec.target_column
115
121
 
116
122
 
117
123
  class ForecastDatasets:
118
- def __init__(self, config: ForecastOperatorConfig):
124
+ def __init__(self, config: ForecastOperatorConfig, historical_data=None, additional_data=None, test_data=None):
119
125
  """Instantiates the DataIO instance.
120
126
 
121
127
  Properties
@@ -125,11 +131,15 @@ class ForecastDatasets:
125
131
  """
126
132
  self.historical_data: HistoricalData = None
127
133
  self.additional_data: AdditionalData = None
128
-
129
134
  self._horizon = config.spec.horizon
130
135
  self._datetime_column_name = config.spec.datetime_column.name
131
136
  self._target_col = config.spec.target_column
132
- self._load_data(config.spec)
137
+ if historical_data is not None:
138
+ self.historical_data = HistoricalData(config.spec, historical_data)
139
+ self.additional_data = AdditionalData(config.spec, self.historical_data, additional_data)
140
+ else:
141
+ self._load_data(config.spec)
142
+ self.test_data = TestData(config.spec, test_data)
133
143
 
134
144
  def _load_data(self, spec):
135
145
  """Loads forecasting input data."""
@@ -167,7 +177,7 @@ class ForecastDatasets:
167
177
  self.historical_data.data,
168
178
  self.additional_data.data,
169
179
  ],
170
- axis=1
180
+ axis=1,
171
181
  )
172
182
 
173
183
  def get_data_by_series(self, include_horizon=True):
@@ -198,7 +208,7 @@ class ForecastDatasets:
198
208
  return self.get_data_at_series(s_id)[-self._horizon :]
199
209
 
200
210
  def has_artificial_series(self):
201
- return self.historical_data._data_transformer.has_artificial_series
211
+ return bool(self.historical_data.spec.target_category_columns)
202
212
 
203
213
  def get_earliest_timestamp(self):
204
214
  return self.historical_data.get_min_time()
@@ -249,7 +259,7 @@ class ForecastOutput:
249
259
  target_column: str,
250
260
  dt_column: str,
251
261
  ):
252
- """Forecast Output contains all of the details required to generate the forecast.csv output file.
262
+ """Forecast Output contains all the details required to generate the forecast.csv output file.
253
263
 
254
264
  init
255
265
  -------
@@ -416,3 +426,59 @@ class ForecastOutput:
416
426
  for df in self.series_id_map.values():
417
427
  output = pd.concat([output, df])
418
428
  return output.reset_index(drop=True)
429
+
430
+
431
+ class ForecastResults:
432
+ """
433
+ Forecast Results contains all outputs from the forecast run.
434
+ This class is returned to users who use the Forecast's `operate` method.
435
+
436
+ """
437
+
438
+ def set_forecast(self, df: pd.DataFrame):
439
+ self.forecast = df
440
+
441
+ def get_forecast(self):
442
+ return getattr(self, "forecast", None)
443
+
444
+ def set_metrics(self, df: pd.DataFrame):
445
+ self.metrics = df
446
+
447
+ def get_metrics(self):
448
+ return getattr(self, "metrics", None)
449
+
450
+ def set_test_metrics(self, df: pd.DataFrame):
451
+ self.test_metrics = df
452
+
453
+ def get_test_metrics(self):
454
+ return getattr(self, "test_metrics", None)
455
+
456
+ def set_local_explanations(self, df: pd.DataFrame):
457
+ self.local_explanations = df
458
+
459
+ def get_local_explanations(self):
460
+ return getattr(self, "local_explanations", None)
461
+
462
+ def set_global_explanations(self, df: pd.DataFrame):
463
+ self.global_explanations = df
464
+
465
+ def get_global_explanations(self):
466
+ return getattr(self, "global_explanations", None)
467
+
468
+ def set_model_parameters(self, df: pd.DataFrame):
469
+ self.model_parameters = df
470
+
471
+ def get_model_parameters(self):
472
+ return getattr(self, "model_parameters", None)
473
+
474
+ def set_models(self, models: List):
475
+ self.models = models
476
+
477
+ def get_models(self):
478
+ return getattr(self, "models", None)
479
+
480
+ def set_errors_dict(self, errors_dict: Dict):
481
+ self.errors_dict = errors_dict
482
+
483
+ def get_errors_dict(self):
484
+ return getattr(self, "errors_dict", None)
@@ -172,8 +172,10 @@ class NeuralProphetOperatorModel(ForecastOperatorBaseModel):
172
172
  ).values,
173
173
  )
174
174
 
175
- self.models[s_id] = model
176
175
  self.trainers[s_id] = model.trainer
176
+ self.models[s_id] = {}
177
+ self.models[s_id]["model"] = model
178
+ self.models[s_id]["le"] = self.le[s_id]
177
179
 
178
180
  self.model_parameters[s_id] = {
179
181
  "framework": SupportedModels.NeuralProphet,
@@ -355,7 +357,8 @@ class NeuralProphetOperatorModel(ForecastOperatorBaseModel):
355
357
 
356
358
  sec5_text = rc.Heading("Neural Prophet Model Parameters", level=2)
357
359
  model_states = []
358
- for s_id, m in self.models.items():
360
+ for s_id, artifacts in self.models.items():
361
+ m = artifacts["model"]
359
362
  model_states.append(
360
363
  pd.Series(
361
364
  m.state_dict(),
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env python
2
2
 
3
- # Copyright (c) 2024 Oracle and/or its affiliates.
3
+ # Copyright (c) 2024, 2025 Oracle and/or its affiliates.
4
4
  # Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/
5
5
 
6
6
  import logging
@@ -43,7 +43,11 @@ def _add_unit(num, unit):
43
43
  def _fit_model(data, params, additional_regressors):
44
44
  from prophet import Prophet
45
45
 
46
+ monthly_seasonality = params.pop("monthly_seasonality", False)
46
47
  model = Prophet(**params)
48
+ if monthly_seasonality:
49
+ model.add_seasonality(name="monthly", period=30.5, fourier_order=5)
50
+ params["monthly_seasonality"] = monthly_seasonality
47
51
  for add_reg in additional_regressors:
48
52
  model.add_regressor(add_reg)
49
53
  model.fit(data)
@@ -108,7 +112,10 @@ class ProphetOperatorModel(ForecastOperatorBaseModel):
108
112
  upper_bound=self.get_horizon(forecast["yhat_upper"]).values,
109
113
  lower_bound=self.get_horizon(forecast["yhat_lower"]).values,
110
114
  )
111
- self.models[series_id] = model
115
+
116
+ self.models[series_id] = {}
117
+ self.models[series_id]["model"] = model
118
+ self.models[series_id]["le"] = self.le[series_id]
112
119
 
113
120
  params = vars(model).copy()
114
121
  for param in ["history", "history_dates", "stan_fit"]:
@@ -252,11 +259,11 @@ class ProphetOperatorModel(ForecastOperatorBaseModel):
252
259
  all_sections = []
253
260
  if len(series_ids) > 0:
254
261
  sec1 = _select_plot_list(
255
- lambda s_id: self.models[s_id].plot(
262
+ lambda s_id: self.models[s_id]["model"].plot(
256
263
  self.outputs[s_id], include_legend=True
257
264
  ),
258
265
  series_ids=series_ids,
259
- target_category_column=self.target_cat_col
266
+ target_category_column=self.target_cat_col,
260
267
  )
261
268
  section_1 = rc.Block(
262
269
  rc.Heading("Forecast Overview", level=2),
@@ -267,25 +274,25 @@ class ProphetOperatorModel(ForecastOperatorBaseModel):
267
274
  )
268
275
 
269
276
  sec2 = _select_plot_list(
270
- lambda s_id: self.models[s_id].plot_components(self.outputs[s_id]),
277
+ lambda s_id: self.models[s_id]["model"].plot_components(self.outputs[s_id]),
271
278
  series_ids=series_ids,
272
- target_category_column=self.target_cat_col
279
+ target_category_column=self.target_cat_col,
273
280
  )
274
281
  section_2 = rc.Block(
275
282
  rc.Heading("Forecast Broken Down by Trend Component", level=2), sec2
276
283
  )
277
284
 
278
285
  sec3_figs = {
279
- s_id: self.models[s_id].plot(self.outputs[s_id]) for s_id in series_ids
286
+ s_id: self.models[s_id]["model"].plot(self.outputs[s_id]) for s_id in series_ids
280
287
  }
281
288
  for s_id in series_ids:
282
289
  add_changepoints_to_plot(
283
- sec3_figs[s_id].gca(), self.models[s_id], self.outputs[s_id]
290
+ sec3_figs[s_id].gca(), self.models[s_id]["model"], self.outputs[s_id]
284
291
  )
285
292
  sec3 = _select_plot_list(
286
293
  lambda s_id: sec3_figs[s_id],
287
294
  series_ids=series_ids,
288
- target_category_column=self.target_cat_col
295
+ target_category_column=self.target_cat_col,
289
296
  )
290
297
  section_3 = rc.Block(rc.Heading("Forecast Changepoints", level=2), sec3)
291
298
 
@@ -294,12 +301,14 @@ class ProphetOperatorModel(ForecastOperatorBaseModel):
294
301
  sec5_text = rc.Heading("Prophet Model Seasonality Components", level=2)
295
302
  model_states = []
296
303
  for s_id in series_ids:
297
- m = self.models[s_id]
304
+ m = self.models[s_id]["model"]
298
305
  model_states.append(
299
306
  pd.Series(
300
307
  m.seasonalities,
301
308
  index=pd.Index(m.seasonalities.keys(), dtype="object"),
302
- name=s_id if self.target_cat_col else self.original_target_column,
309
+ name=s_id
310
+ if self.target_cat_col
311
+ else self.original_target_column,
303
312
  dtype="object",
304
313
  )
305
314
  )
@@ -330,11 +339,15 @@ class ProphetOperatorModel(ForecastOperatorBaseModel):
330
339
  self.formatted_local_explanation = aggregate_local_explanations
331
340
 
332
341
  if not self.target_cat_col:
333
- self.formatted_global_explanation = self.formatted_global_explanation.rename(
334
- {"Series 1": self.original_target_column},
335
- axis=1,
342
+ self.formatted_global_explanation = (
343
+ self.formatted_global_explanation.rename(
344
+ {"Series 1": self.original_target_column},
345
+ axis=1,
346
+ )
347
+ )
348
+ self.formatted_local_explanation.drop(
349
+ "Series", axis=1, inplace=True
336
350
  )
337
- self.formatted_local_explanation.drop("Series", axis=1, inplace=True)
338
351
 
339
352
  # Create a markdown section for the global explainability
340
353
  global_explanation_section = rc.Block(
@@ -91,19 +91,13 @@ class ModelEvaluator:
91
91
  output_dir = operator_config.spec.output_directory.url
92
92
  output_file_path = f'{output_dir}/back_testing/{model}/{backtest}'
93
93
  Path(output_file_path).mkdir(parents=True, exist_ok=True)
94
- historical_data_url = f'{output_file_path}/historical.csv'
95
- additional_data_url = f'{output_file_path}/additional.csv'
96
- test_data_url = f'{output_file_path}/test.csv'
97
- historical_data.to_csv(historical_data_url, index=False)
98
- additional_data.to_csv(additional_data_url, index=False)
99
- test_data.to_csv(test_data_url, index=False)
100
94
  backtest_op_config_draft = operator_config.to_dict()
101
95
  backtest_spec = backtest_op_config_draft["spec"]
102
- backtest_spec["historical_data"]["url"] = historical_data_url
103
- if backtest_spec["additional_data"]:
104
- backtest_spec["additional_data"]["url"] = additional_data_url
105
- backtest_spec["test_data"] = {}
106
- backtest_spec["test_data"]["url"] = test_data_url
96
+ backtest_spec["datetime_column"]["format"] = None
97
+ backtest_spec.pop("test_data")
98
+ backtest_spec.pop("additional_data")
99
+ backtest_spec.pop("historical_data")
100
+ backtest_spec["generate_report"] = False
107
101
  backtest_spec["model"] = model
108
102
  backtest_spec['model_kwargs'] = None
109
103
  backtest_spec["output_directory"] = {"url": output_file_path}
@@ -118,19 +112,23 @@ class ModelEvaluator:
118
112
  def run_all_models(self, datasets: ForecastDatasets, operator_config: ForecastOperatorConfig):
119
113
  cut_offs, train_sets, additional_data, test_sets = self.generate_k_fold_data(datasets, operator_config)
120
114
  metrics = {}
115
+ date_col = operator_config.spec.datetime_column.name
121
116
  for model in self.models:
122
117
  from .model.factory import ForecastOperatorModelFactory
123
118
  metrics[model] = {}
124
119
  for i in range(len(cut_offs)):
125
120
  try:
126
- backtest_historical_data = train_sets[i]
127
- backtest_additional_data = additional_data[i]
128
- backtest_test_data = test_sets[i]
121
+ backtest_historical_data = train_sets[i].set_index([date_col, DataColumns.Series])
122
+ backtest_additional_data = additional_data[i].set_index([date_col, DataColumns.Series])
123
+ backtest_test_data = test_sets[i].set_index([date_col, DataColumns.Series])
129
124
  backtest_operator_config = self.create_operator_config(operator_config, i, model,
130
125
  backtest_historical_data,
131
126
  backtest_additional_data,
132
127
  backtest_test_data)
133
- datasets = ForecastDatasets(backtest_operator_config)
128
+ datasets = ForecastDatasets(backtest_operator_config,
129
+ backtest_historical_data,
130
+ backtest_additional_data,
131
+ backtest_test_data)
134
132
  ForecastOperatorModelFactory.get_model(
135
133
  backtest_operator_config, datasets
136
134
  ).generate_report()
@@ -379,7 +379,7 @@ spec:
379
379
  required: true
380
380
  log_id:
381
381
  type: string
382
- required: true
382
+ required: false
383
383
  auto_scaling:
384
384
  type: dict
385
385
  required: false