textmachine/eval/exp12/exp12_rootcause.py

89 lines
4.7 KiB
Python
Raw Permalink 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
"""exp12 — предварительный АВТО-разбор draft→final по всем 25 главам (затравка root-cause).
Вопрос: редактор glm-5 «ухудшил», «не спас» или «пассивен»? Меряем:
- similarity(draft, final): насколько редактор ВООБЩЕ менял черновик (difflib word-ratio).
~1.0 = редактор почти ничего не сделал (пассивен) → нечитаемость унаследована от черновика.
- len_ratio = chars(final)/chars(draft).
- translationese-маркеры (канцелярит/кальки) в draft и final → снизил ли редактор.
- дефис-диалоги: доля реплик, начатых '-' (дефис) вместо '' (em-dash) — Розенталь §47.
- CJK-остаток в final.
$0. Вход: diag/chunks.json. Выход: diag/rootcause_auto.json + печать сводки.
"""
from __future__ import annotations
import json, re
from difflib import SequenceMatcher
from pathlib import Path
DIAG = Path("/home/ubuntu/books/gu-zhenren/diag")
CJK = re.compile("[一-鿿぀-ヿ]")
# грубые маркеры translationese/канцелярита (RU) — не приговор, а сигнал тренда
CLERICAL = [r"\bявлял?ся\b", r"\bявляется\b", r"\bявляются\b", r"\bосуществля", r"\анн(ый|ая|ое|ые|ого|ому)\b",
r"\bв связи с\b", r"\bпо причине\b", r"\bв случае\b", r"\bпредставля(ет|л) собой\b",
r"\bбыл(а|о|и)? (?:вынужден|способ)", r"\bимел(а|о|и)? место\b", r"\bс целью\b",
r"\bв результате\b", r"\bпосредством\b", r"\есмотря на то,? что\b"]
CLERICAL_RE = re.compile("|".join(CLERICAL), re.I)
# начало прямой речи дефисом вместо тире
DASH_HYPHEN = re.compile(r"(?m)^\s*-\s+[А-ЯЁA-Z]")
DASH_EM = re.compile(r"(?m)^\s*—\s+[А-ЯЁA-Z]")
def words(t: str) -> list[str]:
return re.findall(r"\w+", t.lower())
def marker_count(t: str) -> int:
return len(CLERICAL_RE.findall(t))
def main() -> None:
chunks = json.loads((DIAG / "chunks.json").read_text(encoding="utf-8"))
rows = []
for key, v in chunks.items():
d, f = v["draft"], v["final"]
if not d.strip() or not f.strip():
continue # flagged/skipped edit
sim = SequenceMatcher(None, words(d), words(f)).ratio()
rows.append({
"chunk": key, "chapter": v["chapter"],
"sim": round(sim, 3),
"len_ratio": round(len(f) / max(1, len(d)), 3),
"cler_draft": marker_count(d), "cler_final": marker_count(f),
"hyphen_dlg_final": len(DASH_HYPHEN.findall(f)),
"em_dlg_final": len(DASH_EM.findall(f)),
"cjk_final": len(CJK.findall(f)),
})
n = len(rows)
avg = lambda k: round(sum(r[k] for r in rows) / n, 3)
tot = lambda k: sum(r[k] for r in rows)
summary = {
"n_chunks": n,
"avg_similarity_draft_final": avg("sim"),
"median_similarity": sorted(r["sim"] for r in rows)[n // 2],
"chunks_sim_ge_0.85 (editor near-passive)": sum(1 for r in rows if r["sim"] >= 0.85),
"chunks_sim_ge_0.95 (editor barely touched)": sum(1 for r in rows if r["sim"] >= 0.95),
"avg_len_ratio_final_draft": avg("len_ratio"),
"clerical_markers_draft_total": tot("cler_draft"),
"clerical_markers_final_total": tot("cler_final"),
"clerical_reduced_by_editor": tot("cler_draft") - tot("cler_final"),
"hyphen_dialogue_final_total": tot("hyphen_dlg_final"),
"em_dialogue_final_total": tot("em_dlg_final"),
"cjk_residue_final_total": tot("cjk_final"),
}
(DIAG / "rootcause_auto.json").write_text(
json.dumps({"summary": summary, "per_chunk": rows}, ensure_ascii=False, indent=1), encoding="utf-8")
print("=== AUTO root-cause (draft→final glm-5, all clean chunks) ===")
for k, val in summary.items():
print(f" {k}: {val}")
print("\n По similarity (насколько редактор менял черновик), топ-пассивные:")
for r in sorted(rows, key=lambda x: -x["sim"])[:6]:
print(f" {r['chunk']}: sim={r['sim']} len={r['len_ratio']} cler {r['cler_draft']}{r['cler_final']}")
print(" Топ-активные (редактор много менял):")
for r in sorted(rows, key=lambda x: x["sim"])[:6]:
print(f" {r['chunk']}: sim={r['sim']} len={r['len_ratio']} cler {r['cler_draft']}{r['cler_final']}")
if __name__ == "__main__":
main()