Skip to content

@vizcrush/ai ​

AI-native data analysis primitives. Anomaly detection, changepoint detection, auto-configuration of downsampling/binning, data summarization, shape embeddings, and natural-language query parsing — all running in pure JavaScript so they work everywhere.

These functions are designed to be cheap to call so you can sprinkle them throughout your app: detect anomalies on every new batch of data, ask "what algorithm should I use?" before each render, generate a summary string to feed to an LLM, etc.

Statistical contract ​

The AI module describes and standardizes the supplied analysis window; it does not estimate an unseen population. Variance, standard deviation, Z-score thresholds, and derived features therefore use population variance with an n denominator. This intentionally differs from @vizcrush/aggregate's sample-variance summary (n - 1 denominator).

All numeric AI functions treat NaN, Infinity, and -Infinity as missing observations:

  • single-series functions omit each non-finite value;
  • paired (x, y) functions omit the whole pair when either coordinate is non-finite;
  • anomaly and changepoint indices still refer to positions in the original input;
  • all-non-finite input behaves like empty input.

The two packages retain separate implementations because their numerical contracts are different.

Import ​

typescript
import {
  detectAnomalies,
  detectChangepoints,
  autoOptimize,
  summarize,
  summarizeForLLM,
  parseDataQuery,
  computeShapeVector,
  shapeSimilarity,
} from "@vizcrush/ai";

detectAnomalies(data, sensitivity?) ​

Detect outliers using MAD (Median Absolute Deviation) with modified Z-scores. Returns anomalies classified by type.

typescript
const anomalies = detectAnomalies(data, /* sensitivity */ 3.5);
// [
//   { index: 142, value: 98.7, zScore: 4.2, type: "spike" },
//   { index: 305, value: 12.1, zScore: -3.9, type: "dip" },
//   { index: 580, value: 54.0, zScore: 2.1, type: "shift" },
// ]

Parameters:

  • data: Float64Array — input series
  • sensitivity: number (default 3.5) — modified Z-score threshold. Lower → more sensitive (more anomalies flagged). Typical range: 2.5 (very sensitive) to 5.0 (only egregious outliers).

Anomaly types:

  • spike — local maximum well above the rolling median
  • dip — local minimum well below the rolling median
  • shift — sustained step change, detected by a regime change in the rolling mean

Why MAD instead of standard Z-score? MAD is robust to outliers — a single huge spike doesn't inflate the threshold. Standard (value - mean) / stddev is biased by the very anomalies you're trying to detect.

detectChangepoints(data, minSegment?) ​

CUSUM (Cumulative Sum Control Chart) changepoint detection. Returns the indices where the underlying process appears to shift mean.

typescript
const cps = detectChangepoints(data, /* minSegment */ 50);
// [200, 750] — two structural breaks

Parameters:

  • data: Float64Array — input series
  • minSegment: number (default 10) — minimum length of a segment between changepoints. Larger → fewer, more meaningful changes; smaller → more sensitive.

Use this for:

  • Regime detection in financial time series
  • Detecting when a sensor's drift changes
  • Auto-segmenting a dataset before per-segment analysis

autoOptimize(x, y, screenWidth?) ​

Analyze a dataset and recommend the best downsampling algorithm + parameters. Used internally by some MCP tools and intended to be called before every render so you always pick the right strategy.

typescript
const config = autoOptimize(x, y, /* screenWidth */ 1920);
// {
//   algorithm: "lttb" | "minmax_lttb" | "m4",
//   targetPoints: 1920,
//   binResolution: 0,             // > 0 if scatter binning recommended
//   spatialIndex: "none" | "quadtree",
//   streaming: false,
//   estimatedSpeedup: 520,        // data-reduction factor vs raw (n / targetPoints)
//   reasoning: "Spiky data detected — MinMax-LTTB preserves extrema better.",
// }

