Skip to content

About

Local, inspectable time-series anomaly evidence for Python and the command line

Topics

Resources

Contributing

Security policy

Stars

3 stars

Watchers

0 watching

Forks

Latest commit

 

History

25 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Anomalyzer

CI Python 3.11–3.13 License: MIT

What changed, compared with what should have happened, and what can we conclude? Anomalyzer answers those questions with local, inspectable time-series evidence through a CLI, Python API, and read-only MCP server.

It builds expectations from earlier observations, accounts for trend and seasonality, and detects both extreme points and sustained residual patterns. Declared relationships expose changes such as falling conversion or rising unit cost even when the source metrics look ordinary. Every finding can be traced to its observations, reference, calculation, and limitations.

Analysis runs at the supplied sample cadence, independently of how an application aggregates its charts. Incomplete periods stay visibly incomplete; evidence maturity and caller-supplied policy determine eligibility for further action. It runs locally and requires no hosted service or model API. Scheduling, notification delivery, and operational actions belong to the calling application.

Version 0.4.0 also compares populations of numeric or categorical observations, including shape changes at an unchanged mean. Use compare_distributions for explicit populations and monitor_distributions for bounded causal replay with a frozen reference and a declared repeated-testing budget. See chapters 25–26 and the distribution API / UsageTap handoff.

Version 0.5.0 adds caller-declared timezone and daily/weekday reference matching through compare_seasonal_distributions. It compares equivalent progress through scheduled windows and reports insufficient history or an explicit fallback. See chapter 27 and the seasonal selection handoff.

Note

Anomalyzer is an alpha release. Validate thresholds and detector behavior on representative historical data before using its output for operational decisions.

Quick start

For application builds without a source checkout, use a pinned wheel from GitHub Releases. The release and library-consumer guide explains the first release, dependency hashes, upgrades and Linux/Python 3.12 bundling for Guard. Version 0.5.0 extends the sparse-data progression in chapters 14–24 with population comparisons in chapters 25–26 and explicit seasonal matching in chapter 27. Tagged artifacts are published after the workflow passes and the owner seals the draft.

Install from a checkout with Python 3.11–3.13:

python -m pip install -e .
anomalyzer analyze examples/spike.csv --time timestamp --value calls \
  --frequency 1d --season-length 7 --format json

Or call the Python API:

import json
from anomalyzer import analyze

with open("examples/spike.json", encoding="utf-8") as stream:
    result = analyze(json.load(stream))

print(result.model_dump_json(indent=2))

The checked-in single-series examples cover ordinary behavior, spikes, drops, growth, sustained shifts, and explicit regime resets. The agentic evidence examples cover ratios, lagged responses, explicit policy rules, and incomplete-data gating.

Use the anomaly-detection skill

The anomaly-detection skill lets an agent run Anomalyzer and explain its structured evidence in ordinary language. Open a local agent session at this repository's root with the skill available, and install the CLI using the quick start. The skill looks for anomalyzer on PATH, then the repository's .venv executable, then python -m anomalyzer. If none works, it reports the missing prerequisite; it does not install packages automatically.

Copy the prompts below into the agent chat. They explicitly invoke $anomaly-detection; in another Agent Skills host, select or ask for the anomaly-detection skill. These examples use checked-in synthetic fixtures and reproducible detector results, not production incidents.

Explain an isolated spike

Use $anomaly-detection to analyze examples/spike.csv. The time column is
timestamp, the value column is calls, the cadence is 1d, and the seasonal lag
is 7 samples. Explain what changed, when, and how the observed value compares
with its expectation. Tell me whether the baseline is trustworthy and what
to investigate next. Return a concise text report.

The point trigger is March 7, 2026 (sample 65): 135.2 calls against an expectation of about 94.875. The default robust history prevents a second point trigger one week later. Reference readiness is caution, so the report should explain its reasons and qualify the comparison. It should distinguish the point episode from overlapping residual patterns rather than count them as separate incidents.

To analyze your own CSV, replace the path and column names and supply its actual cadence and known seasonal lag. Keep one entity and unit per series; missing values remain missing. For several related metrics, prepare a schema-1.1 JSON request with explicit relationships, as in the conversion example below.

Find a sustained change without an extreme point

Use $anomaly-detection to analyze examples/location_shift_up.json with the
settings already in the request. Check for a sustained upward change even if
no individual point triggers. Explain the first detection, overlapping pattern
evidence, and baseline limitations. Do not infer a reset point.

This example has zero point flags. The possible upward location shift first signals through Nelson Rule 5 at sample 61, followed by CUSUM at 62, Rule 6 at 63, and Rule 2 at 68. These rules describe overlapping evidence of the same sustained behavior. The report should retain the request's lag of 30, disclose caution readiness, and avoid treating detection as a confirmed incident or a reviewed new regime. See chapter 7.

Check whether orders keep pace with visits

Use $anomaly-detection to analyze examples/agentic/conversion.json. Compare
orders, qualified visits, and the declared orders / qualified_visits ratio.
Explain any relationship departure, show the source values behind it, and
distinguish numerical evidence from possible causes.

At sample 14 (January 15), 8 orders / 130 qualified visits = 6.15% conversion, against the relationship's 10% expectation. The ratio has a point trigger; neither source metric has one. This short fixture has three calibration residuals and readiness is not_assessed. A useful report preserves that limitation and proposes investigation questions without asserting a checkout failure. See chapter 12.

Investigate an explicit rule with short history

Use $anomaly-detection to analyze examples/agentic/unit-cost.json. Explain the
unit-cost rule, the values that crossed it, and the supplied policy's eligibility
decision. Distinguish an exact rule violation from a calibrated anomaly and
from permission to take operational action.

On March 4 (sample 3), $11 / 70 successful requests = about $0.157 per request, above the caller's $0.13 maximum. The result reports insufficient_history for statistical detection but retains the exact criterion_violation. The supplied policy makes that finding action-eligible; this does not establish cause or impact, or authorize the agent to change a service.

Check incomplete data before interpreting a drop

Use $anomaly-detection to analyze examples/agentic/incomplete-usage.json.
Check whether the apparent usage drop has complete supporting inputs. Explain
what evidence remains available, what is unavailable, and what to check next.

Usage falls to 20 events on April 4, but that day's ingestion-completeness sample is explicitly incomplete. The declared joint relationship is unavailable with reason missing_or_incomplete_condition_source; the overall run is partial (CLI exit 1). The usage series still supplies an early departure_candidate, which is not a calibrated trigger. The report should recommend checking ingestion and avoid declaring either normal health or a confirmed usage collapse.

Ask for a chart when you want one

The skill returns text by default. After the spike analysis, request a small chart explicitly:

Chart the spike result as a quick inline plot. Show observed and available
expected values, training/calibration shading, and triggered point markers.

For a more detailed figure:

Create a publication-quality interactive figure for the spike result with
hover details, a residual panel, pattern lanes, and visible readiness limitations.

Chart features depend on the host. The skill uses the host's visualization capability when available; its dependency-free static SVG fallback supports a single dataset. The numerical analysis and text explanation remain useful when visualization is unavailable.

Choose the question, then the recipe

Question Implemented capability Where to start
Did this series depart from its seasonal level or fitted growth? seasonal-residual-v1 (default): robust seasonal references with training-fitted trend and frozen calibration The progression
Should the expected level or growth rate evolve after every observation? adaptive-seasonal-v1: a causal rolling expectation with frozen calibration Chapter 4
What is happening within the day, while the full day is unfinished? multi-resolution-v1: finalized subperiods and completed fixed 24-hour aggregates Chapter 11
Did conversion, a difference, unit cost, or a delayed response change? Schema 1.1: up to four datasets and four declared relationships, using either single-resolution recipe Chapter 12
What evidence can an analyst or agent investigate? Early assessments, explicit rules, cases, policy replay, and MCP evidence drilldown Chapter 13

