Files
bid-doctor/docs/architecture/m3-plan.md
YANG JIANKUAN 08e3fe7eff
Some checks failed
ci / server (push) Has been cancelled
ci / web (push) Has been cancelled
ci / contract (push) Has been cancelled
feat(qualification): 资质库和资质匹配(查标第 4 步,规则集 2026.11-r4)
- 资质库(FR-M6-08):增删改、证书扫描件(PDF/JPG/PNG,企业级密钥加密、签名下载)、
  late_for 按进行中项目的投标截止日提醒、审计;前端资质库页和对话框,侧栏入口
- 资质匹配(FR-M3-05):证书类别词表(16 类,产品证书、人员证书不进匹配),
  QUAL.EXPIRED / LEVEL_LOW / NOT_COVERED;投标文字层按清单项定级,只有资质库记录的降一级,
  出处显示「依据:资质库记录」(查标页、报告模板 v2);一类证书一个问题;评分项不逐条找响应的也查
- 响应错位规则不再把资质表的证书名称格当成照抄的要求
- 评测:qual_defects.py 派生资质情况表,D12 3/3、D18 2/2、规矩的资质表 0 误报;开发集不退化
- 决策 QL1–QL7;方案和落地记录 docs/architecture/qualification-plan.md,评测 check-eval.md §13

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 23:22:50 +08:00

32 KiB
Raw Blame History

查标(M3)实现计划 v0.2(2026-11-04)

范围:FR-M3-01~09,加上查标依赖的 FR-M6-08 最小资质库。 依据:docs/analysis/03-functional-requirements.md、架构 §3.2 / §3.6~§3.9 / §3.12、data-model.md §4.3 / §5.5~§5.6、api-design.md §3~§4、原型 bid-check.html results.html report.html qualifications.html。 v0.2:请 Fable 做顾问,深读参考项目的查标实现(ref-bid-engine-analysis.md §6),对照读标优化的教训(extract-status.md),重新评估了 v0.1 的待定事项。关键结论抽查过源码。

进度

步骤 状态 说明
第 0 步 评测集 试跑完成,继续扩充 规范 v0.2、留出集划分已定,留出集词库和风格 C 已封存;生成、注入、渲染、离线查标、评分工具已写;开发集 7 份招标文件、14 份投标文件第一版跑完,结果和发现见 check-eval.md §2
第 1 步 链路打通 ✅ 2026-11-04 见下方「第 1 步落地记录」
第 2 步 规则 基本完成 响应方式(C12)、文件要在、扫描件兜底、规则检查 rules.py(报价形式、形式要件、响应表)、需人工核对(manual.py、manual-checks 接口)、「报价」类清单项和分项上限(C7)。还差分项和采购需求逐项对照(P13)、散文写法的单价上限。见 check-eval.md §4–§7
第 3 步 AI 响应匹配 进行中 整行上下文、提示词 v2、偏离栏直读、响应错位、复查复用、硬预算;送给模型的同一行按单元格、重复条目只合并显示(C5 修订);致命项重问按数据暂不做。见 check-eval.md §8–§9
第 4 步 资质库 ✅ 2026-11-04 方案和落地记录见 qualification-plan.md(决策 QL1–QL7)。qualification 模块(增删改、附件按企业级密钥加密、late_for 到期提醒、审计)和前端资质库页;check/qualification.py:证书类别词表(16 类)、规则 QUAL.EXPIRED / LEVEL_LOW / NOT_COVERED,投标文字层按清单项定级、只有资质库记录的降一级,规则集 2026.11-r4。评测见 check-eval.md §13
第 5 步 检查报告 ✅ 2026-11-04 report 模块:导出 → worker 用 Gotenberg(Chromium)把 HTML 转成 A4 PDF → 按项目数据密钥加密存对象存储 → report(不可改,存哈希);report_job 记生成过程(迁移 0009),完成推 report.ready、发通知。首页有项目信息、投标文件版本、检查依据、规则版本、导出人、检查覆盖范围、汇总、免责声明;问题明细带两侧出处和处理人;附录是需人工核对的勾选结果;页眉 logo、页脚「本报告为辅助检查结果,仅供投标前自查参考」+ 页码;测试检查模板文字不出现「通过 / 合格 / 保证」。前端「报告」标签页:选项、导出、内嵌预览、历次报告下载。「证据索引」页没有单独做(出处已在问题明细里逐条列出)。同时补了项目状态流转(递交 / 撤销递交 / 归档 / 撤销归档,FR-M6-02):致命问题未处理也能递交,确认框说明;递交后只读,撤销归档回到归档前的状态,都写审计
第 6 步 前端补齐 部分 需人工核对、重复问题折叠、报告页已做;键盘操作、批量处理提示、判断有误反馈还没做

