feat(evals): 风格 B(两个分册、改写、自己的编号、干扰项 T01);注入的三处待修项
- build.py:风格 B 分商务资格册和技术册,表格行按 rewrite.json 改写(数字对不上的改写丢弃,规矩靠构造 保证),用自己的编号,每 12 行技术响应挪到商务资格册(T01);干扰项写进 spec.traps。 - mutate.py:支持多个分册;表格的响应方式以档案的 mode 为准(评分要的技术规格表是 presence,不再把单元 推成逐条响应);「暂不支持」只套在功能类要求上,数值参数写不满足的值;「提供……方案 / 证明 / 报告」 这类文件要在的要求不注入参数类问题。 - prompts/style-B.md、prompts/profile.md:风格 B 改写说明和招标文件档案说明(开发集用)。 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -29,6 +29,7 @@ from build import rmb_upper # noqa: E402
|
||||
|
||||
OUT = EVALS / "out" / "check"
|
||||
VOCAB = {"dev": EVALS / "check" / "vocab-dev.json", "holdout": OUT / "_sealed" / "vocab-holdout.json"}
|
||||
DOC_REQ = re.compile(r"提供.{0,12}(方案|证明|报告|文件|承诺|截图|复印件|证书)")
|
||||
UNIT_DOWN = {"TB": ("GB", 1024), "GB": ("MB", 1024), "Gbps": ("Mbps", 1000), "s": ("ms", 1000), "秒": ("毫秒", 1000)}
|
||||
|
||||
|
||||
@@ -37,7 +38,23 @@ class Mutator:
|
||||
self.spec, self.vocab, self.rng = spec, vocab, rng
|
||||
self.unit_defects: dict[str, dict[str, Any]] = {}
|
||||
self.rule_defects: list[dict[str, Any]] = []
|
||||
self.blocks = spec["volumes"][0]["blocks"]
|
||||
self.vols = spec["volumes"]
|
||||
|
||||
@property
|
||||
def blocks(self) -> list[dict[str, Any]]:
|
||||
"""所有分册的块(只读视图);增删用 remove / insert_after。"""
|
||||
return [b for v in self.vols for b in v["blocks"]]
|
||||
|
||||
def remove(self, b: dict[str, Any]) -> None:
|
||||
for v in self.vols:
|
||||
if b in v["blocks"]:
|
||||
v["blocks"].remove(b)
|
||||
|
||||
def insert_after(self, anchor: dict[str, Any], new: dict[str, Any]) -> None:
|
||||
for v in self.vols:
|
||||
if anchor in v["blocks"]:
|
||||
v["blocks"].insert(v["blocks"].index(anchor) + 1, new)
|
||||
return
|
||||
|
||||
# ---------- 定位 ----------
|
||||
def tables(self, *, explicit: bool | None = None, fixed: bool = False) -> list[dict[str, Any]]:
|
||||
@@ -51,7 +68,8 @@ class Mutator:
|
||||
continue
|
||||
if b.get("fixed") or "only_deviations" not in b:
|
||||
continue
|
||||
if explicit is None or b["only_deviations"] != explicit:
|
||||
mode = table_mode(b)
|
||||
if explicit is None or (explicit and mode == "explicit") or (explicit is False and mode == "implicit"):
|
||||
out.append(b)
|
||||
return out
|
||||
|
||||
@@ -118,6 +136,12 @@ class Mutator:
|
||||
return False
|
||||
tp = r.get("tp") or {}
|
||||
units = r.get("responds") or []
|
||||
req_text = r["cells"][req] if req is not None else ""
|
||||
if DOC_REQ.search(req_text): # 「提供……方案 / 证明 / 报告」是文件要在(presence),不注入参数类问题
|
||||
return False
|
||||
numeric = tp.get("op") in (">=", "<=", "=") and tp.get("value")
|
||||
if op == "D05" and numeric: # 「暂不支持」只套在功能类要求上(尺寸、费用这类数值参数不会这么写)
|
||||
return False
|
||||
if op == "D01":
|
||||
t["rows"].remove(r)
|
||||
self.drop_everywhere(units)
|
||||
@@ -180,7 +204,13 @@ class Mutator:
|
||||
def implicit_deviation(self, n: int) -> None:
|
||||
"""只列偏离的表:挑默认接受的要求写成偏离(D06 负偏离未说明 / D05 写明不支持)。"""
|
||||
for t in self.tables(explicit=False):
|
||||
pool = [r for r in t.get("pool") or [] if self.free(r.get("responds") or [])]
|
||||
req_col = self.col(t, "requirement")
|
||||
pool = [
|
||||
r
|
||||
for r in t.get("pool") or []
|
||||
if self.free(r.get("responds") or [])
|
||||
and not (req_col is not None and DOC_REQ.search(r["cells"][req_col]))
|
||||
]
|
||||
self.rng.shuffle(pool)
|
||||
ans, dev, note, req = (self.col(t, f) for f in ("answer", "deviation", "note", "requirement"))
|
||||
if ans is None or dev is None:
|
||||
@@ -192,10 +222,15 @@ class Mutator:
|
||||
if note is not None:
|
||||
row["cells"][note] = ""
|
||||
self.mark(row["responds"], "D06", row["cells"][dev])
|
||||
elif (tp := row.get("tp") or {}).get("op") in (">=", "<=") and tp.get("value"):
|
||||
# 数值参数不会写「暂不支持」:写一个不满足的值并标负偏离
|
||||
v = Decimal(str(tp["value"]))
|
||||
bad = v / 2 if tp["op"] == ">=" else v * Decimal("1.6")
|
||||
row["cells"][ans] = f"{tp.get('param') or ''}:{bad.normalize():f}{tp.get('unit') or ''}"
|
||||
row["cells"][dev] = "负偏离"
|
||||
self.mark(row["responds"], "D06", "数值不满足并写负偏离")
|
||||
else:
|
||||
param = (row.get("tp") or {}).get("param") or (
|
||||
row["cells"][req][:12] if req is not None else "该功能"
|
||||
)
|
||||
param = tp.get("param") or (row["cells"][req][:12] if req is not None else "该功能")
|
||||
row["cells"][ans] = self.phrase("D05", "phrases").format(param=param)
|
||||
row["cells"][dev] = "负偏离"
|
||||
self.mark(row["responds"], "D05", row["cells"][ans])
|
||||
@@ -214,7 +249,7 @@ class Mutator:
|
||||
kinds = {cats.get(u) for u in units}
|
||||
defect = "D11" if "qualification" in kinds else "D13" if "scoring" in kinds else "D02"
|
||||
self.mark(units, defect, "附件缺失", worded=False)
|
||||
self.blocks.remove(b)
|
||||
self.remove(b)
|
||||
|
||||
def compliance(self) -> None:
|
||||
for t in self.tables(fixed=True):
|
||||
@@ -259,7 +294,7 @@ class Mutator:
|
||||
|
||||
def p06(self) -> None:
|
||||
fp = self.rng.choice(self.vocab["P06"]["foreign_projects"])
|
||||
self.spec["volumes"][0]["header"] = fp["name"]
|
||||
self.vols[-1]["header"] = fp["name"] # 技术分册照搬旧标书最常见
|
||||
self.rule("P06", "页眉留旧项目名", fp["name"])
|
||||
|
||||
def text_paras(self, form_hint: str) -> list[dict[str, Any]]:
|
||||
@@ -295,7 +330,7 @@ class Mutator:
|
||||
letter = next((i for i, b in enumerate(self.blocks) if b.get("type") == "para" and b.get("form")), None)
|
||||
if letter is not None:
|
||||
text = self.phrase("P10", "phrases")
|
||||
self.blocks.insert(letter + 1, {"type": "para", "text": text, "form": self.blocks[letter]["form"]})
|
||||
self.insert_after(self.blocks[letter], {"type": "para", "text": text, "form": self.blocks[letter]["form"]})
|
||||
self.rule("P10", text, text)
|
||||
|
||||
def p12(self) -> None:
|
||||
@@ -303,18 +338,27 @@ class Mutator:
|
||||
if t:
|
||||
alt = f"{Decimal(self.spec['price']['total']) * Decimal('0.97'):,.2f}"
|
||||
text = self.phrase("P12", "phrases").format(alt=alt)
|
||||
self.blocks.insert(self.blocks.index(t) + 1, {"type": "para", "text": text})
|
||||
self.insert_after(t, {"type": "para", "text": text})
|
||||
self.rule("P12", text, text)
|
||||
|
||||
|
||||
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"))
|
||||
|
||||
|
||||
def unit_modes(spec: dict[str, Any]) -> dict[str, str]:
|
||||
"""单元的响应方式:逐条响应表里有 → explicit;只在只列偏离的表里 → implicit;附件 → evidence;格式文件 → presence。"""
|
||||
modes: dict[str, str] = {}
|
||||
for b in spec["volumes"][0]["blocks"]:
|
||||
rank = {"explicit": 0, "implicit": 1, "evidence": 2, "presence": 3}
|
||||
blocks = [b for v in spec["volumes"] for b in v["blocks"]]
|
||||
for b in blocks:
|
||||
if b.get("type") == "table" and "only_deviations" in b:
|
||||
mode = "implicit" if b["only_deviations"] else "explicit"
|
||||
mode = table_mode(b)
|
||||
for r in [*b["rows"], *(b.get("pool") or [])]:
|
||||
for u in r.get("responds") or []:
|
||||
modes.setdefault(u, mode)
|
||||
if u not in modes or rank.get(mode, 9) < rank.get(modes[u], 9):
|
||||
modes[u] = mode
|
||||
elif b.get("attach"):
|
||||
for u in b.get("responds") or []:
|
||||
modes.setdefault(u, "evidence")
|
||||
@@ -366,6 +410,8 @@ def main() -> None:
|
||||
entry.update({"expect": "defect", "reviewed": False, **m.unit_defects[u.id]})
|
||||
elif mode == "implicit":
|
||||
entry.update({"expect": "ok", "trap": "T06"})
|
||||
elif u.id in (spec.get("traps") or {}):
|
||||
entry.update({"expect": "ok", "trap": spec["traps"][u.id]})
|
||||
else:
|
||||
entry["expect"] = {"na": "na", "unknown": "unknown"}.get(mode, "ok")
|
||||
manifest_units[u.id] = entry
|
||||
|
||||
Reference in New Issue
Block a user