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),
    Effect.provide(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. wasm.cold_start marks that call so the one-time cost isn't mistaken for a slow fit.

What is never recorded ​

Spans contain 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

That makes traces safe to send to a shared backend, even when your data is sensitive.

Span reference ​

Operations ​

Each of these functions creates a span named after it:

SpanCreated by
Prophet.fitfit
Prophet.predictpredict
Prophet.predictUncertaintypredictUncertainty
Prophet.crossValidatecrossValidate
Prophet.planRollingOriginplanRollingOrigin
Prophet.searchModelssearchModels
Prophet.evaluateHoldoutevaluateHoldout
Prophet.crossValidateBaselinecrossValidateBaseline
Prophet.comparePerformancecomparePerformance
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

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.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.

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-snapshotReading 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.
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.

effect-prophet.wasm.fit

AttributeMeaning
backend.type, operationAlways rust-wasm and fit
model.type, growthflat-map, linear-piecewise-map or logistic-piecewise-map, and the trend type
observation.countRows in the history
seasonality.count, coefficient.countSeasonal, event and regressor components, and the coefficients behind them
changepoint.count, parameter.countChangepoints and total values solved for, as fitted
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

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

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)
wasm.cold_starttrue only for the call that loaded the engine

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

Inspired by Prophet from Meta. Not affiliated with Meta.