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:
@@ -58,6 +58,7 @@ BIDDER = {
|
|||||||
"agent": "陈立新",
|
"agent": "陈立新",
|
||||||
"date": "2026年11月20日",
|
"date": "2026年11月20日",
|
||||||
}
|
}
|
||||||
|
NUMS = re.compile(r"\d+(?:\.\d+)?")
|
||||||
DIGITS = "零壹贰叁肆伍陆柒捌玖"
|
DIGITS = "零壹贰叁肆伍陆柒捌玖"
|
||||||
UNITS = ["", "拾", "佰", "仟"]
|
UNITS = ["", "拾", "佰", "仟"]
|
||||||
SECTIONS = ["", "万", "亿"]
|
SECTIONS = ["", "万", "亿"]
|
||||||
@@ -181,6 +182,7 @@ def _row(k: int, r: dict[str, Any], columns: list[str]) -> dict[str, Any]:
|
|||||||
"responds": r["responds"],
|
"responds": r["responds"],
|
||||||
"req": r["req"],
|
"req": r["req"],
|
||||||
"iid": r["iid"],
|
"iid": r["iid"],
|
||||||
|
"key": r.get("key"),
|
||||||
"tp": r["tp"],
|
"tp": r["tp"],
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -219,10 +221,13 @@ def build(tender: str, bid: str, style: str, run: str) -> None:
|
|||||||
answer, dev = comply(raw, positive)
|
answer, dev = comply(raw, positive)
|
||||||
else: # 功能类要求:复述型(风格 A)照常写「满足,……」
|
else: # 功能类要求:复述型(风格 A)照常写「满足,……」
|
||||||
answer, dev = "满足," + re.sub(r"^[\s★▲**\d.、()()]+", "", req_text)[:120], "无偏离"
|
answer, dev = "满足," + re.sub(r"^[\s★▲**\d.、()()]+", "", req_text)[:120], "无偏离"
|
||||||
rw = rewrite.get(iid) or {}
|
rw = dict(rewrite.get(iid) or {})
|
||||||
|
if "answer" in rw and NUMS.findall(rw["answer"]) != NUMS.findall(answer):
|
||||||
|
rw.pop("answer") # 改写后的数字和规矩的响应对不上:丢弃改写,规矩靠构造保证
|
||||||
rows_by_route.setdefault(route, []).append(
|
rows_by_route.setdefault(route, []).append(
|
||||||
{
|
{
|
||||||
"iid": iid,
|
"iid": iid,
|
||||||
|
"key": iid,
|
||||||
"requirement": rw.get("requirement", req_text),
|
"requirement": rw.get("requirement", req_text),
|
||||||
"answer": rw.get("answer", answer),
|
"answer": rw.get("answer", answer),
|
||||||
"deviation": dev,
|
"deviation": dev,
|
||||||
@@ -253,11 +258,13 @@ def build(tender: str, bid: str, style: str, run: str) -> None:
|
|||||||
if not sents:
|
if not sents:
|
||||||
continue
|
continue
|
||||||
text = "".join(x.text for x in sents)
|
text = "".join(x.text for x in sents)
|
||||||
|
rw = rewrite.get(f"u:{u.id}") or {}
|
||||||
rows_by_route.setdefault("tech_row", []).append(
|
rows_by_route.setdefault("tech_row", []).append(
|
||||||
{
|
{
|
||||||
"iid": None,
|
"iid": None,
|
||||||
"requirement": text,
|
"key": f"u:{u.id}",
|
||||||
"answer": "满足:" + text,
|
"requirement": rw.get("requirement", text),
|
||||||
|
"answer": rw.get("answer", "满足:" + text),
|
||||||
"deviation": "无偏离",
|
"deviation": "无偏离",
|
||||||
"responds": [u.id],
|
"responds": [u.id],
|
||||||
"req": {"p": sents[0].page_no, "t": gold.norm(sents[0].text)[:24]},
|
"req": {"p": sents[0].page_no, "t": gold.norm(sents[0].text)[:24]},
|
||||||
@@ -298,7 +305,10 @@ def build(tender: str, bid: str, style: str, run: str) -> None:
|
|||||||
lines = [(total * b / sum(base)).quantize(Decimal("0.01")) for b in base]
|
lines = [(total * b / sum(base)).quantize(Decimal("0.01")) for b in base]
|
||||||
lines[-1] = total - sum(lines[:-1])
|
lines[-1] = total - sum(lines[:-1])
|
||||||
unit_prices = [(ln / w).quantize(Decimal("0.01")) for ln, w in zip(lines, weights, strict=False)]
|
unit_prices = [(ln / w).quantize(Decimal("0.01")) for ln, w in zip(lines, weights, strict=False)]
|
||||||
|
moved: list[dict[str, Any]] = [] # 风格 B:一部分技术行写在商务资格册里(干扰项 T01)
|
||||||
|
moved_columns: list[str] = []
|
||||||
for n, f in enumerate(profile["forms"], start=1):
|
for n, f in enumerate(profile["forms"], start=1):
|
||||||
|
start = len(blocks)
|
||||||
head = {"type": "heading", "text": f"{n}.{f['title']}", "new_page": n > 1, "form": f["key"]}
|
head = {"type": "heading", "text": f"{n}.{f['title']}", "new_page": n > 1, "form": f["key"]}
|
||||||
kind = f["kind"]
|
kind = f["kind"]
|
||||||
if kind == "text":
|
if kind == "text":
|
||||||
@@ -364,6 +374,15 @@ def build(tender: str, bid: str, style: str, run: str) -> None:
|
|||||||
else:
|
else:
|
||||||
rows = [_row(k, r, columns) for k, r in enumerate(src, 1)]
|
rows = [_row(k, r, columns) for k, r in enumerate(src, 1)]
|
||||||
pool = []
|
pool = []
|
||||||
|
if style == "B" and f.get("route", "tech_row") == "tech_row" and not f.get("mode"):
|
||||||
|
keep = []
|
||||||
|
for k, r in enumerate(rows):
|
||||||
|
(moved if k % 12 == 11 else keep).append(r)
|
||||||
|
rows, moved_columns = keep, columns
|
||||||
|
if style == "B" and "no" in columns: # 风格 B 用自己的编号
|
||||||
|
prefix = "技" if f.get("route", "tech_row") == "tech_row" else "商"
|
||||||
|
for k, r in enumerate(rows, 1):
|
||||||
|
r["cells"][columns.index("no")] = f"{prefix}-{k:02d}"
|
||||||
blocks += [
|
blocks += [
|
||||||
head,
|
head,
|
||||||
{
|
{
|
||||||
@@ -373,6 +392,7 @@ def build(tender: str, bid: str, style: str, run: str) -> None:
|
|||||||
"form": f["key"],
|
"form": f["key"],
|
||||||
"route": f.get("route", "tech_row"),
|
"route": f.get("route", "tech_row"),
|
||||||
"only_deviations": bool(f.get("only_deviations")),
|
"only_deviations": bool(f.get("only_deviations")),
|
||||||
|
"mode": f.get("mode"),
|
||||||
"columns": columns,
|
"columns": columns,
|
||||||
"pool": pool,
|
"pool": pool,
|
||||||
},
|
},
|
||||||
@@ -399,6 +419,28 @@ def build(tender: str, bid: str, style: str, run: str) -> None:
|
|||||||
blocks.append(b)
|
blocks.append(b)
|
||||||
else:
|
else:
|
||||||
raise ValueError(f"未知的表单类型:{kind}")
|
raise ValueError(f"未知的表单类型:{kind}")
|
||||||
|
tech = kind == "response_table" and f.get("route", "tech_row") == "tech_row"
|
||||||
|
for b in blocks[start:]:
|
||||||
|
b["vol"] = "v2" if style == "B" and tech else "v1"
|
||||||
|
if moved:
|
||||||
|
for k, r in enumerate(moved, 1):
|
||||||
|
if "no" in moved_columns:
|
||||||
|
r["cells"][moved_columns.index("no")] = f"补-{k:02d}"
|
||||||
|
blocks += [
|
||||||
|
{"type": "heading", "text": "补充技术响应", "new_page": True, "vol": "v1"},
|
||||||
|
{
|
||||||
|
"type": "table",
|
||||||
|
"header": moved_columns,
|
||||||
|
"rows": moved,
|
||||||
|
"form": "moved",
|
||||||
|
"route": "tech_row",
|
||||||
|
"only_deviations": False,
|
||||||
|
"mode": None,
|
||||||
|
"columns": moved_columns,
|
||||||
|
"pool": [],
|
||||||
|
"vol": "v1",
|
||||||
|
},
|
||||||
|
]
|
||||||
for sec in profile.get("prose", []):
|
for sec in profile.get("prose", []):
|
||||||
blocks.append({"type": "heading", "text": sec["title"], "new_page": True})
|
blocks.append({"type": "heading", "text": sec["title"], "new_page": True})
|
||||||
units = sorted(set(form_responds.get("prose:" + sec["title"], []) + form_responds.get("prose:", [])))
|
units = sorted(set(form_responds.get("prose:" + sec["title"], []) + form_responds.get("prose:", [])))
|
||||||
@@ -406,6 +448,29 @@ def build(tender: str, bid: str, style: str, run: str) -> None:
|
|||||||
{"type": "para", "text": fill(p, ctx), "responds": units if i == 0 else [], "form": "prose"}
|
{"type": "para", "text": fill(p, ctx), "responds": units if i == 0 else [], "form": "prose"}
|
||||||
for i, p in enumerate(sec["paragraphs"])
|
for i, p in enumerate(sec["paragraphs"])
|
||||||
]
|
]
|
||||||
|
for b in blocks[-len(sec["paragraphs"]) - 1 :]:
|
||||||
|
b["vol"] = "v2" if style == "B" else "v1"
|
||||||
|
blocks[0]["vol"] = "v1"
|
||||||
|
if style == "B":
|
||||||
|
volumes = [
|
||||||
|
{"id": "v1", "kind": "commercial", "header": None, "blocks": [b for b in blocks if b.get("vol") == "v1"]},
|
||||||
|
{
|
||||||
|
"id": "v2",
|
||||||
|
"kind": "technical",
|
||||||
|
"header": None,
|
||||||
|
"blocks": [
|
||||||
|
{"type": "title", "text": f"{proj.get('name', '')}投标文件(技术册)"},
|
||||||
|
*[b for b in blocks if b.get("vol") == "v2"],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
volumes = [{"id": "v1", "kind": "whole", "header": None, "blocks": blocks}]
|
||||||
|
traps = {u: "T01" for r in moved for u in r.get("responds") or []}
|
||||||
|
for v in rewrite: # 被改写的要求(截断、同义替换)是 T02;只写值不复述是 T03
|
||||||
|
for r in [x for xs in rows_by_route.values() for x in xs if x.get("key") == v]:
|
||||||
|
for u in r["responds"]:
|
||||||
|
traps.setdefault(u, "T03" if "answer" in rewrite[v] and "requirement" not in rewrite[v] else "T02")
|
||||||
|
|
||||||
spec = {
|
spec = {
|
||||||
"bid": bid,
|
"bid": bid,
|
||||||
@@ -413,7 +478,8 @@ def build(tender: str, bid: str, style: str, run: str) -> None:
|
|||||||
"style": style,
|
"style": style,
|
||||||
"project_name": proj.get("name", ""),
|
"project_name": proj.get("name", ""),
|
||||||
"bidder": BIDDER,
|
"bidder": BIDDER,
|
||||||
"volumes": [{"id": "v1", "kind": "whole", "header": None, "blocks": blocks}],
|
"volumes": volumes,
|
||||||
|
"traps": traps,
|
||||||
"price": {
|
"price": {
|
||||||
"total": f"{total:.2f}",
|
"total": f"{total:.2f}",
|
||||||
"items": [
|
"items": [
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ from build import rmb_upper # noqa: E402
|
|||||||
|
|
||||||
OUT = EVALS / "out" / "check"
|
OUT = EVALS / "out" / "check"
|
||||||
VOCAB = {"dev": EVALS / "check" / "vocab-dev.json", "holdout": OUT / "_sealed" / "vocab-holdout.json"}
|
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)}
|
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.spec, self.vocab, self.rng = spec, vocab, rng
|
||||||
self.unit_defects: dict[str, dict[str, Any]] = {}
|
self.unit_defects: dict[str, dict[str, Any]] = {}
|
||||||
self.rule_defects: list[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]]:
|
def tables(self, *, explicit: bool | None = None, fixed: bool = False) -> list[dict[str, Any]]:
|
||||||
@@ -51,7 +68,8 @@ class Mutator:
|
|||||||
continue
|
continue
|
||||||
if b.get("fixed") or "only_deviations" not in b:
|
if b.get("fixed") or "only_deviations" not in b:
|
||||||
continue
|
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)
|
out.append(b)
|
||||||
return out
|
return out
|
||||||
|
|
||||||
@@ -118,6 +136,12 @@ class Mutator:
|
|||||||
return False
|
return False
|
||||||
tp = r.get("tp") or {}
|
tp = r.get("tp") or {}
|
||||||
units = r.get("responds") 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":
|
if op == "D01":
|
||||||
t["rows"].remove(r)
|
t["rows"].remove(r)
|
||||||
self.drop_everywhere(units)
|
self.drop_everywhere(units)
|
||||||
@@ -180,7 +204,13 @@ class Mutator:
|
|||||||
def implicit_deviation(self, n: int) -> None:
|
def implicit_deviation(self, n: int) -> None:
|
||||||
"""只列偏离的表:挑默认接受的要求写成偏离(D06 负偏离未说明 / D05 写明不支持)。"""
|
"""只列偏离的表:挑默认接受的要求写成偏离(D06 负偏离未说明 / D05 写明不支持)。"""
|
||||||
for t in self.tables(explicit=False):
|
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)
|
self.rng.shuffle(pool)
|
||||||
ans, dev, note, req = (self.col(t, f) for f in ("answer", "deviation", "note", "requirement"))
|
ans, dev, note, req = (self.col(t, f) for f in ("answer", "deviation", "note", "requirement"))
|
||||||
if ans is None or dev is None:
|
if ans is None or dev is None:
|
||||||
@@ -192,10 +222,15 @@ class Mutator:
|
|||||||
if note is not None:
|
if note is not None:
|
||||||
row["cells"][note] = ""
|
row["cells"][note] = ""
|
||||||
self.mark(row["responds"], "D06", row["cells"][dev])
|
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:
|
else:
|
||||||
param = (row.get("tp") or {}).get("param") or (
|
param = tp.get("param") or (row["cells"][req][:12] if req is not None else "该功能")
|
||||||
row["cells"][req][:12] if req is not None else "该功能"
|
|
||||||
)
|
|
||||||
row["cells"][ans] = self.phrase("D05", "phrases").format(param=param)
|
row["cells"][ans] = self.phrase("D05", "phrases").format(param=param)
|
||||||
row["cells"][dev] = "负偏离"
|
row["cells"][dev] = "负偏离"
|
||||||
self.mark(row["responds"], "D05", row["cells"][ans])
|
self.mark(row["responds"], "D05", row["cells"][ans])
|
||||||
@@ -214,7 +249,7 @@ class Mutator:
|
|||||||
kinds = {cats.get(u) for u in units}
|
kinds = {cats.get(u) for u in units}
|
||||||
defect = "D11" if "qualification" in kinds else "D13" if "scoring" in kinds else "D02"
|
defect = "D11" if "qualification" in kinds else "D13" if "scoring" in kinds else "D02"
|
||||||
self.mark(units, defect, "附件缺失", worded=False)
|
self.mark(units, defect, "附件缺失", worded=False)
|
||||||
self.blocks.remove(b)
|
self.remove(b)
|
||||||
|
|
||||||
def compliance(self) -> None:
|
def compliance(self) -> None:
|
||||||
for t in self.tables(fixed=True):
|
for t in self.tables(fixed=True):
|
||||||
@@ -259,7 +294,7 @@ class Mutator:
|
|||||||
|
|
||||||
def p06(self) -> None:
|
def p06(self) -> None:
|
||||||
fp = self.rng.choice(self.vocab["P06"]["foreign_projects"])
|
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"])
|
self.rule("P06", "页眉留旧项目名", fp["name"])
|
||||||
|
|
||||||
def text_paras(self, form_hint: str) -> list[dict[str, Any]]:
|
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)
|
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:
|
if letter is not None:
|
||||||
text = self.phrase("P10", "phrases")
|
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)
|
self.rule("P10", text, text)
|
||||||
|
|
||||||
def p12(self) -> None:
|
def p12(self) -> None:
|
||||||
@@ -303,18 +338,27 @@ class Mutator:
|
|||||||
if t:
|
if t:
|
||||||
alt = f"{Decimal(self.spec['price']['total']) * Decimal('0.97'):,.2f}"
|
alt = f"{Decimal(self.spec['price']['total']) * Decimal('0.97'):,.2f}"
|
||||||
text = self.phrase("P12", "phrases").format(alt=alt)
|
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)
|
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]:
|
def unit_modes(spec: dict[str, Any]) -> dict[str, str]:
|
||||||
|
"""单元的响应方式:逐条响应表里有 → explicit;只在只列偏离的表里 → implicit;附件 → evidence;格式文件 → presence。"""
|
||||||
modes: dict[str, str] = {}
|
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:
|
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 r in [*b["rows"], *(b.get("pool") or [])]:
|
||||||
for u in r.get("responds") 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"):
|
elif b.get("attach"):
|
||||||
for u in b.get("responds") or []:
|
for u in b.get("responds") or []:
|
||||||
modes.setdefault(u, "evidence")
|
modes.setdefault(u, "evidence")
|
||||||
@@ -366,6 +410,8 @@ def main() -> None:
|
|||||||
entry.update({"expect": "defect", "reviewed": False, **m.unit_defects[u.id]})
|
entry.update({"expect": "defect", "reviewed": False, **m.unit_defects[u.id]})
|
||||||
elif mode == "implicit":
|
elif mode == "implicit":
|
||||||
entry.update({"expect": "ok", "trap": "T06"})
|
entry.update({"expect": "ok", "trap": "T06"})
|
||||||
|
elif u.id in (spec.get("traps") or {}):
|
||||||
|
entry.update({"expect": "ok", "trap": spec["traps"][u.id]})
|
||||||
else:
|
else:
|
||||||
entry["expect"] = {"na": "na", "unknown": "unknown"}.get(mode, "ok")
|
entry["expect"] = {"na": "na", "unknown": "unknown"}.get(mode, "ok")
|
||||||
manifest_units[u.id] = entry
|
manifest_units[u.id] = entry
|
||||||
|
|||||||
44
evals/check/prompts/profile.md
Normal file
44
evals/check/prompts/profile.md
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
# 写「招标文件档案」的说明
|
||||||
|
|
||||||
|
为查标评测集写一份招标文件档案(tender profile),供 `evals/check/build.py` 生成虚构的规矩投标文件。
|
||||||
|
|
||||||
|
## 读这些
|
||||||
|
|
||||||
|
- 档案格式:`evals/check/build.py` 文件头的 docstring,以及各 kind 的生成逻辑。
|
||||||
|
- 响应方式的定义:`evals/check/GUIDELINES.md` §2(含本文件那一行的记录,记录不准时以招标原文为准,并在回复里说明)。
|
||||||
|
- 招标文件全文(带句子编号 `[p页s句]`):`evals/out/docs/<ID>/sentences.txt`
|
||||||
|
- 读标正式清单(要分配 routes 的条目):`evals/out/check/tenders/<ID>.items.tsv`(列:iid、类别、重要性、页码、标题)
|
||||||
|
- 写好的档案可以参考格式(不要照抄内容):`evals/out/check/tenders/S02.json`、`S04.json`、`S10.json`。
|
||||||
|
|
||||||
|
**不要读** `evals/out/check/_sealed/`、`server/`、`prompts/`(仓库根目录那个)。
|
||||||
|
|
||||||
|
## 只写一个文件
|
||||||
|
|
||||||
|
`evals/out/check/tenders/<ID>.json`。不改仓库里的其他文件,不做 git 操作。
|
||||||
|
|
||||||
|
## 要求
|
||||||
|
|
||||||
|
1. `project`:项目名称、编号、采购人、代理机构、投标截止日期(`deadline`,YYYY-MM-DD)、投标有效期天数、最高限价或预算(元,纯数字字符串)、采购包(编号、名称、限价)。多包时只投一个包,写 `bidding_lot`,`ceiling_total` 填那个包的限价,只属于别的包的条目 routes 记 `na`。全部从招标原文取。
|
||||||
|
2. `modes`:按类别(tech_param、substantive、commercial、qualification、scoring,必要时加 scope)记响应方式(explicit / implicit / evidence / presence / platform),evidence 写 `when`(bid 或 award)。每条带 `basis`:`p` 页码,`t` 从 sentences.txt 逐字复制、不少于 8 个字的片段(去掉 `<u>` 这类标记)。同一份文件里两处写法冲突时都记上,并在回复里说明。
|
||||||
|
3. `forms`:按「投标文件格式」章的顺序列出投标人要交的全部格式文件。
|
||||||
|
- 叙述性的(投标函、授权委托书、声明函、承诺书)用 `kind: "text"`,`paragraphs` 照抄固定文字,填空处换成占位符(见 docstring),没有对应占位符的填空写成虚构的合理内容。声明函按规矩投标人的正确填法写;「如有 / 如是」而规矩投标人不适用的不列。
|
||||||
|
- 开标一览表 `price_summary`;分项报价表 `price_items`,`items` 写名称、数量、单位。**有分项最高限价或单价上限的,每项加 `ceiling`(元)**;上限是单价时再加 `ceiling_is_unit: true`。
|
||||||
|
- 偏离表 / 响应表 `response_table`:`route` 是 `tech_row` 或 `commercial_row`;implicit 的写 `only_deviations: true`,explicit 的写 false。表头照抄格式(脚本按表头文字决定每一列放什么,支持「序号 / 名称 / 要求 / 响应 / 偏离 / 说明」这些列)。**不是用来逐条响应、只是要求提交的技术文件**(例如评分要的「所投产品技术规格」表),加 `"mode": "presence"`。商务要求要逐项响应但格式章没给表的,可以自拟一张。
|
||||||
|
- 有固定行的表用 `fixed_rows`,响应栏按规矩填。
|
||||||
|
- 资格证明、评分证明材料放进**一个** `kind: "attachments"` 的表单(它会把全部附件渲染一遍)。
|
||||||
|
4. `attachments`:规矩投标人要附的每份证明材料(按本文件要求来;写明「投标文件中无需提供」的、中标后才交的不附)。证书类 `scan: true`,`lines` 写虚构内容(单位名 {bidder},信用代码 {credit_code},有效期覆盖截止日之后至少一年,社保月份按要求的区间)。全部虚构,不出现真实企业或真实人名。
|
||||||
|
5. `routes`:tsv 里**每一个** iid 都要分配:
|
||||||
|
- `tech_row`:技术参数、功能要求;`commercial_row`:商务要求(交货期、地点、质保、付款、售后响应等);
|
||||||
|
- `attach:<附件 key>`:靠附证明材料满足的;`form:<表单 key>`:靠某份格式文件满足的;
|
||||||
|
- `prose` 或 `prose:<节标题>`:靠技术方案散文响应的(实施方案、售后服务方案等评分项);
|
||||||
|
- 一条要求要走几处时用 `+` 连起来,例如 `tech_row+attach:a3`(表里一行,另附证明材料);
|
||||||
|
- `na`:不需要投标人响应的(无效情形的列举、评审程序、日期、采购人义务、合同条款、说明标注规则的句子、别的包的条目)。
|
||||||
|
6. `prose`:2~4 节虚构的技术方案叙述,每节 2~4 段,节标题要和 `prose:<节标题>` 对得上。**不写任何具体参数数值**,不出现别的项目名称。
|
||||||
|
|
||||||
|
## 自查(用 Python)
|
||||||
|
|
||||||
|
JSON 能解析;tsv 里的 iid 全部出现在 routes 里、没有多余的键;`form:` / `attach:` / `prose:` 都指向存在的表单、附件、节;modes 的 basis 片段都能在对应页逐字找到。可以把 build.py 的输出目录临时指到 scratchpad 试跑一次(不要写进仓库)。
|
||||||
|
|
||||||
|
## 最终回复
|
||||||
|
|
||||||
|
只写简短汇总:routes 各取值的条数、forms 列表(key + kind + 有无 mode)、modes 列表、拿不准的地方。
|
||||||
27
evals/check/prompts/style-B.md
Normal file
27
evals/check/prompts/style-B.md
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# 风格 B「简写型」改写说明(开发集用)
|
||||||
|
|
||||||
|
你在为评测集改写虚构投标文件的技术响应表。输入是一组表格行,每行有编号 `key`、招标要求原文 `requirement`、规矩的响应 `answer`。按下面的风格改写,**只改写法,不改内容**:
|
||||||
|
|
||||||
|
## 改写 `requirement`(招标要求那一栏)
|
||||||
|
|
||||||
|
真实投标人常常不照抄要求原文。每行按下面几种做法之一改写(轮换使用,大约各占三分之一):
|
||||||
|
|
||||||
|
- **截断**:只留前半句或关键词,后面用「……」或直接截掉。例:「支持多种接入方式,包括有线、无线和蜂窝网络,并支持自动切换」→「支持多种接入方式……」
|
||||||
|
- **改写**:换一种说法概括。例:「最小报文发送间隔不大于 50 毫秒」→「报文间隔≤50ms」
|
||||||
|
- **去掉编号和标记**:删掉原文开头的序号、★、▲。
|
||||||
|
|
||||||
|
## 改写 `answer`(投标响应那一栏)
|
||||||
|
|
||||||
|
- 大约三分之一的行**只写值、不复述要求**:例「64GB DDR4」「支持」「3 年」。这些行只输出 `answer`,不输出 `requirement`。
|
||||||
|
- 其余行用简短的说法,例「满足,单机 64GB」「支持,含硬件 BFD」。
|
||||||
|
- **数值、单位、是否满足必须和原响应完全一致**:原响应写 64GB,改写后也必须是 64GB(不能写成 64G 或 65536MB);原响应是满足,改写后不能出现「不支持」「部分」「可选」这类字眼。脚本会核对数字,对不上的改写会被丢弃。
|
||||||
|
|
||||||
|
## 不要做的
|
||||||
|
|
||||||
|
- 不要编造原文没有的参数、型号、品牌。
|
||||||
|
- 不要改变响应的含义(满足的仍然满足)。
|
||||||
|
- 例子里的设备和参数是虚构的,不要照搬到输出里。
|
||||||
|
|
||||||
|
## 输出
|
||||||
|
|
||||||
|
一个 JSON 对象:`{"<key>": {"requirement": "…", "answer": "…"}, …}`,只写 `answer` 的行省略 `requirement`。每个输入行都要有一项。
|
||||||
Reference in New Issue
Block a user