fix(check): 修国动真实投标文件暴露的 6 处(规则集 r5)
国动真实投标文件查标一次(check-eval §14):44 个问题里有用的 9 个, 致命、重要两级都是误报。按决策 E3 全部修(§15): - 响应错位:「完全满足,无偏离。」这类复合肯定写法整体算肯定 - 附加条件:「不以……另行收费」是承诺 - 扫描件:文字层 v5 记每页图片占比,带一行说明的扫描页也认;提示写明扫描页范围 - 不需要这家投标人回答的:无特定资格要求、总括性资格要求、中标后才做的事、 投标文件没声称适用的联合体和价格扣除 - 「方案应明确 A、B、C……」逐项再检索 - 同一批送出的片段都可引用(引文照样核对);出处没过校验的不沿用 国动重新检查 44 → 11(全是提示,有用的 9 个都在);开发集召回 112 → 111 (模型重跑波动),致命级 25/32 不变,规矩的投标文件问题 239 → 213。 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -5,13 +5,18 @@
|
||||
- 无效情形、评审规定(只有「无效」类别、不是标注出来的条款):由规则检查(第 2 步)和人工核对覆盖;
|
||||
- 日期与递交:由规则检查(落款日期、有效期)和需人工核对覆盖;
|
||||
- 评分标准本身:没写要附什么材料的不找;写了的按证明材料找;
|
||||
- 资格条件里写明「无需提供」或由采购人查询(信用中国)的不找。
|
||||
- 资格条件里写明「无需提供」或由采购人查询(信用中国)的不找;
|
||||
- 写明没有的要求(无特定资格要求)、总括性的「须具备招标文件规定的资格要求」、
|
||||
中标或签订合同以后才做的事,不找;
|
||||
- 条件性要求(联合体、残疾人福利性单位 / 监狱企业 / 本国产品的价格扣除):投标文件里有对应的文件、
|
||||
说明投标人声称适用时才找(国动真实投标文件,check-eval §14)。
|
||||
其余按类别取招标文件的响应方式:技术参数用技术类,其余用商务类。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from collections.abc import Iterable
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
@@ -22,15 +27,50 @@ EVIDENCE = re.compile(r"(提供|附|出具)[^。;]{0,20}(证明|报告|截图|
|
||||
# 「签订合同时提供」才算中标后;「以合同签订时间为准」(业绩条款常见)不是
|
||||
AWARD = re.compile(r"(中标后|成交后|签订合同(前|时)(?!间)|合同签订(前|时)(?!间))")
|
||||
NO_PROOF = re.compile(r"无需提供|不需提供|无须提供|采购人[^。;]{0,12}(查询|核查|查验)|信用中国|信用记录")
|
||||
# 写明没有要求、不接受:没有要投标人提供的东西(按标题判断,标题是读标写的一句话概括)
|
||||
# 「不接受联合体」不放这里:投标人真是联合体时恰恰要报,按条件性要求处理
|
||||
NONE_REQUIRED = re.compile(r"无特定资格要求|特定资格要求[::]?(无|/)")
|
||||
SPECIAL_PROOF = re.compile(r"特定资格要求的证明") # 招标文件写了「无特定资格要求」时不用交
|
||||
UMBRELLA = re.compile(
|
||||
r"(具备|满足|符合)[^。;]{0,4}(招标|磋商|采购|谈判|询价)文件[^。;]{0,4}(规定|要求)的?资格(要求|条件)"
|
||||
)
|
||||
AFTER_AWARD = re.compile(r"^(合同签订后|签订合同后|中标后|成交后)")
|
||||
# 条件性要求:标题里的主体 → 投标文件里出现了就说明投标人声称适用
|
||||
# (「残疾人福利性单位声明函」写「不属于」也算出现,照常查)
|
||||
CONDITIONAL = {
|
||||
"consortium": (re.compile(r"联合体"), re.compile(r"联合体(协议|牵头|各方|成员)")),
|
||||
"disabled": (re.compile(r"残疾人福利"), re.compile(r"残疾人福利性单位")),
|
||||
"prison": (re.compile(r"监狱|戒毒"), re.compile(r"监狱企业|戒毒企业")),
|
||||
"domestic": (re.compile(r"本国产品"), re.compile(r"本国产品")),
|
||||
}
|
||||
SKIP_LABELS = {
|
||||
"condition": "无效情形和评审规定",
|
||||
"schedule": "日期与递交要求",
|
||||
"scoring": "评分标准(没要求附证明材料)",
|
||||
"no_proof": "资格条件(写明无需提供证明,或由采购人查询)",
|
||||
"price": "报价类要求(分项、单价上限):由规则比对分项报价表",
|
||||
"none_required": "写明没有的要求(无特定资格要求)",
|
||||
"umbrella": "总括性的资格要求(由各条具体资格条件覆盖)",
|
||||
"after_award": "中标或签订合同以后才做的事",
|
||||
"not_claimed": "条件性要求(联合体、残疾人福利性单位、监狱企业、本国产品),投标文件没有声称适用",
|
||||
}
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class Context:
|
||||
"""整份清单和投标文件上的事实。claims 为 None 时不知道投标文件写了什么,条件性要求照常查。"""
|
||||
|
||||
claims: frozenset[str] | None = None
|
||||
no_special: bool = False # 招标文件写了「无特定资格要求」
|
||||
|
||||
|
||||
def context(items: Iterable[dict[str, Any]], bid_texts: Iterable[str]) -> Context:
|
||||
bid = "".join(bid_texts)
|
||||
claims = frozenset(k for k, (_, claimed) in CONDITIONAL.items() if claimed.search(bid))
|
||||
no_special = any(re.search(r"无特定资格要求|特定资格要求[::]?(无|/)", str(i.get("title") or "")) for i in items)
|
||||
return Context(claims, no_special)
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class Decision:
|
||||
match: bool
|
||||
@@ -40,15 +80,26 @@ class Decision:
|
||||
evidence: bool = False # 要求投标时附证明材料:默认接受也不能免(没附就是没附)
|
||||
|
||||
|
||||
def decide(item: dict[str, Any], modes: dict[str, Mode]) -> Decision:
|
||||
def decide(item: dict[str, Any], modes: dict[str, Mode], ctx: Context | None = None) -> Decision:
|
||||
cats = set(item.get("categories") or [])
|
||||
text = f"{item.get('title') or ''}{item.get('requirement_text') or ''}"
|
||||
title = str(item.get("title") or "")
|
||||
text = f"{title}{item.get('requirement_text') or ''}"
|
||||
if cats == {"invalidity"} and item.get("importance_basis") != "marker":
|
||||
return Decision(False, skip="condition") # 「……的,投标无效」这类情形;★ / 斜体下划线标注的条款是要求,照常找
|
||||
if cats == {"price"}:
|
||||
return Decision(False, skip="price")
|
||||
if "schedule" in cats and not cats & {"tech_param", "qualification", "scoring"}:
|
||||
return Decision(False, skip="schedule")
|
||||
if NONE_REQUIRED.search(title) or (ctx is not None and ctx.no_special and SPECIAL_PROOF.search(title)):
|
||||
return Decision(False, skip="none_required")
|
||||
if "tech_param" not in cats and UMBRELLA.search(text):
|
||||
return Decision(False, skip="umbrella")
|
||||
if AFTER_AWARD.search(title):
|
||||
return Decision(False, skip="after_award")
|
||||
if ctx is not None and ctx.claims is not None:
|
||||
for key, (subject, _) in CONDITIONAL.items():
|
||||
if subject.search(title) and key not in ctx.claims:
|
||||
return Decision(False, skip="not_claimed")
|
||||
evidence = bool(item.get("evidence_required") or EVIDENCE.search(text)) and not AWARD.search(text)
|
||||
if cats <= {"scoring"} and not evidence:
|
||||
return Decision(False, skip="scoring")
|
||||
|
||||
Reference in New Issue
Block a user