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