Skip to content

Tracing ​

Every fit, forecast and evaluation is traced automatically. You don't add any code to the library calls: point your app at a tracing backend such as Jaeger, Honeycomb or Grafana Tempo, and each step shows up there with how long it took and what kind of work it did.

If your app doesn't set up a tracer, nothing is sent anywhere.

See your first trace ​

Start Jaeger locally:

sh
docker run --rm -p 16686:16686 -p 4318:4318 jaegertracing/jaeger:latest

Then send spans to it with Effect's built-in OpenTelemetry exporter. No extra packages are needed:

ts
import { Effect, Layer } from "effect";
import { FetchHttpClient } from "effect/http";
import { OtlpSerialization, OtlpTracer } from "effect/observability";
import { fit, predict, prophetFittingBackendLayer } from "effect-prophet";

// Send spans to any OpenTelemetry collector that accepts OTLP over HTTP, such as Jaeger.
const tracing = OtlpTracer.layer({
  url: "http://localhost:4318/v1/traces",
  resource: { serviceName: "sales-forecast" },
}).pipe(Layer.provide([OtlpSerialization.layerJson, FetchHttpClient.layer]));

const history = Array.from({ length: 90 }, (_, day) => ({
  timestamp: new Date(Date.UTC(2025, 0, 1 + day)).toISOString(),
  value: 100 + day * 0.5 + 10 * Math.sin((2 * Math.PI * day) / 7),
}));

const nextWeek = Array.from({ length: 7 }, (_, offset) =>
  new Date(Date.UTC(2025, 0, 91 + offset)).toISOString(),
);

const forecast = await Effect.runPromise(
  Effect.gen(function* () {
    const model = yield* fit(history);

    return yield* predict(model, nextWeek);
  }).pipe(
    // Your own span. Effect Prophet's spans appear underneath it.
    Effect.withSpan("weekly-sales-forecast"),
    Effect.provide([prophetFittingBackendLayer, tracing]),
  ),
);

console.log(forecast.length);

Open http://localhost:16686, choose the sales-forecast service and open the trace:

txt
weekly-sales-forecast                 your span
├─ Prophet.fit
│  ├─ Prophet.decodeObservations      checking your history
│  ├─ Prophet.decodeOptions           checking your settings
│  └─ effect-prophet.wasm.fit         the forecasting engine fitting the model
└─ Prophet.predict
   └─ effect-prophet.wasm.predict     the forecasting engine computing the forecast

Spans from Effect Prophet join whatever trace is already active. Wrap your own work in Effect.withSpan, as above, and the forecast appears as part of your request, job or pipeline. Any tracer you give Effect works the same way, including one your app already provides.

Why was this fit slow? ​

The engine runs as one step, so a trace has no spans inside it. Instead, the effect-prophet.wasm.fit span records what the engine did. For example:

AttributeValueWhat it tells you
observation.count90Rows in the history
changepoint.count25Places the trend was allowed to bend
parameter.count33Values the engine had to solve for
optimizer.algorithmnewtonThe solving method that ran
optimizer.iteration.count600Solving steps taken
optimizer.terminationobjective-changeWhy it stopped. iteration-limit means it ran out of steps.
wasm.cold_starttrueThis call also loaded the engine, which only happens once

(Every attribute name starts with effect_prophet., left off here to keep the table short.)

Fitting time is roughly the number of steps times the cost of each step. The cost of a step grows with parameter.count, and the 25 default changepoints are often a large share of the parameters. With the default settings, histories under 100 rows use Newton's method, whose steps get expensive as parameters grow.

The number of steps is usually a few dozen to a few hundred. A count much higher than that, as in the example above, usually means the data has almost no noise: a perfectly smooth synthetic series can be fitted exactly, so the solver keeps shrinking its noise estimate toward zero, one step at a time. Python Prophet does the same on such data. Real data rarely triggers this. If yours does, fewer changepoints (see Trends and changepoints) make each step cheaper. Let the trace tell you whether it helped.

The first engine call in a process also loads the engine. In a browser, that includes downloading the binary. wasm.cold_start marks that call so the one-time cost isn't mistaken for a slow fit. Calls that start while it's loading wait for the same load and aren't marked.

What is recorded ​

The attributes Effect Prophet sets on its spans are only counts and fixed labels, such as growth: "linear" or optimizer.algorithm: "newton". They never contain:

  • your values, timestamps or forecasts
  • regressor, event or seasonality names
  • fitted coefficients or other model internals
  • error messages, stack traces or seeds

Failed spans are different. When an operation fails, its span ends with the error, as every Effect span does, and your tracer decides what to do with it. The OTLP exporter above sends the error's message and stack trace with the span, and error messages can name your data. For example, a non-finite regressor value fails with a message that includes the regressor's name:

txt
Expected a finite number
  at [0]["regressors"]["private_customer_health_index"]

