Files
bid-doctor/docs/architecture/addendum-plan.md
YANG JIANKUAN 6e0d4c1dd6
Some checks are pending
ci / server (push) Waiting to run
ci / web (push) Waiting to run
ci / contract (push) Waiting to run
feat(addendum): 补遗追加(FR-M2-10)
- 招标文件解析后可以追加补遗 / 澄清:单独解析、扣 1 次;新条目标「补遗 N」进正式清单,清单回到草稿(AD1)
- 同一种日期、时间不同的旧条目标「已被补遗 N 更新」,逐条确认更新或保留两条(AD2、AD3)
- 项目字段的新值在概览「采用 / 不采用」:采用时以补遗为准、清掉人工值;采用截止时间时日期条目一起确认;
  没确认前查标用旧值,检查预览提示(AD4)
- 补遗不改项目名称、编号、采购人、代理机构、法律体系(更正公告标题会被抽成项目名称);时间比较按北京时间补时区
- 接口:fields/{key}/pending、checklist-items/{id}/supersession;清单项 origin_document、superseded_by;
  关键日期 replaces;解析结果 dates_superseded、fields_pending;检查预览 pending_updates
- 前端:补遗模式的上传页、解析页结果、概览确认、读标页来源筛选和日期确认、检查预览提示
- 决策 AD1–AD6;方案和落地记录 docs/architecture/addendum-plan.md

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 00:27:21 +08:00

12 KiB
Raw Blame History

补遗追加实现方案 v0.1(2026-11-05)

范围:FR-M2-10 补遗追加(简化版):追加的补遗 / 澄清文件单独解析、扣 1 次解析,新条目标注来源并入清单;日期类字段以最新文件为准,在旧值上标「已被补遗 N 更新」,由用户确认;不自动判断条目「新增 / 变更 / 作废」(P1)。 依据:03-functional-requirements.md、02-user-journeys.md J2 第 1 步、01-scope-roles.md(「追加解析 + 来源标注」,影响分析是 P1)、data-model.md §5.2「补遗」、openapi.yaml(/projects/{id}/fields/{key}/pending、/checklist-items/{id}/supersession)、原型 project-new.html?addendum=1 parsing.html?addendum=1 checklist.html project.html 的关键日期卡。 状态:AD1–AD6 已全部按建议确认(2026-11-05,docs/decisions.md)。

0. 结论速览

  • 一半已经有了:后端上传时可以选补遗 / 澄清(编号「补遗 N」「澄清 N」),能单独解析、扣 1 次解析、失败退回;新条目记着来自哪份文件;项目字段的新值和旧值不同时先放进待确认(pending_value),不覆盖。
  • 缺的:日期类条目的「已被补遗 N 更新」关联;两个确认接口(字段新值、日期条目);并入后清单回到草稿;前端的追加入口、解析结果、确认界面、来源筛选。
  • 按 FR 只做日期类的自动关联。原型里「★ 参数原为 480G、补遗改成 960G」那种逐条对照属于「变更判断」,是 P1,这次不做(AD2)。
  • 补遗的新条目直接进正式清单,和招标文件的新旧条目都查:宁可多报,不漏报(AD1)。
  • 没有补遗样本:10 份样本里一份都没有。这次用虚构的更正公告测链路;国动项目如果有更正公告,请一起给我,这是第一份真实补遗。

1. 现状

环节 现在 要补的
上传、编号 role = addendum / clarification,各自从 1 编号 前端入口
解析、扣点 和招标文件同一条流水线,扣 1 次解析,失败、超时、取消退回 解析结果页按补遗写(新增几条、哪些日期有新值)
清单项来源 origin_document_id 指向补遗;但接口的 origin_label 只有「AI」「人工添加」 origin_label 对补遗来的条目写「补遗 1」;读标页按来源筛选;并入后清单回到草稿
项目字段 新值和旧值不同 → pending_value,等确认 确认接口和界面;确认后同步冗余列
日期类条目 没有关联(superseded_by_id 列已建,没写) 并入时关联、确认接口、界面
分包 补遗不改已有的分包 不变

2. 并入时做什么(后端)

