#!/usr/bin/env python3 """Генератор страницы слепого сравнения редакторов (эксперимент 04). Читает eval/data/editor_bench//*.txt, применяет слепую разметку A/B/C/D, эмитит самодостаточный HTML (контент для Artifact-обёртки). Запуск: eval/.venv/bin/python eval/build_editor_artifact.py """ import html import json import re import sys from pathlib import Path ROOT = Path(__file__).resolve().parent BENCH = ROOT / "data" / "editor_bench" # Слепая разметка: фиксированный перемешанный порядок (одинаков на всех фрагментах) BLIND = [("A", "grok-4.3"), ("B", "glm-4.6"), ("C", "gemini-3.1-pro"), ("D", "kimi-k2.6")] FRAGMENTS = [ ("rashomon", "японский → русский", "Акутагава Рюноскэ, «Расёмон»", False), ("ahq", "китайский → русский", "Лу Синь, «Подлинная история А-Кью»", False), ("merosu", "японский → русский", "Дадзай Осаму, «Беги, Мелос!»", False), ("wells", "английский → русский", "Г. Уэллс, «Машина времени»", True), ] # стоимость/латентность из results.json (последний прогон) — для секции-расшифровки COST = {"grok-4.3": "~13 с, без «размышлений» — самый быстрый", "glm-4.6": "~105 с (thinking off)", "gemini-3.1-pro": "~60–86 с, обязательный thinking", "kimi-k2.6": "~150 с, ~11 000 токенов «размышлений» на абзац — самый дорогой"} def paras(text: str) -> str: text = text.strip() blocks = re.split(r"\n\s*\n", text) or [text] if len(blocks) == 1: blocks = [b for b in text.split("\n") if b.strip()] return "".join(f"

{html.escape(b.strip())}

" for b in blocks if b.strip()) def read(frag: str, name: str) -> str: f = BENCH / frag / f"{name}.txt" return f.read_text() if f.exists() else "" def main() -> None: out = Path(sys.argv[1]) S = [] S.append("Сравнение редакторов ru-стиля · Эксперимент 04") S.append(f"") S.append('
') S.append('
') S.append('

TextMachine · полигон · эксперимент 04

') S.append("

Кто лучше правит русский стиль

") S.append('

Четыре модели-редактора получили один и тот же черновой перевод и ' 'задание: превратить его в живую художественную прозу, ничего не переврав. ' 'Версии обезличены — A, B, C, D. Оцени по двум осям:

') S.append('
    ' '
  • стиль — какая русская проза читается живее, без канцелярита и калек;
  • ' '
  • верность — кто не потерял смысл и не добавил отсебятины (сверяй со «смысловым якорем»).
  • ' '
') S.append('

Названия моделей и цена — внизу, под ключом-расшифровкой, чтобы не влияли на оценку. ' 'Фрагмент Уэллса — англ.→рус.: оригинал перед тобой, судишь и стиль, и верность напрямую.

') S.append("
") for i, (frag, direction, work, is_en) in enumerate(FRAGMENTS, 1): anchor = read(frag, "source") if is_en else read(frag, "english_ref") anchor_label = "Оригинал (английский)" if is_en else "Смысловой якорь — дословный английский перевод" draft = read(frag, "draft") S.append('
') S.append(f'
{i:02d}' f'

{html.escape(direction)}

' f'

{html.escape(work)}

' + ('твой полный контроль' if is_en else '') + '
') S.append('
Смысловой якорь и черновик' '
') S.append(f'

{html.escape(anchor_label)}

' f'
{paras(anchor)}
') S.append(f'

Черновой перевод (вход редактора)

' f'
{paras(draft)}
') S.append("
") S.append('
') for letter, model in BLIND: txt = read(frag, model) S.append(f'
{letter}
' f'
{paras(txt)}
') S.append("
") S.append("
") # ключ-расшифровка + стоимость (свёрнуто) S.append('
Показать расшифровку и стоимость') S.append('') for letter, model in BLIND: S.append(f'' f'') S.append("
МеткаМодельЛатентность / цена редактуры
{letter}{html.escape(model)}{html.escape(COST.get(model, ""))}
") S.append('

Черновик — DeepSeek (на китайском А-Кью DeepSeek «эхал» оригинал, ' 'поэтому там черновик от GLM — деталь для бэкенда, на сравнение редакторов не влияет). ' 'Латентность — на коротком фрагменте; в продакшене всё идёт батчем.

