Summary
The pricing table is duplicated across cli.py (Python) and dashboard.py (JS, embedded in HTML_TEMPLATE). They currently agree, but there's nothing in the codebase that enforces that. The existing tests/test_dashboard.py::TestPricingParity regex-parses the JS table out of HTML_TEMPLATE and compares it back to the Python dict, which catches drift but doesn't prevent it — every model addition has to be done twice and remembered to be kept in sync, and any user adding a new model in their fork has to touch both places.
This was previously raised in #7 (closed) — the values were realigned at that time, but the structural duplication wasn't addressed.
Suggested fix
Move the pricing table into a new pricing.py module (Python is the single source of truth) and inject it into the HTML at request time:
# pricing.py
PRICING = {
"claude-opus-4-7": {"input": 5.00, "output": 25.00, "cache_read": 0.50, "cache_write": 6.25},
"claude-opus-4-6": ...,
...
}
def get_pricing(model): ... # moved from cli.py
# dashboard.py
import json
from pricing import PRICING
# HTML template carries a placeholder instead of a hardcoded JS object:
# const PRICING = /*__PRICING_JSON__*/;
HTML_TEMPLATE = r"""...
const PRICING = /*__PRICING_JSON__*/;
..."""
def render_html() -> bytes:
return HTML_TEMPLATE.replace(
"/*__PRICING_JSON__*/",
json.dumps(PRICING),
).encode("utf-8")
# cli.py
from pricing import PRICING, get_pricing
# (delete the local copies)
The JS keys are string-compatible with the Python dict layout, so json.dumps(PRICING) is the JS object literal verbatim.
The existing parity test then degrades to an existence check ("the JS PRICING is generated from pricing.PRICING"), which is what we actually want.
Bonus — ephemeral cache tier accuracy
This is a bigger change so I'm tracking it separately, but worth flagging here because it lives in the same code path: the API distinguishes between 5-minute and 1-hour prompt caching, and they have different write prices (base × 1.25 vs base × 2.0; reads stay at base × 0.1 regardless of TTL). The JSONL records these as usage.cache_creation.ephemeral_5m_input_tokens / ephemeral_1h_input_tokens. The current scanner sums both into a single cache_creation_tokens column and the dashboard prices the lot at the 5-minute rate, which under-counts cost for sessions that use the 1-hour tier (Cowork sessions are a typical example — I see ~$70 missing on ~$480 of haiku traffic in mine).
I'll open a follow-up issue + PR for that once the consolidation lands, since it needs a schema migration.
PR coming.
Severity
Medium. The duplication is a maintenance hazard, not a present-day bug — values currently agree. But it's the kind of thing that silently breaks on the next pricing change.
Summary
The pricing table is duplicated across
cli.py(Python) anddashboard.py(JS, embedded inHTML_TEMPLATE). They currently agree, but there's nothing in the codebase that enforces that. The existingtests/test_dashboard.py::TestPricingParityregex-parses the JS table out ofHTML_TEMPLATEand compares it back to the Python dict, which catches drift but doesn't prevent it — every model addition has to be done twice and remembered to be kept in sync, and any user adding a new model in their fork has to touch both places.This was previously raised in #7 (closed) — the values were realigned at that time, but the structural duplication wasn't addressed.
Suggested fix
Move the pricing table into a new
pricing.pymodule (Python is the single source of truth) and inject it into the HTML at request time:The JS keys are string-compatible with the Python dict layout, so
json.dumps(PRICING)is the JS object literal verbatim.The existing parity test then degrades to an existence check ("the JS PRICING is generated from
pricing.PRICING"), which is what we actually want.Bonus — ephemeral cache tier accuracy
This is a bigger change so I'm tracking it separately, but worth flagging here because it lives in the same code path: the API distinguishes between 5-minute and 1-hour prompt caching, and they have different write prices (
base × 1.25vsbase × 2.0; reads stay atbase × 0.1regardless of TTL). The JSONL records these asusage.cache_creation.ephemeral_5m_input_tokens/ephemeral_1h_input_tokens. The current scanner sums both into a singlecache_creation_tokenscolumn and the dashboard prices the lot at the 5-minute rate, which under-counts cost for sessions that use the 1-hour tier (Cowork sessions are a typical example — I see ~$70 missing on ~$480 of haiku traffic in mine).I'll open a follow-up issue + PR for that once the consolidation lands, since it needs a schema migration.
PR coming.
Severity
Medium. The duplication is a maintenance hazard, not a present-day bug — values currently agree. But it's the kind of thing that silently breaks on the next pricing change.