第 1 步落地记录(和本文设计不一致、或留到后面的,记在这里):

  • 后端 check 模块六个阶段都已接上,rules 阶段还是空的;问题由 check_item_result 生成,状态沿用、计费三种方式、健全性检查(投标文件读不出文字按 input 失败、不扣点)都有集成测试。
  • 响应匹配目前只有第三层:检索用内存 BM25,分词先用汉字 2-gram + 「数字 + 单位」+ 英文型号,jieba 留到第 3 步按评测决定;批量先按 8 条清单项一批,按投标章节打批、token 预算、复查复用、重复条目分组、致命项重问留到第 3 步;cache_key、group_key 列已建好、还没写入。
  • 提示词 check.match v1;假模型按字面重合判断,只用来测链路。
  • 投标分册加了 volume_kind 列(迁移 0005),同一分册重新上传按 lineage_id 递增版本;电子投标平台加密格式先只认 .kedt(苏采云)和条目全部加密的压缩包,见过新的再补。
  • 前端:上传投标文件并检查页、查标页(进度、覆盖范围、按级别筛选、逐条处理和行内说明、撤销、原文抽屉、已处理和本次已解决),项目头抽成公共组件,加了「查标」标签页;键盘操作、批量处理、判断有误、需人工核对、导出报告留到第 6 步。
  • 用真实模型(DeepSeek Flash)在虚构的 tender-mini + bid-mini 上走了一遍:漏响应的 ★ 条款查出为致命「未找到响应」,另两条判为已响应且出处核对通过;补上响应的 v2 复查后该问题「本次已解决」。一次检查一次模型调用(输入 1,552 token、输出 147 token)。这只说明链路通了,不代表效果,效果按第 0 步的评测集评。

0. 结论速览

  • 表结构和接口契约大部分已有:check_run finding finding_citation manual_check report qualification 在基线迁移里,openapi.yaml 里查标相关的 20 多个操作都已定义。要做的是 check、rules、report、qualification 四个后端模块、投标文件的解析和结构识别,以及 4 个前端页面。
  • 和 v0.1 相比,三处改动最大:
    1. 先建评测集,再写第一条规则。金标准是构造出来的:虚构投标文件时同时写一份「响应清单」,记下每条要求在哪里响应、故意埋了什么问题,按清单打分,不再用 AI 裁定。
    2. 每条清单项都留一条带证据的结论,「已响应」也不例外。新增 check_item_result 表,问题只为没响应好的条目生成。
    3. 成本靠结构控制,不靠一个上限数字:按投标文件的章节打批,同一段正文只送一次;每次检查有 token 预算,超出预算没查的条目列在报告的「检查覆盖范围」里;24 小时内复查时,没变的条目复用上次结果。
  • 原则:认不出、查不了、没查的,都写进「检查覆盖范围」(coverage),不静默跳过。 读标里章识别静默失效的教训。
  • 分 7 步交付,第 0 步(评测集)在第 2 步(规则)之前完成。
  • §7 是 11 个待确认事项的最终建议(含 SK1、SK2)。

1. 现状盘点

