textmachine/eval/dovodka/sessii.py

113 lines
7.5 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
"""РАСКЛАДКА СУДЕЙСКИХ ПАЧЕК ПО СЕССИЯМ и печать готовых промтов. $0.
Зачем скриптом, а не руками. Состав сессии — часть прибора, а не оформление: от него зависит,
(1) не попадут ли обе половины одной пары шумового пола к ОДНОМУ судье — тогда пол не поймает
межсессионную компоненту, и порог будет занижен, как в паке 23 на 19%; (2) одинаково ли
нагружены семейства — если Sol судит по 10 пачек за сессию, а Claude по 4, разница усталости
станет разницей семейств; (3) воспроизводима ли раскладка при пере-прогоне.
Обвязка берётся из `judge-prompts/{claude,sol}.md`, которые собраны из ОДНОГО ядра и сверяются
гейтом `paritet.py` (норма Д0.13 П-4). Здесь подставляется только список файлов.
⚠ Наборы `p1` и `p2` НИКОГДА не попадают в одну сессию: это разные половины пары пола.
Запуск: sessii.py план раскладки
sessii.py --write записать готовые промты файлами
"""
from __future__ import annotations
import sys
from pathlib import Path
ZONE = Path(__file__).resolve().parent
PROMPTS = ZONE / "judge-prompts"
# ⚠ Ось — параметр. Пачки разных осей НЕЛЬЗЯ класть в один каталог и нельзя отдавать одной
# сессии: судья, увидевший обе, перестаёт быть слепым к паре.
AXIS = next((a.split("=", 1)[1] for a in sys.argv if a.startswith("--axis=")), "d4")
# ⚠ КОРЕНЬ — ТОЖЕ ПАРАМЕТР (заведено 16.08 под заход Д17). У осей фазы раскладка каталогов
# `judging/sud-<ось>`, а у захода она на уровень глубже: `judging/z17/<пара>`, потому что пара
# там своя у каждой панели. Прежде корень выводился из имени оси, и для захода пришлось бы
# заводить ВТОРОЙ такой же скрипт — то есть размножить будущие расхождения раскладки. Флаг
# `--root=` даёт тот же механизм чужой раскладке, не копируя его.
_ROOT = next((a.split("=", 1)[1] for a in sys.argv if a.startswith("--root=")), "")
FAM = {
"claude": dict(root=(Path(_ROOT).expanduser() if _ROOT
else Path.home() / "books" / "judging" / f"sud-{AXIS}"),
tpl=PROMPTS / "claude.md"),
"sol": dict(root=Path.home() / "books" / "judging" / f"sol-{AXIS}-work", tpl=PROMPTS / "sol.md"),
}
if _ROOT:
FAM.pop("sol") # внешнее семейство пакет получает своим сборщиком
PER_SESSION = 4 # как у харнесса Sol в паке 23; одинаково для обоих семейств
MISSING_ONLY = "--missing" in sys.argv
def batches(root: Path, half: str) -> list[list[str]]:
"""Пачки набора, разложенные по сессиям. Порядок — от имени файла, значит воспроизводим.
⚠ `--missing` РАСКЛАДЫВАЕТ ТОЛЬКО НЕОТСУЖЕННОЕ, и это не удобство, а защита данных. Сессия
может оборваться на середине (16.08: семь из четырнадцати упали на лимите харнесса, успев
записать часть ответов). Пере-запуск ЦЕЛОЙ сессии переписал бы уже полученные оценки новыми —
то есть тихо подменил бы данные замера теми, что судья выдал во второй раз. Добор идёт только
по пачкам без ответа; уже отсуженное не трогается вовсе.
"""
toks = sorted(p.stem for p in (root / f"{half}-tasks").glob("*.txt"))
if MISSING_ONLY:
done = {p.stem for p in (root / f"{half}-answers").glob("*.txt")}
toks = [t for t in toks if t not in done]
return [toks[i:i + PER_SESSION] for i in range(0, len(toks), PER_SESSION)]
# Язык исходника — из провайдера пары, а не из текста шаблона: судья английской пачки, которому
# сказано «сверяй с китайским исходником», сверяет не с тем, что видит.
LANG = {"d4": "китайском", "d3": "английском", "d6": "японском", "d1": "китайском",
"z17-zh": "китайском", "z17-en": "английском"}
# ⚠ Творительный падеж — ОТДЕЛЬНАЯ подстановка. Первая редакция просто вычёркивала язык из фразы
# «сверяй смысл с китайским исходником», потому что склонение не подставлялось. Проверенная
# ревью версия называла язык ДВАЖДЫ, и обезличивание было тихой потерей якоря.
LANG_INS = {"d4": "китайским", "d3": "английским", "d6": "японским", "d1": "китайским",
"z17-zh": "китайским", "z17-en": "английским"}
def render(fam: str, half: str, toks: list[str]) -> str:
m = FAM[fam]
body = m["tpl"].read_text(encoding="utf-8")
body = (body.replace("{ЯЗЫКЕ}", LANG[AXIS]).replace("{КАТАЛОГ}", m["root"].name)
.replace("{НАБОР}", half).replace("{ЯЗЫКОМ}", LANG_INS[AXIS]))
body = body.replace(f"~/{m['root'].name}/", f"{m['root']}/")
lst = "\n".join(f" {m['root']}/{half}-tasks/{t}.txt" for t in toks)
return body.replace("{СПИСОК ФАЙЛОВ}", lst)
def main() -> int:
write = "--write" in sys.argv
total = 0
for fam, m in FAM.items():
root = m["root"]
if not root.exists():
print(f"{fam}: каталога {root} нет — пропуск")
continue
out = root / "prompts"
if write:
out.mkdir(exist_ok=True)
print(f"\n=== {fam.upper()} ({root}) ===")
n = 0
for half in ("p1", "p2"):
for i, toks in enumerate(batches(root, half), 1):
n += 1
name = f"{half}-session-{i:02d}.md"
print(f" {name:22s} пачек {len(toks)}: {' '.join(toks)}")
if write:
(out / name).write_text(render(fam, half, toks), encoding="utf-8")
total += n
print(f" сессий у семейства: {n}" + (f" · записано в {out}" if write else ""))
print(f"\nвсего сессий: {total} · по {PER_SESSION} пачки · кап агент-сессий 80")
print("⚠ наборы p1 и p2 — разные половины пары пола, одному судье их отдавать НЕЛЬЗЯ")
return 0
if __name__ == "__main__":
sys.exit(main())