@@ -902,7 +902,9 @@ export function getExpectedExcessCost(data: CopilotUsageData[], plan: string = C
902902 if ( ! lastDate ) return 0 ;
903903
904904 const lastDateObj = new Date ( lastDate ) ;
905+ const daysElapsed = lastDateObj . getDate ( ) ;
905906 const totalDaysInMonth = new Date ( lastDateObj . getFullYear ( ) , lastDateObj . getMonth ( ) + 1 , 0 ) . getDate ( ) ;
907+ const isPartialMonth = daysElapsed < totalDaysInMonth ;
906908
907909 // Group all items by user
908910 const userDataMap : Record < string , CopilotUsageData [ ] > = { } ;
@@ -913,6 +915,67 @@ export function getExpectedExcessCost(data: CopilotUsageData[], plan: string = C
913915
914916 let totalExpectedCost = 0 ;
915917
918+ if ( isPartialMonth ) {
919+ const remainingDaysInMonth = totalDaysInMonth - daysElapsed ;
920+
921+ Object . values ( userDataMap ) . forEach ( userItems => {
922+ if ( ! userItems . length ) return ;
923+
924+ // Group by date (YYYY-MM-DD), sorted ascending.
925+ // We keep the same "exclude latest usage day" averaging pattern to avoid partial-day skew.
926+ const byDate : Record < string , CopilotUsageData [ ] > = { } ;
927+ userItems . forEach ( item => {
928+ const date = item . timestamp . toISOString ( ) . split ( 'T' ) [ 0 ] ;
929+ if ( ! byDate [ date ] ) byDate [ date ] = [ ] ;
930+ byDate [ date ] . push ( item ) ;
931+ } ) ;
932+ const sortedDates = Object . keys ( byDate ) . sort ( ) ;
933+
934+ let datesForAverage = sortedDates . slice ( 0 , - 1 ) ;
935+ if ( datesForAverage . length === 0 ) {
936+ datesForAverage = sortedDates ;
937+ }
938+ if ( datesForAverage . length === 0 ) return ;
939+
940+ const historicalModelTotals : Record < string , number > = { } ;
941+ datesForAverage . forEach ( date => {
942+ byDate [ date ] . forEach ( item => {
943+ historicalModelTotals [ item . model ] = ( historicalModelTotals [ item . model ] || 0 ) + item . requestsUsed ;
944+ } ) ;
945+ } ) ;
946+
947+ const currentModelTotals : Record < string , number > = { } ;
948+ userItems . forEach ( item => {
949+ currentModelTotals [ item . model ] = ( currentModelTotals [ item . model ] || 0 ) + item . requestsUsed ;
950+ } ) ;
951+
952+ const projectedModelTotals : Record < string , number > = { } ;
953+ const averagingDays = datesForAverage . length ;
954+ Object . entries ( currentModelTotals ) . forEach ( ( [ model , currentTotal ] ) => {
955+ const historicalTotal = historicalModelTotals [ model ] || 0 ;
956+ const dailyAverage = historicalTotal / averagingDays ;
957+ projectedModelTotals [ model ] = currentTotal + dailyAverage * remainingDaysInMonth ;
958+ } ) ;
959+
960+ const projectedMonthlyTotal = Object . values ( projectedModelTotals ) . reduce ( ( sum , value ) => sum + value , 0 ) ;
961+ const projectedExcess = projectedMonthlyTotal - planLimit ;
962+ if ( projectedExcess <= 0 || projectedMonthlyTotal <= 0 ) return ;
963+
964+ // Allocate only the projected amount above the free plan quota across models,
965+ // then apply each model multiplier to compute cost.
966+ Object . entries ( projectedModelTotals ) . forEach ( ( [ model , projectedTotalForModel ] ) => {
967+ const multiplier = getModelMultiplier ( model ) ;
968+ if ( multiplier === 0 ) return ;
969+
970+ const modelShare = projectedTotalForModel / projectedMonthlyTotal ;
971+ const projectedExcessForModel = projectedExcess * modelShare ;
972+ totalExpectedCost += projectedExcessForModel * multiplier * EXCESS_REQUEST_COST ;
973+ } ) ;
974+ } ) ;
975+
976+ return totalExpectedCost ;
977+ }
978+
916979 Object . values ( userDataMap ) . forEach ( userItems => {
917980 // Only process users who have reached the plan limit
918981 const totalRequests = userItems . reduce ( ( sum , item ) => sum + item . requestsUsed , 0 ) ;
0 commit comments