import { metricsData, type MetricCategoryData, type MetricValue, type SubMetric } from "@/components/metricsData"; import type { D4MetricsResponse } from "@/lib/d4Metrics"; export type MetricDataState = "ready" | "loading" | "error" | "unavailable"; export type RuntimeMetricValue = Omit & { displayValue: string; helperText?: string; progress: number | null; score: number | null; state: MetricDataState; }; export type RuntimeSubMetric = Omit & { values: RuntimeMetricValue[]; score: number | null; state: MetricDataState; }; export type RuntimeMetric = Omit & { subMetrics: RuntimeSubMetric[]; avgScore: string; score: number | null; state: MetricDataState; summary: string; }; export type RuntimeMetricsByCategory = Record; export interface D4MetricsLoadState { data: D4MetricsResponse | null; isLoading: boolean; error: string | null; } const countFormatter = new Intl.NumberFormat("zh-CN"); function clampScore(value: number) { return Math.max(0, Math.min(100, Math.round(value))); } function rateToScore(rate: number | null | undefined) { return rate == null ? null : clampScore(rate * 100); } function averageScore(values: Array) { const validValues = values.filter((value): value is number => typeof value === "number" && !Number.isNaN(value)); if (validValues.length === 0) { return null; } return clampScore(validValues.reduce((sum, value) => sum + value, 0) / validValues.length); } function formatPercent(rate: number | null | undefined) { const score = rateToScore(rate); return score == null ? "暂无数据" : `${score}%`; } function formatCount(value: number | null | undefined) { return value == null ? "暂无数据" : countFormatter.format(value); } function formatDuration(ms: number | null | undefined) { if (ms == null) { return "暂无数据"; } const totalSeconds = Math.max(0, Math.round(ms / 1000)); if (totalSeconds < 60) { return `${totalSeconds} 秒`; } const totalMinutes = Math.round(totalSeconds / 60); if (totalMinutes < 60) { return `${totalMinutes} 分钟`; } const hours = Math.floor(totalMinutes / 60); const minutes = totalMinutes % 60; if (hours < 24) { return minutes > 0 ? `${hours} 小时 ${minutes} 分钟` : `${hours} 小时`; } const days = Math.floor(hours / 24); const restHours = hours % 24; return restHours > 0 ? `${days} 天 ${restHours} 小时` : `${days} 天`; } function buildPendingValue(value: MetricValue, state: MetricDataState, message: string, helperText?: string): RuntimeMetricValue { return { ...value, displayValue: message, helperText, progress: null, score: null, state, }; } function buildPendingMetric(metric: MetricCategoryData, state: MetricDataState, message: string, summary: string): RuntimeMetric { return { ...metric, avgScore: state === "loading" ? "..." : "--", score: null, state, summary, subMetrics: metric.subMetrics.map((subMetric) => ({ ...subMetric, score: null, state, values: subMetric.values.map((value) => buildPendingValue(value, state, message, summary)), })), }; } function buildD4ReadyMetric(metric: MetricCategoryData, data: D4MetricsResponse): RuntimeMetric { const completionScore = rateToScore(data.taskCompletion.completionRate); const onTimeScore = rateToScore(data.taskCompletion.onTimeCompletionRate); const conflictScore = rateToScore(data.conflict.resolutionRate); const score = averageScore([completionScore, onTimeScore, conflictScore]); const subMetrics: RuntimeSubMetric[] = metric.subMetrics.map((subMetric) => { if (subMetric.id === "D4-1") { const values: RuntimeMetricValue[] = [ { name: "协作任务完成率", displayValue: formatPercent(data.taskCompletion.completionRate), helperText: `${formatCount(data.taskCompletion.completedCount)} / ${formatCount(data.taskCompletion.assignedCount)} 项任务完成`, progress: completionScore, score: completionScore, state: "ready", }, { name: "个人任务按时完成率", displayValue: formatPercent(data.taskCompletion.onTimeCompletionRate), helperText: `${formatCount(data.taskCompletion.onTimeCompletedCount)} / ${formatCount(data.taskCompletion.completedCount)} 项完成任务满足 SLA`, progress: onTimeScore, score: onTimeScore, state: "ready", }, ]; return { ...subMetric, values, score: averageScore(values.map((value) => value.score)), state: "ready", }; } if (subMetric.id === "D4-2") { const values: RuntimeMetricValue[] = [ { name: "团队沟通平均响应耗时", displayValue: formatDuration(data.communication.averageResponseMs), helperText: `${formatCount(data.communication.respondedMessageCount)} / ${formatCount(data.communication.sentMessageCount)} 条消息形成响应`, progress: null, score: null, state: "ready", }, { name: "有效协作次数", displayValue: formatCount(data.collaboration.effectiveCollaborationCount), helperText: "D4_COLLAB_EFFECTIVE 事件计数", progress: null, score: null, state: "ready", }, { name: "协作冲突解决次数", displayValue: formatCount(data.conflict.resolvedCount), helperText: `${formatCount(data.conflict.createdCount)} 次冲突创建`, progress: null, score: null, state: "ready", }, { name: "协作冲突解决率", displayValue: formatPercent(data.conflict.resolutionRate), helperText: `${formatCount(data.conflict.resolvedCount)} / ${formatCount(data.conflict.createdCount)} 次冲突已解决`, progress: conflictScore, score: conflictScore, state: "ready", }, ]; return { ...subMetric, values, score: averageScore(values.map((value) => value.score)), state: "ready", }; } return { ...subMetric, score: null, state: "ready", values: subMetric.values.map((value) => buildPendingValue(value, "ready", "暂无数据")), }; }); return { ...metric, avgScore: score == null ? "--" : `${score}`, score, state: "ready", summary: `已同步 ${formatCount(data.scan.scannedEvents)} 条 D4 日志事件`, subMetrics, }; } function buildD4Metric(metric: MetricCategoryData, loadState: D4MetricsLoadState): RuntimeMetric { if (loadState.isLoading) { return buildPendingMetric(metric, "loading", "加载中", "正在从 D4 指标接口同步数据"); } if (loadState.error) { return buildPendingMetric(metric, "error", "接口异常", loadState.error); } if (!loadState.data) { return buildPendingMetric(metric, "unavailable", "暂无数据", "D4 接口尚未返回数据"); } return buildD4ReadyMetric(metric, loadState.data); } export function buildRuntimeMetrics(d4LoadState: D4MetricsLoadState): RuntimeMetricsByCategory { return Object.fromEntries( Object.entries(metricsData).map(([category, metrics]) => [ category, metrics.map((metric) => { if (metric.id === "D4") { return buildD4Metric(metric, d4LoadState); } return buildPendingMetric(metric, "unavailable", "未接入", "后端暂未提供该指标接口"); }), ]), ); } export function getRuntimeMetric(metrics: RuntimeMetricsByCategory, metricId: string) { const normalizedMetricId = metricId.toUpperCase(); const category = normalizedMetricId.charAt(0); return metrics[category]?.find((metric) => metric.id === normalizedMetricId) ?? null; }