Skip to content

Add troubleshooting guide and enhance error messages for ADS Forecast Operator #1248

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ads/opctl/operator/lowcode/forecast/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
5 changes: 5 additions & 0 deletions ads/opctl/operator/lowcode/forecast/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""

Expand All @@ -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."
)


Expand All @@ -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."
)
2 changes: 2 additions & 0 deletions ads/opctl/operator/lowcode/forecast/model/base_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
SpeedAccuracyMode,
SupportedMetrics,
SupportedModels,
TROUBLESHOOTING_GUIDE,
)
from ..operator_config import ForecastOperatorConfig, ForecastOperatorSpec
from .forecast_datasets import ForecastDatasets, ForecastResults
Expand Down Expand Up @@ -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(
Expand Down
10 changes: 9 additions & 1 deletion ads/opctl/operator/lowcode/forecast/model/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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."
)


Expand Down
13 changes: 11 additions & 2 deletions ads/opctl/operator/lowcode/forecast/model/forecast_datasets.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -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):
Expand All @@ -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"
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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):
Expand All @@ -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

Expand Down
7 changes: 6 additions & 1 deletion ads/opctl/operator/lowcode/forecast/model_evaluator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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 = {
Expand Down