mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 08:17:11 +00:00
fix: address analytics review follow-ups
This commit is contained in:
@@ -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),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user