#!/usr/bin/env python3 """ЭНДПОЙНТ ДИЗАЙНА Д (пре-рег Д47): РЕЖЕТ ЛИ `reasoning_effort:"low"` РАЗМЫШЛЕНИЕ У `pro`. $0. ⚠ ЗАЧЕМ ОТДЕЛЬНЫЙ ФАЙЛ, а не счёт в уме отчёта. Правило решения Д47.3 написано ДО чисел, и оно обязано применяться механически, иначе «правило до чисел» превращается в «правило под числа». Файл написан ДО прихода клеток и коммитится вместе с ними: кто угодно пере-снимает вывод одной командой и видит ровно то, что видел автор. ЧТО ЗДЕСЬ ЕСТЬ И ЧЕГО НЕТ: * есть парный знаковый тест по восьми главам (`reasoning_tokens` у `RNL` против `RN2`); * есть по-клеточный гейт годности В ТОЙ ФОРМЕ, В КАКОЙ ОН ЗАМОРОЖЕН, включая проверку провенанса `Δprompt_tokens ∈ [60,100]` — она ловит клетку, до которой параметр НЕ доехал; * НЕТ ни одной ручки, которой можно подвинуть порог: α, полосы и список глав — константы файла, и селфтест стережёт, что они те же, что в отчёте. ⛔ ОТНОШЕНИЯ (×N) ПЕЧАТАЮТСЯ КАК ОПИСАНИЕ, А НЕ КАК ВЫВОД. Дизайн Д стоит на восьми ДЕШЁВЫХ главах (10.2% цены панели) — переносить их величины на дорогую моду запрещено там же, где выбраны главы. Запуск: eval/.venv/bin/python eval/dovodka/stupen.py [--selftest] """ from __future__ import annotations import json import math import sys from pathlib import Path ZONE = Path(__file__).resolve().parent sys.path.insert(0, str(ZONE)) import rol # noqa: E402 OUT = Path.home() / "books" / "dovodka" # ── КОНСТАНТЫ ПРЕ-РЕГА Д47. Меняются только вместе с отчётом и только ДО покупок ─────────────── CHAPTERS = ("b065a92dc0", "5a8f48d24a", "100b088f71", "49ca859c94", "8e50448cea", "c3517980c7", "3bd6481182", "26c3bff1d4") ARM_HIGH, ARM_LOW = "RN2", "RNL" # контроль (ручка не шлётся) и испытуемый (`low`) ALPHA = 0.05 # двусторонний, Д47.3 DPT_BAND = (60, 100) # провенанс параметра: ожидается 79 (Д46.1) CYR_MIN = 0.99 # доля кириллицы среди букв LAT_MAX = 0 # латиница-эхо def _letters(t: str) -> tuple[int, int, int]: """(букв всего, кириллических, латинских).""" al = [c for c in t if c.isalpha()] cyr = sum(1 for c in al if "а" <= c.lower() <= "я" or c.lower() == "ё") lat = sum(1 for c in al if "a" <= c.lower() <= "z") return len(al), cyr, lat def _cell(arm: str, uid: str) -> dict | None: f = OUT / f"{rol.tag('en', arm, uid)}.json" return json.loads(f.read_text(encoding="utf-8")) if f.exists() else None def sign_p(k: int, n: int) -> float: """Точный двусторонний знаковый тест при p=0.5. k — сколько пар легли в ожидаемую сторону.""" if n == 0: return 1.0 tail = sum(math.comb(n, i) for i in range(k, n + 1)) / 2 ** n return min(1.0, 2 * tail) def main() -> int: rows, missing = [], [] for uid in CHAPTERS: hi, lo = _cell(ARM_HIGH, uid), _cell(ARM_LOW, uid) if not hi or not lo: missing.append(uid) continue n_al, cyr, lat = _letters(lo.get("content") or "") share = cyr / n_al if n_al else 0.0 dpt = hi["prompt_tokens"] - lo["prompt_tokens"] bad = [] if hi.get("finish") != "stop" or lo.get("finish") != "stop": bad.append("finish≠stop") if share < CYR_MIN: bad.append(f"кириллица {share:.3f}") if lat > LAT_MAX: bad.append(f"латиница {lat}") if not DPT_BAND[0] <= dpt <= DPT_BAND[1]: bad.append(f"Δpt {dpt} вне [{DPT_BAND[0]},{DPT_BAND[1]}] — параметр не доехал") rows.append(dict(uid=uid, hi=hi["reasoning_tokens"], lo=lo["reasoning_tokens"], dpt=dpt, cyr=share, lat=lat, bad=bad, cost=hi["cost_usd"] + lo["cost_usd"])) if missing: print(f"⛔ клеток нет у глав: {' '.join(missing)} — эндпойнт неполон") if not rows: return 1 print(f"{'глава':12}{'RN2 дум':>9}{'RNL дум':>9}{'отнош':>8}{'Δpt':>6}{'кирилл':>8}" f"{'лат':>5} гейт") for r in rows: ratio = r["hi"] / r["lo"] if r["lo"] else float("inf") print(f"{r['uid']:12}{r['hi']:>9}{r['lo']:>9}{ratio:>8.2f}{r['dpt']:>6}" f"{r['cyr']:>8.3f}{r['lat']:>5} " f"{'OK' if not r['bad'] else '⛔ ' + ' · '.join(r['bad'])}") good = [r for r in rows if not r["bad"]] n = len(good) k = sum(1 for r in good if r["lo"] < r["hi"]) p = sign_p(k, n) ratios = sorted(r["hi"] / r["lo"] for r in good if r["lo"]) med = ratios[len(ratios) // 2] if ratios else float("nan") print(f"\nгодных пар {n} из {len(CHAPTERS)} · размышление у `low` МЕНЬШЕ в {k} из {n}") print(f"знаковый тест, двусторонний: p = {p:.4f} при пороге α = {ALPHA}") print(f"цена прогона: ${sum(r['cost'] for r in rows):.6f}") # ── ПРАВИЛО Д47.3. Ветки написаны до чисел; ни одна из них ничего не «дозакупает» ────────── print() if n < len(CHAPTERS): print("⚠ ПАР МЕНЬШЕ ВОСЬМИ: правило Д47.3 писалось на n=8. Вывод печатается по факту " "годных пар и объявляется НЕПОЛНЫМ — добор до восьми ПОСЛЕ взгляда на числа запрещён.") if k == n and p < ALPHA: print(f"⭐ ЕДИНОГЛАСНО И ЗНАЧИМО: ручка РЕЖЕТ размышление. Медиана отношения ×{med:.2f} — " "ОПИСАНИЕ этих восьми дешёвых глав, не перенос на дорогую моду и не на пару zh.") elif p >= ALPHA: print(f"⛔ НЕ ЗНАЧИМО (p = {p:.4f} ≥ {ALPHA}): «не установлено». Это РЕЗУЛЬТАТ, а не повод " "докупать девятую главу — добор до значимости после чисел запрещён (Д47.3).") else: print(f"⛔ ЗНАЧИМО В ОБРАТНУЮ СТОРОНУ (k={k} из {n}): ручка размышление НЕ режет.") return 0 def selftest() -> int: bad = 0 def ck(ok: bool, what: str) -> None: nonlocal bad print(f"[{'OK ' if ok else 'ПРОВАЛ'}] {what}") bad += 0 if ok else 1 ck(len(CHAPTERS) == 8 and len(set(CHAPTERS)) == 8, "восемь глав пре-рега, без повторов") rep = (ZONE.parents[1] / "docs" / "experiments" / "23-editor-tier.md").read_text(encoding="utf-8") ck(all(u in rep for u in CHAPTERS), "все восемь глав названы в отчёте (пре-рег Д47.2)") ck(ARM_HIGH in rol.ARMS and ARM_LOW in rol.ARMS, "оба арма есть в реестре покупок") ck(rol.THINK_LEVEL.get(ARM_HIGH) is None and rol.THINK_LEVEL.get(ARM_LOW) == "low", "контроль без ручки, испытуемый на `low`") ck(rol.ARMS[ARM_HIGH][0] == rol.ARMS[ARM_LOW][0] == "deepseek-v4-pro", "модель у армов одна") ck(rol.tag("en", ARM_HIGH, "x") != rol.tag("en", ARM_LOW, "x"), "теги армов не схлопываются") # правило решения обязано быть НЕ достижимо ничем, кроме единогласия — это и есть цена n=8 ck(sign_p(8, 8) < ALPHA <= sign_p(7, 8), "при n=8 значимость даёт ТОЛЬКО 8 из 8") ck(abs(sign_p(8, 8) - 0.0078125) < 1e-9, "минимальный двусторонний p посчитан верно") ck(sign_p(4, 8) == 1.0, "полный ноль эффекта даёт p = 1") print(f"\n{'ПРИБОР ГОДЕН' if not bad else f'ПРОВАЛОВ: {bad}'}") return bad if __name__ == "__main__": sys.exit(selftest() if "--selftest" in sys.argv else main())