From ec42e92bb355200185d8810e617eeb8b88553106 Mon Sep 17 00:00:00 2001 From: Paul Holstein <44263169+holstein13@users.noreply.github.com> Date: Mon, 23 Feb 2026 11:43:35 -0500 Subject: [PATCH] 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 --- ...026-02-23-unify-cost-calculation-design.md | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 docs/plans/2026-02-23-unify-cost-calculation-design.md diff --git a/docs/plans/2026-02-23-unify-cost-calculation-design.md b/docs/plans/2026-02-23-unify-cost-calculation-design.md new file mode 100644 index 00000000..76a91505 --- /dev/null +++ b/docs/plans/2026-02-23-unify-cost-calculation-design.md @@ -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