Anofox Forecast DuckDB Extension — Cheat Sheet
Extension: anofox_forecast v0.4.6 | DuckDB: v1.4.x+ | Dual naming: ts_* and anofox_fcst_ts_*
Installation
LOAD anofox_forecast;
-- All functions available as ts_* and anofox_fcst_ts_* (identical)
Critical Gotchas
Seasonality is NOT auto-detected. You must pass seasonal_period explicitly.
Detect first with ts_detect_periods_by, then pass to forecasting.
DO NOT chain _by table functions in CTEs. Returns 0 rows silently under parallel execution.
Always CREATE TABLE between pipeline steps:
-- BROKEN (0 rows):
WITH step1 AS (SELECT * FROM ts_fill_gaps_by(...))
SELECT * FROM ts_fill_nulls_const_by('step1', ...);
-- CORRECT:
CREATE TABLE step1 AS SELECT * FROM ts_fill_gaps_by(...);
SELECT * FROM ts_fill_nulls_const_by('step1', ...);
Model names are case-sensitive. 'AutoETS' works, 'autoets' errors.
ts_cv_forecast_by requires pre-created folds. Input table must have fold_id and split columns
(from ts_cv_folds_by or ts_cv_split_by). Passing raw data throws a clear error.
ts_forecast_by requires frequency as 7th positional parameter. No default — you must specify it:
-- WRONG: missing frequency
SELECT * FROM ts_forecast_by('sales', id, date, val, 'Naive', 12);
-- CORRECT:
SELECT * FROM ts_forecast_by('sales', id, date, val, 'Naive', 12, '1d');
Metric _by table macros are deprecated. Use scalar functions with GROUP BY:
-- Deprecated: SELECT * FROM ts_mae_by(...)
-- Use instead:
SELECT id, ts_mae(LIST(y ORDER BY ds), LIST(yhat ORDER BY ds)) AS mae
FROM results GROUP BY id;
Always use ORDER BY in LIST() for temporal correctness:
LIST(value ORDER BY date) -- correct
LIST(value) -- wrong: order not guaranteed
Three API Styles
1. Table Macros (primary — use these)
Operate on table names as strings. Handle grouping automatically.
SELECT * FROM ts_forecast_by('sales', product_id, date, revenue, 'AutoETS', 14, '1d',
MAP{'seasonal_period': '7'});
2. Scalar Functions
Operate on arrays. Use with LIST() aggregation and GROUP BY.
SELECT product_id,
ts_mae(LIST(actual ORDER BY date), LIST(forecast ORDER BY date)) AS mae
FROM results GROUP BY product_id;
3. Aggregate Functions
Return structs. Access fields with (result).field_name.
SELECT product_id, (ts_stats(LIST(value ORDER BY date))).*
FROM sales GROUP BY product_id;
Parameter Syntax
STRUCT (recommended)
MAP{'seasonal_period': '7'}
MAP{'seasonal_periods': '[7, 365]'}
MAP{'method': 'autoperiod', 'max_period': '28'}
All param values are strings (even numbers). Arrays use JSON syntax: '[7, 365]'.
Frequency Strings
| Format |
Examples |
| Polars style |
'1d', '1h', '30m', '1w', '1mo', '1q', '1y' |
| DuckDB INTERVAL |
'1 day', '1 hour' |
| Raw integer |
'1', '7' (interpreted as days) |
Common Workflows
1. Basic Forecast
-- Forecast 14 days ahead with weekly seasonality
SELECT * FROM ts_forecast_by(
'sales', product_id, date, revenue,
'HoltWinters', 14, '1d',
MAP{'seasonal_period': '7'}
);
2. Data Preparation Pipeline (CREATE TABLE between steps!)
-- Step 1: Fill gaps
CREATE TABLE gaps_filled AS
SELECT * FROM ts_fill_gaps_by('raw_data', product_id, date, value, '1d');
-- Step 2: Impute NULLs
CREATE TABLE nulls_filled AS
SELECT * FROM ts_fill_nulls_const_by('gaps_filled', product_id, date, value, 0.0);
-- Step 3: Drop short series
CREATE TABLE clean_data AS
SELECT * FROM ts_drop_short_by('nulls_filled', product_id, 20);
3. Detect Seasonality → Forecast
-- Step 1: Detect
SELECT id, (periods).primary_period
FROM ts_detect_periods_by('sales', product_id, date, value, MAP{});
-- Returns e.g. primary_period = 7 (weekly)
-- Step 2: Forecast with detected period
SELECT * FROM ts_forecast_by(
'sales', product_id, date, value,
'AutoETS', 14, '1d', MAP{'seasonal_period': '7'}
);
4. Cross-Validation & Model Comparison
-- Step 1: Create folds
CREATE TABLE cv_folds AS
SELECT * FROM ts_cv_folds_by('data', unique_id, ds, y, 3, 12, MAP{});
-- Step 2: Forecast per fold (for each model)
CREATE TABLE cv_naive AS
SELECT * FROM ts_cv_forecast_by('cv_folds', unique_id, ds, y, 'Naive', MAP{});
CREATE TABLE cv_autoets AS
SELECT * FROM ts_cv_forecast_by('cv_folds', unique_id, ds, y, 'AutoETS',
MAP{'seasonal_period': '7'});
-- Step 3: Compare metrics
SELECT 'Naive' AS model,
ts_mae(LIST(y ORDER BY ds), LIST(yhat ORDER BY ds)) AS mae,
ts_rmse(LIST(y ORDER BY ds), LIST(yhat ORDER BY ds)) AS rmse
FROM cv_naive GROUP BY ALL
UNION ALL
SELECT 'AutoETS',
ts_mae(LIST(y ORDER BY ds), LIST(yhat ORDER BY ds)),
ts_rmse(LIST(y ORDER BY ds), LIST(yhat ORDER BY ds))
FROM cv_autoets GROUP BY ALL;
5. Full Production Pipeline
-- 1. Quality check
SELECT id, (stats).length, (stats).n_nulls, (stats).n_gaps
FROM ts_stats_by('raw', product_id, date, value, '1d');
-- 2. Prep (materialize each step!)
CREATE TABLE step1 AS
SELECT * FROM ts_fill_gaps_by('raw', product_id, date, value, '1d');
CREATE TABLE step2 AS
SELECT * FROM ts_fill_nulls_const_by('step1', product_id, date, value, 0.0);
CREATE TABLE clean AS
SELECT * FROM ts_drop_short_by('step2', product_id, 20);
-- 3. Detect seasonality
SELECT id, (periods).primary_period
FROM ts_detect_periods_by('clean', product_id, date, value, MAP{});
-- 4. Backtest
CREATE TABLE cv_folds AS
SELECT * FROM ts_cv_folds_by('clean', product_id, date, value, 5, 14, MAP{});
CREATE TABLE backtest AS
SELECT * FROM ts_cv_forecast_by('cv_folds', product_id, date, value, 'AutoETS',
MAP{'seasonal_period': '7'});
-- 5. Evaluate
SELECT product_id,
ts_mae(LIST(y ORDER BY date), LIST(yhat ORDER BY date)) AS mae,
ts_rmse(LIST(y ORDER BY date), LIST(yhat ORDER BY date)) AS rmse
FROM backtest GROUP BY product_id;
-- 6. Forecast
CREATE TABLE forecasts AS
SELECT * FROM ts_forecast_by('clean', product_id, date, value,
'AutoETS', 14, '1d', MAP{'seasonal_period': '7'});
-- 7. Conformal intervals
CREATE TABLE calibration AS
SELECT * FROM ts_conformal_calibrate('backtest', value, yhat, {'alpha': 0.1});
SELECT * FROM ts_conformal_apply_by(
'forecasts', product_id, yhat,
(SELECT conformity_score FROM calibration)
);
Model Quick Reference (32 Models)
Automatic Selection (6)
| Model |
Optional Params |
Best For |
AutoETS |
seasonal_period |
Unknown patterns (default pick) |
AutoARIMA |
seasonal_period |
Unknown patterns, ARIMA family |
AutoTheta |
seasonal_period |
Unknown patterns, Theta family |
AutoMFLES |
seasonal_periods[] |
Multiple seasonalities |
AutoMSTL |
seasonal_periods[] |
Multiple seasonalities |
AutoTBATS |
seasonal_periods[] |
Multiple seasonalities |
Basic (6)
| Model |
Required |
Optional |
Best For |
Naive |
— |
— |
Baseline benchmark |
SMA |
— |
window (def: 5) |
Smoothed baseline |
SeasonalNaive |
seasonal_period |
— |
Seasonal baseline |
SES |
— |
alpha (def: 0.3) |
No trend, no seasonality |
SESOptimized |
— |
— |
Optimized SES |
RandomWalkDrift |
— |
— |
Trend without seasonality |
Exponential Smoothing (4)
| Model |
Required |
Optional |
Holt |
— |
alpha, beta |
HoltWinters |
seasonal_period |
alpha, beta, gamma |
SeasonalES |
seasonal_period |
alpha, gamma |
SeasonalESOptimized |
seasonal_period |
— |
Theta Methods (5)
| Model |
Optional |
Theta |
seasonal_period, theta |
OptimizedTheta |
seasonal_period |
DynamicTheta |
seasonal_period, theta |
DynamicOptimizedTheta |
seasonal_period |
AutoTheta |
seasonal_period |
State Space & ARIMA (4)
| Model |
Required |
Optional |
ETS |
— |
seasonal_period, model |
AutoETS |
— |
seasonal_period |
ARIMA |
p, d, q |
P, D, Q, s |
AutoARIMA |
— |
seasonal_period |
Multiple Seasonality (6)
| Model |
Required |
Optional |
MFLES |
seasonal_periods[] |
iterations |
AutoMFLES |
— |
seasonal_periods[] |
MSTL |
seasonal_periods[] |
stl_method |
AutoMSTL |
— |
seasonal_periods[] |
TBATS |
seasonal_periods[] |
use_box_cox |
AutoTBATS |
— |
seasonal_periods[] |
Intermittent Demand (6)
| Model |
Optional |
Best For |
CrostonClassic |
— |
Sparse demand |
CrostonOptimized |
— |
Sparse demand |
CrostonSBA |
— |
Sparse demand (bias-corrected) |
ADIDA |
— |
Aggregate-Disaggregate |
IMAPA |
— |
Multiple aggregation |
TSB |
alpha_d, alpha_p |
Best intermittent (tunable) |
Model Selection Guide
| Data Characteristics |
Recommended Models |
| No trend, no seasonality |
Naive, SES, SESOptimized |
| Trend, no seasonality |
Holt, Theta, RandomWalkDrift |
| Single seasonal period |
SeasonalNaive, HoltWinters, SeasonalES |
| Multiple seasonalities |
MSTL, MFLES, TBATS |
| Many zeros (intermittent) |
CrostonClassic, CrostonSBA, TSB |
| Unknown characteristics |
AutoETS, AutoARIMA, AutoTheta |
| Short series (< 20 pts) |
Naive, SES |
| Scenario |
First Try |
Alternative |
| Daily retail sales |
HoltWinters |
MSTL |
| Weekly financial data |
Theta |
AutoETS |
| Hourly sensor data |
MFLES |
MSTL |
| Spare parts demand |
CrostonSBA |
TSB |
Function Quick Reference
Forecasting
| Function |
Purpose |
ts_forecast_by(table, group, date, value, method, horizon, frequency, params) |
Multi-series forecast |
ts_forecast_exog_by(table, group, date, value, x_cols, future_table, future_date, future_x, model, horizon, params, freq) |
Forecast with exogenous variables |
Data Preparation
| Function |
Purpose |
ts_fill_gaps_by(table, group, date, value, freq) |
Fill missing timestamps with NULL |
ts_fill_forward_by(table, group, date, value, target_date, freq) |
Extend series to target date |
ts_fill_nulls_const_by(table, group, date, value, fill_val) |
Replace NULLs with constant |
ts_fill_nulls_forward_by(table, group, date, value) |
Forward-fill NULLs |
ts_fill_nulls_backward_by(table, group, date, value) |
Backward-fill NULLs |
ts_fill_nulls_mean_by(table, group, date, value) |
Fill NULLs with mean |
ts_drop_constant_by(table, group, value) |
Remove constant series |
ts_drop_short_by(table, group, min_len) |
Remove short series |
ts_drop_gappy_by(table, group, value, max_gap_ratio) |
Remove gappy series |
ts_drop_zeros_by(table, group, value) |
Remove all-zero series |
ts_drop_leading_zeros_by(table, group, date, value) |
Trim leading zeros |
ts_drop_trailing_zeros_by(table, group, date, value) |
Trim trailing zeros |
ts_drop_edge_zeros_by(table, group, date, value) |
Trim both edges |
ts_diff_by(table, group, date, value, order) |
Compute differences |
Statistics & Quality
| Function |
Purpose |
ts_stats_by(table, group, date, value, freq) |
36 statistics per series |
ts_data_quality_by(table, group, date, value, min_len, freq) |
Quality scores (0-1) |
ts_quality_report(stats_table, min_len) |
Summary quality report |
ts_stats_summary(stats_table) |
Summary across all series |
Period Detection & Decomposition
| Function |
Purpose |
ts_detect_periods_by(table, group, date, value, params) |
Detect seasonal periods |
ts_classify_seasonality_by(table, group, date, value, period) |
Classify seasonality type |
ts_mstl_decomposition_by(table, group, date, value, periods[], params) |
MSTL decomposition |
ts_detrend_by(table, group, date, value, method) |
Remove trend |
ts_detect_peaks_by(table, group, date, value, params) |
Detect peaks |
ts_analyze_peak_timing_by(table, group, date, value, period, params) |
Peak timing analysis |
Cross-Validation
| Function |
Purpose |
ts_cv_folds_by(table, group, date, value, n_folds, horizon, params) |
Create CV folds |
ts_cv_forecast_by(folds_table, group, date, value, method, params) |
Forecast on CV folds |
ts_cv_split_by(table, group, date, value, cutoff_dates[], horizon, params) |
Custom fold boundaries |
ts_cv_hydrate_by(folds, source, group, date, features[], params) |
Add features to folds |
Evaluation Metrics (scalar — use with GROUP BY)
| Function |
Signature |
Description |
ts_mae |
(LIST, LIST) → DOUBLE |
Mean Absolute Error |
ts_mse |
(LIST, LIST) → DOUBLE |
Mean Squared Error |
ts_rmse |
(LIST, LIST) → DOUBLE |
Root Mean Squared Error |
ts_mape |
(LIST, LIST) → DOUBLE |
Mean Absolute Percentage Error |
ts_smape |
(LIST, LIST) → DOUBLE |
Symmetric MAPE |
ts_r2 |
(LIST, LIST) → DOUBLE |
R-squared |
ts_bias |
(LIST, LIST) → DOUBLE |
Bias (mean error) |
ts_mase |
(LIST, LIST, LIST) → DOUBLE |
Mean Absolute Scaled Error |
ts_rmae |
(LIST, LIST, LIST) → DOUBLE |
Relative MAE |
ts_coverage |
(LIST, LIST, LIST) → DOUBLE |
Interval coverage |
ts_quantile_loss |
(LIST, LIST, DOUBLE) → DOUBLE |
Quantile loss |
Conformal Prediction
| Function |
Purpose |
ts_conformal_by(backtest, group, actual, forecast, point_forecast, params) |
One-step conformal intervals |
ts_conformal_calibrate(backtest, actual, forecast, params) |
Calibrate conformity score |
ts_conformal_apply_by(forecasts, group, forecast_col, score) |
Apply calibrated score |
ts_conformal_predict(residuals[], forecasts[], alpha) |
Array-based conformal |
ts_conformal_predict_asymmetric(residuals[], forecasts[], alpha) |
Asymmetric intervals |
ts_conformal_quantile(residuals[], alpha) |
Compute conformity quantile |
ts_conformal_intervals(forecasts[], score) |
Apply score to array |
ts_conformal_coverage(actuals[], lower[], upper[]) |
Empirical coverage |
ts_conformal_evaluate(actuals[], lower[], upper[], alpha) |
Full evaluation |
ts_interval_width_by(table, group, lower, upper) |
Mean interval width |
Feature Extraction
| Function |
Purpose |
ts_features_by(table, group, date, value) |
Extract 117 tsfresh features |
ts_features_list() |
List available features |
Hierarchical
| Function |
Purpose |
ts_combine_keys((SELECT date, val, id1, id2, ...), params) |
Combine ID columns |
ts_aggregate_hierarchy((SELECT date, val, id1, id2, ...), params) |
Aggregate at all levels |
ts_split_keys((SELECT uid, date, val), ...) |
Split combined keys |
ts_validate_separator((SELECT id1, id2, ...), ...) |
Validate separator char |
Changepoint Detection
| Function |
Purpose |
ts_detect_changepoints_by(table, group, date, value, params) |
Detect structural breaks |
Timestamp Validation
| Function |
Purpose |
ts_validate_timestamps_by(table, group, date, expected_dates[]) |
Validate timestamps exist |
ts_validate_timestamps_summary_by(table, group, date, expected_dates[]) |
Validation summary |
Future Value Handling
| Function |
Purpose |
ts_fill_unknown_by(table, group, date, value, cutoff, params) |
Fill unknown future values |
ts_mark_unknown_by(table, group, date, cutoff) |
Mark known/unknown rows |
Minimum Data Requirements
| Model Category |
Minimum Observations |
| Naive, SMA |
1+ |
| SES, Holt |
3+ |
| HoltWinters, SeasonalES |
2 × seasonal_period |
| MSTL, MFLES, TBATS |
2 × max(seasonal_periods) |
| AutoETS, AutoARIMA |
10+ (more is better) |
| Croston variants, TSB |
4+ |
| Cross-validation |
horizon × n_folds + initial_train_size |
Testing Queries
Verify generated SQL works against the actual extension:
bash .claude/skills/anofox-forecast/scripts/test-query.sh "SELECT * FROM ts_features_list() LIMIT 3"
Always test SQL before presenting it to the user. The script runs in-memory with the project's built extension.
Deep-Dive References
For full function signatures, all parameters, return columns, and detailed examples:
- Forecasting Models — All 32 models, exogenous support
- Data Preparation — Filtering, imputation, gap filling
- Statistics & Quality — 36 stats, quality scores
- Period Detection & Decomposition — 12 detection methods, MSTL, detrending
- Cross-Validation — Folds, forecasting, hydration, custom splits
- Evaluation Metrics — All 11 scalar metrics
- Feature Extraction — 117 tsfresh features
- Conformal Prediction — Distribution-free intervals
- Hierarchical — Multi-key combine, aggregate, split
- Changepoint Detection — Structural break detection
1---2name: anofox-forecast3description: Comprehensive reference for the Anofox Forecast DuckDB extension (v0.4.6). Use when working with ts_* or anofox_fcst_ts_* functions, time series forecasting in DuckDB, or the anofox_forecast extension. Provides API signatures, model selection, common workflows, and critical gotchas.4---5
6# Anofox Forecast DuckDB Extension — Cheat Sheet
7
8**Extension:** `anofox_forecast` v0.4.6 | **DuckDB:** v1.4.x+ | **Dual naming:** `ts_*` and `anofox_fcst_ts_*`
9
10## Installation
11
12```sql
13LOAD anofox_forecast;
14-- All functions available as ts_* and anofox_fcst_ts_* (identical)
15```
16
17---
18
19## Critical Gotchas
20
211. **Seasonality is NOT auto-detected.** You must pass `seasonal_period` explicitly.
22 Detect first with `ts_detect_periods_by`, then pass to forecasting.
23
242. **DO NOT chain `_by` table functions in CTEs.** Returns 0 rows silently under parallel execution.
25 Always `CREATE TABLE` between pipeline steps:
26 ```sql
27 -- BROKEN (0 rows):
28 WITH step1 AS (SELECT * FROM ts_fill_gaps_by(...))
29 SELECT * FROM ts_fill_nulls_const_by('step1', ...);
30
31 -- CORRECT:
32 CREATE TABLE step1 AS SELECT * FROM ts_fill_gaps_by(...);
33 SELECT * FROM ts_fill_nulls_const_by('step1', ...);
34 ```
35
363. **Model names are case-sensitive.** `'AutoETS'` works, `'autoets'` errors.
37
384. **`ts_cv_forecast_by` requires pre-created folds.** Input table must have `fold_id` and `split` columns
39 (from `ts_cv_folds_by` or `ts_cv_split_by`). Passing raw data throws a clear error.
40
415. **`ts_forecast_by` requires frequency as 7th positional parameter.** No default — you must specify it:
42 ```sql
43 -- WRONG: missing frequency
44 SELECT * FROM ts_forecast_by('sales', id, date, val, 'Naive', 12);
45 -- CORRECT:
46 SELECT * FROM ts_forecast_by('sales', id, date, val, 'Naive', 12, '1d');
47 ```
48
496. **Metric `_by` table macros are deprecated.** Use scalar functions with `GROUP BY`:
50 ```sql
51 -- Deprecated: SELECT * FROM ts_mae_by(...)
52 -- Use instead:
53 SELECT id, ts_mae(LIST(y ORDER BY ds), LIST(yhat ORDER BY ds)) AS mae
54 FROM results GROUP BY id;
55 ```
56
577. **Always use `ORDER BY` in `LIST()` for temporal correctness:**
58 ```sql
59 LIST(value ORDER BY date) -- correct
60 LIST(value) -- wrong: order not guaranteed
61 ```
62
63---
64
65## Three API Styles
66
67### 1. Table Macros (primary — use these)
68Operate on table names as strings. Handle grouping automatically.
69```sql
70SELECT * FROM ts_forecast_by('sales', product_id, date, revenue, 'AutoETS', 14, '1d',
71 MAP{'seasonal_period': '7'});
72```
73
74### 2. Scalar Functions
75Operate on arrays. Use with `LIST()` aggregation and `GROUP BY`.
76```sql
77SELECT product_id,
78 ts_mae(LIST(actual ORDER BY date), LIST(forecast ORDER BY date)) AS mae
79FROM results GROUP BY product_id;
80```
81
82### 3. Aggregate Functions
83Return structs. Access fields with `(result).field_name`.
84```sql
85SELECT product_id, (ts_stats(LIST(value ORDER BY date))).*
86FROM sales GROUP BY product_id;
87```
88
89---
90
91## Parameter Syntax
92
93### STRUCT (recommended)
94```sql
95MAP{'seasonal_period': '7'}
96MAP{'seasonal_periods': '[7, 365]'}
97MAP{'method': 'autoperiod', 'max_period': '28'}
98```
99
100All param values are strings (even numbers). Arrays use JSON syntax: `'[7, 365]'`.
101
102### Frequency Strings
103| Format | Examples |
104|--------|---------|
105| Polars style | `'1d'`, `'1h'`, `'30m'`, `'1w'`, `'1mo'`, `'1q'`, `'1y'` |
106| DuckDB INTERVAL | `'1 day'`, `'1 hour'` |
107| Raw integer | `'1'`, `'7'` (interpreted as days) |
108
109---
110
111## Common Workflows
112
113### 1. Basic Forecast
114
115```sql
116-- Forecast 14 days ahead with weekly seasonality
117SELECT * FROM ts_forecast_by(
118 'sales', product_id, date, revenue,
119 'HoltWinters', 14, '1d',
120 MAP{'seasonal_period': '7'}
121);
122```
123
124### 2. Data Preparation Pipeline (CREATE TABLE between steps!)
125
126```sql
127-- Step 1: Fill gaps
128CREATE TABLE gaps_filled AS
129SELECT * FROM ts_fill_gaps_by('raw_data', product_id, date, value, '1d');
130
131-- Step 2: Impute NULLs
132CREATE TABLE nulls_filled AS
133SELECT * FROM ts_fill_nulls_const_by('gaps_filled', product_id, date, value, 0.0);
134
135-- Step 3: Drop short series
136CREATE TABLE clean_data AS
137SELECT * FROM ts_drop_short_by('nulls_filled', product_id, 20);
138```
139
140### 3. Detect Seasonality → Forecast
141
142```sql
143-- Step 1: Detect
144SELECT id, (periods).primary_period
145FROM ts_detect_periods_by('sales', product_id, date, value, MAP{});
146-- Returns e.g. primary_period = 7 (weekly)
147
148-- Step 2: Forecast with detected period
149SELECT * FROM ts_forecast_by(
150 'sales', product_id, date, value,
151 'AutoETS', 14, '1d', MAP{'seasonal_period': '7'}
152);
153```
154
155### 4. Cross-Validation & Model Comparison
156
157```sql
158-- Step 1: Create folds
159CREATE TABLE cv_folds AS
160SELECT * FROM ts_cv_folds_by('data', unique_id, ds, y, 3, 12, MAP{});
161
162-- Step 2: Forecast per fold (for each model)
163CREATE TABLE cv_naive AS
164SELECT * FROM ts_cv_forecast_by('cv_folds', unique_id, ds, y, 'Naive', MAP{});
165
166CREATE TABLE cv_autoets AS
167SELECT * FROM ts_cv_forecast_by('cv_folds', unique_id, ds, y, 'AutoETS',
168 MAP{'seasonal_period': '7'});
169
170-- Step 3: Compare metrics
171SELECT 'Naive' AS model,
172 ts_mae(LIST(y ORDER BY ds), LIST(yhat ORDER BY ds)) AS mae,
173 ts_rmse(LIST(y ORDER BY ds), LIST(yhat ORDER BY ds)) AS rmse
174FROM cv_naive GROUP BY ALL
175UNION ALL
176SELECT 'AutoETS',
177 ts_mae(LIST(y ORDER BY ds), LIST(yhat ORDER BY ds)),
178 ts_rmse(LIST(y ORDER BY ds), LIST(yhat ORDER BY ds))
179FROM cv_autoets GROUP BY ALL;
180```
181
182### 5. Full Production Pipeline
183
184```sql
185-- 1. Quality check
186SELECT id, (stats).length, (stats).n_nulls, (stats).n_gaps
187FROM ts_stats_by('raw', product_id, date, value, '1d');
188
189-- 2. Prep (materialize each step!)
190CREATE TABLE step1 AS
191SELECT * FROM ts_fill_gaps_by('raw', product_id, date, value, '1d');
192
193CREATE TABLE step2 AS
194SELECT * FROM ts_fill_nulls_const_by('step1', product_id, date, value, 0.0);
195
196CREATE TABLE clean AS
197SELECT * FROM ts_drop_short_by('step2', product_id, 20);
198
199-- 3. Detect seasonality
200SELECT id, (periods).primary_period
201FROM ts_detect_periods_by('clean', product_id, date, value, MAP{});
202
203-- 4. Backtest
204CREATE TABLE cv_folds AS
205SELECT * FROM ts_cv_folds_by('clean', product_id, date, value, 5, 14, MAP{});
206
207CREATE TABLE backtest AS
208SELECT * FROM ts_cv_forecast_by('cv_folds', product_id, date, value, 'AutoETS',
209 MAP{'seasonal_period': '7'});
210
211-- 5. Evaluate
212SELECT product_id,
213 ts_mae(LIST(y ORDER BY date), LIST(yhat ORDER BY date)) AS mae,
214 ts_rmse(LIST(y ORDER BY date), LIST(yhat ORDER BY date)) AS rmse
215FROM backtest GROUP BY product_id;
216
217-- 6. Forecast
218CREATE TABLE forecasts AS
219SELECT * FROM ts_forecast_by('clean', product_id, date, value,
220 'AutoETS', 14, '1d', MAP{'seasonal_period': '7'});
221
222-- 7. Conformal intervals
223CREATE TABLE calibration AS
224SELECT * FROM ts_conformal_calibrate('backtest', value, yhat, {'alpha': 0.1});
225
226SELECT * FROM ts_conformal_apply_by(
227 'forecasts', product_id, yhat,
228 (SELECT conformity_score FROM calibration)
229);
230```
231
232---
233
234## Model Quick Reference (32 Models)
235
236### Automatic Selection (6)
237| Model | Optional Params | Best For |
238|-------|----------------|----------|
239| `AutoETS` | `seasonal_period` | Unknown patterns (default pick) |
240| `AutoARIMA` | `seasonal_period` | Unknown patterns, ARIMA family |
241| `AutoTheta` | `seasonal_period` | Unknown patterns, Theta family |
242| `AutoMFLES` | `seasonal_periods[]` | Multiple seasonalities |
243| `AutoMSTL` | `seasonal_periods[]` | Multiple seasonalities |
244| `AutoTBATS` | `seasonal_periods[]` | Multiple seasonalities |
245
246### Basic (6)
247| Model | Required | Optional | Best For |
248|-------|----------|----------|----------|
249| `Naive` | — | — | Baseline benchmark |
250| `SMA` | — | `window` (def: 5) | Smoothed baseline |
251| `SeasonalNaive` | **seasonal_period** | — | Seasonal baseline |
252| `SES` | — | `alpha` (def: 0.3) | No trend, no seasonality |
253| `SESOptimized` | — | — | Optimized SES |
254| `RandomWalkDrift` | — | — | Trend without seasonality |
255
256### Exponential Smoothing (4)
257| Model | Required | Optional |
258|-------|----------|----------|
259| `Holt` | — | `alpha`, `beta` |
260| `HoltWinters` | **seasonal_period** | `alpha`, `beta`, `gamma` |
261| `SeasonalES` | **seasonal_period** | `alpha`, `gamma` |
262| `SeasonalESOptimized` | **seasonal_period** | — |
263
264### Theta Methods (5)
265| Model | Optional |
266|-------|----------|
267| `Theta` | `seasonal_period`, `theta` |
268| `OptimizedTheta` | `seasonal_period` |
269| `DynamicTheta` | `seasonal_period`, `theta` |
270| `DynamicOptimizedTheta` | `seasonal_period` |
271| `AutoTheta` | `seasonal_period` |
272
273### State Space & ARIMA (4)
274| Model | Required | Optional |
275|-------|----------|----------|
276| `ETS` | — | `seasonal_period`, `model` |
277| `AutoETS` | — | `seasonal_period` |
278| `ARIMA` | **p**, **d**, **q** | `P`, `D`, `Q`, `s` |
279| `AutoARIMA` | — | `seasonal_period` |
280
281### Multiple Seasonality (6)
282| Model | Required | Optional |
283|-------|----------|----------|
284| `MFLES` | **seasonal_periods[]** | `iterations` |
285| `AutoMFLES` | — | `seasonal_periods[]` |
286| `MSTL` | **seasonal_periods[]** | `stl_method` |
287| `AutoMSTL` | — | `seasonal_periods[]` |
288| `TBATS` | **seasonal_periods[]** | `use_box_cox` |
289| `AutoTBATS` | — | `seasonal_periods[]` |
290
291### Intermittent Demand (6)
292| Model | Optional | Best For |
293|-------|----------|----------|
294| `CrostonClassic` | — | Sparse demand |
295| `CrostonOptimized` | — | Sparse demand |
296| `CrostonSBA` | — | Sparse demand (bias-corrected) |
297| `ADIDA` | — | Aggregate-Disaggregate |
298| `IMAPA` | — | Multiple aggregation |
299| `TSB` | `alpha_d`, `alpha_p` | Best intermittent (tunable) |
300
301---
302
303## Model Selection Guide
304
305| Data Characteristics | Recommended Models |
306|---------------------|--------------------|
307| No trend, no seasonality | `Naive`, `SES`, `SESOptimized` |
308| Trend, no seasonality | `Holt`, `Theta`, `RandomWalkDrift` |
309| Single seasonal period | `SeasonalNaive`, `HoltWinters`, `SeasonalES` |
310| Multiple seasonalities | `MSTL`, `MFLES`, `TBATS` |
311| Many zeros (intermittent) | `CrostonClassic`, `CrostonSBA`, `TSB` |
312| Unknown characteristics | `AutoETS`, `AutoARIMA`, `AutoTheta` |
313| Short series (< 20 pts) | `Naive`, `SES` |
314
315| Scenario | First Try | Alternative |
316|----------|-----------|-------------|
317| Daily retail sales | `HoltWinters` | `MSTL` |
318| Weekly financial data | `Theta` | `AutoETS` |
319| Hourly sensor data | `MFLES` | `MSTL` |
320| Spare parts demand | `CrostonSBA` | `TSB` |
321
322---
323
324## Function Quick Reference
325
326### Forecasting
327| Function | Purpose |
328|----------|---------|
329| `ts_forecast_by(table, group, date, value, method, horizon, frequency, params)` | Multi-series forecast |
330| `ts_forecast_exog_by(table, group, date, value, x_cols, future_table, future_date, future_x, model, horizon, params, freq)` | Forecast with exogenous variables |
331
332### Data Preparation
333| Function | Purpose |
334|----------|---------|
335| `ts_fill_gaps_by(table, group, date, value, freq)` | Fill missing timestamps with NULL |
336| `ts_fill_forward_by(table, group, date, value, target_date, freq)` | Extend series to target date |
337| `ts_fill_nulls_const_by(table, group, date, value, fill_val)` | Replace NULLs with constant |
338| `ts_fill_nulls_forward_by(table, group, date, value)` | Forward-fill NULLs |
339| `ts_fill_nulls_backward_by(table, group, date, value)` | Backward-fill NULLs |
340| `ts_fill_nulls_mean_by(table, group, date, value)` | Fill NULLs with mean |
341| `ts_drop_constant_by(table, group, value)` | Remove constant series |
342| `ts_drop_short_by(table, group, min_len)` | Remove short series |
343| `ts_drop_gappy_by(table, group, value, max_gap_ratio)` | Remove gappy series |
344| `ts_drop_zeros_by(table, group, value)` | Remove all-zero series |
345| `ts_drop_leading_zeros_by(table, group, date, value)` | Trim leading zeros |
346| `ts_drop_trailing_zeros_by(table, group, date, value)` | Trim trailing zeros |
347| `ts_drop_edge_zeros_by(table, group, date, value)` | Trim both edges |
348| `ts_diff_by(table, group, date, value, order)` | Compute differences |
349
350### Statistics & Quality
351| Function | Purpose |
352|----------|---------|
353| `ts_stats_by(table, group, date, value, freq)` | 36 statistics per series |
354| `ts_data_quality_by(table, group, date, value, min_len, freq)` | Quality scores (0-1) |
355| `ts_quality_report(stats_table, min_len)` | Summary quality report |
356| `ts_stats_summary(stats_table)` | Summary across all series |
357
358### Period Detection & Decomposition
359| Function | Purpose |
360|----------|---------|
361| `ts_detect_periods_by(table, group, date, value, params)` | Detect seasonal periods |
362| `ts_classify_seasonality_by(table, group, date, value, period)` | Classify seasonality type |
363| `ts_mstl_decomposition_by(table, group, date, value, periods[], params)` | MSTL decomposition |
364| `ts_detrend_by(table, group, date, value, method)` | Remove trend |
365| `ts_detect_peaks_by(table, group, date, value, params)` | Detect peaks |
366| `ts_analyze_peak_timing_by(table, group, date, value, period, params)` | Peak timing analysis |
367
368### Cross-Validation
369| Function | Purpose |
370|----------|---------|
371| `ts_cv_folds_by(table, group, date, value, n_folds, horizon, params)` | Create CV folds |
372| `ts_cv_forecast_by(folds_table, group, date, value, method, params)` | Forecast on CV folds |
373| `ts_cv_split_by(table, group, date, value, cutoff_dates[], horizon, params)` | Custom fold boundaries |
374| `ts_cv_hydrate_by(folds, source, group, date, features[], params)` | Add features to folds |
375
376### Evaluation Metrics (scalar — use with GROUP BY)
377| Function | Signature | Description |
378|----------|-----------|-------------|
379| `ts_mae` | `(LIST, LIST) → DOUBLE` | Mean Absolute Error |
380| `ts_mse` | `(LIST, LIST) → DOUBLE` | Mean Squared Error |
381| `ts_rmse` | `(LIST, LIST) → DOUBLE` | Root Mean Squared Error |
382| `ts_mape` | `(LIST, LIST) → DOUBLE` | Mean Absolute Percentage Error |
383| `ts_smape` | `(LIST, LIST) → DOUBLE` | Symmetric MAPE |
384| `ts_r2` | `(LIST, LIST) → DOUBLE` | R-squared |
385| `ts_bias` | `(LIST, LIST) → DOUBLE` | Bias (mean error) |
386| `ts_mase` | `(LIST, LIST, LIST) → DOUBLE` | Mean Absolute Scaled Error |
387| `ts_rmae` | `(LIST, LIST, LIST) → DOUBLE` | Relative MAE |
388| `ts_coverage` | `(LIST, LIST, LIST) → DOUBLE` | Interval coverage |
389| `ts_quantile_loss` | `(LIST, LIST, DOUBLE) → DOUBLE` | Quantile loss |
390
391### Conformal Prediction
392| Function | Purpose |
393|----------|---------|
394| `ts_conformal_by(backtest, group, actual, forecast, point_forecast, params)` | One-step conformal intervals |
395| `ts_conformal_calibrate(backtest, actual, forecast, params)` | Calibrate conformity score |
396| `ts_conformal_apply_by(forecasts, group, forecast_col, score)` | Apply calibrated score |
397| `ts_conformal_predict(residuals[], forecasts[], alpha)` | Array-based conformal |
398| `ts_conformal_predict_asymmetric(residuals[], forecasts[], alpha)` | Asymmetric intervals |
399| `ts_conformal_quantile(residuals[], alpha)` | Compute conformity quantile |
400| `ts_conformal_intervals(forecasts[], score)` | Apply score to array |
401| `ts_conformal_coverage(actuals[], lower[], upper[])` | Empirical coverage |
402| `ts_conformal_evaluate(actuals[], lower[], upper[], alpha)` | Full evaluation |
403| `ts_interval_width_by(table, group, lower, upper)` | Mean interval width |
404
405### Feature Extraction
406| Function | Purpose |
407|----------|---------|
408| `ts_features_by(table, group, date, value)` | Extract 117 tsfresh features |
409| `ts_features_list()` | List available features |
410
411### Hierarchical
412| Function | Purpose |
413|----------|---------|
414| `ts_combine_keys((SELECT date, val, id1, id2, ...), params)` | Combine ID columns |
415| `ts_aggregate_hierarchy((SELECT date, val, id1, id2, ...), params)` | Aggregate at all levels |
416| `ts_split_keys((SELECT uid, date, val), ...)` | Split combined keys |
417| `ts_validate_separator((SELECT id1, id2, ...), ...)` | Validate separator char |
418
419### Changepoint Detection
420| Function | Purpose |
421|----------|---------|
422| `ts_detect_changepoints_by(table, group, date, value, params)` | Detect structural breaks |
423
424### Timestamp Validation
425| Function | Purpose |
426|----------|---------|
427| `ts_validate_timestamps_by(table, group, date, expected_dates[])` | Validate timestamps exist |
428| `ts_validate_timestamps_summary_by(table, group, date, expected_dates[])` | Validation summary |
429
430### Future Value Handling
431| Function | Purpose |
432|----------|---------|
433| `ts_fill_unknown_by(table, group, date, value, cutoff, params)` | Fill unknown future values |
434| `ts_mark_unknown_by(table, group, date, cutoff)` | Mark known/unknown rows |
435
436---
437
438## Minimum Data Requirements
439
440| Model Category | Minimum Observations |
441|---------------|---------------------|
442| Naive, SMA | 1+ |
443| SES, Holt | 3+ |
444| HoltWinters, SeasonalES | 2 × seasonal_period |
445| MSTL, MFLES, TBATS | 2 × max(seasonal_periods) |
446| AutoETS, AutoARIMA | 10+ (more is better) |
447| Croston variants, TSB | 4+ |
448| Cross-validation | horizon × n_folds + initial_train_size |
449
450---
451
452## Testing Queries
453
454Verify generated SQL works against the actual extension:
455
456```bash
457bash .claude/skills/anofox-forecast/scripts/test-query.sh "SELECT * FROM ts_features_list() LIMIT 3"
458```
459
460Always test SQL before presenting it to the user. The script runs in-memory with the project's built extension.
461
462---
463
464## Deep-Dive References
465
466For full function signatures, all parameters, return columns, and detailed examples:
467
468- [Forecasting Models](references/forecasting-models.md) — All 32 models, exogenous support
469- [Data Preparation](references/data-preparation.md) — Filtering, imputation, gap filling
470- [Statistics & Quality](references/statistics-and-quality.md) — 36 stats, quality scores
471- [Period Detection & Decomposition](references/period-detection-and-decomposition.md) — 12 detection methods, MSTL, detrending
472- [Cross-Validation](references/cross-validation.md) — Folds, forecasting, hydration, custom splits
473- [Evaluation Metrics](references/evaluation-metrics.md) — All 11 scalar metrics
474- [Feature Extraction](references/feature-extraction.md) — 117 tsfresh features
475- [Conformal Prediction](references/conformal-prediction.md) — Distribution-free intervals
476- [Hierarchical](references/hierarchical.md) — Multi-key combine, aggregate, split
477- [Changepoint Detection](references/changepoint-detection.md) — Structural break detection