- 资质库(FR-M6-08):增删改、证书扫描件(PDF/JPG/PNG,企业级密钥加密、签名下载)、 late_for 按进行中项目的投标截止日提醒、审计;前端资质库页和对话框,侧栏入口 - 资质匹配(FR-M3-05):证书类别词表(16 类,产品证书、人员证书不进匹配), QUAL.EXPIRED / LEVEL_LOW / NOT_COVERED;投标文字层按清单项定级,只有资质库记录的降一级, 出处显示「依据:资质库记录」(查标页、报告模板 v2);一类证书一个问题;评分项不逐条找响应的也查 - 响应错位规则不再把资质表的证书名称格当成照抄的要求 - 评测:qual_defects.py 派生资质情况表,D12 3/3、D18 2/2、规矩的资质表 0 误报;开发集不退化 - 决策 QL1–QL7;方案和落地记录 docs/architecture/qualification-plan.md,评测 check-eval.md §13 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
98 lines
5.3 KiB
Python
98 lines
5.3 KiB
Python
"""从规矩的投标文件派生带「企业资质情况表」的投标文件(qualification-plan §5):q00 证书都有效、等级够;
|
||
q01 注入 D12(有效期早于投标截止日)和 D18(等级低于要求)。
|
||
|
||
cd server && ../.venv/bin/python ../evals/check/qual_defects.py <规矩的投标文件> <输出> <q00|q01>
|
||
然后 ../.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()
|