项 已有 要做
表结构 基线迁移 06_check.sql、07_…sql;project.latest_check_run_id open_fatal_count last_charged_check_at ORM 模型;新增 check_item_result(C9);清单快照里的重复组标记(C5)
接口契约 check-preview、check-runs、findings 的 resolve / reopen / bulk-resolve、manual-checks、reports、qualifications、feedback 实现;补「每条清单项的检查结论」的读取接口。改了契约就 pnpm gen:api
文件上传 分片上传、按文件头识别格式;role = bid_volume 能挂到项目上 分册类型和版本;电子投标平台加密格式的识别还没做(C8)
文件解析 解析流水线和 ParseJob 绑在一起(runner.py),阶段函数基本是纯函数 拆出阶段函数给检查批次复用;新增投标侧结构识别 bid_structure
清单 五类清单项,技术参数带 op / value / unit;确认时生成快照 检查前生成 kind = check 快照;「报价」类清单项还没有抽取任务(C7)
额度 预扣 / 结算 / 退回、幂等键、余额行锁 检查的三种计费方式和免扣流水
模型调用 分块并发、截断检测(near_limit)、对半重跑、原因码 并发上限还是每个任务各算各的(extract.py:36 的 TODO)。解析和检查同时跑会把供应商的每分钟请求数打满,查标上线前要做「供应商 × 租户」统一限流
事件流 Valkey stream、Last-Event-ID 续读、resync 加 check_run、report_job 的进度;发布用的客户端设短超时(SK1)
CI PostgreSQL、Valkey 服务容器 加 Gotenberg(投标样例有 Word);数据库角色改为直接跑 init-roles.sh(SK2)
OCR 未做(技术验证 S5) 扫描件先按 C10 兜底;S5 排在 M3 之后
样本 10 份真实招标文件、S01–S10 标注集 没有投标文件(C1)

2. 检查流水线

POST /projects/{id}/check-runs 在同一个事务里:算计费方式 → 预扣(或记免扣)→ 生成清单快照 → 建 check_run → 入队。worker 按六个阶段跑,每个阶段写检查点、开始前清掉本阶段残留的产物,失败后从失败的阶段续跑(架构 §3.4)。

阶段 做什么 产出
prepare_bid 每个投标分册版本跑解析流水线的前五个阶段(格式核对、转换、文字层、OCR、结构识别),不抽取;同一版本只解析一次。然后做投标侧结构识别:偏离表、开标一览表、分项报价表、格式文件(投标函、授权书、声明函)、资格证明附件。表格沿用读标的按单元格切句(F8),偏离表直接从单元格里读。健全性检查:文字层为空或字数过少、全是图片页时,按 input 失败,不扣点、不生成问题 投标侧文字层;结构索引;每页有没有文字层
match 对每条参与检查的清单项找响应,见 §2.1 check_item_result
rules 确定性规则,见 §2.4 规则问题
verify 投标侧三道校验(编号、原文、依据)。没有校验通过的证据,不能判为「已响应」 校验状态和原因码
carry_over 和上一次检查对齐,算出新增 / 沿用 / 本次已解决(FR-M3-08,对齐方式见 C5) presence
summarize 由 check_item_result 生成问题;写 counts、coverage;更新就绪状态和项目状态;汇总本次 ai_call 的 token 和费用;结算额度;发通知 检查完成

结构识别认不出(比如没找到偏离表)时,改用全文检索,并在 coverage 里写明「没有识别出偏离表」。

2.1 响应匹配

