From 19003d5b38cf459f6a2ce1d1411a56df6dd83a09 Mon Sep 17 00:00:00 2001 From: Anton Sizikov Date: Sat, 6 Jun 2026 23:37:24 +0200 Subject: [PATCH 1/2] refactor: remove report support gating Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/App.tsx | 1 - src/pipeline/aggregators/usageMetrics.ts | 4 +- src/pipeline/aicIncludedCredits.test.ts | 1 - src/pipeline/includedCreditsPolicy.test.ts | 2 - src/pipeline/parser.test.ts | 71 ++-------------------- src/pipeline/parser.ts | 23 +------ src/pipeline/reportAdapters.test.ts | 16 ++--- src/pipeline/reportAdapters.ts | 24 +------- src/pipeline/reportUsageMetrics.test.ts | 1 - src/pipeline/runPipeline.test.ts | 63 +++---------------- src/pipeline/runPipeline.ts | 11 +--- 11 files changed, 30 insertions(+), 187 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index dab1731..1a675cc 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -146,7 +146,6 @@ function App() { userAggregator, ] }, { - enableNativeAiCreditsProcessing: true, includedCreditsOverrides, progressResolution: 500, onProgress, diff --git a/src/pipeline/aggregators/usageMetrics.ts b/src/pipeline/aggregators/usageMetrics.ts index 300c3c9..2acfbdf 100644 --- a/src/pipeline/aggregators/usageMetrics.ts +++ b/src/pipeline/aggregators/usageMetrics.ts @@ -1,5 +1,5 @@ import type { TokenUsageRecord } from '../parser' -import { getDefaultSupportedUsageReportAdapter, type ReportFormat, type ReportFormatMetadata } from '../reportAdapters' +import { getDefaultUsageReportAdapter, type ReportFormat, type ReportFormatMetadata } from '../reportAdapters' import { getReportUsageMetrics } from '../reportUsageMetrics' export type AggregatorUsageMetrics = { @@ -16,7 +16,7 @@ export function getAggregatorReportFormat( reportMetadataOrFormat?: ReportFormat | ReportFormatMetadata, ): ReportFormat { if (!reportMetadataOrFormat) { - return getDefaultSupportedUsageReportAdapter().metadata.format + return getDefaultUsageReportAdapter().metadata.format } return typeof reportMetadataOrFormat === 'string' diff --git a/src/pipeline/aicIncludedCredits.test.ts b/src/pipeline/aicIncludedCredits.test.ts index 9e92aad..13e2336 100644 --- a/src/pipeline/aicIncludedCredits.test.ts +++ b/src/pipeline/aicIncludedCredits.test.ts @@ -73,7 +73,6 @@ const NATIVE_AI_CREDITS_HEADER = [ const NATIVE_AI_CREDITS_REPORT_METADATA = { format: 'native-ai-credits', label: 'Native AI Credits report', - supported: false, } as const const UNKNOWN_HIGH_MONTHLY_QUOTA = 2147483647 diff --git a/src/pipeline/includedCreditsPolicy.test.ts b/src/pipeline/includedCreditsPolicy.test.ts index d81f6b6..21cc77b 100644 --- a/src/pipeline/includedCreditsPolicy.test.ts +++ b/src/pipeline/includedCreditsPolicy.test.ts @@ -11,13 +11,11 @@ import { const TRANSITION_PERIOD_REPORT_METADATA = { format: 'transition-period-billing-preview', label: 'Transition Period Billing Preview report', - supported: true, } satisfies ReportFormatMetadata const NATIVE_AI_CREDITS_REPORT_METADATA = { format: 'native-ai-credits', label: 'Native AI Credits report', - supported: false, } satisfies ReportFormatMetadata describe('resolveIncludedCreditsPolicy', () => { diff --git a/src/pipeline/parser.test.ts b/src/pipeline/parser.test.ts index 4ab36e9..e722926 100644 --- a/src/pipeline/parser.test.ts +++ b/src/pipeline/parser.test.ts @@ -9,10 +9,8 @@ import { parseNormalizedTokenUsageRecord, parseTokenUsageHeader, parseTokenUsageRecord, - UnsupportedNativeAiCreditsReportError, - UnsupportedReportVersionError, + PreAiCreditsReportVersionError, validateHeader, - validateSupportedReportRecord, } from './parser' const FULL_HEADER = [ @@ -822,7 +820,7 @@ describe('validateHeader', () => { expect(() => validateHeader(header)).toThrow(InvalidReportError) }) - it('throws UnsupportedReportVersionError when only aic columns are missing', () => { + it('throws PreAiCreditsReportVersionError when only aic columns are missing', () => { const preAicHeader = [ 'date', 'username', @@ -841,10 +839,10 @@ describe('validateHeader', () => { 'cost_center_name', ].join(',') const header = parseTokenUsageHeader(preAicHeader) - expect(() => validateHeader(header)).toThrow(UnsupportedReportVersionError) + expect(() => validateHeader(header)).toThrow(PreAiCreditsReportVersionError) }) - it('throws UnsupportedReportVersionError when only one aic column is missing', () => { + it('throws PreAiCreditsReportVersionError when only one aic column is missing', () => { const preAicHeader = [ 'date', 'username', @@ -864,7 +862,7 @@ describe('validateHeader', () => { 'aic_quantity', ].join(',') const header = parseTokenUsageHeader(preAicHeader) - expect(() => validateHeader(header)).toThrow(UnsupportedReportVersionError) + expect(() => validateHeader(header)).toThrow(PreAiCreditsReportVersionError) }) it('throws InvalidReportError when a billing header omits a required non-aic billing column', () => { @@ -891,62 +889,3 @@ describe('validateHeader', () => { expect(() => validateHeader(header)).toThrow(InvalidReportError) }) }) - -describe('validateSupportedReportRecord', () => { - it('throws a clear error for the native AI Credits report format', () => { - const header = parseTokenUsageHeader(HEADER_WITHOUT_EXCEEDS_QUOTA) - const record = parseTokenUsageRecord( - buildRow([ - '5/29/26', - 'mona', - 'copilot', - 'copilot_ai_credit', - 'Auto: Claude Haiku 4.5', - '96.9990345', - 'ai-credits', - '0.01', - '0.969990345', - '0', - '0.969990345', - '3900', - 'example-org', - '', - '96.9990345', - '0.969990345', - ]), - header, - ) - - expect(() => validateSupportedReportRecord(header, record)).toThrow(UnsupportedNativeAiCreditsReportError) - expect(() => validateSupportedReportRecord(header, record)).toThrow( - 'currently supports PRU vs usage-based billing reports generated for the April and May billing periods', - ) - }) - - it('accepts PRU report rows when exceeds_quota is absent', () => { - const header = parseTokenUsageHeader(HEADER_WITHOUT_EXCEEDS_QUOTA) - const record = parseTokenUsageRecord( - buildRow([ - '2026-05-29', - 'mona', - 'copilot', - 'copilot_premium_request', - 'Auto: Claude Haiku 4.5', - '2', - 'requests', - '0.04', - '0.08', - '0', - '0.08', - '300', - 'example-org', - 'Cost Center A', - '20', - '0.20', - ]), - header, - ) - - expect(() => validateSupportedReportRecord(header, record)).not.toThrow() - }) -}) diff --git a/src/pipeline/parser.ts b/src/pipeline/parser.ts index 1e8e781..a98b4b5 100644 --- a/src/pipeline/parser.ts +++ b/src/pipeline/parser.ts @@ -130,24 +130,13 @@ export class InvalidReportError extends Error { } } -export class UnsupportedReportVersionError extends Error { +export class PreAiCreditsReportVersionError extends Error { constructor() { super( `This report was exported before usage-based billing was introduced and cannot be displayed. ` + `Please upload a more recent report that includes the AI Credits columns.`, ) - this.name = 'UnsupportedReportVersionError' - } -} - -export class UnsupportedNativeAiCreditsReportError extends Error { - constructor() { - super( - `This billing preview app currently supports PRU vs usage-based billing reports generated for ` + - `the April and May billing periods. Reports generated on or after June 1 use AI Credits as ` + - `the primary unit and are not supported yet.`, - ) - this.name = 'UnsupportedNativeAiCreditsReportError' + this.name = 'PreAiCreditsReportVersionError' } } @@ -162,7 +151,7 @@ export function validateHeader(header: TokenUsageHeader): void { validateBaseHeader(header) const missingAic = REQUIRED_AIC_COLUMNS.filter((col) => !(col in header.index)) if (missingAic.length > 0) { - throw new UnsupportedReportVersionError() + throw new PreAiCreditsReportVersionError() } } @@ -173,12 +162,6 @@ export function hasNativeAiCreditsReportSignature(header: TokenUsageHeader, reco return lacksExceedsQuota && usesNativeAiCreditsUnit } -export function validateSupportedReportRecord(header: TokenUsageHeader, record: TokenUsageRecord): void { - if (hasNativeAiCreditsReportSignature(header, record)) { - throw new UnsupportedNativeAiCreditsReportError() - } -} - function stripBom(s: string): string { return s.replace(/^\uFEFF/, '') } diff --git a/src/pipeline/reportAdapters.test.ts b/src/pipeline/reportAdapters.test.ts index a0fece0..05dac96 100644 --- a/src/pipeline/reportAdapters.test.ts +++ b/src/pipeline/reportAdapters.test.ts @@ -2,8 +2,7 @@ import { describe, expect, it } from 'vitest' import { InvalidReportError, - UnsupportedNativeAiCreditsReportError, - UnsupportedReportVersionError, + PreAiCreditsReportVersionError, parseTokenUsageHeader, parseTokenUsageRecord, } from './parser' @@ -103,7 +102,6 @@ describe('usage report adapters', () => { expect(detectReportFormat(header, record)).toBe('transition-period-billing-preview') expect(selectUsageReportAdapter(header, record).metadata).toMatchObject({ format: 'transition-period-billing-preview', - supported: true, }) expect(() => validateUsageReportFirstRecord(header, record)).not.toThrow() }) @@ -196,7 +194,7 @@ describe('usage report adapters', () => { }) }) - it('detects native AI Credits reports and routes them to an unsupported adapter', () => { + it('detects native AI Credits reports and routes them to the native adapter', () => { const header = parseTokenUsageHeader(HEADER_WITHOUT_EXCEEDS_QUOTA) const row = buildRow([ '2026-06-01', @@ -226,11 +224,9 @@ describe('usage report adapters', () => { expect(detectReportFormat(header, record)).toBe('native-ai-credits') expect(adapter.metadata).toMatchObject({ format: 'native-ai-credits', - supported: false, }) - expect(() => adapter.validateFirstRecord(header, record)).toThrow(UnsupportedNativeAiCreditsReportError) - expect(() => validateUsageReportFirstRecord(header, record)).toThrow(UnsupportedNativeAiCreditsReportError) + expect(() => validateUsageReportFirstRecord(header, record)).not.toThrow() expect(adapter.parseRecord(row, header)).toMatchObject({ date: '2026-06-01', quantity: 96.9990345, @@ -267,7 +263,7 @@ describe('usage report adapters', () => { expect(() => validateUsageReportHeader(header)).not.toThrow() expect(detectReportFormat(header, record)).toBe('native-ai-credits') expect(adapter.metadata.format).toBe('native-ai-credits') - expect(() => validateUsageReportFirstRecord(header, record, { allowUnsupportedNativeAiCredits: true })).not.toThrow() + expect(() => validateUsageReportFirstRecord(header, record)).not.toThrow() expect(adapter.parseRecord(row, header)).toMatchObject({ date: '2026-06-01', quantity: 42.726213, @@ -280,7 +276,7 @@ describe('usage report adapters', () => { }) }) - it('normalizes native AI Credits dates through the unsupported adapter parser hook', () => { + it('normalizes native AI Credits dates through the native adapter parser hook', () => { const header = parseTokenUsageHeader(HEADER_WITHOUT_EXCEEDS_QUOTA) const row = buildRow([ '2026-06-01', @@ -331,6 +327,6 @@ describe('usage report adapters', () => { 'cost_center_name', ].join(',')) - expect(() => validateUsageReportHeader(header)).toThrow(UnsupportedReportVersionError) + expect(() => validateUsageReportHeader(header)).toThrow(PreAiCreditsReportVersionError) }) }) diff --git a/src/pipeline/reportAdapters.ts b/src/pipeline/reportAdapters.ts index b61761a..32b140e 100644 --- a/src/pipeline/reportAdapters.ts +++ b/src/pipeline/reportAdapters.ts @@ -1,11 +1,9 @@ import { - UnsupportedNativeAiCreditsReportError, hasNativeAiCreditsReportSignature, parseNativeAiCreditsUsageRecord, parseNormalizedTokenUsageRecord, validateBaseHeader, validateHeader as validateTokenUsageHeader, - validateSupportedReportRecord, type TokenUsageHeader, type TokenUsageRecord, } from './parser' @@ -15,32 +13,22 @@ export type ReportFormat = 'transition-period-billing-preview' | 'native-ai-cred export type ReportFormatMetadata = { format: ReportFormat label: string - supported: boolean } export interface UsageReportAdapter { metadata: ReportFormatMetadata validateHeader(header: TokenUsageHeader): void - validateFirstRecord(header: TokenUsageHeader, record: TokenUsageRecord): void parseRecord(line: string, header: TokenUsageHeader): TokenUsageRecord | null } -export interface UsageReportValidationOptions { - allowUnsupportedNativeAiCredits?: boolean -} - const TRANSITION_PERIOD_BILLING_PREVIEW_REPORT_ADAPTER: UsageReportAdapter = { metadata: { format: 'transition-period-billing-preview', label: 'Transition Period Billing Preview report', - supported: true, }, validateHeader(header) { validateTokenUsageHeader(header) }, - validateFirstRecord(header, record) { - validateSupportedReportRecord(header, record) - }, parseRecord(line, header) { return parseNormalizedTokenUsageRecord(line, header) }, @@ -50,14 +38,10 @@ const NATIVE_AI_CREDITS_REPORT_ADAPTER: UsageReportAdapter = { metadata: { format: 'native-ai-credits', label: 'Native AI Credits report', - supported: false, }, validateHeader(header) { validateBaseHeader(header) }, - validateFirstRecord() { - throw new UnsupportedNativeAiCreditsReportError() - }, parseRecord(line, header) { return parseNativeAiCreditsUsageRecord(line, header) }, @@ -72,7 +56,7 @@ function hasTransitionPeriodAicColumns(header: TokenUsageHeader): boolean { return 'aic_quantity' in header.index && 'aic_gross_amount' in header.index } -export function getDefaultSupportedUsageReportAdapter(): UsageReportAdapter { +export function getDefaultUsageReportAdapter(): UsageReportAdapter { return TRANSITION_PERIOD_BILLING_PREVIEW_REPORT_ADAPTER } @@ -84,7 +68,7 @@ export function validateUsageReportHeader(header: TokenUsageHeader): UsageReport return NATIVE_AI_CREDITS_REPORT_ADAPTER } - const adapter = getDefaultSupportedUsageReportAdapter() + const adapter = getDefaultUsageReportAdapter() adapter.validateHeader(header) return adapter } @@ -105,12 +89,8 @@ export function selectUsageReportAdapter(header: TokenUsageHeader, firstRecord: export function validateUsageReportFirstRecord( header: TokenUsageHeader, firstRecord: TokenUsageRecord, - options?: UsageReportValidationOptions, ): UsageReportAdapter { const adapter = selectUsageReportAdapter(header, firstRecord) adapter.validateHeader(header) - if (!(options?.allowUnsupportedNativeAiCredits && adapter.metadata.format === 'native-ai-credits')) { - adapter.validateFirstRecord(header, firstRecord) - } return adapter } diff --git a/src/pipeline/reportUsageMetrics.test.ts b/src/pipeline/reportUsageMetrics.test.ts index a8c3009..49e5a6f 100644 --- a/src/pipeline/reportUsageMetrics.test.ts +++ b/src/pipeline/reportUsageMetrics.test.ts @@ -51,7 +51,6 @@ const NATIVE_AI_CREDITS_HEADER = [ const TRANSITION_PERIOD_METADATA: ReportFormatMetadata = { format: 'transition-period-billing-preview', label: 'Transition Period Billing Preview report', - supported: true, } function buildRow(values: string[]): string { diff --git a/src/pipeline/runPipeline.test.ts b/src/pipeline/runPipeline.test.ts index d83faee..6257d4e 100644 --- a/src/pipeline/runPipeline.test.ts +++ b/src/pipeline/runPipeline.test.ts @@ -5,8 +5,7 @@ import { DailyUsageAggregator } from './aggregators/dailyUsageAggregator' import { UserUsageAggregator } from './aggregators/userUsageAggregator' import { InvalidReportError, - UnsupportedNativeAiCreditsReportError, - UnsupportedReportVersionError, + PreAiCreditsReportVersionError, type TokenUsageHeader, type TokenUsageRecord, } from './parser' @@ -71,13 +70,11 @@ const NATIVE_AI_CREDITS_HEADER_WITHOUT_ALIASES = [ const TRANSITION_PERIOD_REPORT_METADATA = { format: 'transition-period-billing-preview', label: 'Transition Period Billing Preview report', - supported: true, } const NATIVE_AI_CREDITS_REPORT_METADATA = { format: 'native-ai-credits', label: 'Native AI Credits report', - supported: false, } as const function createCsv(rows: string[][], header = HEADER): File { @@ -153,39 +150,11 @@ describe('runPipeline', () => { ].join(',') const aggregator = new CaptureAggregator() - await expect(runPipeline(createCsv([], header), [aggregator])).rejects.toThrow(UnsupportedReportVersionError) + await expect(runPipeline(createCsv([], header), [aggregator])).rejects.toThrow(PreAiCreditsReportVersionError) expect(aggregator.result()).toEqual([]) }) - it('rejects native AI Credits reports before aggregator calls', async () => { - const file = createCsv([ - [ - '5/29/26', - 'mona', - 'copilot', - 'copilot_ai_credit', - 'Auto: Claude Haiku 4.5', - '96.9990345', - 'ai-credits', - '0.01', - '0.969990345', - '0', - '0.969990345', - '3900', - 'example-org', - '', - '96.9990345', - '0.969990345', - ], - ], NATIVE_AI_CREDITS_HEADER) - const aggregator = new CaptureAggregator() - - await expect(runPipeline(file, [aggregator])).rejects.toThrow(UnsupportedNativeAiCreditsReportError) - expect(aggregator.headerCalls()).toBe(0) - expect(aggregator.result()).toEqual([]) - }) - - it('processes native AI Credits reports with native metadata when explicitly enabled', async () => { + it('processes native AI Credits reports with native metadata', async () => { const file = createCsv([ [ '5/29/26', @@ -208,9 +177,7 @@ describe('runPipeline', () => { ], NATIVE_AI_CREDITS_HEADER) const aggregator = new CaptureAggregator() - const result = await runPipeline(file, [aggregator], { - enableNativeAiCreditsProcessing: true, - }) + const result = await runPipeline(file, [aggregator]) expect(result).toEqual({ reportMetadata: NATIVE_AI_CREDITS_REPORT_METADATA, @@ -275,9 +242,7 @@ describe('runPipeline', () => { const daily = new DailyUsageAggregator(NATIVE_AI_CREDITS_REPORT_METADATA) const users = new UserUsageAggregator(NATIVE_AI_CREDITS_REPORT_METADATA) - await runPipeline(file, [daily, users], { - enableNativeAiCreditsProcessing: true, - }) + await runPipeline(file, [daily, users]) expect(daily.result().dailyData).toEqual([ expect.objectContaining({ @@ -363,8 +328,6 @@ describe('runPipeline', () => { await runPipeline(file, (reportMetadata) => { daily = new DailyUsageAggregator(reportMetadata) return [daily] - }, { - enableNativeAiCreditsProcessing: true, }) expect(daily.result().dailyData).toEqual([ @@ -431,8 +394,6 @@ describe('runPipeline', () => { const result = await runPipeline(file, (reportMetadata) => { daily = new DailyUsageAggregator(reportMetadata) return [daily] - }, { - enableNativeAiCreditsProcessing: true, }) expect(result.reportMetadata).toEqual(NATIVE_AI_CREDITS_REPORT_METADATA) @@ -480,8 +441,6 @@ describe('runPipeline', () => { factoryMetadata = reportMetadata daily = new DailyUsageAggregator(reportMetadata) return [daily] - }, { - enableNativeAiCreditsProcessing: true, }) expect(factoryMetadata).toEqual(NATIVE_AI_CREDITS_REPORT_METADATA) @@ -524,12 +483,8 @@ describe('runPipeline', () => { const summerAggregator = new CaptureAggregator() const septemberAggregator = new CaptureAggregator() - await runPipeline(createNativePolicyCsv('8/31/26'), [summerAggregator], { - enableNativeAiCreditsProcessing: true, - }) - await runPipeline(createNativePolicyCsv('9/1/26'), [septemberAggregator], { - enableNativeAiCreditsProcessing: true, - }) + await runPipeline(createNativePolicyCsv('8/31/26'), [summerAggregator]) + await runPipeline(createNativePolicyCsv('9/1/26'), [septemberAggregator]) expect(summerAggregator.result()[0]).toEqual(expect.objectContaining({ date: '2026-08-31', @@ -541,7 +496,7 @@ describe('runPipeline', () => { expect(septemberAggregator.result()[0].aic_net_amount).toBeCloseTo(11) }) - it('returns transition-period metadata while processing supported reports', async () => { + it('returns transition-period metadata while processing transition-period reports', async () => { const file = createCsv([ ['2026-04-25', 'mona', 'copilot', 'copilot_premium_request', 'GPT-5', '0', 'requests', '0.04', '0', '0', '0', 'False', '300', '', '', '0', '0'], ['2026-04-25', 'mona', 'copilot', 'copilot_premium_request', 'GPT-5', '10', 'requests', '0.04', '0.40', '0', '0.40', 'False', '0', '', '', '100', '1.00'], @@ -567,7 +522,7 @@ describe('runPipeline', () => { }) }) - it('keeps transition-period allocation for supported reports after the native policy boundary', async () => { + it('keeps transition-period allocation for transition-period reports after the native policy boundary', async () => { const file = createCsv([ ['2026-09-01', 'mona', 'copilot', 'copilot_ai_credit', 'GPT-5', '3000', 'ai-credits', '0.01', '30.00', '0', '30.00', 'False', '300', 'example-org', 'Cost Center A', '3000', '30.00'], ]) diff --git a/src/pipeline/runPipeline.ts b/src/pipeline/runPipeline.ts index 2b43230..e0d7553 100644 --- a/src/pipeline/runPipeline.ts +++ b/src/pipeline/runPipeline.ts @@ -12,11 +12,10 @@ import { validateUsageReportHeader, type ReportFormatMetadata, type UsageReportAdapter, - type UsageReportValidationOptions, } from './reportAdapters' import { streamLines, type StreamProgress } from './streamer' -async function validateFileFormat(file: File, options?: UsageReportValidationOptions): Promise { +async function validateFileFormat(file: File): Promise { let header: TokenUsageHeader | null = null let selectedAdapter: UsageReportAdapter | null = null @@ -32,7 +31,7 @@ async function validateFileFormat(file: File, options?: UsageReportValidationOpt continue } - return validateUsageReportFirstRecord(header, parseTokenUsageRecord(trimmed, header), options) + return validateUsageReportFirstRecord(header, parseTokenUsageRecord(trimmed, header)) } if (!selectedAdapter) { @@ -51,7 +50,6 @@ export interface PipelineProgress { } export interface PipelineOptions { - enableNativeAiCreditsProcessing?: boolean includedCreditsOverrides?: AicIncludedCreditsOverrides progressResolution?: number onProgress?: (progress: PipelineProgress) => void @@ -96,14 +94,11 @@ export async function runPipeline( options?: PipelineOptions, ): Promise { const { - enableNativeAiCreditsProcessing = false, includedCreditsOverrides = {}, progressResolution = 500, onProgress, } = options ?? {} - const reportAdapter = await validateFileFormat(file, { - allowUnsupportedNativeAiCredits: enableNativeAiCreditsProcessing, - }) + const reportAdapter = await validateFileFormat(file) const reportMetadata = reportAdapter.metadata const aggregators = typeof aggregatorsOrFactory === 'function' ? aggregatorsOrFactory(reportMetadata) From 6c499300e8e08352632cdde92945815b3b4c552b Mon Sep 17 00:00:00 2001 From: Anton Sizikov Date: Sun, 7 Jun 2026 10:56:04 +0200 Subject: [PATCH 2/2] fix: classify header-only native reports Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/pipeline/reportAdapters.test.ts | 6 ++++++ src/pipeline/reportAdapters.ts | 6 +----- src/pipeline/runPipeline.test.ts | 11 +++++++++++ 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/pipeline/reportAdapters.test.ts b/src/pipeline/reportAdapters.test.ts index 05dac96..a9e0fd1 100644 --- a/src/pipeline/reportAdapters.test.ts +++ b/src/pipeline/reportAdapters.test.ts @@ -135,6 +135,12 @@ describe('usage report adapters', () => { expect(() => validateUsageReportFirstRecord(header, record)).not.toThrow() }) + it('uses the native adapter for header-only reports without exceeds_quota', () => { + const header = parseTokenUsageHeader(HEADER_WITHOUT_EXCEEDS_QUOTA) + + expect(validateUsageReportHeader(header).metadata.format).toBe('native-ai-credits') + }) + it('normalizes transition-period rows through the adapter parser', () => { const header = parseTokenUsageHeader(TRANSITION_PERIOD_HEADER) const adapter = validateUsageReportHeader(header) diff --git a/src/pipeline/reportAdapters.ts b/src/pipeline/reportAdapters.ts index 32b140e..2c82961 100644 --- a/src/pipeline/reportAdapters.ts +++ b/src/pipeline/reportAdapters.ts @@ -52,10 +52,6 @@ const REPORT_ADAPTERS: Record = { 'native-ai-credits': NATIVE_AI_CREDITS_REPORT_ADAPTER, } -function hasTransitionPeriodAicColumns(header: TokenUsageHeader): boolean { - return 'aic_quantity' in header.index && 'aic_gross_amount' in header.index -} - export function getDefaultUsageReportAdapter(): UsageReportAdapter { return TRANSITION_PERIOD_BILLING_PREVIEW_REPORT_ADAPTER } @@ -63,7 +59,7 @@ export function getDefaultUsageReportAdapter(): UsageReportAdapter { export function validateUsageReportHeader(header: TokenUsageHeader): UsageReportAdapter { validateBaseHeader(header) - if (!('exceeds_quota' in header.index) && !hasTransitionPeriodAicColumns(header)) { + if (!('exceeds_quota' in header.index)) { NATIVE_AI_CREDITS_REPORT_ADAPTER.validateHeader(header) return NATIVE_AI_CREDITS_REPORT_ADAPTER } diff --git a/src/pipeline/runPipeline.test.ts b/src/pipeline/runPipeline.test.ts index 6257d4e..79d82c2 100644 --- a/src/pipeline/runPipeline.test.ts +++ b/src/pipeline/runPipeline.test.ts @@ -123,6 +123,17 @@ describe('runPipeline', () => { expect(aggregator.result()).toEqual([]) }) + it('returns native metadata for a valid native header-only report', async () => { + const aggregator = new CaptureAggregator() + + await expect(runPipeline(createCsv([], NATIVE_AI_CREDITS_HEADER), [aggregator])).resolves.toEqual({ + reportMetadata: NATIVE_AI_CREDITS_REPORT_METADATA, + reportRowCount: 0, + processedRowCount: 0, + }) + expect(aggregator.result()).toEqual([]) + }) + it('rejects a malformed header-only report', async () => { const aggregator = new CaptureAggregator()