diff --git a/AGENTS.md b/AGENTS.md index d720fa6..c8a6e3b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -222,4 +222,5 @@ samples/ - 2026-11-04:查标第 5 步检查报告:导出 → 后台用 Gotenberg 生成 A4 PDF(首页覆盖范围和免责声明、问题明细、需人工核对附录,页眉 logo、页脚免责一句和页码)→ 加密归档,可重复下载;前端加「报告」标签页(选项、导出、内嵌预览、历次下载)。补了项目状态流转(递交 / 撤销递交 / 归档 / 撤销归档,致命未处理也能递交,递交后只读,都留痕)。产品可以从上传招标文件一路走到导出报告、标记递交。见 `docs/architecture/m3-plan.md` 进度。 - 2026-11-04:查标第 4 步资质库和资质匹配(`docs/architecture/qualification-plan.md`,QL1–QL7 全部按建议确认)。资质库页可增删改、挂证书扫描件(企业级密钥加密),到期按进行中项目的投标截止日提醒;查标按证书类别词表(16 类,产品证书、人员证书不进匹配)核对有效期、等级、有没有覆盖,投标文字层按清单项定级,只有资质库记录的降一级、出处显示「依据:资质库记录」,规则集 2026.11-r4。开发集不退化;派生的资质情况表上 D12 3 / 3、D18 2 / 2、规矩的资质表 0 误报,但表和词表是对着同一批样本写的,泛化要等国动真实文件(`check-eval.md` §13)。 - 2026-11-04:项目基本信息修改和分包选择(FR-M2-03、04,`docs/architecture/project-info-plan.md`,PI1–PI7 全部按建议确认)。新增「概览」标签页(项目要点可改、带出处、可恢复 AI 值;采购包;关键日期)和选包页(手工调整、一键「不分包」);清单默认只列通用 + 所投的包;检查记下当时用的截止时间、限价、所投的包、法律体系,报告按它写,之后改了信息查标页提示重新检查。样本里分包识别对的只有 S10(S06 把分项认成 5 个包,S01、S08 单包写了包号),改进分包抽取记进读标专项。 -- 下一步:国动真实投标文件(用户晚些提供,到时先读标、请用户看清单,再查标一次;请用户按公司真实证书录资质库);其余可做:补遗追加(FR-M2-10,含补遗带来的新值确认)、第 6 步前端补齐(键盘操作、批量处理、判断有误反馈、日期选择器的日历弹层、概览页的检查记录和文件卡)、M6 剩下的 P0(额度流水、套餐开通、数据删除、运营后台)。 +- 2026-11-05:补遗追加(FR-M2-10,`docs/architecture/addendum-plan.md`,AD1–AD6 全部按建议确认)。招标文件解析后可以追加补遗 / 澄清(单独解析、扣 1 次),新条目标「补遗 N」进正式清单、清单回到草稿;同一种日期时间不同的旧条目标「已被补遗 N 更新」,项目字段的新值在概览「采用 / 不采用」,采用截止时间时日期条目一起确认,没确认前查标用旧值、检查预览提示。补遗不改项目名称、编号、采购人等(更正公告标题会被抽成项目名称)。真实模型对虚构更正公告抽得对;还没有真实补遗样本。 +- 下一步:国动真实投标文件(用户晚些提供,到时先读标、请用户看清单,再查标一次;请用户按公司真实证书录资质库;有更正公告一起要);其余可做:第 6 步前端补齐(键盘操作、批量处理、判断有误反馈、日期选择器的日历弹层、概览页的检查记录和文件卡)、M6 剩下的 P0(额度流水、套餐开通、数据删除、运营后台)。 diff --git a/apps/web/src/components/ChecklistItemRow.vue b/apps/web/src/components/ChecklistItemRow.vue index 90980f0..5f97a15 100644 --- a/apps/web/src/components/ChecklistItemRow.vue +++ b/apps/web/src/components/ChecklistItemRow.vue @@ -33,12 +33,14 @@ const facts = computed(() => specFacts(props.item.spec));
{{ item.title }}
AI - {{ item.origin_label || (item.origin === "human" ? "人工添加" : "规则") }} + {{ item.origin === "human" ? "人工添加" : "规则" }} + {{ item.origin_document.label }} {{ category(c) }} {{ item.lot_no ? `采购包 ${item.lot_no}` : "通用" }} {{ [pendingReason(item.pending_reason), failureText(item.ai_values)].filter(Boolean).join(" · ") }} 人工确认、无出处 已修改 + 已被{{ item.superseded_by.document_label }} 更新,待确认

