Skip to content

Commit 8cafdf6

Browse files
Copilotrajbos
andcommitted
Update power user logic to show top 10% instead of fixed threshold
Co-authored-by: rajbos <6085745+rajbos@users.noreply.github.com>
1 parent c5f2f2d commit 8cafdf6

2 files changed

Lines changed: 43 additions & 32 deletions

File tree

src/lib/utils.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ export interface PowerUserSummary {
232232
powerUserModelSummary: ModelUsageSummary[];
233233
}
234234

235-
// Define power user threshold - users with more than 10 requests
235+
// Define power user threshold - users with more than 10 requests (kept for backward compatibility)
236236
export const POWER_USER_THRESHOLD = 10;
237237

238238
export function getPowerUsers(data: CopilotUsageData[]): PowerUserSummary {
@@ -242,11 +242,17 @@ export function getPowerUsers(data: CopilotUsageData[]): PowerUserSummary {
242242
userTotals[item.user] = (userTotals[item.user] || 0) + item.requestsUsed;
243243
});
244244

245-
// Identify power users (users exceeding threshold)
246-
const powerUserNames = Object.keys(userTotals).filter(
247-
user => userTotals[user] > POWER_USER_THRESHOLD
245+
// Get all users sorted by total requests (descending)
246+
const allUsersSorted = Object.keys(userTotals).sort(
247+
(a, b) => userTotals[b] - userTotals[a]
248248
);
249249

250+
// Calculate top 10% of users (at least 1 user if any users exist)
251+
const powerUserCount = Math.max(1, Math.ceil(allUsersSorted.length * 0.1));
252+
253+
// Take the top 10% of users as power users
254+
const powerUserNames = allUsersSorted.slice(0, powerUserCount);
255+
250256
// Filter data to only power users
251257
const powerUserData = data.filter(item => powerUserNames.includes(item.user));
252258

src/test/power-users.test.ts

Lines changed: 33 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -56,20 +56,20 @@ describe('Power Users Functionality', () => {
5656
it('should identify power users correctly', () => {
5757
const result = getPowerUsers(mockData);
5858

59-
expect(result.totalPowerUsers).toBe(2);
60-
expect(result.powerUsers).toHaveLength(2);
59+
// With 4 users total, top 10% = Math.ceil(4 * 0.1) = 1 user
60+
expect(result.totalPowerUsers).toBe(1);
61+
expect(result.powerUsers).toHaveLength(1);
6162

62-
// Should be sorted by total requests (descending)
63+
// Should be sorted by total requests (descending) - only the top user
6364
expect(result.powerUsers[0].user).toBe('power-user-1');
6465
expect(result.powerUsers[0].totalRequests).toBe(35); // 15 + 8 + 12
65-
expect(result.powerUsers[1].user).toBe('power-user-2');
66-
expect(result.powerUsers[1].totalRequests).toBe(20);
6766
});
6867

6968
it('should calculate total power user requests correctly', () => {
7069
const result = getPowerUsers(mockData);
7170

72-
expect(result.totalPowerUserRequests).toBe(55); // 35 + 20
71+
// Only power-user-1 is a power user now
72+
expect(result.totalPowerUserRequests).toBe(35); // Only power-user-1
7373
});
7474

7575
it('should aggregate requests by model for power users', () => {
@@ -81,10 +81,9 @@ describe('Power Users Functionality', () => {
8181
'gpt-3.5': 8
8282
});
8383

84+
// power-user-2 is no longer a power user (only top 10% = 1 user)
8485
const powerUser2 = result.powerUsers.find(u => u.user === 'power-user-2');
85-
expect(powerUser2?.requestsByModel).toEqual({
86-
'claude': 20
87-
});
86+
expect(powerUser2).toBeUndefined();
8887
});
8988

9089
it('should calculate daily activity for power users', () => {
@@ -100,19 +99,21 @@ describe('Power Users Functionality', () => {
10099
it('should create model usage summary for power users only', () => {
101100
const result = getPowerUsers(mockData);
102101

103-
expect(result.powerUserModelSummary).toHaveLength(3);
102+
// Only power-user-1 is a power user, so only models they used should be included
103+
expect(result.powerUserModelSummary).toHaveLength(2);
104104

105-
// Should include models used by power users
105+
// Should include models used by power users (only power-user-1)
106106
const gpt4Summary = result.powerUserModelSummary.find(m => m.model === 'gpt-4');
107107
expect(gpt4Summary?.totalRequests).toBe(27);
108108
expect(gpt4Summary?.compliantRequests).toBe(15);
109109
expect(gpt4Summary?.exceedingRequests).toBe(12);
110110

111-
const claudeSummary = result.powerUserModelSummary.find(m => m.model === 'claude');
112-
expect(claudeSummary?.totalRequests).toBe(20);
113-
114111
const gpt35Summary = result.powerUserModelSummary.find(m => m.model === 'gpt-3.5');
115112
expect(gpt35Summary?.totalRequests).toBe(8);
113+
114+
// claude should not be included since power-user-2 is not a power user anymore
115+
const claudeSummary = result.powerUserModelSummary.find(m => m.model === 'claude');
116+
expect(claudeSummary).toBeUndefined();
116117
});
117118

118119
it('should handle case with no power users', () => {
@@ -137,38 +138,42 @@ describe('Power Users Functionality', () => {
137138

138139
const result = getPowerUsers(lowUsageData);
139140

140-
expect(result.totalPowerUsers).toBe(0);
141-
expect(result.powerUsers).toHaveLength(0);
142-
expect(result.totalPowerUserRequests).toBe(0);
143-
expect(result.powerUserModelSummary).toHaveLength(0);
141+
// With 2 users, top 10% = Math.ceil(2 * 0.1) = 1 user, so user-1 will be the power user
142+
expect(result.totalPowerUsers).toBe(1);
143+
expect(result.powerUsers).toHaveLength(1);
144+
expect(result.powerUsers[0].user).toBe('user-1'); // user with highest requests (5)
145+
expect(result.totalPowerUserRequests).toBe(5);
146+
expect(result.powerUserModelSummary).toHaveLength(1);
144147
});
145148

146149
it('should generate daily data aggregated across all power users', () => {
147150
const result = getPowerUsers(mockData);
148151
const dailyData = getPowerUserDailyData(result.powerUsers);
149152

153+
// Only power-user-1 is a power user now
150154
expect(dailyData).toEqual([
151-
{ date: '2025-01-01', requests: 43 }, // power-user-1: 23 + power-user-2: 20
152-
{ date: '2025-01-02', requests: 12 } // power-user-1: 12
155+
{ date: '2025-01-01', requests: 23 }, // power-user-1 only: 23
156+
{ date: '2025-01-02', requests: 12 } // power-user-1 only: 12
153157
]);
154158
});
155159

156-
it('should use correct power user threshold', () => {
157-
expect(POWER_USER_THRESHOLD).toBe(10);
160+
it('should use top 10% logic for power users', () => {
161+
expect(POWER_USER_THRESHOLD).toBe(10); // Still kept for backward compatibility
158162

159-
// Test with user exactly at threshold
160-
const thresholdData: CopilotUsageData[] = [
163+
// Test with single user - should always have 1 power user
164+
const singleUserData: CopilotUsageData[] = [
161165
{
162166
timestamp: new Date('2025-01-01T10:00:00Z'),
163-
user: 'threshold-user',
167+
user: 'only-user',
164168
model: 'gpt-4',
165-
requestsUsed: 10,
169+
requestsUsed: 1,
166170
exceedsQuota: false,
167171
totalMonthlyQuota: '100'
168172
}
169173
];
170174

171-
const result = getPowerUsers(thresholdData);
172-
expect(result.totalPowerUsers).toBe(0); // Should not be a power user (needs > threshold)
175+
const result = getPowerUsers(singleUserData);
176+
expect(result.totalPowerUsers).toBe(1); // Should have 1 power user (top 10% of 1 user = 1)
177+
expect(result.powerUsers[0].user).toBe('only-user');
173178
});
174179
});

0 commit comments

Comments
 (0)