一份清单有 216~1348 条正式条目(参考项目每个项目只判 90 条左右,它的成本做法不能当依据)。分三层,能在前一层解决的就不进下一层:

  1. 偏离表直取。偏离表的行和清单项要先对齐:投标人抄过来的要求常被截断、改写、重新编号,所以用去掉编号后的文本相似度、参数名和顺序做确定性对齐,设置信阈值,对不上的行进第三层。MVP 以偏离表为准,「偏离表写无偏离,正文却相反」不查,写进 coverage。
  2. 规则比较。技术参数要求有 op / value / unit 的,用 Decimal 和单位换算比较读到的响应值,覆盖区间、枚举、支持 / 不支持、版本号。响应只写「满足」「响应」、没给具体值的,结论是「未给出具体值」(提示级),不是「疑似不满足」。模型只负责找出响应值在哪,满不满足由规则判断;模型给出的值必须出现在它引用的句子里。★ 参数判为「疑似不满足」时,问题里要同时列出两边的值、单位和比较依据,防止因为单位换算判错出致命误报。
  3. 检索 + 模型判断。见 §2.2。批量方式:
    • 按投标文件的章节打批:一段投标正文只送一次,带上候选句落在这段里的所有清单项。参考项目按清单项打批,同一页正文在一批里重复送好几遍,是反例。
    • 每条清单项都必须有结论,漏答的记原因码 missing_answer,结论记「需人工核对」。
    • 模型引用句子编号,不写页码和自由引文,这样三道校验能原样复用。
    • 沿用读标的输出上限、near_limit 截断检测和对半重跑;每批之间检查取消标记;并发走统一限流。
    • 部分批次因为模型的临时错误失败时,不拖垮整个批次:失败的条目记「未判定(模型错误)」,写进 coverage。
    • 致命级条目的证据没通过校验时,允许重问一次:只把模型指的那一页作为候选,提示词不变。重问之后仍不通过,按 C3 处理。
    • 提示词 check.match v1 的例子一律虚构,不取自评测集里的投标文件。

token 预算:每次检查有一个硬预算。超出后剩下的条目按 提示 → 重要 → 致命 的顺序放弃(先放弃提示级),没查的条目在 coverage 里逐条列出。预算按实测定(C2),正常情况下不应触发。

2.2 检索

用内存里的 BM25,保留架构 §6.2 的做法。ref-bid-engine-analysis.md §5.4 写的「用 PostgreSQL 的 pg_trgm / 全文检索」改掉,理由:

  • 句子放对象存储、不进数据库(DM1)。用 PostgreSQL 检索就要为投标文字层另建一张句子表;
  • 检索只在 worker 里、一次检查之内用。一个项目的投标文件最多几百页,在内存里建索引用不了几秒;
  • 中文全文检索要装 zhparser 之类的第三方扩展,托管数据库和私有化部署都麻烦;pg_trgm 没有 IDF;
  • 内存索引是纯函数,单元测试和离线评测都能直接用。

实现要点:

  • 索引单位是带编号的句子和单元格,出处能回链;
  • 分词用 jieba 加字 2-gram / 3-gram,「数字 + 单位」(≥32GB)和英文型号(10GE)保留成整词;
  • 查询按类别构造:技术参数用参数名、值和单位,资格条件用材料名;
  • 给模型的候选按句子窗口取上下文,不送整页;
  • 排序带结构先验:技术参数先找技术偏离表,商务先找商务偏离表,资格先找资格证明分册。分册类型只用来排序,不限制检索范围(C8)。

2.3 问题级别

级别从清单项算出来,不让模型定(参考项目让模型定级别,模型可以把高危项降成低危):

清单项 级别
importance = substantive(★ 条款、法规列明、原文写明无效),或类别含资格条件、无效 / 废标 致命
▲ 参数、评分项缺证明材料 重要
一般参数、其他 提示

问题上带 importance_basis,界面把「法规列明」和「文件写明」的无效分开显示(决策 Q4)。explanation 只写事实和出处,不写整改建议:参考项目让模型给每条结论写建议,用在报价类问题上就会写出报价数字(红线 6)。

2.4 规则(rules 模块)

每条规则都有编号,规则集有版本,写进 check_run.rule_set_version。每条规则都要有单元测试,至少配一个反例(不该报的写法)。

