"""从规矩的投标文件派生带「企业资质情况表」的投标文件(qualification-plan §5):q00 证书都有效、等级够; q01 注入 D12(有效期早于投标截止日)和 D18(等级低于要求)。 cd server && ../.venv/bin/python ../evals/check/qual_defects.py <规矩的投标文件> <输出> 然后 ../.venv/bin/python ../evals/check/render.py <输出> - 资质情况表是文字表格(证书原件在后面的扫描件页上,和规矩的那份一样),插在资格证明材料那一章的开头; - 证书名称、编号、日期都是虚构的,写法故意和词表的别名不完全一样(国标号在前、括注英文缩写、日期几种写法); - 响应清单照抄规矩的那份(单元都是 expect=ok),rule_defects 记注入的问题;不挂标注单元,不需要复核(E1 只管单元); - 局限:表是对着 check/qualification.py 的词表写的,分数说明不了泛化(qualification-plan §5)。 """ from __future__ import annotations import argparse import json import pathlib from typing import Any ROOT = pathlib.Path(__file__).resolve().parent OUT = ROOT.parent / "out" / "check" HEADER = ["序号", "证书名称", "证书编号", "等级", "发证机构", "有效期"] # 招标文件 → 资质情况表的行(名称、编号、等级、发证机构、有效期),全部虚构 CERTS: dict[str, list[list[str]]] = { "S01": [ ["建筑业企业资质证书(电子与智能化工程专业承包)", "D232000001", "贰级", "某省住房和城乡建设厅(虚构)", "2024年5月21日至2029年5月20日"], ["安全生产许可证", "(苏)JZ安许证字〔2023〕000016", "—", "某省住房和城乡建设厅(虚构)", "2023年11月16日至2027年11月15日"], ["信息系统业务安全服务资质证书(CCIA)", "CCIA-2025-000231", "一级", "某网络安全行业组织(虚构)", "2025-02-01至2028-01-31"], ["信息安全服务资质认证证书(安全集成)", "CCRC-2024-ISV-000188", "一级", "某认证中心(虚构)", "有效期至2027.06.30"], ], "S04": [ ["GB/T 19001-2016/ISO 9001:2015 质量管理体系认证证书", "00125Q30001R1M", "—", "某认证有限公司(虚构)", "2025年3月10日至2028年3月9日"], ["环境管理体系认证证书(ISO 14001)", "00125E30002R1M", "—", "某认证有限公司(虚构)", "2025年3月10日至2028年3月9日"], ["职业健康安全管理体系认证证书 GB/T 45001-2020", "00125S30003R1M", "—", "某认证有限公司(虚构)", "2025年3月10日至2028年3月9日"], ], "S07": [ ["信息技术服务标准(ITSS)运行维护符合性证书", "ITSS-YW-2-320020250001", "二级", "某行业协会(虚构)", "2025年6月1日至2028年5月31日"], ["信息系统建设和服务能力等级证书", "CS3-3201-2024000077", "CS3", "某行业联合会(虚构)", "2024-12-31至2028-12-30"], ], } # 招标文件 → 注入的问题:(行号, 列号, 新值, 问题编号, 写法) DEFECTS: dict[str, list[tuple[int, int, str, str, str]]] = { "S01": [ (1, 5, "2023年11月16日至2026年11月15日", "D12", "资格条件的证书早于截止日到期"), (2, 3, "二级", "D18", "评分项要求一级,写的是二级"), ], "S04": [(1, 5, "2023年3月10日至2026年3月9日", "D12", "评分项的证书已过期")], "S07": [ (0, 5, "2023年6月1日至2026年5月31日", "D12", "评分项的证书已过期"), (1, 3, "CS2", "D18", "评分项要求 CS3 及以上,写的是 CS2"), ], } def table(rows: list[list[str]], vol: str) -> list[dict[str, Any]]: return [ {"type": "para", "text": "企业资质情况表(证书复印件附后)", "vol": vol}, { "type": "table", "header": HEADER, "rows": [{"cells": [str(i + 1), *r]} for i, r in enumerate(rows)], "vol": vol, }, ] def main() -> None: ap = argparse.ArgumentParser() ap.add_argument("clean") ap.add_argument("out") ap.add_argument("kind", choices=["q00", "q01"]) a = ap.parse_args() src = OUT / a.clean spec = json.loads((src / "spec.json").read_text()) manifest = json.loads((src / "manifest.json").read_text()) tender = spec["tender"] rows = [list(r) for r in CERTS[tender]] defects = [] if a.kind == "q01": for n, (row, col, value, code, variant) in enumerate(DEFECTS[tender], start=1): rows[row][col - 1] = value defects.append({"id": f"{code}-{n}", "type": code, "variant": variant, "t": value}) vol = spec["volumes"][0] at = next(i for i, b in enumerate(vol["blocks"]) if b.get("type") == "heading" and b.get("form") == "evidence") vol["blocks"][at + 1 : at + 1] = table(rows, vol["blocks"][at].get("vol") or "v1") out = OUT / a.out out.mkdir(parents=True, exist_ok=True) spec["bid"] = a.out manifest.update(bid=a.out, kind="clean" if a.kind == "q00" else "sloppy", density="qualification", rule_defects=defects) (out / "spec.json").write_text(json.dumps(spec, ensure_ascii=False, indent=1)) (out / "manifest.json").write_text(json.dumps(manifest, ensure_ascii=False, indent=1)) print(f"{a.out}:{a.kind},资质情况表 {len(rows)} 行,注入 {[d['id'] for d in defects]}") if __name__ == "__main__": main()