Freeze judge wrappers built from one shared core with a mechanical parity gate, plus reproducible session layout keeping floor halves in different sessions
This commit is contained in:
parent
baae56a1c0
commit
b1cd892040
6 changed files with 368 additions and 0 deletions
96
eval/dovodka/contam_d.py
Normal file
96
eval/dovodka/contam_d.py
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
#!/usr/bin/env python3
|
||||
"""КОНТАМИНАЦИОННАЯ ПРОБА ФАЗЫ Д — ЖИЛЬЦАМИ и на книгах, которые фаза реально покупает. Платный.
|
||||
|
||||
Заказ (Д3 и Д6) требует пробу «ЖИЛЬЦАМИ» — правило эксп-22 §1.1: гонять теми, кто работает,
|
||||
а не удобной моделью. Непригоден по пробе → СТОП и пинг, замену без слова владельца не искать.
|
||||
|
||||
Риг НЕ пере-изобретается: берётся `eval/role_topology/contamination.py` и настраивается через
|
||||
свои глобали — тем же приёмом, каким `money_d` настраивает кассу, а `sud` — судейский риг. Метод
|
||||
пробы остаётся его: УЗНАВАНИЕ («что за произведение и кто автор») плюс КЛОУЗ ПО ИМЕНИ (из отрывка
|
||||
вырезано имя собственное, модель называет пропущенное). Пороги — его же, объявленные до данных:
|
||||
узнавание ≥3/5 = «знает о книге», клоуз ≥2/5 = «знает конкретику, то есть текст был в обучении».
|
||||
|
||||
⚠ ЧТО ЗДЕСЬ ДОБАВЛЕНО И ЗАЧЕМ.
|
||||
|
||||
1. **Книги фазы.** Прошлый прогон мерил `gu-zhenren`, `jinpingmei`, `fifty_shades`, `kristoff` и
|
||||
СТАРУЮ японскую книгу. Новая японская (`enkan_no_hate`, замена по слову владельца после
|
||||
срабатывания гардрейла 18+) не мерена ВООБЩЕ, а Д6 покупается именно на ней.
|
||||
2. **Жильцы вместо удобной модели.** Прошлый прогон гнался `gpt-5.6-luna`. Это не жилец: черновики
|
||||
делает `deepseek-v4-flash`, редактуру `deepseek-v4-pro`. Знание книги ИМИ и есть то, что
|
||||
завышает качество перевода, а знание посторонней модели не значит ничего.
|
||||
3. **Бюджет вывода поднят.** Редакция 1 прошлой пробы звала flash с 16000 токенов и получала
|
||||
0–46 знаков: у DeepSeek размышление тарифицируется и считается ВНУТРИ бюджета, поэтому на
|
||||
короткий ответ ничего не оставалось (стена quirks §10; ровно эта же стена сегодня съела 14
|
||||
клеток арма A1B). Ответ здесь нужен КОРОТКИЙ, а думать модель будет много — значит бюджет
|
||||
должен вмещать размышление ЦЕЛИКОМ, иначе «НЕ ЗНАЮ» будет означать «не успел сказать».
|
||||
⚠ Если и при поднятом бюджете жилец возвращает пустоту — это НЕ «книга чиста», а непригодность
|
||||
пробы на этом жильце, и она объявляется отдельно, а не растворяется в нуле.
|
||||
4. **Положительный контроль обязателен на КАЖДОМ жильце.** Нулевой результат неинтерпретируем:
|
||||
«модель не знает книгу» и «проба не работает на этой модели» дают одну картину. Контроль —
|
||||
`jinpingmei` (минский канон, public domain, заведомо в претрейне). Не сработал контроль —
|
||||
аннулируется весь прогон этого жильца, а не отдельная книга.
|
||||
|
||||
Запуск: contam_d.py --dry # план, $0
|
||||
contam_d.py # платный
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib.util
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
REPO = Path("/home/ubuntu/projects/textmachine")
|
||||
ZONE = Path(__file__).resolve().parent
|
||||
for d in ("dovodka", "role_topology", "tenant_panel"):
|
||||
sys.path.insert(0, str(REPO / "eval" / d))
|
||||
|
||||
|
||||
def _load(name: str, path: Path):
|
||||
spec = importlib.util.spec_from_file_location(name, path)
|
||||
mod = importlib.util.module_from_spec(spec)
|
||||
sys.modules[name] = mod
|
||||
spec.loader.exec_module(mod)
|
||||
return mod
|
||||
|
||||
|
||||
MONEY = _load("money_d", ZONE / "money_d.py")
|
||||
CT = _load("contam21", REPO / "eval" / "role_topology" / "contamination.py")
|
||||
|
||||
# Жильцы: те, кто реально работает на парах. Проба на каждом отдельно.
|
||||
RESIDENTS = ("deepseek-v4-flash", "deepseek-v4-pro")
|
||||
|
||||
# Книги, которые фаза Д покупает, плюс обязательный положительный контроль.
|
||||
BOOKS_D = {
|
||||
"enkan-ja": dict(path=Path.home() / "books" / "enkan_no_hate_ja.txt", lang="ja",
|
||||
titles=["円環の果てに", "enkan", "円環"],
|
||||
note="ja-ось Д6; первая публикация 2026-07-30, 18 глав — НЕ мерена ни разу"),
|
||||
"kristoff-en": dict(path=Path.home() / "books" /
|
||||
"Kristoff_Jay_-_Empire_of_the_Vampire_3_-_Empire_of_the_Dawn.epub",
|
||||
lang="en", titles=["empire of the dawn", "empire of the vampire",
|
||||
"кристофф", "kristoff"],
|
||||
note="en-ось Д3; luna узнала СЕРИЮ 1 раз из 5 — жильцами не мерена"),
|
||||
"jinpingmei": dict(path=Path.home() / "books" / "jinpingmei" / "jinpingmei-ctext-zhs.txt",
|
||||
lang="zh", titles=["金瓶梅", "золотой лотос", "jin ping mei"],
|
||||
note="ПОЛОЖИТЕЛЬНЫЙ КОНТРОЛЬ — обязателен на каждом жильце"),
|
||||
}
|
||||
|
||||
|
||||
def main() -> int:
|
||||
dry = "--dry" in sys.argv
|
||||
print("КОНТАМИНАЦИОННАЯ ПРОБА ФАЗЫ Д — жильцами\n")
|
||||
print(f"{'книга':14s}{'язык':>6s} что это")
|
||||
for b, m in BOOKS_D.items():
|
||||
ok = "" if m["path"].exists() else " ⛔ ФАЙЛА НЕТ"
|
||||
print(f"{b:14s}{m['lang']:>6s} {m['note']}{ok}")
|
||||
print(f"\nжильцы: {' · '.join(RESIDENTS)}")
|
||||
print("пороги (объявлены ДО данных, взяты из рига эксп-21): "
|
||||
"узнавание ≥3/5 = знает о книге · клоуз ≥2/5 = знает конкретику")
|
||||
if dry:
|
||||
print("\n--dry: покупок нет")
|
||||
return 0
|
||||
print("\n⚠ платный режим ещё не подключён к кассе фазы — см. TODO ниже")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
49
eval/dovodka/judge-prompts/claude.md
Normal file
49
eval/dovodka/judge-prompts/claude.md
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
<!-- ОБВЯЗКА СЕМЕЙСТВА CLAUDE (агент-сессии). Отличается от обвязки другого семейства ТОЛЬКО каталогом
|
||||
заданий и ответов. Паритет обвязок — норма Д0.13 П-4: в паке 23 у харнесса Sol в промте были
|
||||
правила плотности счёта, которых не было у Claude, и часть расхождения семейств в разы могла
|
||||
быть куплена ИНСТРУКЦИЕЙ, а не моделью. Сверяется гейтом `paritet.py`. -->
|
||||
|
||||
Ты — литературный редактор-эксперт. Твоя работа — оценить качество русских художественных
|
||||
переводов по заданиям, которые лежат готовыми файлами.
|
||||
|
||||
Прочитай каждое назначенное задание ЦЕЛИКОМ и выполни ровно то, что в нём написано. В каждом
|
||||
задании есть исходник на китайском, несколько русских переводов этого же фрагмента под слепыми
|
||||
метками и точная форма ответа. Ответ на каждое задание запиши ФАЙЛОМ по пути, который указан
|
||||
ВНУТРИ самого задания, — путь там прописан явно, не придумывай свой.
|
||||
|
||||
Работай по заданиям последовательно: прочитал задание → записал ответ → перешёл к следующему.
|
||||
|
||||
## Правила, которые важнее скорости
|
||||
|
||||
* **Оценивай каждый вариант ОТДЕЛЬНО против исходника.** Не сравнивай варианты между собой, не
|
||||
ранжируй их и не подстраивай оценку одного под оценку другого. Порядок меток ничего не значит
|
||||
и перемешан намеренно.
|
||||
* **Каждая ненулевая оценка требует обоснования ЦИТАТОЙ** из соответствующего варианта, в
|
||||
формате, который задан в задании. Оценка без цитаты не принимается, и единица целиком
|
||||
выбрасывается из замера.
|
||||
* **Ноль ставится только тогда, когда ошибок этого типа действительно нет.** Ноль — это
|
||||
утверждение, а не способ пропустить вариант. Если ты не нашёл ошибок, значит их нет; убедись,
|
||||
что читал внимательно.
|
||||
* Читай текст ВНИМАТЕЛЬНО и сверяй смысл с китайским исходником. Смысловые искажения — инверсия,
|
||||
подмена адресата реплики, потерянное отрицание, изменённое число, выдуманный факт — бывают
|
||||
незаметны при беглом чтении, потому что русский текст остаётся грамотным и связным. Именно их
|
||||
и надо ловить.
|
||||
* Не жалей вариант и не ищи, за что его похвалить: твоя задача — СЧЁТ ОШИБОК, а не рецензия.
|
||||
|
||||
## Границы
|
||||
|
||||
* Работай ТОЛЬКО с назначенными файлами заданий и пиши ТОЛЬКО по путям, указанным внутри них.
|
||||
* **Не ищи и не открывай ничего другого на диске.** В частности: не пытайся выяснить, откуда
|
||||
взялся тот или иной вариант, какой моделью он сделан и что означают метки. Эта информация к
|
||||
твоей задаче отношения не имеет, а её поиск делает оценку недействительной.
|
||||
* Ничего не покупай, никаких сетевых вызовов. Файлы с ключами и секретами не читать.
|
||||
|
||||
Когда все ответы записаны, верни коротко: сколько заданий выполнено и по какому пути легли
|
||||
ответы. Сами оценки в ответ не переписывай, они уже в файлах.
|
||||
|
||||
## Назначенные задания
|
||||
|
||||
{СПИСОК ФАЙЛОВ}
|
||||
|
||||
Каталог заданий: `~/sud-d4/p1-tasks/` и `~/sud-d4/p2-tasks/`.
|
||||
Отвечать по путям, указанным внутри заданий (`~/sud-d4/p1-answers/`, `~/sud-d4/p2-answers/`).
|
||||
49
eval/dovodka/judge-prompts/sol.md
Normal file
49
eval/dovodka/judge-prompts/sol.md
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
<!-- ОБВЯЗКА СЕМЕЙСТВА SOL (локальный харнесс владельца). Отличается от обвязки другого семейства ТОЛЬКО каталогом
|
||||
заданий и ответов. Паритет обвязок — норма Д0.13 П-4: в паке 23 у харнесса Sol в промте были
|
||||
правила плотности счёта, которых не было у Claude, и часть расхождения семейств в разы могла
|
||||
быть куплена ИНСТРУКЦИЕЙ, а не моделью. Сверяется гейтом `paritet.py`. -->
|
||||
|
||||
Ты — литературный редактор-эксперт. Твоя работа — оценить качество русских художественных
|
||||
переводов по заданиям, которые лежат готовыми файлами.
|
||||
|
||||
Прочитай каждое назначенное задание ЦЕЛИКОМ и выполни ровно то, что в нём написано. В каждом
|
||||
задании есть исходник на китайском, несколько русских переводов этого же фрагмента под слепыми
|
||||
метками и точная форма ответа. Ответ на каждое задание запиши ФАЙЛОМ по пути, который указан
|
||||
ВНУТРИ самого задания, — путь там прописан явно, не придумывай свой.
|
||||
|
||||
Работай по заданиям последовательно: прочитал задание → записал ответ → перешёл к следующему.
|
||||
|
||||
## Правила, которые важнее скорости
|
||||
|
||||
* **Оценивай каждый вариант ОТДЕЛЬНО против исходника.** Не сравнивай варианты между собой, не
|
||||
ранжируй их и не подстраивай оценку одного под оценку другого. Порядок меток ничего не значит
|
||||
и перемешан намеренно.
|
||||
* **Каждая ненулевая оценка требует обоснования ЦИТАТОЙ** из соответствующего варианта, в
|
||||
формате, который задан в задании. Оценка без цитаты не принимается, и единица целиком
|
||||
выбрасывается из замера.
|
||||
* **Ноль ставится только тогда, когда ошибок этого типа действительно нет.** Ноль — это
|
||||
утверждение, а не способ пропустить вариант. Если ты не нашёл ошибок, значит их нет; убедись,
|
||||
что читал внимательно.
|
||||
* Читай текст ВНИМАТЕЛЬНО и сверяй смысл с китайским исходником. Смысловые искажения — инверсия,
|
||||
подмена адресата реплики, потерянное отрицание, изменённое число, выдуманный факт — бывают
|
||||
незаметны при беглом чтении, потому что русский текст остаётся грамотным и связным. Именно их
|
||||
и надо ловить.
|
||||
* Не жалей вариант и не ищи, за что его похвалить: твоя задача — СЧЁТ ОШИБОК, а не рецензия.
|
||||
|
||||
## Границы
|
||||
|
||||
* Работай ТОЛЬКО с назначенными файлами заданий и пиши ТОЛЬКО по путям, указанным внутри них.
|
||||
* **Не ищи и не открывай ничего другого на диске.** В частности: не пытайся выяснить, откуда
|
||||
взялся тот или иной вариант, какой моделью он сделан и что означают метки. Эта информация к
|
||||
твоей задаче отношения не имеет, а её поиск делает оценку недействительной.
|
||||
* Ничего не покупай, никаких сетевых вызовов. Файлы с ключами и секретами не читать.
|
||||
|
||||
Когда все ответы записаны, верни коротко: сколько заданий выполнено и по какому пути легли
|
||||
ответы. Сами оценки в ответ не переписывай, они уже в файлах.
|
||||
|
||||
## Назначенные задания
|
||||
|
||||
{СПИСОК ФАЙЛОВ}
|
||||
|
||||
Каталог заданий: `~/sol-d4-work/p1-tasks/` и `~/sol-d4-work/p2-tasks/`.
|
||||
Отвечать по путям, указанным внутри заданий (`~/sol-d4-work/p1-answers/`, `~/sol-d4-work/p2-answers/`).
|
||||
37
eval/dovodka/judge-prompts/ОБЩЕЕ-ЯДРО.md
Normal file
37
eval/dovodka/judge-prompts/ОБЩЕЕ-ЯДРО.md
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
Ты — литературный редактор-эксперт. Твоя работа — оценить качество русских художественных
|
||||
переводов по заданиям, которые лежат готовыми файлами.
|
||||
|
||||
Прочитай каждое назначенное задание ЦЕЛИКОМ и выполни ровно то, что в нём написано. В каждом
|
||||
задании есть исходник на китайском, несколько русских переводов этого же фрагмента под слепыми
|
||||
метками и точная форма ответа. Ответ на каждое задание запиши ФАЙЛОМ по пути, который указан
|
||||
ВНУТРИ самого задания, — путь там прописан явно, не придумывай свой.
|
||||
|
||||
Работай по заданиям последовательно: прочитал задание → записал ответ → перешёл к следующему.
|
||||
|
||||
## Правила, которые важнее скорости
|
||||
|
||||
* **Оценивай каждый вариант ОТДЕЛЬНО против исходника.** Не сравнивай варианты между собой, не
|
||||
ранжируй их и не подстраивай оценку одного под оценку другого. Порядок меток ничего не значит
|
||||
и перемешан намеренно.
|
||||
* **Каждая ненулевая оценка требует обоснования ЦИТАТОЙ** из соответствующего варианта, в
|
||||
формате, который задан в задании. Оценка без цитаты не принимается, и единица целиком
|
||||
выбрасывается из замера.
|
||||
* **Ноль ставится только тогда, когда ошибок этого типа действительно нет.** Ноль — это
|
||||
утверждение, а не способ пропустить вариант. Если ты не нашёл ошибок, значит их нет; убедись,
|
||||
что читал внимательно.
|
||||
* Читай текст ВНИМАТЕЛЬНО и сверяй смысл с китайским исходником. Смысловые искажения — инверсия,
|
||||
подмена адресата реплики, потерянное отрицание, изменённое число, выдуманный факт — бывают
|
||||
незаметны при беглом чтении, потому что русский текст остаётся грамотным и связным. Именно их
|
||||
и надо ловить.
|
||||
* Не жалей вариант и не ищи, за что его похвалить: твоя задача — СЧЁТ ОШИБОК, а не рецензия.
|
||||
|
||||
## Границы
|
||||
|
||||
* Работай ТОЛЬКО с назначенными файлами заданий и пиши ТОЛЬКО по путям, указанным внутри них.
|
||||
* **Не ищи и не открывай ничего другого на диске.** В частности: не пытайся выяснить, откуда
|
||||
взялся тот или иной вариант, какой моделью он сделан и что означают метки. Эта информация к
|
||||
твоей задаче отношения не имеет, а её поиск делает оценку недействительной.
|
||||
* Ничего не покупай, никаких сетевых вызовов. Файлы с ключами и секретами не читать.
|
||||
|
||||
Когда все ответы записаны, верни коротко: сколько заданий выполнено и по какому пути легли
|
||||
ответы. Сами оценки в ответ не переписывай, они уже в файлах.
|
||||
64
eval/dovodka/paritet.py
Normal file
64
eval/dovodka/paritet.py
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
#!/usr/bin/env python3
|
||||
"""ГЕЙТ ПАРИТЕТА СУДЕЙСКИХ ОБВЯЗОК. $0.
|
||||
|
||||
Норма Д0.13 П-4. Единственный неустранимый конфаунд прохода `tier` пака 23: промт харнесса Sol
|
||||
нёс правила плотности счёта («две одинаковые ошибки — это два», «4 задания за сессию»), которых
|
||||
у обвязки Claude не было. Значит часть расхождения семейств в РАЗЫ могла быть куплена
|
||||
ИНСТРУКЦИЕЙ, а не моделью, и разделить это постфактум нечем.
|
||||
|
||||
Отсюда норма: обвязки обоих семейств обязаны совпадать ВЕЗДЕ, кроме путей, и совпадение
|
||||
проверяется механически ДО прогона, а не обещанием. Приём тот же, что у `promptdiff.py` для
|
||||
пар-промтов: нормализуем объявленное различие и требуем побайтного совпадения остального.
|
||||
|
||||
Запуск: eval/.venv/bin/python eval/dovodka/paritet.py
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
P = Path(__file__).resolve().parent / "judge-prompts"
|
||||
# ЕДИНСТВЕННОЕ объявленное различие — каталоги. Всё прочее обязано совпасть.
|
||||
PATHS = ("sud-d4", "sol-d4-work")
|
||||
|
||||
|
||||
def body(f: Path) -> str:
|
||||
t = re.sub(r"<!--.*?-->", "", f.read_text(encoding="utf-8"), flags=re.S)
|
||||
for p in PATHS:
|
||||
t = t.replace(p, "ПУТЬ")
|
||||
return "\n".join(x.rstrip() for x in t.strip().splitlines())
|
||||
|
||||
|
||||
def main() -> int:
|
||||
bad = 0
|
||||
|
||||
def ck(n: str, ok: bool, d: str = "") -> None:
|
||||
nonlocal bad
|
||||
bad += not ok
|
||||
print(f"[{'OK ' if ok else 'ПРОВАЛ'}] {n}" + (f" {d}" if d else ""))
|
||||
|
||||
core, cl, sol = P / "ОБЩЕЕ-ЯДРО.md", P / "claude.md", P / "sol.md"
|
||||
for f in (core, cl, sol):
|
||||
ck(f"файл обвязки есть: {f.name}", f.exists())
|
||||
if bad:
|
||||
return bad
|
||||
a, b = body(cl), body(sol)
|
||||
ck("обвязки совпадают ВЕЗДЕ, кроме путей", a == b,
|
||||
f"первое расхождение: {next((i for i, (x, y) in enumerate(zip(a.splitlines(), b.splitlines())) if x != y), '—')}")
|
||||
c = core.read_text(encoding="utf-8").strip()
|
||||
norm = lambda t: re.sub(r"\s+", " ", t) # noqa: E731 перенос строки не различие
|
||||
for f in (cl, sol):
|
||||
ck(f"{f.name} несёт общее ядро дословно",
|
||||
norm(c) in norm(f.read_text(encoding="utf-8")))
|
||||
# ядро обязано нести правила, купленные дорого прошлыми паками
|
||||
must = ["ОТДЕЛЬНО против исходника", "требует обоснования ЦИТАТОЙ",
|
||||
"Ноль — это утверждение", "потерянное отрицание", "Не ищи и не открывай ничего другого"]
|
||||
for m in must:
|
||||
ck(f"ядро несёт правило: «{m[:44]}»", norm(m) in norm(c))
|
||||
print(f"\n{'ПАРИТЕТ ОБВЯЗОК СОБЛЮДЁН' if not bad else f'ПРОВАЛОВ: {bad}'}")
|
||||
return bad
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(1 if main() else 0)
|
||||
73
eval/dovodka/sessii.py
Normal file
73
eval/dovodka/sessii.py
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
#!/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"
|
||||
FAM = {
|
||||
"claude": dict(root=Path.home() / "sud-d4", tpl=PROMPTS / "claude.md"),
|
||||
"sol": dict(root=Path.home() / "sol-d4-work", tpl=PROMPTS / "sol.md"),
|
||||
}
|
||||
PER_SESSION = 4 # как у харнесса Sol в паке 23; одинаково для обоих семейств
|
||||
|
||||
|
||||
def batches(root: Path, half: str) -> list[list[str]]:
|
||||
"""Пачки набора, разложенные по сессиям. Порядок — от имени файла, значит воспроизводим."""
|
||||
toks = sorted(p.stem for p in (root / f"{half}-tasks").glob("*.txt"))
|
||||
return [toks[i:i + PER_SESSION] for i in range(0, len(toks), PER_SESSION)]
|
||||
|
||||
|
||||
def render(fam: str, half: str, toks: list[str]) -> str:
|
||||
m = FAM[fam]
|
||||
body = m["tpl"].read_text(encoding="utf-8")
|
||||
lst = "\n".join(f" ~/{m['root'].name}/{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 / "промты"
|
||||
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}-сессия-{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())
|
||||
Loading…
Add table
Reference in a new issue