规则组 内容
PRICE.* 超出限价:总价、采购包、分项三层(分项见 C7);开标一览表和分项表的合计是否一致(苏采云范本写明「以开标一览表为准」,定为「重要」,说明里引用原文);大写和小写金额是否一致;是否只给了一个报价。金额只从投标文件读,detail 里存读到的金额、原文写法和出处
FORM.* 格式文件齐不齐;格式文件里该填的地方没填(C4);签章位置有没有图像(C4);落款日期是否合理;出现了别的项目的名称、编号或采购人:以本项目的名称、编号、采购人为准做比对,重点扫页眉页脚(照搬旧标书最常留在那里);泛泛匹配公司名只作弱证据;格式文件的固定文字有没有被改动:依赖招标侧识别出格式章,识别不出就不查,写进 coverage
QUAL.* 证书在投标截止日是否已过期;等级能比较时看够不够;资格条件和评分项要求的材料有没有覆盖
REGIME.* 按法律体系切换术语(「投标无效」/「否决投标」)和无效情形清单;体系识别不出时用中性措辞。招标投标体系没有样本,coverage 里写「规则未经样本验证」

「需人工核对」的条目(份数、装订密封、递交方式、CA 加密)由规则按清单项生成 manual_review,勾选结果按项目存在 manual_check,复查时沿用(P2)。

2.5 检查覆盖范围(coverage)

第 1 步就定下字段,报告首页、查标页顶部都从这里读:

  • 查了哪些分册和版本;哪些页没有文字层(扫描件);
  • 哪些条目没查,以及原因:在待确认区、不在所投的包、超出预算、模型错误、扫描件;
  • 结构识别结果:偏离表、开标一览表、格式文件,哪些认出来了;
  • 法律体系,以及这套体系的规则是否经过样本验证;
  • 清单是否已确认(G7);
  • 规则集版本、提示词版本;本次 token 和费用;证据没通过校验的条目数(按原因码计)。

3. 计费

按 data-model.md §4.3 实现:

情况 check_run.billing 流水
专业版,24 小时窗口外 charged 在解析额度上预扣 −1;成功结算,失败、取消退回;成功后更新 project.last_charged_check_at
专业版,24 小时窗口内 waived_recheck 免扣一行 0,reason = recheck_24h,关联本次检查
免费版 free_plan 不记流水

check-preview 和 POST check-runs 共用同一个计费函数;expected_charge 对不上时返回 409 billing_changed,不扣点。投标文件的解析算在本次检查里,不另外扣点。

复查复用结果:第三层(模型判断)的结论按「清单项内容哈希 + 投标分册版本哈希 + 提示词版本」缓存。没变的条目直接复用上次的 check_item_result,不再调用模型。否则一次扣点可能被拿来跑三五次全量。用户改了清单项,或者换了分册版本,相关条目就会重新判断。

4. 评测集(第 0 步)

没有任何真实投标文件,先自己造。读标的三个坑这样堵:

读标的坑 查标的做法
AI 标注、AI 裁定,结果偏宽 投标文件是按「响应清单」写出来的,问题在构造时就已知,响应清单就是金标准,不需要 AI 裁定
评分脚本照搬抽取规则 评分只对照响应清单,不用检索和偏离表对齐的逻辑来判对错。准确率分两列报:对上响应清单的,和其他
规则照着样本写法调 至少两种写法风格,按招标文件整体留出,开工前选定

做法:

  1. 问题分类表先定:从 05 §5 的 R01–R20、07 F6 / F7 和 7 种问题类型派生,不少于 15 类,每类至少 3 种写法。例如:缺行、值不满足、单位不同、写在错的分册里、偏离表写「无偏离」正文却否定、页眉留着旧项目名、大小写金额不一致、证书过期、该填的地方没填。
  2. 用招标文件自己的格式章写投标文件:投标函、开标一览表、偏离表、声明函的模板,S01–S10 里都有。每份招标文件写两份投标文件,一份「规矩的」、一份「马虎的」;至少两种写法风格,其中一种由另一套提示词或另一个人来写。
  3. 响应清单:每条清单项记响应在哪(分册、页码 + 片段),以及怎么响应的(偏离表的行、正文句子、附件);没响应的记问题类型。
  4. 留出集:按招标文件整体划分,开工前选定,其中至少包括 S06(有分项限价)或 S10(多包、有单价上限)中的一份。
  5. 裁定政策:开发集上查出了响应清单里没有的问题,由人裁定一次,再改响应清单。投标文件里的问题是客观可查的,这是唯一允许「看过输出再改标注」的情况。留出集只记录,不改。
  6. 人工复核只看一件事:写出来的投标文件里,有没有无意中留下的问题(占位符没填、复制错位)。
  7. 指标:致命级问题的召回率(漏报比误报严重);问题准确率;证据没通过校验的问题占致命问题的比例(目标 < 10%,否则会成为查标的「待确认区噪音」);每次检查的 token 和费用。
  8. 按真实招标文件写出的投标文件,和招标文件一样不进 git。

