import { useEffect, useMemo, useState } from "react"; import { ArrowRight, CheckCircle2, Clock3, FileText, Info, RefreshCw, TriangleAlert } from "lucide-react"; import { useNavigate, useParams } from "react-router"; import { toast } from "sonner"; import { Button } from "~/components/ui/button"; import { Card, CardAction, CardContent, CardDescription, CardHeader, CardTitle } from "~/components/ui/card"; import { Input } from "~/components/ui/input"; import { Skeleton } from "~/components/ui/skeleton"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "~/components/ui/table"; import { Textarea } from "~/components/ui/textarea"; import { AbnormalBadge, FilterSelect, GradeBadge, MetricLineChart, OperationsPageHeader, SortingComboChart, StatusBadge, } from "./OperationsUi"; import { FEATURE_METRICS, SORTING_DISTRIBUTION, abnormalLevelOf, abnormalReasonOf, gradeOf, modelTrend, monthsBetween, } from "./modelData"; import { useOperationsData, useOperationsModelDetail } from "./OperationsDataContext"; import { useCanOperationsAction } from "./operationsRole"; function recentMonths(count: number): string[] { const end = 2026 * 12 + 6; return Array.from({ length: count }, (_, index) => { const value = end - count + 1 + index; return `${Math.floor(value / 12)}-${String(value % 12 + 1).padStart(2, "0")}`; }); } export default function MonitoringDetailPage() { const navigate = useNavigate(); const params = useParams(); const { models } = useOperationsData(); const modelId = params.modelId ?? models[0]?.modelId ?? ""; const detail = useOperationsModelDetail(modelId, "2026-07"); const baseModel = detail.model ?? models.find((item) => item.modelId === modelId) ?? models[0]; const model = detail.monitoringResult ?? baseModel ?? models[0]!; const [range, setRange] = useState("6"); const [fromMonth, setFromMonth] = useState("2026-02"); const [toMonth, setToMonth] = useState("2026-07"); const [compareId, setCompareId] = useState(""); const [selectedFeature, setSelectedFeature] = useState(null); const [reviewStage, setReviewStage] = useState<0 | 1 | 2>(0); const [reviewDecision, setReviewDecision] = useState("暂不处理"); const [reviewNote, setReviewNote] = useState(""); const months = range === "custom" ? monthsBetween(fromMonth, toMonth) : recentMonths(Number(range)); const monitorMonth = detail.monitoringResult?.monitorMonth ?? ""; const ksTrend = modelTrend(model, "ks", months); const psiTrend = modelTrend(model, "psi", months); const compareModel = models.find((item) => item.modelId === compareId && item.modelId !== model.modelId) ?? null; const compareKsTrend = compareModel ? modelTrend(compareModel, "ks", months) : null; const comparePsiTrend = compareModel ? modelTrend(compareModel, "psi", months) : null; const grade = gradeOf(model); const abnormal = abnormalLevelOf(model); const chosenFeature = FEATURE_METRICS.find((item) => item.key === selectedFeature) ?? null; const ivTop = useMemo(() => [...FEATURE_METRICS].sort((left, right) => right.ivDrop - left.ivDrop), []); const csiTop = useMemo(() => [...FEATURE_METRICS].sort((left, right) => right.csiRise - left.csiRise), []); const canInitialReview = useCanOperationsAction("monitor:initial-review"); const canFinalReview = useCanOperationsAction("monitor:final-review"); const effectiveReviewStage = grade === "A" ? 2 : reviewStage; const canReview = grade !== "A" && ((reviewStage === 0 && canInitialReview) || (reviewStage === 1 && canFinalReview)); const submitReview = () => { if (!reviewNote.trim()) { toast.error("请填写处理说明"); return; } if (reviewStage === 0) { setReviewStage(1); toast.success(`模型团队初审已提交:${reviewDecision}`); } else { setReviewStage(2); toast.success(`业务团队终审已提交:${reviewDecision}`); } setReviewNote(""); }; useEffect(() => { setReviewStage(0); setReviewDecision("暂不处理"); setReviewNote(""); setSelectedFeature(null); setCompareId(""); }, [modelId]); if (detail.loading) { return
; } if (detail.error) { return

模型监控详情加载失败

{detail.error ?? "模型不存在"}

; } return (
navigate("/operations/monitoring")} actions={(
item.status !== "下线").map((item) => ({ label: `${item.bank} · ${item.name} ${item.version}`, value: item.modelId, }))} onChange={(modelId) => { if (modelId) navigate(`/operations/monitoring/${modelId}`); }} /> item.status !== "下线" && item.modelId !== model.modelId).map((item) => ({ label: `${item.bank} · ${item.modelId}`, value: item.modelId }))} onChange={setCompareId} /> {range === "custom" && ( <> )}
)} /> ① 监控结论 {monitorMonth ? `${monitorMonth} 监控周期 · 结果优先展示` : "暂无监控结果"}
{[ ["监控结果等级", grade], ["排序性", model.ranking], ["KS", `${model.ks.toFixed(2)}%`], ["PSI", `${model.psi.toFixed(2)}%`], ["KS 环比降幅", `${model.ksDrop.toFixed(2)}%`], ["异常等级", abnormal], ].map(([label, value]) => (
{label} {value}
))}
{grade === "A" ? (
本期无异常

各项指标均在阈值内,生成模型监控报告并继续按月监控。

) : (
异常原因({abnormal})

{abnormalReasonOf(model)}

{(model.ks < 30 || model.psi > 50) &&

已达到主动干预条件,请建模团队优先处理。

}
)}
处理进度

{grade === "A" ? "本期无异常,无需进入处理流程。" : effectiveReviewStage === 0 ? "待模型团队初审,初审完成后流转至业务团队终审。" : effectiveReviewStage === 1 ? "模型团队已完成初审,待业务团队终审。" : "本期处理已结案。"}

= 1 ? "bg-success text-white" : "bg-primary text-white"}`}>1 模型团队初审 = 2 ? "bg-success text-white" : effectiveReviewStage === 1 ? "bg-primary text-white" : "bg-muted"}`}>2 业务团队终审
{canReview &&