ontology_p2_verify.py 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. """P2 验收器: dsh 输出的答案 id 提取 → 本体机器对拍 (验收=句句可溯 + 判级词与对象一致, 非人读).
  4. 用法: .venv/bin/python scripts/ontology_p2_verify.py <dsh输出文件>"""
  5. import json, pathlib, re, sys
  6. ROOT = pathlib.Path(__file__).resolve().parent.parent
  7. OBJ = json.loads((ROOT / 'outputs/rudong/ontology/objects.json').read_text())
  8. # 事实契约 id (claim/<claim_id>): 离线问答接契约后 (2026-09-06) 答案会引用它, 对象不在本体而在契约里.
  9. _CP = ROOT / 'outputs/rudong/guanlan/facts_contract_v0.json'
  10. CLAIMS = {c['claim_id']: c for c in json.loads(_CP.read_text(encoding='utf-8')).get('claims', [])} if _CP.is_file() else {}
  11. def _unit_aliases():
  12. try:
  13. import re as _re
  14. from src.windscada.deid import unit_map
  15. ids = sorted(set(_re.findall(r'WTG(\d{2})', '\n'.join(OBJ.keys()))))
  16. return {alias: 'WTG' + raw for raw, alias in unit_map(ids).items()}
  17. except Exception:
  18. return {}
  19. UNIT_ALIAS = _unit_aliases()
  20. def _canonical_id(oid):
  21. out = oid
  22. for alias, raw in sorted(UNIT_ALIAS.items(), key=lambda kv: -len(kv[0])):
  23. out = re.sub(r'(?<![A-Za-z0-9])' + re.escape(alias) + r'(?![A-Za-z0-9])', raw, out)
  24. out = out.replace('/site/', '/rudong/')
  25. return out
  26. LEVEL_EN = {
  27. '优秀': ('Excellent',),
  28. '良好': ('Good',),
  29. '报警': ('Alarm',),
  30. '危险': ('Critical',),
  31. '不可判': ('Not evaluable', 'Indeterminate', 'Undeterminable'),
  32. '预警': ('Alert', 'Warning', 'Early warning'),
  33. '定论': ('Established', 'Confirmed'),
  34. '候选': ('Candidate',),
  35. '观察': ('Observation', 'Watch'),
  36. '监视': ('Watch', 'Monitoring'),
  37. '销案': ('Closed',),
  38. '参考': ('Reference',),
  39. '正样本': ('Healthy reference', 'Positive sample'),
  40. '仲裁输入': ('Arbitration input',),
  41. }
  42. def _level_tokens(level, lang):
  43. if lang != 'en':
  44. return (level,)
  45. vals = []
  46. for zh, ens in LEVEL_EN.items():
  47. if zh in (level or ''):
  48. vals.extend(ens)
  49. return tuple(dict.fromkeys(vals)) or ((level,) if level else ())
  50. def verify(text, lang='zh'):
  51. ids = re.findall(r'\[\s*(?:对象id|对象|来源|出处|id)?\s*[::]?\s*([a-z]+/[A-Za-z0-9_/.\-]+)\s*\]', text or '')
  52. ids = [i for i in ids if '/' in i]
  53. seen = list(dict.fromkeys(ids))
  54. rows, n_ok = [], 0
  55. canonical = {}
  56. for i in seen:
  57. ci = _canonical_id(i)
  58. canonical[i] = ci
  59. if i.startswith('claim/'):
  60. c = CLAIMS.get(i[6:])
  61. if not c:
  62. rows.append((i, 'MISSING', '契约里无此结论编号 — 模型编造 id'))
  63. else:
  64. ok = c['verdict'] in (text or '')
  65. rows.append((i, 'OK' if ok else 'LEVEL_MISMATCH', f"契约裁决='{c['verdict']}' {'出现在答案中' if ok else '未在答案出现 — 判级词疑被改写'}"))
  66. n_ok += ok
  67. continue
  68. if ci not in OBJ:
  69. rows.append((i, 'MISSING', '对象不存在 — 模型编造 id'))
  70. continue
  71. o = OBJ[ci]
  72. lv = o.get('props', {}).get('level')
  73. if o['type'] == 'Verdict' and lv:
  74. toks = _level_tokens(lv, lang)
  75. ok = (not toks) or any(t in (text or '') for t in toks)
  76. rows.append((i, 'OK' if ok else 'LEVEL_MISMATCH',
  77. f"对象判级='{lv}' {'有对应判级词出现在答案中' if ok else '未在答案出现 — 判级词疑被改写'}"))
  78. n_ok += ok
  79. else:
  80. rows.append((i, 'OK', f"{o['type']} 存在"))
  81. n_ok += 1
  82. verdict_cited = any(OBJ.get(canonical.get(i, i), {}).get('type') == 'Verdict' or (i.startswith('claim/') and i[6:] in CLAIMS) for i in seen)
  83. gate = (('claim_check' in (text or '') and '通过' in (text or '')) or
  84. ('grounding gate' in (text or '').lower() and 'passed' in (text or '').lower()))
  85. ok_all = bool(seen) and all(r[1] == 'OK' for r in rows) and verdict_cited
  86. return dict(引用id数=len(seen), 全通过=ok_all, 引用了判级对象=verdict_cited,
  87. 接地闸出现=gate, 明细=rows)
  88. if __name__ == '__main__':
  89. p = pathlib.Path(sys.argv[1])
  90. lang = sys.argv[2] if len(sys.argv) > 2 else 'zh'
  91. r = verify(p.read_text(), lang=lang)
  92. print(json.dumps(r, ensure_ascii=False, indent=1, default=str))
  93. sys.exit(0 if r['全通过'] else 2)