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; lnpl floors 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 }. mr is the moving range at that point — useful for "is today's change unusual?".
  • signals — special-cause signals, sorted by start date.
  • statuspredictable, signals_present, or insufficient_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, and length (point count).
  • Trend signals carry from, to, and moves (number of consecutive same-direction transitions; 6 moves = 7 same-direction points).
  • Point and mR signals carry count plus peak (for above-limit) or trough (for below-limit). Consecutive out-of-bounds points are consolidated into one streak with from/to; single-point streaks carry date instead.

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:

  • stablepredictable status, no signals.
  • signals — special cause detected; investigate.
  • chaosmr_above_url present; 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 analyze for 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_above of 55 points covering 2026-01-01 → 2026-02-24.
  • A run_below of 50 points covering 2026-02-25 → 2026-04-14.
  • A point_below_lnpl streak 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 note field 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_data and computes nothing.
  • fit-xmr on npm — installation and changelog.
  • Trace Analysis — qualitative analysis of agent traces; the same "let the data tell you" posture applied to a different domain.