import { useMemo, useState } from "react"; import { Download, FileCheck2, Save, Send, TriangleAlert } from "lucide-react"; import { useNavigate, useSearchParams } 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 { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "~/components/ui/table"; import { FilterSelect, MetricLineChart, OperationsPageHeader, SortingComboChart } from "./OperationsUi"; import { FEATURE_METRICS, MONITOR_MONTHS, SORTING_DISTRIBUTION, modelTrend } from "./modelData"; import { useOperationsData } from "./OperationsDataContext"; import { useCanOperationsAction } from "./operationsRole"; import { latestReports, type ReportStatus, type ReportType } from "./reportData"; import { useReportStore } from "./reportStore"; type Filters = { bank: string; name: string; version: string; modelId: string }; function unique(values: string[]): string[] { return [...new Set(values)].sort((left, right) => left.localeCompare(right, "zh-CN")); } function statusClass(status: ReportStatus): string { if (status === "已发送业务团队") return "bg-success-soft text-success-strong"; if (status === "编辑中") return "bg-brand-soft text-primary"; return "bg-warning-soft text-warning"; } function ReportTypeBadge({ type }: { type: ReportType }) { return {type}; } function ScoreDistributionTable() { return ( 分数区间总账户数账户占比账户累计占比暂无评分分布数据
); } export default function ReportPage() { const navigate = useNavigate(); const { models } = useOperationsData(); const [searchParams] = useSearchParams(); const requestedId = searchParams.get("report"); const allReports = useReportStore((state) => state.reports); const updateReport = useReportStore((state) => state.updateReport); const reports = requestedId && allReports.some((report) => report.reportId === requestedId) ? allReports : latestReports(allReports); const [filters, setFilters] = useState({ bank: "", name: "", version: "", modelId: "" }); const [selectedId, setSelectedId] = useState(requestedId ?? reports[0]?.reportId ?? ""); const pool = reports.filter((report) => ( (!filters.bank || report.bank === filters.bank) && (!filters.name || report.modelName === filters.name) && (!filters.version || report.version === filters.version) && (!filters.modelId || report.modelId === filters.modelId) )); const report = pool.find((item) => item.reportId === selectedId) ?? pool[0] ?? null; const model = report ? models.find((item) => item.modelId === report.modelId) ?? null : null; const isHistorical = Boolean(report && report.monitorMonth !== "2026-07"); const ksTrend = model ? modelTrend(model, "ks") : []; const psiTrend = model ? modelTrend(model, "psi") : []; const canEdit = !isHistorical && useCanOperationsAction("report:edit"); const canExport = useCanOperationsAction("report:export"); const canSend = !isHistorical && useCanOperationsAction("report:send"); const updateFilter = (key: K, value: Filters[K]) => { setFilters((current) => ({ ...current, [key]: value })); }; const reportOptions = useMemo(() => pool.map((item) => ({ label: `${item.bank} · ${item.modelName} ${item.version} · ${item.monitorMonth} · ${item.type}`, value: item.reportId, })), [pool]); const printReport = () => { document.body.classList.add("printing-operations-report"); window.print(); window.setTimeout(() => document.body.classList.remove("printing-operations-report"), 300); }; return (
{canExport && }{canEdit && }{canSend && report.status !== "已发送业务团队" && }} /> 报告筛选{isHistorical ? "历史报告只读查看" : "本页默认仅保留最新月份;历史报告请进入历史报告汇总"}
item.bank))} onChange={(value) => updateFilter("bank", value)} /> item.modelName))} onChange={(value) => updateFilter("name", value)} /> item.version))} onChange={(value) => updateFilter("version", value)} /> item.modelId))} onChange={(value) => updateFilter("modelId", value)} />
{report && model ? ( <> {report.unreadDays >= 5 && report.status !== "已发送业务团队" &&
阅读催办

该报告已超过 {report.unreadDays} 个工作日未阅读,系统已发送催办短信。

} {report.bank} · {report.modelName} · {report.version}信用卡申请评分模型{report.type}{report.status}{report.synced && 已同步}

一、申请评分模型{report.type === "诊断报告" ? "诊断" : "监控"}说明

申请评分模型使用行内存量数据验证区分能力与稳定性。风险区分度采用具有完整表现期的核准账户样本,评分分布和稳定性采用本期申请有评分账户进行验证。

二、{report.modelName}申请评分模型效果验证

{[ ["验证样本", "—"], ["坏样本", "—"], ["KS", `${model.ks.toFixed(2)}%`], ["PSI", `${model.psi.toFixed(2)}%`], ].map(([label, value]) =>
{label}{value}
)}

(一)本期申请评分分布

(二)模型评分排序能力

{model.ranking === "相符" ? "随着评分增加,坏账户占比逐渐降低,模型排序能力表现稳定。" : "随着评分增加,坏账户占比未保持单调下降,中段评分区间出现反升,建议结合特征层面进一步排查。"}

(三)评分风险区分度验证KS 分档线 40%,干预线 30% (四)评分稳定性验证PSI 使用滚动基准期
{report.type === "诊断报告" &&

(五)入模特征诊断

特征IV(当期)IV 降幅CSI(当期)CSI 升幅诊断结论{FEATURE_METRICS.map((feature) => {feature.name}{feature.key}{feature.iv.toFixed(3)}-{feature.ivDrop}%{feature.csi.toFixed(3)}+{feature.csiRise}pp{feature.csiRise > 2 ? "分布迁移明显,建议核查上游数据口径" : "分布基本稳定"})}
}

三、总结

本次模型{report.type === "诊断报告" ? "诊断" : "监控"}显示:模型 KS 为 {model.ks.toFixed(2)}%,PSI 为 {model.psi.toFixed(2)}%;{model.ranking === "相符" ? "排序性保持稳定。" : "排序性存在反转,建议启动模型微调或重构评估。"}

报告中的指标由平台程序取数计算,大模型仅负责文字表述与归因;正文不展示监控结果等级。

) : 当前筛选条件下没有报告}
); }