Skip to content

Commit 3a55a46

Browse files
committed
Update sparkService.ts
1 parent 4b8a04f commit 3a55a46

1 file changed

Lines changed: 97 additions & 28 deletions

File tree

src/spark/sparkService.ts

Lines changed: 97 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -34,53 +34,122 @@ export interface SparkTopUser {
3434
totalTokens: number;
3535
}
3636

37+
/** Per-key usage within one hourly or daily bucket (for ranking teams/keys in that window). */
38+
export interface SparkBucketKeyContribution {
39+
keyId: number;
40+
/** Spark key description (typically `"{teamName} ({scope})"`). */
41+
description: string;
42+
count: number;
43+
totalTokens: number;
44+
}
45+
46+
export interface SparkAggregatedHourlyBlock {
47+
hour: string;
48+
count: number;
49+
totalTokens: number;
50+
/** Highest-usage keys in this hour (by `totalTokens`, then `count`). */
51+
topKeys: SparkBucketKeyContribution[];
52+
}
53+
54+
export interface SparkAggregatedDailyBlock {
55+
date: string;
56+
count: number;
57+
totalTokens: number;
58+
/** Highest-usage keys on this day (by `totalTokens`, then `count`). */
59+
topKeys: SparkBucketKeyContribution[];
60+
}
61+
3762
/** Combined stats for every Spark key in a course offering (same buckets as single-key stats). */
3863
export interface SparkAggregatedKeysStats {
3964
keyIds: number[];
4065
totalRequests: number;
4166
totalTokens: number;
4267
lastUsedAt: string | null;
43-
hourly: { hour: string; count: number; totalTokens: number }[];
44-
daily: { date: string; count: number; totalTokens: number }[];
68+
hourly: SparkAggregatedHourlyBlock[];
69+
daily: SparkAggregatedDailyBlock[];
4570
topUsers: SparkTopUser[];
4671
}
4772

4873
type SparkStatsResponse = SparkStats & { topUsers?: SparkTopUser[] };
4974

50-
const mergeHourlySeries = (
51-
series: { hour: string; count: number; totalTokens: number }[][],
52-
): { hour: string; count: number; totalTokens: number }[] => {
53-
const combined = new Map<string, { count: number; totalTokens: number }>();
54-
for (const arr of series) {
55-
for (const row of arr) {
56-
const prev = combined.get(row.hour) ?? { count: 0, totalTokens: 0 };
57-
combined.set(row.hour, {
58-
count: prev.count + row.count,
59-
totalTokens: prev.totalTokens + row.totalTokens,
75+
const TOP_KEYS_PER_BUCKET = 10;
76+
77+
const buildHourlyBlocksWithTopKeys = (
78+
keys: SparkKey[],
79+
perKey: SparkStatsResponse[],
80+
): SparkAggregatedHourlyBlock[] => {
81+
const byHour = new Map<string, Map<number, SparkBucketKeyContribution>>();
82+
for (let i = 0; i < keys.length; i++) {
83+
const sparkKey = keys[i];
84+
for (const row of perKey[i].hourly) {
85+
let m = byHour.get(row.hour);
86+
if (!m) {
87+
m = new Map();
88+
byHour.set(row.hour, m);
89+
}
90+
m.set(sparkKey.id, {
91+
keyId: sparkKey.id,
92+
description: sparkKey.description,
93+
count: row.count,
94+
totalTokens: row.totalTokens,
6095
});
6196
}
6297
}
63-
return [...combined.entries()]
98+
return [...byHour.entries()]
6499
.sort(([a], [b]) => a.localeCompare(b))
65-
.map(([hour, v]) => ({ hour, ...v }));
100+
.map(([hour, keyMap]) => {
101+
const contributions = [...keyMap.values()];
102+
const count = contributions.reduce((s, c) => s + c.count, 0);
103+
const totalTokens = contributions.reduce((s, c) => s + c.totalTokens, 0);
104+
const topKeys = [...contributions]
105+
.sort(
106+
(a, b) =>
107+
b.totalTokens - a.totalTokens ||
108+
b.count - a.count ||
109+
a.keyId - b.keyId,
110+
)
111+
.slice(0, TOP_KEYS_PER_BUCKET);
112+
return { hour, count, totalTokens, topKeys };
113+
});
66114
};
67115

68-
const mergeDailySeries = (
69-
series: { date: string; count: number; totalTokens: number }[][],
70-
): { date: string; count: number; totalTokens: number }[] => {
71-
const combined = new Map<string, { count: number; totalTokens: number }>();
72-
for (const arr of series) {
73-
for (const row of arr) {
74-
const prev = combined.get(row.date) ?? { count: 0, totalTokens: 0 };
75-
combined.set(row.date, {
76-
count: prev.count + row.count,
77-
totalTokens: prev.totalTokens + row.totalTokens,
116+
const buildDailyBlocksWithTopKeys = (
117+
keys: SparkKey[],
118+
perKey: SparkStatsResponse[],
119+
): SparkAggregatedDailyBlock[] => {
120+
const byDate = new Map<string, Map<number, SparkBucketKeyContribution>>();
121+
for (let i = 0; i < keys.length; i++) {
122+
const sparkKey = keys[i];
123+
for (const row of perKey[i].daily) {
124+
let m = byDate.get(row.date);
125+
if (!m) {
126+
m = new Map();
127+
byDate.set(row.date, m);
128+
}
129+
m.set(sparkKey.id, {
130+
keyId: sparkKey.id,
131+
description: sparkKey.description,
132+
count: row.count,
133+
totalTokens: row.totalTokens,
78134
});
79135
}
80136
}
81-
return [...combined.entries()]
137+
return [...byDate.entries()]
82138
.sort(([a], [b]) => a.localeCompare(b))
83-
.map(([date, v]) => ({ date, ...v }));
139+
.map(([date, keyMap]) => {
140+
const contributions = [...keyMap.values()];
141+
const count = contributions.reduce((s, c) => s + c.count, 0);
142+
const totalTokens = contributions.reduce((s, c) => s + c.totalTokens, 0);
143+
const topKeys = [...contributions]
144+
.sort(
145+
(a, b) =>
146+
b.totalTokens - a.totalTokens ||
147+
b.count - a.count ||
148+
a.keyId - b.keyId,
149+
)
150+
.slice(0, TOP_KEYS_PER_BUCKET);
151+
return { date, count, totalTokens, topKeys };
152+
});
84153
};
85154

86155
const mergeTopUsers = (
@@ -353,8 +422,8 @@ export const getSparkAggregatedKeyStats = async (
353422
totalRequests: perKey.reduce((s, x) => s + x.totalRequests, 0),
354423
totalTokens: perKey.reduce((s, x) => s + x.totalTokens, 0),
355424
lastUsedAt: maxIsoDate(perKey.map((x) => x.lastUsedAt)),
356-
hourly: mergeHourlySeries(perKey.map((x) => x.hourly)),
357-
daily: mergeDailySeries(perKey.map((x) => x.daily)),
425+
hourly: buildHourlyBlocksWithTopKeys(keys, perKey),
426+
daily: buildDailyBlocksWithTopKeys(keys, perKey),
358427
topUsers: mergeTopUsers(
359428
perKey.map((x) => x.topUsers),
360429
10,

0 commit comments

Comments
 (0)