Skip to content

feat: honest spend attribution and context-size lens in usage display#3772

Open
pauldambra wants to merge 1 commit into
mainfrom
posthog-code/aio-spend-honest-attribution
Open

feat: honest spend attribution and context-size lens in usage display#3772
pauldambra wants to merge 1 commit into
mainfrom
posthog-code/aio-spend-honest-attribution

Conversation

@pauldambra

Copy link
Copy Markdown
Member

Problem

The usage banner's top-tool headline literally says "<tool> drives X% of your spend," computed from share_of_scoped in packages/core/src/billing/spendSuggestions.ts. That field is a co-occurrence artifact: a single turn can call several tools, and the backend attributes the full turn cost to every tool it touched, so whichever tool is most ubiquitous (Bash) looks artificially dominant, and the numbers don't reconcile to total spend.

This is the client half of a companion change to the posthog monorepo's /api/llm_analytics/@me/spend/ endpoint, which adds honest attribution fields plus a genuinely new lever: spend broken down by input-context size.

Changes

  • Types (packages/api-client/src/spend-analysis.ts and packages/core/src/billing/spendAnalysisTypes.ts, kept in sync): added optional cost_attributed_usd / share_attributed to SpendAnalysisToolRow (fractional cost split across a turn's tools, reconciling to scoped spend), a new SpendAnalysisInputSizeRow type, and an optional by_input_size? breakdown on SpendAnalysisResponse.
  • Suggestions (packages/core/src/billing/spendSuggestions.ts): the top-tool suggestion now uses share_attributed and is worded as co-occurrence-aware ("X was involved in N% of your spend when each turn's cost is split across the tools it used") instead of claiming a tool "drives" the spend. A new primary suggestion reads by_input_size and recommends trimming context (clearing between unrelated tasks, avoiding re-reads of large files/output) when the largest-context bucket or the top bucket by cost exceeds ~40% of scoped spend. The no-tool suggestion wording was tightened. All of this falls back to the previous behavior when the new fields are absent.
  • UI (packages/ui/src/features/usage/components/SpendBreakdownTables.tsx, SpendAnalysisSection.tsx): the tool table gains an "Attributed" cost column when the field is present, and a new "By context size" table renders by_input_size when the backend serves it.

All new fields are optional on the client, exactly like the existing by_day? field, so this PR is backwards-compatible and can merge before or after the API rollout.

How did you test this?

  • pnpm --filter @posthog/core typecheck, pnpm --filter @posthog/api-client typecheck, pnpm --filter @posthog/ui typecheck, and a full workspace pnpm typecheck (24/24 tasks) all pass.
  • pnpm --filter @posthog/core test: 2502/2502 pass, including a new spendSuggestions.test.ts (13 cases) covering the attributed-cost wording, the share_of_scoped/share_attributed fallback, the by_input_size threshold logic (including the >300k-under-threshold-but-another-bucket-over fallback), and the absent-field paths.
  • pnpm --filter @posthog/api-client test: 130/130 pass.
  • pnpm --filter @posthog/ui test: 2012/2012 pass.
  • biome check on all changed files: clean.
  • Full pnpm test (turbo, whole workspace): the only failures are in @posthog/workspace-server (git-network-integration and HogQL-fetch tests unrelated to this change, timing out in this sandbox with no diff in that package) — pre-existing and not touched by this PR.

Automatic notifications

  • Publish to changelog?
  • Alert Sales and Marketing teams?

Created with PostHog Code

The usage banner's top-tool headline ("<tool> drives X% of your spend")
was computed from share_of_scoped, a co-occurrence artifact that
overstates whichever tool is most ubiquitous (Bash), since a single
turn's cost gets counted against every tool it touched. This adopts
the new optional API fields (cost_attributed_usd, share_attributed,
by_input_size) that split each turn's cost fractionally across its
tools and break spend down by input-context size, and reworks the
suggestions/tables to use them when present while falling back to the
prior behavior when a backend hasn't rolled them out yet.

Generated-By: PostHog Code
Task-Id: 68fa35a8-56d2-4be3-a0c9-d870e966acd2
@trunk-io

trunk-io Bot commented Jul 23, 2026

Copy link
Copy Markdown

Merging to main in this repository is managed by Trunk.

  • To merge this pull request, check the box to the left or comment /trunk merge below.

After your PR is submitted to the merge queue, this comment will be automatically updated with its status. If the PR fails, failure details will also be posted here

@github-actions

Copy link
Copy Markdown

React Doctor found no issues in the changed files. 🎉

Reviewed by React Doctor for commit 9273014.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 92730143ae

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +75 to +76
: topByCost.share_of_scoped > LARGE_CONTEXT_SHARE_THRESHOLD
? topByCost

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Restrict the fallback to genuinely large context buckets

When a low-context bucket is the largest cost bucket and exceeds 40%—for example <50k with min_input_tokens: 0—this fallback selects it and tells the user that calls with 0+ tokens prove trimming context is their biggest lever. Since one ordinary bucket can naturally contain most spend due to call volume, filter the fallback to large-context buckets before making this recommendation.

Useful? React with 👍 / 👎.

function topToolSuggestion(
toolItems: SpendAnalysisResponse["by_tool"]["items"],
): string | null {
const top = toolItems[0];

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Select the top tool by attributed share

When the returned rows remain ordered by legacy co-occurrence cost, the first row need not have the greatest share_attributed. For example, a co-occurrence-heavy first tool at 20% followed by a tool at 50% causes this function to return no tool suggestion, even though the latter exceeds the threshold; choose the maximum attributed-share row when attribution is available.

Useful? React with 👍 / 👎.

@pauldambra pauldambra added the Stamphog This will request an autostamp by stamphog on small changes label Jul 23, 2026
@stamphog

stamphog Bot commented Jul 23, 2026

Copy link
Copy Markdown

Note

🤖 stamphog reviewed 92730143aea6cf31e42f912fbe13855265147033 — verdict: REFUSED

Gates denied this PR (deny-list match on billing, tier classified T2-never), and it carries two unresolved Codex P2 comments pointing at real logic bugs (fallback selecting low-context buckets, top-tool selection not using max attributed share) with no human or team sign-off addressing them.

  • chatgpt-codex-connector[bot] reviewed the current head.
  • Deterministic gates denied this PR: deny-list match (billing) and T2-never tier classification.
  • Two unresolved Codex inline comments describe concrete logic bugs in spendSuggestions.ts (context-size fallback and top-tool selection) that are not clearly addressed in the diff.
  • No ownership match found and no human/team review has approved the current head — only a comment-only bot review.
Gate mechanics and policy version
Gate Result
prerequisites all clear
deny-list matches: billing
size 196L, 5F substantive, 489L/6F incl. docs/generated/snapshots — within ceiling
tier classified as T2-never: T2-never (489L, 6F, single-area, feat)
stamphog 2.0.0b3 .stamphog/policy.yml @ 61f1d0f · reviewed head 9273014

@stamphog stamphog Bot removed the Stamphog This will request an autostamp by stamphog on small changes label Jul 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant