Anofox forecast detection
Skill DataZooDE/anofox-forecast/.claude/skills/anofox-forecast-detection
Seasonality, changepoint, peak, and decomposition detection for the anofox_forecast DuckDB extension. Use when identifying seasonal periods before configuring seasonal forecasting models, detecting structural breaks, analysing peak timing regularity, or decomposing a series into trend / seasonal / residual components.From its SKILL.md
npx -y skills add DataZooDE/anofox-forecast --skill anofox-forecast-detectionAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
SKILL.md
9.6 KB, ~2.5k tokens by cl100k_base, as published. Nobody here has run it
Anofox Forecast — Detection & Decomposition Cheat Sheet
Extension: anofox_forecast v0.15.3 | DuckDB: v1.4.5 LTS / v1.5.4+ | Dual naming: ts_* and anofox_fcst_ts_*
Detect signal structure — seasonality is not auto-detected by the forecasters; you must run detection first and pass seasonal_period explicitly to ts_forecast_by.
Requires: json extension
Detection functions use the json extension for parameter marshalling. Enable auto-load once per session:
SET autoinstall_known_extensions = 1;
SET autoload_known_extensions = 1;
Period detection — 12 methods
ts_detect_periods_by — primary entry point
ts_detect_periods_by(source VARCHAR, group_col COLUMN, date_col COLUMN, value_col COLUMN,
params MAP/STRUCT) → TABLE
Output columns (group column name is preserved):
| Column | Type | Description |
|---|---|---|
<group_col> | (input type) | Preserved group column (e.g. product_id) |
periods | STRUCT(period, confidence, strength, amplitude, phase, iteration, ...)[] | Array of detected periods |
n_periods | BIGINT | Count of detected periods |
primary_period | DOUBLE | Top-level primary period (convenience, avoids struct indexing) |
method | VARCHAR | Method that produced the result |
Params:
| Key | Default | Description |
|---|---|---|
method | 'fft' | Detection method (see table below) |
max_period | series length / 2 | Upper bound on detected period |
min_period | 2 | Lower bound |
-- Default (FFT) — use the top-level primary_period column
SELECT product_id, primary_period, method
FROM ts_detect_periods_by('sales', product_id, ds, y, MAP{});
-- Autoperiod (FFT + ACF validation) — access full period list via struct-array unnest
SELECT product_id, method, unnest(periods).period AS p, unnest(periods).confidence AS conf
FROM ts_detect_periods_by('sales', product_id, ds, y,
MAP{'method': 'autoperiod'});
Methods available
| Method string | Underlying algorithm | Best for |
|---|---|---|
'fft' | FFT periodogram | Clean signals, fast (default) |
'acf' | Autocorrelation | Noisy signals, cyclical |
'autoperiod' | FFT + ACF cross-check | General purpose, robust |
'aic' | AIC criterion | Model-selection style |
'lomb_scargle' | Lomb-Scargle | Irregularly sampled data |
'sazed' | SAZED | Ensemble of methods |
'stl' | STL decomposition | Trend + seasonal separation |
'ssa' | Singular Spectrum Analysis | Multi-component |
'matrix_profile' | Matrix profile | Motif-based |
'cfd_autoperiod' | Clipped-FD autoperiod | Robust variant |
'instantaneous' | Instantaneous frequency | Time-varying periods |
'auto' | Auto-select | Unknown data |
Also available as scalar functions over LIST(y ORDER BY ds): ts_autoperiod, ts_cfd_autoperiod, ts_aic_period, ts_lomb_scargle, ts_ssa_period, ts_stl_period, ts_sazed_period, ts_matrix_profile_period, ts_estimate_period_fft, ts_estimate_period_acf, ts_instantaneous_period.
ts_detect_multiple_periods — multi-seasonal series
Some series have both weekly (7) and yearly (365) seasonality — use this for hourly / high-frequency data.
SELECT id, unnest(periods) AS p
FROM ts_detect_multiple_periods_by('sales', product_id, ds, y, MAP{});
Detect-then-forecast workflow (the standard pattern)
-- Step 1: Detect per-series period (use the top-level primary_period column)
CREATE OR REPLACE TABLE periods AS
SELECT product_id, primary_period AS sp
FROM ts_detect_periods_by('sales', product_id, ds, y, MAP{});
-- Step 2: Forecast with per-group detected period
-- Common: pick the mode across the panel, apply uniformly
CREATE OR REPLACE TABLE forecasts AS
SELECT * FROM ts_forecast_by('sales', product_id, ds, y,
'AutoETS', 14, '1d',
MAP{'seasonal_period': (SELECT mode() WITHIN GROUP (ORDER BY sp) FROM periods)::VARCHAR}
);
Changepoint detection
ts_detect_changepoints_by — Bayesian Online Changepoint Detection (BOCD)
ts_detect_changepoints_by(source, group_col, date_col, value_col, params) → TABLE
Params:
| Key | Default | Description |
|---|---|---|
hazard_lambda | 250.0 | Hazard rate. Lower → more changepoints |
Returns one row per input point with is_changepoint BOOLEAN and changepoint_probability DOUBLE.
-- Detect changepoints
SELECT product_id, ds, y, is_changepoint, changepoint_probability
FROM ts_detect_changepoints_by('sales', product_id, ds, y,
MAP{'hazard_lambda': '100'})
WHERE is_changepoint;
Also: ts_detect_changepoints (scalar) and ts_detect_changepoints_agg (aggregate).
Seasonality analysis — classify / measure strength
ts_classify_seasonality_by — timing / modulation / strength
ts_classify_seasonality_by(source, group_col, date_col, value_col, period DOUBLE) → TABLE
Output columns (group column preserved):
| Column | Type | Description |
|---|---|---|
<group_col> | (input) | Preserved group column |
timing_classification | VARCHAR | e.g. Regular, Weekly, Irregular |
modulation_type | VARCHAR | Additive / Multiplicative / None |
has_stable_timing | BOOLEAN | Peak timing regularity |
timing_variability | DOUBLE | Numeric variability score |
seasonal_strength | DOUBLE | ∈ [0, 1] |
is_seasonal | BOOLEAN | Overall verdict |
cycle_strengths | DOUBLE[] | Per-cycle strengths |
weak_seasons | BIGINT[] | Indices of weak seasonal peaks |
SELECT product_id, timing_classification, modulation_type, is_seasonal, seasonal_strength
FROM ts_classify_seasonality_by('sales', product_id, ds, y, 7.0);
Scalar variants: ts_classify_seasonality, ts_classify_seasonality_agg.
ts_seasonal_strength, ts_seasonal_strength_windowed, ts_analyze_seasonality
Numeric measures over LIST(y ORDER BY ds). windowed variant tracks strength changes across the series.
ts_detect_seasonality, ts_detect_seasonality_changes
Detect the presence and any regime shifts in the seasonal pattern.
Peak detection & timing
ts_detect_peaks_by
ts_detect_peaks_by(source, group_col, date_col, value_col, params) → TABLE
Returns detected peak indices and values per group.
ts_analyze_peak_timing_by
ts_analyze_peak_timing_by(source, group_col, date_col, value_col, period, params) → TABLE
Measures how tightly peaks cluster at a specific phase of the seasonal cycle. Useful for retail (do peaks land on the same day of the week?).
Also: ts_detect_peaks, ts_analyze_peak_timing, ts_detect_amplitude_modulation.
Decomposition
ts_mstl_decomposition_by — Multiple Seasonal-Trend decomposition (LOESS)
ts_mstl_decomposition_by(source, group_col, date_col, value_col, params) → TABLE
seasonal_periods goes INSIDE the params (JSON-string form): MAP{'seasonal_periods': '[7, 365]'}. Returns trend, seasonal_<i>, remainder per point.
SELECT product_id, ds, trend, seasonal_1, remainder
FROM ts_mstl_decomposition_by('sales', product_id, ds, y,
MAP{'seasonal_periods': '[7]'});
ts_decompose_seasonal (scalar) — classical additive / multiplicative
ts_detrend_by
Remove linear or polynomial trend:
ts_detrend_by(source, group_col, date_col, value_col, method) → TABLE
method: 'linear' (default), 'polynomial', 'ols'.
Gotchas
- Seasonality is NOT auto-detected by forecasters.
AutoETS/AutoARIMA/AutoThetaaccept aseasonal_periodparam — if you don't set it, they select non-seasonal variants. Run detection first, then pass explicitly. ts_detect_periods_byreturns one row per group with aperiodsSTRUCT. Access fields withprimary_period.- Detection needs sufficient history: FFT-family methods need ≥ 2 full cycles; ACF-family needs ≥ 3. On short-history panels,
ts_detect_periodsmay returnprimary_period = 1(no seasonality) even when a period exists. ts_mstl_decomposition_byseasonal_periods is a JSON string ('[7, 365]'), not a native array.
Canonical detection pipeline
-- 1. Detect periods across the panel
CREATE OR REPLACE TABLE detected AS
SELECT id, primary_period AS sp, (periods).confidence AS conf
FROM ts_detect_periods_by('sales', product_id, ds, y, MAP{'method': 'autoperiod'});
-- 2. Classify seasonality mode (additive vs multiplicative) at the modal period
CREATE OR REPLACE TABLE modes AS
SELECT id, classification, confidence
FROM ts_classify_seasonality_by('sales', product_id, ds, y,
(SELECT mode() WITHIN GROUP (ORDER BY sp) FROM detected));
-- 3. Optional: flag changepoints for post-hoc review
CREATE OR REPLACE TABLE breaks AS
SELECT product_id, ds, is_changepoint
FROM ts_detect_changepoints_by('sales', product_id, ds, y, MAP{'hazard_lambda': '250'})
WHERE is_changepoint;
See also: anofox-forecast-data-prep (fill gaps before detection — needs regular grid), anofox-forecast-eda (trend_strength / seasonality_strength gate the need for detection), anofox-forecast-models (pass detected seasonal_period to ts_forecast_by).
Reference docs:
docs/api/05-period-detection.mddocs/api/05a-decomposition.mddocs/api/05b-peak-detection.mddocs/api/06-changepoint-detection.md
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.