Skip to content

Commit 538da1b

Browse files
authored
Merge pull request #179 from rajbos/copilot/update-prediction-data-counters
Use rolling 30-day window for annual projections instead of current month
2 parents f3f3c81 + 27c48ac commit 538da1b

2 files changed

Lines changed: 70 additions & 33 deletions

File tree

src/extension.ts

Lines changed: 52 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ interface DetailedStats {
5555
today: PeriodStats;
5656
month: PeriodStats;
5757
lastMonth: PeriodStats;
58+
last30Days: PeriodStats;
5859
lastUpdated: Date;
5960
}
6061

@@ -670,10 +671,13 @@ class CopilotTokenTracker implements vscode.Disposable {
670671
// Calculate last month boundaries
671672
const lastMonthEnd = new Date(now.getFullYear(), now.getMonth(), 0, 23, 59, 59, 999); // Last day of previous month
672673
const lastMonthStart = new Date(lastMonthEnd.getFullYear(), lastMonthEnd.getMonth(), 1);
674+
// Calculate last 30 days boundary
675+
const last30DaysStart = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
673676

674677
const todayStats = { tokens: 0, sessions: 0, interactions: 0, modelUsage: {} as ModelUsage, editorUsage: {} as EditorUsage };
675678
const monthStats = { tokens: 0, sessions: 0, interactions: 0, modelUsage: {} as ModelUsage, editorUsage: {} as EditorUsage };
676679
const lastMonthStats = { tokens: 0, sessions: 0, interactions: 0, modelUsage: {} as ModelUsage, editorUsage: {} as EditorUsage };
680+
const last30DaysStats = { tokens: 0, sessions: 0, interactions: 0, modelUsage: {} as ModelUsage, editorUsage: {} as EditorUsage };
677681

678682
try {
679683
// Clean expired cache entries
@@ -701,14 +705,14 @@ class CopilotTokenTracker implements vscode.Disposable {
701705
// Fast check: Get file stats first to avoid processing old files
702706
const fileStats = fs.statSync(sessionFile);
703707

704-
// Skip files modified before last month (quick filter)
708+
// Skip files modified before last 30 days (quick filter)
705709
// This is the main performance optimization - filters out old sessions without reading file content
706-
if (fileStats.mtime < lastMonthStart) {
710+
if (fileStats.mtime < last30DaysStart) {
707711
skippedFiles++;
708712
continue;
709713
}
710714

711-
// For files within current month, check if data is cached to avoid redundant reads
715+
// For files within last 30 days, check if data is cached to avoid redundant reads
712716
const mtime = fileStats.mtime.getTime();
713717
const fileSize = fileStats.size;
714718
const wasCached = this.isCacheValid(sessionFile, mtime, fileSize);
@@ -732,15 +736,37 @@ class CopilotTokenTracker implements vscode.Disposable {
732736
? new Date(details.lastInteraction)
733737
: new Date(details.modified);
734738

735-
if (lastActivity >= monthStart) {
739+
// Update cache statistics (do this once per file)
740+
if (wasCached) {
741+
cacheHits++;
742+
} else {
743+
cacheMisses++;
744+
}
736745

737-
// Update cache statistics
738-
if (wasCached) {
739-
cacheHits++;
740-
} else {
741-
cacheMisses++;
746+
// Check if activity is within last 30 days
747+
if (lastActivity >= last30DaysStart) {
748+
last30DaysStats.tokens += tokens;
749+
last30DaysStats.sessions += 1;
750+
last30DaysStats.interactions += interactions;
751+
752+
// Add editor usage to last 30 days stats
753+
if (!last30DaysStats.editorUsage[editorType]) {
754+
last30DaysStats.editorUsage[editorType] = { tokens: 0, sessions: 0 };
755+
}
756+
last30DaysStats.editorUsage[editorType].tokens += tokens;
757+
last30DaysStats.editorUsage[editorType].sessions += 1;
758+
759+
// Add model usage to last 30 days stats
760+
for (const [model, usage] of Object.entries(modelUsage)) {
761+
if (!last30DaysStats.modelUsage[model]) {
762+
last30DaysStats.modelUsage[model] = { inputTokens: 0, outputTokens: 0 };
763+
}
764+
last30DaysStats.modelUsage[model].inputTokens += usage.inputTokens;
765+
last30DaysStats.modelUsage[model].outputTokens += usage.outputTokens;
742766
}
767+
}
743768

769+
if (lastActivity >= monthStart) {
744770
monthStats.tokens += tokens;
745771
monthStats.sessions += 1;
746772
monthStats.interactions += interactions;
@@ -785,12 +811,6 @@ class CopilotTokenTracker implements vscode.Disposable {
785811
}
786812
else if (lastActivity >= lastMonthStart && lastActivity <= lastMonthEnd) {
787813
// Session is from last month - only track lastMonth stats
788-
if (wasCached) {
789-
cacheHits++;
790-
} else {
791-
cacheMisses++;
792-
}
793-
794814
lastMonthStats.tokens += tokens;
795815
lastMonthStats.sessions += 1;
796816
lastMonthStats.interactions += interactions;
@@ -812,15 +832,15 @@ class CopilotTokenTracker implements vscode.Disposable {
812832
}
813833
}
814834
else {
815-
// Session is too old (no activity in current or last month), skip it
835+
// Session is too old (no activity in last 30 days), skip it
816836
skippedFiles++;
817837
}
818838
} catch (fileError) {
819839
this.warn(`Error processing session file ${sessionFile}: ${fileError}`);
820840
}
821841
}
822842

823-
this.log(`✅ Analysis complete: Today ${todayStats.sessions} sessions, Month ${monthStats.sessions} sessions, Last Month ${lastMonthStats.sessions} sessions`);
843+
this.log(`✅ Analysis complete: Today ${todayStats.sessions} sessions, Month ${monthStats.sessions} sessions, Last 30 Days ${last30DaysStats.sessions} sessions, Last Month ${lastMonthStats.sessions} sessions`);
824844
if (skippedFiles > 0) {
825845
this.log(`⏭️ Skipped ${skippedFiles} session file(s) (empty or no activity in recent months)`);
826846
}
@@ -833,14 +853,17 @@ class CopilotTokenTracker implements vscode.Disposable {
833853
const todayCo2 = (todayStats.tokens / 1000) * this.co2Per1kTokens;
834854
const monthCo2 = (monthStats.tokens / 1000) * this.co2Per1kTokens;
835855
const lastMonthCo2 = (lastMonthStats.tokens / 1000) * this.co2Per1kTokens;
856+
const last30DaysCo2 = (last30DaysStats.tokens / 1000) * this.co2Per1kTokens;
836857

837858
const todayWater = (todayStats.tokens / 1000) * this.waterUsagePer1kTokens;
838859
const monthWater = (monthStats.tokens / 1000) * this.waterUsagePer1kTokens;
839860
const lastMonthWater = (lastMonthStats.tokens / 1000) * this.waterUsagePer1kTokens;
861+
const last30DaysWater = (last30DaysStats.tokens / 1000) * this.waterUsagePer1kTokens;
840862

841863
const todayCost = this.calculateEstimatedCost(todayStats.modelUsage);
842864
const monthCost = this.calculateEstimatedCost(monthStats.modelUsage);
843865
const lastMonthCost = this.calculateEstimatedCost(lastMonthStats.modelUsage);
866+
const last30DaysCost = this.calculateEstimatedCost(last30DaysStats.modelUsage);
844867

845868
const result: DetailedStats = {
846869
today: {
@@ -879,6 +902,18 @@ class CopilotTokenTracker implements vscode.Disposable {
879902
waterUsage: lastMonthWater,
880903
estimatedCost: lastMonthCost
881904
},
905+
last30Days: {
906+
tokens: last30DaysStats.tokens,
907+
sessions: last30DaysStats.sessions,
908+
avgInteractionsPerSession: last30DaysStats.sessions > 0 ? Math.round(last30DaysStats.interactions / last30DaysStats.sessions) : 0,
909+
avgTokensPerSession: last30DaysStats.sessions > 0 ? Math.round(last30DaysStats.tokens / last30DaysStats.sessions) : 0,
910+
modelUsage: last30DaysStats.modelUsage,
911+
editorUsage: last30DaysStats.editorUsage,
912+
co2: last30DaysCo2,
913+
treesEquivalent: last30DaysCo2 / this.co2AbsorptionPerTreePerYear,
914+
waterUsage: last30DaysWater,
915+
estimatedCost: last30DaysCost
916+
},
882917
lastUpdated: now
883918
};
884919

src/webview/details/main.ts

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ type DetailedStats = {
2727
today: PeriodStats;
2828
month: PeriodStats;
2929
lastMonth: PeriodStats;
30+
last30Days: PeriodStats;
3031
lastUpdated: string | Date;
3132
};
3233

@@ -52,25 +53,23 @@ console.log('[CopilotTokenTracker] details webview loaded');
5253
console.log('[CopilotTokenTracker] window.__INITIAL_DETAILS__:', window.__INITIAL_DETAILS__);
5354
console.log('[CopilotTokenTracker] initialData:', initialData);
5455

55-
function calculateProjection(monthValue: number): number {
56-
const now = new Date();
57-
const day = now.getDate();
58-
const isLeap = (now.getFullYear() % 4 === 0 && now.getFullYear() % 100 !== 0) || now.getFullYear() % 400 === 0;
59-
const daysInYear = isLeap ? 366 : 365;
60-
if (day === 0) { return 0; }
61-
return (monthValue / day) * daysInYear;
56+
function calculateProjection(last30DaysValue: number): number {
57+
// Project annual value based on last 30 days average
58+
// This gives better predictions at the beginning of the month
59+
const daysInYear = 365.25; // Average days per year (accounting for leap year cycle)
60+
return (last30DaysValue / 30) * daysInYear;
6261
}
6362

6463
function render(stats: DetailedStats): void {
6564
const root = document.getElementById('root');
6665
if (!root) { return; }
6766

68-
const projectedTokens = Math.round(calculateProjection(stats.month.tokens));
69-
const projectedSessions = Math.round(calculateProjection(stats.month.sessions));
70-
const projectedCo2 = calculateProjection(stats.month.co2);
71-
const projectedWater = calculateProjection(stats.month.waterUsage);
72-
const projectedCost = calculateProjection(stats.month.estimatedCost);
73-
const projectedTrees = calculateProjection(stats.month.treesEquivalent);
67+
const projectedTokens = Math.round(calculateProjection(stats.last30Days.tokens));
68+
const projectedSessions = Math.round(calculateProjection(stats.last30Days.sessions));
69+
const projectedCo2 = calculateProjection(stats.last30Days.co2);
70+
const projectedWater = calculateProjection(stats.last30Days.waterUsage);
71+
const projectedCost = calculateProjection(stats.last30Days.estimatedCost);
72+
const projectedTrees = calculateProjection(stats.last30Days.treesEquivalent);
7473

7574
renderShell(root, stats, {
7675
projectedTokens,
@@ -307,11 +306,12 @@ function buildEditorUsageSection(stats: DetailedStats): HTMLElement | null {
307306
const todayUsage = stats.today.editorUsage[editor] || { tokens: 0, sessions: 0 };
308307
const monthUsage = stats.month.editorUsage[editor] || { tokens: 0, sessions: 0 };
309308
const lastMonthUsage = stats.lastMonth.editorUsage[editor] || { tokens: 0, sessions: 0 };
309+
const last30DaysUsage = stats.last30Days.editorUsage[editor] || { tokens: 0, sessions: 0 };
310310
const todayPercent = todayTotal > 0 ? (todayUsage.tokens / todayTotal) * 100 : 0;
311311
const monthPercent = monthTotal > 0 ? (monthUsage.tokens / monthTotal) * 100 : 0;
312312
const lastMonthPercent = lastMonthTotal > 0 ? (lastMonthUsage.tokens / lastMonthTotal) * 100 : 0;
313-
const projectedTokens = Math.round(calculateProjection(monthUsage.tokens));
314-
const projectedSessions = Math.round(calculateProjection(monthUsage.sessions));
313+
const projectedTokens = Math.round(calculateProjection(last30DaysUsage.tokens));
314+
const projectedSessions = Math.round(calculateProjection(last30DaysUsage.sessions));
315315

316316
const tr = document.createElement('tr');
317317
const labelTd = document.createElement('td');
@@ -399,10 +399,12 @@ function buildModelUsageSection(stats: DetailedStats): HTMLElement | null {
399399
const todayUsage = stats.today.modelUsage[model] || { inputTokens: 0, outputTokens: 0 };
400400
const monthUsage = stats.month.modelUsage[model] || { inputTokens: 0, outputTokens: 0 };
401401
const lastMonthUsage = stats.lastMonth.modelUsage[model] || { inputTokens: 0, outputTokens: 0 };
402+
const last30DaysUsage = stats.last30Days.modelUsage[model] || { inputTokens: 0, outputTokens: 0 };
402403
const todayTotal = todayUsage.inputTokens + todayUsage.outputTokens;
403404
const monthTotal = monthUsage.inputTokens + monthUsage.outputTokens;
404405
const lastMonthTotal = lastMonthUsage.inputTokens + lastMonthUsage.outputTokens;
405-
const projected = Math.round(calculateProjection(monthTotal));
406+
const last30DaysTotal = last30DaysUsage.inputTokens + last30DaysUsage.outputTokens;
407+
const projected = Math.round(calculateProjection(last30DaysTotal));
406408
const todayInputPct = todayTotal > 0 ? (todayUsage.inputTokens / todayTotal) * 100 : 0;
407409
const todayOutputPct = todayTotal > 0 ? (todayUsage.outputTokens / todayTotal) * 100 : 0;
408410
const monthInputPct = monthTotal > 0 ? (monthUsage.inputTokens / monthTotal) * 100 : 0;

0 commit comments

Comments
 (0)