docs: 查标实现计划 v0.2,确认 C1–C11、SK1–SK2;参考项目查标深读

- m3-plan.md:请 Fable 当顾问深读参考项目的查标实现、对照读标教训复评后的计划。
  先建评测集再写规则(金标准是虚构投标文件的响应清单);每条清单项留带证据的结论;
  成本靠按章节打批、硬预算和复查复用控制;认不出、没查的都写进检查覆盖范围。
- decisions.md:C1–C11、SK1–SK2 全部按建议确认;用户的真实国动投标文件作为留出集,第 3 步冻结代码后再用。
- ref-bid-engine-analysis.md:新增 §6 查标深读;§5.4 的检索方案改为内存 BM25。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
YANG JIANKUAN
2026-11-04 11:48:10 +08:00
parent ad34b0cb23
commit 6ab42959df
3 changed files with 328 additions and 1 deletions

View File

@@ -256,4 +256,46 @@
4. 识别占位符;
5. 提示词的禁止事项和服务端校验一一对应。
- **评测之后做**(涉及文字层数据结构):顶级章节识别并按章节类型分派任务;每页质量指标和扫描件判定;表格按行生成证据单元、带表头(解决两栏表、竖排文字串行)。
- **留到 M3 设计**:检查项来源分层、判定分层、检索不到不调模型、版式规则扫描;必须补投标侧引文逐字校验,pass 必须有校验通过的证据。检索用 PostgreSQL 的 pg_trgm / 全文检索,不自建倒排。
- **留到 M3 设计**:检查项来源分层、判定分层、检索不到不调模型、版式规则扫描;必须补投标侧引文逐字校验,pass 必须有校验通过的证据。~~检索用 PostgreSQL 的 pg_trgm / 全文检索,不自建倒排。~~(2026-11-04 改:句子不进库(DM1),中文全文检索要第三方扩展,改用内存 BM25,见 §6.4 和 `m3-plan.md` §2.2。)
## 6. 查标深读(2026-11-04 补)
> 为 M3 设计再读一次,聚焦 `handler/bidreview/`(18 个文件,约 7100 行)。由 Fable 做顾问走查,关键结论对照源码抽查过(预设清单项、按清单项重复送页面文本、投标侧证据不校验)。只读、未运行。路径相对 `bid-engine-backend/pkg/handler/bidreview/`。采纳情况见 `m3-plan.md`。
### 6.1 规模不同,成本做法不能照搬
它每个项目只有 14 条预设项、最多 24 条否决条款、若干资格字段和评分行、最多 18 条材料清单(`checklist.go:42-59, 133-135, 292-300`),大约 90 条。「技术方案对招标需求全覆盖」只算一条预设项(`checklist.go:57`),技术参数不逐条查。我们的正式清单有 216~1348 条。
### 6.2 值得学的做法
| 做法 | 位置 | 我们怎么用 |
|---|---|---|
| 检查项来源分层,每条有稳定的键;重建清单时只删非人工来源的检查项 | `checklist.go:97-124`;`verdict.go:147-150` | 对应 `match_key`。它的评分项键带序号(`checklist.go:214`),重抽后排序一变键就变,我们用清单项编号 |
| 召回为空就不调模型,直接「需人工确认」;批量结果里漏答的条目默认给 warning,不给 pass | `verdict.go:296-306, 375-394, 498-509` | 每条必答,漏答记 `missing_answer` |
| 判定阶段部分失败不拖垮整体,失败的条目可以单独复检 | `verdict.go:237-245`;`handlers_items.go:122-201` | 失败条目记「未判定(模型错误)」,写进覆盖范围 |
| 所有清单项都召回不到候选时,整个阶段失败并提示检查投标文件 | `pipeline.go:387-389` | `prepare_bid` 之后的健全性检查,按 `input` 失败、不扣点 |
| 版式扫描分强 / 弱证据;没有文字层的页面占比过高时,出「需人工核对」并列出页码 | `format_scan.go:100-176, 235-278` | 「别的项目名称」以本项目名称、编号、采购人为准做强证据;没有文字层的页码写进覆盖范围 |
| 页眉页脚单独落块,版式检查专门扫它 | `parse.go:194-211`;`format_scan.go:178-233` | 我们文字层已把页眉页脚单独成句(SK10),查别的项目名称时重点扫 |
| 报告附一张证据索引:每条证据的文件、页码、引文单独成行 | `excel.go:162-179` | 报告加「证据索引」页 |
| 每个功能的输出上限写死在代码里;模型并发用一个信号量,流水线和人工复检共用 | `llm.go:22-29, 68-91` | `check.match` 固定输出上限;解析和检查共用统一限流 |
### 6.3 需要避坑
| 问题 | 位置 | 对照 |
|---|---|---|
| 报价、签章、截止日都是「把要求写成文字,让模型读投标文件判断」:限价没有确定性的金额比对,签章没有任何图像层面的检查 | `checklist.go:46-50, 102-106`;`verdict.go:254` | 红线 6、7 |
| 投标侧证据不校验:模型给的页码和引文原样入库,页码对不上就挂到第一个召回文件;pass 没有证据也照样 pass;批量判定也一样 | `verdict.go:322-327, 397-402, 428-434, 465-486` | 红线 2 |
| 问题级别由模型定,模型可以把高危项降成低危 | `verdict.go:321, 396, 498-522` | 级别从清单项算 |
| 模型给每条结论写「整改建议」 | `verdict.go:72, 96, 113` | 报价类问题会写出数字(红线 6);我们不写建议 |
| 检索:每块只索引前 240 个词,后半块静默丢失;候选页截到 1400 字;一批里同一页正文给每条清单项各送一遍 | `parse.go:436-438`;`retrieval.go:75`;`verdict.go:345-353` | 召回静默损失,成本翻倍 |
| 投标文件的表格不做结构解析,偏离表、报价表被压成纯文本 | `pipeline.go:273`;`parse.go:113-116` | 偏离表直取靠单元格结构 |
| 解析不出页时把全文每 3000 字切成「伪页」;转 PDF 失败就直接解析原文件 | `parse.go:214-231` | 页码失真;我们按 `input` 失败、不扣点 |
| 输出截断不检测:JSON 修补失败就整批丢掉 | `llm.go:151-157` | 沿用 `near_limit` 和对半重跑 |
| 否决条款最多映射 24 条,超出的静默截掉 | `checklist.go:127-135` | 没查的都写进覆盖范围 |
| 报告用「通过 / 高风险 / 竞争力预估得分」 | `excel.go:18-24, 76`;`scoring.go:77-82` | 红线 3、5 |
| 复检后清单项上的人工确认状态不重置,用户确认的是旧结论(推测) | `verdict.go:411-448`;`handlers_items.go:96-101` | 证据所在分册换了版本时提示 |
| 企业规则的适用条件存了但从不求值,每条规则都进清单 | `checklist.go:237-251` | V1 做规则库时条件要真求值 |
### 6.4 检索方案
保留架构 §6.2 的内存 BM25 + jieba,不用 PostgreSQL 检索:句子放对象存储、不进库(DM1);检索只在 worker 里、一次检查内用,几百页建索引用不了几秒;中文全文检索要装 zhparser 之类的第三方扩展,pg_trgm 没有 IDF;内存索引是纯函数,便于测试和离线评测。它把 2-gram / 3-gram 倒排放进 MySQL 的代价(只索引前 240 词、没有 IDF)就是反例。