From 47c6a1258ec65b73f21e3b5a6221680dd33891f7 Mon Sep 17 00:00:00 2001 From: YANG JIANKUAN Date: Wed, 4 Nov 2026 15:53:45 +0800 Subject: [PATCH] =?UTF-8?q?feat(check):=20=E6=96=87=E4=BB=B6=E8=A6=81?= =?UTF-8?q?=E5=9C=A8=E3=80=81=E6=89=AB=E6=8F=8F=E4=BB=B6=E5=85=9C=E5=BA=95?= =?UTF-8?q?=EF=BC=88C10=EF=BC=89=EF=BC=9B=E5=93=8D=E5=BA=94=E6=96=B9?= =?UTF-8?q?=E5=BC=8F=E5=AD=98=E8=BF=9B=E6=B8=85=E5=8D=95=E5=B9=B6=E5=8F=AF?= =?UTF-8?q?=E6=94=B9=EF=BC=88C12=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - check/presence.py:「提供《授权委托书》」「须提交投标函」这类要求按标题里有没有这个文件名判断,不调模型。 - 扫描件兜底:要附证明材料的要求没找到、投标文件里有扫描件页时,改为提示级「可能在扫描件里,请人工核对」。 - 响应方式解析时识别、带依据句子存进 checklist.response_modes(迁移 0008);清单页显示、可改(写审计, 重新解析不覆盖人工改过的);查标用存下来的。新接口 PUT /projects/{id}/checklist/response-modes/{category}。 - check_item_result.layer 加 presence(迁移 0007);契约同步默认接受的两种结论。 开发集 17 份:规矩投标文件的致命问题 120 → 51,致命级召回不变(22 / 32)(docs/architecture/check-eval.md §5)。 Co-Authored-By: Claude Opus 5.5 --- AGENTS.md | 3 +- apps/web/src/pages/ChecklistPage.vue | 71 +++++++++++++++- docs/architecture/check-eval.md | 22 ++++- docs/architecture/openapi.yaml | 58 ++++++++++++- evals/check/report.py | 1 + evals/check/run_check.py | 11 ++- evals/check/score.py | 9 ++- packages/api-client/src/schema.d.ts | 81 ++++++++++++++++++- .../versions/0007_check_layer_presence.py | 27 +++++++ .../versions/0008_checklist_response_modes.py | 22 +++++ server/src/bid_doctor/check/findings.py | 8 +- server/src/bid_doctor/check/match.py | 24 ++++++ server/src/bid_doctor/check/presence.py | 31 +++++++ server/src/bid_doctor/check/runner.py | 15 +++- server/src/bid_doctor/checklist/api.py | 19 ++++- server/src/bid_doctor/checklist/models.py | 1 + .../src/bid_doctor/checklist/response_mode.py | 73 ++++++++++++++++- server/src/bid_doctor/checklist/service.py | 17 ++++ .../bid_doctor/document/pipeline/runner.py | 4 +- server/tests/test_check_flow.py | 21 +++++ server/tests/test_check_units.py | 35 ++++++++ 21 files changed, 529 insertions(+), 24 deletions(-) create mode 100644 server/migrations/versions/0007_check_layer_presence.py create mode 100644 server/migrations/versions/0008_checklist_response_modes.py create mode 100644 server/src/bid_doctor/check/presence.py 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));