Schema 1.0 is the single-series contract. Schema 1.1 adds named datasets, relationships, assessments, policy, and cases; it also works with one dataset and no relationships. Both use the same analysis core through CLI, Python, and MCP. Sparse schema-1.1, multi-resolution and identified events are described in chapters 14–24. The legacy schema-1.0 multi-resolution profile does not support reset_points; both other recipes support explicit resets.

Capability maps

The CapabilityKit maps describe Anomalyzer's observable behavior, acceptance criteria, implementation evidence, and dependencies.

The story map splits the current product into eight deliverable lanes:

Lane Reviewable outcome
Basic CLI anomaly detection Supply a series, establish evidence support, find spikes and drops, and read the result from files or stdin.
Trend and seasonal analysis Refine ordinary growth and repeating cycles, protect comparisons, follow evolving growth, and identify sustained changes.
Incomplete-period monitoring Keep unfinished periods visible while comparing finalized subperiods.
Related-metric analysis Align declared sources and investigate conversion, unit cost, metric gaps, and delayed responses.
Evidence and review policy Interpret early evidence and baseline quality, investigate cases, retain partial results, and review eligibility under stated rules.
Python integration Embed the same validated analysis in a local application.
Anomaly detection Skill Ask an agent for an evidence-grounded explanation and optionally a chart.
Read-only MCP server Connect an agent to local analysis and drill into findings and evidence.

Start with Basic CLI anomaly detection, then choose the analysis extensions or integration surface needed for your workflow. Capabilities have one primary lane; dependencies show shared prerequisites across lanes. CapabilityKit calls these planning slices releases; the eight lanes above describe implemented deliverables. Additional Future - ... lanes contain proposed work marked planned, without release dates or delivery commitments. Capability titles name user outcomes; summaries and acceptance criteria describe the precise behavior and its limits. Review policy determines eligibility; the caller owns notification delivery and operational actions. The Skill supplies agent instructions, while chart features depend on the host and its static fallback supports one dataset.

The static preview below links to the hosted interactive dependency map. Coverage reflects saved capability reviews, not detection accuracy. The viewers also work locally: open .capabilities/story-map-viewer.html or .capabilities/dependency-viewer.html from a checkout.

Anomalyzer capability dependency graph

Future direction

The story map also includes 18 unimplemented stories across six future lanes. Each story has a behavior contract, four acceptance criteria, proposed automated coverage scenarios, and one manual review focus. Verification scenarios are planning guidance: implementation references, runnable checks, and saved review evidence will be added when the behavior is implemented. The planned stories therefore have explicit missing-verification signals in CapabilityKit.

Future lane Ordered user stories
Historical evaluation Replay the evidence available at each historical cutoff; measure detection timing against reviewed incidents; compare settings on held-out history.
Calendar and data readiness Compare local calendar days and weeks; identify late or missing updates; inspect related metrics within unfinished periods.
Business-aware expectations Model daily and weekly cycles together; interpret count-based rates using supporting volume; review proposed baseline changes before accepting a reset.
Continuous analysis Save and restore local analysis state; append finalized samples; reconcile late or corrected data with a visible evidence history.
Cohort investigation Analyze bounded entity batches separately; locate additive contributors to a total change; separate within-cohort rate changes from population mix.
Investigation memory Record reviewed outcomes beside original evidence; export portable investigation bundles; retrieve comparable reviewed cases with explicit matching reasons.

Suggested delivery starts with Historical evaluation so later changes can be judged on representative replay. Calendar and data readiness addresses whether comparisons use the right periods and current sources; Business-aware expectations then improves the reference for those comparisons. Continuous analysis carries the evidence forward between caller-managed runs. Cohort investigation broadens diagnosis, while Investigation memory preserves what reviewers learn. These are proposed priorities; within each lane, story order shows the progression toward a reviewable user outcome.

The direction keeps Anomalyzer local and evidence-focused. Calling applications continue to own schedules, notification delivery, access control, and operational actions. Proposed change points and similar historical cases help investigation; they do not automatically reset a baseline or establish a cause.

The progression

An anomaly is only a departure from an expectation. The useful detector grows by making that expectation more realistic, one assumption at a time. The progression starts with trend and seasonality, adds protection and review, then carries the evidence into incomplete periods, relationships, and agent investigation. The twelve figures use synthetic data and real detector output; most build on the checked-in examples. They demonstrate mechanics, not real-world accuracy.

1. Start with the trend

With no repeating season, a flat average is the wrong reference for a series that is steadily growing. Ordinary later values drift farther from that average and can look exceptional simply because time passed. A fitted linear trend turns the growth into the expectation, leaving the two introduced spikes visibly distinct.

A growing non-seasonal series compared with a flat average and fitted linear trend

2. Add seasonality to linear growth

Real series often grow while repeating a weekly shape. Comparing Monday with the overall average still confuses seasonality with change. Anomalyzer instead compares each observation with the same point in the previous season and adds the fitted linear change across that seven-day gap. The red points are the introduced changes; the orange points show the seasonal-naive echo that appears when each changed value becomes the reference one week later. This chapter deliberately uses outlier_handling=include; chapter 8 adds the robust default that prevents the echo.

Weekly seasonality on a growing linear trend with introduced changes and one-week echoes

3. Let growth compound

A straight line adds the same amount every day. Compound growth adds the same rate, so the absolute increase gets larger over time. Forced linear trend leaves a widening residual and flags 34 samples in this example. With five seasonal cycles in its training window, the compound model follows the changing rate and reduces the result to four flags: the two introduced changes and their two one-week echoes. As in chapter 2, the comparison uses outlier_handling=include so trend choice is isolated from outlier handling.

The same compound-growth series scored with linear and compound trend models

4. Update the expectation one step at a time

A frozen trend is easy to audit, but it can mistake ordinary acceleration for a long incident. adaptive-seasonal-v1 recomputes each expectation from only the history available before that sample. It takes the median of recent pairwise slopes, projects up to 56 earlier samples forward, and blends the median of matching seasonal phases at 80% once three are available. The upper panel shows the frozen compound model drifting far above the gradually accelerating series. The lower panel shows the rolling reference following the changing growth rate while still exposing the isolated change at sample 80.

The expectation rolls, but the residual calibration does not. This keeps scores comparable while Nelson location rules and CUSUM accumulate moderate misses that may matter before one point crosses the configured threshold. Robust mode keeps the first two consecutive extreme misses in one direction out of the reference; a persistent third miss is admitted so a real regime can be followed after it has already produced evidence.

A gradually accelerating seasonal series compared with frozen and one-step rolling expectations

5. Treat discontinuities as new regimes

One continuous trend cannot explain a series whose level and direction change at known boundaries. Without that context, both later regimes remain departures and 100 consecutive samples are flagged. With reviewed change points at positions 50 and 100, seasonal history, trend fitting, and calibration restart inside each segment. The three regimes are then modeled independently and no samples flag. Resets are always explicit: Anomalyzer does not automatically normalize an anomaly run by declaring a new regime. This chapter holds outlier handling at include; chapter 9 combines robust handling with the reviewed-reset workflow.

A stepped series before and after explicit change-point resets

6. Discover the season

Sometimes value-only positional data arrives without a known lag. Anomalyzer detrends an initial prefix, ranks autocorrelation candidates, and requires the same candidate to win in its earliest three-cycle window. Here lag 7 is confirmed from positions 0–27; lags 14 and 21 are harmonics of the same weekly cycle. Inference is a conservative suggestion—an explicit domain setting still takes precedence.

A repeating weekly series and its ranked season-length candidates

7. Detect a sustained location shift