If your field names or other inputs are sensitive, decide what your exporter may send before pointing it at a shared backend: filter or redact exception events and status messages in your tracer or in a collector processor. Effect Prophet leaves that policy to your application, and doesn't change or hide its errors.

Span reference ​

Operations ​

Each of these functions creates a span named after it:

SpanCreated by
Prophet.fitfit
Prophet.predictpredict
Prophet.predictUncertaintypredictUncertainty
Prophet.predictWithIntervalspredictWithIntervals, around its Prophet.predictUncertainty and Prophet.predict spans
Prophet.detectAnomaliesdetectAnomalies, around Prophet.decodeObservations and Prophet.predictWithIntervals
Prophet.residualsresiduals, around Prophet.decodeObservations and Prophet.predict
Prophet.forecastforecast, around Prophet.decodeObservations, Prophet.fit and Prophet.predict
Prophet.anomaliesanomalies, around Prophet.decodeObservations, Prophet.fit and Prophet.detectAnomalies
Prophet.crossValidatecrossValidate
Prophet.makeFutureTimestampsmakeFutureTimestamps
Prophet.planRollingOriginplanRollingOrigin
Prophet.makeParameterGridmakeParameterGrid, around one Prophet.decodeOptions for the base and one per candidate
Prophet.searchModelssearchModels
Prophet.evaluateHoldoutevaluateHoldout
Prophet.crossValidateBaselinecrossValidateBaseline
Prophet.comparePerformancecomparePerformance
Prophet.scaledErrorMetricscaledErrorMetric, around Prophet.decodeObservations
Prophet.encodeFittedModel, Prophet.decodeFittedModelSaving and loading models
Prophet.encodeEvaluationReport, Prophet.decodeEvaluationReportSaving and loading evaluation reports
Prophet.decodeObservations, Prophet.decodeOptionsChecking input, also called inside fit
Prophet.inspectCountryHolidaysinspectCountryHolidays
Prophet.parseCountryHolidaysChecking a countryHolidays selection, inside decodeOptions and inspectCountryHolidays
Prophet.parseEventCalendar, Prophet.parseRegressorDefinitions, Prophet.decodeLinearOptimizerChecking settings, inside decodeOptions

Regressor models ​

A regressor forecast by its own model adds a span for that model's work:

SpanCovers
Prophet.fitRegressorModelFitting one regressor's model inside Prophet.fit, with its own effect-prophet.wasm.fit span and Prophet.fit attributes
Prophet.predictRegressorModelForecasting one regressor inside Prophet.predict, with its own effect-prophet.wasm.predict span
Prophet.simulateRegressorModelSimulating one regressor's future values inside Prophet.predictUncertainty, with its own effect-prophet.wasm.simulate span

Each regressor model is fitted before the main model, so its effect-prophet.wasm.fit span comes first. A prediction whose rows all fall inside the history needs no regressor forecast and creates no Prophet.predictRegressorModel or Prophet.simulateRegressorModel span. Options that fail validation create no Prophet.fitRegressorModel span.

Engine and evaluation steps ​

SpanCovers
effect-prophet.wasm.fitFitting in the engine, including loading it and reading results
effect-prophet.wasm.predictComputing a forecast in the engine
effect-prophet.wasm.simulateSimulating uncertainty ranges in the engine
effect-prophet.wasm.componentRangesEach component's range over a posterior, in predictWithIntervals
effect-prophet.evaluation.foldOne cross-validation fold: a fit and a forecast
effect-prophet.evaluation.candidateOne candidate setting during searchModels
effect-prophet.evaluation.holdoutThe final check in evaluateHoldout

Input that fails validation, or a request with nothing to do, such as an empty list of dates, returns before reaching the engine and creates no engine span. forecast checks its history and future rows before fitting, so malformed rows create no Prophet.fit span. anomalies also checks the history's length before fitting, so a history too long to simulate creates no Prophet.fit span.

Country calendar steps ​

SpanCovers
Prophet.resolveCountryHolidaysResolving a country calendar's dates and coverage for the requested years, inside fit, predict, predictUncertainty and inspectCountryHolidays
effect-prophet.calendar.load-snapshotLoading and checking a packaged country calendar file, inside a resolution for fit or inspectCountryHolidays

Prediction resolves dates from the snapshot saved in the model, so it reads no file and creates no effect-prophet.calendar.load-snapshot span. A malformed selection or year list fails before resolution and creates no calendar step span; an empty prediction creates none either.

Attributes ​

Every attribute name starts with effect_prophet..

Prophet.fit

AttributeMeaning
seasonality.custom.countCustom seasonalities you added
seasonality.builtin.enabled.countBuilt-in seasonalities (yearly, weekly, daily) in use
seasonality.builtin.{yearly,weekly,daily}.resolutionWhy each was used or skipped: explicitly-enabled, automatic-enabled, explicitly-disabled, insufficient-history or sampling-too-sparse
regressor.countExtra factors (regressors). Only present when there are some.
regressor_model.countRegressors forecast by their own models. Only present when there are some.
event.countHolidays and events, including country holidays. Only present when there are some.
seasonality.condition.countConditions used by conditional seasonalities. Only present when there are some.

