| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251 |
- 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<MetricValue, "score"> & {
- displayValue: string;
- helperText?: string;
- progress: number | null;
- score: number | null;
- state: MetricDataState;
- };
- export type RuntimeSubMetric = Omit<SubMetric, "values"> & {
- values: RuntimeMetricValue[];
- score: number | null;
- state: MetricDataState;
- };
- export type RuntimeMetric = Omit<MetricCategoryData, "subMetrics"> & {
- subMetrics: RuntimeSubMetric[];
- avgScore: string;
- score: number | null;
- state: MetricDataState;
- summary: string;
- };
- export type RuntimeMetricsByCategory = Record<string, RuntimeMetric[]>;
- 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<number | null | undefined>) {
- 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;
- }
|