Skip to content

feat(json): report models with missing pricing in JSON output - #1737

Closed
ravsau wants to merge 3 commits into
ccusage:mainfrom
ravsau:feat/json-pricing-completeness
Closed

ravsau wants to merge 3 commits into
ccusage:mainfrom
ravsau:feat/json-pricing-completeness

Conversation

@ravsau

@ravsau ravsau commented Sep 16, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #1736.

A model with no known price contributes zero cost. Table output warns on stderr, but JSON output gave no signal, so scripts could not tell a free model from an unknown one.

What changes in JSON

  • totals.unpricedModels: sorted, deduplicated model names. The key is omitted when empty, so priced reports and every existing totals snapshot are byte-identical. Scripts use .totals.unpricedModels // [].
  • modelBreakdowns[].missingPricing: true, present only when set. ModelBreakdown.missing_pricing already existed and was skip_serializing; it is now skip_serializing_if false.
  • Focused codex reports set missingPricing on the affected entries of each period's models map, and their totals carry unpricedModels from the existing codex_missing_pricing_models.
  • --no-cost keeps both fields. They describe pricing availability, not amounts. Tested.

Where it is computed

One helper pair in ccusage-core::output (unpriced_models, attach_unpriced_models) used by the shared totals_json, by the unified report's own totals_json in ccusage-adapter-all, and by the codex adapter's totals. The stderr warning path now reuses unpriced_models instead of its own filter. Covered: daily, weekly, monthly, and session reports for every agent, including --instances, --sections (totals follow the invoked section), and --by-agent (nested agent breakdowns carry the flag).

What it deliberately does not claim

Detection follows the cost mode. Display mode, and auto mode for entries with stored costs, look nothing up and report nothing; a model can appear in unpricedModels and still show positive cost when some entries carried stored costs. The docs say an absent key means "no unpriced usage detected under this mode". blocks and session --id have no totals object and are unchanged; happy to follow up if you want them.

Open design questions from the issue: placement inside totals vs top-level, and unpricedModels vs missingPricingModels. Either is a small change.

Tests: core totals (sorted, deduplicated, absent when priced), breakdown serialization, strip_cost_json keeps the fields, cost-mode suppression (auto with stored cost, display, calculate), zero-rate pricing vs unknown model through real lookup, unified report with nested agents and --sections, codex unknown vs priced vs zero-rate under Standard and Fast policies. Docs section in json-output.md.

Validation: cargo test --workspace (all green, no snapshot changes), cargo clippy --workspace --all-targets -D warnings, cargo fmt --check, release binary end to end on real pi, claude, codex, and unified data.


Summary by cubic

Fixes #1736 by reporting models with missing pricing in JSON output, so scripts can distinguish free models from unpriced ones.

  • totals.unpricedModels lists sorted, deduplicated model names; the key is omitted when empty, so priced reports and snapshots stay byte-identical.
  • Affected breakdowns carry missingPricing: true, including --by-agent nested breakdowns and focused codex reports' per-period models maps.
  • Detection follows cost mode: display mode and entries with stored costs report nothing; --no-cost keeps both fields because they describe pricing availability.
  • Covers daily, weekly, monthly, and session for every agent, including --instances and --sections; with --sections, totals follow the invoked section.
  • blocks and session --id are unchanged, and json-output.md documents the fields with jq examples.

Written for commit 77cd542. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features
    • JSON reports now identify models without available pricing through totals.unpricedModels.
    • Affected model breakdowns include missingPricing: true; priced models omit this field.
    • Unpriced model names are sorted and deduplicated, and metadata is preserved when using --no-cost.
    • Reports record usage cost as 0 when pricing is unavailable.
    • Detection respects cost mode and stored costs, avoiding false positives.
  • Documentation
    • Added usage guidance, examples, coverage details, and steps for resolving missing pricing data.

A model with no known price contributes zero cost. Table output warns on
stderr, but JSON output gave no signal, so scripts could not tell a free
model from an unknown one. Totals now carry `unpricedModels` (sorted,
deduplicated, key omitted when empty) from the shared totals builder, the
unified report's own totals, and the codex adapter's totals. Model
breakdowns carry `missingPricing: true` only when set, and focused codex
reports mark the affected entries of each period's `models` map. Priced
reports and their snapshots are unchanged. `--no-cost` keeps both fields
because they describe availability, not amounts. Detection follows the
cost mode, so display mode and stored costs report nothing, and the docs
say so. Blocks and `session --id` have no totals object and stay as is.
@coderabbitai

coderabbitai Bot commented Sep 16, 2026 •

Copy link
Copy Markdown

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: f466d3f7-57b7-4177-98e8-5d93a8a6812f

📥 Commits

Reviewing files that changed from the base of the PR and between 3d6a77d and 77cd542.

