Skip to content

Release v1.7.0

Choose a tag to compare

@github-actions github-actions released this 02 Sep 15:20
71ac162

Summary

Flat-rate plans are bought per vendor and spent across every project. workstats allocate answers what share of that spend one project accounts for — the question a reimbursement claim turns on.

It splits each vendor's pool separately and weighs it by how many plans that vendor holds, rather than pooling every token together. With 2 Claude and 4 Codex plans, 44% of the Claude pool and 41% of the OpenAI pool are worth different amounts; pooling the tokens would have reported a single misleading 43%.

$ workstats allocate -p Ada --sub claude=2 --sub codex=4 --month 2026-08

  ALLOCATION  Ada
  2026-08 · 6 subscriptions @ $200/mo · $1,200 billed · basis: output tokens

  month     family   subs      project         pool    share       owed
  ────────────────────────────────────────────────────────────────────
  2026-08   claude      2        65.5M       147.5M    44.4%       $178
  2026-08   openai      4        45.8M       112.1M    40.8%       $326
  ────────────────────────────────────────────────────────────────────
  ATTRIBUTABLE                                        42.0%       $504

Three failure modes share one shape — a silent zero that reads as "nothing to claim" when it means "nothing was seen" — and none of them are reported quietly:

  • Pruned history. A month whose transcripts are gone is not a month of no work. --gap-policy chooses explicitly between excluding it, claiming nothing for it, or imputing that vendor's mean share.
  • Clients billing on their own seat. A Copilot seat is not a Claude or ChatGPT plan even when it runs their models. Copilot forms its own pool instead of diluting one it was never billed to — which also stops a single stray Copilot session from making a month of missing vendor history look covered.
  • A misspelled project. Named, with the nearest real repository offered.

Every run prints all five bases side by side, so a share that depends on the metric is visible rather than chosen quietly. Human time is not the default and is marked estimated: it is inferred from prompt counts and session edges, so orchestration-heavy work books more apparent attention per real hour than one long session does.

Added

  • workstats allocate apportions flat-rate subscription spend to one project, splitting each vendor pool separately and weighting it by plans held.
  • Per-model breakdown behind every share, with models weighed by published list rate so a million Opus tokens and a million Haiku tokens are not equal claims on a plan.
  • --basis selects the measured quantity — output (default), value, wall, tokens or human — and every run cross-checks all five against each other.
  • --gap-policy makes the handling of pruned history an explicit choice rather than a silent zero.
  • Table, JSON and CSV output, composing with the existing --month, --since and --until window flags.

Notes for review

  • src/pricing.rs carries the published list rates, stamped with RATES_AS_OF so a stale table is visible in output rather than silent. They are used as relative weights and as a stated ceiling, never as an amount owed.
  • Allocation takes &[ReportRow] rather than a whole Report; it is a pure function of the grouped rows and is unit tested directly.
  • ReportRow gains #[derive(Default)] so tests can build rows without restating every field.
  • 282 tests pass (17 new). Two of them are regression tests for bugs found during development: the cross-check row for the chosen basis contradicting the headline it corroborates, and Copilot usage being counted into the vendor pool.