Repository navigation
fix(dashboard): utilization series reports the tightest quota window per bucket - #16463
Conversation
|
Heads-up for review: the workflow runs on this PR (CI, Quality Gates, semgrep, API Route Typecheck) are sitting in Checked locally by merging this branch onto the current Once the checks are green, the PR still needs the One unrelated note: |
…per bucket getAggregatedSnapshots() grouped by (bucket, provider, window_key) and returned one row per quota window. The Utilization page keeps the last row of each bucket, which is an arbitrary window: a provider with a 5h session window at 97% and an exhausted weekly window was drawn from whichever row came last, hiding the exhaustion. Collapse the windows inside the query so each (bucket, provider-or-connection) yields one point: the tightest window (lowest remaining percentage, named in windowKey), exhausted when any window is. Samples of the same window in a bucket are still averaged first. windowKey was also always undefined because the SQL column is window_key; it is now aliased.
f25ac37 to
1648f7e
Compare
|
Rebased onto the current |
c1dde2e
into
cheaper-inference:release/v3.8.52
|
Added a follow-up commit (8f78863) for the multi-account case: with two accounts of one provider, the provider-level series averaged/MAX'ed across accounts per bucket, so a single exhausted account (re-snapshotted often) painted the whole provider as exhausted/0%, while a healthy account with no recent change contributed nothing (snapshots are stored only on change). Now each account is reduced to its tightest window, unchanged accounts are carried forward (seeded from before the range), and the provider point follows the best account — exhausted only when every account is. Connection aggregation is untouched. 4 new regression tests in tests/unit/db-quota-snapshots.test.ts. |
|
Follow-up (5c42307): the per-account view still averaged only the windows that had a row in the bucket, so an account whose weekly window rarely changes looked nearly full while the provider view showed it much lower. Both views now share the same per-account reduction (tightest window, unchanged windows carried forward). Test added. |
Summary
/dashboard/analytics/utilizationcan show a provider as healthy while one of its quota windows is exhausted. Example from a Claude Team account: the weekly window sat at 0 % (exhausted) for days while the chart and the "latest snapshot" card read ~13 % / 96 %.getAggregatedSnapshots()grouped by(bucket, provider, window_key)and returned one row per quota window, butProviderUtilizationTabbuildsrow[provider] = remainingPctper timestamp and takes the "latest" point per provider, so whichever window's row came last in a bucket won. For a provider with a 5h session window and a weekly window that is arbitrary, and it hid the exhaustion.(bucket, provider-or-connection)now yields one point: the tightest window (lowest remaining percentage, named inwindowKey),isExhaustedwhen any window is. Samples of the same window inside a bucket are still averaged first, so the existing behaviour for single-window providers is unchanged.windowKeywas alwaysundefinedin the response because the SQL column iswindow_key; it is now aliased, so the field declared inProviderUtilizationPointis finally populated.Related Issues
Target Branch
release/v3.8.xbranch (the highest openrelease/v*), notmainProof Of Fix (required)
tests/unit/db-quota-snapshots.test.ts(new case:quotaSnapshots aggregation returns one row per bucket with the tightest window)usage-utilization-connection-metaandutilization-route-import-10939(9/9 together).Scope
Validation
node --import tsx/esm --test tests/unit/db-quota-snapshots.test.ts tests/unit/usage-utilization-connection-meta.test.ts tests/unit/utilization-route-import-10939.test.tsnpm run linton the touched files (the repo hooks passed on commit). One pre-existingno-unused-varsfor theJsonRecordtype inquotaSnapshots.tsis on the base branch and is untouched here.Reviewer Notes
SELECTof the CTE and is easy to special-case.getLatestQuotaSnapshotsForConnection), so the route and the React tab are untouched and the response shape is unchanged.