feat(evals): 查标评测集生成器、注入、评分和只检索基线

- build.py:从「招标文件档案」和读标清单生成规矩投标文件稿;技术参数按 op / value / unit 算满足的值,
  分项报价按分项上限比例分摊,落款日期取截止日前 3 天,偏离表按格式表头决定列(含设备名称列),
  读标漏抽的技术参数也补上响应;routes 支持「+」组合和 prose 散文响应。
- mutate.py:按词库注入要求项问题(单点 / 连续块,带不带标志词)和规则问题,写响应清单;
  符合性条件响应表的问题按表级记一个。
- score.py:按单元去重、类型族比对、读标漏抽单列、端到端召回、无法对照单列。
- run_check.py:加只检索不调模型的基线;render.py:扫描件按最后一次出现定位。
- vocab-dev.json:开发集注入词库(留出集的另行封存,不进 git)。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
YANG JIANKUAN
2026-11-04 12:18:48 +08:00
parent 01ddc86281
commit a07663cbdb
6 changed files with 1070 additions and 7 deletions

View File

@@ -132,12 +132,11 @@ def block_text(b: dict[str, Any], row: dict[str, Any] | None = None) -> str:
return str(b.get("text") or " ".join(b.get("lines") or []))
def locate(texts: dict[int, str], snippet: str) -> int | None:
def locate(texts: dict[int, str], snippet: str, last: bool = False) -> int | None:
"""last:取最后一次出现的页(附件在文件末尾,名称常和前面格式文件的标题相同)。"""
q = text_layer.norm(snippet)[:SNIPPET]
for p, t in texts.items():
if q and q in t:
return p
return None
hits = [p for p, t in texts.items() if q and q in t]
return (hits[-1] if last else hits[0]) if hits else None
def render(bid: str) -> None:
@@ -151,7 +150,7 @@ def render(bid: str) -> None:
page_html, header = volume_html(spec, vol)
pdf = render_pdf(page_html, header)
texts = page_texts(pdf)
scans = {p for b in vol["blocks"] if b.get("type") == "scan" and (p := locate(texts, b["text"]))}
scans = {p for b in vol["blocks"] if b.get("type") == "scan" and (p := locate(texts, b["text"], last=True))}
located: list[tuple[list[str], str]] = []
for b in vol["blocks"]:
if b.get("type") == "table":