Skip to content

feat: show Claude subscription leverage from record-level cost provenance (#200) - #249

Open
Borisserz wants to merge 2 commits into
chattymin:mainfrom
Borisserz:feat/subscription-leverage
Open

Borisserz wants to merge 2 commits into
chattymin:mainfrom
Borisserz:feat/subscription-leverage

Conversation

@Borisserz

@Borisserz Borisserz commented Aug 31, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Adds an optional Claude subscription leverage row for #200, scoped to Claude only as requested in review. Rebuilt from current main.

When the default Claude login is Max / Pro / Team and the user enters a monthly plan price in Settings, the popover shows one line under this week / this month:

Claude plan $100.00/mo · API-equiv $610.00 · 6.1×

How the numerator is computed (record-level provenance):

  • UsageStore.claudeMonthAPIEquivalentCost reads the Claude snapshot's month usageCost. It is looked up by id, the same way the existing claudeActiveBlock lookup works.
  • That total is already folded per record by LocalUsageReader.Bucket:
    • Claude's source-reported costUSD (from cost-state) counts as .source.
    • A price-table amount counts as .estimate.
    • An unpriced record counts as .unavailable: it adds nothing to the amount and marks the total partial.
  • Both .source and .estimate Claude amounts are API-rate figures, so every known amount counts as API-equivalent.
  • An unpriced record never inflates the ratio. The row's tooltip reuses the existing cost explanation, so a partial month says so.
  • There is no provider-level costIsEstimate flag any more. Other providers (Gemini/Codex estimates, Grok/OpenCode/Hermes bills) never enter the numerator. The combined month header still sums every provider, unlabeled.

Gate: all of the following must hold, or the row is hidden:

  • LimitStatus.isFlatRateSubscription (max / pro / team, any case).
  • Plan price > 0.
  • Claude month $ > 0.
  • Exactly one Claude login. With several logins the month total is machine-wide while the plan belongs to one account, so the ratio would mix plans. Per-account leverage can follow separately.

The menu bar and all cost rows are unchanged: bare $, no "API-equiv" caption anywhere except the leverage row (#224).

Support for other providers is intentionally out of scope and can be added later with their own plan inputs.

Type of change

  • Bug fix
  • New feature
  • Refactor / cleanup
  • Documentation
  • Other:

UI changes

Before After
Popover: week / month totals only. Same totals. For a single Max/Pro/Team Claude login with a plan price set, one extra caption line: Claude plan $X/mo · API-equiv $Y · Z×. Its tooltip explains the source, estimate, or partial coverage.
Settings → General: no plan price. Optional Claude monthly plan price ($) field (default 0 = off). It drafts locally and commits on submit, blur, or leaving Settings (same pattern as #187), so UserDefaults isn't written on every keystroke. Negative, NaN, or infinite values load and save as 0.
Menu bar cost: $12.3. Unchanged.

Validation

  • ./scripts/test-gate.sh: 1395 tests, 0 failures (17 skipped); logic-core line coverage 93.39% (≥ 75%).
  • New tests:
    • UsageStoreTests:
      • testLeverageUsesOnlyClaudeMonthCost: Claude $610 + Gemini estimate $200 + Grok bill $50, plan $100. Header = $860, leverage = 6.1×.
      • testOtherProvidersNeverFeedLeverage.
      • testLeverageKeepsRecordLevelCoverage: mixed source/estimate/unavailable gives the partial hint.
      • testLeverageGates: price unset or 0, Free, no plan, no credential, unpriced-only month, non-costing snapshot.
      • testLeverageOpensForProAndTeam.
      • testLeverageHiddenWithSeveralClaudeAccounts.
      • testMonthlyPlanPricePersistsAndSanitizes.
      • testLeverageStaysOutOfTheMenuBar.
    • ModelDecodingTests: testIsFlatRateSubscription, testMultiplierFormat, testLeverageUiShape (no store binding; draft and onDisappear commit).
    • LocalizationInterpolationTests: leverage placeholders survive in all 7 languages.
  • Fault injection: I removed each guard in turn and the targeted tests failed.
    • Dropping the Claude-id filter: other providers fed a 4× ratio.
    • Dropping the multi-account gate.
    • Dropping the finite-price check.
  • llvm-cov --show-regions shows no ^0 regions in the new UsageStore code.
  • Docs: the defect-log.md UI entry covers the record-level vs. provider-level provenance and the multi-account gate. provider-extension.md notes that the leverage lookup is an explicit provider-specific branch, like claudeActiveBlock.

Checklist

  • swift build and swift test pass locally
  • PR title and description are written in English
  • UI changes are described above (before/after — images optional)
  • No copyrighted assets, secrets, or private tooling references are committed (see CONTRIBUTING)
  • Tests were added or updated for this change

Related

@shawnla90 shawnla90 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Built and ran this same feature on my own machine last week (branch linked in #200), so two notes from that experience:

1. The multiple mixes subscriptions. monthAPIEquivalentCost sums every costIsEstimate provider (Claude, Codex, Gemini, ...) but subscriptionLeverage gates on the Claude plan and divides by one plan price. A Max user who also runs Codex CLI (covered by a ChatGPT subscription, not the Claude plan) gets an inflated 'leverage' number: on my machine, August is ~$2,783 of Claude API-equivalent, and Codex estimates on top would push the displayed multiple past what the Max plan actually delivered. Two clean fixes: scope the numerator to claude_code (the plan the gate reads), or keep the mixed sum and make the row's copy say what it covers. I'd do the former — the hint text says the price is for the Max/Pro/Team row, so users will type their Claude plan price.

2. The price field writes defaults per keystroke. TextField("0", value: $store.monthlyPlanPrice, ...) binds straight to the store, and the didSet writes to UserDefaults on every edit. The scan-roots field from #187 established the draft + commit-on-submit/blur pattern for exactly this reason, and CustomScanRootsTests.testSettingsUsesProviderPickerAndCommitsOnSubmit locks it there ('direct TextField binding to the store writes on every keystroke'). Worth matching that pattern here so the convention holds.

Neither blocks the shape — per-provider costIsEstimate is the right structure and matches the #224 close. Happy to test the branch on my dual-login setup once these land.

@Borisserz

Copy link
Copy Markdown
Contributor Author

Landed in c8d7c71.

1. Mixed subscriptions. $Y is now Claude's month estimate only (costIsEstimate && providerID == claude_code). Combined month header still sums everyone unlabeled. Fixture: Claude $610 + Gemini $200 estimate + Grok $50 bill → header $900, leverage 6.1× (not 8.1×). Settings hint now says the field is the Claude Max/Pro/Team price.

2. Per-keystroke defaults. Plan price uses a local draft and commits on submit, blur, and leaving Settings — same pattern as custom scan roots (#187). $store.monthlyPlanPrice is no longer bound to the TextField.

Happy to have you run the dual-login setup against this commit.

@chattymin chattymin left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Please keep this PR scoped to Claude subscription leverage for now and align the PR description, tests, and UI copy with that behavior. The calculation should use record-level cost provenance and include only the Claude API-equivalent cost, rather than classifying an entire provider with one estimate flag. Please rebase onto current main. Support for additional providers can be added separately later.

@Borisserz
Borisserz force-pushed the feat/subscription-leverage branch from c8d7c71 to f0420a3 Compare October 3, 2026 19:53
@Borisserz Borisserz changed the title feat: show Max/Pro/Team subscription leverage without mislabeling billed providers (#200) feat: show Claude subscription leverage from record-level cost provenance (#200) Oct 3, 2026
@Borisserz

Copy link
Copy Markdown
Contributor Author

Reworked per your review and rebased onto current main (f0420a3):

  • Claude-only scope. The numerator is the Claude snapshot's month usageCost, looked up by id like claudeActiveBlock. No other provider can enter it; the combined header still sums everyone.
  • Record-level provenance. The provider-level costIsEstimate flag is gone. The amount comes from the per-record CostCoverage that Bucket already merges (source-reported costUSD / price-table estimate / unavailable). Unpriced records add nothing and mark the month partial, which the row's tooltip explains.
  • Multiple Claude logins hide the row, since the month is machine-wide but the plan is one account's.
  • Description, tests and copy ("Claude plan … · API-equiv … · Z×", "Claude monthly plan price") now match this behavior. Gate: 1395 tests green, 93.39% coverage.

This branch has not been deployed

No deployments
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.

3 participants