5. 分步交付

每一步都要通过后端检查和前端检查(AGENTS.md「工程」一节);新接口都要有「A 企业的令牌访问 B 企业的资源,返回 404」的用例。

第 0 步:评测集

按 §4。问题分类表、响应清单格式、留出集划分,在写第一条规则之前定稿。投标文件可以和第 1 步并行写。

第 1 步:链路打通(假模型)

  • 投标分册:类型和版本;加密格式识别(C8)。
  • 拆 ParseJob 的阶段函数,prepare_bid 复用;健全性检查。
  • check 模块:check-preview、发起 / 取消 / 查询检查、六个阶段的骨架、计费三个分支、状态沿用、check_item_result(C9)、coverage 字段(§2.5)。
  • 处理问题:resolve(忽略要写原因;致命问题「确认无误」也要写说明,同时写一条 feedback)、reopen、只能批量处理「提示」级、只能处理最近一次检查的问题;同一个事务里更新 open_fatal_count。沿用「已确认无误」的致命问题时,如果证据所在的分册换了版本,标出「证据所在文件已更新」。
  • SK1、SK2 的补丁:Valkey 发布客户端设短超时,SSE 连接池设上限;CI 加 Gotenberg、直接跑 init-roles.sh;架构 §8 改掉 testcontainers 的写法。
  • 前端:上传投标文件和发起检查页、查标页的最小版本。

验收:在虚构样例上走完「上传投标文件 → 检查 → 处理问题 → 复查后状态沿用」;扣点、免扣、免费版、失败退回、409 billing_changed、健全性检查失败不扣点,都有测试。

第 2 步:投标侧结构识别和规则

  • 读标新增「响应方式」抽取(C12):类别 × 方式 × 出处,规则优先;查标按响应方式决定「未找到响应」报不报、报什么级别。

  • bid_structure:偏离表、开标一览表、分项报价表、格式文件、资格证明附件;认不出时走兜底,并写进 coverage。

  • rules 模块:§2.4 的四组规则;需人工核对的条目和 manual-checks 接口。

  • 「报价」类清单项(C7)。

  • 只在开发集上调,每条结构规则配一个开发集以外的反例。

第 3 步:AI 响应匹配

  • §2.1 三层、§2.2 检索、check.match 提示词、投标侧三道校验、致命项重问一次。
  • 「供应商 × 租户」统一限流(解决 extract.py:36 的 TODO)。
  • token 预算、复查复用结果、重复条目分组(C5)、扫描件兜底(C10)。
  • 在开发集上调完后冻结代码,留出集跑一次,结果写进 docs/architecture/check-eval.md,按实测定 C2 的元数。

第 4 步:资质库和资质匹配

  • qualification 模块:资质的增删改、附件(企业级数据密钥,DM4);前端资质库页。
  • QUAL.*:过期、等级不足、未覆盖。资格证明是扫描件时,按名称回到资质库里匹配(C10)。资质数据只来自用户自己的资质库和投标文件(红线 5)。

