a5a3c967ce
- models.ts: 所有档位默认值、回退链、conflict 固定值全部换为 opus-4-8 - 回退链从三项缩为两项 [opus-4-8, sonnet-4-6](删除重复项) - metrics.ts: 额度权重 opus-4-8 升为 1.0(现为最高档) - 测试同步更新 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
110 lines
4.1 KiB
TypeScript
110 lines
4.1 KiB
TypeScript
/**
|
||
* 指标聚合的类型与纯函数(无 I/O,便于单测)。
|
||
* 实际从 runs/events/tasks 聚合的逻辑见 Store.metrics()。
|
||
*/
|
||
|
||
/** executor 执行时长统计(仅计入已结束、时长非负的 run;单位毫秒) */
|
||
export interface DurationStats {
|
||
count: number; // 计入统计的 run 数
|
||
avgMs: number; // 平均时长
|
||
p50Ms: number; // 中位数
|
||
p95Ms: number; // 95 分位
|
||
maxMs: number; // 最长
|
||
}
|
||
|
||
/** executor 运行成功率(只统计已落定的 succeeded/failed,started/cancelled 不计) */
|
||
export interface RunStats {
|
||
total: number; // succeeded + failed
|
||
succeeded: number;
|
||
failed: number;
|
||
successRate: number | null; // succeeded / total;无样本 → null
|
||
}
|
||
|
||
/** 复审通过率(code review + 安全审计;verdict=approve 占比) */
|
||
export interface ReviewStats {
|
||
reviewTotal: number; // 有 review verdict(approve/reject)的任务数
|
||
reviewApprove: number;
|
||
reviewRate: number | null; // approve / total;无样本 → null
|
||
securityTotal: number; // 有 security verdict 的任务数
|
||
securityApprove: number;
|
||
securityRate: number | null;
|
||
}
|
||
|
||
/** 重试 / 需人工关注 */
|
||
export interface RetryStats {
|
||
retries: number; // failed→queued 重试发生次数(来自 status.changed 事件)
|
||
taskCount: number; // 范围内任务总数(重试率分母)
|
||
retryRate: number | null; // retries / taskCount;无任务 → null
|
||
needsAttention: number; // 当前处于 needs_attention 的任务数
|
||
}
|
||
|
||
/** 按模型的额度估算(无 token 数据时按 时长×档位 粗估,estimated=true) */
|
||
export interface ModelUsage {
|
||
model: string;
|
||
runs: number; // 计入时长的 executor run 数
|
||
durationMs: number; // 累计执行时长
|
||
estCostUnits: number; // 估算额度(相对单位:执行分钟数 × 档位权重)
|
||
estimated: boolean; // 是否为估算(当前恒 true)
|
||
}
|
||
|
||
export interface Metrics {
|
||
projectId: string | null; // null = 全部项目
|
||
taskCount: number;
|
||
duration: DurationStats;
|
||
runs: RunStats;
|
||
review: ReviewStats;
|
||
retry: RetryStats;
|
||
byModel: ModelUsage[]; // 按估算额度降序
|
||
estimated: boolean; // 额度是否为估算(无 token 用量 → true)
|
||
}
|
||
|
||
/**
|
||
* 各模型档位的”每分钟相对额度权重”(粗估用,非真实计费):
|
||
* opus-4-8(hard/reviewer)最贵 → sonnet(easy)最省。
|
||
* 未知模型用 DEFAULT_COST_PER_MIN 兜底。
|
||
*/
|
||
export const MODEL_COST_PER_MIN: Record<string, number> = {
|
||
'claude-opus-4-8': 1.0,
|
||
'claude-sonnet-4-6': 0.2,
|
||
};
|
||
export const DEFAULT_COST_PER_MIN = 0.4;
|
||
|
||
export function costPerMin(model: string): number {
|
||
return MODEL_COST_PER_MIN[model] ?? DEFAULT_COST_PER_MIN;
|
||
}
|
||
|
||
/** 最近秩法分位数(sortedAsc 必须升序);空数组 → 0。 */
|
||
export function percentile(sortedAsc: number[], p: number): number {
|
||
if (sortedAsc.length === 0) return 0;
|
||
const rank = Math.ceil((p / 100) * sortedAsc.length);
|
||
const idx = Math.min(sortedAsc.length - 1, Math.max(0, rank - 1));
|
||
return sortedAsc[idx];
|
||
}
|
||
|
||
/** 比率(保留 4 位小数);分母 ≤ 0 → null。 */
|
||
export function ratio(num: number, den: number): number | null {
|
||
return den > 0 ? Math.round((num / den) * 10000) / 10000 : null;
|
||
}
|
||
|
||
/** 由时长(毫秒)与模型档位估算额度单位(保留 2 位小数)。 */
|
||
export function estimateCostUnits(durationMs: number, model: string): number {
|
||
return Math.round((durationMs / 60000) * costPerMin(model) * 100) / 100;
|
||
}
|
||
|
||
/** 空指标(无数据时各项归零,不崩)。 */
|
||
export function emptyMetrics(projectId: string | null): Metrics {
|
||
return {
|
||
projectId,
|
||
taskCount: 0,
|
||
duration: { count: 0, avgMs: 0, p50Ms: 0, p95Ms: 0, maxMs: 0 },
|
||
runs: { total: 0, succeeded: 0, failed: 0, successRate: null },
|
||
review: {
|
||
reviewTotal: 0, reviewApprove: 0, reviewRate: null,
|
||
securityTotal: 0, securityApprove: 0, securityRate: null,
|
||
},
|
||
retry: { retries: 0, taskCount: 0, retryRate: null, needsAttention: 0 },
|
||
byModel: [],
|
||
estimated: true,
|
||
};
|
||
}
|