fix(check): 修留出集暴露的 5 处(规则集 2026.11-r3)

- 响应方式:「逐条响应」要说的是要求 / 参数 / 偏离,声明函填写说明不算;「否则视为无效响应」不算默认接受
- 偏离栏按「是不是明确满足」判断:偏离栏、响应情况栏一起看,其他短写法请人核对(最高重要);表体序号认「技-86」等
- 另报价、附加条件按结构判断(另一个不同的总价金额、保留 / 另行 / 调整报价等)
- 空白栏:「精确到年月日」不算没填
- 比对时折叠异体字(戶→户等),原文照存
- 记录决策 E2:先修再跑国动真实投标文件

开发集召回 75%、规矩投标文件问题数不变;留出集参考数字见 check-eval §12(已看过,仅参考)。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
YANG JIANKUAN
2026-11-04 20:34:12 +08:00
parent fee1c0f842
commit 902a130c82
13 changed files with 436 additions and 70 deletions

View File

@@ -91,7 +91,9 @@ def test_response_mode_by_category() -> None:
m = response_mode.detect(sents)
assert (m["technical"].mode, m["technical"].status) == ("implicit", "found")
assert (m["commercial"].mode, m["commercial"].status) == ("explicit", "found")
both = response_mode.detect([sent(1, "须逐项作出响应。"), sent(2, "否则将认为投标人接受招标文件的要求。")])
both = response_mode.detect(
[sent(1, "须对技术要求逐项作出响应。"), sent(2, "否则将认为投标人接受招标文件的要求。")]
)
assert (both["technical"].mode, both["technical"].status) == ("explicit", "conflict") # 写法冲突按逐条(C12)
none = response_mode.detect([sent(1, "本项目为货物采购。")])
assert (none["commercial"].mode, none["commercial"].status) == ("explicit", "unknown")
@@ -293,3 +295,54 @@ def test_award_time_wording_is_not_award_stage_evidence() -> None:
assert applicability.decide(scoring, {}).match
later = {**scoring, "requirement_text": "签订合同时提供业绩合同原件备查。"}
assert applicability.decide(later, {}).skip == "scoring"
def test_response_mode_ignores_declaration_boilerplate_and_negative_consequence() -> None:
"""响应方式识别(check-eval §11):声明函「逐一、准确填写产品名称……」不算要求逐条响应;
「否则视为无效响应」说的是不响应的后果,不算默认接受。"""
from bid_doctor.checklist import response_mode
implicit = [
sent(1, "供应商需在《声明函》中逐一、准确填写产品名称、厂名、生产厂址三项信息。"),
sent(2, "对招标文件中的所有商务、技术要求,除本表所列明的所有偏离外,均视作供应商已对之理解和响应。"),
]
modes = response_mode.detect(implicit)
assert (modes["technical"].mode, modes["technical"].status) == ("implicit", "found")
explicit = [
sent(1, "供应商需对磋商文件中的技术要求与商务要求逐项作出响应或偏离说明。"),
sent(2, "供应商须对技术要求中的实质性要求做出明确响应,否则视为无效响应。"),
]
modes = response_mode.detect(explicit)
assert (modes["technical"].mode, modes["technical"].status) == ("explicit", "found")
def test_direct_read_uses_header_column_and_flags_unclear_values() -> None:
"""偏离栏按表头认出来:写的不是明确的「无偏离 / 满足」,改判负偏离并标「请核对」。
级别最高到重要(check-eval §11)。"""
from bid_doctor.check import deviation
def c(seq: int, text: str, x0: float, y: float) -> Sentence:
s = Sentence(key=f"b:p1:s{seq}", page_no=1, seq=seq, text=text, quads=[[x0 + 2, y + 4, x0 + 90, y + 16]])
s.cell = [x0, y, x0 + 100, y + 30]
return s
head = [c(1, "序号", 0, 20), c(2, "招标要求", 100, 20), c(3, "投标响应", 200, 20), c(4, "偏离", 300, 20)]
row1 = [c(5, "1", 0, 50), c(6, "支持双电源", 100, 50), c(7, "满足,支持双电源", 200, 50), c(8, "接近要求", 300, 50)]
row2 = [
c(9, "2", 0, 80),
c(10, "支持热插拔", 100, 80),
c(11, "满足,支持热插拔", 200, 80),
c(12, "无偏离", 300, 80),
]
d = match.BidDoc(uuid4(), 1, "技术分册", [*head, *row1, *row2])
a, b = uuid4(), uuid4()
results = {
a: match.Result("responded", "ai", "verified", evidence=[(d, row1[2])]),
b: match.Result("responded", "ai", "verified", evidence=[(d, row2[2])]),
}
assert deviation.direct_read(results, [d]) == 1
assert results[a].verdict == "negative_deviation" and results[a].detail["deviation_unclear"] is True
assert results[b].verdict == "responded"
spec = findings.spec(results[a], {"title": "支持双电源", "importance": "substantive", "categories": ["tech_param"]})
assert spec is not None and spec.severity == "important" and spec.title.startswith("偏离栏需核对")