Skip to content

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.

Terminal window
node scripts/dashboard/series.js # one line per series per project
node scripts/dashboard/series.js --json # the bob-series/1 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 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.

A merged requirement’s point equals the CLI’s total for the same selection:

Terminal window
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.

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.