Not every meaningful change contains an individually extreme observation. In this example, the residual process moves upward to a steady +2.28 standard deviations, so no point crosses the three-sigma anomaly threshold. The selected Nelson rules still expose the concentration on one side of the frozen residual centerline: Rule 5 first signals at sample 61, CUSUM at 62, Rule 6 at 63, and Rule 2 at 68. These are described as possible upward location shifts relative to the seasonal/trend expectation. They do not claim that the full distribution changed, identify a cause, or automatically establish a new operating regime. The downward example has symmetric behavior. The CLI reports zero point-anomaly episodes and several pattern findings for this example. The overlapping shift rules describe the same sustained behavior; their count is not a count of separate incidents.

Standardized residuals showing Nelson location-shift rules before any point exceeds three sigma

8. Protect the model from an incident

An extreme observation is still real evidence even when it should not define the future baseline. The robust default first scores and preserves the actual point, then substitutes its pre-anomaly expectation only in the model's future seasonal reference history. Trend training uses a bounded-influence residual screen and a clean refit; calibration removes only extreme residual contamination before its mean and standard deviation are frozen. The upper panel shows the include comparator: the March 7 spike becomes March 14's seasonal reference and creates a false downward echo. The lower panel shows the default: March 7 remains a flagged, auditable actual, but March 14 compares with the protected model reference and does not flag.

The same isolated spike with include-all history and robust model-only replacement

9. Keep regime changes explicit

Robust handling must not turn “exclude anomalies” into “silently choose a new normal.” In the upper panel a persistent level shift remains a departure because each extreme point is prevented from contaminating later seasonal references. In the lower panel external review establishes a reset at position 60. Seasonal history, trend fitting, and calibration then rebuild inside the new segment; early post-reset evidence is intentionally non-triggering until the new regime has enough history. Detection can suggest review, but only an explicit entry in reset_points changes the operating regime.

A persistent level shift kept abnormal until a reviewed reset rebuilds the baseline

This example uses the default recipe. Its seasonal references can gradually absorb moderate shifts, while extreme shifts can remain flagged. The adaptive recipe admits the third consecutive same-direction extreme miss into its rolling history. That can change future expectations, but it does not declare a new regime or rebuild calibration; an explicit reset still does both.

10. Check whether the reference deserves trust

Detection is only as useful as the reference behind it. Each method reports supported, caution, or not_assessed readiness using training and calibration data. This checks reference quality without allowing later anomalies to change the assessment; it does not measure detection accuracy.

For example, this fixed random series has no repeating seven-sample season or selected trend. It produces no point-anomaly episodes and no pattern findings, but the reference-quality check still warns that the declared seasonal baseline is a poor fit:

import random
from anomalyzer import analyze

rng = random.Random(2)
values = [100 + rng.gauss(0, 10) for _ in range(90)]
result = analyze(values, {"season_length": 7})
readiness = result.methods[0].diagnostics["detection_readiness"]
training_pattern = result.methods[0].diagnostics["segments"][0][
    "detection_readiness"]["training_pattern"]
print(len(result.observations), len(result.anomaly_patterns), readiness["status"])
print(training_pattern["trend_selected"], training_pattern["seasonality"]["status"])

The two lines print 0 0 caution and none weak_or_unstable. The warning also notes that this modeled reference has higher calibration MAE than the constant training median. Zero findings in one random draw do not establish normality or a reliable alert rate. See detection readiness for how this assessment is calculated.

A random series with zero findings but caution readiness, and higher calibration error for its seasonal reference than for a constant training median

11. Monitor before the day is finished

A daily chart should not force an application to wait until midnight to inspect new evidence. But a partial day's total should not be compared with complete days as though it were a collapse. multi-resolution-v1 separates those questions: finalized subperiods are scored at their native cadence, and only completed fixed 24-hour aggregates enter the completed-period analysis.

For hourly data, the default seasonal references are a week of hourly samples and a week of completed daily aggregates. A trailing partial aggregate remains visible with links to the intraday evidence available so far. An unfinished source sample can also be marked incomplete and excluded from both views. sum, mean, and last aggregation and the fixed-period anchor are configurable. These two views share observations and provide correlated evidence. See incomplete periods and multi-resolution analysis for the request format and fixed UTC-period semantics.

The figure shows a synthetic hourly spike in the 09:00 UTC sample. At noon, 12 finalized hours have evidence, while the unfinished daily sum remains visible and excluded from completed-day analysis. The two views share the same data; they are not independent confirmation.

An hourly spike detected during an unfinished day, with the partial daily total kept outside completed-day analysis

12. Look between the metrics

Individual metrics can each look ordinary while their relationship changes. In the conversion example, neither orders nor qualified visits has a point trigger, but orders / qualified_visits does at sample 14. The useful question has moved from “are these volumes unusual?” to “are orders keeping pace with qualified visits?”

Aligned visits, orders, and conversion charts: neither source metric has a point trigger, but conversion falls from 10 percent to 6.15 percent and triggers at sample 14

This compact example has three calibration residuals. Its numerical criterion triggers, but reference readiness is not_assessed; the figure demonstrates the declared ratio calculation and detector behavior rather than a reliable alert rate.

Schema 1.1 accepts up to four named datasets and four explicit relationships. Every dataset declares a non-empty entity mapping; cases for different entities remain separate.

Relationship Calculation What it exposes
ratio numerator[t] / denominator[t] Conversion, unit cost, or another declared rate
difference minuend[t] - subtrahend[t] A gap between quantities with identical declared units
normalized_residual (observed[t] - expected[t]) / scale Departure from a supplied expectation using a positive caller-supplied scale
lagged_response response[t] - coefficient * predictor[t-lag] Failure to match a declared delayed response; coefficient is supplied or fitted once on a declared training prefix
joint_condition all(...) or any(...) of two to four threshold comparisons A declared combination of metric conditions, represented as 0 or 1

Related sources must have identical UTC-normalized timestamps, the same declared cadence, matching entity metadata, and unique source timestamps. Lag is measured in samples. Anomalyzer does not search for relationships or lags, interpolate, resample, convert units, or turn unavailable values into zero. A zero ratio denominator is unavailable by default, or an error when explicitly configured. Each derived value carries its formula, parameters, source timestamps, and original source indexes in lineage.

import json
from anomalyzer import analyze_relationships

with open("examples/agentic/conversion.json", encoding="utf-8") as stream:
    result = analyze_relationships(json.load(stream))

relationship = result.relationship_results[0]
print(relationship.assessments[-1].classification)
print(relationship.lineage[-1].source_indexes)

The agentic examples continue with a delayed response, a unit-cost rule, and completeness gating. Missing or incomplete relationship inputs yield unavailable evidence with a reason; valid sibling results remain available if another dataset or relationship fails. Check the run and target statuses before interpreting findings. Unavailable evidence does not establish that a metric is normal.

13. Turn evidence into an investigation

A numerical departure becomes useful when a caller can explain what is known, inspect its support, and decide what review is appropriate. Schema 1.1 keeps these dimensions separate:

Dimension Meaning
Calculation certainty The arithmetic or rule crossing supported by the supplied values
Evidence maturity observation_only, early, provisional, or calibrated
Evidence strength Magnitude under the named reference; not an incident probability
Claim scope What the numbers establish and explicitly do not establish
Review state Defaults to unreviewed; the contract also represents expected_change, confirmed_incident, or new_regime

One observation is recorded as an observation; a second can establish a change; later values outside the preceding range can become an early departure_candidate. These descriptive comparisons remain available before statistical calibration, but candidates are non-triggering and cannot create cases. A supported_departure means a configured calibrated numerical criterion was met. A criterion_violation establishes that a caller-supplied rule was crossed; it can be useful even without enough history for calibration.

Datasets and relationships support three explicit rule types: acceptable_range, maximum_absolute_change, and maximum_relative_change. Action policy defaults to disabled. allow_explicit_rules permits rule violations; allow_calibrated_departures permits supported departures. By default, notification eligibility requires action eligibility; with notification_requires_action=false, a met criterion can be notification-eligible without being action-eligible. Policy does not check detection readiness, establish cause or impact, or grant authority to act. Review-state fields do not implement an incident workflow, and only reset_points rebuild a regime.

