Skip to content

Commit 9f31ebc

Browse files
fix(ccusage): sort session reports by cost instead of last activity (#907)
Closes #903 Co-authored-by: ryoppippi <1560508+ryoppippi@users.noreply.github.com>
1 parent 31611ce commit 9f31ebc

1 file changed

Lines changed: 31 additions & 18 deletions

File tree

apps/ccusage/src/data-loader.ts

Lines changed: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { createInterface } from 'node:readline';
2020
import { toArray } from '@antfu/utils';
2121
import { Result } from '@praha/byethrow';
2222
import { uniq } from 'es-toolkit';
23+
import { sort } from 'fast-sort';
2324
import { createFixture } from 'fs-fixture';
2425
import { isDirectorySync } from 'path-type';
2526
import { glob } from 'tinyglobby';
@@ -901,7 +902,7 @@ export async function loadDailyUsageData(options?: LoadOptions): Promise<DailyUs
901902
* Loads and aggregates Claude usage data by session
902903
* Groups usage data by project path and session ID based on file structure
903904
* @param options - Optional configuration for loading and filtering data
904-
* @returns Array of session usage summaries sorted by last activity
905+
* @returns Array of session usage summaries sorted by cost (highest first)
905906
*/
906907
export async function loadSessionData(options?: LoadOptions): Promise<SessionUsage[]> {
907908
// Get all Claude paths or use the specific one from options
@@ -1072,7 +1073,17 @@ export async function loadSessionData(options?: LoadOptions): Promise<SessionUsa
10721073
options?.project,
10731074
);
10741075

1075-
return sortByDate(sessionFiltered, (item) => item.lastActivity, options?.order);
1076+
// Sort sessions by cost (highest first by default), as documented
1077+
const sorted = sort(sessionFiltered);
1078+
const order = options?.order ?? 'desc';
1079+
switch (order) {
1080+
case 'asc':
1081+
return sorted.asc((item) => item.totalCost);
1082+
case 'desc':
1083+
return sorted.desc((item) => item.totalCost);
1084+
default:
1085+
unreachable(order);
1086+
}
10761087
}
10771088

10781089
/**
@@ -2801,14 +2812,14 @@ invalid json line
28012812
expect(session?.versions).toEqual(['1.0.0', '1.1.0']); // Sorted and unique
28022813
});
28032814

2804-
it('sorts by last activity descending', async () => {
2815+
it('sorts by cost descending by default', async () => {
28052816
const sessions = [
28062817
{
28072818
sessionId: 'session1',
28082819
data: {
28092820
timestamp: createISOTimestamp('2024-01-15T12:00:00Z'),
28102821
message: { usage: { input_tokens: 100, output_tokens: 50 } },
2811-
costUSD: 0.01,
2822+
costUSD: 0.05,
28122823
},
28132824
},
28142825
{
@@ -2824,7 +2835,7 @@ invalid json line
28242835
data: {
28252836
timestamp: createISOTimestamp('2024-01-31T12:00:00Z'),
28262837
message: { usage: { input_tokens: 100, output_tokens: 50 } },
2827-
costUSD: 0.01,
2838+
costUSD: 0.1,
28282839
},
28292840
},
28302841
];
@@ -2837,21 +2848,21 @@ invalid json line
28372848
},
28382849
});
28392850

2840-
const result = await loadSessionData({ claudePath: fixture.path });
2851+
const result = await loadSessionData({ claudePath: fixture.path, mode: 'display' });
28412852

2842-
expect(result[0]?.sessionId).toBe('session3');
2853+
expect(result[0]?.sessionId).toBe('session3'); // highest cost
28432854
expect(result[1]?.sessionId).toBe('session1');
2844-
expect(result[2]?.sessionId).toBe('session2');
2855+
expect(result[2]?.sessionId).toBe('session2'); // lowest cost
28452856
});
28462857

2847-
it("sorts by last activity ascending when order is 'asc'", async () => {
2858+
it("sorts by cost ascending when order is 'asc'", async () => {
28482859
const sessions = [
28492860
{
28502861
sessionId: 'session1',
28512862
data: {
28522863
timestamp: createISOTimestamp('2024-01-15T12:00:00Z'),
28532864
message: { usage: { input_tokens: 100, output_tokens: 50 } },
2854-
costUSD: 0.01,
2865+
costUSD: 0.05,
28552866
},
28562867
},
28572868
{
@@ -2867,7 +2878,7 @@ invalid json line
28672878
data: {
28682879
timestamp: createISOTimestamp('2024-01-31T12:00:00Z'),
28692880
message: { usage: { input_tokens: 100, output_tokens: 50 } },
2870-
costUSD: 0.01,
2881+
costUSD: 0.1,
28712882
},
28722883
},
28732884
];
@@ -2883,21 +2894,22 @@ invalid json line
28832894
const result = await loadSessionData({
28842895
claudePath: fixture.path,
28852896
order: 'asc',
2897+
mode: 'display',
28862898
});
28872899

2888-
expect(result[0]?.sessionId).toBe('session2'); // oldest first
2900+
expect(result[0]?.sessionId).toBe('session2'); // lowest cost first
28892901
expect(result[1]?.sessionId).toBe('session1');
2890-
expect(result[2]?.sessionId).toBe('session3'); // newest last
2902+
expect(result[2]?.sessionId).toBe('session3'); // highest cost last
28912903
});
28922904

2893-
it("sorts by last activity descending when order is 'desc'", async () => {
2905+
it("sorts by cost descending when order is 'desc'", async () => {
28942906
const sessions = [
28952907
{
28962908
sessionId: 'session1',
28972909
data: {
28982910
timestamp: createISOTimestamp('2024-01-15T12:00:00Z'),
28992911
message: { usage: { input_tokens: 100, output_tokens: 50 } },
2900-
costUSD: 0.01,
2912+
costUSD: 0.05,
29012913
},
29022914
},
29032915
{
@@ -2913,7 +2925,7 @@ invalid json line
29132925
data: {
29142926
timestamp: createISOTimestamp('2024-01-31T12:00:00Z'),
29152927
message: { usage: { input_tokens: 100, output_tokens: 50 } },
2916-
costUSD: 0.01,
2928+
costUSD: 0.1,
29172929
},
29182930
},
29192931
];
@@ -2929,11 +2941,12 @@ invalid json line
29292941
const result = await loadSessionData({
29302942
claudePath: fixture.path,
29312943
order: 'desc',
2944+
mode: 'display',
29322945
});
29332946

2934-
expect(result[0]?.sessionId).toBe('session3'); // newest first (same as default)
2947+
expect(result[0]?.sessionId).toBe('session3'); // highest cost (same as default)
29352948
expect(result[1]?.sessionId).toBe('session1');
2936-
expect(result[2]?.sessionId).toBe('session2'); // oldest last
2949+
expect(result[2]?.sessionId).toBe('session2'); // lowest cost
29372950
});
29382951

29392952
it('filters by date range based on last activity', async () => {

0 commit comments

Comments
 (0)