Cost and Churn Series
Cost and Churn Series
Section titled “Cost and Churn Series”scripts/dashboard/series.js builds one bob-series/1 document: where the
money goes and how the work churns, for every project registered on this
machine, over a 30-day window (#2072). It is data for sparklines. Every
figure reconciles with a CLI that already exists: dollars come from
token-report.js’s model-aware rate table (#1805) through the same functions
token-report.js calls, never a second price list.
Commands
Section titled “Commands”node scripts/dashboard/series.js # one line per series per projectnode scripts/dashboard/series.js --json # the bob-series/1 documentDocument
Section titled “Document”{ "schema": "bob-series/1", "generated_at": "2026-09-13T22:00:00.000Z", "refresh_s": null, "machine": "farm-01", "window": { "days": 30, "from": "2026-08-15", "to": "2026-09-13" }, "pricing": { "as_of": "2026-06-24", "unknown_policy": "upper-bound: …" }, "projects": [ { "name": "bigbrain", "repo": "paulirv/bigbrain", "series": { "tokens_per_day": { "status": "ok", "reason": null, "shape": "daily", "unit": "tokens", "source": "~/.claude/token-usage.jsonl (session records, total_tokens)", "as_of": "2026-09-13T22:00:00.000Z", "value": 15000, "runs": 3, "points": [{ "x": "2026-08-15", "value": 0 }, "…30 points"] }, "ci_duration": { "status": "unavailable", "reason": "rate-limited", "shape": "events", "unit": "seconds", "source": "gh run list --repo paulirv/bigbrain", "as_of": "…", "value": null, "points": [] } } } ]}Every series has the same frame:
| Key | Meaning |
|---|---|
status |
ok, not-measured (the source is absent or was not read), or unavailable (the source failed; gh failures use the #1968 reason names) |
reason |
Why there is no data. null when status is ok |
shape |
daily (one point per day of the window; a day with nothing is a measured 0), weekly (7-day buckets), events (one point per run, requirement or CI run) or current (one point: the count now) |
unit |
tokens, usd, ratio, recurrences, reruns, seconds or releases |
source |
Where the figures were read |
as_of |
When they were read (the document’s clock) |
value |
The series’ summary figure (below). null when there is nothing to summarise |
points |
[{ x, value, … }]: x is a YYYY-MM-DD day or an ISO instant |
A series whose status is not ok has value: null and no points. It renders
as not measured, never as a flat line that reads as zero.
Series
Section titled “Series”| Series | Shape | value |
Read from |
|---|---|---|---|
tokens_per_day |
daily | total tokens in the window (runs: session count) |
~/.claude/token-usage.jsonl session records whose project names the repo (owner/repo) or the project name |
usd_per_day |
daily | total USD. qualified: false when any record carries no priced model (an upper bound, #1805). decision_split carries the project’s decision-shaped share by backend as decision-split/1, counting only the seam calls that joined one of its sessions (token-monitoring, #2983) |
the same records, estimateCost |
usd_per_run |
events | mean USD per session. Each point’s ref is the session’s requirement |
the same records |
usd_per_merged_req |
events | mean USD per priced merged requirement. merged: issues referenced; unpriced: merged issues with no session |
the same records, joined on each record’s requirement to the #N references of default-branch commits in the window (a Merge … subject’s #N is a PR and is skipped) |
fix_to_feat |
weekly | fix commits per feat commit (fix, feat counts beside it). null for a span with no feat commit |
conventional-commit prefixes (fix:, fix(…), fix!) on the default branch: origin’s HEAD, else main, else master, else HEAD |
harness_defect_recurrences |
current | recurrences recorded now (open, unreported beside it) |
the project’s warp-drive state file, through harness-defect-summary.js stateSummary, the reader audit.js snapshot uses for harness_defects (#1838). The state file keeps no history, so this is a count, not a trend |
flaky_reruns |
daily | recurrences of quarantined checks last seen in the window (issues lists them) |
one gh search issues over the registry’s owners, kept to bug issues carrying quarantine-report.js’s <!-- quarantined-check: … --> marker (#1979), bucketed by last seen |
ci_duration |
events | median seconds of completed runs in the window (runs; truncated at 100) |
gh run list --repo <owner/repo>, updatedAt − startedAt |
releases |
daily | vX.Y.Z tags cut in the window (latest beside it) |
git for-each-ref refs/tags, creator date |
A project without a GitHub origin remote has ci_duration and flaky_reruns
not-measured. A missing token log makes all four token and USD series
not-measured, naming the file.
Reconciling with token-report
Section titled “Reconciling with token-report”A merged requirement’s point equals the CLI’s total for the same selection:
node scripts/warp-drive/token-report.js --project paulirv/bigbrain --issue 2071 --since 2026-08-15T00:00:00Z --json | jq .totals.usd--issue <N> (added by #2072) keeps the sessions whose record names
requirement N. With no --last, it reports every one of them.
--project is a partial match, so name the repo exactly. tests/test-series.js
asserts the equality against a fixture token log and git history.
Served by the dashboard
Section titled “Served by the dashboard”bob dash serve (the one dashboard server, scripts/dashboard/server.js)
serves the document at /series.json. bob viz
draws it on /bob.
| Behaviour | Detail |
|---|---|
| Refresh | Rebuilt at most once every refresh_s seconds (DASH_SERIES_REFRESH_S, default 300), however many tabs poll it: a build runs one issue search and one gh run list per repo. A request that arrives during a build waits for that build instead of starting another. |
| Clock | The window ends now. DASH_SERIES_NOW (an ISO time) pins it instead, so a test over fixed-date commits stays true as the calendar moves. |
| Page | /bob re-reads /series.json every refresh_s. Each tile on the serving machine gains spend 30d and tokens 30d rows (a sparkline and the total), and the churn strip under the grid shows one row per project: usd_per_merged_req, usd_per_run, fix_to_feat, harness_defect_recurrences, flaky_reruns, ci_duration, releases. Every column sorts, by clicking its header or from the sort by select, and the strip opens sorted by usd_per_merged_req, largest first (#3097). |
| gh | The forge reads use the server’s own gh runner, so they count toward its gh liveness tracker (#1968). An unreadable ci_duration or flaky_reruns carries the server’s reason, and the document gains the same gh evidence block as the /warp panes. |
| Render | scripts/dashboard/bob-series.js, served as /bob/series.js, is a pure function of the document. A sparkline places the points evenly, from 0 to the highest value, with a gap at a null point and a dot on the last known one. A current series is a number, not a line. tests/test-series.js snapshots its output (tests/fixtures/bob-viz/series.html). |
| Failure | If the document cannot be built at all, the response has projects: [] and an error, and the strip shows the error. |
See also
Section titled “See also”- Token Monitoring: the token log and
token-report.js - Notices: the
bob-notices/1inbox built the same way - Fleet Feed: the document the
/bobtiles draw - bob viz: the page these series are drawn on