Repository navigation
Release v1.7.0
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% $504Three 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-policychooses 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 allocateapportions 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.
--basisselects the measured quantity —output(default),value,wall,tokensorhuman— and every run cross-checks all five against each other.--gap-policymakes the handling of pruned history an explicit choice rather than a silent zero.- Table, JSON and CSV output, composing with the existing
--month,--sinceand--untilwindow flags.
Notes for review
src/pricing.rscarries the published list rates, stamped withRATES_AS_OFso 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 wholeReport; it is a pure function of the grouped rows and is unit tested directly. ReportRowgains#[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.