Skip to content

Accuracy and Pricing

romaingaspard edited this page Jun 16, 2026 · 2 revisions

Accuracy and Pricing

Two questions decide whether this tool is worth trusting: are the token counts right? and is the cost right? They are answered separately — counts come from careful parsing reconciled against ccusage; cost comes from a transparent, read-time pricing grid.

Why the token counts are right

Claude Code writes a single assistant message as several JSONL lines carrying progressive snapshots of the same response — output_tokens grows line by line, and the model can even change mid-message. Naively summing every line double-counts and inflates totals by roughly 2.5×.

The parser counts each response exactly once:

  • Progressive snapshots — for a given message.id + requestId it keeps the largest usage snapshot; ties break to the first line, so a message straddling midnight belongs to its start day.
  • Global dedup — deduplication is across all files, not per session. That is what corrects resumed / --resume sessions, where the same records are replayed into a new file.
  • Fake prompts filteredisMeta entries, <command-name> / <local-command-stdout> blocks, [Request interrupted by user], and the synthetic post-compaction "continuation" message are kept out of prompts.csv and out of categorization, while their token usage is still counted against the session.
  • Sidechain / sub-agent policy — inline isSidechain events and separate subagents/*.jsonl files are parsed and their cost rolled into the parent prompt (so nothing is silently under-counted), but excluded from the parent's assistant_turns / tool_calls.
  • Cache writes split by TTLcache_write_5m (1.25×) vs cache_write_1h (2×); server_tool_use requests counted separately (billed per request).

Reconciliation against ccusage

These totals reconcile bucket-for-bucket with bunx ccusage --json (day × model) on real history. The reconciliation is scripted in scripts/reconcile_ccusage.py — it is not a one-time claim but a re-runnable check.

The parser is also pinned against fixtures per Claude Code version (tests/fixtures/claude-code-<version>/, captured with scripts/capture_fixture.py), so a format change in a new Claude Code release surfaces as a failing test rather than silent drift.

On every run, each command prints its data source, and extract ends with a loud report — files read / skipped, unknown event types, unpriced models, Claude Code versions seen — so a silent format break shows up immediately.

The V7 invariant

tokens.csv carries two dimensions that keep usage attributable and priceable:

  • session_id / model stay denormalized, so pseudo-prompt usage (<session>:_continuation, with no prompts.csv row) is still attributable.
  • is_sidechain (0/1) splits subagent usage as a dimension.

Summing over is_sidechain reproduces per-prompt totals exactly. Any schema change must keep per-prompt sums identical and re-reconcile with ccusage — that is the V7 invariant, and it is tested (requests.csv sums also equal tokens.csv per prompt, exactly).

How pricing works

Costs are computed at read time from raw token counts, never stored. That is why changing a price never requires a re-extract.

The rates live in a generic multi-provider grid, prompt_analytics/data/pricing.yml, loaded and validated by pricing.py. Grid sections:

  • providers: — each a rate card of per-token prices, optionally per_request (for server_tool_use). Two ship by default:
    • anthropic — published API rates.
    • copilot — the GitHub Copilot equivalent, so compare answers "what would this usage cost billed through Copilot instead of the API?"
  • plans: — Claude flat-rate subscriptions, used by break-even.
  • copilot_plans: — GitHub Copilot subscription tiers plus their bundled AI-credit allowance, used by the Copilot cost view (overage beyond the allowance is per-token on the copilot grid).
  • updated_at: — the grid's date stamp.

Model lookup: get_model_pricing() does an exact match first, then the longest-prefix match (version suffixes stripped), so a newly released point version is priced by its family until the grid is updated. Long-context surcharges are handled by is_long_context().

Add your own rate card under providers: (an internal plan, a Bedrock tier, a negotiated rate) or pass --pricing ./my.yml. See CONTRIBUTING.md for the schema and lookup rules.

Drift control: the bundled grid is kept honest by a weekly CI drift job that checks it against LiteLLM and the live GitHub Copilot pricing page, so the defaults don't silently rot.

See Architecture for where pricing sits in the pipeline and Codebase Guide for the files to touch when you add a provider.

Clone this wiki locally