Files
pxpipe/tests/hero.test.ts
T
teamchong 48f82ffee2 fix(dashboard): hero uses cache-weighted tokens (match Details/Saved)
The session hero computed (1 - sent / raw_count_tokens_baseline) — the same
cache-blind math just removed from the Details panel. It priced the all-text
baseline at full rate, ignoring that most of it would have been cheap
cache-reads, so a warm net-loss session showed a big "fewer tokens" win up top
while Details/Saved showed a loss. Two panels, one session, opposite signs.

- renderSessionSummaryFragment now reads baselineInputWeighted /
  actualInputWeighted (the same cache-weighted pair as Details + the Saved
  column) instead of rawActualTokens / rawBaselineTokens.
- Headline is input-only; dropped the output-on-both-sides lumping (output is
  never "sent" and only dampened the %).
- Honest labels: "effective tokens … after normal cache discounts"; still no $
  assumptions (0.1x/1.25x are token multipliers, not $/Mtok).
- types.ts: corrected stale field comments (raw fields are math-drawer only).
- +4 hero tests pinning direction to the weighted pair and that output can't
  move the headline %.

374 tests pass; typecheck + build clean.
2026-06-19 15:58:22 -04:00

54 lines
2.4 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import { renderSessionSummaryFragment } from '../src/dashboard/fragments.js';
import type { CurrentSessionPayload } from '../src/dashboard/types.js';
/**
* The hero must read the SAME cache-weighted pair as the Details panel + Saved
* column. The old bug divided raw count_tokens (cache-blind) by sent tokens and
* could claim a big "fewer tokens" win on a session the Saved column showed as a
* net loss. These pin direction to `baselineInputWeighted` vs `actualInputWeighted`.
*/
function payload(p: Partial<CurrentSessionPayload>): CurrentSessionPayload {
return { sessionId: 's', baselineMeasuredCount: 1, rawOutputTokens: 139, ...p };
}
describe('renderSessionSummaryFragment hero', () => {
it('shows "fewer tokens" when the weighted image beat weighted text', () => {
const html = renderSessionSummaryFragment(
payload({ baselineInputWeighted: 7000, actualInputWeighted: 1800 }),
);
expect(html).toContain('fewer tokens');
expect(html).not.toContain('more tokens');
expect(html).toContain('74%'); // 1 - 1800/7000
});
it('flips to "more tokens" on a warm net-loss session (matches Saved "-")', () => {
// The exact trap: raw text (e.g. 7.2k) would look like a huge win, but the
// cache-weighted text baseline (1,546) is below what imaging actually sent (1,863).
const html = renderSessionSummaryFragment(
payload({ baselineInputWeighted: 1546, actualInputWeighted: 1863 }),
);
expect(html).toContain('more tokens');
expect(html).not.toContain('fewer tokens');
expect(html).toContain('hero-neg'); // red styling on a loss
});
it('never lumps output into the headline ratio', () => {
// Same input pair, wildly different output — headline % must not move.
const a = renderSessionSummaryFragment(
payload({ baselineInputWeighted: 2000, actualInputWeighted: 1000, rawOutputTokens: 10 }),
);
const b = renderSessionSummaryFragment(
payload({ baselineInputWeighted: 2000, actualInputWeighted: 1000, rawOutputTokens: 9000 }),
);
expect(a).toContain('50%');
expect(b).toContain('50%');
});
it('renders the warming-up state with no measured requests', () => {
const html = renderSessionSummaryFragment(payload({ baselineMeasuredCount: 0 }));
expect(html).toContain('Warming up');
expect(html).not.toContain('fewer tokens');
});
});