Skip to content

Commit da7b2b3

Browse files
asizikovCopilot
andcommitted
fix: handle unlabeled review usage budgets
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 3570f76 commit da7b2b3

2 files changed

Lines changed: 42 additions & 7 deletions

File tree

src/utils/enterpriseBudgetSimulation.test.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,28 @@ describe('simulateBudgetFromRecords', () => {
143143
})
144144
})
145145

146+
it('uses the non-copilot code review label for empty-username user budgets', () => {
147+
const result = simulateBudgetFromRecords([
148+
createRecord({ username: '', model: 'code review', quantity: 50, aic_quantity: 50, aic_gross_amount: 5 }),
149+
createRecord({ username: '', model: 'code review', quantity: 50, aic_quantity: 50, aic_gross_amount: 5 }),
150+
], { userBudgetUsd: 5 }, {
151+
...pooledContext,
152+
organizationEntitlementPool: 100,
153+
})
154+
155+
expect(result).toEqual({
156+
totalBill: 0,
157+
blockedUsers: 1,
158+
blockedRequests: 50,
159+
blockedEntitlementsAic: 50,
160+
budgetExhausted: false,
161+
firstUserBlockedDate: '2026-06-01',
162+
enterpriseBlockedDate: null,
163+
productBlockedDates: {},
164+
adjustedDailyNetCostByDate: [],
165+
})
166+
})
167+
146168
it('does not count entitlements that later get consumed by other usage', () => {
147169
const result = simulateBudgetFromRecords([
148170
createRecord({ username: 'mona', quantity: 50, aic_quantity: 50, aic_gross_amount: 5 }),

src/utils/enterpriseBudgetSimulation.ts

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { calculateAicEntitlementContext, type AicEntitlementContext, type AicEntitlementOverrides } from '../pipeline/aicEntitlements'
22
import { getAicUsageMetrics, getUsageMetrics, parseTokenUsageHeader, parseTokenUsageRecord, type TokenUsageHeader, type TokenUsageRecord } from '../pipeline/parser'
3-
import { getProductBudgetName, type ProductBudgetName } from '../pipeline/productClassification'
3+
import { getProductBudgetName, isNonCopilotCodeReviewUsage, NON_COPILOT_CODE_REVIEW_USER_LABEL, type ProductBudgetName } from '../pipeline/productClassification'
44
import { streamLines } from '../pipeline/streamer'
55

66
export type BudgetSimulationResult = {
@@ -61,6 +61,19 @@ function getIndividualEntitlementKey(record: TokenUsageRecord): string | null {
6161
return `${username}\u0000${weekStart}`
6262
}
6363

64+
function getBudgetSubject(record: TokenUsageRecord): string | null {
65+
const username = record.username.trim()
66+
if (username) {
67+
return username
68+
}
69+
70+
if (isNonCopilotCodeReviewUsage(record)) {
71+
return NON_COPILOT_CODE_REVIEW_USER_LABEL
72+
}
73+
74+
return null
75+
}
76+
6477
function getRemainingEntitlements(
6578
record: TokenUsageRecord,
6679
context: BudgetSimulationContext,
@@ -123,7 +136,7 @@ export function simulateBudgetFromRecords(
123136
const seenIndividualEntitlementKeys = new Set<string>()
124137

125138
for (const record of records) {
126-
const username = record.username.trim()
139+
const budgetSubject = getBudgetSubject(record)
127140
const productBudgetName = getProductBudgetName(record)
128141
const { requests } = getUsageMetrics(record)
129142
const { aicQuantity, aicGrossAmount } = getAicUsageMetrics(record)
@@ -145,7 +158,7 @@ export function simulateBudgetFromRecords(
145158

146159
const remainingUserBudget = userBudgetCap === Number.POSITIVE_INFINITY
147160
? Number.POSITIVE_INFINITY
148-
: (remainingUserBudgetByUser.get(username) ?? userBudgetCap)
161+
: (budgetSubject ? (remainingUserBudgetByUser.get(budgetSubject) ?? userBudgetCap) : Number.POSITIVE_INFINITY)
149162
const remainingProductBudget = remainingProductBudgetByName.get(productBudgetName) ?? Number.POSITIVE_INFINITY
150163
const remainingEntitlements = getRemainingEntitlements(
151164
record,
@@ -177,8 +190,8 @@ export function simulateBudgetFromRecords(
177190

178191
if (allowedRatio < 1) {
179192
blockedRequests += requests * (1 - allowedRatio)
180-
if (username) {
181-
blockedUsers.add(username)
193+
if (budgetSubject) {
194+
blockedUsers.add(budgetSubject)
182195
}
183196
if (userBudgetLimited && firstUserBlockedDate === null) {
184197
firstUserBlockedDate = record.date || null
@@ -228,8 +241,8 @@ export function simulateBudgetFromRecords(
228241
remainingEnterpriseBudget = nextRemainingEnterpriseBudget
229242
}
230243

231-
if (remainingUserBudget !== Number.POSITIVE_INFINITY) {
232-
remainingUserBudgetByUser.set(username, Math.max(remainingUserBudget - allowedGrossAmount, 0))
244+
if (budgetSubject && remainingUserBudget !== Number.POSITIVE_INFINITY) {
245+
remainingUserBudgetByUser.set(budgetSubject, Math.max(remainingUserBudget - allowedGrossAmount, 0))
233246
}
234247
if (remainingProductBudget !== Number.POSITIVE_INFINITY) {
235248
const nextRemainingProductBudget = Math.max(remainingProductBudget - overageAmount, 0)

0 commit comments

Comments
 (0)