第 5 步:检查报告

  • HTML 模板 → Gotenberg 的 Chromium 转 PDF,嵌入思源宋体、思源黑体;后台生成,完成后发 report.ready。
  • 内容按 FR-M3-09:首页(项目信息、投标文件版本、检查时间、规则版本、检查覆盖范围、免责声明「辅助检查结果,仅供投标前自查参考」)、汇总、问题明细、需人工核对附录。另加一页「证据索引」,每条证据的文件、页码、引文单独成行,方便逐条翻原文。
  • 报告生成后不再改,存哈希;重复导出就生成新报告。CI 检查模板文案,不能出现「通过 / 合格 / 保证」;logo 不当作结论章使用。

第 6 步:前端补齐

  • 查标页:对照布局(招标原文 / 投标原文)、键盘操作(含 U 键撤销)、批量处理、「反馈:判断有误」、需人工核对、coverage 摘要。
  • 读标清单上显示每条清单项的检查结论(「已响应 · 第 N 页」),已响应的也能反馈判断有误。
  • 报告页、项目概览的检查记录、工作台的就绪状态和致命问题数。致命问题没处理完时显示「未就绪」,但不限制导出和递交(红线 4)。

6. 红线对照

红线 落在哪里
1 辅助定位 每条清单项的检查结论都留痕,包括「已响应」;处理问题记录处理人和时间
2 可复核 招标侧出处必有;投标侧出处要过三道校验,没通过的不判「已响应」;「未找到」的写明找过哪些分册、哪些章节;规则问题没有清单项时挂规则编号
3 不用绝对化表述 只称「辅助检查 / 风险提示」;没查的写进覆盖范围;报告模板做 CI 文案检查
4 不阻断用户 就绪状态只是提示
5 不编造 资质只来自资质库和投标文件;不给整改建议
6 报价 只读金额、不生成金额,问题说明里不写建议
7 签章 只提示位置和有无,写明「不鉴定真伪;电子签章可能在投标平台加盖」
9 额度 失败、取消、健全性检查不过都不扣点;免扣也记一行;复查复用结果

7. 待确认事项:最终建议(2026-11-04 全部确认,见 decisions.md)