📒 Files selected for processing (2)
  • rust/crates/ccusage-adapter-all/src/tests.rs
  • rust/crates/ccusage-core/src/output.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • rust/crates/ccusage-core/src/output.rs

Included review availability: Your plan provides up to 10 included reviews per hour; 8 remain after this review.


📝 Walkthrough

Walkthrough

Changes

The JSON output now identifies usage from models without pricing. Totals include sorted, deduplicated unpricedModels when applicable. Affected model breakdowns include missingPricing: true. Codex and unified reports, tests, and documentation cover the new fields.

Unpriced model reporting

Layer / File(s) Summary
Core JSON contract and detection
rust/crates/ccusage-core/src/types.rs, rust/crates/ccusage-core/src/output.rs, rust/crates/ccusage-core/src/cost.rs, rust/crates/ccusage-core/src/lib.rs
JSON conditionally serializes missingPricing, adds sorted and deduplicated totals.unpricedModels, preserves both fields when costs are stripped, and tests zero-rate and cost-mode behavior.
Report adapter integration
rust/crates/ccusage-adapter-all/src/report.rs, rust/crates/ccusage-adapter-all/src/tests.rs, rust/adapters/codex/src/report.rs
Unified and Codex reports attach unpriced model totals and mark affected model entries. Tests cover nested agents, sections, focused Codex reports, priced models, and zero-rate models.
JSON output documentation
docs/guide/json-output.md
Documents the new fields, detection rules, report coverage, examples, and pricing update guidance.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Feature · Severity of issue fixed: Medium

Suggested reviewers: ryoppippi

Sequence Diagram(s)

sequenceDiagram
  participant ReportAdapter
  participant CoreOutput
  participant JSONReport
  ReportAdapter->>CoreOutput: provide model breakdowns
  CoreOutput->>CoreOutput: collect missing pricing models
  CoreOutput->>JSONReport: attach unpricedModels and missingPricing
Loading

Merge Risk: ⚪ Minimal · up to 77cd5

The new pricing-availability metadata is propagated through supported reports and preserved with no-cost output.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 52.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 25 functions across 7 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: adding JSON reporting for models with missing pricing.
Linked Issues check ✅ Passed The PR meets the coding requirements in issue #1736. It adds conditional, sorted, deduplicated totals.unpricedModels and conditional missingPricing: true markers. It covers shared, unified, and fo…
Out of Scope Changes check ✅ Passed The whole-PR diff contains documentation, implementation, and tests that directly support issue #1736. The previously reported work-log.yaml change is not present at the reviewed head. No unrelated …
  • Fix all pre-merge checks with AI
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

Some tools did not complete. Review the errors below.

🔧 Clippy (1.98.0)

Clippy execution failed


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown
Contributor

Pullfrog could not complete the automated PR review. The PR was left open for maintainer review.

@pullfrog pullfrog Bot 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.

Important

Stray work-log.yaml must go before merge; the JSON feature itself looks correct.

Reviewed changes

  • Added totals.unpricedModels (sorted, deduplicated, omitted when empty) via shared unpriced_models / attach_unpriced_models helpers in ccusage-core::output, wired into shared, unified, and codex totals.
  • Changed ModelBreakdown.missing_pricing from skip_serializing to skip_serializing_if false, so modelBreakdowns[].missingPricing: true appears only when set and priced snapshots stay byte-identical.
  • Marked focused codex per-period models entries with missingPricing and attached totals from codex_missing_pricing_models; unified --by-agent / --sections paths carry the flag through nested breakdowns.
  • Documented cost-mode semantics, --no-cost retention, and blocks / session --id non-coverage in json-output.md, with unit coverage for sorting, dedup, zero-rate vs unknown, mode suppression, and strip_cost_json retention.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using Muse Spark Contributor (free via Pullfrog for OSS) | 𝕏

Comment thread work-log.yaml Resolved

@cubic-dev-ai cubic-dev-ai Bot 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.

All reported issues were addressed

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread rust/crates/ccusage-core/src/output.rs Resolved
@ryoppippi

Copy link
Copy Markdown
Member

Thank you for the careful implementation and test coverage. I also verified the focused Rust tests, Clippy, and formatting locally.\n\nI’m closing this because it adds a new public JSON contract rather than fixing existing report behavior. The parent issue was already triaged as an excluded feature request, and maintaining the placement, naming, and compatibility of these fields would expand the project’s supported surface. The existing table warning remains the supported signal for missing pricing.\n\nThis is a scope decision, not a quality issue with the contribution.

@ryoppippi

Copy link
Copy Markdown
Member

We revisited the earlier scope decision and carried your implementation forward on current main in #1750. You are credited as a co-author on the commit. Thank you for the solid implementation and test coverage.

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.

JSON output gives no signal when a model has no price; cost silently reads as 0

2 participants