mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-12 20:15:40 +00:00
fix: address analytics review follow-ups
This commit is contained in:
1 parent
e2f1363708
commit
286b5781a7
3 files changed
+90
-2
No files matched your search
@@ -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
|
* Merge daily usage data from multiple sources
|
||||||
* Combines entries with same date by aggregating tokens
|
* Combines entries with same date by aggregating tokens
|
||||||
@@ -208,7 +212,7 @@ export function mergeHourlyData(sources: HourlyUsage[][]): HourlyUsage[] {
|
|||||||
existing.cacheCreationTokens += hour.cacheCreationTokens;
|
existing.cacheCreationTokens += hour.cacheCreationTokens;
|
||||||
existing.cacheReadTokens += hour.cacheReadTokens;
|
existing.cacheReadTokens += hour.cacheReadTokens;
|
||||||
existing.totalCost += hour.totalCost;
|
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]);
|
const modelSet = new Set([...existing.modelsUsed, ...hour.modelsUsed]);
|
||||||
existing.modelsUsed = Array.from(modelSet);
|
existing.modelsUsed = Array.from(modelSet);
|
||||||
// Merge model breakdowns
|
// Merge model breakdowns
|
||||||
@@ -231,7 +235,7 @@ export function mergeHourlyData(sources: HourlyUsage[][]): HourlyUsage[] {
|
|||||||
...hour,
|
...hour,
|
||||||
modelsUsed: [...hour.modelsUsed],
|
modelsUsed: [...hour.modelsUsed],
|
||||||
modelBreakdowns: hour.modelBreakdowns.map((b) => ({ ...b })),
|
modelBreakdowns: hour.modelBreakdowns.map((b) => ({ ...b })),
|
||||||
requestCount: hour.requestCount ?? 0,
|
requestCount: getHourlyRequestCount(hour),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -694,6 +694,7 @@ export async function handleMonthly(
|
|||||||
cacheReadTokens: number;
|
cacheReadTokens: number;
|
||||||
totalCost: number;
|
totalCost: number;
|
||||||
modelsUsed: string[];
|
modelsUsed: string[];
|
||||||
|
modelBreakdowns: DailyUsage['modelBreakdowns'];
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
if (since || until) {
|
if (since || until) {
|
||||||
@@ -708,6 +709,17 @@ export async function handleMonthly(
|
|||||||
cacheReadTokens: number;
|
cacheReadTokens: number;
|
||||||
totalCost: number;
|
totalCost: number;
|
||||||
modelsUsed: Set<string>;
|
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,
|
cacheReadTokens: 0,
|
||||||
totalCost: 0,
|
totalCost: 0,
|
||||||
modelsUsed: new Set<string>(),
|
modelsUsed: new Set<string>(),
|
||||||
|
modelBreakdowns: new Map(),
|
||||||
};
|
};
|
||||||
|
|
||||||
existing.inputTokens += day.inputTokens;
|
existing.inputTokens += day.inputTokens;
|
||||||
@@ -731,6 +744,22 @@ export async function handleMonthly(
|
|||||||
for (const model of day.modelsUsed) {
|
for (const model of day.modelsUsed) {
|
||||||
existing.modelsUsed.add(model);
|
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);
|
monthMap.set(month, existing);
|
||||||
}
|
}
|
||||||
@@ -744,6 +773,7 @@ export async function handleMonthly(
|
|||||||
cacheReadTokens: month.cacheReadTokens,
|
cacheReadTokens: month.cacheReadTokens,
|
||||||
totalCost: month.totalCost,
|
totalCost: month.totalCost,
|
||||||
modelsUsed: Array.from(month.modelsUsed),
|
modelsUsed: Array.from(month.modelsUsed),
|
||||||
|
modelBreakdowns: Array.from(month.modelBreakdowns.values()),
|
||||||
}))
|
}))
|
||||||
.sort((a, b) => a.month.localeCompare(b.month));
|
.sort((a, b) => a.month.localeCompare(b.month));
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -306,4 +306,58 @@ describe('usage aggregator cliproxy integration', () => {
|
|||||||
expect(result[0].totalCost).toBe(3);
|
expect(result[0].totalCost).toBe(3);
|
||||||
expect(result[0].modelBreakdowns).toHaveLength(2);
|
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