# 事项 建议
C1 投标文件样本 按 §4 自己写:用招标文件自己的格式章,响应清单就是金标准,按招标文件整体留出,人工只裁定开发集上「响应清单里漏记的问题」。用户有一份真实的国动小程序投标文件,作为留出集里唯一的真实样本:第 3 步冻结代码后再要来跑一次,之前不看、不按它调
C2 单次检查成本 不先定元数。设计时按 token 预算:模型输入总量不超过投标正文字数的 3 倍加上清单文字。另加三条:按投标文件章节打批、每次检查有硬预算且超出部分写进覆盖范围、复查复用结果。第 3 步在 S06 规模的样本上实测,再定元数(一起为 D2 提供依据)
C3 模型说已响应,但证据没通过校验 不判已响应。问题类型记「未找到响应」,级别不降,detail 记证据状态和原因码,说明里写「模型认为第 N 页有响应,引文未能核对」,界面可以按原因码筛。致命项允许重问一次(只给模型指的那一页)。评测单列这类问题的比例,目标是占致命问题的 10% 以内
C4 签章 只提示位置和有无。主要检查是格式文件里该填的地方有没有填:投标人名称、法定代表人或授权代表姓名、落款日期(「年 月 日」)还空着。「(盖单位章)」「(签字)」这类括注在盖章后通常仍然留着,括注本身不算问题。图像检测只看签章位置附近有没有图像,结论只到「提示」。Word 稿也转成了规范化 PDF,两种稿走同一套检查
C5 重复条目 检查时分组:只在同类别、同一个包里,按保守的文本相似度聚组(阈值记成规则,配反例)。问题挂在组里编号最小的清单项上,其他成员的出处作为附加的招标侧出处,detail.group_members 记全组;复查对齐时,组内任一成员对上就算同一个问题。分组结果记在检查快照里,界面显示「此要求出现在 3 处」。读标阶段合并以后再做
C6 是否全查 正式清单全查。没查的写进覆盖范围:待确认区的条数、不在所投包的条目、超出预算的条目
C7 分项限价 补 extract.price,规则优先(「限价 / 最高限价 / 单价不超过」+ 金额 + 所在的表行),模型只做结构化;用户也能在清单里手工新增「报价」类条目。10 份里只有 S06、S10 两份有分项限价或单价上限,不让它卡住总价和采购包两层
C8 投标分册类型 固定选项:整本(默认)/ 资格证明 / 商务 / 技术 / 报价 / 其他,再加一个自填的分册名称。类型只用来排序检索候选和填写「找过哪些分册」,不限制检索范围。电子投标平台的加密格式按文件头和扩展名维护一张表,422 写明是哪个平台的格式
C9 新增:每条清单项的检查结论 新增 check_item_result 表(检查批次 × 清单项:结论、走的哪一层、投标侧出处、校验状态、原因码、token)。「已响应」也是 AI 结论,按红线 1、2 要能复核、能反馈;复查复用结果、覆盖范围也靠它。问题仍然只为没响应好的条目生成。需要新迁移,并同步 data-model.md
C10 新增:扫描件投标文件 真实投标的资格证明、业绩合同多是盖章扫描件,OCR(S5)还没做,不处理的话会出一大片致命级「未找到」。MVP:候选分册里图片页占比超过阈值的条目,不出「未找到响应」,改出「该部分为扫描件,请人工核对」;资格类按名称回到资质库匹配;覆盖范围列出没有文字层的页码。S5 排在 M3 之后
C11 新增:检索方案 内存 BM25 + jieba(§2.2),改掉 ref-bid-engine-analysis.md §5.4 的 pg_trgm 写法
C12 每条要求怎么才算「响应了」(2026-11-04 新增,已按建议确认,decisions.md C12) 招标文件对响应方式的规定按类别不同(evals/check/GUIDELINES.md §2):逐条响应、默认接受(只列偏离)、要证明材料(分投标时和中标后)、文件要在、平台内应答,同一份文件里常有几种,S07 还自相矛盾。建议:① 读标新增「响应方式」抽取,粒度是「类别 × 方式 × 出处」,规则优先、模型兜底,条款原文写了「提供……证明材料」的按条覆盖并记投标时 / 中标后;在清单页显示、可改,作为人工确认点。② 两处写法冲突时按更严的(逐条)处理,问题说明里同时引两处原文。③ 默认接受的要求没找到响应:一般要求记「按招标文件默认接受」、不出问题,结论带出处;实质性 / ★ 要求出「提示」级「请人工核对」,不出致命;正文里找到了不满足的值照常报。④ 证明材料是中标后提供的,投标时没附不报。⑤ 识别不出响应方式时按逐条处理(宁可多报),这一组「未找到响应」标明「招标文件未写明是否要求逐条响应」,写进覆盖范围;项目上给一个「本项目只列偏离」的开关,打开后这一组降为提示
SK1 进度推送用 Valkey stream 确认。补两处:发布用的客户端设短超时(现在 valkey() 没设,Valkey 挂掉时会卡住 worker,而不是只记日志);SSE 连接池设上限
SK2 集成测试连 Compose 的 PostgreSQL 确认。补三处:CI 直接跑 init-roles.sh,不再单独写一份建角色的 SQL;CI 加 Gotenberg;架构 §8 改掉 testcontainers 的写法

8. 读标教训在本计划里的对应

读标的教训 本计划
评测集建得太晚,在同一批样本上既调又评 第 0 步在规则之前;按招标文件整体留出;冻结代码后留出集只跑一次
AI 标注、AI 裁定偏宽 响应清单就是金标准
评分脚本照搬抽取规则 只对照响应清单打分,准确率分两列
提示词例子取自测试文件 check.match 的例子一律虚构
输出临近上限时条目静默丢失 沿用截断检测和对半重跑;每条必答,漏答记原因码
待确认区噪音 证据没通过的问题记原因码、单列指标、可筛选
章识别静默失效 认不出就兜底,并写进覆盖范围
规则只修真正的缺陷 投标侧引文对不上不在附近找句子改挂;致命项重问是重试,不放宽校验