XmR Analysis
XmR Analysis
A metric on its own is a number. A metric over time is a process. XmR control charts (individuals and moving range) tell you whether that process is stable or reacting to a special cause — using the data itself to compute natural process limits, with no external targets required.
fit-xmr reads a time-series CSV, computes the limits,
detects four families of signals, and prints either a human-readable
report, a compact markdown summary, or structured JSON for further
analysis. It is the analysis surface on top of any CSV that records
observations of a metric over time.
This guide walks through the CSV schema, the commands, the signal rules, and how to interpret the report.
Prerequisites
- Node.js 18+
- A CSV of observations with the schema below
- At least 15 data points per metric for limits to be meaningful
CSV schema
fit-xmr expects the header
date,metric,value,unit,run,note and one row per
observation:
date,metric,value,unit,run,note
2026-01-01,open_vulnerabilities,12,count,https://example.com/run/1,
2026-01-02,open_vulnerabilities,11,count,https://example.com/run/2,
2026-01-03,open_vulnerabilities,11,count,,
Trailing commas keep the column count consistent when
run and note are empty.
| Field | Required | Notes |
|---|---|---|
date |
yes | ISO 8601 (YYYY-MM-DD). Sort key. |
metric |
yes | Metric name. One CSV may carry multiple metrics — they are grouped. |
value |
yes |
Numeric. Non-numeric strings are rejected by
validate.
|
unit |
yes |
Free text (count, days,
pct, ...). Empty unit is rejected.
|
run |
no | Optional URL or identifier of the run that produced the observation. |
note |
no | Free text — annotate with what you discovered when a signal appears. |
Validate before analysis:
npx fit-xmr validate observations.csv
A non-zero exit code means the file does not match the schema.
1. Get oriented
List what's in the file before charting:
npx fit-xmr list observations.csv
Prints one row per metric with the count of observations and the date range. Useful when a CSV carries several metrics and you only want to chart one of them.
2. Run the analysis
The full report lives in analyze:
npx fit-xmr analyze observations.csv
npx fit-xmr analyze observations.csv --metric open_vulnerabilities
npx fit-xmr analyze observations.csv --format json
JSON output is the canonical shape; text is a pretty-print of the
same data. The JSON document is wrapped in
{ source, generated, metrics: [...] } — each metric in
that array carries:
-
x_bar— central line, mean of all values. -
mr_bar— average of the moving ranges|x_i - x_{i-1}|. -
unpl/lnpl— upper / lower natural process limits (x_bar ± 2.66 * mr_bar;lnplfloors at 0 for counts). -
url— upper range limit for the moving range chart (3.27 * mr_bar). -
latest— most recent observation as{ date, value, mr }.mris the moving range at that point — useful for "is today's change unusual?". -
signals— special-cause signals, sorted by start date. -
status—predictable,signals_present, orinsufficient_data.
The constants 2.66 and 3.27 are XmR's
standard scale factors for the n=2 moving range (≈ 3σ for
individuals, D4 for the range chart). They are not tunable —
that's what makes XmR limits comparable across processes.
3. Read the signals
fit-xmr detects four families of special-cause signals.
Treat each as a prompt to investigate, not a verdict.
| Rule | Meaning |
|---|---|
point_above_unpl / point_below_lnpl
|
One or more points outside the natural process limits. |
run_above / run_below |
8+ consecutive points on the same side of x_bar.
|
trend_up / trend_down |
6+ consecutive increases or decreases. |
mr_above_url |
Moving range exceeds URL — unusual point-to-point churn. |
Signal payloads:
-
Run signals carry
from,to, andlength(point count). -
Trend signals carry
from,to, andmoves(number of consecutive same-direction transitions; 6 moves = 7 same-direction points). -
Point and mR signals carry
countpluspeak(for above-limit) ortrough(for below-limit). Consecutive out-of-bounds points are consolidated into one streak withfrom/to; single-point streaks carrydateinstead.
Runs and trends are usually the most actionable —
they say the process level has shifted. Why those rules? A
predictable process produces points that hop randomly across
x_bar; under that null, eight consecutive points on one
side or six consecutive same-direction moves are each rarer than 1%.
Point signals confirm the magnitude of the shift; the size matters
for prioritization, not for the verdict.
mr_above_url is special. The limits are computed from
mr_bar, so an outlier moving range inflates
mr_bar and pulls UNPL/LNPL wider than the process
actually warrants. Until you investigate the outlier — usually a
single big jump — the rest of the report describes a stretched
envelope, not a real one.
4. Summarize across metrics
For a status overview across many metrics,
summarize produces a markdown table you can paste into
a status page or PR description:
npx fit-xmr summarize observations.csv
npx fit-xmr summarize observations.csv --metric open_vulnerabilities
npx fit-xmr summarize observations.csv --format json
--metric filters to a single row;
--format json returns the same data as JSON. Each row
carries the metric, sample count, latest value, x_bar,
the limits, a coarse classification, and a compact list of signals.
The classifications are deterministic:
-
stable—predictablestatus, no signals. -
signals— special cause detected; investigate. -
chaos—mr_above_urlpresent; the limits are not trustworthy. -
insufficient— fewer than 15 points; keep recording.
Metrics with insufficient data are listed below the main table so they don't crowd the active signals.
5. Sparklines for inline reporting
For one-glance trend indicators in markdown tables,
spark prints a twelve-character block-element sparkline
of the last twelve points:
npx fit-xmr spark observations.csv --metric open_vulnerabilities
# ▆▇█▇▆▅▄▃▃▂▁▁
How to read it:
-
Scale is per-series, not absolute — the lowest of
the last 12 points maps to
▁, the highest to█. Two sparklines from different metrics are not comparable on visual height. - Padding — fewer than 12 points pad with leading spaces, so the bars always right-align in markdown table cells.
-
Flat or single-value series render as a row of
mid-bars (
▄). -
Last 12 points only — a level shift older than 12
observations is invisible. Use
analyzefor shift detection; sparklines are situational awareness.
Pair them with summarize for a status table that reads
at a glance and drills into detail when needed.
Worked example: a security backlog
A team records open vulnerability counts daily into
security/2026.csv. After three months they run:
npx fit-xmr analyze security/2026.csv --metric open_vulnerabilities
The report shows status: signals_present with three
signals on the single metric:
-
A
run_aboveof 55 points covering 2026-01-01 → 2026-02-24. -
A
run_belowof 50 points covering 2026-02-25 → 2026-04-14. -
A
point_below_lnplstreak of 45 points in the post-shift window.
This is the classic shape of a level shift: the
global x_bar sits between the pre-shift and post-shift
levels, so points fall consistently on one side then consistently on
the other. Both runs are real signals — they say the process you
have today is not the process you had in January.
The team annotates the CSV note field on 2026-02-25
with the change that landed (a new pre-merge security gate) and
treats the new level as the process baseline going forward. The next
analysis is run against post-shift data only —
otherwise x_bar keeps averaging across two different
processes and the limits describe neither.
This is the loop: record, analyze, investigate signals, annotate, recompute once a shift is locked in, repeat.
Interpretation guidance
-
Do not react to individual data points when
status: predictable. Routine variation is common-cause noise; treating it as a problem and intervening makes the process worse on average. W. Edwards Deming called this tampering. - Do not set targets based on the natural process limits. They describe what the process does, not what it should do. Targets come from the work; limits describe the work.
-
A series spanning a level shift will surface signals on both
sides
of
x_bar— that's not a bug. Once you confirm the shift is locked in, recompute against the post-shift segment so the new limits describe the new process. -
Annotate the CSV with what you discovered when a
signal appears. The
notefield is the durable record of why the process changed. -
Keep at least 15 points before reading limits.
Below that the report marks the metric
insufficient_dataand computes nothing.
Related
-
fit-xmron npm — installation and changelog. - Trace Analysis — qualitative analysis of agent traces; the same "let the data tell you" posture applied to a different domain.