Skip to content

Commit 8d70792

Browse files
asizikovCopilot
andcommitted
feat: select included credit policy
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 26fc1ef commit 8d70792

3 files changed

Lines changed: 126 additions & 0 deletions

File tree

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { describe, expect, it } from 'vitest'
2+
3+
import type { ReportFormatMetadata } from './reportAdapters'
4+
import {
5+
NATIVE_AI_CREDITS_STANDARD_INCLUDED_CREDITS_POLICY,
6+
NATIVE_AI_CREDITS_SUMMER_PROMO_INCLUDED_CREDITS_POLICY,
7+
TRANSITION_PERIOD_INCLUDED_CREDITS_POLICY,
8+
resolveIncludedCreditsPolicy,
9+
} from './includedCreditsPolicy'
10+
11+
const TRANSITION_PERIOD_REPORT_METADATA = {
12+
format: 'transition-period-billing-preview',
13+
label: 'Transition Period Billing Preview report',
14+
supported: true,
15+
} satisfies ReportFormatMetadata
16+
17+
const NATIVE_AI_CREDITS_REPORT_METADATA = {
18+
format: 'native-ai-credits',
19+
label: 'Native AI Credits report',
20+
supported: false,
21+
} satisfies ReportFormatMetadata
22+
23+
describe('resolveIncludedCreditsPolicy', () => {
24+
it('returns the transition-period billing preview policy for transition reports', () => {
25+
expect(resolveIncludedCreditsPolicy(TRANSITION_PERIOD_REPORT_METADATA)).toBe(
26+
TRANSITION_PERIOD_INCLUDED_CREDITS_POLICY,
27+
)
28+
expect(resolveIncludedCreditsPolicy('transition-period-billing-preview', {
29+
startDate: '2026-09-01',
30+
endDate: '2026-09-30',
31+
})).toBe(TRANSITION_PERIOD_INCLUDED_CREDITS_POLICY)
32+
})
33+
34+
it('returns the native AI Credits summer promo policy for native report periods before September 2026', () => {
35+
expect(resolveIncludedCreditsPolicy(NATIVE_AI_CREDITS_REPORT_METADATA, {
36+
startDate: '2026-06-01',
37+
endDate: '2026-06-30',
38+
})).toBe(NATIVE_AI_CREDITS_SUMMER_PROMO_INCLUDED_CREDITS_POLICY)
39+
})
40+
41+
it('uses period start to keep native report periods starting on 2026-08-31 in the summer promo policy', () => {
42+
expect(resolveIncludedCreditsPolicy(NATIVE_AI_CREDITS_REPORT_METADATA, {
43+
startDate: '2026-08-31',
44+
endDate: '2026-09-30',
45+
})).toBe(NATIVE_AI_CREDITS_SUMMER_PROMO_INCLUDED_CREDITS_POLICY)
46+
})
47+
48+
it('returns the native AI Credits standard policy for native report periods starting on 2026-09-01 onward', () => {
49+
expect(resolveIncludedCreditsPolicy(NATIVE_AI_CREDITS_REPORT_METADATA, {
50+
startDate: '2026-09-01',
51+
endDate: '2026-09-30',
52+
})).toBe(NATIVE_AI_CREDITS_STANDARD_INCLUDED_CREDITS_POLICY)
53+
expect(resolveIncludedCreditsPolicy(NATIVE_AI_CREDITS_REPORT_METADATA, {
54+
startDate: '2026-10-01',
55+
endDate: '2026-10-31',
56+
})).toBe(NATIVE_AI_CREDITS_STANDARD_INCLUDED_CREDITS_POLICY)
57+
})
58+
59+
it('defaults native AI Credits reports without a valid period start to the standard policy', () => {
60+
expect(resolveIncludedCreditsPolicy(NATIVE_AI_CREDITS_REPORT_METADATA)).toBe(
61+
NATIVE_AI_CREDITS_STANDARD_INCLUDED_CREDITS_POLICY,
62+
)
63+
expect(resolveIncludedCreditsPolicy(NATIVE_AI_CREDITS_REPORT_METADATA, {
64+
endDate: '2026-08-31',
65+
})).toBe(NATIVE_AI_CREDITS_STANDARD_INCLUDED_CREDITS_POLICY)
66+
expect(resolveIncludedCreditsPolicy(NATIVE_AI_CREDITS_REPORT_METADATA, {
67+
startDate: '8/31/26',
68+
endDate: '2026-08-31',
69+
})).toBe(NATIVE_AI_CREDITS_STANDARD_INCLUDED_CREDITS_POLICY)
70+
})
71+
})