Cases group qualifying assessments by event time and declared entity scope, with deterministic IDs and evidence revisions. They are distinct from point-anomaly episodes. Both single-resolution recipes create cases for calibrated point departures and explicit rule violations. With adaptive-seasonal-v1, overlapping same-direction Nelson location rules and CUSUM also produce a grouped assessment at the first detection position and can create a case before any point trigger. Diagnostic trend, oscillation, mixture, and variation findings remain separate.

Cases include a plain-language explanation, evidence references, investigation questions, and deterministic warning or critical severity. Standardized strength of at least 4.5 or criteria met in multiple source datasets can raise severity. A derived relationship provides context without being counted as another source dataset. Severity is a numerical grouping rule, not validated incident impact or proof that source datasets are statistically independent.

The Python facade can inspect a case and replay eligibility without recomputing numerical evidence:

import json
from anomalyzer import analyze_relationships, get_case, replay_policy

with open("examples/agentic/unit-cost.json", encoding="utf-8") as stream:
    result = analyze_relationships(json.load(stream))

review_only = replay_policy(result, {})  # default policy disables eligibility
case = get_case(review_only, review_only.cases[0].id)
print(case.explanation)
print(case.action_eligible)  # False; the numerical rule violation is preserved

For an agent, start the read-only local stdio server with anomalyzer-mcp. Configure the MCP client's command as anomalyzer-mcp, or its absolute path in the installed environment. The advertised tools follow the investigation:

MCP tool Input Result
analyze {"request": <canonical schema-1.0 or schema-1.1 request>} Compact assessment, readiness, data-quality checks, finding preview, and run_id
list_findings run_id, optional cursor and limit Paged cases, early candidates, point episodes, patterns, and quality findings
get_evidence run_id, finding_id, optional cursor and limit Supporting numerical record, assessments, lineage, readiness, and paged samples

MCP discovery includes interpretation guidance, so clients can use the evidence without loading the repository's skill. Full results are retained only for that server session in a cache bounded to eight runs and 64 MB; evicted run IDs require reanalysis. Older tool names remain callable for compatibility but are not advertised. Recurring monitoring, deduplication of previously reported findings, review state, alerts, and production actions require caller-held state and logic.

14. Choose what an empty period means

The same timestamps can mean different things when collection guarantees differ. The safe default preserves missing values. An explicit additive-volume policy can interpret confirmed complete empty periods as zero. Unknown collection never provides that guarantee. A typical value, expectation, interpolation or previous measurement is never inserted as an observation.

Same input with preserved gaps versus explicitly authorized zero volume

Compare preserve.json with zero_empty.json. The latter declares data_policy={"measurement":"additive_volume","missing":"zero_for_empty"}; its four coverage="empty" positions have visible conversion reasons. The blue observations are unchanged; gold markers distinguish policy-derived zeros.

15. Keep zero, unknown and unfinished distinct

An observed zero is a real value. An unknown period stays a gap. An open period can show its reported progress without entering a completed baseline or total. The coverage example retains the original values, statuses and grid, including the open period's value of 3.

Observed zeros, unknown gaps and an unfinished value shown separately

Missing periods cannot establish normality or recovery. An expectation may remain visible as a separate dashed reference across a gap; the observed line breaks.

16. Retain a quiet customer's later event

For a completely collected additive count, quiet periods can legitimately be zero. This synthetic customer normally has one call every 12 hours. Confirmed quiet hours remain in their actual seasonal phase; a later measurement of 40 calls is still assessable.

A quiet but completely collected volume series and a later large measurement

The profile learns from eligible measurements and authorized empty counts. Business materiality, severity and incident decisions remain the caller's job.

17. Compare real calls across quiet hours

No calls means cost per call is undefined. The calendar profile keeps 431 hourly positions, but lacks matching daily-phase support. The event-index profile explicitly compares 12 genuine comparable calls and retains every call's original event timestamp.

Undefined quiet-hour cost values versus comparable event-index observations

Set data_policy.observation_basis="event_index", omit frequency, and use season_length=1. This is event position, not elapsed time or a calendar rate. Model/workload compatibility is a caller declaration; tokens or consumed units are not implicitly independent events.

18. Keep the weekday when observations are absent

Sparse calendar history must not be squeezed into a shorter positional sequence. The weekday example preserves five missing days. Its reference still compares the same seven-day phase, using eligible training values and a fixed calibration origin.

Sparse observations retaining their actual seven-day seasonal phase

The sparse frozen profile can borrow supported training phases across gaps. Unsupported phases remain unavailable. Phase support, calibration eligibility and reference quality are reported separately; evaluation samples do not update this frozen reference. Adaptive and explicit trend recipes currently need complete contiguous spans and an explicit reset/restart before new post-gap learning.

19. Restart consecutive patterns at a gap

Point-reference reuse does not make missing residuals consecutive. In this example, four positive residuals, one unknown hour and seven positive residuals do not make an eleven-observation run. CUSUM restarts independently; Nelson Rule 2 does not bridge the gap.

Consecutive residual patterns broken by an unknown collection period

Unknown, incomplete and explicitly excluded model samples break pattern runs. Reviewed resets also break early comparisons, references and calibration.

20. Apply a measured rule on the first observation

A caller-supplied range can be checked before any statistical learning. The first-value example observes 100 measured units against an explicitly declared acceptable range of 0–20. It retains the rule assessment and case while statistical status is insufficient_history.

First-observation numerical rule violation while statistical learning is unavailable

This threshold is illustrative, not a recommended business-risk limit. Change rules need a comparable prior observation and respect gaps/resets; relative change from zero stays unavailable. Early descriptive/provisional evidence alone cannot trigger a case. Numerical policy eligibility is not operational authority.

21. Keep undefined ratios unavailable

Cost per call requires an aligned valid numerator and denominator. At hour 1 the denominator is zero; hours 2 and 9 have missing inputs. These points stay undefined with explicit reasons and source lineage, even when another source uses an additive zero policy.

Aligned source quantities and undefined derived ratios shown with real gaps

An observed numerator of zero over positive volume can produce an observed ratio of zero. A missing numerator or zero denominator cannot.

22. Preserve incomplete days and useful hourly evidence

A daily total needs every expected finalized child. In the sparse daily example, day 1 has an unknown hour, day 2 is complete, and day 3 remains open. Only day 2 supplies a completed total. Eligible hourly evidence remains available while daily learning lacks support.

Unknown child and unfinished day kept out of completed totals while hourly evidence survives

Schema-1.1 multi-resolution outputs label each view and retain assessments, relationships, cases and policy replay. Daily ratios divide summed compatible quantities; they do not average percentages. Daily thresholds belong in separate completed_period_rules. Views are correlated evidence, not independent votes. This supports fixed UTC days and frozen seasonal children; local-calendar/DST aggregation, adaptive multi-resolution children and simultaneous multi-seasonality remain separate work.

The sparse-data API guide documents policy defaults, bounded grid expansion, event-index restrictions, availability/correction semantics and the remaining complete-span restrictions. All new fixtures run through examples/run_demo.py; the existing generator and figure renderer reproduce the full progression.

23. Keep distinct calls at the same recorded time

Two completed calls can share the exact timestamp while having different costs. The identified-call example supplies aligned event_ids under data_policy.observation_basis="event_index". It retains all ten original calls: three in the training cohort, four in the calibration cohort, and three at 09:00:10.789Z. The last three cost $0.80, $0.12 and $0.90. They remain three observations at the same time; no jitter, averaging or delivery order is introduced.

Distinct call costs at exact shared timestamps, with separate IDs and a frozen prior-time reference

