@@ -13,6 +13,7 @@ import {
1313 computeHeatmapSnapshot ,
1414 applyEventToProjection ,
1515 computeDayBucket ,
16+ sumBucketsToTotals ,
1617 StatsProjError ,
1718} from "../UsageStatsProjection"
1819import { UsageAggregator , computeEventContribution , serializeBucketKey } from "../UsageAggregator"
@@ -1034,4 +1035,180 @@ describe("UsageStatsProjection", () => {
10341035 expect ( fastSnapshot . totals . costUsd ) . toBeCloseTo ( eventScanSnapshot . totals . costUsd , 10 )
10351036 } )
10361037 } )
1038+
1039+ // ── sumBucketsToTotals ─────────────────────────────────────────────────
1040+
1041+ describe ( "sumBucketsToTotals" , ( ) => {
1042+ it ( "should return a zeroed bucket for empty buckets array" , ( ) => {
1043+ const totals = sumBucketsToTotals ( [ ] )
1044+ expect ( totals . events ) . toBe ( 0 )
1045+ expect ( totals . completedCalls ) . toBe ( 0 )
1046+ expect ( totals . failedCalls ) . toBe ( 0 )
1047+ expect ( totals . cancelledCalls ) . toBe ( 0 )
1048+ expect ( totals . inputTokens ) . toBe ( 0 )
1049+ expect ( totals . outputTokens ) . toBe ( 0 )
1050+ expect ( totals . cacheReadTokens ) . toBe ( 0 )
1051+ expect ( totals . cacheWriteTokens ) . toBe ( 0 )
1052+ expect ( totals . reasoningTokens ) . toBe ( 0 )
1053+ expect ( totals . totalTokens ) . toBe ( 0 )
1054+ expect ( totals . costUsd ) . toBe ( 0 )
1055+ expect ( totals . unknownEventCount ) . toBe ( 0 )
1056+ expect ( totals . key ) . toEqual ( { } )
1057+ } )
1058+
1059+ it ( "should accurately aggregate all metrics across multiple buckets" , ( ) => {
1060+ const b1 = {
1061+ key : { model : "model-a" } ,
1062+ events : 2 ,
1063+ completedCalls : 2 ,
1064+ failedCalls : 0 ,
1065+ cancelledCalls : 0 ,
1066+ inputTokens : 1000 ,
1067+ outputTokens : 200 ,
1068+ cacheReadTokens : 100 ,
1069+ cacheWriteTokens : 50 ,
1070+ reasoningTokens : 25 ,
1071+ totalTokens : 1200 ,
1072+ costUsd : 0.05 ,
1073+ unknownEventCount : 1 ,
1074+ }
1075+ const b2 = {
1076+ key : { model : "model-b" } ,
1077+ events : 3 ,
1078+ completedCalls : 1 ,
1079+ failedCalls : 1 ,
1080+ cancelledCalls : 1 ,
1081+ inputTokens : 2000 ,
1082+ outputTokens : 400 ,
1083+ cacheReadTokens : 300 ,
1084+ cacheWriteTokens : 100 ,
1085+ reasoningTokens : 50 ,
1086+ totalTokens : 2400 ,
1087+ costUsd : 0.1 ,
1088+ unknownEventCount : 0 ,
1089+ }
1090+
1091+ const totals = sumBucketsToTotals ( [ b1 , b2 ] )
1092+
1093+ expect ( totals . events ) . toBe ( 5 )
1094+ expect ( totals . completedCalls ) . toBe ( 3 )
1095+ expect ( totals . failedCalls ) . toBe ( 1 )
1096+ expect ( totals . cancelledCalls ) . toBe ( 1 )
1097+ expect ( totals . inputTokens ) . toBe ( 3000 )
1098+ expect ( totals . outputTokens ) . toBe ( 600 )
1099+ expect ( totals . cacheReadTokens ) . toBe ( 400 )
1100+ expect ( totals . cacheWriteTokens ) . toBe ( 150 )
1101+ expect ( totals . reasoningTokens ) . toBe ( 75 )
1102+ expect ( totals . totalTokens ) . toBe ( 3600 )
1103+ expect ( totals . costUsd ) . toBeCloseTo ( 0.15 , 10 )
1104+ expect ( totals . unknownEventCount ) . toBe ( 1 )
1105+ expect ( totals . key ) . toEqual ( { } )
1106+ } )
1107+ } )
1108+
1109+ describe ( "rollup totals synthesis (sumBucketsToTotals)" , ( ) => {
1110+ it ( "maintains sum(buckets.costUsd) === totals.costUsd when mixing priced and unpriced models" , ( ) => {
1111+ // Event 1: Model with custom pricing
1112+ const evtPriced = makeEvent ( {
1113+ eventId : "evt-priced" ,
1114+ idempotencyKey : "idem-priced" ,
1115+ provider : "openai" ,
1116+ model : "priced-custom-model" ,
1117+ usage : {
1118+ inputTokens : { value : 10000 , source : "provider" } ,
1119+ outputTokens : { value : 500 , source : "provider" } ,
1120+ costUsd : { value : 0.05 , source : "provider" } ,
1121+ } ,
1122+ } )
1123+
1124+ // Event 2: Model without pricing (0 cost, missing from custom pricing map)
1125+ const evtUnpriced = makeEvent ( {
1126+ eventId : "evt-unpriced" ,
1127+ idempotencyKey : "idem-unpriced" ,
1128+ provider : "openai" ,
1129+ model : "unpriced-custom-model" ,
1130+ usage : {
1131+ inputTokens : { value : 50000 , source : "provider" } ,
1132+ outputTokens : { value : 200 , source : "provider" } ,
1133+ costUsd : { value : 0 , source : "provider" } ,
1134+ } ,
1135+ } )
1136+
1137+ db . append ( evtPriced )
1138+ db . append ( evtUnpriced )
1139+
1140+ // Custom pricing ONLY provided for the first model
1141+ const customPricing = new Map ( [ [ "openai|priced-custom-model" , { inputPrice : 2.0 , cacheReadsPrice : 0.5 } ] ] )
1142+
1143+ const snapshot = assembleRollupSnapshot ( db , makeQuery ( { groupBy : [ "model" ] , cacheRatio : 0.94 } ) , {
1144+ customPricing,
1145+ } )
1146+
1147+ // Invariant: sum of bucket costUsd must equal totals.costUsd exactly
1148+ const bucketCostSum = snapshot . buckets . reduce ( ( acc , b ) => acc + b . costUsd , 0 )
1149+ expect ( snapshot . totals . costUsd ) . toBeCloseTo ( bucketCostSum , 10 )
1150+
1151+ // The priced model should receive its discount:
1152+ // discountBase = (10000 / 1M) * (2.0 - 0.5) = 0.015
1153+ // cost = 0.05 - 0.94 * 0.015 = 0.0359
1154+ const pricedBucket = snapshot . buckets . find ( ( b ) => b . key . model === "priced-custom-model" )
1155+ expect ( pricedBucket ) . toBeDefined ( )
1156+ expect ( pricedBucket ! . costUsd ) . toBeCloseTo ( 0.0359 , 10 )
1157+
1158+ // The unpriced model has 0 cost
1159+ const unpricedBucket = snapshot . buckets . find ( ( b ) => b . key . model === "unpriced-custom-model" )
1160+ expect ( unpricedBucket ) . toBeDefined ( )
1161+ expect ( unpricedBucket ! . costUsd ) . toBe ( 0 )
1162+
1163+ // Totals cost must NOT be offset to 0 by any global calculation
1164+ expect ( snapshot . totals . costUsd ) . toBeCloseTo ( 0.0359 , 10 )
1165+ expect ( snapshot . totals . costUsd ) . toBeGreaterThan ( 0 )
1166+ } )
1167+
1168+ it ( "maintains sum(buckets.costUsd) === totals.costUsd for other single-axis groupBys (provider, day)" , ( ) => {
1169+ const evt1 = makeEvent ( {
1170+ eventId : "evt-p1" ,
1171+ idempotencyKey : "idem-p1" ,
1172+ occurredAt : "2026-07-19T10:00:00.000Z" ,
1173+ provider : "openai" ,
1174+ model : "model-1" ,
1175+ usage : {
1176+ inputTokens : { value : 10000 , source : "provider" } ,
1177+ outputTokens : { value : 500 , source : "provider" } ,
1178+ costUsd : { value : 0.03 , source : "provider" } ,
1179+ } ,
1180+ } )
1181+ const evt2 = makeEvent ( {
1182+ eventId : "evt-p2" ,
1183+ idempotencyKey : "idem-p2" ,
1184+ occurredAt : "2026-07-20T10:00:00.000Z" ,
1185+ provider : "anthropic" ,
1186+ model : "claude-sonnet-4-20250514" ,
1187+ usage : {
1188+ inputTokens : { value : 20000 , source : "provider" } ,
1189+ outputTokens : { value : 1000 , source : "provider" } ,
1190+ costUsd : { value : 0.06 , source : "provider" } ,
1191+ } ,
1192+ } )
1193+
1194+ db . append ( evt1 )
1195+ db . append ( evt2 )
1196+
1197+ const customPricing = new Map ( [ [ "openai|model-1" , { inputPrice : 2.0 , cacheReadsPrice : 0.5 } ] ] )
1198+
1199+ // Test provider axis
1200+ const providerSnapshot = assembleRollupSnapshot ( db , makeQuery ( { groupBy : [ "provider" ] , cacheRatio : 0.5 } ) , {
1201+ customPricing,
1202+ } )
1203+ const providerCostSum = providerSnapshot . buckets . reduce ( ( acc , b ) => acc + b . costUsd , 0 )
1204+ expect ( providerSnapshot . totals . costUsd ) . toBeCloseTo ( providerCostSum , 10 )
1205+
1206+ // Test day axis
1207+ const daySnapshot = assembleRollupSnapshot ( db , makeQuery ( { groupBy : [ "day" ] , cacheRatio : 0.5 } ) , {
1208+ customPricing,
1209+ } )
1210+ const dayCostSum = daySnapshot . buckets . reduce ( ( acc , b ) => acc + b . costUsd , 0 )
1211+ expect ( daySnapshot . totals . costUsd ) . toBeCloseTo ( dayCostSum , 10 )
1212+ } )
1213+ } )
10371214} )
0 commit comments