dashboard: fix bogus savings math + emit compressed_chars

The dashboard headline was showing 0% saved because the math was
apples-to-oranges:
  - orig_chars = static slab + tool docs ONLY (~135 KB)
  - image_count = ALL images including reminders + tool_results (~33)
  - txtReplaced = 135k/4 = 33.7k tokens
  - imgTokensEst = 33 × 2500 = 82.5k tokens
  - extraText = max(0, 33.7k - 82.5k) = 0  ← clamped negative
  - baseline == paid → 'saved 0%' headline despite real savings happening

Two fixes:

1. Track 'compressedChars' separately from 'origChars'. It accumulates
   only when a block actually image-renders (passes the break-even gate
   AND any per-block min thresholds). Includes the static slab + all
   compressed reminders + all compressed tool_results.

2. Remove the Math.max(0,...) clamp in baselineCost(). Negative savings
   surface honestly — they're the signal that small-block compressions
   are cost-bleeding, which the break-even gate is supposed to prevent.

Wires:
  - TransformInfo.compressedChars (new field, mandatory).
  - All 4 image-render success paths accumulate (static slab + reminder
    + tool_result string branch + tool_result array branch).
  - TrackEvent.compressed_chars (new optional field, emitted when > 0).
  - dashboard.baselineCost() takes compressedChars now; clamp removed.
  - sessions.aggregateSessions() also uses compressed_chars + honest math.

229/229 tests passing. Bundle stays at 496 KB gzipped.

Post-restart, the dashboard 'tokens saved' will show real numbers
including negatives. Empirically expect ~1-5% net positive in
steady-state cache-hit traffic.
This commit is contained in:
teamchong
2026-05-18 23:01:12 -04:00
parent 4f5b10aaee
commit 8fbd53ea3e
5 changed files with 51 additions and 17 deletions
+5 -9
View File
@@ -159,17 +159,13 @@ export async function aggregateSessions(
if (s.project === undefined && ev.cwd) s.project = ev.cwd;
if (
ev.compressed &&
typeof ev.orig_chars === 'number' &&
typeof ev.compressed_chars === 'number' &&
typeof ev.image_count === 'number'
) {
// Honest savings math, mirrors src/dashboard.ts:
// textTokens = orig_chars / 4 (Anthropic ~4 chars/token rule)
// imageTokens = image_count × 2500 (empirical per-image cost)
// tokensSaved = textTokens − imageTokens (CAN be negative when
// small per-block compressions cost more than they save)
// Previous formula compared chars to PNG bytes (mismatched units) AND
// clamped via `if (saved > 0)` which hid the cost-bleed entirely.
const textTokens = ev.orig_chars / 4;
// Honest savings math (mirrors src/dashboard.ts:baselineCost).
// compressed_chars is the actual text we IMAGE-encoded; orig_chars
// would include text-only blocks that didn't contribute to image_count.
const textTokens = ev.compressed_chars / 4;
const imageTokens = ev.image_count * 2500;
const tokensSaved = textTokens - imageTokens;
s.tokensSavedEst += Math.round(tokensSaved);