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.
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 jsonOr 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.
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.
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.
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.
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.
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.
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.
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.
| 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.
The CapabilityKit maps describe Anomalyzer's observable behavior, acceptance criteria, implementation evidence, and dependencies.
- Interactive story map: explore deliverable lanes and the user outcomes within each one.
- Interactive dependency map: explore behavior dependencies and inspect each capability's acceptance coverage.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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 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.
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?”
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.
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 preservedFor 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.
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.
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.
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.
Missing periods cannot establish normality or recovery. An expectation may remain visible as a separate dashed reference across a gap; the observed line breaks.
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.
The profile learns from eligible measurements and authorized empty counts. Business materiality, severity and incident decisions remain the caller's job.
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.
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.
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.
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.
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.
Unknown, incomplete and explicitly excluded model samples break pattern runs. Reviewed resets also break early comparisons, references and calibration.
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.
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.
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.
An observed numerator of zero over positive volume can produce an observed ratio of zero. A missing numerator or zero denominator cannot.
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.
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.
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.
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.
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.
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.
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.
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)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.
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.
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 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.
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_onlyreports the raw residual and relative deviation (residual / abs(expected), or null when undefined) onceseason_lengthearlier samples exist. It has no standardized score.provisionalreports a causal standardized residual after at least three earlier calibration residuals exist. Its scale is still changing.calibrateduses 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.
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.
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.
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.
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.
| 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:
| 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.
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.
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.
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.
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.
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.
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.
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 -qOn Unix, use .venv/bin/ instead of .venv/Scripts/.
Alternatively install into a compatible environment with
python -m pip install -e '.[dev]'.
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.
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.
Anomalyzer is available under the MIT License.



