The $0.80 and $0.90 calls each have inspectable point/range evidence and a distinct Case.event_id. A whole timestamp cohort finishes training or calibration before a later cohort can use that reference; alphabetical ID order never trains one same-time call on another. Actual support can therefore exceed the requested minimum training/calibration count. Event IDs and source indexes link evidence back to the original measurements. Separate findings do not imply separate notifications or independent confirmations.

24. Declare identity, retries and order separately

event-identity-v1 requires caller-trusted logical IDs. A retry keeps the same ID; the duplicate-delivery fixture is rejected instead of counted as another call. Repeating a valid snapshot preserves its case and evidence IDs. The correction fixture addresses call-07 using SampleRevision.event_id, keeps its original timestamp, and changes its case revision while retaining the case ID. The earlier as-of snapshot still returns the original evidence.

Identical same-time residuals support points but no timestamp-only sequence pattern; an explicit trusted sequence permits order-based checks

In the timestamp-only example, twelve comparable evaluation calls share one timestamp. Their point reference is usable, but their ID layout cannot establish change-rule, Nelson, CUSUM or moving-range continuity. Each tied call breaks those comparisons. There is no sequence-pattern case in this example.

The ordered variant adds data_policy.event_order="trusted_sequence" and unique aligned integer event_sequence ordinals. Only a caller with an actual trustworthy ordering contract should assert that order. The same timestamps stay unchanged; the declared sequence permits residual pattern checks without establishing elapsed rates, causal explanation or independence. Without IDs, the existing unique-time event-index contract continues unchanged. See the API guide for validation, replay, alignment and caller responsibilities.

25. Detect redistribution while the average stays steady

Individual cache misses and hits can both be ordinary. The question is whether the recent population changed. The new numeric detector compares empirical distributions, including zeros and ties, rather than asking whether the latest call crosses a residual threshold.

A mean-preserving change in cache fractions is detected by a numeric distribution test; causal replay shows the first detection against a frozen reference

The numeric shape example has 400 calls in each population and a mean cache fraction of 50% in both. The proportions at 0%, 25%, 50%, 75% and 100% change, but no individual value exceeds three reference standard deviations. ks_tie_exact reports a CDF distance of 0.2 and an exact conditional permutation p-value of approximately 2.22e-8. The ordinary control has the same distribution and produces no finding. These are synthetic behavior checks, not measurements of production accuracy.

The upper panel uses the separate causal monitoring example: 128 earlier positions remain frozen, and 32/64-call windows share a budget across 129 planned looks. The introduced change starts at call 160; the first finding is at zero-based call 182. That delay is specific to this fixture and its settings. Unknown current positions break windows; incomplete suffixes are excluded. References never adapt to or automatically reset on a finding.

import json
from anomalyzer import compare_distributions, monitor_distributions

with open("examples/distributions/numeric-shape.json") as stream:
    comparison = compare_distributions(json.load(stream))
print(comparison.change_detected, comparison.statistic, comparison.p_value)

with open("examples/distributions/numeric-monitor.json") as stream:
    replay = monitor_distributions(json.load(stream))
print(replay.first_detection)

26. Compare the whole category composition

A feature mix can change through several small share movements rather than one large category jump. The categorical detector compares all supplied categories together, then reports each share change to explain that one comparison.

A global feature-composition comparison detects sixteen small category-share changes while total report counts remain unchanged

The categorical example preserves 800 reporting events per population. Each of 16 categories changes by only 1.875 percentage points. Whole-composition Jensen-Shannon divergence is about 0.0167 bits; 1,999 reproducible permutations give p=0.001 under this fixture's explicit exchangeability assumption and prospective settings. Counts and shares come from genuine events. The weighted example reports a descriptive composition change only: consumed units are not independent event counts and cannot manufacture statistical support.

When individual report quantities are available, the weighted-report example compares consumed-unit mix by permuting whole category/quantity reports. Sample support counts reports; changing the quantity unit does not multiply the evidence.

anomalyzer compare-distributions examples/distributions/categorical-mix.json
anomalyzer monitor-distributions examples/distributions/numeric-monitor.json
python examples/run_distribution_demo.py
python examples/render_readme_progression.py --only-distributions

Both detectors require caller-asserted exchangeability for inference. Statistical and practical effect gates are separate; business impact stays unresolved. Bonferroni correction covers the declared comparisons, including overlapping windows, but not an unlimited sequence of restarted monitors. Category rarity, permutation resolution, clustered calls and changing traffic mix need explicit attention. Read the integration guide for contracts, defaults, budgets, source identity, limits and UsageTap adoption checks.

27. Compare the 8am batch with earlier mornings

A scheduled morning batch can normally have different cache reuse from evening traffic. Comparing today's calls with all prior calls can flag that expected composition. Explicit local-clock matching selects comparable prior mornings before running the same distribution detector.

The same normal 8am calls differ from broad day/night history but match the selected prior morning windows

The seasonal morning fixture contains morning cache fractions of 80% and evening fractions of 10%. Today's 08:00–09:00 window is assessed at 08:05 in America/Los_Angeles; only the same 08:00–08:05 portion of the prior three mornings supplies its reference. The matched comparison has 96 reference calls and 32 current calls, p=1, and no finding. The synthetic broad-history comparison flags their expected day/night difference. This demonstrates reference selection, not a guaranteed reduction in production false-positive rates.

anomalyzer compare-seasonal-distributions examples/distributions/seasonal-morning.json

Choose daily, same_weekday, or weekday_weekend explicitly, with an IANA timezone, bounded lookback and minimum support across distinct dates. There is no automatic schedule discovery. A broader daily fallback is opt-in, based on support alone, and visibly reported. Partial windows match the same clock-time progress; historical DST gaps/folds are skipped with reasons. Selected IDs, windows, fallback and testing allocation remain inspectable. Existing APIs stay available; the handoff guide defines supported boundaries and evidence limits.

Matching does not make calls within a batch independent. No block resampling or automatic reset is added. Keep elapsed-time rate and application-wide mix checks to detect a missing, moved or enlarged batch, and preserve the caller's testing budget across partial looks, detectors and days.

Baseline and calibration reference

The robust expectation for sample t uses four previous matching seasonal positions, each adjusted to t with the training-fitted trend. It drops the highest and lowest values and averages the middle two with weights from 1 to 1.5 favoring recent positions. Until four complete cycles are available, it uses the previous seasonal reference. A season length of 1 uses the four most recent observations once available. This limits one unusual week's influence even when its score was below the 4.5 extreme cutoff. Set robust_reference_seasons=1 (CLI: --robust-reference-seasons 1) to reproduce the earlier single-reference robust baseline. outlier_handling=include retains the single-reference raw baseline. An extreme point's pre-anomaly expectation becomes its model-only reference after scoring; the actual remains in evidence. A season length of 7 can represent weekly seasonality in daily data. Each prediction uses only earlier samples within its manual segment.

Set recipe to adaptive-seasonal-v1 (CLI: --recipe adaptive-seasonal-v1) when the expected level or growth rate should change continuously. For every sample, it uses at most the prior 56 observations (adaptive_window), computes the median of pairwise slopes separated by at most 24 samples (adaptive_slope_lookback), and projects every observation in that window to the next sample. Their median is the general rolling trend. When at least three matching seasonal phases exist, their projected median receives 80% weight (adaptive_season_weight) and the general trend receives 20%. All four settings are explicit in structured output. The calculation is causal and prefix invariant: future observations never revise an earlier expectation.

The adaptive recipe intentionally freezes the calibration mean and standard deviation while updating the expectation. This separates “what value should be expected now?” from “how large is an ordinary forecast miss?” An isolated calibrated residual beyond 4.5 standard deviations is replaced by its expected value in future rolling references. If extreme residuals persist in the same direction, the third and later values enter the rolling history so a persistent change can be followed without an automatic regime reset. Nelson Rules 2, 5, and 6 and CUSUM continue to examine the frozen-calibration residual stream during that adaptation. In schema 1.1, overlapping same-direction location rules form one early case at their first detection position.

