1+ import type { Aggregator } from './base'
2+ import { getUsageMetrics , type TokenUsageHeader , type TokenUsageRecord } from '../parser'
3+
4+ export type ProductUsageTotals = {
5+ requests : number
6+ aiuQuantity : number
7+ grossAmount : number
8+ aiuGrossAmount : number
9+ }
10+
11+ export type ProductUsage = {
12+ product : string
13+ totals : ProductUsageTotals
14+ models : Record < string , ProductUsageTotals >
15+ }
16+
17+ export type ProductUsageResult = {
18+ products : ProductUsage [ ]
19+ }
20+
21+ function createTotals ( ) : ProductUsageTotals {
22+ return {
23+ requests : 0 ,
24+ aiuQuantity : 0 ,
25+ grossAmount : 0 ,
26+ aiuGrossAmount : 0 ,
27+ }
28+ }
29+
30+ function getFriendlyProductName ( record : TokenUsageRecord ) : string {
31+ const model = record . model . trim ( )
32+
33+ if ( model === 'Code Review model' ) return 'Code Review'
34+ if ( model === 'Coding Agent model' ) return 'Coding Agent'
35+ return 'Copilot'
36+ }
37+
38+ export class ProductUsageAggregator implements Aggregator < TokenUsageRecord , ProductUsageResult , TokenUsageHeader > {
39+ private readonly byProduct = new Map < string , { totals : ProductUsageTotals ; models : Map < string , ProductUsageTotals > } > ( )
40+
41+ onHeader ( ) : void {
42+ // header is intentionally ignored (we rely on parsed TokenUsageRecord fields)
43+ }
44+
45+ accumulate ( record : TokenUsageRecord ) : void {
46+ const product = getFriendlyProductName ( record )
47+ const model = record . model . trim ( ) || 'Unknown'
48+
49+ let productUsage = this . byProduct . get ( product )
50+ if ( ! productUsage ) {
51+ productUsage = {
52+ totals : createTotals ( ) ,
53+ models : new Map ( ) ,
54+ }
55+ this . byProduct . set ( product , productUsage )
56+ }
57+
58+ const { requests, aiuQuantity, grossAmount, aiuGrossAmount } = getUsageMetrics ( record )
59+ productUsage . totals . requests += requests
60+ productUsage . totals . aiuQuantity += aiuQuantity
61+ productUsage . totals . grossAmount += grossAmount
62+ productUsage . totals . aiuGrossAmount += aiuGrossAmount
63+
64+ let modelTotals = productUsage . models . get ( model )
65+ if ( ! modelTotals ) {
66+ modelTotals = createTotals ( )
67+ productUsage . models . set ( model , modelTotals )
68+ }
69+
70+ modelTotals . requests += requests
71+ modelTotals . aiuQuantity += aiuQuantity
72+ modelTotals . grossAmount += grossAmount
73+ modelTotals . aiuGrossAmount += aiuGrossAmount
74+ }
75+
76+ result ( ) : ProductUsageResult {
77+ const products = Array . from ( this . byProduct . entries ( ) )
78+ . map < ProductUsage > ( ( [ product , usage ] ) => ( {
79+ product,
80+ totals : usage . totals ,
81+ models : Object . fromEntries (
82+ Array . from ( usage . models . entries ( ) ) . sort ( ( a , b ) => b [ 1 ] . grossAmount - a [ 1 ] . grossAmount ) ,
83+ ) ,
84+ } ) )
85+ . sort ( ( a , b ) => b . totals . grossAmount - a . totals . grossAmount )
86+
87+ return { products }
88+ }
89+ }
0 commit comments