Files
maestro/src/model/metrics.ts
T
wangjia a5a3c967ce fix: 全量替换 claude-fable-5 → claude-opus-4-8(模型不可用)
- 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>
2026-06-14 15:14:09 +08:00

110 lines
4.1 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.
/**
* 指标聚合的类型与纯函数(无 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/failedstarted/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 verdictapprove/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-8hard/reviewer)最贵 → sonneteasy)最省。
* 未知模型用 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,
};
}