trend defaults to auto, which fits linear and exponential (compound) trends with additive seasonal offsets using only the completed training window. Both upward and downward trends are supported. Each increase in complexity must cut training residual sum of squares by at least 50%; otherwise the simpler model is retained. Exponential fits require positive amplitude. Model selection is a heuristic, not a statistical confidence test. Fitting requires at least max(8, 4 * season_length) training samples; shorter windows disclose a fallback to no trend. The fit uses at most the final max(256, 4 * season_length) training samples. Exponential log rates are bounded by the smaller of 0.1 per sample and 20 divided by the fit-window length. With outlier_handling=robust, Anomalyzer also fits a bounded-influence seasonal residual screen, identifies only residuals beyond 4.5 robust standard deviations, substitutes their fitted values inside the training copy, and repeats ordinary model selection and fitting. The public trend still reports the refitted ordinary coefficients; diagnostics report the screened training positions and Huber downweight count.

For seasonal-residual-v1 and its multi-resolution views, use --trend linear, --trend exponential, or --trend none to override auto selection (JSON uses the trend key). The adaptive recipe owns its rolling trend and requires trend=auto. With the default recipe, none selects the seasonal_naive method; other settings report seasonal_trend, with the selected model and fitted parameters in diagnostics.trend. Unsupported exponential fits fall back to no trend with a diagnostic reason. Early reference evidence remains seasonal-only until training completes. The trend is then frozen before calibration, so evaluation observations cannot change its fitted parameters. Changes in growth rate can still flag; contamination in training and long-range extrapolation can reduce accuracy. Overflow stops scoring with a disclosed error.

Both single-resolution recipes emit evidence after one full season. Defaults reserve 28 initial samples (at least one season), then 14 calibration samples. Calibration forecast errors establish a mean and sample standard deviation. In robust mode, the completed calibration window is screened with its median and normal-consistent MAD at the same 4.5 threshold; extreme residuals are excluded, seasonal references are rebuilt, and the mean and sample standard deviation are computed from the retained residuals. At least three residuals must remain. The resulting center and scale remain fixed while later samples are scored:

residual = observed - expected
score = (residual - calibration_mean) / max(calibration_stddev, scale_floor)
flag when abs(score) > point_threshold

Each evidence record has a signal_maturity:

  • reference_only reports the raw residual and relative deviation (residual / abs(expected), or null when undefined) once season_length earlier samples exist. It has no standardized score.
  • provisional reports a causal standardized residual after at least three earlier calibration residuals exist. Its scale is still changing.
  • calibrated uses the complete frozen calibration window.

Only calibrated evidence can cross the point threshold or form an anomaly episode. Reference-only and provisional evidence are deliberately non-triggering. The default threshold is 3; it is an exploratory threshold, not a probability. Zero or tiny variance uses an explicit scale floor (default 1e-8 in input units) and reports a limitation. At least one sample after calibration is required for calibrated scoring. Before that, the result is insufficient_history but contains the weaker evidence available so far.

Each evidence record also reports excluded_from_model, model_value, exclusion_reason, and reference_action. A calibrated point is excluded from future model references only in robust mode and when its absolute standardized residual exceeds 4.5; adaptive mode admits the third and later consecutive same-direction extreme misses instead of replacing them. The ordinary point threshold remains separately configurable and defaults to 3. Thus a point can be anomalous without being extreme enough to alter model history. The multi-season baseline still limits its influence when four matching references are available. Set outlier_handling to include (CLI: --outlier-handling include) to preserve every raw reference and reproduce the earlier echo/adaptation behavior.

With season_length=7, training_size=2, and the default 14-point calibration, the first calibrated score needs 21 prior samples; the default 28-point training reservation instead needs 42. Reducing that reservation is an explicit tradeoff: the seasonal-naive baseline itself needs only one season, but a longer ordinary history makes the chosen calibration window easier to inspect and defend.

Detection readiness

Each method reports diagnostics.detection_readiness with supported, caution, or not_assessed, plus reasons and the number of segments checked. Each calibrated segment has its own assessment in diagnostics.segments[].detection_readiness. The CLI shows the overall status and reasons. Schema 1.1 carries the same diagnostics on dataset and relationship methods; multi-resolution results assess each view separately.

This is a reference-quality check, not a measured accuracy score. It uses only training and calibration data, so a later anomaly does not lower the rating. It compares calibration mean absolute error (MAE) with the MAE of a constant training median on the same samples; less than 10% improvement raises a caution. For a declared season longer than one sample, it checks lag correlation in each half of up to 4,096 detrended training values; either half below 0.5 raises a caution. Absolute lag-1 calibration residual correlation of at least 0.65, a change in the calibration residual center of at least 1.25 standard deviations between halves, or residuals near the scale floor also raise a caution. Too few training cycles to check a declared season raises a caution; fewer than eight retained calibration residuals produce not_assessed.

No detected trend or season is not automatically a failure: a stable level can still be a useful reference. Conversely, a weakly repeating season or a model that does not improve on the training level makes point flags harder to interpret. These cutoffs are exploratory checks, not validated false-alarm or detection rates. A supported rating does not establish that operational alerts will be useful; replay on representative history and review labeled outcomes before relying on them.

Read the evidence

The result has three related levels of evidence:

Level JSON location Meaning
Point anomaly methods[].evidence[].triggers contains point One value departed far enough from its modeled expectation under the frozen residual calibration to cross the configured point threshold.
Point-anomaly episode observations One or more consecutive point anomalies grouped into an interval. A single flagged point is a one-sample episode.
Pattern finding anomaly_patterns A consecutive point-anomaly run or a rule finding across calibrated residuals. Nelson, CUSUM, and moving-range findings can occur without any point anomaly or episode.

The point threshold compares each value with a seasonal/trend expectation and the frozen residual center and scale. It does not estimate a new "current distribution" and decide whether the point is unusual within it. A sustained location pattern suggests that the residual center may have moved relative to the calibrated reference, even when every individual point stays below the point threshold. The Nelson location rules use short, fixed windows rather than fitting a new distribution: Rule 5 looks for two of three residuals beyond 2σ, Rule 6 for four of five beyond 1σ, and Rule 2 for nine on one side of the centerline. They can expose a modest sustained shift before any point exceeds the default 3σ threshold. A sufficiently large point can still flag after just one observation, so Nelson rules do not always need fewer observations. Rules 3, 4, and 8 and moving range instead describe other residual structure. None of these findings establishes a change in the full distribution of the underlying metric.

Two or more adjacent point anomalies also produce a descriptive consecutive_run pattern. That pattern and the corresponding episode summarize the same underlying flags; they are not independent findings. A pattern does not assign a probability, decide that a run is a new regime or incident, suppress point evidence, or retrain the baseline. Multiple Nelson and CUSUM patterns may describe the same interval and should be investigated together.

See the residual detector reference for each implemented rule, threshold, and interpretation.

Only consecutive, calibrated residuals participate; provisional evidence and gaps or manual-reset boundaries break the sequence. Each pattern reports its rule, direction, first detection index, triggering interval, evidence references, and a plain-language description. For location patterns, increase and decrease describe direction relative to the expectation; mixed-direction diagnostics use mixed. These checks do not establish arbitrary changes in variance, tails, or distribution shape. Classical false-alarm behavior can be changed by residual autocorrelation, non-normal tails, and uncertain calibration estimates, so replay the alert burden on representative history before operational use.

Schema 1.1 keeps observations and anomaly_patterns on each dataset and relationship result. Its cases currently group supported calibrated point departures and caller-supplied rule violations. With adaptive-seasonal-v1, grouped calibrated Nelson/CUSUM location shifts can also create a case without a point trigger. With the default recipe those patterns remain findings only.

