mirror of
https://github.com/tiennm99/ccs.git
synced 2026-09-09 02:17:03 +00:00
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:
@@ -824,6 +824,7 @@ usageRoutes.get(
|
|||||||
*
|
*
|
||||||
* Returns hourly usage trends for chart visualization.
|
* Returns hourly usage trends for chart visualization.
|
||||||
* Query: ?since=YYYYMMDD&until=YYYYMMDD (defaults to last 24 hours)
|
* Query: ?since=YYYYMMDD&until=YYYYMMDD (defaults to last 24 hours)
|
||||||
|
* Fills in gaps with zero values for hours without activity.
|
||||||
*/
|
*/
|
||||||
usageRoutes.get(
|
usageRoutes.get(
|
||||||
'/hourly',
|
'/hourly',
|
||||||
@@ -855,12 +856,12 @@ usageRoutes.get(
|
|||||||
requests: hour.modelBreakdowns.length,
|
requests: hour.modelBreakdowns.length,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Sort by hour ascending for chart display
|
// Fill gaps with zero values for hours without activity
|
||||||
trends.sort((a, b) => a.hour.localeCompare(b.hour));
|
const filledTrends = fillHourlyGaps(trends, since, until);
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
success: true,
|
success: true,
|
||||||
data: trends,
|
data: filledTrends,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
errorResponse(res, error, 'Failed to fetch hourly usage');
|
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
|
* GET /api/usage/models
|
||||||
*
|
*
|
||||||
|
|||||||
Reference in New Issue
Block a user