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