{{ f }} diff --git a/apps/web/src/pages/BidCheckPage.vue b/apps/web/src/pages/BidCheckPage.vue index ef2a52b..874df2f 100644 --- a/apps/web/src/pages/BidCheckPage.vue +++ b/apps/web/src/pages/BidCheckPage.vue @@ -213,6 +213,12 @@ async function start() { 本次查:通用条目 + {{ project.data.value.bidding_lots.map((n) => `采购包 ${n}`).join("、") }} 更改所投的包

+ +
+ +

补遗带来的新值还有 {{ pv.pending_updates }} 处没确认,这次检查按原来的值。可以先在概览和读标页确认。

+ 去确认 +
diff --git a/apps/web/src/pages/ChecklistPage.vue b/apps/web/src/pages/ChecklistPage.vue index 65a859f..413e79f 100644 --- a/apps/web/src/pages/ChecklistPage.vue +++ b/apps/web/src/pages/ChecklistPage.vue @@ -11,7 +11,7 @@ import { unwrap } from "@bid-doctor/api-client"; import { FileText, Info, Plus, Search } from "@lucide/vue"; import { useQuery, useQueryClient } from "@tanstack/vue-query"; import { computed, onBeforeUnmount, onMounted, ref, watch } from "vue"; -import { RouterLink } from "vue-router"; +import { RouterLink, useRoute } from "vue-router"; import ChecklistItemRow from "@/components/ChecklistItemRow.vue"; import CitationBlock from "@/components/CitationBlock.vue"; import ConfirmDialog from "@/components/ConfirmDialog.vue"; @@ -58,13 +58,35 @@ type Cat = "all" | "pending" | "deleted" | ChecklistItem["categories"][number]; const cat = ref("all"); const kw = ref(""); const starOnly = ref(false); +/* 补遗(FR-M2-10):按来源文件筛选(解析页「核对补遗」带 ?source=<文档>),只看待确认的日期更新 */ +const route = useRoute(); +const source = ref(typeof route.query.source === "string" ? route.query.source : ""); +const supOnly = ref(false); +const sources = computed(() => { + const m = new Map(); + for (const i of formal.value) { + const d = i.origin_document; + if (!d) continue; + const e = m.get(d.id) ?? { id: d.id, label: d.label, n: 0 }; + e.n++; + m.set(d.id, e); + } + return [...m.values()].sort((a, b) => (a.label === "招标文件" ? -1 : b.label === "招标文件" ? 1 : a.label.localeCompare(b.label, "zh-CN"))); +}); +const addenda = computed(() => sources.value.filter((x) => x.label !== "招标文件")); +const supPending = (i: ChecklistItem) => !!i.superseded_by && !i.superseded_by.confirmed; +const supCount = computed(() => formal.value.filter(supPending).length); const deletedQ = useDeletedItems(() => props.projectId, () => cat.value === "deleted"); const deleted = computed(() => deletedQ.data.value ?? []); const catCount = (c: string) => formal.value.filter((i) => i.categories.includes(c as ChecklistItem["categories"][number])).length; const cats = computed(() => CATEGORY_ORDER.filter((c) => catCount(c) > 0)); -const match = (i: ChecklistItem) => (!starOnly.value || i.importance === "substantive") && (!kw.value || i.title.includes(kw.value.trim())); +const match = (i: ChecklistItem) => + (!starOnly.value || i.importance === "substantive") && + (!kw.value || i.title.includes(kw.value.trim())) && + (!source.value || i.origin_document?.id === source.value) && + (!supOnly.value || supPending(i)); /** 「全部」视图下每条只出现一次,放在它的第一个分类里(一条要求可以属于多个分类) */ const groups = computed(() => { @@ -79,7 +101,7 @@ const groups = computed(() => { .filter((g) => g.rows.length); }); const listMode = computed(() => (cat.value === "pending" || cat.value === "deleted" ? cat.value : "formal")); -const showTop = computed(() => cat.value === "all" && !kw.value && !starOnly.value); +const showTop = computed(() => cat.value === "all" && !kw.value && !starOnly.value && !source.value && !supOnly.value); const showPending = computed(() => (cat.value === "all" && showTop.value) || cat.value === "pending"); /** 当前视图里自上而下的条目(J / K 用) */ const visible = computed(() => { @@ -88,6 +110,19 @@ const visible = computed(() => { return cat.value === "pending" ? top : [...top, ...groups.value.flatMap((g) => g.rows)]; }); +/** 确认「已被补遗 N 更新」:采用时旧条目删除(可以恢复),保留两条时解除关联(AD3) */ +async function resolveSupersession(i: ChecklistItem, accept: boolean) { + try { + await unwrap(api.POST("/checklist-items/{item_id}/supersession", { params: { path: { item_id: i.id } }, body: { accept } })); + void qc.invalidateQueries({ queryKey: qk.checklistItems(props.projectId) }); + void qc.invalidateQueries({ queryKey: qk.checklist(props.projectId) }); + void qc.invalidateQueries({ queryKey: qk.project(props.projectId) }); + toast(accept ? "已按补遗更新,旧条目已删除(可以在「已删除」里恢复)" : "两条都保留,请自己核对"); + } catch (e) { + toast(errorTitle(e), { ok: false }); + } +} + /* ---------- 标注规则(如「打 ★ 的为实质性要求」)和数量核对 ---------- */ function ruleText(r: MarkingRule): string { const mk = r.marker === "keyword" ? `「${r.marker_text ?? ""}」字样` : `「${r.marker_text || MARKER[r.marker]}」`; @@ -289,11 +324,21 @@ onBeforeUnmount(() => window.removeEventListener("keydown", onKey));
+ 追加补遗
+
+ +

+ {{ addenda.map((x) => `${x.label} 带来 ${x.n} 条`).join(",") }}。标博士不判断哪些旧条目被补遗改了或作废了,请核对:改了的旧条目可以修改或删除。 +

+ + +
+
采购包