Results include expected and observed values, residuals, scores, evidence references, data checks, calibration windows, resolved settings, dependency versions, and an input fingerprint. Readable output and JSON use the same result. Business impact remains unresolved.

With the default recipe and outlier_handling=include, the seasonal reference adapts to a level change after one season and a spike can cause an echo when it enters the next season's baseline. The robust default prevents extreme points from entering later seasonal references. A sustained moderate shift can gradually enter the four-season baseline, while a sustained extreme shift may continue to trigger until a reviewed reset. Robust exclusion is a modeling decision, not a claim that the actual was erroneous or operationally unimportant. Choose representative training and calibration periods, inspect excluded positions, and compare both modes during chronological replay. Context about known events is preserved but does not suppress flags.

Seasonal settings and regime boundaries

season_length is one fixed lag measured in samples. For example, hourly data can use 24 for a daily pattern or 168 for a weekly pattern; daily data can use 7 for a weekly pattern; weekly data can use 52 for an approximate annual pattern. Each view models only one seasonal lag at a time. It does not model multiple seasonalities, calendar months/holidays, or daylight-saving wall-clock patterns.

For value-only positional input, omit season_length to request conservative inference. The detector detrends an initial prefix, ranks autocorrelation candidates, requires at least three cycles, and confirms that the same lag wins in its earliest three-cycle window. Calibration and evaluation remain later windows. If no stable candidate passes the disclosed thresholds, lag 1 is used and marked as a fallback. Inference is a suggestion, not proof of a real-world cycle; explicit domain settings take precedence. Automatic candidates are bounded to lags 2–512 and the first 4,096 usable selection values; longer lags must be explicit. Timestamped input retains the default lag of 1 when no season is supplied.

Known events can start new manual regimes with reset_points in JSON or repeated --reset-point CLI options. Each value may be a zero-based sample position, a YYYY-MM-DD date that identifies exactly one observed sample, or an exact timestamp. Positions apply to the prepared chronological series after sorting, duplicate aggregation, and as_of filtering. Position zero and points outside the prepared series are rejected; duplicate resolved boundaries are rejected.

At a boundary, the sample belongs to the new segment. No seasonal reference crosses the boundary. The segment rebuilds its seasonal history and then refits trend using its own training window, followed by its own calibration window. The configured season_length is reused; automatic lag inference is not rerun per segment. Evidence is absent during the first season after a reset, then is reference-only/provisional until segment calibration completes. A short final segment therefore does not establish normality. Segment windows, fitted trends, and calibration details appear in diagnostics.segments.

Resetting is always explicit. Consecutive anomalies and detected anomaly runs do not declare a new regime or restart calibration. Adaptive expectation updates continue within the current regime. Use a reset only when an external event or reviewed change point establishes a new operating regime.

Residual detector reference

The small series below use standardized residuals, not raw observations. Zero is the frozen calibration-residual centerline; positive values are above the seasonal/trend expectation and negative values are below it. The numbering follows the standard Nelson rules, but Anomalyzer currently implements only the checks shown here.

Small standardized-residual series showing the point and location rules plus the separate adjacent-anomaly pattern

Check Small residual series Interpretation
Point, default threshold [-0.2, +0.4, -0.5, +3.4, +0.1] The +3.4 point exceeds 3σ. With the default threshold this is equivalent to Nelson Rule 1.
Nelson Rule 2, above [+0.2, +0.4, +0.1, +0.5, +0.3, +0.6, +0.2, +0.7, +0.4] Nine residuals above zero: possible upward location shift.
Nelson Rule 2, below [-0.2, -0.4, -0.1, -0.5, -0.3, -0.6, -0.2, -0.7, -0.4] Nine residuals below zero: possible downward location shift.
Nelson Rule 5 [+2.2, +0.3, +2.4] Two of three exceed +2σ: possible upward location shift. The test is mirrored below −2σ.
Nelson Rule 6 [-1.4, -1.2, +0.2, -1.6, -1.3] Four of five are below −1σ: possible downward location shift. The test is mirrored above +1σ.
Adjacent point-anomaly run [+3.2, +3.5] Two adjacent point anomalies form consecutive_run. This is an Anomalyzer summary pattern, not a Nelson rule.

The remaining implemented detectors have different semantics:

Small standardized-residual series showing trend, oscillation, mixture, CUSUM, and moving-range detectors

Check Small residual series Interpretation
Nelson Rule 3 [-0.5, -0.3, -0.1, +0.1, +0.3, +0.5] Six increasing residuals: possible residual trend or changing model slope, not a location shift. The test is symmetric for decreasing values.
Nelson Rule 4 [-0.4, +0.4] repeated seven times Fourteen alternating residual moves: possible systematic oscillation, baseline echo, or missed periodic structure.
Nelson Rule 8 [+1.2, -1.2] repeated four times Eight residuals outside ±1σ on both sides: possible mixture, stratification, or missed structure.
Two-sided CUSUM +0.8 repeated seventeen times With defaults k=0.5, h=5, moderate positive evidence accumulates until the positive CUSUM crosses its threshold: possible upward location shift. A negative CUSUM mirrors it.
Moving range [-2.0, +2.0] The adjacent range is 4, above the default standardized threshold 3.686: possible short-term variation increase, not a location shift.

Rule 2 counts side, not magnitude: nine small positive residuals can qualify. Rules 5 and 6 count how many values in a short window cross a same-side sigma band; the remaining values need not be on that side. “Possible location shift” always refers to the center of the residual process, not proof of a general distribution change.

Nelson Rule 7 is deliberately not enabled. Fifteen points within ±1σ usually diagnose limits that are too wide or stratification; the default 14-point calibration window is too short to make that a dependable default signal.

Why these detectors

The point check detects a large individual miss; the location rules detect repeated departures on one side. CUSUM accumulates smaller same-direction residuals. NIST describes its sensitivity to mean shifts of 2σ or less compared with Shewhart charts; this motivates the detector, without establishing the same performance for Anomalyzer's residuals. NIST CUSUM.

Moving range adds variation evidence, which the location rules do not provide. Nelson 3, 4, and 8 are explicit model/process diagnostics. EWMA is not implemented. NIST Individuals/Moving Range, NIST EWMA.

All of these checks operate on the same residual stream and are correlated evidence, not independent votes. Seasonal-naive residuals can also be autocorrelated and produce echo effects. Classical false-alarm rates therefore do not transfer automatically; use chronological replay, including pattern_counts and first_detection_by_rule, to compare detection delay and alert burden before operational use. No pattern automatically resets the model.

Regenerate the twelve progression PNGs and both rule references with .venv/Scripts/python examples/render_readme_progression.py (or the equivalent .venv/bin/python command on Unix). The renderer has one figure function and output entry for each illustrated chapter, plus the two detector references. It requires an installed Chromium browser; set ANOMALYZER_BROWSER if browser discovery needs an explicit path.

Incomplete periods and multi-resolution analysis

An unfinished aggregate must be marked rather than submitted as though it were final. Canonical JSON may align period_statuses with values; values marked incomplete must form a trailing suffix. They are preserved in data_quality.incomplete_periods and excluded from baseline fitting, calibration, scoring, episodes, and patterns:

{
  "datasets": [{
    "timestamps": ["2026-01-01T00:00:00Z", "2026-01-02T00:00:00Z"],
    "values": [100, 20],
    "period_statuses": ["complete", "incomplete"],
    "frequency": "1d"
  }]
}

multi-resolution-v1 accepts regular timestamped sub-day samples whose cadence evenly divides 24 hours. It keeps two correlated views: finalized source samples at their native cadence and completed fixed 24-hour aggregates. The trailing partial aggregate is clearly returned as data_quality.incomplete_period and is never inserted into the completed-period series. That record links the native subperiod evidence produced so far through intraday_evidence_refs. For hourly data the default intraday lag is 168 samples and the completed-period lag is seven days:

