feat: harden analytics dashboard
This commit is contained in:
145
lib/query-rankings.ts
Normal file
145
lib/query-rankings.ts
Normal file
@@ -0,0 +1,145 @@
|
||||
import { query } from "./db";
|
||||
import { quotaToUsd } from "./metrics";
|
||||
import { CACHE_CREATION, CACHE_READ, REAL_MODEL, cacheKey, cached, getChannelNames, getDisplayNames, timeWhere } from "./query-shared";
|
||||
|
||||
// ── 排名 ──────────────────────────────────────────────────────
|
||||
|
||||
export interface RankingItem {
|
||||
rank: number;
|
||||
name: string;
|
||||
id?: number;
|
||||
calls: number;
|
||||
prompt_tokens: number;
|
||||
completion_tokens: number;
|
||||
cache_creation_tokens: number;
|
||||
cache_read_tokens: number;
|
||||
total_tokens: number;
|
||||
quota: number;
|
||||
quota_usd: number;
|
||||
}
|
||||
export function getUserRanking(
|
||||
startTs?: number,
|
||||
endTs?: number,
|
||||
limit = 50
|
||||
): Promise<RankingItem[]> {
|
||||
return cached(cacheKey("userRank", startTs, endTs, limit), async () => {
|
||||
const params: (string | number | boolean | null)[] = [];
|
||||
const where = timeWhere(params, startTs, endTs);
|
||||
params.push(limit);
|
||||
|
||||
const displayNames = await getDisplayNames();
|
||||
|
||||
const rows = await query(
|
||||
`SELECT user_id, username,
|
||||
COUNT(*)::int as calls,
|
||||
COALESCE(SUM(prompt_tokens), 0)::bigint as prompt,
|
||||
COALESCE(SUM(completion_tokens), 0)::bigint as completion,
|
||||
COALESCE(SUM(${CACHE_CREATION}), 0)::bigint as cache_creation,
|
||||
COALESCE(SUM(${CACHE_READ}), 0)::bigint as cache_read,
|
||||
COALESCE(SUM(quota), 0)::bigint as quota
|
||||
FROM logs WHERE ${where}
|
||||
GROUP BY user_id, username
|
||||
ORDER BY COALESCE(SUM(prompt_tokens),0) + COALESCE(SUM(completion_tokens),0) + COALESCE(SUM(${CACHE_CREATION}),0) + COALESCE(SUM(${CACHE_READ}),0) DESC
|
||||
LIMIT $${params.length}`,
|
||||
params
|
||||
);
|
||||
|
||||
return rows.map((r, i) => ({
|
||||
rank: i + 1,
|
||||
name: displayNames[r.user_id] || r.username,
|
||||
username: r.username as string,
|
||||
id: Number(r.user_id),
|
||||
calls: Number(r.calls),
|
||||
prompt_tokens: Number(r.prompt),
|
||||
completion_tokens: Number(r.completion),
|
||||
cache_creation_tokens: Number(r.cache_creation),
|
||||
cache_read_tokens: Number(r.cache_read),
|
||||
total_tokens: Number(r.prompt) + Number(r.completion) + Number(r.cache_creation) + Number(r.cache_read),
|
||||
quota: Number(r.quota),
|
||||
quota_usd: quotaToUsd(Number(r.quota)),
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
export function getModelRanking(
|
||||
startTs?: number,
|
||||
endTs?: number,
|
||||
limit = 50
|
||||
): Promise<RankingItem[]> {
|
||||
return cached(cacheKey("modelRank", startTs, endTs, limit), async () => {
|
||||
const params: (string | number | boolean | null)[] = [];
|
||||
const where = timeWhere(params, startTs, endTs);
|
||||
params.push(limit);
|
||||
|
||||
const rows = await query(
|
||||
`SELECT ${REAL_MODEL} as model,
|
||||
COUNT(*)::int as calls,
|
||||
COALESCE(SUM(prompt_tokens), 0)::bigint as prompt,
|
||||
COALESCE(SUM(completion_tokens), 0)::bigint as completion,
|
||||
COALESCE(SUM(${CACHE_CREATION}), 0)::bigint as cache_creation,
|
||||
COALESCE(SUM(${CACHE_READ}), 0)::bigint as cache_read,
|
||||
COALESCE(SUM(quota), 0)::bigint as quota
|
||||
FROM logs WHERE ${where}
|
||||
GROUP BY model
|
||||
ORDER BY COALESCE(SUM(prompt_tokens),0) + COALESCE(SUM(completion_tokens),0) + COALESCE(SUM(${CACHE_CREATION}),0) + COALESCE(SUM(${CACHE_READ}),0) DESC
|
||||
LIMIT $${params.length}`,
|
||||
params
|
||||
);
|
||||
|
||||
return rows.map((r, i) => ({
|
||||
rank: i + 1,
|
||||
name: r.model || "(unknown)",
|
||||
calls: Number(r.calls),
|
||||
prompt_tokens: Number(r.prompt),
|
||||
completion_tokens: Number(r.completion),
|
||||
cache_creation_tokens: Number(r.cache_creation),
|
||||
cache_read_tokens: Number(r.cache_read),
|
||||
total_tokens: Number(r.prompt) + Number(r.completion) + Number(r.cache_creation) + Number(r.cache_read),
|
||||
quota: Number(r.quota),
|
||||
quota_usd: quotaToUsd(Number(r.quota)),
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
export function getChannelRanking(
|
||||
startTs?: number,
|
||||
endTs?: number,
|
||||
limit = 50
|
||||
): Promise<RankingItem[]> {
|
||||
return cached(cacheKey("channelRank", startTs, endTs, limit), async () => {
|
||||
const params: (string | number | boolean | null)[] = [];
|
||||
const where = timeWhere(params, startTs, endTs);
|
||||
params.push(limit);
|
||||
|
||||
const channelNames = await getChannelNames();
|
||||
|
||||
const rows = await query(
|
||||
`SELECT channel_id,
|
||||
COUNT(*)::int as calls,
|
||||
COALESCE(SUM(prompt_tokens), 0)::bigint as prompt,
|
||||
COALESCE(SUM(completion_tokens), 0)::bigint as completion,
|
||||
COALESCE(SUM(${CACHE_CREATION}), 0)::bigint as cache_creation,
|
||||
COALESCE(SUM(${CACHE_READ}), 0)::bigint as cache_read,
|
||||
COALESCE(SUM(quota), 0)::bigint as quota
|
||||
FROM logs WHERE ${where}
|
||||
GROUP BY channel_id
|
||||
ORDER BY COALESCE(SUM(prompt_tokens),0) + COALESCE(SUM(completion_tokens),0) + COALESCE(SUM(${CACHE_CREATION}),0) + COALESCE(SUM(${CACHE_READ}),0) DESC
|
||||
LIMIT $${params.length}`,
|
||||
params
|
||||
);
|
||||
|
||||
return rows.map((r, i) => ({
|
||||
rank: i + 1,
|
||||
name: channelNames[r.channel_id] || `已删除(${r.channel_id})`,
|
||||
id: Number(r.channel_id),
|
||||
calls: Number(r.calls),
|
||||
prompt_tokens: Number(r.prompt),
|
||||
completion_tokens: Number(r.completion),
|
||||
cache_creation_tokens: Number(r.cache_creation),
|
||||
cache_read_tokens: Number(r.cache_read),
|
||||
total_tokens: Number(r.prompt) + Number(r.completion) + Number(r.cache_creation) + Number(r.cache_read),
|
||||
quota: Number(r.quota),
|
||||
quota_usd: quotaToUsd(Number(r.quota)),
|
||||
}));
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user