Files
new-api-key-usage/packages/core/src/usage.ts
T
skycurtain d47cb5543c feat(engine): 实现同步引擎与 engine_status 快照
apps/engine 独立进程(syncRound + 防重叠调度);上游 log/token;QPU 采样;
server 读 engine_status;时刻出参 toUnixSec;中文日志;zod-openapi 扩展顺序修复。
2026-10-07 12:54:13 +08:00

85 lines
2.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 用量聚合辅助(公共约定 §5:后端换算、微分取整、聚合先求和再一次取整)
*/
import {
microSumToUsdStr,
qpuAt,
quotaToMicroUsd,
quotaToUsdStr,
} from "./money.ts";
/** 上游事实:QuotaPerUnit 默认 500000(qpu_history 未采样时的回退) */
export const DEFAULT_QPU = 500_000;
export type QpuPoint = { qpu: number; effective_from: Date | number };
export function resolveQpuTimeline(
rows: readonly QpuPoint[],
): QpuPoint[] {
if (rows.length > 0) return [...rows];
return [{ qpu: DEFAULT_QPU, effective_from: 0 }];
}
export interface UsageSum {
count: number;
quota: number;
quota_usd: string;
}
export function emptyUsage(): UsageSum {
return { count: 0, quota: 0, quota_usd: "0.00" };
}
/** 逐行微分累加(历史正确性:按日志时刻取 QPU) */
export function sumUsage(
rows: ReadonlyArray<{ quota: number; created_at: Date | number }>,
timeline: readonly QpuPoint[],
): UsageSum {
let quota = 0;
let micro = 0;
for (const r of rows) {
quota += r.quota;
const qpu = qpuAt(r.created_at, timeline);
micro += quotaToMicroUsd(r.quota, qpu);
}
return { count: rows.length, quota, quota_usd: microSumToUsdStr(micro) };
}
export function usageAt(
quota: number,
created_at: Date | number,
timeline: readonly QpuPoint[],
): UsageSum {
const qpu = qpuAt(created_at, timeline);
return { count: 1, quota, quota_usd: quotaToUsdStr(quota, qpu) };
}
export function addUsage(a: UsageSum, b: UsageSum): UsageSum {
// 合并已成形的两段时,quota_usd 需重算——调用方应尽量走 sumUsage
return {
count: a.count + b.count,
quota: a.quota + b.quota,
quota_usd: a.quota_usd,
};
}
/** 解析日志行 other JSON(billing_source / subscription_id) */
export function parseOtherJson(other: string | undefined | null): {
billing_source: string | null;
subscription_id: number | null;
raw: Record<string, unknown> | null;
} {
if (!other) return { billing_source: null, subscription_id: null, raw: null };
try {
const o = JSON.parse(other) as Record<string, unknown>;
return {
billing_source: typeof o.billing_source === "string" ? o.billing_source : null,
subscription_id:
typeof o.subscription_id === "number" ? o.subscription_id : null,
raw: o,
};
} catch {
return { billing_source: null, subscription_id: null, raw: null };
}
}