feat(evalboard): generate the cost table from pricing.py, and chart in every edition - #70
Open
uipreliga wants to merge 1 commit into
Open
feat(evalboard): generate the cost table from pricing.py, and chart in every edition#70uipreliga wants to merge 1 commit into
uipreliga wants to merge 1 commit into
Conversation
uipreliga
requested review from
akshaylive,
bai-uipath and
tmatup
as code owners
July 30, 2026 23:53
|
Claude finished @uipreliga's task in 1m 33s —— View job 📋 Review Task List
|
Contributor
|
@uipreliga Does this have conflicts with #66 ? |
Mihaiii
reviewed
Jul 31, 2026
| cacheReadPerMTok: 0.25, | ||
| }, | ||
| "gpt-5.6-luna": { | ||
| inputPerMTok: 1, |
There was a problem hiding this comment.
This is incorrect or at least depends on the provider. I'd look for some REST endpoint to call that gets the data from a database.
This comment was marked as off-topic.
This comment was marked as off-topic.
…ery edition Rebuilt on top of #69, which independently landed the multi-harness overview this branch was also building. Everything #69 covers is dropped — the harness table and its colours, the multi-series chart refactor, the badge/selector changes, the All chip, whole-page ?h= scoping. What remains is the work #69 does not have. Pricing SSOT. Per-MTok rates were hand-copied from src/coder_eval/pricing.py into lib/pricing.ts, which is how 7 rates drifted (Opus 4.6/4.7/4.8 billed at 3x actual) and how a not-mirrored allowlist grew to suppress 17 models — two of which real runs use, so the board rendered "—" for their cost. scripts/gen-pricing.mjs now emits lib/pricing.generated.ts from the Python table, and the parity test asserts exact match, so the allowlist is gone and staleness is the only failure mode left. The test imports readTable, not parsePythonTable: comparing two products of the same regex cannot see a row that regex skipped, so readTable cross-checks matched rows against the ModelPricing( count and refuses to emit on a mismatch. The one deliberate gap is EXCLUDED_MODELS, which reconciles generation with #66's decision to stop statically pricing the OpenRouter open-weight models: OpenRouter routes per request, so a headline rate is wrong, and the detail view shows captured per-call cost instead. pricing.py still prices them for the Python max_usd fallback, so generating blindly would have silently reintroduced exactly what #66 removed. Guarded both ways — a stale entry throws, a reintroduced static rate fails the parity test — and the Bedrock open-weight models are pinned as NOT excluded, since those are fixed-rate and must stay priced. Analytics in every edition. The charts and tag rail sat behind isInternal, a gate dating to the initial public release rather than to anything in the block: it renders whatever runs the instance is pointed at, so a default-configured board showed only a run list. The harness selector is ungated with it — the charts name a harness per line, so gating the only control that isolates one leaves an OSS instance able to see a harness but not select it. The Harness column in the run table stays gated separately. windowHarnesses. The chip set came from listRecentHarnesses' fixed-count discovery scan, so a harness that ran inside the 30d window but not recently got a chart line with no chip to select it. getOverview now also returns every harness in the window, captured BEFORE the harness filter — invariant under selection by construction, where deriving it after the filter would collapse it to the active harness and delete every other chip on click. OverviewData.harnesses keeps its meaning (the plotted set, so "all N harnesses" still equals the lines drawn); the new test contrasts the two derivations over the same scoped input, so it fails if the chips are ever re-pointed at the filtered set. Ad-hoc runs are scoped by ?h= like the rest of the page. They carry a harness stamp like any other run and that section has no Harness column, so leaving it unfiltered showed codex uploads under a Claude Code scope with nothing on screen to explain why. module-boundaries.test.ts pins two bundle invariants previously held by comments alone, each worth a measured ~105 kB First Load JS and invisible to tsc, the suite, and a green build: lib/harness.ts stays a true leaf (client components import it for values; lib/overview.ts reaches node:fs and @azure/storage-blob), and tag-rail.tsx stays chart-free (it renders on /runs/[id] and /trends, which have no chart). Verified against the build: /runs/[id] 167 kB, /trends 119 kB. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
uipreliga
force-pushed
the
feat/evalboard-multi-harness-charts
branch
from
August 1, 2026 02:56
7bd3ef5 to
24098cb
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Goal
Write the evalboard's per-MTok rates in exactly one file in one language, and stop the analytics block being hidden on a default-configured instance.
The cost bug this fixes
lib/pricing.tsdeclares itself a hand-copy ofsrc/coder_eval/pricing.py. Measured againstmaintoday, that 30-row copy has drifted three ways:claude-opus-4-8,4-7,4-6and4-5-20251101read Opus 4.1's legacy $15/$75 against an actual $5/$25 — overstating cost 3× on every rendered estimate.claude-opus-4-6appears in real run data. Alsoclaude-haiku-4-5-20251001($0.80/$4 vs $1/$5) andgemini-3-flash-preview($1.5/$9 vs $0.50/$3).DELIBERATELY_UNMIRROREDallowlist; the other 17 drifted past it unnoticed.claude-opus-4-6-20250514,claude-sonnet-4-6-20250514— absent from the Python table, and redundant given the undated fallback.The allowlist meant to hold the line had failed twice over: 17 models drifted past it, and 2 of its 8 entries were models real runs use (
gpt-5.6-terrais the single most-run model in the recorded data), so it was actively suppressing cost for runs the board does execute.Rather than hand-copy better — which converts a one-time copy error into a recurring obligation to retype 53 rows × 4 numbers across two languages — rates are generated into a checked-in artifact.
pnpm gen:pricingafter editingpricing.py. No Python file is modified by this PR; if a rate is wrong there, that is a separate backend change.The drift guard is not self-blind
pricing-parity.test.tsimportsreadTable, notparsePythonTable. Comparing two products of the same regex cannot detect a row that regex skipped — the row would be missing from the artifact and from the expectation, so deep-equal passes while the board renders "—" for that model forever.readTablecross-checks matched rows against the count ofModelPricing(constructions in the file and refuses to emit on a mismatch. Generation also makes exact-match affordable, so the allowlist is gone entirely.Reconciling with #66's per-call costs
One deliberate gap remains, and it is the reason a naive generator would have been a regression. #66 removed the three OpenRouter open-weight models from this table on purpose: OpenRouter routes per request, so a static headline rate is wrong, and the detail view now shows each call's captured actual cost instead.
pricing.pystill prices them for the Pythonmax_usdfallback — so generating blindly would have silently reintroduced exactly what #66 removed, putting a confidently wrong estimate beside the measured one in the per-message Cost column, the Tokens↔USD toggle andthinkingSim.EXCLUDED_MODELSin the generator carries that rationale and is guarded both ways: a stale entry (an idpricing.pyno longer prices) throws rather than silently outliving its model, and a reintroduced static rate fails the parity test. The Bedrock open-weight models are pinned as not excluded — those run at fixed rates with no per-call capture, so static pricing is correct and required there.The analytics block is no longer edition-gated
The charts and tag rail sat behind
isInternal, so a default-configured instance — including the deployed board — showed only the run list. That gate dates to the initial public release, not to any recent change. Nothing in the block is UiPath-specific: it renders whatever runs the instance is pointed at, so an OSS clone charts its own results.The harness selector is ungated with it. The charts name a harness per line, so gating the only control that isolates one leaves an OSS instance able to see a harness but not select it. The run table's Harness column stays gated separately, so on an OSS instance the chart legend can name a harness the table has no column for.
Two scoping fixes on top of #69
windowHarnesses— the chip set came fromlistRecentHarnesses' fixed-count discovery scan, so a harness that ran inside the 30d window but not recently got a chart line with no chip to select it.getOverviewnow also returns every harness in the window, captured before the harness filter — invariant under selection by construction, where deriving it after the filter would collapse it to the active harness and delete every other chip on click.OverviewData.harnesseskeeps its meaning (the plotted set, so "all N harnesses" still equals the lines drawn). The new test contrasts the two derivations over the same scoped input, so it fails if the chips are ever re-pointed at the filtered set.?h=like the rest of the page. They carry a harness stamp like any other run and that section has no Harness column, so leaving it unfiltered showed codex uploads under a Claude Code scope with nothing on screen to explain why.Bundle-boundary guards
module-boundaries.test.tspins two invariants previously held by comments alone, each worth a measured ~105 kB First Load JS and invisible totsc, the suite, and a greennext build:lib/harness.tsstays a true leaf (zero imports). Client components import it for values, andlib/overview.tstransitively reachesnode:fsand@azure/storage-blob— one value import from there drags that graph into the browser bundle. Type-only imports erase; values do not.tag-rail.tsxstays chart-free. It renders on/runs/[id]and/trends, which have no chart at all; the tempting regression is reusing the overview's sibling legend primitives, which pull the chart module and recharts with them.Verification
pnpm tsc --noEmitclean ·pnpm next buildclean ·pnpm gen:pricingidempotent/runs/[id]167 kB,/trends119 kB First Load JS — recharts is not leaking onto the chart-less pagesModelPricing(row refuses to emit rather than writing a partial tableNothing in
.github/workflows/runs theevalboard/suite —grep -rn evalboard .github/workflows/yields one hit, a comment sayingevalboard/pnpm-lock.yamlis out of scope. So the 439 tests and the pricing drift guard are green-by-absence on this PR, and the drift class this PR removes stays undetected going forward: a rate edited inpricing.pywithoutpnpm gen:pricingwill pass CI. That is precisely how the 3× Opus rates accumulated.Deliberately not fixed here — adding a required check to
pr-checks.ymlis a repo-wide CI policy change affecting every contributor's PR, outside a change scoped toevalboard/. The concrete job to add is the top item in.claude/harness-candidates.md, alongside four other deferred guards recorded by this PR.Not in scope
Non-Claude rates (
gpt-5.x,gemini-3.x, Bedrock open-weight) are generated faithfully but were never verified against their vendors — generation makes the mirror faithful, not the source right.normalizeModel(TS) and_normalize_model(Python) have drifted: TS strips a trailing-YYYYMMDDand Python does not, so a dated id can price on one side and not the other; generation shares rate values, not lookup logic. Plugin rates registered viaregister_pricing(e.g.coder_eval_uipath) cannot reach the generated table at all, since the generator parsespricing.py's literal table only. Introductory pricing (Sonnet 5 at $2/$10 through 2026-08-31 against a $3/$15 list) would need time-dependent rates in the Python schema. All four are written up in.claude/harness-candidates.md.