export type HealthGrade = "A" | "B" | "C" | "D" | "F"; export interface HealthPenalty { reason: string; deduction: number; } export interface HealthDelta { metric: string; delta: number; trend: "improving" | "degrading" | "stable"; } export interface RunHealth { score: number; grade: HealthGrade; penalties: HealthPenalty[]; deltas: HealthDelta[]; } const STALLED_THRESHOLD_MS = 5 * 60 * 1000; export function scoreToGrade(score: number): HealthGrade { if (score >= 90) return "A"; if (score >= 70) return "B"; if (score >= 50) return "C"; if (score >= 30) return "D"; return "F"; } export interface TaskSummary { id: string; status: string; stalledSince?: number; } export interface ManifestSummary { runId: string; tasks: TaskSummary[]; createdAt: string; } export function computeRunHealth(manifest: ManifestSummary): RunHealth { const penalties: HealthPenalty[] = []; const tasks = manifest.tasks; const taskCount = tasks.length; if (taskCount === 0) return { score: 100, grade: "A", penalties: [], deltas: [] }; const failedCount = tasks.filter((t) => t.status === "failed").length; const stalledCount = tasks.filter((t) => t.stalledSince !== undefined && Date.now() - t.stalledSince > STALLED_THRESHOLD_MS).length; const failureRate = failedCount / taskCount; if (failureRate > 0.2) { penalties.push({ reason: "high-failure-rate", deduction: Math.round(failureRate * 50), }); } if (stalledCount > 0) { penalties.push({ reason: "stalled-tasks", deduction: Math.min(15, stalledCount * 5), }); } if (taskCount > 20) { penalties.push({ reason: "large-task-count", deduction: Math.min(10, Math.floor((taskCount - 20) / 10)), }); } const totalDeduction = penalties.reduce((sum, p) => sum + p.deduction, 0); const score = Math.max(0, Math.min(100, 100 - totalDeduction)); return { score, grade: scoreToGrade(score), penalties, deltas: [], }; }