108 lines
6.3 KiB
Python
108 lines
6.3 KiB
Python
#!/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*%|\bтри процент', 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()
|