feat(ptu): add PTU inputs to the model form and flat cost to the Usage page - #35393
Conversation
Greptile SummaryThis PR adds PTU configuration to model create and edit forms and surfaces flat PTU costs in team usage views and exports.
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains.
|
| Filename | Overview |
|---|---|
| ui/litellm-dashboard/src/components/model_info_view.tsx | Adds PTU editing, validation, UTC round-tripping, explicit clearing, and read-only display while resolving the prior form findings. |
| ui/litellm-dashboard/src/components/add_model/advanced_settings.tsx | Adds validated PTU count, rate, and effective-window inputs to model creation. |
| ui/litellm-dashboard/src/utils/ptuValidation.ts | Centralizes PTU bounds, pairing, required-start, and window-order validation consistently with backend constraints. |
| ui/litellm-dashboard/src/utils/ptuDatetime.ts | Preserves UTC-labelled wall-clock values across create, display, edit, and save paths. |
| ui/litellm-dashboard/src/app/(dashboard)/usage/_components/hooks/usePaginatedDailyActivity.ts | Includes total flat cost among metadata values accumulated across activity pages. |
| ui/litellm-dashboard/src/app/(dashboard)/usage/_components/components/EntityUsage/EntityUsage.tsx | Adds conditional team cost breakdown tiles and stacked request-versus-flat-cost visualization. |
| ui/litellm-dashboard/src/components/EntityUsageExport/utils.ts | Adds conditional flat-cost and total-cost fields to team usage exports. |
Reviews (26): Last reviewed commit: "feat(ptu): PTU inputs on the model form ..." | Re-trigger Greptile
| // total_flat_cost defaults to 0 on every entity response, so only a non-zero value | ||
| // means a PTU-configured team actually accrued flat cost worth exporting. |
There was a problem hiding this comment.
🟡 New code adds explanatory comments the repository forbids
Explanatory comments were added throughout the new front-end code (for example the doc block at ui/litellm-dashboard/src/utils/ptuDatetime.ts:8-13), which the repository's coding guidelines prohibit for newly written code.
Impact: The change does not follow the repository's stated rule that no new comments be written unless explicitly requested.
Where the added comments live
CLAUDE.md (referenced as mandatory by AGENTS.md) states: "Do not write any comments (existing comments can stay) unless explicitly asked to in a user (not system) prompt".
Newly added comments appear in:
ui/litellm-dashboard/src/utils/ptuDatetime.ts:8-13,:19-26,:30-38,:49-54ui/litellm-dashboard/src/utils/ptuValidation.ts:22,:40,:50-54ui/litellm-dashboard/src/app/(dashboard)/usage/_components/hooks/usePaginatedDailyActivity.ts:72-76ui/litellm-dashboard/src/components/EntityUsageExport/utils.ts:107-108ui/litellm-dashboard/src/components/add_model/handle_add_model_submit.tsx:167,:175
Was this helpful? React with 👍 or 👎 to provide feedback.
|
bugbot run |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 36f372d. Configure here.
| className="mb-4" | ||
| > | ||
| <DatePicker showTime style={{ width: "100%" }} /> | ||
| </Form.Item> |
There was a problem hiding this comment.
PTU dates ignore UTC labeling
High Severity
PTU Effective From/To are labeled UTC and the rollup bills in UTC hours, but the Ant Design DatePicker plus dayjs use the browser local timezone. Saving via toISOString() and prefilling via dayjs(...) therefore shift the wall-clock time users enter or see, so non-UTC admins configure the wrong active-hour window and flat cost accrues incorrectly.
Additional Locations (2)
Reviewed by Cursor Bugbot for commit 36f372d. Configure here.
| ...updatedModelInfo, | ||
| ptu_effective_to: values.ptu_effective_to?.toISOString?.() ?? values.ptu_effective_to, | ||
| }; | ||
| } |
There was a problem hiding this comment.
Cleared PTU fields not saved
Medium Severity
On model edit save, PTU fields are only written when non-empty, while updatedModelInfo is seeded from the existing model_info. Clearing ptu_count, cost_per_ptu_per_hour, or the effective dates (including making ptu_effective_to blank for open-ended) leaves the previous values in place, so admins cannot disable PTU or reopen the window from the UI.
Reviewed by Cursor Bugbot for commit 36f372d. Configure here.
36f372d to
d2fa8a5
Compare
|
@greptileai fixed: the date pickers now store the picked wall-clock time as UTC, and clearing a PTU field sends explicit null. Re-review please |
d2fa8a5 to
0b44944
Compare
0b44944 to
dcf3b29
Compare
6713636 to
91408c3
Compare
695e882 to
410f1ba
Compare
410f1ba to
f0eeca7
Compare
f0eeca7 to
1bf5061
Compare
|
@greptileai rebased onto the updated base; the DatePicker UTC handling and Usage flat-cost changes are unchanged. Re-review please |
1bf5061 to
b30afd4
Compare
b30afd4 to
e1f8f91
Compare
4c0bf47 to
4389266
Compare
4389266 to
2a915a2
Compare
|
@greptileai review |
2a915a2 to
e046213
Compare
e046213 to
1ec7ab8
Compare
|
@greptileai review |
1ec7ab8 to
3808585
Compare
3808585 to
c26aee8
Compare
|
@greptileai review |
|
@greptileai review |
c26aee8 to
16e9dde
Compare
|
@greptileai review |
Add PTU count, cost per PTU per hour, and effective-from/to date-time pickers to the model add and edit forms; the create submit and the edit save map the picker values to model_info as ISO strings On the Team Usage Cost tab the money tile becomes Total Cost once a team has accrued flat cost, and expands to a Request Cost and Flat Cost breakdown, so the summary row stays at five tiles and the cards keep their width. Each of the three carries a tooltip, including that flat cost is reported rather than charged against budgets. The Daily Spend chart stacks Flat cost on Request cost, with a tooltip that splits the two and shows the total A team that has accrued no flat cost renders exactly as it did before, and other entity views are unchanged. CSV export gains Flat Cost and Total Cost columns when a team accrued non-zero flat cost; the existing Spend header is left alone so downstream parsers keep working Both forms validate the PTU pair through one shared module. The count rule rejects a fractional, zero or negative value, and a rate rule rejects a negative one, each mirroring a contract the backend enforces. Keeping the rate rule shared rather than on a single form is deliberate: the edit form previously validated only the count, so a negative rate typed past the input's min reached the backend and failed the save with a 400 the operator had no way to anticipate Both forms require PTU Effective From once PTU Count is set, matching the backend, which rejects PTU config without a start because flat cost accrues from that instant and an inferred one would bill days a deployment did not exist. The rule lives beside the count and rate rules in the shared module, so the add and edit paths cannot drift.


