/** * Correct ledger entries written while one API response was counted many times. * * The claude-code provider used to emit one usage record per transcript *line*. * Claude Code writes one line per content block and repeats the response's `usage` * object on every one, so a response spanning three blocks was charged three times. * Measured on this repo's 53 transcripts: 17,292 usage-bearing lines against 9,357 * unique `message.id` values — an over-count factor of **1.848**, with 7,935 repeat * lines and zero whose usage object differed. * * `providers/claude-code.ts` now groups by `message.id`, which fixes capture going * forward. This corrects what is already on disk, by re-reading each entry's source * transcript through the fixed provider. * * Three properties are deliberate, and each is the same choice `rederive.ts` made * for the same reason: * * 1. **Bounded by the entry's own coverage window.** One entry's cursor sits in a * 633-record transcript while the entry covers only 80 of them, so re-deriving * over the whole file inflated it 23x there. The bound is what makes this * arithmetic rather than estimation. * 2. **A missing transcript is reported, never estimated.** An entry that cannot be * re-read is left byte-identical and counted. Scaling it by the measured 1.848x * is explicitly rejected: an estimate that looks like a measurement is the * defect this whole change exists to remove. `provenance.ts` keeps `unknown` * distinct from `foreign` for the same reason — under-recovering is the safe * direction. * 3. **Verification is on precedence-weighted dollars, not raw token sums.** Raw * sums can balance while weighted totals move, which is the exact shape of a * defect that once reported a plausible total while double-counting 950 entries. * * Read-only unless `apply` is set. The plan names the dollar delta before anything * is written, because applying it moves every cost figure this project has already * published in a retrospective or a blog post. * * Requirements: context-budget/AC-11, AC-12, AC-13; NFR-5 * Design: design.md Part 3 * * @module */ import type { CostConfig } from './types.js'; import type { ProviderEnv } from './providers/provider.js'; import type { ProvenanceIndex } from './provenance.js'; export interface DedupeReport { /** Priced entries examined. */ scanned: number; /** Entries whose units were re-derived to a smaller figure. */ rederived: number; /** Entries re-read and found already correct. */ unchanged: number; /** * Entries that are provably a second charge for a response already counted. * * These are the bulk of the historical defect, and they are *extra entries* * rather than inflated ones: `rederive.ts` split most buckets into one zero-width * entry per transcript line, so each duplicate line became its own ledger entry. * Re-deriving such an entry is meaningless — the correct figure for it is that it * should not exist. * * The proof is what separates this from `transcriptGone`: the entry's transcript * **resolved**, and the deduped read of it emits no record at this entry's own * instant. A timestamp disappears from the deduped output for exactly one reason — * its line was a non-leading content block of a response whose usage is now * carried by the group's first line. So the charge exists elsewhere. * * Dropped under `--apply`; counted and left alone in a plan. */ duplicates: number; /** Micro-USD carried by those duplicate entries, unweighted. */ duplicateMicroUsd: number; /** * Entries whose transcript could not be found, with the reason, left byte-identical. * * Kept strictly separate from `duplicates` because the two justify opposite * actions and only one of them is proven. A cursor that resolves nowhere is an * honest unknown — the transcript rotated or was deleted — and an unknown is never * resolved by deleting data or by scaling it to a plausible figure. */ transcriptGone: string[]; /** Entries a human placed with `cost record --manual`; never overruled. */ manualSkipped: number; /** Precedence-weighted dollars before, in micro-USD. */ microUsdBefore: number; /** Precedence-weighted dollars after, in micro-USD. */ microUsdAfter: number; /** False for a plan; true when ledgers were rewritten. */ applied: boolean; /** Ledger files that would change (or did). */ files: string[]; } /** * Plan (or apply) the dedupe correction across every ledger. * * @param apply - false plans and writes nothing; true rewrites each changed ledger * atomically via `rewriteLedger`, all-or-nothing per file (NFR-5). */ export declare function dedupeHistory(projectRoot: string, config: CostConfig | null, apply: boolean, env?: ProviderEnv, provenanceFiles?: ProvenanceIndex): Promise; //# sourceMappingURL=dedupe-history.d.ts.map