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

@@ -131,3 +131,38 @@ def test_implicit_not_found_is_accepted_but_substantive_gets_a_hint() -> None:
assert results[a].verdict == "implicit_accepted" and findings.spec(results[a], items[a]) is None
spec = findings.spec(results[b], items[b])
assert results[b].verdict == "implicit_review" and spec is not None and spec.severity == "hint"
def test_presence_names_and_find() -> None:
from bid_doctor.check import presence
assert presence.names("须提供加盖公章的《法定代表人授权委托书》,否则投标无效") == ["法定代表人授权委托书"]
assert presence.names("投标人须提交投标函及开标一览表") == ["投标函"]
heading = sent(3, "4.法定代表人授权委托书")
body = sent(4, "本授权委托书声明:我单位授权……(这是正文里顺带提到的,很长" + "," * 60 + ")")
assert presence.find("法定代表人授权委托书", [body, heading]) is heading
assert presence.find("营业执照", [heading]) is None
def test_scanned_fallback_turns_missing_evidence_into_a_hint() -> None:
from uuid import uuid4
from bid_doctor.check import applicability
a, b = uuid4(), uuid4()
results = {a: match.Result("not_found", "model"), b: match.Result("not_found", "model")}
items = {
a: {"categories": ["qualification"], "title": "营业执照"},
b: {"categories": ["tech_param"], "title": "内存"},
}
decisions = {a: applicability.Decision(True, evidence=True), b: applicability.Decision(True)}
docs = [match.BidDoc(uuid4(), 1, "资格证明册", [], [12, 13])]
match.scanned_fallback(results, items, decisions, docs)
spec = findings.spec(results[a], items[a])
assert (
results[a].verdict == "scanned"
and spec is not None
and spec.severity == "hint"
and "2 页扫描件" in spec.explanation
)
assert results[b].verdict == "not_found" # 技术参数不是证明材料,照常报未找到