#!/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данн(ый|ая|ое|ые|ого|ому)\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несмотря на то,? что\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()