7.5 KiB
What 1 % of a Claude plan window is worth
Claude Code's /usage shows how full the subscription's windows are (rolling
5 h, 7-day week), as whole percentages. The transcripts show what was spent, in
tokens. scripts/usage-calibration.py lines the two up and fits the exchange
rate, so a dollar figure on the dashboard can be read as "% of my week", and
the plan's ceiling can be expressed in tokens.
uv run scripts/usage-calibration.py # personal seat, last 9 days
uv run scripts/usage-calibration.py --since 2026-09-13 --json /tmp/cal.json
uv run scripts/usage-calibration.py --account work --prefix sensor.claude_work_usage
Inputs
- Usage %: Home Assistant long-term statistics (hourly min/max) of the
sensors that the homelab's
claude-usage-notifierpushes (<prefix>_5h,_week,_fable). The script reads the recorder SQLite db read-only, so it goes back to the sensors' creation, beyond the 10-day state history. - Tokens: the account's conversations listed by
/api/conversations?account=(the backend resolves each conversation's account), parsed from the archive, subagents included. Records are de-duplicated on (message id, request id) and priced at list price (the script's own table; see "Pricing" below).
Method
The data is binned by hour. A window's % only rises between resets, so each
bin's increase is the usage spent in it. Bins that contain a reset are dropped,
and so are 5 h bins above 100 % (overage). The fit is Δ% = k·cost + c, by
non-negative least squares. c soaks up usage the archive can't see
(claude.ai, the desktop app, a Mac that isn't paired as a worker). It came out
at ≈ 0 once both Macs were paired.
Unweighted list-price dollars leave a drift. Through a Fable-heavy week the cumulative $/% climbed from $13.5 to $20 as the mix moved to Opus. A Fable multiplier removes it. It is chosen by grid search on the combined R² of the 5 h and week fits, and with it the cumulative ratio stays flat at $21–23/%. Other weightings were tested and lost: cache reads free, cache reads at 0.1× input, cache reads at 1× input, Opus 5.5 priced as Opus 5, and raw token counts.
Use 1 h bins. With 3 h bins most 5 h resets fall inside a bin, which then gets dropped, and the 5 h fit collapses.
Calibration — personal Max plan (€200/month), 2026-10-12
Window: 2026-10-04 18:00 → 2026-10-12 (Opus 5.5 / Fable 5.1 era), 1 h bins. Intervals are 90 % CIs from a 300-draw day-block bootstrap.
| API $ at list price | tokens at the real Opus 5.5 mix¹ | |
|---|---|---|
| 1 % of the 5 h window | $5.6 [5.4–6.8] | ≈ 17 Mtok |
| 1 % of the week | $22.1 [21.1–24.6] | ≈ 68 Mtok |
| Fable multiplier | ×1.75 [1.6–2.5]: 1 % week ≈ $13 of Fable 5.1 | ≈ 14 Mtok of Fable¹ |
| full 5 h window | ≈ $560 ≈ 25 % of a week | |
| full week | ≈ $2,200 | ≈ 6.8 Gtok |
| 100 % of every week for a month (×4.35) | ≈ $9,600 (≈ $5,700 if all Fable) | ≈ 30 Gtok |
¹ Agentic Claude Code traffic is 98 % cache reads by tokens. On Opus 5.5 they are about 61 % of the dollars, cache writes 32 % and output 8 %. The same 1 % of the week buys ≈ 1.1 M Opus 5.5 output tokens, or 5.5 M fresh input, or 110 M cache reads.
- No separate Fable bucket. The
seven_day_<model>key never appeared in the status line'srate_limitsfrom 2026-07-17 to 2026-10-12, despite heavy Fable 5 and Fable 5.1 use. Fable draws on the same 5 h and week windows, about 1.75× faster per list-price dollar than Opus 5.5. - The week binds, not the 5 h window. A full 5 h window is ≈ 25 % of the week, and a week has 33.6 of them.
- Earlier eras differ. In Jul–Aug (Opus 4.8 / Opus 5 / Fable 5), unweighted fits gave ≈ $30 per 1 % of the week. Limits and prices move with model launches, so re-run the script after one.
Plan units: what the limits actually count
Re-fitting the whole history (Jul → Oct 2026) per model version shows the
token allowance never moved — only its list-price value did. When every new
model is metered at its predecessor's price (Opus 5.5 as Opus 5, Fable 5.1
as Fable 5, Sonnet 5.5 as Sonnet 5, Haiku 5.5 as Haiku 4.5) and Fable/Mythos
tokens weigh ×1.75, one constant explains July, August and October alike:
≈ 42–52 plan units per 1 % of the personal week, ≈ 8–12 per 1 % of 5 h. That
is pricing.plan_units. Effort changes how many tokens a turn spends, not the
rate (the per-effort fits all overlap). The work seat meters the same way with
smaller buckets: ≈ 24 units per 1 % of its week (½ the personal), ≈ 4 per 1 %
of its 5 h (⅓).
| version | used | list $ per 1 % week | list $ per 1 % 5h | Mtok per 1 % week |
|---|---|---|---|---|
| Opus 4.8 | Jul | 35 | 6.8 | 46 |
| Opus 5 | Jul–Oct | 42 | 9.1 | 59 |
| Opus 5.5 | Sep → | 22 | 5.6 | 69 |
| Fable 5 | Jul–Aug | 26 | 5.2 | 16 |
| Fable 5.1 | Sep → | 14 | 3.6 | 16 |
| Sonnet 5 | Jul–Oct | 36 | 6.9 | 123 |
In the app (since 2026-10-12)
- Exact prices.
backend/conversations/pricing.pykeeps a per-version price book (MODEL_PRICES, stampedPRICE_BOOKon every summary aspriceBook): each model's own cache-read rate, 5 m / 1 h cache writes, fast mode ×2, Haiku 5.5's >100K-prompt tier. Unknown ids price as the newest of their family. - One usage per API call. Claude Code writes an assistant message as one
record per content block, each repeating the call's full
usage; the parser used to sum them all (tokens ≈ 1.93× too high). It now prices a call once, keyed on (message id, request id), and re-homes that charge onto the tool the call drove. With the old family prices on top, the dashboard used to read ≈ 3.7× list price. - Per conversation:
planUnits,planPct({week, h5}— the units over the account's calibration in force),byModel(per model id: turns, tokens, list $, plan units, usage, and the same per--effortlevel),meterBins(epoch hour → [list $, plan units]),fastCalls. - The official % is recorded by the backend (
backend/usage/): every change of each account'srate-limits.json(mounted read-only from~/.claude{,-work}/usage-state,USAGE_STATE_DIRS), plus a 30 min heartbeat, kept forever inusage_limits. History before that came from HA:scripts/usage-backfill-ha.pyposts HA's hourly statistics to/api/usage/import(idempotent). - Daily recalibration (
usage/calibrate.py):Δ% = units/k + cover the last 14 days, hourly; a fit becomes the rate in force only when it is trusted (≥ 24 busy hours, R² ≥ 0.5, within ×3 of the seed rate — usage on an unpaired machine in the same hours as lab work fits cleanly at a rate 10× off). A trusted move of more than 20 % pushes a notification (a model launch or a limits change).POST /api/usage/calibrate {"backfill": true}adds the weekly fits over the whole history (the $ per 1 % over time chart). - Untracked usage: per day, the observed rise minus what the archived spend predicts — claude.ai, the desktop app, a machine that isn't paired.
- Where it shows: the conversation card chip (
≈0.4% wk), the Details drawer's Plan usage card (per-model / per-effort table), the Conversations dashboard (spend by model version, $ per 1 % over time), the Plan usage dashboard (/dashboards/plan: gauges, history, observed vs predicted, calibration, recalibrate), and every--finalnotification (· $3.20 · ≈0.4% wk).
scripts/usage-calibration.py stays as the offline cross-check (pandas/scipy,
per-version fits, bootstrap).