docs: add design doc for unified cost calculation
Shared pricing module replacing dual cost calculation systems (jsonl.ts hardcoded + sessionAnalyzer.ts LiteLLM) with a single src/shared/utils/pricing.ts used by both main and renderer processes. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
3749d7e41a
commit
ec42e92bb3
1 changed files with 71 additions and 0 deletions
71
docs/plans/2026-02-23-unify-cost-calculation-design.md
Normal file
71
docs/plans/2026-02-23-unify-cost-calculation-design.md
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
# Unify Cost Calculation — Design Document
|
||||
|
||||
**Date:** 2026-02-23
|
||||
**Branch:** `feat/unify-cost-calculation`
|
||||
**Related:** PR #60 (Session Analysis Report), PR #65 (Cost Calculation Metric), Issue #72 (Plan Usage Tracking)
|
||||
|
||||
## Problem
|
||||
|
||||
Cost calculation exists in two places with different pricing data and logic:
|
||||
|
||||
1. **Main process** (`src/main/utils/jsonl.ts`): Uses LiteLLM-sourced `pricing.json` (206 models, tiered 200k-token pricing). Populates `SessionMetrics.costUsd` for the chat UI.
|
||||
2. **Renderer** (`src/renderer/utils/sessionAnalyzer.ts`): Uses a hardcoded 6-model pricing table with no tiered pricing. Generates per-model cost breakdown for the Session Report.
|
||||
|
||||
The two systems can produce different cost numbers for the same session and will drift further as models change.
|
||||
|
||||
## Solution
|
||||
|
||||
Create a single shared pricing module that both processes import.
|
||||
|
||||
### New Module: `src/shared/utils/pricing.ts`
|
||||
|
||||
**Exports:**
|
||||
|
||||
| Export | Description |
|
||||
|--------|-------------|
|
||||
| `ModelPricing` | Interface for per-model rates (input, output, cache read, cache creation, plus tiered variants) |
|
||||
| `getPricing(modelName: string): ModelPricing \| null` | Model lookup: exact match, lowercase, case-insensitive scan |
|
||||
| `calculateTieredCost(tokens, baseRate, tieredRate?): number` | Applies 200k-token tier threshold |
|
||||
| `calculateMessageCost(model, inputTokens, outputTokens, cacheReadTokens, cacheCreationTokens): number` | Computes cost for a single API call |
|
||||
|
||||
**Pricing data:** Static `import pricingData from '../../../resources/pricing.json'` with `resolveJsonModule: true`. Replaces `fs.readFileSync` runtime loading.
|
||||
|
||||
### Consumer Changes
|
||||
|
||||
**`src/main/utils/jsonl.ts`:**
|
||||
- Remove: `ModelPricing` interface, `loadPricingData()`, `calculateTieredCost()`, `getPricing()`, `fs`/`path` imports
|
||||
- Keep: `calculateMetrics()` function
|
||||
- Change: inline cost loop body → call `calculateMessageCost()` from `@shared/utils/pricing`
|
||||
|
||||
**`src/renderer/utils/sessionAnalyzer.ts`:**
|
||||
- Remove: `MODEL_PRICING` table (~40 lines), `DEFAULT_PRICING`, local `getPricing()`, local `costUsd()`
|
||||
- Change: calls at lines 476 and 900 → `calculateMessageCost()` from `@shared/utils/pricing`
|
||||
|
||||
**Tests:**
|
||||
- `test/main/utils/costCalculation.test.ts` → update to test shared module functions
|
||||
- `test/renderer/utils/sessionAnalyzer.test.ts` → mock `@shared/utils/pricing` instead of local functions
|
||||
- New `test/shared/utils/pricing.test.ts` for the shared module
|
||||
|
||||
### Pricing JSON Import Strategy
|
||||
|
||||
- `pricing.json` stays in `resources/` for Electron's `extraResources` packaging
|
||||
- Both Vite (renderer) and electron-vite (main) resolve the JSON import at compile time
|
||||
- Remove the `fs.readFileSync` dev/prod path logic from `jsonl.ts`
|
||||
|
||||
### Fallback Behavior
|
||||
|
||||
- `getPricing()` returns `null` for unknown models
|
||||
- `calculateMessageCost()` returns `0` for unknown models (matches current `jsonl.ts` behavior)
|
||||
- Session analyzer callers can apply a default if needed
|
||||
|
||||
### What Changes for Users
|
||||
|
||||
- Report costs become more accurate (tiered pricing, 206 models instead of 6)
|
||||
- Cost numbers between chat view and Session Report now agree exactly
|
||||
- No UI changes — same components, same layout
|
||||
|
||||
## Out of Scope
|
||||
|
||||
- Plan usage tracking (see Issue #72 — pending community feedback)
|
||||
- New UI surfaces for cost display
|
||||
- Changes to the `costFormatting.ts` shared utility
|
||||
Loading…
Reference in a new issue