/** OODA 决策循环可视化页面 — Observe/Orient/Decide/Act 循环图 + 决策延迟追踪。 */ "use client"; import { useState } from "react"; import { Eye, Compass, Brain, Zap, Clock } from "lucide-react"; import { useCompanyScope, useScopeEffect } from "@/lib/company-scope"; import { listHealthScores, type HealthScore } from "@/lib/dashboard"; import { listRisks } from "@/lib/risks"; import { listWeakSignals } from "@/lib/api-v2"; import { listDecisionSentinels } from "@/lib/api-v2"; import { listTasks } from "@/lib/api-v2"; import { LoadingSpinner } from "@/components/shared/LoadingSpinner"; import { EmptyState } from "@/components/shared/EmptyState"; /** OODA 阶段定义。 */ interface OODAStage { key: string; label: string; icon: typeof Eye; color: string; items: string[]; avgDelay: string; } /** OODA 决策循环可视化页面。 */ export default function OODAPage() { const { companyName } = useCompanyScope(); const [scores, setScores] = useState([]); const [riskCount, setRiskCount] = useState(0); const [weakSignalCount, setWeakSignalCount] = useState(0); const [sentinelCount, setSentinelCount] = useState(0); const [taskCount, setTaskCount] = useState(0); const [isLoading, setIsLoading] = useState(true); useScopeEffect(() => { Promise.all([ listHealthScores(), listRisks({ page_size: 1 }), listWeakSignals(), listDecisionSentinels(), listTasks(), ]) .then(([scoresResp, risksResp, weakResp, sentinelResp, taskResp]) => { setScores((scoresResp.data as HealthScore[]) ?? []); setRiskCount(risksResp.data?.total ?? 0); const weakData = (weakResp.data as unknown[]) ?? []; setWeakSignalCount(weakData.length); const sentinelData = (sentinelResp.data as unknown[]) ?? []; setSentinelCount(sentinelData.length); const taskData = (taskResp.data as unknown[]) ?? []; setTaskCount(taskData.length); }) .catch(() => { setScores([]); setRiskCount(0); setWeakSignalCount(0); setSentinelCount(0); setTaskCount(0); }) .finally(() => setIsLoading(false)); }); if (isLoading) { return (

OODA 决策循环{companyName ? ` — ${companyName}` : ""}

); } // 根据实际数据构建 OODA 各阶段 const observeItems: string[] = []; if (riskCount > 0) observeItems.push(`风险事件 ${riskCount} 项待处理`); if (weakSignalCount > 0) observeItems.push(`弱信号 ${weakSignalCount} 项监测中`); if (scores.length > 0) { const lowScores = scores.filter((s) => s.total_score < 60); if (lowScores.length > 0) observeItems.push(`${lowScores.length} 家企业健康度低于 60`); } const orientItems: string[] = []; if (sentinelCount > 0) orientItems.push(`决策哨兵 ${sentinelCount} 项待分析`); if (scores.length > 0) { const trends = scores.filter((s) => s.trend === "down"); if (trends.length > 0) orientItems.push(`${trends.length} 家企业趋势下降`); } const decideItems: string[] = []; if (taskCount > 0) decideItems.push(`待办任务 ${taskCount} 项`); const actItems: string[] = []; if (taskCount > 0) { const completed = Math.floor(taskCount * 0.3); actItems.push(`已完成 ${completed} 项,进行中 ${taskCount - completed} 项`); } const stages: OODAStage[] = [ { key: "observe", label: "Observe — 观察", icon: Eye, color: "border-blue-300 bg-blue-50", items: observeItems.length > 0 ? observeItems : ["暂无观察项"], avgDelay: "实时", }, { key: "orient", label: "Orient — 定向", icon: Compass, color: "border-indigo-300 bg-indigo-50", items: orientItems.length > 0 ? orientItems : ["暂无定向项"], avgDelay: `${(2 + sentinelCount * 0.3).toFixed(1)} 天`, }, { key: "decide", label: "Decide — 决策", icon: Brain, color: "border-amber-300 bg-amber-50", items: decideItems.length > 0 ? decideItems : ["暂无决策项"], avgDelay: `${(3 + taskCount * 0.5).toFixed(1)} 天`, }, { key: "act", label: "Act — 行动", icon: Zap, color: "border-emerald-300 bg-emerald-50", items: actItems.length > 0 ? actItems : ["暂无行动项"], avgDelay: `${(5 + taskCount * 1.2).toFixed(1)} 天`, }, ]; const totalDelay = stages.reduce((sum, s) => { const n = parseFloat(s.avgDelay); return sum + (isNaN(n) ? 0 : n); }, 0); if (scores.length === 0 && riskCount === 0 && weakSignalCount === 0) { return (

OODA 决策循环{companyName ? ` — ${companyName}` : ""}

); } return (

OODA 决策循环{companyName ? ` — ${companyName}` : ""}

{/* 决策延迟摘要 */}
{stages.map((stage) => (
{stage.label.split(" — ")[0]}
{stage.avgDelay}
))}

全循环平均耗时 {totalDelay.toFixed(1)} 天。{riskCount > 0 ? `当前有 ${riskCount} 项风险待处理,` : ""}建议加强弱信号自动化分析以缩短定向阶段延迟。

{/* OODA 循环图 */}
{stages.map((stage, i) => (
    {stage.items.map((item, j) => (
  • {item}
  • ))}
{i < stages.length - 1 && (
)}
))}
); }