#!/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 dst [ 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"])