Handling errors
Every operation can fail in a few specific, named ways. Each error has a _tag, so you can handle the ones you care about and let the rest through.
import { Effect } from "effect";
import { fit, prophetFittingBackendLayer } from "effect-prophet";
// A typo: the timestamp is not a full UTC ISO string.
const history = [
{ timestamp: "2025-01-01", value: 10 },
{ timestamp: "2025-01-02T00:00:00.000Z", value: 12 },
];
const message = await Effect.runPromise(
fit(history).pipe(
Effect.as("fitted"),
// Every failure has a `_tag`, so you can handle each kind separately.
Effect.catchTags({
ObservationError: (error) =>
Effect.succeed(`Bad row ${error.rowIndex} (${error.reason}, ${error.field})`),
OptionsError: (error) => Effect.succeed(`Bad option (${error.reason}): ${error.message}`),
FittingError: (error) => Effect.succeed(`Could not fit (${error.reason}): ${error.message}`),
}),
Effect.provide(prophetFittingBackendLayer),
),
);
console.log(message);Bad row 0 (invalid-shape, timestamp)The errors you may see
| Error | Meaning | Typical fix |
|---|---|---|
| Input errors | The data or options were rejected before any fitting: a bad timestamp, a missing field, an unknown option, or an option past its limit such as mcmc.chains. Each kind of input has its own error; see below. | reason says what's wrong, and the location fields and issues point to the exact row or option |
FittingError | The model couldn't be fitted to this data. reason says why, e.g. insufficient-observations, memory-limit or sampling-limit. When a regressor's own model failed, regressor names it. | Check for too few rows or all-identical timestamps; see limits |
PredictionError | A forecast couldn't be computed for a row. reason says why, e.g. simulation-limit, or non-finite-forecast when a residual overflows. When a regressor's own model failed, regressor names it. | Check that future rows have every field the model needs |
CountryHolidayError | A country calendar couldn't be used. reason says why, e.g. known-coverage-restriction. | See calendar errors |
ModelSerializationError | Saved model data couldn't be read | Make sure it was saved by the same version |
EvaluationMetricError | A score couldn't be computed. reason says why, e.g. zero-actual when mape meets an actual near zero, zero-denominator when a custom ratio divides by zero, or non-finite when a custom score returns something other than a finite number. | See actual values of zero and your own metrics |
MetricScaleError | A scaled error like mase couldn't get a scale for one round. reason is insufficient-history when the round's training rows have no pairs for the lag, zero-scale when its training values never change, or non-finite when the naive errors overflow. fold and cutoff name the round. | See scaled errors |
EvaluationError and friends | Cross-validation, search or holdout checks failed | reason and the wrapped cause explain which step |
In a browser, a FittingError or PredictionError with reason: "backend-failure" and backendPhase: "load" means the engine binary couldn't be downloaded, and a CountryHolidayError with reason: "snapshot-unavailable" means a calendar file couldn't be. See Browsers and bundlers.
Input errors
Each kind of input has its own error, so you can catch exactly the one you expect. Every input error has:
reason: what's wrong, one of a fixed list for that error.issues: every problem with thatreason, each with amessageand apathrelative to the input, such as[3, "regressors", "price"]for row 3'spriceregressor.message: a readable summary.
Errors about rows also name the first rejected row: rowIndex, the field (timestamp, value, capacity, floor, regressors or conditions) and, for a regressor or condition, its name. The other input errors have path, the first rejected location.
When the input has several kinds of problems, the error reports the first kind found. Once that's fixed, the next kind shows up.
| Error | Input | reason values |
|---|---|---|
ObservationError | A history of observations, as fit, forecast, crossValidate, planRollingOrigin, searchModels, residuals, detectAnomalies, anomalies and the baseline helpers take | empty, invalid-shape, missing-capacity, inconsistent-floor, capacity-not-above-floor, bound-on-non-logistic, missing-regressor, unexpected-regressor, unrepresentable-regressor, missing-condition, unexpected-condition |
PredictionInputError | Timestamps or rows to predict, including the rows residuals and detectAnomalies build from your observations | invalid-shape, missing-capacity, missing-floor, unexpected-floor, capacity-not-above-floor, bound-on-non-logistic, missing-regressor, unexpected-regressor, unrepresentable-regressor, missing-condition, unexpected-condition |
OptionsError | Model options, checked alone and against the history they fit | invalid-shape, invalid-value, name-collision, changepoint-out-of-range, conflicting-options, limit-exceeded |
FutureTimestampsError | makeFutureTimestamps's anchor and options | invalid-shape, timestamp-overflow |
UncertaintyOptionsError | Simulation options for predictUncertainty, predictWithIntervals, anomaly detection, interval cross-validation and holdout checks | invalid-shape, unsupported-output |
EvaluationPlanError | A cross-validation plan, plan summary or cross-validation controls | invalid-shape, horizon-exceeds-history, insufficient-data-after-initial, cutoff-out-of-range, empty-assessment, summary-mismatch, limit-exceeded, timestamp-overflow |
MetricOptionsError | Metric options for performanceMetrics and comparePerformance, baseline definitions and scaledErrorMetric options | invalid-shape, row-count-out-of-range, window-exceeds-rows |
SearchOptionsError | A searchModels request or a makeParameterGrid grid | invalid-shape, invalid-candidate-options, invalid-base-options, invalid-mode, cutoff-mismatch, duplicate-choice, limit-exceeded |
HoldoutInputError | An evaluateHoldout request | empty, invalid-shape, partition-not-later, limit-exceeded, unsupported-objective, receipt-mismatch, objective-dropped, baselines-need-point-metric, duplicate-baseline, unsupported-uncertainty, coverage-needs-intervals |
invalid-shape means the input doesn't have the expected form, such as a wrong type, a missing or unknown key, or a number out of its allowed range. issues then lists each problem.
A few errors carry more context:
SearchOptionsErrornames the candidate withcandidateIndex. When a candidate's options or the search'smodeare the problem,causeholds the originalOptionsError,EvaluationPlanErrororUncertaintyOptionsError.HoldoutInputErrornames thepartition,developmentorholdout, when one is the problem, and its first rejected row inrowIndex.- Options for a regressor's own model are reported as an
OptionsErrorunder["regressors", index, "predictor", ...]. - An operation that takes several inputs reports each with its own error, with paths relative to that input.
searchModelsreports itsplanwith anEvaluationPlanError, andevaluateHoldoutreports the selected options anduncertaintywith anOptionsErrorand anUncertaintyOptionsError. - A search candidate that fails with
failurePolicy: "record"keeps only its error'stagandreasonin the result, so the result can be saved without the issues' messages.
To handle any input problem the same way, use isInputError, which matches every error above. The InputError type is their union:
import { Effect } from "effect";
import { fit, isInputError, prophetFittingBackendLayer } from "effect-prophet";
const result = fit(history).pipe(
Effect.catchIf(isInputError, (error) => Effect.succeed(`Bad input: ${error.message}`)),
Effect.provide(prophetFittingBackendLayer),
);Without Effect
If you just await Effect.runPromise(...) and an error happens, the promise rejects with that error, so a normal try/catch works:
try {
const model = await Effect.runPromise(
fit(history).pipe(Effect.provide(prophetFittingBackendLayer)),
);
} catch (error) {
console.error(error);
}