- check/presence.py:「提供《授权委托书》」「须提交投标函」这类要求按标题里有没有这个文件名判断,不调模型。
- 扫描件兜底:要附证明材料的要求没找到、投标文件里有扫描件页时,改为提示级「可能在扫描件里,请人工核对」。
- 响应方式解析时识别、带依据句子存进 checklist.response_modes(迁移 0008);清单页显示、可改(写审计,
重新解析不覆盖人工改过的);查标用存下来的。新接口 PUT /projects/{id}/checklist/response-modes/{category}。
- check_item_result.layer 加 presence(迁移 0007);契约同步默认接受的两种结论。
开发集 17 份:规矩投标文件的致命问题 120 → 51,致命级召回不变(22 / 32)(docs/architecture/check-eval.md §5)。
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
77 lines
3.1 KiB
Python
77 lines
3.1 KiB
Python
"""汇总一批投标文件的查标评分(evals/check/GUIDELINES.md §7)。先对每份跑过 score.py。
|
||
|
||
cd server && ../.venv/bin/python ../evals/check/report.py --run base <投标文件> [...]
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import argparse
|
||
import json
|
||
import pathlib
|
||
from collections import Counter
|
||
|
||
OUT = pathlib.Path(__file__).resolve().parents[1] / "out" / "check"
|
||
|
||
|
||
def pct(a: int, b: int) -> str:
|
||
return f"{a}/{b}({a / b:.0%})" if b else "—"
|
||
|
||
|
||
def main() -> None:
|
||
ap = argparse.ArgumentParser()
|
||
ap.add_argument("bids", nargs="+")
|
||
ap.add_argument("--run", default="base")
|
||
a = ap.parse_args()
|
||
rec: Counter[str] = Counter()
|
||
tot: Counter[str] = Counter()
|
||
by_style: Counter[str] = Counter()
|
||
by_style_tot: Counter[str] = Counter()
|
||
fp = ok = unmapped = other = 0
|
||
traps: Counter[str] = Counter()
|
||
tokens = [0, 0]
|
||
findings_clean = items_clean = 0
|
||
for bid in a.bids:
|
||
d = OUT / bid
|
||
m = json.loads((d / "manifest.json").read_text())
|
||
s = json.loads((d / "runs" / a.run / "score.json").read_text())
|
||
r = json.loads((d / "runs" / a.run / "results.json").read_text())
|
||
tokens[0] += r["summary"]["usage"]["tokens_in"]
|
||
tokens[1] += r["summary"]["usage"]["tokens_out"]
|
||
for k, (hit, n) in s["recall"].items():
|
||
rec[k] += hit
|
||
tot[k] += n
|
||
if "查标" in s["recall"]:
|
||
by_style[m["style"]] += s["recall"]["查标"][0]
|
||
by_style_tot[m["style"]] += s["recall"]["查标"][1]
|
||
if m["kind"] == "clean":
|
||
fp += s["fp"]
|
||
ok += s["ok_units"]
|
||
other += s["other_findings"]
|
||
unmapped += s["unmapped"]
|
||
traps.update(s["fp_by_trap"])
|
||
findings_clean += sum(1 for it in r["items"] if it["finding"])
|
||
items_clean += len(r["items"])
|
||
lines = [
|
||
f"# 汇总:{len(a.bids)} 份投标文件 · 运行 {a.run}",
|
||
"",
|
||
f"- 查标召回:{pct(rec['查标'], tot['查标'])};端到端:{pct(rec['端到端'], tot['端到端'])};致命级:{pct(rec['致命级'], tot['致命级'])}",
|
||
"- 按风格:" + ";".join(f"{k} {pct(by_style[k], by_style_tot[k])}" for k in sorted(by_style_tot)),
|
||
"",
|
||
"| 分类 | 召回 |",
|
||
"|---|---|",
|
||
*[f"| {k} | {pct(rec[k], tot[k])} |" for k in sorted(tot) if k not in ("查标", "端到端", "致命级")],
|
||
f"| 召回里只是「扫描件待人工核对」的 | {rec['其中只是扫描件待人工核对']} |",
|
||
"",
|
||
f"- 规矩的投标文件:清单 {items_clean} 条,报出问题 {findings_clean} 个;其中规矩响应的要求上 {fp}/{ok}(每 100 条 {100 * fp / ok:.1f})"
|
||
if ok
|
||
else "",
|
||
f"- 规矩的投标文件里,不需要响应(na / unknown)的要求上报了问题 {other} 个,对不上标注单元的问题 {unmapped} 个",
|
||
f"- 干扰项误报:{dict(traps)}",
|
||
f"- token:输入 {tokens[0]:,},输出 {tokens[1]:,}({len(a.bids)} 次检查)",
|
||
]
|
||
print("\n".join(lines))
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|