textmachine/eval/exp14b_score.py

108 lines
6.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""exp14b — детерминированный скоринг DET-классов (a/b/c/d): извлекает span-рендер трапа в
каждом арме (C/F/X) + применяет правило (полярность/число/ранг/направление) → fix/fail/ambiguous
+ печатает span для верификации. $0. Реюз P0 для контраста где есть.
SOFT-классы (e/f) — отдельным судейским проходом (exp14b_judge.py).
"""
from __future__ import annotations
import json, re
from pathlib import Path
ARMS = Path("/home/ubuntu/books/gu-zhenren/exp14b/arms")
EX14 = Path("/home/ubuntu/books/gu-zhenren/exp14/arms")
# DET-трапы: (id, chunk, anchor-regex для поиска строки, rule(line)->'fix'/'fail'/'?', описание)
def rule_polarity_all(line):
l = line.lower()
if re.search(r'ни один|никто|не знал ни|ни одного человека не', l): return 'fail'
if re.search(r'все |всем |каждый|мёртв|не было ни одного,? (кто|который)', l): return 'fix'
return '?'
def rule_polarity_envy(line):
l = line.lower()
if 'завист' not in l and 'зависти' not in l and 'зависть' not in l: return '?'
if re.search(r'никто не|не было зависти|без зависти', l): return 'fail'
return 'fix' # завист присутствует в утвердит. контексте
def rule_num_b1(line):
l = line.lower()
# 十二分之一 = 1/12 ; 三成 = 30%
has12 = bool(re.search(r'одну двенадцатую|двенадцатую часть|1/12|двенадцатой', l))
has30 = bool(re.search(r'тридцать процент|30\s*%|три десят|целых три десят', l))
bad = bool(re.search(r'\b3\s*%|\ри процент', l)) # 三成→3% = 10× ошибка
if bad and not has30: return 'fail'
if has30 or has12: return 'fix'
return '?'
def rule_num_b2(line):
l = line.lower()
if re.search(r'сорок четыре процент|44\s*%|сорока четырёх процент', l): return 'fix'
if re.search(r'4[.,]4|\b4\s*%|четыре процент', l): return 'fail'
return '?'
def rule_rank_above(line):
l = line.lower()
if re.search(r'среди людей|одним из', l): return 'fail'
if re.search(r'над людьми|выше (других|прочих|обычных)|возвыс|человеком над', l): return 'fix'
return '?'
def rule_grade_bing(line):
l = line.lower()
# 丙 = 3-й = В/C ; ошибка = Б/второй/A/B
if re.search(r'разряд[а-я]* в|класс[а-я]* в|«в»|третьего разряда|третий разряд', l): return 'fix'
if re.search(r'разряд[а-я]* б|класс[а-я]* б|«б»|второго разряда', l): return 'fail'
return '?'
def rule_patriarch(line):
l = line.lower()
if re.search(r'старейшин', l): return 'fail'
if re.search(r'глав[аеуы].{0,20}поколени|четвёртого поколения|патриарх|четвёртый глава', l): return 'fix'
return '?'
def rule_counterfactual(line):
l = line.lower()
if re.search(r'иначе|в противном случае|не то|если бы не', l): return 'fix'
return '?'
TRAPS = [
("a1", "7.0", r'Гуюэ.*(знал|извест|мёртв)|(знал|извест).*Гуюэ', rule_polarity_all, "все знали (двойн.отриц.)"),
("a2", "7.0", r'завист|зависти|зависть', rule_polarity_envy, "все полны зависти (无不)"),
("b1", "10.1", r'двенадцат|тридцать|десят|процент|祭', rule_num_b1, "1/12 & 30%"),
("b2", "6.0", r'сорок|44|процент|апертур', rule_num_b2, "44%"),
("c1", "17.0", r'людьми|людей|возвыс', rule_rank_above, "над людьми (人上之人)"),
("c2", "9.1", r'Фан Юань.*(разряд|класс)|(разряд|класс).*Фан', rule_grade_bing, "丙=разряд В"),
("c3", "16.0", r'族长|поколени|старейшин|глава клана|патриарх', rule_patriarch, "族长=глава, не старейшина"),
("d1", "19.0", r'иначе|счастью|повезло|противном', rule_counterfactual, "幸亏…否则 контрфактив"),
("d2", "7.0", r'иначе|противном|揭破|разоблач', rule_counterfactual, "否则 контрфактив"),
]
ARM_MODELS = {"C": "glm-5(P1a)", "F": "gpt-5.4(F-disc)", "X": "grok-4.3(X-disc)", "P0": "prod-baseline",
"D": "deepseek-v4-pro", "K": "kimi-k2.6", "M": "mistral-large"}
ALL_ARMS = ("C", "F", "X", "D", "K", "M")
def find_span(text, anchor):
for line in text.split("\n"):
if line.strip() and re.search(anchor, line):
return line.strip()
return ""
def main():
print("=== exp14b DET-скоринг (a/b/c/d) — fix/fail/? + span ===\n")
rates = {arm: {"fix": 0, "fail": 0, "?": 0} for arm in ALL_ARMS}
for tid, cid, anchor, rule, desc in TRAPS:
print(f"── {tid} ch{cid} [{desc}] ──")
for arm in ("P0",) + ALL_ARMS:
base = ARMS if arm in ALL_ARMS else EX14
fp = base / arm / f"{cid}.txt" if arm != "P0" else EX14 / "P0" / f"{cid}.txt"
if not fp.exists():
print(f" {arm:<3}"); continue
span = find_span(fp.read_text(encoding="utf-8"), anchor)
verd = rule(span) if span else '?'
if arm in rates: rates[arm][verd] += 1
mark = {"fix": "", "fail": "✗КАТ" if tid in ("a1","a2","c1") else "", "?": "?"}[verd]
print(f" {arm:<3} {mark:<5} {span[:88] if span else '(span не найден)'}")
print()
print("=== FIX-RATE по армам (DET, 9 инстансов) ===")
for arm in ALL_ARMS:
r = rates[arm]; n = sum(r.values())
print(f" {arm} {ARM_MODELS[arm]:<18}: fix {r['fix']}/{n}, fail {r['fail']}, ? {r['?']}")
json.dump(rates, open(Path("/home/ubuntu/books/gu-zhenren/exp14b")/"det_rates.json","w"), ensure_ascii=False, indent=1)
if __name__ == "__main__":
main()