Skip to content

Commit 740a1d4

Browse files
asizikovCopilot
andcommitted
fix: ignore unknown quota values in plan detection
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 90cd1e9 commit 740a1d4

3 files changed

Lines changed: 89 additions & 7 deletions

File tree

src/pipeline/aggregators/userUsageAggregator.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { getUsageMetrics, type TokenUsageHeader, type TokenUsageRecord } from '.
33
import { getDisplayModelName } from '../modelLabels'
44
import { getFriendlyProductName } from '../productClassification'
55
import { classifyUserSpendSegments, type UserSpendSegmentId } from '../../utils/userSpendSegments'
6+
import { selectKnownMonthlyQuota } from '../aicIncludedCredits'
67

78
export type UserModelDailyUsage = {
89
requests: number
@@ -167,7 +168,7 @@ export class UserUsageAggregator implements Aggregator<TokenUsageRecord, UserUsa
167168
if (!user) {
168169
user = {
169170
username,
170-
totalMonthlyQuota: record.total_monthly_quota,
171+
totalMonthlyQuota: selectKnownMonthlyQuota(0, record.total_monthly_quota),
171172
organizations: new Set(),
172173
costCenters: new Set(),
173174
daily: new Map(),
@@ -186,9 +187,7 @@ export class UserUsageAggregator implements Aggregator<TokenUsageRecord, UserUsa
186187
this.byUser.set(username, user)
187188
}
188189

189-
if (record.total_monthly_quota > user.totalMonthlyQuota) {
190-
user.totalMonthlyQuota = record.total_monthly_quota
191-
}
190+
user.totalMonthlyQuota = selectKnownMonthlyQuota(user.totalMonthlyQuota, record.total_monthly_quota)
192191

193192
const organization = record.organization.trim()
194193
if (organization) {

src/pipeline/aicIncludedCredits.test.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
PRO_PLUS_MONTHLY_AIC_INCLUDED_CREDITS,
1919
PooledAicIncludedCreditsAllocator,
2020
PRO_PLUS_MONTHLY_QUOTA,
21+
selectKnownMonthlyQuota,
2122
} from './aicIncludedCredits'
2223
import { CostCenterAggregator } from './aggregators/costCenterAggregator'
2324
import { OrganizationAggregator } from './aggregators/organizationAggregator'
@@ -45,6 +46,7 @@ const HEADER = [
4546
'aic_quantity',
4647
'aic_gross_amount',
4748
].join(',')
49+
const UNKNOWN_HIGH_MONTHLY_QUOTA = 2147483647
4850

4951
function createCsv(rows: string[][]): File {
5052
const body = [HEADER, ...rows.map((row) => row.join(','))].join('\n')
@@ -170,6 +172,13 @@ describe('AIC included credit tiering and pool sizing', () => {
170172
expect(getPlanLabel(0)).toBe('Unknown')
171173
})
172174

175+
it('selects the maximum known monthly quota while ignoring unknown quota values', () => {
176+
expect(selectKnownMonthlyQuota(0, UNKNOWN_HIGH_MONTHLY_QUOTA)).toBe(0)
177+
expect(selectKnownMonthlyQuota(BUSINESS_MONTHLY_QUOTA, UNKNOWN_HIGH_MONTHLY_QUOTA)).toBe(BUSINESS_MONTHLY_QUOTA)
178+
expect(selectKnownMonthlyQuota(UNKNOWN_HIGH_MONTHLY_QUOTA, ENTERPRISE_MONTHLY_QUOTA)).toBe(ENTERPRISE_MONTHLY_QUOTA)
179+
expect(selectKnownMonthlyQuota(BUSINESS_MONTHLY_QUOTA, ENTERPRISE_MONTHLY_QUOTA)).toBe(ENTERPRISE_MONTHLY_QUOTA)
180+
})
181+
173182
it('does not create an organization pool for a single-user Pro/Student report', async () => {
174183
const file = createCsv([
175184
['2026-03-01', 'mona', 'copilot', 'copilot_ai_credit', 'GPT-5', '10', 'ai-credits', '0.01', '0.10', '0', '0.10', 'False', '300', '', '', '10', '0.10'],
@@ -213,6 +222,32 @@ describe('AIC included credit tiering and pool sizing', () => {
213222
)
214223
})
215224

225+
it('ignores unknown high quota rows when sizing a business pool', async () => {
226+
const file = createCsv([
227+
['2026-03-01', 'mona', 'copilot', 'copilot_ai_credit', 'GPT-5', '10', 'ai-credits', '0.01', '0.10', '0', '0.10', 'False', '300', 'octo', 'Cats', '10', '0.10'],
228+
['2026-03-02', 'mona', 'copilot', 'copilot_ai_credit', 'GPT-5', '10', 'ai-credits', '0.01', '0.10', '0', '0.10', 'False', `${UNKNOWN_HIGH_MONTHLY_QUOTA}`, 'octo', 'Cats', '10', '0.10'],
229+
])
230+
231+
await expect(calculateAicIncludedCreditsPool(file)).resolves.toBe(BUSINESS_MONTHLY_AIC_INCLUDED_CREDITS)
232+
})
233+
234+
it('ignores unknown high quota rows when sizing an enterprise pool', async () => {
235+
const file = createCsv([
236+
['2026-03-01', 'hubot', 'copilot', 'copilot_ai_credit', 'GPT-5', '10', 'ai-credits', '0.01', '0.10', '0', '0.10', 'False', '1000', 'octo', 'Cats', '10', '0.10'],
237+
['2026-03-02', 'hubot', 'copilot', 'copilot_ai_credit', 'GPT-5', '10', 'ai-credits', '0.01', '0.10', '0', '0.10', 'False', `${UNKNOWN_HIGH_MONTHLY_QUOTA}`, 'octo', 'Cats', '10', '0.10'],
238+
])
239+
240+
await expect(calculateAicIncludedCreditsPool(file)).resolves.toBe(ENTERPRISE_MONTHLY_AIC_INCLUDED_CREDITS)
241+
})
242+
243+
it('does not size a pool from users that only have unknown high quota rows', async () => {
244+
const file = createCsv([
245+
['2026-03-01', 'mona', 'copilot', 'copilot_ai_credit', 'GPT-5', '10', 'ai-credits', '0.01', '0.10', '0', '0.10', 'False', `${UNKNOWN_HIGH_MONTHLY_QUOTA}`, 'octo', 'Cats', '10', '0.10'],
246+
])
247+
248+
await expect(calculateAicIncludedCreditsPool(file)).resolves.toBe(0)
249+
})
250+
216251
it('uses override seat counts instead of active users when sizing an organization pool', async () => {
217252
const file = createCsv([
218253
['2026-03-01', 'mona', 'copilot', 'copilot_ai_credit', 'GPT-5', '10', 'ai-credits', '0.01', '0.10', '0', '0.10', 'False', '300', 'example-org', 'Cost Center A', '10', '0.10'],
@@ -406,6 +441,40 @@ describe('AIC included credit tiering and pool sizing', () => {
406441
}),
407442
])
408443
})
444+
445+
it('keeps user aggregation and license summary aligned when unknown high quota rows are present', async () => {
446+
const file = createCsv([
447+
['2026-03-01', 'mona', 'copilot', 'copilot_ai_credit', 'GPT-5', '1500', 'ai-credits', '0.01', '15.00', '0', '15.00', 'False', '300', 'example-org', 'Cost Center A', '1500', '15.00'],
448+
['2026-03-02', 'mona', 'copilot', 'copilot_ai_credit', 'GPT-5', '1500', 'ai-credits', '0.01', '15.00', '0', '15.00', 'False', `${UNKNOWN_HIGH_MONTHLY_QUOTA}`, 'example-org', 'Cost Center A', '1500', '15.00'],
449+
['2026-03-03', 'hubot', 'copilot', 'copilot_ai_credit', 'GPT-5', '3500', 'ai-credits', '0.01', '35.00', '0', '35.00', 'False', '1000', 'example-org', 'Cost Center A', '3500', '35.00'],
450+
['2026-03-04', 'hubot', 'copilot', 'copilot_ai_credit', 'GPT-5', '3500', 'ai-credits', '0.01', '35.00', '0', '35.00', 'False', `${UNKNOWN_HIGH_MONTHLY_QUOTA}`, 'example-org', 'Cost Center A', '3500', '35.00'],
451+
['2026-03-05', 'octocat', 'copilot', 'copilot_ai_credit', 'GPT-5', '100', 'ai-credits', '0.01', '1.00', '0', '1.00', 'False', `${UNKNOWN_HIGH_MONTHLY_QUOTA}`, 'example-org', 'Cost Center B', '100', '1.00'],
452+
])
453+
const users = new UserUsageAggregator()
454+
455+
await runPipeline(file, [users])
456+
457+
const userResult = users.result().users
458+
const licenseSummary = calculateLicenseSummary(userResult)
459+
460+
expect(userResult.find((user) => user.username === 'mona')).toEqual(expect.objectContaining({
461+
totalMonthlyQuota: BUSINESS_MONTHLY_QUOTA,
462+
}))
463+
expect(userResult.find((user) => user.username === 'hubot')).toEqual(expect.objectContaining({
464+
totalMonthlyQuota: ENTERPRISE_MONTHLY_QUOTA,
465+
}))
466+
expect(userResult.find((user) => user.username === 'octocat')).toEqual(expect.objectContaining({
467+
totalMonthlyQuota: 0,
468+
}))
469+
expect(licenseSummary).toEqual({
470+
rows: [
471+
{ label: 'Copilot Business', users: 1, includedAic: 3000 },
472+
{ label: 'Copilot Enterprise', users: 1, includedAic: 7000 },
473+
],
474+
totalUsers: 2,
475+
totalIncludedAic: 10000,
476+
})
477+
})
409478
})
410479

411480
describe('pooled AIC allocation and derived AIC discounts', () => {

src/pipeline/aicIncludedCredits.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ export const BUSINESS_MONTHLY_QUOTA = 300
1111
export const ENTERPRISE_MONTHLY_QUOTA = 1000
1212
export const PRO_MONTHLY_QUOTA = 300
1313
export const PRO_PLUS_MONTHLY_QUOTA = 1500
14+
const KNOWN_MONTHLY_QUOTAS = new Set([
15+
BUSINESS_MONTHLY_QUOTA,
16+
ENTERPRISE_MONTHLY_QUOTA,
17+
PRO_MONTHLY_QUOTA,
18+
PRO_PLUS_MONTHLY_QUOTA,
19+
])
1420

1521
export const BUSINESS_MONTHLY_AIC_INCLUDED_CREDITS = 3000
1622
export const ENTERPRISE_MONTHLY_AIC_INCLUDED_CREDITS = 7000
@@ -70,6 +76,16 @@ function calculateOrganizationIncludedCreditsPool(overrides: AicIncludedCreditsO
7076
)
7177
}
7278

79+
export function isKnownMonthlyQuota(totalMonthlyQuota: number): boolean {
80+
return Number.isFinite(totalMonthlyQuota) && KNOWN_MONTHLY_QUOTAS.has(totalMonthlyQuota)
81+
}
82+
83+
export function selectKnownMonthlyQuota(currentQuota: number, candidateQuota: number): number {
84+
const currentKnownQuota = isKnownMonthlyQuota(currentQuota) ? currentQuota : 0
85+
if (!isKnownMonthlyQuota(candidateQuota)) return currentKnownQuota
86+
return Math.max(currentKnownQuota, candidateQuota)
87+
}
88+
7389
export function inferReportPlanScope(userCount: number, hasOrganizationContext = false): ReportPlanScope {
7490
return userCount === 1 && !hasOrganizationContext ? 'individual' : 'organization'
7591
}
@@ -200,9 +216,7 @@ export async function calculateAicIncludedCreditsContext(
200216
}
201217

202218
const currentQuota = quotasByUser.get(username) ?? 0
203-
if (record.total_monthly_quota > currentQuota) {
204-
quotasByUser.set(username, record.total_monthly_quota)
205-
}
219+
quotasByUser.set(username, selectKnownMonthlyQuota(currentQuota, record.total_monthly_quota))
206220
}
207221

208222
const reportPlanScope = inferReportPlanScope(quotasByUser.size, hasOrganizationContext)

0 commit comments

Comments
 (0)