# augurs Adapter
On-graph time-series analysis backed by the [`augurs`](https://docs.rs/augurs)
toolkit. Provides six transform operators over sliding windows:
| `augurs_forecast` | `f64` | `AugursForecast` (point + intervals); ETS or MSTL |
| `augurs_outlier` | `Vec<f64>` | `AugursOutliers`; MAD or DBSCAN detector |
| `augurs_changepoint` | `f64` | `AugursChangepoints` (BOCPD indices) |
| `augurs_seasons` | `f64` | `AugursSeasons` (periodogram periods) |
| `augurs_dtw` | `Vec<f64>` | `AugursDistanceMatrix` (pairwise DTW) |
| `augurs_cluster` | `Vec<f64>` | `AugursClusters` (DBSCAN labels via DTW) |
The `Vec<f64>` operators take one reading per series per tick.
## Module Structure
```
augurs/
mod.rs # Module doc, value types, transpose_window() helper
forecast.rs # ETS + MSTL forecasting
outlier.rs # MAD + DBSCAN outlier detection
changepoint.rs # Bayesian online changepoint detection
seasons.rs # periodogram seasonality detection
dtw.rs # dynamic time warping distance matrix
cluster.rs # DBSCAN clustering over the DTW distance matrix
CLAUDE.md # This file
```
## Key Design Decisions
- **Not a service adapter.** augurs is a pure-Rust compute library — there is no
endpoint, no Docker container, and no `async`. Consequently there is **no
`augurs-integration-test` feature**; coverage lives in `#[cfg(test)]` blocks in
each file and runs under `--features augurs`. This is the skill's "Option C".
- **Transform, not pub/sub.** The skill's `_sub` / `_pub` verbs do not fit a
computation library, so the adapter exposes fluent stream operators mirroring
core operators like `.window(...)` and `.fold(...)`.
- **Models are fitted inside `cycle()`.** Pure CPU work on the single-threaded
graph engine — no locks, no I/O, so the "no locks on the graph path" invariant
holds. It is not free, though: each cycle refits/recomputes over the whole
window (ETS model search, DTW is `O(n² · window²)`, BOCPD re-scans the window).
Throttle the input with `.sample(...)` / `.throttle(...)` upstream if a fresh
result every tick is too expensive.
- **Multi-series transpose.** The `Vec<f64>` operators buffer the last `window`
ticks and transpose them into aligned per-series columns via the shared
`transpose_window()` helper in `mod.rs`. Short samples are forward-filled so
columns stay equal length; a series that first appears part-way through the
window has its leading gap back-filled with its own first value (never a
fabricated `0.0`, which the detectors would read as a large deviation).
- **Model / detector choice via config, not new operators.** `augurs_forecast`
takes an `AugursForecastModel` (`Ets` | `Mstl{periods}`) and `augurs_outlier`
an `AugursOutlierDetector` (`Mad` | `Dbscan`), keeping one operator per
concern. `AugursForecastConfig::mstl(periods)` and
`AugursOutlierConfig::dbscan(...)` are the ergonomic constructors.
- **Warm-up floors.** ETS needs >~8 points (floored at 12). MSTL/STL cannot
decompose fewer than **two full seasonal periods**, so the forecast floor
becomes `2 * max_period + 1` for MSTL. When the requested `window` is below a
model's floor the node grows its effective window to the floor so it still
warms up and emits (rather than silently never ticking). `augurs_dtw` /
`augurs_cluster` need ≥2 series for a distance matrix; `augurs_outlier` (MAD)
runs with a single series.
- **BOCPD index 0 is dropped.** Bayesian online changepoint detection always
reports index 0 (the window start) as a changepoint; the node filters it out
so only genuine internal regime changes are emitted.
- **augurs error types are not all `Send + Sync`.** `augurs::outlier::Error`
cannot flow through `anyhow::Context`, so the outlier node renders it with
`anyhow::anyhow!("...: {e}")`. The ETS/MSTL errors *are* `Send + Sync` and use
`.context(...)`.
## Gotchas
- Pinned to `augurs = "0.10.2"` with `default-features = false` and the sub-
features `ets, mstl, outlier, changepoint, seasons, dtw, clustering`. Bumping
the version may move these APIs. **Prophet is deliberately excluded** — it
needs a bundled Stan toolchain (cmdstan/wasmstan), a heavy external build that
does not fit a lightweight pure-Rust adapter.
- MAD divides by the per-series median, so series whose median is `0.0` surface a
"division by zero" error from augurs. Feed it positive-scale data.
- DBSCAN outlier detection needs **at least three** series to form a cluster;
with fewer it reports nothing.
- `sensitivity` must be in `(0, 1)`; an out-of-range value panics at wiring time
(in the operator, before the graph starts), which is acceptable for a factory.
## Pre-Commit Requirements
```bash
cargo fmt --all
cargo clippy --workspace --all-targets --all-features
cargo test -p wingfoil --features augurs adapters::augurs
```