补遗解析完、并入清单时,在现有的 checklist.merge 之后多做三件事:

  1. 新条目照常进清单:和招标文件一样按校验结果进正式清单或待确认区,标「补遗 N」。
  2. 关联日期类条目:补遗里每条有具体时间的「日期与递交」条目(投标截止、开标、提疑截止、保证金截止、投标有效期),在更早的文件(招标文件、编号更小的补遗)里找同一种日期、还在正式清单里的条目,时间不同的把旧条目的 superseded_by_id 指向新条目,等用户确认。时间相同的不关联(补遗只是复述)。
  3. 清单回到草稿:已确认的清单改为草稿,历史版本保留,要求重新确认(原型:「补遗 1 并入后待重新确认」)。

解析任务的结果里加:items_added(新增几条)、dates_superseded(几处日期有新值)、fields_pending(几个项目字段有新值),解析页按这三个数写。

3. 确认(后端)

按契约实现两个接口:

  • POST /projects/{id}/fields/{key}/pending(accept: true / false):
    • 采用:补遗的值变成 AI 值(出处换成补遗那一句),清掉人工值(以补遗为准;用户之后还可以再改),冗余列同步;
    • 不采用:丢掉待确认值,保持原值;
    • 采用投标截止时间时,清单里「投标截止」日期条目的更新一起确认(AD3),不用在读标页再点一次;
    • 都写审计(project_field.resolve_pending)。
  • POST /checklist-items/{id}/supersession(accept: true / false,对旧条目调用):
    • 采用:旧条目软删除(可以恢复),新条目保留;
    • 保留两条:解除关联,两条都在,由用户自己处理;
    • 都写审计(checklist_item.resolve_supersession)。

接口返回里补上已有字段:清单项的 superseded_by(新条目编号、来自哪份补遗)、清单概况的 superseded_pending(还有几处没确认)、关键日期的 superseded_pending。

没确认之前查标用旧值(AD4):项目字段的契约就是「确认后才生效」(data-model.md §5.2)。检查预览提示「补遗 1 带来的新值还有 N 处没确认」,不阻止发起检查(红线 4)。确认后截止时间变了,已有的检查结果按 PI5 提示重新检查。

4. 前端

  • 追加入口:概览页右上角和读标页清单栏各放一个「追加补遗 / 澄清」,进新建项目页的补遗模式(原型 project-new.html?addendum=1)。补遗模式下不填项目名称,文件类型选补遗或澄清答复,说明写「补遗单独解析,新条目标注『补遗 N』并入清单。标博士不判断哪些旧条目已作废,并入后由你核对」,按钮旁写扣 1 次解析。招标文件还没解析完时不显示入口(AD5)。
  • 解析页:标题「解析补遗 1」;完成后写「新增 N 条 · M 处日期有新值 · K 个项目字段有新值 · 清单回到草稿,核对后重新确认」,下一步「核对补遗」进读标页、只看这份补遗的条目。
  • 概览页:
    • 项目要点里有待确认值的字段写「补遗 1:新值 → 原值」,带出处,给「采用 / 不采用」;
    • 关键日期卡:被更新、还没确认的日期显示「已被补遗 1 更新,待确认」;确认后显示新值并注「已按补遗 1 更新 · 原为 11月18日 09:30」(原型 project.html)。
  • 读标页:
    • 来源筛选:全部 / 招标文件 / 补遗 1 / ……,每项带条数;
    • 被更新的日期条目写「已被补遗 1 更新:新值 ……」,给「确认更新(删除旧条目)」和「保留两条」;
    • 并入后顶部提示「补遗 1 新增 N 条。标博士不判断哪些旧条目被它改了或作废了,请核对:改了的旧条目可以修改或删除」,带「只看补遗 1」。
  • 查标页:检查预览里有没确认的新值时出上面那条提示。

5. 测试

  • 虚构一份更正公告样例(server/tests/fixtures/addendum-mini.pdf,内容全虚构):改投标截止时间、开标时间,新增一条 ★ 参数,复述一条没改的日期。
  • 集成测试:追加 → 解析 → 扣 1 次 → 新条目标「补遗 1」、清单回到草稿、截止时间进待确认、日期条目关联(复述的不关联);字段采用 / 不采用;采用截止时间时日期条目一起确认;日期条目采用(旧条目软删除、可以恢复)/ 保留两条;检查预览的提示;只读项目 409。
  • 假模型能不能从样例里抽出日期要看它的规则;抽不出时测试里用接口建条目。浏览器里走一遍追加到确认的全程。
  • 用真实模型对这份虚构样例跑一次解析看看抽取结果,花费几分钱(AD6)。

