fix(analytics): fill hourly gaps with zero values in 24H view

- add fillHourlyGaps() function to usage-routes.ts

- insert zero entries for hours without activity

- ensures continuous timeline display in chart
This commit is contained in:
kaitranntt
2025-12-12 22:40:16 -05:00
parent 25741ac515
commit 4412d22f3e
+94 -3
View File
@@ -824,6 +824,7 @@ usageRoutes.get(
*
* Returns hourly usage trends for chart visualization.
* Query: ?since=YYYYMMDD&until=YYYYMMDD (defaults to last 24 hours)
* Fills in gaps with zero values for hours without activity.
*/
usageRoutes.get(
'/hourly',
@@ -855,12 +856,12 @@ usageRoutes.get(
requests: hour.modelBreakdowns.length,
}));
// Sort by hour ascending for chart display
trends.sort((a, b) => a.hour.localeCompare(b.hour));
// Fill gaps with zero values for hours without activity
const filledTrends = fillHourlyGaps(trends, since, until);
res.json({
success: true,
data: trends,
data: filledTrends,
});
} catch (error) {
errorResponse(res, error, 'Failed to fetch hourly usage');
@@ -868,6 +869,96 @@ usageRoutes.get(
}
);
/**
* Fill gaps in hourly data with zero values
* Ensures continuous timeline for chart display
*/
function fillHourlyGaps(
data: Array<{
hour: string;
tokens: number;
inputTokens: number;
outputTokens: number;
cacheTokens: number;
cost: number;
modelsUsed: number;
requests: number;
}>,
since?: string,
until?: string
): typeof data {
// If no date range specified, return as-is
if (!since && !until) {
return data.sort((a, b) => a.hour.localeCompare(b.hour));
}
// Create a map of existing hours for O(1) lookup
const hourMap = new Map(data.map((d) => [d.hour, d]));
// Determine the hour range
const now = new Date();
const startDate = since
? new Date(
parseInt(since.slice(0, 4)),
parseInt(since.slice(4, 6)) - 1,
parseInt(since.slice(6, 8)),
0,
0,
0
)
: new Date(now.getTime() - 24 * 60 * 60 * 1000); // Default: 24 hours ago
const endDate = until
? new Date(
parseInt(until.slice(0, 4)),
parseInt(until.slice(4, 6)) - 1,
parseInt(until.slice(6, 8)),
23,
59,
59
)
: now;
const result: typeof data = [];
// Iterate through each hour in the range
const current = new Date(startDate);
current.setMinutes(0, 0, 0);
while (current <= endDate) {
// Format hour key as "YYYY-MM-DD HH:00" in UTC to match storage format
const year = current.getUTCFullYear();
const month = String(current.getUTCMonth() + 1).padStart(2, '0');
const day = String(current.getUTCDate()).padStart(2, '0');
const hour = String(current.getUTCHours()).padStart(2, '0');
const hourKey = `${year}-${month}-${day} ${hour}:00`;
if (hourMap.has(hourKey)) {
const entry = hourMap.get(hourKey);
if (entry) {
result.push(entry);
}
} else {
// Insert zero entry for this hour
result.push({
hour: hourKey,
tokens: 0,
inputTokens: 0,
outputTokens: 0,
cacheTokens: 0,
cost: 0,
modelsUsed: 0,
requests: 0,
});
}
// Move to next hour
current.setTime(current.getTime() + 60 * 60 * 1000);
}
return result;
}
/**
* GET /api/usage/models
*