#!/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()