') S.append("
") S.append('') S.append("
") out.write_text("\n".join(S), encoding="utf-8") print("written", out, out.stat().st_size, "bytes") CSS = r""" :root{ --paper:#f7f6f3; --surface:#fffffe; --ink:#1c1b19; --muted:#6f6b64; --hair:#e4e0d8; --accent:#9e2b25; --accent-soft:#f0e2df; --style:#3f6f52; --fidel:#9e2b25; --serif:Georgia,'Iowan Old Style','Times New Roman',serif; --sans:system-ui,-apple-system,'Segoe UI',Roboto,sans-serif; --measure:64ch; } @media (prefers-color-scheme:dark){ :root{--paper:#17181a; --surface:#1f2124; --ink:#e9e7e2; --muted:#9a978f; --hair:#31343a; --accent:#d67a72; --accent-soft:#33231f; --style:#7db38f; --fidel:#d67a72;} } :root[data-theme="light"]{--paper:#f7f6f3; --surface:#fffffe; --ink:#1c1b19; --muted:#6f6b64; --hair:#e4e0d8; --accent:#9e2b25; --accent-soft:#f0e2df; --style:#3f6f52; --fidel:#9e2b25;} :root[data-theme="dark"]{--paper:#17181a; --surface:#1f2124; --ink:#e9e7e2; --muted:#9a978f; --hair:#31343a; --accent:#d67a72; --accent-soft:#33231f; --style:#7db38f; --fidel:#d67a72;} *{box-sizing:border-box;} body{margin:0;background:var(--paper);color:var(--ink);font-family:var(--sans); line-height:1.5;-webkit-font-smoothing:antialiased;} .wrap{max-width:1080px;margin:0 auto;padding:clamp(1.2rem,4vw,3.5rem) clamp(1rem,4vw,2rem) 4rem;} .eyebrow{font-size:.72rem;letter-spacing:.14em;text-transform:uppercase;color:var(--accent); font-weight:600;margin:0 0 .5rem;} h1{font-family:var(--serif);font-weight:600;font-size:clamp(1.9rem,5vw,3rem);line-height:1.08; margin:.1rem 0 .8rem;text-wrap:balance;letter-spacing:-.01em;} .lede{font-size:1.08rem;max-width:var(--measure);color:var(--ink);margin:0 0 1rem;} .criteria{list-style:none;padding:0;margin:0 0 1.2rem;max-width:var(--measure); display:flex;flex-direction:column;gap:.4rem;} .criteria li{padding-left:.2rem;color:var(--muted);} .k{display:inline-block;font-family:var(--sans);font-size:.72rem;font-weight:700;text-transform:uppercase; letter-spacing:.08em;padding:.12em .5em;border-radius:3px;margin-right:.4em;color:#fff;} .k-style{background:var(--style);} .k-fidel{background:var(--fidel);} .note{font-size:.9rem;color:var(--muted);max-width:var(--measure);} .intro{border-bottom:1px solid var(--hair);padding-bottom:2rem;margin-bottom:2.5rem;} .frag{margin:0 0 3.5rem;} .frag-head{display:flex;align-items:baseline;gap:1rem;margin-bottom:1.1rem;flex-wrap:wrap;} .fnum{font-family:var(--serif);font-size:1.5rem;color:var(--accent);font-weight:600; font-variant-numeric:tabular-nums;line-height:1;} .frag-head h2{font-family:var(--serif);font-weight:600;font-size:1.4rem;margin:.1rem 0 0;letter-spacing:-.01em;} .badge{margin-left:auto;align-self:center;font-size:.7rem;font-weight:600;text-transform:uppercase; letter-spacing:.08em;color:var(--accent);background:var(--accent-soft); padding:.28em .7em;border-radius:999px;} .context{margin:0 0 1.3rem;border:1px solid var(--hair);border-radius:10px;background:var(--surface);} .context>summary{cursor:pointer;padding:.75rem 1rem;font-size:.82rem;font-weight:600; text-transform:uppercase;letter-spacing:.06em;color:var(--muted);list-style:none;} .context>summary::-webkit-details-marker{display:none;} .context>summary::before{content:"▸ ";color:var(--accent);} .context[open]>summary::before{content:"▾ ";} .context-grid{display:grid;grid-template-columns:1fr 1fr;gap:1.5rem;padding:0 1rem 1.1rem;} .ctx-label{font-size:.7rem;text-transform:uppercase;letter-spacing:.07em;color:var(--accent); font-weight:600;margin:0 0 .4rem;} @media(max-width:680px){.context-grid{grid-template-columns:1fr;}} .versions{display:grid;grid-template-columns:1fr 1fr;gap:1.1rem;} @media(max-width:760px){.versions{grid-template-columns:1fr;}} .ver{background:var(--surface);border:1px solid var(--hair);border-radius:12px; padding:1.1rem 1.3rem 1.3rem;position:relative;} .ver-head{margin:0 0 .5rem;} .chip{display:inline-flex;align-items:center;justify-content:center;width:2rem;height:2rem; font-family:var(--serif);font-weight:700;font-size:1.05rem;color:#fff;background:var(--accent); border-radius:50%;} .chip.sm{width:1.6rem;height:1.6rem;font-size:.85rem;} .prose{font-family:var(--serif);font-size:1.02rem;line-height:1.62;max-width:var(--measure);} .prose p{margin:0 0 .75em;} .prose p:last-child{margin-bottom:0;} .prose.muted{color:var(--muted);font-size:.94rem;line-height:1.55;} .reveal{margin:2rem 0 0;border:1px solid var(--hair);border-radius:10px;background:var(--surface);} .reveal>summary{cursor:pointer;padding:.9rem 1.1rem;font-weight:600;color:var(--accent);list-style:none;} .reveal>summary::-webkit-details-marker{display:none;} .reveal>summary::before{content:"🔑 ";} .reveal table{width:100%;border-collapse:collapse;margin:0 0 .5rem;} .reveal th,.reveal td{text-align:left;padding:.55rem 1.1rem;border-top:1px solid var(--hair);font-size:.92rem;} .reveal th{font-size:.72rem;text-transform:uppercase;letter-spacing:.06em;color:var(--muted);font-weight:600;} .chip-cell{width:3rem;} .mono{font-family:ui-monospace,'SF Mono',Menlo,monospace;font-size:.88rem;} .reveal .note{padding:0 1.1rem 1rem;} .foot{margin-top:2.5rem;padding-top:1.5rem;border-top:1px solid var(--hair);color:var(--muted);font-size:.9rem;} a:focus-visible,summary:focus-visible{outline:2px solid var(--accent);outline-offset:3px;border-radius:4px;} @media(prefers-reduced-motion:reduce){*{transition:none!important;animation:none!important;}} """ if __name__ == "__main__": main()