diff --git a/AGENTS.md b/AGENTS.md index 6e0d5b1..30e1661 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -212,4 +212,5 @@ samples/ - 2026-11-04:用户抽查第二批复核 28 条,全部同意(两批合计 45 条)。 - 2026-11-04:查标第一轮改进:候选句带同一行的单元格、提示词 check.match v2。开发集 17 份上召回 30% → 50%,带标志词的问题 2% → 54%,误报略降,输入 token +44%;漏响应和响应对象错位变差(`docs/architecture/check-eval.md` §3)。评测集修了待修项、加了 3 份低密度投标文件,第三批复核 55 条、用户抽查 11 条全部同意。 - 2026-11-04:查标第二轮:评测集纠错(漏响应、错位的「变差」多是生成器只改了一处);新增响应方式识别(C12,规则优先)和「这条要不要投标人回答」的判断。开发集 17 份上规矩投标文件报出的问题 639 → 229,误报 21.3 → 12.4 / 100,召回 68% → 65%,致命级 20 → 21 / 32;证明材料缺失几乎查不出(扫描件)。第四批复核 45 条,用户抽查 9 条全部同意(四批共 65 条)。见 `docs/architecture/check-eval.md` §4。 -- 下一步:扫描件兜底和文件要在(C10);响应方式存进清单并可改;查标第 2 步的投标侧结构识别和规则。 +- 2026-11-04:查标第三轮:文件要在(按标题找文件名,不调模型)、扫描件兜底(C10,没找到的证明材料改为提示级「可能在扫描件里,请人工核对」)、响应方式存进清单并在清单页显示、可改(C12 的人工确认点,迁移 0007、0008)。开发集上规矩投标文件的致命问题 120 → 51,致命级召回不变(22 / 32);证明材料缺失仍查不出,要靠 OCR / 多模态(S5)。见 `docs/architecture/check-eval.md` §5。 +- 下一步:查标第 2 步的投标侧结构识别和规则检查(报价形式、形式要件)。 diff --git a/apps/web/src/pages/ChecklistPage.vue b/apps/web/src/pages/ChecklistPage.vue index e0c9880..1f28740 100644 --- a/apps/web/src/pages/ChecklistPage.vue +++ b/apps/web/src/pages/ChecklistPage.vue @@ -9,7 +9,7 @@ */ import { unwrap } from "@bid-doctor/api-client"; import { FileText, Info, Plus, Search } from "@lucide/vue"; -import { useQuery } from "@tanstack/vue-query"; +import { useQuery, useQueryClient } from "@tanstack/vue-query"; import { computed, onBeforeUnmount, onMounted, ref, watch } from "vue"; import { RouterLink } from "vue-router"; import ChecklistItemRow from "@/components/ChecklistItemRow.vue"; @@ -23,6 +23,7 @@ import { api, errorTitle } from "@/lib/api"; import { useChecklistOps, useDeletedItems, type ItemFields } from "@/lib/checklistOps"; import { groupCitations, type CitationGroup } from "@/lib/citations"; import { useCrumbs } from "@/lib/crumbs"; +import { qk } from "@/lib/keys"; import { category, CATEGORY_ORDER, fmtDateTime, MARKER, projectStatus } from "@/lib/labels"; import { useChecklist, useChecklistItems, useProject, type ChecklistItem, type Citation, type MarkingRule } from "@/lib/queries"; import { toast } from "@/lib/toast"; @@ -108,6 +109,29 @@ function openCitation(c: Citation, tail: Citation[] = []) { target.value = { id: c.id, page: c.page_no, quads: c.quads, sha: c.normalized_sha256, more }; } const openGroup = (g: CitationGroup) => openCitation(g.head, g.tail); + +/* ---------- 响应方式(C12):招标文件要求逐条响应,还是只列偏离、没列出的视为接受;查标按它判没找到响应算不算问题 ---------- */ +const qc = useQueryClient(); +const MODE_CATEGORY: Record = { technical: "技术要求", commercial: "商务要求" }; +const MODE_STATUS: Record = { unknown: "招标文件没写明,按逐条响应处理", conflict: "招标文件两处写法不一致,按逐条响应处理" }; +const modeBusy = ref(""); +async function setMode(category: "technical" | "commercial", mode: "explicit" | "implicit") { + modeBusy.value = category; + try { + await unwrap( + api.PUT("/projects/{project_id}/checklist/response-modes/{category}", { + params: { path: { project_id: props.projectId, category } }, + body: { mode }, + }), + ); + await qc.invalidateQueries({ queryKey: qk.checklist(props.projectId) }); + toast(`已改为「${mode === "implicit" ? "只列偏离,没列出的视为接受" : "逐条响应"}」,下次检查按这个判断`); + } catch (e) { + toast(errorTitle(e), { ok: false }); + } finally { + modeBusy.value = ""; + } +} function select(i: ChecklistItem, opt: { scroll?: boolean } = {}) { if (picking.value && picking.value.item.id !== i.id) cancelPick(); selId.value = i.id; @@ -266,6 +290,26 @@ onBeforeUnmount(() => window.removeEventListener("keydown", onKey)); +
+ 响应方式 +
+ + + + {{ m.set_by?.name }}改过 + {{ MODE_STATUS[m.status] }} +
+
+

{{ w.message }}

@@ -423,6 +467,31 @@ onBeforeUnmount(() => window.removeEventListener("keydown", onKey));