Prophet.makeFutureTimestamps

AttributeMeaning
frequencyThe frequency used, such as D or MS
timestamp.countTimestamps returned, history included. Neither is set when the input is invalid.

Prophet.makeParameterGrid

AttributeMeaning
axis.countSettings varied. Set only when the grid is valid.
candidate.countCandidates returned. Set only when the grid is valid.

A grid that is too large, has an empty or repeated setting, or isn't shaped like a grid fails before any Prophet.decodeOptions span.

effect-prophet.wasm.fit

AttributeMeaning
backend.type, operationAlways rust-wasm and fit
model.type, growthflat-map, linear-piecewise-map, logistic-piecewise-map, flat-mcmc, linear-piecewise-mcmc or logistic-piecewise-mcmc, and the trend type
observation.countRows in the history
seasonality.count, coefficient.countSeasonal, event and regressor components, and the coefficients behind them
changepoint.countPlaces the trend was allowed to bend. Always 0 for flat trends
parameter.countTotal values solved for, including one per changepoint for flat trends, which never use them
scaling.modeHow values were scaled before fitting
component.modemixed for multiplicative effects and capped trends; additive for flat trends with only additive effects
wasm.cold_starttrue only for the call that loaded the engine
optimizer.requested_algorithm, optimizer.max_iterations, optimizer.fallbackThe optimizer settings you passed in map.optimizer, or Prophet's defaults
optimizer.algorithm, optimizer.termination, optimizer.iteration.countWhat ran, why it stopped and how many steps it took
optimizer.attempt.count, optimizer.failed_attempt.iteration.count, optimizer.hessian_reset.countRetries and recoveries, when the first method fell back to the second
optimizer.initializationwarm-start when you passed map.warmStart. Only present then.
optimizer.warm_start.delta, optimizer.warm_start.betawarm-start, or default when the counts differed. Only present when a warm-started fit ran the optimizer
mcmc.chains, mcmc.samples, mcmc.warmupThe Bayesian fitting settings. Only present with mcmc, which has no optimizer.* attributes
mcmc.initializationwarm-start when you passed map.warmStart, otherwise default
mcmc.termination, mcmc.draw.countsampled or constant-target-shortcut, and the draws kept
mcmc.divergence.count, mcmc.max_tree_depth.countDivergent draws and draws that hit the tree-depth limit. Absent for the constant-target shortcut

effect-prophet.wasm.predict, effect-prophet.wasm.simulate and effect-prophet.wasm.componentRanges

AttributeMeaning
model.type, prediction.countThe model and the number of dates forecast
seasonality.count, scaling.modeAs for fitting (predict only)
output.kind, sample.countintervals or samples, and simulations per date (simulate only); a posterior runs ceil(samples / draws) per draw
mcmc.draw.countPosterior draws (simulate and componentRanges, posteriors only)
wasm.cold_starttrue only for the call that loaded the engine

Prophet.forecast

AttributeMeaning
observation.countRows in the history. Set only when the forecast succeeds.
prediction.countRows forecast. Set only when the forecast succeeds.

Prophet.detectAnomalies and Prophet.anomalies

AttributeMeaning
observation.countMeasurements checked. Set only when the check succeeds.
anomaly.countMeasurements outside their range. Set only on success.

Prophet.scaledErrorMetric

AttributeMeaning
metric.lagThe naive rule: previous-row or duration. Set only on success.
fold.countRounds given a scale. Set only when every round has one.

Prophet.residuals

AttributeMeaning
observation.countMeasurements compared. Set only when every residual is computed.

Country calendars

SpanAttributes
Prophet.resolveCountryHolidayscalendar.source (bundled from the package, embedded from a fitted model), calendar.coverage, calendar.year.count, calendar.occurrence.count, calendar.issue.count
effect-prophet.calendar.load-snapshotcalendar.source (always bundled), calendar.name.count, calendar.partition.count

calendar.occurrence.count and calendar.issue.count are set only when resolution succeeds.

Evaluation

SpanAttributes
Prophet.crossValidate, Prophet.searchModelsevaluation.scaling.mode
effect-prophet.evaluation.foldoperation, fold.index, fold.count, training.count, assessment.count, growth, model.type, evaluation.scaling.mode, uncertainty.enabled, sample.count
effect-prophet.evaluation.candidateoperation, candidate.index, candidate.count, fold.count, objective, uncertainty.enabled, evaluation.scaling.mode
effect-prophet.evaluation.holdoutoperation, growth, model.type, training.count, assessment.count, baseline.count, uncertainty.enabled

objective is the built-in metric name, such as mae, or custom for a custom metric. A custom metric's own name is never set as an attribute, because you choose it. An error your score function fails with can still reach your exporter through the failed span, as described in What is recorded.

Inspired by Prophet from Meta. Not affiliated with Meta.