src/pipeline/includedCreditsPolicy.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import type { ReportFormat, ReportFormatMetadata } from './reportAdapters'
2+
13
export type QuotaUnit = 'pru' | 'aic'
24
export type OrganizationIncludedCreditTier = 'business' | 'enterprise'
35

@@ -27,8 +29,15 @@ export type IncludedCreditsPolicy = {
2729
readonly organizationPlans: OrganizationIncludedCreditPlans
2830
}
2931

32+
export type ReportPeriod = {
33+
readonly startDate?: string | null
34+
readonly endDate?: string | null
35+
}
36+
3037
const COPILOT_BUSINESS_LABEL = 'Copilot Business'
3138
const COPILOT_ENTERPRISE_LABEL = 'Copilot Enterprise'
39+
const NATIVE_AI_CREDITS_STANDARD_POLICY_START_DATE = '2026-09-01'
40+
const ISO_DATE_PATTERN = /^(\d{4})-(\d{2})-(\d{2})$/
3241

3342
export const TRANSITION_PERIOD_INCLUDED_CREDITS_POLICY = {
3443
id: 'transition-period-billing-preview',
@@ -101,3 +110,46 @@ export const NATIVE_AI_CREDITS_STANDARD_INCLUDED_CREDITS_POLICY = {
101110
},
102111
},
103112
} as const satisfies IncludedCreditsPolicy
113+
114+
function getReportFormat(reportMetadataOrFormat: ReportFormat | ReportFormatMetadata): ReportFormat {
115+
return typeof reportMetadataOrFormat === 'string'
116+
? reportMetadataOrFormat
117+
: reportMetadataOrFormat.format
118+
}
119+
120+
function isValidIsoDate(value: string): boolean {
121+
const match = ISO_DATE_PATTERN.exec(value)
122+
if (!match) return false
123+
124+
const year = Number(match[1])
125+
const month = Number(match[2])
126+
const day = Number(match[3])
127+
const date = new Date(Date.UTC(year, month - 1, day))
128+
129+
return (
130+
date.getUTCFullYear() === year
131+
&& date.getUTCMonth() === month - 1
132+
&& date.getUTCDate() === day
133+
)
134+
}
135+
136+
function isBeforeIsoDate(value: string | null | undefined, boundary: string): boolean {
137+
if (!value || !isValidIsoDate(value)) return false
138+
139+
return value < boundary
140+
}
141+
142+
export function resolveIncludedCreditsPolicy(
143+
reportMetadataOrFormat: ReportFormat | ReportFormatMetadata,
144+
reportPeriod: ReportPeriod = {},
145+
): IncludedCreditsPolicy {
146+
if (getReportFormat(reportMetadataOrFormat) === 'transition-period-billing-preview') {
147+
return TRANSITION_PERIOD_INCLUDED_CREDITS_POLICY
148+
}
149+
150+
if (isBeforeIsoDate(reportPeriod.startDate, NATIVE_AI_CREDITS_STANDARD_POLICY_START_DATE)) {
151+
return NATIVE_AI_CREDITS_SUMMER_PROMO_INCLUDED_CREDITS_POLICY
152+
}
153+
154+
return NATIVE_AI_CREDITS_STANDARD_INCLUDED_CREDITS_POLICY
155+
}

src/pipeline/runPipeline.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { Aggregator } from './aggregators/base'
22
import { createAicIncludedCreditsAllocator, type AicIncludedCreditsOverrides } from './aicIncludedCredits'
3+
import { resolveIncludedCreditsPolicy } from './includedCreditsPolicy'
34
import {
45
InvalidReportError,
56
parseTokenUsageHeader,
@@ -92,6 +93,7 @@ export async function runPipeline(
9293
const { includedCreditsOverrides = {}, progressResolution = 500, onProgress } = options ?? {}
9394
const reportAdapter = await validateFileFormat(file)
9495
const reportMetadata = reportAdapter.metadata
96+
const includedCreditsPolicy = resolveIncludedCreditsPolicy(reportMetadata)
9597
let lastProgressStage: PipelineProgress['stage'] | null = null
9698
let lastProgressPercent = -1
9799
let lastProgressTimestamp = 0
@@ -138,6 +140,7 @@ export async function runPipeline(
138140
}
139141

140142
const aicIncludedCreditAllocator = await createAicIncludedCreditsAllocator(file, includedCreditsOverrides, {
143+
includedCreditsPolicy,
141144
onProgress: (streamProgress) => {
142145
emitProgress('analyzing', 0, streamProgress)
143146
},

0 commit comments

Comments
 (0)