textmachine/eval/premise_review/recount.py

91 lines
4.1 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
"""НЕЗАВИСИМЫЙ пере-счёт пробы C: свой парсер, своя нормализация, свой знаменатель.
Цель — не «повторить 25/45», а проверить число ДРУГИМ кодом. Ничего не импортирую из
`arbitrate.py`: если унаследованный парсер систематически ошибается, повтор его же логикой
это не покажет. Совпадение чисел = подтверждение; расхождение = дефект посылки пробы C.
"""
from __future__ import annotations
import json
import re
import sys
import unicodedata
from collections import Counter
from pathlib import Path
RAW = Path.home() / "books" / "gu-zhenren" / "bank-arbitration"
GOLDF = Path("/home/ubuntu/projects/textmachine/eval/bank_arbitration/gold/gold.jsonl")
def norm(s: str) -> str:
"""Нормализация цели: NFKC, регистр, ё→е, снять кавычки/дефисные варианты, схлопнуть пробелы."""
s = unicodedata.normalize("NFKC", s or "").casefold().replace("ё", "е")
s = re.sub(r"[«»\"'`]", "", s)
return " ".join(s.split())
def parse(tag: str) -> tuple[dict[str, str], float, dict]:
"""Свой парсер: строка = src <TAB> dst [<TAB> conf]. Табов нет — берём двойной пробел/'|'."""
out: dict[str, str] = {}
cost = 0.0
diag = Counter()
for f in sorted(RAW.glob(f"{tag}-b*.json")):
rec = json.loads(f.read_text(encoding="utf-8"))
cost += rec["cost_usd"]
for ln in rec["content"].split("\n"):
ln = ln.strip()
if not ln or ln.startswith("#") or ln.startswith("`"):
diag["skip_empty_or_comment"] += 1
continue
parts = [p.strip() for p in re.split(r"\t| {2,}|\s*\|\s*", ln) if p.strip()]
if len(parts) < 2:
diag["skip_lt2fields"] += 1
continue
src, dst = parts[0], parts[1]
# третье поле — уверенность (число или слово); в dst её быть не должно
if re.fullmatch(r"\d{1,3}", dst) and len(parts) >= 3:
diag["conf_in_second_field"] += 1
dst = parts[2]
if src in out and out[src] != dst:
diag["dup_conflict"] += 1
out[src] = dst
diag["parsed"] += 1
return out, cost, dict(diag)
def main(tags: list[str]) -> None:
gold = [json.loads(l) for l in GOLDF.read_text(encoding="utf-8").splitlines() if l.strip()]
scored = [g for g in gold if g.get("gold_dst")]
print(f"голд: строк {len(gold)}, с подписанным gold_dst {len(scored)}, "
f"из них in_bank {sum(1 for g in scored if g.get('in_bank'))}")
print()
hdr = f"{'пасс':10s} {'строк':>6s} {'точно':>6s} {'+флексии':>9s} {'нет ответа':>11s} {'$':>9s}"
print(hdr)
print("-" * len(hdr))
for tag in tags:
got, cost, diag = parse(tag)
exact = infl = missing = 0
misses = []
for g in scored:
src = g["src"]
if src not in got:
missing += 1
continue
d = norm(got[src])
if d == norm(g["gold_dst"]):
exact += 1
elif any(d == norm(a) for a in g.get("gold_alt", [])):
infl += 1
else:
misses.append((src, got[src], g["gold_dst"]))
print(f"{tag:10s} {len(got):6d} {exact:6d} {exact + infl:9d} {missing:11d} ${cost:8.6f}")
if "-v" in sys.argv:
print(f" диагностика парсера: {diag}")
for m in misses[:8]:
print(f" мимо: {m[0]} → «{m[1]}» (голд «{m[2]}»)")
print(f"\nзнаменатель для счёта: {len(scored)}")
if __name__ == "__main__":
main([a for a in sys.argv[1:] if not a.startswith("-")] or
["glm1", "gro1", "mis1", "ds1", "fdp", "flu", "fte"])