diff --git a/ads/opctl/operator/lowcode/forecast/const.py b/ads/opctl/operator/lowcode/forecast/const.py index f238b3409..f2265418a 100644 --- a/ads/opctl/operator/lowcode/forecast/const.py +++ b/ads/opctl/operator/lowcode/forecast/const.py @@ -91,3 +91,4 @@ class ForecastOutputColumns(ExtendedEnum): AUTO_SELECT = "auto-select" AUTO_SELECT_SERIES = "auto-select-series" BACKTEST_REPORT_NAME = "back_test.csv" +TROUBLESHOOTING_GUIDE = "https://github.com/oracle-samples/oci-data-science-ai-samples/blob/main/ai-operators/troubleshooting.md" diff --git a/ads/opctl/operator/lowcode/forecast/errors.py b/ads/opctl/operator/lowcode/forecast/errors.py index c71580864..87afc2d8e 100644 --- a/ads/opctl/operator/lowcode/forecast/errors.py +++ b/ads/opctl/operator/lowcode/forecast/errors.py @@ -4,6 +4,9 @@ # Copyright (c) 2023 Oracle and/or its affiliates. # Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/ +from ads.opctl.operator.lowcode.forecast.const import TROUBLESHOOTING_GUIDE + + class ForecastSchemaYamlError(Exception): """Exception raised when there is an issue with the schema.""" @@ -12,6 +15,7 @@ def __init__(self, error: str): "Invalid forecast operator specification. Check the YAML structure and ensure it " "complies with the required schema for forecast operator. \n" f"{error}" + f"\nPlease refer to the troubleshooting guide at {TROUBLESHOOTING_GUIDE} for resolution steps." ) @@ -23,4 +27,5 @@ def __init__(self, error: str): "Invalid input data. Check the input data and ensure it " "complies with the validation criteria. \n" f"{error}" + f"\nPlease refer to the troubleshooting guide at {TROUBLESHOOTING_GUIDE} for resolution steps." ) diff --git a/ads/opctl/operator/lowcode/forecast/model/base_model.py b/ads/opctl/operator/lowcode/forecast/model/base_model.py index a57299e1a..db2c73507 100644 --- a/ads/opctl/operator/lowcode/forecast/model/base_model.py +++ b/ads/opctl/operator/lowcode/forecast/model/base_model.py @@ -51,6 +51,7 @@ SpeedAccuracyMode, SupportedMetrics, SupportedModels, + TROUBLESHOOTING_GUIDE, ) from ..operator_config import ForecastOperatorConfig, ForecastOperatorSpec from .forecast_datasets import ForecastDatasets, ForecastResults @@ -743,6 +744,7 @@ def _validate_automlx_explanation_mode(self): raise ValueError( "AUTOMLX explanation accuracy mode is only supported for AutoMLX models. " "Please select mode other than AUTOMLX from the available explanations_accuracy_mode options" + f"\nPlease refer to the troubleshooting guide at {TROUBLESHOOTING_GUIDE} for resolution steps." ) @runtime_dependency( diff --git a/ads/opctl/operator/lowcode/forecast/model/factory.py b/ads/opctl/operator/lowcode/forecast/model/factory.py index 566244405..262fe5bbc 100644 --- a/ads/opctl/operator/lowcode/forecast/model/factory.py +++ b/ads/opctl/operator/lowcode/forecast/model/factory.py @@ -4,7 +4,14 @@ # Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/ from ads.opctl.operator.lowcode.common.transformations import Transformations -from ..const import AUTO_SELECT, SpeedAccuracyMode, SupportedModels, AUTO_SELECT_SERIES + +from ..const import ( + AUTO_SELECT, + AUTO_SELECT_SERIES, + TROUBLESHOOTING_GUIDE, + SpeedAccuracyMode, + SupportedModels, +) from ..meta_selector import MetaSelector from ..model_evaluator import ModelEvaluator from ..operator_config import ForecastOperatorConfig @@ -23,6 +30,7 @@ def __init__(self, model_type: str): super().__init__( f"Model: `{model_type}` " f"is not supported. Supported models: {SupportedModels.values()}" + f"\nPlease refer to the troubleshooting guide at {TROUBLESHOOTING_GUIDE} for resolution steps." ) diff --git a/ads/opctl/operator/lowcode/forecast/model/forecast_datasets.py b/ads/opctl/operator/lowcode/forecast/model/forecast_datasets.py index 26ddcb225..486c30467 100644 --- a/ads/opctl/operator/lowcode/forecast/model/forecast_datasets.py +++ b/ads/opctl/operator/lowcode/forecast/model/forecast_datasets.py @@ -18,7 +18,7 @@ get_frequency_of_datetime, ) -from ..const import ForecastOutputColumns, SupportedModels +from ..const import ForecastOutputColumns, SupportedModels, TROUBLESHOOTING_GUIDE from ..operator_config import ForecastOperatorConfig @@ -49,7 +49,8 @@ def _verify_dt_col(self, spec): f"{SupportedModels.AutoMLX} requires data with a frequency of at least one hour. Please try using a different model," " or select the 'auto' option." ) - raise InvalidParameterError(message) + raise InvalidParameterError(f"{message}" + f"\nPlease refer to the troubleshooting guide at {TROUBLESHOOTING_GUIDE} for resolution steps.") class AdditionalData(AbstractData): @@ -65,10 +66,12 @@ def __init__(self, spec, historical_data, additional_data=None, subset=None): if historical_data.get_max_time() > add_dates[-spec.horizon]: raise DataMismatchError( f"The Historical Data ends on {historical_data.get_max_time()}. The additional data horizon starts on {add_dates[-spec.horizon]}. The horizon should have exactly {spec.horizon} dates after the Historical at a frequency of {historical_data.freq}" + f"\nPlease refer to the troubleshooting guide at {TROUBLESHOOTING_GUIDE} for resolution steps." ) elif historical_data.get_max_time() != add_dates[-(spec.horizon + 1)]: raise DataMismatchError( f"The Additional Data must be present for all historical data and the entire horizon. The Historical Data ends on {historical_data.get_max_time()}. The additonal data horizon starts after {add_dates[-(spec.horizon+1)]}. These should be the same date." + f"\nPlease refer to the troubleshooting guide at {TROUBLESHOOTING_GUIDE} for resolution steps." ) else: self.name = "additional_data" @@ -215,6 +218,7 @@ def get_data_at_series(self, s_id, include_horizon=True): except Exception as e: raise InvalidParameterError( f"Unable to retrieve series id: {s_id} from data. Available series ids are: {self.list_series_ids()}" + f"\nPlease refer to the troubleshooting guide at {TROUBLESHOOTING_GUIDE} for resolution steps." ) from e def get_horizon_at_series(self, s_id): @@ -296,6 +300,7 @@ def add_series_id( if not overwrite and series_id in self.series_id_map: raise ValueError( f"Attempting to update ForecastOutput for series_id {series_id} when this already exists. Set overwrite to True." + f"\nPlease refer to the troubleshooting guide at {TROUBLESHOOTING_GUIDE} for resolution steps." ) forecast = self._check_forecast_format(forecast) self.series_id_map[series_id] = forecast @@ -336,6 +341,7 @@ def populate_series_output( except KeyError as e: raise ValueError( f"Attempting to update output for series: {series_id}, however no series output has been initialized." + f"\nPlease refer to the troubleshooting guide at {TROUBLESHOOTING_GUIDE} for resolution steps." ) from e if (output_i.shape[0] - self.horizon) == len(fit_val): @@ -356,18 +362,21 @@ def populate_series_output( if len(forecast_val) != self.horizon: raise ValueError( f"Attempting to set forecast along horizon ({self.horizon}) for series: {series_id}, however forecast is only length {len(forecast_val)}" + f"\nPlease refer to the troubleshooting guide at {TROUBLESHOOTING_GUIDE} for resolution steps." ) output_i["forecast_value"].iloc[-self.horizon :] = forecast_val if len(upper_bound) != self.horizon: raise ValueError( f"Attempting to set upper_bound along horizon ({self.horizon}) for series: {series_id}, however upper_bound is only length {len(upper_bound)}" + f"\nPlease refer to the troubleshooting guide at {TROUBLESHOOTING_GUIDE} for resolution steps." ) output_i[self.upper_bound_name].iloc[-self.horizon :] = upper_bound if len(lower_bound) != self.horizon: raise ValueError( f"Attempting to set lower_bound along horizon ({self.horizon}) for series: {series_id}, however lower_bound is only length {len(lower_bound)}" + f"\nPlease refer to the troubleshooting guide at {TROUBLESHOOTING_GUIDE} for resolution steps." ) output_i[self.lower_bound_name].iloc[-self.horizon :] = lower_bound diff --git a/ads/opctl/operator/lowcode/forecast/model_evaluator.py b/ads/opctl/operator/lowcode/forecast/model_evaluator.py index ad11f19bd..d5986703e 100644 --- a/ads/opctl/operator/lowcode/forecast/model_evaluator.py +++ b/ads/opctl/operator/lowcode/forecast/model_evaluator.py @@ -10,7 +10,10 @@ from ads.opctl import logger from ads.opctl.operator.lowcode.common.const import DataColumns from ads.opctl.operator.lowcode.common.errors import InsufficientDataError -from ads.opctl.operator.lowcode.forecast.const import BACKTEST_REPORT_NAME +from ads.opctl.operator.lowcode.forecast.const import ( + BACKTEST_REPORT_NAME, + TROUBLESHOOTING_GUIDE, +) from ads.opctl.operator.lowcode.forecast.model.factory import SupportedModels from .model.forecast_datasets import ForecastDatasets @@ -79,6 +82,7 @@ def generate_k_fold_data( raise InsufficientDataError( "Insufficient data to evaluate multiple models. Please specify a model " "instead of using auto-select." + f"\nPlease refer to the troubleshooting guide at {TROUBLESHOOTING_GUIDE} for resolution steps." ) training_datasets = [ sampled_historical_data[sampled_historical_data[date_col] <= cut_off_date] @@ -223,6 +227,7 @@ def find_best_model( model = SupportedModels.Prophet logger.error( f"Running {model} model as auto-select failed with the following error: {e.message}" + f"\nPlease refer to the troubleshooting guide at {TROUBLESHOOTING_GUIDE} for resolution steps." ) return model nonempty_metrics = {