{
  "datasets": [{
    "id": "calls",
    "timestamps": ["2026-01-01T00:00:00Z", "2026-01-01T01:00:00Z"],
    "values": [12, 15],
    "frequency": "1h",
    "units": "calls"
  }],
  "config": {
    "recipe": "multi-resolution-v1",
    "aggregate_function": "sum"
  }
}

The aggregation function is configurable: sum (default), mean, or last. Fixed 24-hour windows default to 00:00 UTC on the first observation date; use aggregate_anchor to choose another exact boundary. This first version does not claim local-calendar or daylight-saving-aware daily periods. Source timestamps are interpreted as the starts of finalized subperiods. A caller may mark a trailing unfinished source sample as incomplete; it is excluded from both views.

Completed-period and intraday results use separate method and evidence IDs. They come from the same source observations and must not be counted as independent confirmation. CUSUM and Nelson patterns operate on native subperiod residuals, not repeatedly accumulated current-day totals.

Inputs and validation

CSV always requires a value column. Time and frequency are supplied together for timestamped data or both omitted for ordered positional data. Empty values mean missing, never zero. Extra varying columns are rejected; select one entity and unit first. Schema-1.0 canonical JSON supports one named dataset; use schema 1.1 for multiple datasets and declared relationships:

{
  "schema_version": "1.0",
  "task": "analyze",
  "datasets": [{
    "id": "calls",
    "timestamps": ["2026-01-01T00:00:00Z", "2026-01-02T00:00:00Z"],
    "values": [100, 102],
    "frequency": "1d",
    "units": "calls"
  }],
  "config": {"season_length": 7}
}

This short input correctly returns insufficient_history. Longer synthetic examples are in examples/; regenerate with python examples/generate.py. JSON can be piped to anomalyzer analyze - --input-format json --format json. It may also be a bare array such as [100, 102, 101]; combine that with CLI settings or a separate --config settings file. Positional evidence has null timestamps, zero-based sample indices and cutoff positions, and integer episode intervals. Positions are assumed equally spaced because no cadence can be validated.

Timestamps require an explicit offset or an IANA timezone. Ambiguous/nonexistent local DST times require offsets. Cadence is a fixed duration, such as 1h, 1d, 1w, or 15min; days are 24 elapsed hours and weeks are seven such days. Sorting and UTC normalization are reported. Duplicates require --duplicate-policy mean|sum; nulls propagate. In schema 1.0, missing values or irregular cadence make the baseline inapplicable. No implicit imputation, resampling, interpolation, or unit conversion occurs.

--config accepts settings or a complete canonical request. Precedence is embedded settings, settings file, CLI flags. Unknown keys and options fail clearly. The former methods, alpha, and seed settings are removed and rejected, as is the former baseline-v1 recipe. CUSUM uses positive cusum_k and cusum_h settings (defaults 0.5 and 5); moving range uses positive moving_range_threshold (default 3.686). The default frozen recipe is seasonal-residual-v1; select adaptive-seasonal-v1 for the causal rolling expectation described above. Public JSON schemas are in schemas/, generated by scripts/export_schemas.py.

Python API

import json
from anomalyzer import analyze

with open("examples/spike.json", encoding="utf-8") as stream:
    result = analyze(json.load(stream))
print(result.model_dump_json(indent=2))

Value-only data accepts settings separately:

from anomalyzer import analyze

result = analyze([100, 103, 99, 102, 101], {"season_length": 1})
automatic = analyze([10, 20, 5, 15, 8, 30, 12] * 12)

The second form attempts positional season inference because season_length is absent. All other configuration uses the normal validated defaults.

analyze dispatches either canonical schema version. analyze_series is a single-dataset facade that accepts a dataset object, optional settings, and optional context; use context.as_of for an event-time cutoff on timestamped data. The cutoff excludes later observations before analysis and reset-position resolution. context.known_events and context.concern preserve review context without suppressing evidence. analyze_relationships, get_case, and replay_policy support the schema-1.1 workflow shown in chapters 12 and 13.

The internal anomalyzer.evaluation.replay helper reports held-out MAE/RMSE, trigger counts, episodes, pattern counts, and first detection by rule. The internal anomalyzer.evaluation.relationships.replay_relationships helper reports when relationship observations, candidates, departures, rule violations, and policy eligibility first appear. Tests check independent numerical references, chronological scoring, constant/seasonal/spike/shift data, invalid inputs, schemas, reproducibility, failure handling, and CLI behavior. Synthetic examples establish behavior, not product effectiveness. Historical analyses do not establish general effectiveness, detection precision, or recall without representative replay and reviewed incident labels.

Output and limits

Stdout contains only the result; diagnostics use stderr. --output PATH writes instead of printing and requires --overwrite to replace an existing file.

Exit 0 means completed, including anomalies, insufficient history, or inapplicability. Exit 2 means invalid input/configuration. Exit 1 means execution failure, interruption, or a partial/budget-limited run.

Defaults: 10,000 source observations per request (shared across schema-1.1 datasets), 10 MB CLI input, and 60 seconds cooperative runtime. Override with --max-points, --max-bytes, and --max-runtime-seconds. Seasonal scoring uses bounded reference history; adaptive scoring additionally computes pairwise slopes within its configured bounded window. Limits are checked cooperatively between samples, rather than enforcing a hard process timeout. Partial evidence is retained on execution failure. Numerical results are deterministic; UUIDs and runtimes vary.

Dependencies and scope

Numerical calculations use Python's standard library. Pydantic handles input validation and schemas; tzdata supplies IANA timezone data across supported runtimes. Pytest and jsonschema are development-only dependencies. StatsForecast, SciPy, NumPy, pandas, Numba/LLVM, and their forecasting dependencies have been removed.

The historical plan and product brief describe broader possibilities, not current commitments. Forecasts are internal expectations for scoring; there is no standalone forecasting API, automatic relationship discovery, EWMA detector, ensemble, or incident-probability estimate. The local MCP adapter is included; no hosted service or model API is required.

The problem framing explains why chart aggregation should not set the response time. The core already scores eligible samples at the supplied cadence and exposes evidence, including below-threshold scores, through Python and JSON independently of chart presentation. Applications can use that evidence for AI agents and notification policies while keeping daily or weekly charts. Monitoring schedules and notification delivery belong to the calling application.

Development

Python 3.11–3.13; verified on Windows with Python 3.12.

uv sync --locked --extra dev
.venv/Scripts/anomalyzer methods --format json
.venv/Scripts/python examples/run_demo.py
.venv/Scripts/anomalyzer analyze examples/spike.csv --time timestamp --value calls --frequency 1d --season-length 7 --format json
.venv/Scripts/anomalyzer analyze --config examples/shift.json
.venv/Scripts/python -m pytest -q

On Unix, use .venv/bin/ instead of .venv/Scripts/. Alternatively install into a compatible environment with python -m pip install -e '.[dev]'.

Command-line help

Start with anomalyzer --help for command discovery, then anomalyzer analyze --help for input requirements, option meanings and defaults, configuration precedence, exit codes, and copyable examples. anomalyzer methods --help explains how to inspect the installed methods. Use .venv/Scripts/anomalyzer if the executable is not on your PATH, or .venv/bin/anomalyzer on Unix. Each command also accepts -h.

The example walkthrough checks ordinary behavior, a spike, a drop, and a sustained change, including the baseline's echo/adaptation limits.

Contributing and security

Bug reports and focused pull requests are welcome. See CONTRIBUTING.md for the development and verification workflow. Please report security issues using the private process in SECURITY.md, not a public issue.

License

Anomalyzer is available under the MIT License.

About

Local, inspectable time-series anomaly evidence for Python and the command line

Topics

Resources

Contributing

Security policy

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages