feat(check): 文件要在、扫描件兜底(C10);响应方式存进清单并可改(C12)

- 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 <noreply@anthropic.com>
This commit is contained in:
YANG JIANKUAN
2026-11-04 15:53:45 +08:00
parent cb0c2fcafe
commit 47c6a1258e
21 changed files with 529 additions and 24 deletions

View File

@@ -18,7 +18,7 @@ from sqlalchemy import select
from bid_doctor.ai.providers.base import TransientError
from bid_doctor.billing import service as billing
from bid_doctor.checklist import extract
from bid_doctor.checklist import extract, response_mode
from bid_doctor.checklist import service as checklist
from bid_doctor.core import events
from bid_doctor.core.db import TenantContext, tenant_tx
@@ -138,6 +138,8 @@ async def _stages(ctx: TenantContext, job_id: UUID) -> dict[str, Any]:
d = (await s.execute(select(SourceDocument).where(SourceDocument.id == doc.id))).scalar_one()
lots = await project_extracted.apply(s, ctx, d, info, extract.legal_regime_by_rule(body), items)
stats = await checklist.merge(s, ctx, d, job_id, items, notes, lots)
cl = await checklist.get_or_create(s, ctx, d.project_id)
await response_mode.save(s, ctx, cl, d, body) # 招标文件的响应方式(C12),清单页显示、可改
return {
**stats,
"lots_found": len([k for k in lots if k != "默认"]),