Files
new-api-analytics/lib/query-trends.ts

87 lines
3.0 KiB
TypeScript

import { query } from "./db";
import { CACHE_CREATION, CACHE_READ, REAL_MODEL, cacheKey, cached, timeWhere } from "./query-shared";
// ── 趋势 ──────────────────────────────────────────────────────
export interface TrendPoint {
date: string;
calls: number;
prompt_tokens: number;
completion_tokens: number;
cache_creation_tokens: number;
cache_read_tokens: number;
total_tokens: number;
quota: number;
}
export type TrendGranularity = "hour" | "day" | "week" | "month";
export interface TrendFilters {
username?: string;
model?: string;
channelId?: number;
}
function appendTrendFilters(
where: string,
params: (string | number | boolean | null)[],
filters: TrendFilters = {}
): string {
if (filters.username) {
params.push(filters.username);
where += ` AND username = $${params.length}`;
}
if (filters.model) {
params.push(filters.model);
where += ` AND ${REAL_MODEL} = $${params.length}`;
}
if (filters.channelId !== undefined) {
params.push(filters.channelId);
where += ` AND channel_id = $${params.length}`;
}
return where;
}
export function getTrends(
granularity: TrendGranularity = "day",
startTs?: number,
endTs?: number,
filters: TrendFilters = {}
): Promise<TrendPoint[]> {
return cached(cacheKey("trends", granularity, startTs, endTs, filters.username, filters.model, filters.channelId), async () => {
const params: (string | number | boolean | null)[] = [];
const where = appendTrendFilters(timeWhere(params, startTs, endTs), params, filters);
const truncExpr =
granularity === "hour"
? `to_char(date_trunc('hour', to_timestamp(created_at) AT TIME ZONE 'Asia/Shanghai'), 'YYYY-MM-DD HH24:00')`
: granularity === "day"
? `((to_timestamp(created_at) AT TIME ZONE 'Asia/Shanghai')::date)::text`
: `(date_trunc('${granularity}', to_timestamp(created_at) AT TIME ZONE 'Asia/Shanghai')::date)::text`;
const rows = await query(
`SELECT
${truncExpr} as date,
COUNT(*)::int as calls,
COALESCE(SUM(prompt_tokens), 0)::bigint as prompt_tokens,
COALESCE(SUM(completion_tokens), 0)::bigint as completion_tokens,
COALESCE(SUM(${CACHE_CREATION}), 0)::bigint as cache_creation_tokens,
COALESCE(SUM(${CACHE_READ}), 0)::bigint as cache_read_tokens,
COALESCE(SUM(quota), 0)::bigint as quota
FROM logs WHERE ${where}
GROUP BY date ORDER BY date`,
params
);
return rows.map((r) => ({
date: String(r.date).slice(0, granularity === "hour" ? 16 : 10),
calls: Number(r.calls),
prompt_tokens: Number(r.prompt_tokens),
completion_tokens: Number(r.completion_tokens),
cache_creation_tokens: Number(r.cache_creation_tokens),
cache_read_tokens: Number(r.cache_read_tokens),
total_tokens: Number(r.prompt_tokens) + Number(r.completion_tokens) + Number(r.cache_creation_tokens) + Number(r.cache_read_tokens),
quota: Number(r.quota),
}));
});
}