Ingest-Time Cost Semantics#
Overview#
Decant computes session cost once, at ingest time, and writes it permanently to the estimated_cost_usd column on the session row. No background job or pricing-table update ever rewrites historical rows.
This is an intentional design choice: cost figures are point-in-time snapshots that reflect the pricing table in effect when a session was first ingested.
How Cost Is Calculated#
The entry point is estimateCost(model, usage, pricing) in src/cost.ts. It delegates to estimateCostParts, which applies per-million-token rates across four billing dimensions:
| Dimension | Rate field |
|---|---|
| Input tokens | inputPerMtok |
| Output tokens | outputPerMtok |
| Cache read tokens | cacheReadPerMtok |
| Cache creation tokens (5-min TTL) | cacheWritePerMtok |
| Cache creation tokens (1-hour TTL) | cacheWrite1hPerMtok |
For Claude, cache-read is 10% of input rate and cache-write is 1.25× (5-min) or 2× (1-hour) of input rate. For OpenAI models prior to GPT-5.6, cache writes carry no additional fee; GPT-5.6 bills cache writes at 1.25× the uncached input rate.
If the model slug is unrecognized or explicitly unpriceable (e.g., codex-auto-review, gpt-5.3-codex-spark), estimateCostParts returns all-zero parts rather than guessing a neighboring model's rate.
Where Pricing Comes From#
defaultPricing() returns an in-code Map<string, Price> covering Claude and OpenAI model families. The pricing table was last verified on July 25, 2026, against official Anthropic and OpenAI sources. The rates use standard global API token prices and do not apply Batch, Flex, Priority, regional-processing, or partner-cloud modifiers.
Model slug normalization is handled by canonicalModel(), which strips provider prefixes, lowercases, and maps version-specific suffixes to a canonical pricing key (e.g., claude-opus-4.5 → claude-opus).
Where Cost Is Stored#
writeSession() in src/ingest.ts calls estimateCost(s.model, s.totals, defaultPricing()) and immediately passes the result as parameter ?17 in the INSERT INTO session(...) statement. The value lands in estimated_cost_usd REAL NOT NULL DEFAULT 0 on the session table.
Cost is inserted as part of the same database transaction as the rest of the session row — it is never updated afterward.
Implications for Pricing Changes#
- Existing rows are unaffected when
defaultPricing()is updated. Historicalestimated_cost_usdvalues remain frozen at the rates that were in code when each session was ingested. - To re-estimate existing sessions, rebuild the archive. This re-runs the full ingest pipeline with the current pricing table.
- New sources that report no token usage must surface cost as unavailable, not zero, per the add-source guide. The presentation tier is wired deliberately based on the source's capability report.
Key Files#
| File | Role |
|---|---|
src/cost.ts | estimateCost, estimateCostParts, defaultPricing, canonicalModel |
src/ingest.ts | Calls estimateCost and inserts the result into the session row |
src/schema.sql | estimated_cost_usd column definition |
docs/pricing.md | Human-readable rate tables and source verification notes |