feat(check): 规则检查(报价形式、形式要件、响应表),表格按单元格分行

- check/rules.py(规则集 2026.11-r1,不调模型):大小写金额、分项合计、超最高限价、
  多个报价、别的项目名称、没填的地方、落款日期、投标有效期、附加条件、响应表里的未响应;
  每条挂投标原文,有依据的挂招标侧出处;缺限价、截止时间、有效期要求时写进覆盖范围
- 文字层的句子带上单元格框,表格按单元格上沿分行
- 按 Fable 审读意见收紧致命级:认不准的降为重要或提示
- 评测:score.py 评规则问题;P07 注入记录改为空白处前后原文,剔除注入到人员表的 D10
- 上传页的检查范围文案同步

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
YANG JIANKUAN
2026-11-04 16:44:22 +08:00
parent 47c6a1258e
commit db1b1f6f9f
17 changed files with 1792 additions and 20 deletions

View File

@@ -334,6 +334,8 @@ class Mutator:
def compliance(self) -> None:
for t in self.tables(fixed=True):
if not any("响应" in str(h) for h in t.get("header") or []):
continue # 只有响应表(表头有「响应情况」栏)才注入;人员表、业绩表不是
rows = [r for r in t["rows"] if self.free(r.get("responds") or [])]
if not rows:
continue
@@ -388,7 +390,7 @@ class Mutator:
b = self.rng.choice(paras)
blank = self.phrase("P07", "blanks")
b["text"] = b["text"].replace(bidder, blank, 1)
self.rule("P07", "投标人名称空", b["text"][:24])
self.rule("P07", "投标人名称空", blank_context(b["text"], blank))
def p08(self) -> None:
signs = [b for b in self.blocks if b.get("type") == "sign"]
@@ -423,6 +425,13 @@ class Mutator:
self.rule("P12", text, text)
def blank_context(text: str, blank: str) -> str:
"""空白处前后的原文(不跨句号),评分时拿它对照问题的投标侧出处。"""
i = text.index(blank)
start = max(i - 10, max(text.rfind(x, 0, i) for x in "。;") + 1)
return text[start : i + len(blank) + 10]
def table_mode(b: dict[str, Any]) -> str:
"""表格的响应方式:档案里表单写了 mode 的以它为准(如 S02 评分要的技术规格表是 presence,不是逐条响应的机制)。"""
return str(b.get("mode") or ("implicit" if b.get("only_deviations") else "explicit"))
@@ -473,7 +482,7 @@ def main() -> None:
for f in OUT.glob(f"{tender}-*/manifest.json"):
for uid, e in json.loads(f.read_text())["units"].items():
if (e.get("review") or {}).get("ok") is False:
m.rejected.add(uid) # noqa: S311 评测数据,不用于安全
m.rejected.add(uid)
if not a.manifest_only:
if a.density == "high":
m.d01_block()