@@ -70,11 +70,7 @@ export class UsageAggregator {
7070 * @param query Statistics query
7171 * @param options Additional options (e.g. recordingPaused)
7272 */
73- query (
74- events : UsageEventV1 [ ] ,
75- query : StatsQuery ,
76- options : { recordingPaused ?: boolean } = { } ,
77- ) : StatsSnapshot {
73+ query ( events : UsageEventV1 [ ] , query : StatsQuery , options : { recordingPaused ?: boolean } = { } ) : StatsSnapshot {
7874 // 1. Time range filtering
7975 const { from, to } = this . resolveTimeRange ( query )
8076 const filtered = events . filter ( ( event ) => {
@@ -86,9 +82,7 @@ export class UsageAggregator {
8682
8783 // 2. Cancelled event filtering
8884 const includeCancelled = query . includeCancelled ?? false
89- const visibleEvents = includeCancelled
90- ? filtered
91- : filtered . filter ( ( e ) => e . status !== "cancelled" )
85+ const visibleEvents = includeCancelled ? filtered : filtered . filter ( ( e ) => e . status !== "cancelled" )
9286
9387 // 3. Compute bucket keys based on timezone
9488 const aggregatable : AggregatableEvent [ ] = visibleEvents . map ( ( event ) => {
@@ -341,10 +335,7 @@ export class UsageAggregator {
341335 * Returns the bucket key combinations for the groupBy axes from the event.
342336 * Up to 3 axes can be combined.
343337 */
344- private getGroupKeys (
345- item : AggregatableEvent ,
346- groupBy : StatsQuery [ "groupBy" ] ,
347- ) : Record < string , string > [ ] {
338+ private getGroupKeys ( item : AggregatableEvent , groupBy : StatsQuery [ "groupBy" ] ) : Record < string , string > [ ] {
348339 if ( groupBy . length === 0 ) {
349340 return [ { } ]
350341 }
@@ -397,33 +388,33 @@ export class UsageAggregator {
397388 case "status" :
398389 return [ event . status ]
399390 case "source" : {
400- // Separate by the source of costUsd.
401- // Feature 1: If the event has no costUsd but the cost can be
402- // computed on-the-fly from model pricing, treat the source as
403- // "estimated" (since it is derived, not provider-reported).
404- const sources = new Set < string > ( )
405- if ( event . usage . costUsd ) {
406- sources . add ( event . usage . costUsd . source )
407- } else {
408- // Check if cost can be computed; if so, mark as "estimated".
409- // Otherwise the source remains "unknown".
410- const computedCost = computeEventCost ( event )
411- if ( computedCost > 0 ) {
412- sources . add ( "estimated" )
413- }
391+ // Separate by the source of costUsd.
392+ // Feature 1: If the event has no costUsd but the cost can be
393+ // computed on-the-fly from model pricing, treat the source as
394+ // "estimated" (since it is derived, not provider-reported).
395+ const sources = new Set < string > ( )
396+ if ( event . usage . costUsd ) {
397+ sources . add ( event . usage . costUsd . source )
398+ } else {
399+ // Check if cost can be computed; if so, mark as "estimated".
400+ // Otherwise the source remains "unknown".
401+ const computedCost = computeEventCost ( event )
402+ if ( computedCost > 0 ) {
403+ sources . add ( "estimated" )
414404 }
415- // Also consider the source of input/output tokens
416- if ( event . usage . inputTokens ) {
417- sources . add ( event . usage . inputTokens . source )
418- }
419- if ( event . usage . outputTokens ) {
420- sources . add ( event . usage . outputTokens . source )
421- }
422- if ( sources . size === 0 ) {
423- sources . add ( "unknown" )
424- }
425- return Array . from ( sources )
426405 }
406+ // Also consider the source of input/output tokens
407+ if ( event . usage . inputTokens ) {
408+ sources . add ( event . usage . inputTokens . source )
409+ }
410+ if ( event . usage . outputTokens ) {
411+ sources . add ( event . usage . outputTokens . source )
412+ }
413+ if ( sources . size === 0 ) {
414+ sources . add ( "unknown" )
415+ }
416+ return Array . from ( sources )
417+ }
427418 default :
428419 return [ ]
429420 }
@@ -510,7 +501,9 @@ export class UsageAggregator {
510501 bucket . reasoningTokens += reasoningTokens
511502 }
512503
513- bucket . totalTokens += totalTokens
504+ // Recompute from input + output (provider-neutral) to repair historical events
505+ // that may have been persisted with the old double-counted sum.
506+ bucket . totalTokens += inputTokens + outputTokens
514507 bucket . costUsd += costUsd
515508 }
516509
@@ -566,9 +559,7 @@ export class UsageAggregator {
566559 ) : StatsSnapshot [ "coverage" ] {
567560 const times = visibleEvents . map ( ( e ) => new Date ( e . event . occurredAt ) . getTime ( ) ) . sort ( ( a , b ) => a - b )
568561
569- const backfilledEventCount = visibleEvents . filter (
570- ( e ) => e . event . provenance === "history-backfill" ,
571- ) . length
562+ const backfilledEventCount = visibleEvents . filter ( ( e ) => e . event . provenance === "history-backfill" ) . length
572563
573564 return {
574565 firstEventAt : times . length > 0 ? new Date ( times [ 0 ] ) . toISOString ( ) : undefined ,
0 commit comments