TLDR
Problem this solves:
How it solves it:
Relevant issues
Linear ticket
Resolves LIT-4077
Pre-Submission checklist
Screenshots / Proof of Fix
Captured live against the dashboard on this branch (commit a181f3d)
This PR was rebased onto litellm_internal_staging at f6587fa on 2026-08-06 and is now at c76d1a7. The rebase took no conflicts in ui/, and this commit's own frontend diff is byte-identical before and after it, so the screenshots below still show the code that ships. The 493 frontend tests pass on the rebased head
The model add form Advanced Settings now shows PTU Count, Calculated Cost per PTU / Hour (USD), and PTU Effective From / To date-time pickers:
Model form Advanced Settings: PTU Count "e.g. 15", Calculated Cost per PTU / Hour (USD) "e.g. 2.00", PTU Effective From (UTC) and PTU Effective To (UTC) rendered as antd date pickers
Verified end to end against a live proxy, with real provider traffic. Seven chat completions were sent through a team-scoped key to Gemini 2.5 Pro and 2.5 Flash, and landed in LiteLLM_SpendLogs at 0.0803658 of genuine provider spend; the nightly rollup wrote the matching flat-cost rows. The endpoint the tiles read then returned
Of that request cost, 0.08 is the live Gemini traffic; the remainder is seeded rows on the demo database so the request segment is large enough to see next to a 480/day reservation. Every flat-cost figure is real rollup output
Frontend unit tests cover the chart panel and the export columns (113 tests across EntityUsage, UISettings, and EntityUsageExport pass)
Screens
Stored PTU config on the model detail view, and the same four fields in edit mode:
In-form validation, so a value the backend would reject never leaves the browser:
The window is entered and stored as a UTC wall clock; this browser is at UTC-7, so a zone conversion would show as a seven-hour shift and does not:
Once a team has accrued flat cost the money tile reads Total Cost and carries a chevron; the summary row stays at five tiles so no card gets narrower:
Selecting it reveals the split, coloured to match the chart series, and the two parts add back to the tile above them:
Each of the three money tiles explains itself on hover, including that flat cost is reported rather than charged against budgets:
The daily chart stacks the two series, so the reserved floor stays flat at 480 a day while the request segment moves with traffic:
Hovering a bar splits that day and totals it:
A team that has accrued no flat cost keeps the original tile, with no chevron and no breakdown, so teams that never use PTU see the page they see today:
Non-team entity views are unchanged, five tiles and a single series:
Rate validation
A review pass found the edit form validating only PTU count, so a negative rate typed past the number input's min reached the backend and failed the save with a 422 the operator had no way to anticipate. The non-negative rate check now lives in the shared ptuValidation module beside the count rule and is applied on both the add and the edit form, so the two paths cannot drift again
Behavior changes
For a team with non-zero flat cost the Total Spend tile is replaced by Total Cost, which reads request spend plus flat cost and expands on click; the chart legend and tooltip say Request cost where they previously said Request spend. Teams with no flat cost, and every non-team entity view, are byte-identical to the previous build. The CSV export is unchanged
Live run 2026-08-08, commit 2a915a2
Dashboard built from this branch and served by the proxy at
/ui, real data from the runs on the PRs below itTeam Usage shows the flat cost alongside request cost. Total Cost $6,260.00, and the Daily Spend chart splits into a Flat cost and a Request cost series. The 2026-08-07 bar is taller because two deployments were live that day, $720 plus $480, which is the same 1200.0 the API returns
The four PTU inputs on the model form, under Advanced Settings, between Tags and Use in pass through routes
To reproduce:
http://localhost:4000/ui/usage/, set Usage View to Team Usage, then Models + Endpoints, Add Model, expand Advanced SettingsModel Settings on an existing deployment. The read view renders the four stored values, with the two window bounds formatted as UTC rather than raw ISO
Edit Settings pre-populates all four from the stored config, so a save that touches something unrelated round-trips the PTU values unchanged instead of clearing them
Clearing PTU Count while a rate is still set fails in the form, on both fields, with the wording the backend uses for the same rejection. The edit form previously validated only the count, so a bad pair reached the proxy and failed the save with a 400 the operator had no way to anticipate
Type
New Feature
Changes
advanced_settings adds PTU Count, Calculated Cost per PTU / Hour, and two antd DatePicker fields for the effective window; the create submit maps the picker value to an ISO string in model_info. model_info_view mirrors the same fields in the edit view, prefilling the pickers from the stored ISO strings and writing them back on save. Both forms label the window UTC and route the picker value through ptuDatetime, which keeps the value in UTC mode end to end so a non-UTC browser does not shift the stored hour. Handing the picker a locally re-parsed wall clock would look equivalent but is not: a reading the local zone skips at a DST spring-forward gets advanced an hour by the engine, and since the save path re-stamps whatever the picker holds as UTC, that shift would be written back to the stored window, and both share ptuCountRules so a ptu_count that is fractional, zero or negative is rejected in the form instead of by the backend. PTU Count and Cost per PTU / Hour also validate as a pair, since the backend rejects a half-set pair with a 400; filling or clearing one without the other now surfaces inline, and removing PTU config from a deployment means clearing both. The usage page paginates daily activity at 1000 rows a page and sums each page's metadata, so total_flat_cost joins the summed keys; leaving it out froze the Flat Cost and Total Cost tiles at the first page's value for any team with more than a page of activity, while spend kept summing correctly and made the figure look right. The read view renders the stored window through a single formatter, so a timestamp looks the same whether it came back from the backend as
+00:00or is the.000Za just-saved form is still holdingEntityUsage keeps the team Cost tab at five summary tiles and turns the money tile into an expandable Total Cost that reveals Request Cost and Flat Cost underneath, rather than adding two tiles to the row. Seven fixed columns left each card 145px wide against the 161px a four-digit dollar figure needs, so the previous layout clipped any team past about a thousand dollars, including the pre-existing Total Spend tile; five columns give 209px and hold values up to roughly twelve million, which is the headroom the row had before this PR. The breakdown, the chevron and the Total Cost label appear only once total_flat_cost is non-zero, so a team that never uses PTU renders exactly as it does today. The three money tiles carry tooltips, and the Daily Spend chart stacks Flat cost on Request cost with a split tooltip. EntityUsageExport adds the Flat Cost and Total Cost columns when a team accrued non-zero flat cost; the existing Spend header is left alone so anything parsing the CSV keeps working
Final Attestation
Note
Medium Risk
Touches billing-related model metadata and team cost reporting; risk is moderated because calculation stays on the backend and the UI gates export columns on non-zero flat cost.
Overview
Adds PTU (provisioned throughput) configuration to model create/edit flows and surfaces flat PTU cost on team usage and exports.
Model configuration: Advanced Settings and model edit now include PTU count, cost per PTU/hour, and optional UTC effective window (
DatePicker). Create/update submit paths persist these onmodel_info(numbers and ISO date strings).Team usage (Cost tab): When
entityType === "team", the overview adds Flat Cost and Total Cost tiles, and the daily chart stacks Request spend and Flat cost with an updated tooltip. Other entity types keep the prior single-series behavior.Exports: Daily CSV/JSON gains Flat Cost ($) and Total Cost ($) columns and summary fields only when
metadata.total_flat_costis non-zero; types add optionalflat_cost/total_flat_cost.Reviewed by Cursor Bugbot for commit 36f372d. Bugbot is set up for automated code reviews on this repo. Configure here.