Heuristics it uses:

  • Monotonicity check — strictly monotonic x → time-series, otherwise scatter
  • Spike ratio — count(|y - median| > 3*MAD) / n > 0.1 → spiky → recommend MinMax-LTTB
  • Dataset size — > 500K and scatter → recommend a quadtree
  • Screen width — sets targetPoints to screenWidth (or screenWidth * 4 for M4)

summarize(x, y) ​

Generate a structured summary of an (x, y) series. Useful for dashboards and as input to LLM-based analysis.

typescript
const summary = summarize(x, y);
// {
//   trend: "increasing" | "decreasing" | "stable" | "volatile",
//   trendSlope: 0.42,
//   range: { min: 12.0, max: 98.7 },
//   distribution: { mean: 54.3, stdDev: 18.2, skewness: 0.3 },
//   spikeCount: 7,
//   anomalyCount: 3,
//   summary: "1.2M-point time series, generally increasing (slope 0.42). 7 spikes detected.",
// }

The text summary is a one-line natural-language description suitable for displaying directly or passing to an LLM as context.

summarizeForLLM(x, y) ​

Returns a longer natural-language string optimized for LLM context windows. Includes shape, distribution, anomaly summary, and recommendations.

typescript
const text = summarizeForLLM(x, y);
// "This dataset contains 1,200,000 points spanning x=[0, 1199999], y=[12.0, 98.7].
//  The series is generally increasing with slope 0.42 (R² = 0.78). Distribution is
//  mildly right-skewed (skewness 0.3). 7 spikes and 3 anomalies were detected.
//  Recommended visualization: line chart with MinMax-LTTB downsampling to 1920 points."

await llm.chat({ system: text, user: "What's notable about this data?" });

parseDataQuery(query, dataInfo) ​

Parse simple natural-language data queries. Used by the MCP server to translate user requests into algorithm calls.

typescript
const result = parseDataQuery("show me anomalies above 100 in the last hour", {
  length: 1_000_000,
  hasTimestamps: true,
});
// {
//   operation: "filter+anomaly",
//   params: { above: 100, range: { unit: "hour", count: 1 } },
//   description: "Filter values > 100 within the last hour, then run anomaly detection",
// }

Patterns it recognizes:

PhraseOperation
"above N", "greater than N", "over N", "exceeding N", "spikes above N"Filter > N
"below N", "less than N", "under N"Filter < N
"last hour", "last 5 minutes", "last day"Time range filter
"anomalies", "outliers", "unusual"Anomaly detection
"downsample to N", "reduce to N", "N points"Downsample to N
"trend", "slope", "direction"Trend analysis

It's intentionally a small rule-based parser, not an LLM call — fast, deterministic, and zero-cost. For more sophisticated NLP, route the query through your LLM and let it call MCP tools instead.

computeShapeVector(data, dimensions?) and shapeSimilarity(a, b) ​

Compute a fixed-size feature vector that captures the shape of a time series, independent of length and absolute scale. Then compare two series with cosine similarity.

typescript
const vec1 = computeShapeVector(seriesA);
const vec2 = computeShapeVector(seriesB);

const similarity = shapeSimilarity(vec1, vec2);
// number in [0, 1] — 1 means identical shape, 0 means orthogonal

What's in the vector (default 16 dimensions):

DimensionsFeature
0–7Histogram (8 bins, normalized)
8Trend slope (sign + magnitude)
9Volatility (coefficient of variation)
10Spike ratio
11–14Autocorrelation at lags 1, 2, 4, 8
15Mean (normalized)

Use shape vectors for:

  • "Find similar charts" — compare a query series against a library of reference patterns
  • Clustering — group dashboards by data shape
  • Anomaly retrieval — find historical periods that look like the current one

All sync, all the time ​

Every function in @vizcrush/ai is synchronous and runs in pure JavaScript. They're cheap enough to call on every render or every new data batch, and they don't require WebAssembly. There's a Rust crate (vizcrush-ai) planned for the heavier cases, but the JS implementations are already fast enough for most use cases.

See also ​

Released under the MIT License.