From 5beff9af07c7febbc70d461c69e00b8f0307dc6b Mon Sep 17 00:00:00 2001 From: yuqei Date: Thu, 21 May 2026 20:54:46 +0800 Subject: [PATCH] feat: add quality evaluation center --- frontend/src/app/quality-evals/page.tsx | 472 ++++++++++++++++++ frontend/src/components/layout/app-topbar.tsx | 2 + ...µ·草与审查助手-当前工作进度保存-2026-05-11.md | 32 +- ...法律文书能力吸收开发方案-代码对齐检查-2026-05-21.md | 54 +- 4 files changed, 532 insertions(+), 28 deletions(-) create mode 100644 frontend/src/app/quality-evals/page.tsx diff --git a/frontend/src/app/quality-evals/page.tsx b/frontend/src/app/quality-evals/page.tsx new file mode 100644 index 00000000..6a7fd930 --- /dev/null +++ b/frontend/src/app/quality-evals/page.tsx @@ -0,0 +1,472 @@ +"use client"; + +import { + CheckCircleOutlined, + CloseCircleOutlined, + ClockCircleOutlined, + ExperimentOutlined, + FileSearchOutlined, + LinkOutlined, + PlayCircleOutlined, + ReloadOutlined, +} from "@ant-design/icons"; +import { Alert, Button, Card, Empty, Progress, Select, Space, Statistic, Table, Tag, Typography, message } from "antd"; +import type { ColumnsType } from "antd/es/table"; +import Link from "next/link"; +import { useCallback, useEffect, useMemo, useState } from "react"; + +import { getPromptAssetDetail, listPromptAssets, runPromptAssetEvaluation } from "@/services/prompt-asset"; +import { useAuthStore } from "@/stores/auth.store"; +import type { PromptAssetDetailResponse, PromptAssetEvaluationRunItem, PromptAssetItem, PromptAssetType } from "@/types/api"; + +const { Paragraph, Text, Title } = Typography; + +const promptTypeOptions: Array<{ value: PromptAssetType | "all"; label: string }> = [ + { value: "all", label: "全部能力" }, + { value: "contract_generation", label: "合同/文书生成" }, + { value: "contract_review", label: "合同自动审查" }, + { value: "clause_recommendation", label: "条款推荐" }, + { value: "legal_chat", label: "法规问答" }, + { value: "wording_rewrite", label: "措辞改写" }, + { value: "regulation_impact", label: "法规影响分析" }, +]; + +const evaluationStatusOptions = [ + { value: "all", label: "全部评测状态" }, + { value: "passed", label: "评测通过" }, + { value: "failed", label: "评测失败" }, + { value: "pending", label: "等待评测" }, + { value: "not_evaluated", label: "尚未评测" }, + { value: "blocked", label: "发布受阻" }, +]; + +const statusOrder: Record = { + failed: 0, + pending: 1, + not_evaluated: 2, + passed: 3, +}; + +function formatDateTime(value?: string | null) { + return value ? new Date(value).toLocaleString("zh-CN", { hour12: false }) : "-"; +} + +function getPromptTypeLabel(value?: string | null) { + return promptTypeOptions.find((item) => item.value === value)?.label ?? value ?? "-"; +} + +function getEvaluationLabel(value?: string | null) { + if (value === "passed") return "评测通过"; + if (value === "failed") return "评测失败"; + if (value === "pending") return "等待评测"; + return "尚未评测"; +} + +function getEvaluationTag(value?: string | null) { + if (value === "passed") return 评测通过; + if (value === "failed") return 评测失败; + if (value === "pending") return 等待评测; + return 尚未评测; +} + +function getPublishTag(item: PromptAssetItem) { + if (item.status === "published") return 已发布; + if (item.latest_evaluation_status === "passed") return 可发布; + return 发布受阻; +} + +function getEvaluationModeLabel(report?: Record | null) { + const mode = typeof report?.evaluation_mode === "string" ? report.evaluation_mode : ""; + if (mode === "llm") return "逐条大模型"; + if (mode === "local_fallback") return "本地兜底"; + if (mode === "llm_required_not_configured") return "模型未配置"; + if (mode === "local") return "本地规则"; + return "未记录"; +} + +function getReportNumber(report: Record | null | undefined, key: string) { + const value = report?.[key]; + return typeof value === "number" && Number.isFinite(value) ? value : null; +} + +function getSampleCount(report?: Record | null) { + const sampleCount = getReportNumber(report, "sample_count"); + if (sampleCount != null) return sampleCount; + const sampleResults = report?.sample_results; + return Array.isArray(sampleResults) ? sampleResults.length : 0; +} + +function getPassedSamples(report?: Record | null) { + const passed = getReportNumber(report, "passed_samples"); + if (passed != null) return passed; + const sampleResults = report?.sample_results; + return Array.isArray(sampleResults) ? sampleResults.filter((item) => Boolean((item as { passed?: unknown }).passed)).length : 0; +} + +function getPassRateFromAsset(item: PromptAssetItem) { + const sampleCount = getSampleCount(item.latest_evaluation_report); + if (sampleCount > 0) { + return Math.round((getPassedSamples(item.latest_evaluation_report) / sampleCount) * 100); + } + const score = item.latest_evaluation_score; + if (typeof score === "number" && Number.isFinite(score)) { + return Math.max(0, Math.min(100, Math.round(score))); + } + return 0; +} + +function sortByRisk(items: PromptAssetItem[]) { + return [...items].sort((left, right) => { + const leftOrder = statusOrder[left.latest_evaluation_status ?? "not_evaluated"] ?? 2; + const rightOrder = statusOrder[right.latest_evaluation_status ?? "not_evaluated"] ?? 2; + if (leftOrder !== rightOrder) return leftOrder - rightOrder; + return new Date(right.updated_at).getTime() - new Date(left.updated_at).getTime(); + }); +} + +export default function QualityEvalsPage() { + const [messageApi, contextHolder] = message.useMessage(); + const currentUser = useAuthStore((state) => state.currentUser); + const [loading, setLoading] = useState(true); + const [runningId, setRunningId] = useState(null); + const [items, setItems] = useState([]); + const [promptTypeFilter, setPromptTypeFilter] = useState("all"); + const [evaluationStatusFilter, setEvaluationStatusFilter] = useState("all"); + const [selectedDetail, setSelectedDetail] = useState(null); + + const isAdmin = currentUser?.role === "admin"; + + const loadAssets = useCallback(async () => { + if (!isAdmin) { + setLoading(false); + return; + } + setLoading(true); + try { + const first = await listPromptAssets(1, 100, promptTypeFilter, "all"); + if (first.code !== 0) { + messageApi.error(first.message || "质量评测数据加载失败"); + return; + } + const allItems = [...first.data.items]; + const totalPages = Math.ceil(first.data.total / first.data.page_size); + for (let page = 2; page <= totalPages; page += 1) { + const next = await listPromptAssets(page, 100, promptTypeFilter, "all"); + if (next.code === 0) { + allItems.push(...next.data.items); + } + } + setItems(sortByRisk(allItems)); + } catch (error) { + messageApi.error(error instanceof Error ? error.message : "质量评测数据加载失败"); + } finally { + setLoading(false); + } + }, [isAdmin, messageApi, promptTypeFilter]); + + const loadDetail = useCallback( + async (assetId: string) => { + try { + const result = await getPromptAssetDetail(assetId); + if (result.code !== 0) { + messageApi.error(result.message || "评测历史加载失败"); + return null; + } + setSelectedDetail(result.data); + return result.data; + } catch (error) { + messageApi.error(error instanceof Error ? error.message : "评测历史加载失败"); + return null; + } + }, + [messageApi], + ); + + useEffect(() => { + void loadAssets(); + }, [loadAssets]); + + const filteredItems = useMemo(() => { + if (evaluationStatusFilter === "all") return items; + if (evaluationStatusFilter === "blocked") { + return items.filter((item) => item.status !== "published" && item.latest_evaluation_status !== "passed"); + } + return items.filter((item) => (item.latest_evaluation_status ?? "not_evaluated") === evaluationStatusFilter); + }, [evaluationStatusFilter, items]); + + useEffect(() => { + if (!selectedDetail && filteredItems[0]) { + void loadDetail(filteredItems[0].id); + } + if (selectedDetail && !filteredItems.some((item) => item.id === selectedDetail.asset.id)) { + setSelectedDetail(null); + } + }, [filteredItems, loadDetail, selectedDetail]); + + const summary = useMemo(() => { + const total = items.length; + const passed = items.filter((item) => item.latest_evaluation_status === "passed").length; + const failed = items.filter((item) => item.latest_evaluation_status === "failed").length; + const pending = items.filter((item) => item.latest_evaluation_status === "pending").length; + const notEvaluated = items.filter((item) => !item.latest_evaluation_status || item.latest_evaluation_status === "not_evaluated").length; + const publishBlocked = items.filter((item) => item.status !== "published" && item.latest_evaluation_status !== "passed").length; + const totalSamples = items.reduce((sum, item) => sum + getSampleCount(item.latest_evaluation_report), 0); + const passedSamples = items.reduce((sum, item) => sum + getPassedSamples(item.latest_evaluation_report), 0); + return { + total, + passed, + failed, + pending, + notEvaluated, + publishBlocked, + samplePassRate: totalSamples > 0 ? Math.round((passedSamples / totalSamples) * 100) : 0, + }; + }, [items]); + + const handleRunEvaluation = async (item: PromptAssetItem) => { + setRunningId(item.id); + try { + const result = await runPromptAssetEvaluation(item.id); + if (result.code !== 0) { + messageApi.error(result.message || "运行评测失败"); + return; + } + messageApi.success(`评测完成:${getEvaluationLabel(result.data.version.evaluation_status)}`); + await loadAssets(); + await loadDetail(item.id); + } catch (error) { + messageApi.error(error instanceof Error ? error.message : "运行评测失败"); + } finally { + setRunningId(null); + } + }; + + const columns: ColumnsType = [ + { + title: "评测对象", + key: "asset", + width: 260, + render: (_, item) => ( + + {item.name} + {item.prompt_key} + {getPromptTypeLabel(item.prompt_type)} + + ), + }, + { + title: "质量门禁", + key: "gate", + width: 160, + render: (_, item) => ( + + {getEvaluationTag(item.latest_evaluation_status)} + {getPublishTag(item)} + + ), + }, + { + title: "样本通过率", + key: "passRate", + width: 180, + render: (_, item) => ( + + + + {getPassedSamples(item.latest_evaluation_report)}/{getSampleCount(item.latest_evaluation_report)} 条样本 + + + ), + }, + { + title: "执行方式", + key: "mode", + width: 130, + render: (_, item) => {getEvaluationModeLabel(item.latest_evaluation_report)}, + }, + { + title: "当前版本", + key: "version", + width: 130, + render: (_, item) => ( + + {item.current_version_label} + {item.version_count} 个版本 + + ), + }, + { + title: "更新时间", + dataIndex: "updated_at", + key: "updated_at", + width: 180, + render: (value: string) => formatDateTime(value), + }, + { + title: "操作", + key: "actions", + width: 260, + render: (_, item) => ( + + + + + + ), + }, + ]; + + const evaluationRunColumns: ColumnsType = [ + { + title: "套件", + dataIndex: "suite_key", + key: "suite_key", + width: 180, + }, + { + title: "状态", + key: "status", + width: 120, + render: (_, item) => getEvaluationTag(item.status), + }, + { + title: "样本", + key: "samples", + width: 120, + render: (_, item) => `${item.passed_samples}/${item.sample_count}`, + }, + { + title: "通过率", + key: "pass_rate", + width: 140, + render: (_, item) => `${Math.round((item.pass_rate ?? 0) * 100)}%`, + }, + { + title: "分数", + key: "quality_score", + width: 100, + render: (_, item) => item.quality_score ?? "-", + }, + { + title: "执行方式", + key: "mode", + width: 150, + render: (_, item) => {getEvaluationModeLabel(item.report)}, + }, + { + title: "失败原因", + dataIndex: "failure_reason", + key: "failure_reason", + ellipsis: true, + render: (value: string | null) => value ?? "-", + }, + { + title: "完成时间", + dataIndex: "finished_at", + key: "finished_at", + width: 180, + render: (value: string | null) => formatDateTime(value), + }, + ]; + + if (!isAdmin) { + return ( +
+ {contextHolder} + + + +
+ ); + } + + return ( +
+ {contextHolder} + +
+
+ 质量评测中心 + 黄金样本评测与发布门禁 + + 汇总生成、审查、法规问答、条款推荐、措辞改写和法规影响分析的 Prompt 评测结果,把原来分散在 Prompt 治理里的质量状态变成独立入口。 + +
+ + Phase15-F + + 发布前质量门禁 + + + 未通过评测的 Prompt 不能发布;已发布 Prompt 可在治理页继续回滚、下线和查看运行证据。 + + +
+ +
+ + } /> + + + } valueStyle={{ color: "#237804" }} /> + + + } valueStyle={{ color: "#a8071a" }} /> + + + } valueStyle={{ color: "#ad6800" }} /> + + + } /> + +
+ + + + + + + + + + }} + pagination={{ pageSize: 10, showSizeChanger: true }} + rowKey="id" + /> + + + + + {selectedDetail ? ( + + +
+ + ) : ( + + )} + + + + ); +} diff --git a/frontend/src/components/layout/app-topbar.tsx b/frontend/src/components/layout/app-topbar.tsx index cbd0513f..48ef81b9 100644 --- a/frontend/src/components/layout/app-topbar.tsx +++ b/frontend/src/components/layout/app-topbar.tsx @@ -10,6 +10,7 @@ import { DashboardOutlined, DeploymentUnitOutlined, DownOutlined, + ExperimentOutlined, FileDoneOutlined, FileTextOutlined, FileSearchOutlined, @@ -87,6 +88,7 @@ const adminNavItems = [ { href: "/templates", label: "模板管理", icon: }, { href: "/knowledge-bases", label: "知识库管理", icon: }, { href: "/prompt-assets", label: "Prompt 治理", icon: }, + { href: "/quality-evals", label: "质量评测", icon: }, { href: "/legal-change-impacts", label: "法规影响分析", icon: }, { href: "/operation-kpis", label: "运营 KPI", icon: }, { href: "/sales-followups", label: "销售跟进", icon: }, diff --git a/project-docs/AI合同起草与审查助手-当前工作进度保存-2026-05-11.md b/project-docs/AI合同起草与审查助手-当前工作进度保存-2026-05-11.md index 65d103e6..1742da68 100644 --- a/project-docs/AI合同起草与审查助手-当前工作进度保存-2026-05-11.md +++ b/project-docs/AI合同起草与审查助手-当前工作进度保存-2026-05-11.md @@ -4211,7 +4211,7 @@ - 本轮核对结论: - Phase15-A 到 Phase15-H 的主线开发已经在代码层闭环。 - `/clauses/apply` 未单独实现,但条款推荐和改写结果已经通过 `/documents/[id]/edit` 编辑器写入合同正文并生成 `generated_contract_versions`,属于等价闭环。 - - `/quality-evals` 未单独实现,当前评测入口集中在 `/prompt-assets`,已能支撑 Prompt 发布前评测门禁;如要做统一质量中心,后续可单独补页面和通用评测表。 + - `/quality-evals` 在本轮核对当时尚未单独实现,评测入口集中在 `/prompt-assets`;该缺口已在后续“Phase15-F 独立质量评测中心 `/quality-evals` 本地闭环”中补齐。 - 法规影响分析代码统一使用 `/legal-change-impacts`、`legal_change_events`、`legal_change_impacts` 命名,后续文档应以此为准。 - 本轮验证: - `$env:PYTHONPATH='backend'; python -m unittest backend.tests.test_legal_document_type_service backend.tests.test_legal_document_types_api backend.tests.test_legal_document_element_schema_service backend.tests.test_legal_document_element_schemas_api backend.tests.test_legal_document_draft_service backend.tests.test_legal_document_drafts_api backend.tests.test_contracts_api backend.tests.test_review_risk_action_service backend.tests.test_review_risk_actions_api backend.tests.test_clause_recommendation_service backend.tests.test_clauses_api backend.tests.test_prompt_asset_service backend.tests.test_prompt_assets_api backend.tests.test_prompt_runtime_service backend.tests.test_legal_change_impact_service backend.tests.test_legal_change_impacts_api backend.tests.test_operation_kpi_service backend.tests.test_operation_kpis_api` @@ -4223,7 +4223,7 @@ - 剩余边界: - 当前仍有 Phase15-C、Phase15-F、Phase15-G 相关未提交改动,需要后续统一整理提交。 - 最新代码尚未部署到 `192.168.3.90` 做真实库烟测。 - - 专业富文本库、字段级 diff、独立 `/quality-evals`、全量法规向量化、真实微信或支付宝支付、电子签章和小程序均属于后续增强,不是当前法律文书能力吸收主线阻塞。 + - 专业富文本库、字段级 diff、全量法规向量化、真实微信或支付宝支付、电子签章和小程序均属于后续增强,不是当前法律文书能力吸收主线阻塞;独立 `/quality-evals` 已在后续闭环中补齐。 ## 2026-05-21 Phase15-C/F/G 提交推送与 3.90 真实库烟测闭环 @@ -4270,6 +4270,32 @@ - 远程真实 PostgreSQL 已在 `20260521_0038 (head)`。 - 法规影响 worker 已在测试服务器常驻运行,并通过真实库临时事件消费烟测。 - 剩余边界: - - 本轮没有做 `/quality-evals` 独立质量中心。 + - 本轮当时没有做 `/quality-evals` 独立质量中心;该项已在后续“Phase15-F 独立质量评测中心 `/quality-evals` 本地闭环”中补齐。 - 本轮没有做全量法规向量化。 - 本轮没有接入真实微信或支付宝支付。 + +## 2026-05-21 Phase15-F 独立质量评测中心 `/quality-evals` 本地闭环 + +- 已按《AI合同起草与审查助手-法律文书能力吸收开发方案-2026-05-20.md》补齐文档提到的独立质量评测产品入口。 +- 本轮新增前端页面: + - `frontend/src/app/quality-evals/page.tsx` + - 管理员菜单新增“质量评测”,入口位于 `frontend/src/components/layout/app-topbar.tsx`。 +- 当前实现口径: + - 不新增重复的质量评测后端模型,继续以 `prompt_assets`、`prompt_asset_versions`、`prompt_asset_evaluation_runs` 作为黄金样本评测真相源。 + - `/quality-evals` 负责聚合生成、审查、法规问答、条款推荐、措辞改写、法规影响分析等 Prompt 类型的评测状态。 + - 页面支持按能力类型、评测状态、发布受阻筛选。 + - 页面展示评测对象、质量门禁、样本通过率、执行方式、当前版本、更新时间和评测历史。 + - 页面可直接触发 Prompt 黄金样本评测,并可跳转 `/prompt-assets` 做发布、下线、回滚和运行证据治理。 +- 已更新代码对齐检查报告: + - `project-docs/AI合同起草与审查助手-法律文书能力吸收开发方案-代码对齐检查-2026-05-21.md` + - 旧结论“没有独立 `/quality-evals` 页面”已改为“独立入口已闭环”。 +- 本地验证: + - `cd frontend; npx eslint "src/app/quality-evals/page.tsx" "src/components/layout/app-topbar.tsx"` + - `cd frontend; npm run build` + - 构建结果:`Compiled successfully`,静态页面生成 `40/40`,路由清单包含 `/quality-evals`。 +- 当前闭环结论: + - 文档提到的“独立 `/quality-evals` 页面”已经完成本地开发闭环。 + - Phase15-F 现在形成“Prompt 治理评测真相源 -> `/quality-evals` 独立质量入口 -> 运行评测 -> 查看历史 -> 发布门禁 -> 回到 Prompt 治理”的产品入口闭环。 +- 剩余边界: + - 本轮暂未新增通用 `ai_quality_eval_runs/items` 表;该表只在未来要把非 Prompt 质量评测也做成独立后端领域时再补。 + - 下一步需要提交推送,并部署到 `192.168.3.90` 后访问 `/quality-evals` 做前端路由烟测。 diff --git a/project-docs/AI合同起草与审查助手-法律文书能力吸收开发方案-代码对齐检查-2026-05-21.md b/project-docs/AI合同起草与审查助手-法律文书能力吸收开发方案-代码对齐检查-2026-05-21.md index 834e2e95..1b0859eb 100644 --- a/project-docs/AI合同起草与审查助手-法律文书能力吸收开发方案-代码对齐检查-2026-05-21.md +++ b/project-docs/AI合同起草与审查助手-法律文书能力吸收开发方案-代码对齐检查-2026-05-21.md @@ -15,11 +15,11 @@ 当前代码已经覆盖 Phase15-A 到 Phase15-H 的主线开发闭环。也就是说,文书类型入口、动态要素表单、草稿保存恢复、生成接口升级、审查建议采纳、条款推荐与行业惯例、富文本编辑器和 AI 侧边栏、Prompt 治理、黄金样本评测、法规影响扫描、运营 KPI 与商业化增强,均已在代码层形成可运行闭环。 -但仍有三类事项需要分开看: +截至本轮独立质量评测中心收口后,仍有三类事项需要分开看: -1. 已开发但待提交或待部署验证:当前工作区存在 Phase15-C、Phase15-F、Phase15-G 的未提交改动,且最新 worker 和语义扫描未在 `192.168.3.90` 做远程部署烟测。 -2. 文档建议路径与代码等价实现不同:例如文档建议 `/quality-evals` 和 `/clauses/apply`,当前分别落在 `/prompt-assets` 评测历史和合同编辑器版本链中。 -3. 后续增强项:专业富文本库、字段级差异对比、独立质量评测中心、全量法规向量化、真实微信或支付宝支付、电子签章、小程序等不属于当前 Phase15 主线硬阻塞。 +1. 本轮新增 `/quality-evals` 独立页面,不新增数据库迁移,继续复用 Prompt 评测运行记录作为质量评测真相源。 +2. 文档建议路径与代码仍保留一个等价实现差异:文档建议 `/clauses/apply`,当前落在合同编辑器版本链中;`/quality-evals` 已补成独立产品入口。 +3. 后续增强项:专业富文本库、字段级差异对比、通用 `ai_quality_eval_runs/items`、全量法规向量化、真实微信或支付宝支付、电子签章、小程序等不属于当前 Phase15 主线硬阻塞。 ## 三、逐阶段对齐表 @@ -39,7 +39,7 @@ | LAW-CLAUSE-002 | 措辞优化 | 支持专业化、简化、偏甲方、偏乙方、均衡等改写模式,返回法律边界和谈判建议 | 已闭环 | | Phase15-D | 富文本编辑器与 AI 侧边栏 | `/documents/[id]/edit` 支持正文编辑、自动保存、版本历史、条款推荐、措辞改写、相关法条和 Chat 入口 | 主线已闭环 | | Phase15-E | Prompt 资产治理 | `/prompt-assets` 支持 Prompt 类型、版本、评测、发布、下线、回滚和运行证据 | 已闭环 | -| Phase15-F | 黄金样本与质量评测 | 已有 Prompt 评测运行记录、逐条黄金样本报告、发布前门禁和前端评测历史 | 已闭环 | +| Phase15-F | 黄金样本与质量评测 | 已有 Prompt 评测运行记录、逐条黄金样本报告、发布前门禁、`/prompt-assets` 治理入口和 `/quality-evals` 独立质量评测中心 | 已闭环 | | Phase15-G | 法规变更影响分析 | 已有法条变更入队、结构化和语义影响扫描、影响项、页面、后台 worker 和 Docker 服务 | 已闭环 | | Phase15-H | 运营 KPI 与商业化增强 | `/operation-kpis` 已展示用量、文书生成、审查、Chat、模板、条款采纳、试用和转化指标;计费、发票状态、销售跟进已有业务闭环 | 主线已闭环 | @@ -83,6 +83,7 @@ - 合同编辑器和 AI 侧边栏:`frontend/src/app/documents/[id]/edit/page.tsx` - 审查建议处理:`frontend/src/app/reviews/[id]/page.tsx` - Prompt 治理:`frontend/src/app/prompt-assets/page.tsx` +- 质量评测中心:`frontend/src/app/quality-evals/page.tsx` - 法规影响分析:`frontend/src/app/legal-change-impacts/page.tsx` - 运营 KPI:`frontend/src/app/operation-kpis/page.tsx` - 销售跟进:`frontend/src/app/sales-followups/page.tsx` @@ -108,14 +109,15 @@ ### 5.2 `/quality-evals` -文档建议新增独立 `/quality-evals` 页面。当前代码没有这个独立路由,质量评测落在 `/prompt-assets` 中: +文档建议新增独立 `/quality-evals` 页面。本轮已经补齐独立路由,并保持评测数据真相源不重复: -- Prompt 详情页可以运行评测。 -- 评测结果回填当前版本。 -- 发布按钮受评测状态约束。 -- 评测运行历史可以追溯。 +- `/quality-evals` 聚合生成、审查、法规问答、条款推荐、措辞改写、法规影响分析等 Prompt 类型的评测状态。 +- 页面支持按 Prompt 类型、评测状态和“发布受阻”筛选。 +- 页面展示评测对象、质量门禁、样本通过率、执行方式、版本、更新时间和评测历史。 +- 页面可直接触发 `POST /api/v1/prompt-assets/{asset_id}/evaluations/run` 重新运行黄金样本评测。 +- 页面保留跳转 `/prompt-assets` 的治理入口,发布、下线、回滚和运行证据仍由 Prompt 治理承接。 -这对“Prompt 发布前黄金样本评测门禁”已经闭环。但如果要做跨生成、审查、Chat、条款推荐、法规影响的统一质量中心,仍建议后续补独立 `/quality-evals`。 +因此,“文档要求独立质量评测产品入口”已经闭环;后续只有在需要把非 Prompt 质量评测也沉淀为独立领域模型时,才需要另补通用 `ai_quality_eval_runs/items` 表。 ### 5.3 `regulation-changes` @@ -131,19 +133,20 @@ ### 6.1 待提交和待部署验证 -当前工作区存在未提交改动,主要集中在: +Phase15-C、Phase15-F、Phase15-G 已完成提交、推送和 `192.168.3.90` 真实库烟测,详见当前工作进度保存文档。 -- Phase15-C 行业惯例推荐。 -- Phase15-F 真实黄金样本逐条评测。 -- Phase15-G 法规影响语义扫描和后台 worker。 - -这些代码本地测试已通过,但尚未提交远程 Git,也未在 `192.168.3.90` 重新部署后做真实库烟测。 +本轮新增 `/quality-evals` 不涉及 Alembic 迁移或后端服务变更。完成提交后,只需要部署前端并访问 `/quality-evals` 做页面烟测。 ### 6.2 独立质量评测中心 -当前评测能力已经在 Prompt 治理内闭环,但没有独立 `/quality-evals` 页面,也没有通用 `ai_quality_eval_runs/items` 表承接所有质量评测类型。 +独立 `/quality-evals` 页面已经补齐,当前闭环采用“独立页面 + 复用 Prompt 评测真相源”的方式: -这不是 Prompt 发布门禁阻塞项,但属于文档和产品形态的一个剩余增强点。 +- 前端入口:`frontend/src/app/quality-evals/page.tsx`。 +- 菜单入口:管理员菜单“质量评测”。 +- 数据来源:`prompt_assets`、`prompt_asset_versions`、`prompt_asset_evaluation_runs`。 +- 执行动作:复用 Prompt 资产评测接口重新运行黄金样本评测。 + +当前仍未单独新增通用 `ai_quality_eval_runs/items` 表。这个表属于后续“跨 Prompt、跨业务对象的统一质量评测域”增强,不影响 Phase15-F 的产品入口闭环。 ### 6.3 专业级富文本增强 @@ -194,7 +197,7 @@ OK 命令: ```powershell -npx eslint "src/app/documents/new/page.tsx" "src/app/documents/new/[type]/page.tsx" "src/app/documents/[id]/edit/page.tsx" "src/app/prompt-assets/page.tsx" "src/app/legal-change-impacts/page.tsx" "src/app/operation-kpis/page.tsx" "src/components/layout/app-topbar.tsx" "src/types/api.ts" +npx eslint "src/app/documents/new/page.tsx" "src/app/documents/new/[type]/page.tsx" "src/app/documents/[id]/edit/page.tsx" "src/app/prompt-assets/page.tsx" "src/app/quality-evals/page.tsx" "src/app/legal-change-impacts/page.tsx" "src/app/operation-kpis/page.tsx" "src/components/layout/app-topbar.tsx" "src/types/api.ts" ``` 结果:通过,无错误输出。 @@ -211,7 +214,7 @@ npm run build ```text Compiled successfully -Generating static pages (39/39) +Generating static pages (40/40) ``` 构建路由中已包含: @@ -220,6 +223,7 @@ Generating static pages (39/39) - `/documents/new/[type]` - `/documents/[id]/edit` - `/prompt-assets` +- `/quality-evals` - `/legal-change-impacts` - `/operation-kpis` - `/sales-followups` @@ -228,9 +232,9 @@ Generating static pages (39/39) 为了让文档和代码彻底闭合,建议下一步不要继续堆新功能,先按这个顺序收口: -1. 整理并提交当前 Phase15-C、Phase15-F、Phase15-G 的代码和文档改动。 -2. 如果本阶段要求“文档路径和产品入口完全一致”,补独立 `/quality-evals` 质量评测中心,把 `/prompt-assets` 中的评测历史聚合过去。 +1. 提交并推送本轮 `/quality-evals` 页面和文档更新。 +2. 部署前端到 `192.168.3.90`,访问 `/quality-evals` 做页面烟测。 3. 统一文档命名,把法规影响分析的接口名称固定为 `/legal-change-impacts`,避免后续误认为还缺 `/regulation-changes`。 -4. 等决定部署时,在 `192.168.3.90` 执行最新 Alembic,并启用 `legal-change-impact-worker` 服务做真实库烟测。 +4. 如未来要做更宽的统一质量平台,再补通用 `ai_quality_eval_runs/items` 和非 Prompt 质量评测对象。 -按当前代码状态判断,《法律文书能力吸收开发方案》的主线开发没有跑偏,已经基本完成。真正剩下的是提交、部署验证、独立质量中心和若干生产级增强。 +按当前代码状态判断,《法律文书能力吸收开发方案》的主线开发没有跑偏,已经基本完成。`/quality-evals` 独立入口补齐后,真正剩下的是若干生产级增强,而不是 Phase15 主线功能缺口。