fix: address analytics review follow-ups

This commit is contained in:
Tam Nhu Tran
2026-04-26 15:31:32 -04:00
parent e2f1363708
commit 286b5781a7
3 changed files with 90 additions and 2 deletions
+6 -2
View File
@@ -99,6 +99,10 @@ async function loadInstanceData(instancePath: string): Promise<{
}
}
function getHourlyRequestCount(hour: HourlyUsage): number {
return hour.requestCount ?? hour.modelBreakdowns.length;
}
/**
* Merge daily usage data from multiple sources
* Combines entries with same date by aggregating tokens
@@ -208,7 +212,7 @@ export function mergeHourlyData(sources: HourlyUsage[][]): HourlyUsage[] {
existing.cacheCreationTokens += hour.cacheCreationTokens;
existing.cacheReadTokens += hour.cacheReadTokens;
existing.totalCost += hour.totalCost;
existing.requestCount = (existing.requestCount ?? 0) + (hour.requestCount ?? 0);
existing.requestCount = getHourlyRequestCount(existing) + getHourlyRequestCount(hour);
const modelSet = new Set([...existing.modelsUsed, ...hour.modelsUsed]);
existing.modelsUsed = Array.from(modelSet);
// Merge model breakdowns
@@ -231,7 +235,7 @@ export function mergeHourlyData(sources: HourlyUsage[][]): HourlyUsage[] {
...hour,
modelsUsed: [...hour.modelsUsed],
modelBreakdowns: hour.modelBreakdowns.map((b) => ({ ...b })),
requestCount: hour.requestCount ?? 0,
requestCount: getHourlyRequestCount(hour),
});
}
}
+30
View File
@@ -694,6 +694,7 @@ export async function handleMonthly(
cacheReadTokens: number;
totalCost: number;
modelsUsed: string[];
modelBreakdowns: DailyUsage['modelBreakdowns'];
}>;
if (since || until) {
@@ -708,6 +709,17 @@ export async function handleMonthly(
cacheReadTokens: number;
totalCost: number;
modelsUsed: Set<string>;
modelBreakdowns: Map<
string,
{
modelName: string;
inputTokens: number;
outputTokens: number;
cacheCreationTokens: number;
cacheReadTokens: number;
cost: number;
}
>;
}
>();
@@ -721,6 +733,7 @@ export async function handleMonthly(
cacheReadTokens: 0,
totalCost: 0,
modelsUsed: new Set<string>(),
modelBreakdowns: new Map(),
};
existing.inputTokens += day.inputTokens;
@@ -731,6 +744,22 @@ export async function handleMonthly(
for (const model of day.modelsUsed) {
existing.modelsUsed.add(model);
}
for (const breakdown of day.modelBreakdowns) {
const existingBreakdown = existing.modelBreakdowns.get(breakdown.modelName) ?? {
modelName: breakdown.modelName,
inputTokens: 0,
outputTokens: 0,
cacheCreationTokens: 0,
cacheReadTokens: 0,
cost: 0,
};
existingBreakdown.inputTokens += breakdown.inputTokens;
existingBreakdown.outputTokens += breakdown.outputTokens;
existingBreakdown.cacheCreationTokens += breakdown.cacheCreationTokens;
existingBreakdown.cacheReadTokens += breakdown.cacheReadTokens;
existingBreakdown.cost += breakdown.cost;
existing.modelBreakdowns.set(breakdown.modelName, existingBreakdown);
}
monthMap.set(month, existing);
}
@@ -744,6 +773,7 @@ export async function handleMonthly(
cacheReadTokens: month.cacheReadTokens,
totalCost: month.totalCost,
modelsUsed: Array.from(month.modelsUsed),
modelBreakdowns: Array.from(month.modelBreakdowns.values()),
}))
.sort((a, b) => a.month.localeCompare(b.month));
} else {
@@ -306,4 +306,58 @@ describe('usage aggregator cliproxy integration', () => {
expect(result[0].totalCost).toBe(3);
expect(result[0].modelBreakdowns).toHaveLength(2);
});
it('falls back to model cardinality when merging legacy hourly buckets without requestCount', () => {
const result = aggregator.mergeHourlyData([
[
{
hour: '2026-03-02 10:00',
source: 'legacy-a',
inputTokens: 10,
outputTokens: 1,
cacheCreationTokens: 0,
cacheReadTokens: 0,
cost: 1,
totalCost: 1,
modelsUsed: ['claude-sonnet-4-5'],
modelBreakdowns: [
{
modelName: 'claude-sonnet-4-5',
inputTokens: 10,
outputTokens: 1,
cacheCreationTokens: 0,
cacheReadTokens: 0,
cost: 1,
},
],
},
],
[
{
hour: '2026-03-02 10:00',
source: 'legacy-b',
inputTokens: 20,
outputTokens: 2,
cacheCreationTokens: 0,
cacheReadTokens: 0,
cost: 2,
totalCost: 2,
modelsUsed: ['gemini-2.5-pro'],
modelBreakdowns: [
{
modelName: 'gemini-2.5-pro',
inputTokens: 20,
outputTokens: 2,
cacheCreationTokens: 0,
cacheReadTokens: 0,
cost: 2,
},
],
},
],
]);
expect(result).toHaveLength(1);
expect(result[0].requestCount).toBe(2);
});
});