6. 工作量和顺序

  1. 后端:并入时关联日期、清单回到草稿、两个确认接口、接口字段补齐、检查预览提示,以及测试样例和测试。约半天。
  2. 前端:补遗模式的上传页、解析页的补遗结果、概览页的确认、读标页的来源筛选和日期确认。约半天。

7. 待确认事项

编号 事项 建议
AD1 补遗的新条目进正式清单还是待确认区 按招标文件同一套规则进正式清单(没出处、校验没过的照样进待确认区),标「补遗 N」,读标页给「只看补遗 N」和核对提示,清单回到草稿要求重新确认。新旧条目都在、都查,宁可多报,不漏报;全放待确认区的话,用户不逐条采纳就查不到补遗的新要求
AD2 哪些条目自动标「已被补遗更新」 只做日期类(同一种日期、时间不同),按 FR。原型里参数类「原为 480G → 现为 960G」的逐条对照是变更判断,P1;先等真实补遗样本看写法
AD3 怎么确认 字段在概览页逐个「采用 / 不采用」,日期条目在读标页逐条确认;采用投标截止时间时清单里的「投标截止」条目一起确认。采用时清掉人工值,以补遗为准
AD4 没确认之前查标用哪个值 旧值,确认后生效(数据模型原定);检查预览提示还有几处没确认,不阻止检查
AD5 招标文件没解析完能不能追加补遗 不能,界面上招标文件解析成功后才显示入口。没有招标文件时补遗的日期没有可关联的旧值
AD6 用真实模型对虚构样例跑一次解析 跑一次(DeepSeek,几分钱),看抽取对补遗写法(「原为……现更正为……」)的表现;结果只记录,这次不按它调提示词

8. 落地记录(2026-11-05)

  • 后端:checklist/addendum.py(并入时关联日期条目、确认 / 保留两条、采用截止时间时一起确认「投标截止」条目);project/edit.resolve_pending(采用时补遗的值变成 AI 值、出处换成补遗那一句、清掉人工值);清单项接口补 origin_document、superseded_by,origin_label 对补遗来的条目写「补遗 N」;清单概况 superseded_pending;关键日期 replaces(「已按补遗 1 更新 · 原为……」);解析结果 dates_superseded、fields_pending;检查预览 pending_updates。
  • 补遗不改项目是谁:解析补遗时项目名称、编号、采购人、代理机构、法律体系不进待确认。测试里发现,更正公告的标题会被抽成「项目名称」,和招标文件的不同,每份补遗都会冒出一条没意义的待确认。
  • 时间比较按北京时间补时区:模型给的时间有时带时区、有时不带,补遗复述原时间时不能误判成「有新值」。
  • ORM 给 JSONB 列写 None 存的是 JSON null,不是 SQL NULL,SQL 里数待确认值时要排除 JSON null。
  • 前端:补遗模式的上传页(选补遗 / 澄清答复)、解析页的补遗结果和「核对补遗」、概览页「追加补遗 / 澄清」和字段「采用 / 不采用」、关键日期的待确认和「已按补遗 1 更新」、读标页的来源筛选、核对提示、「只看日期更新」和逐条确认、条目上的「补遗 1」标签、检查预览的提示。
  • AD6 结果:用 DeepSeek Flash 对虚构的更正公告(推后递交截止时间、新增一条 ★ 参数)跑了一次解析(6 次调用):两处改动都抽对了,★ 参数归为技术参数 + 无效条款、新截止时间 12 月 8 日,出处都是「补遗 1 · 第 1 页」,没有多抽。只是一份很规整的虚构样例,「原为……现更正为……」那种写法这份样例里没有,真实补遗到了再看。
  • 浏览器里走过:追加补遗 → 解析结果 → 概览采用新截止时间(项目头、要点、关键日期一起变,旧的「投标截止」条目自动删除)→ 读标页只看补遗 1。