Merge the two overlapping rule lists into one axis list, add the restored-paragraph arm and harden the coverage gate
This commit is contained in:
parent
824bd209eb
commit
273d2df41f
2 changed files with 129 additions and 48 deletions
|
|
@ -36,47 +36,49 @@
|
|||
разворачивать идиому и сворачивать описание · выбирать слово, которого в словаре напротив
|
||||
исходного не стоит. Буквальность здесь не достоинство. Если выбор стоит между точной калькой и
|
||||
живой русской фразой — бери живую фразу.
|
||||
⚠ Живой не значит нарядный. Простую фразу исходника не украшай: если герой идёт — он идёт, а не
|
||||
шествует. Сворачивая описание, ты сжимаешь СЛОВА, а не факты: участники, действия, числа и
|
||||
полярность остаются все до одного.
|
||||
|
||||
У этой свободы одна граница, и она важнее всего списка выше: ПОВТОР, СТОЯЩИЙ В ПАРАЛЛЕЛЬНЫХ
|
||||
ПОЗИЦИЯХ САМОГО ИСХОДНИКА, — авторский рефрен, а не бедность словаря. Такой повтор сохраняй и НЕ
|
||||
«разнообразь» лексику там, где автор повторяется намеренно.
|
||||
«разнообразь» лексику там, где автор повторяется намеренно. Повтор, которого в исходнике нет, —
|
||||
наоборот, дефект: немотивированные повторы убирай.
|
||||
|
||||
ЧЕГО НЕЛЬЗЯ — пять запретов, и они жёсткие:
|
||||
1. Перевирать факт: участника действия, отрицание, число, ранг, меру, родство, кто кому что
|
||||
сказал. Полярность и адресат неприкосновенны.
|
||||
2. Терять смысловые ходы. Дробить и сливать можно; выбрасывать нельзя. Ничего не добавляй от себя
|
||||
как факт — связка и уточнение допустимы, новое событие нет; сцены не пересочиняй.
|
||||
3. Переводить буквально жест, идиому и реалию. Это три места, где подстрочник врёт чаще всего:
|
||||
поклон — не «ударил головой»; идиому передавай смыслом, не сворачивая её в общее место и не
|
||||
калькируя дословно; реалию сперва пойми, а потом переводи — неверно понятая реалия остаётся
|
||||
ошибкой смысла, даже если фраза звучит гладко.
|
||||
4. Менять канон. Имена, термины и реалии — строго в тех формах, что даны в глоссарии ниже,
|
||||
склоняя по контексту. Своих вариантов не придумывай.
|
||||
5. Оставлять в выводе исходный язык, служебные преамбулы, комментарии, пояснения, заметки о
|
||||
решениях и markdown-заголовки.
|
||||
ПО ЧЕМУ БУДЕТ ПРОВЕРЕН РЕЗУЛЬТАТ. Это же и есть полный список того, что считается ошибкой —
|
||||
других запретов нет:
|
||||
|
||||
РЕГИСТР — держи тот же градус, что в исходнике:
|
||||
грубость передавай грубостью, ярость — яростью, спокойствие — спокойствием. Брань не смягчай и не
|
||||
заменяй эвфемизмом: если персонаж ругается, он ругается. Не поднимай слог — там, где в исходнике
|
||||
простое резкое слово, книжное слово будет ошибкой. Вежливость есть свойство персонажа, а не
|
||||
переводчика: не добавляй вежливых форм и смягчающих оборотов, которых в реплике нет. Речевой
|
||||
портрет героя (просторечие, архаика, чужой акцент) передавай лексикой и синтаксисом.
|
||||
* СМЫСЛ. Перевран факт: участник действия, отрицание, число, ранг, мера, родство, кто кому что
|
||||
сказал. Полярность и адресат неприкосновенны. Сюда же буквально переведённые жест, идиома и
|
||||
реалия — три места, где подстрочник врёт чаще всего: обиходный жест называй тем, что он значит,
|
||||
а не тем, из чего состоит; идиому передавай смыслом, не сворачивая в общее место и не калькируя;
|
||||
реалию сперва пойми, а потом переводи — неверно понятая реалия остаётся ошибкой смысла, даже
|
||||
если фраза звучит гладко.
|
||||
* ПОЛНОТА. Пропал смысловой ход исходника — или появилось событие, которого там не было. Дробить
|
||||
и сливать можно; выбрасывать и досочинять нельзя, сцены не пересочиняй. Связка и уточнение
|
||||
допустимы, новый факт — нет.
|
||||
* КАНОН. Имя, термин или реалия стоит не в той форме, что дана в глоссарии (если он приложен).
|
||||
Склоняй по контексту, своих вариантов не придумывай: книга выходит на тысячу с лишним глав, и
|
||||
форма имени обязана совпасть во всех.
|
||||
* ЯЗЫК. Фраза, которую носитель русского не написал бы сам: калька, канцелярит, переводной суржик,
|
||||
немотивированный повтор.
|
||||
* РЕГИСТР. Градус не тот, что в исходнике. Грубость передавай грубостью, ярость — яростью,
|
||||
спокойствие — спокойствием. Брань не смягчай и не заменяй эвфемизмом: если персонаж ругается,
|
||||
он ругается. Не поднимай слог: там, где в исходнике простое резкое слово, книжное будет ошибкой.
|
||||
Вежливость есть свойство персонажа, а не переводчика — не добавляй вежливых форм и смягчающих
|
||||
оборотов, которых в реплике нет. Речевой портрет героя (просторечие, архаика, чужой акцент)
|
||||
передавай лексикой и синтаксисом.
|
||||
|
||||
ПО КАКИМ КРИТЕРИЯМ БУДЕТ ПРОВЕРЕН РЕЗУЛЬТАТ — проверь себя по ним сам, молча, перед выводом:
|
||||
* СМЫСЛ — ни один факт не перевран (см. запрет 1);
|
||||
* ПОЛНОТА — каждый смысловой ход исходника присутствует в переводе;
|
||||
* КАНОН — каждый термин глоссария стоит в канонной форме;
|
||||
* ЯЗЫК — в тексте нет ни одной фразы, которую носитель русского не написал бы сам: ни кальки,
|
||||
ни канцелярита, ни переводного суржика;
|
||||
* РЕГИСТР — градус тот же, что в исходнике.
|
||||
Вольность в пределах этих критериев ошибкой НЕ СЧИТАЕТСЯ. Текст, который читается лучше и не
|
||||
нарушает ни одного из пяти, лучше текста, который следует за буквой.
|
||||
Вольность в пределах этих пяти ошибкой НЕ СЧИТАЕТСЯ. Текст, который читается лучше и не нарушает
|
||||
ни одной из осей, лучше текста, который следует за буквой.
|
||||
|
||||
{{ПАР_ЕДИНИЦЫ}}
|
||||
|
||||
{{ПАР_ПЕРЕВЁРСТКА}}
|
||||
Максимума длины абзаца нет.
|
||||
|
||||
Выведи ТОЛЬКО перевод.
|
||||
Выведи ТОЛЬКО перевод — без преамбул, комментариев, пояснений, заметок о решениях и
|
||||
markdown-заголовков. Иноязычные вставки, которые велят сохранить правила выше, — не нарушение.
|
||||
|
||||
---USER---
|
||||
Переведи следующий фрагмент:
|
||||
|
|
|
|||
|
|
@ -82,10 +82,45 @@ PAIRS = {
|
|||
"en": {"letter": "n", "phase": "ФД-N", "onepass": "E2",
|
||||
"wire": EN.wire, "units": EN.units, "para": lambda: PARA.EN},
|
||||
}
|
||||
# арм → (модель, номер генерации). Генерация нужна, чтобы два прогона ОДНОЙ модели не схлопнулись
|
||||
# в один тег: кэш `purchase` смотрит на существование файла и вернул бы первую клетку второй раз.
|
||||
# ── ВОЗВРАТ АВТОРСКИХ АБЗАЦЕВ ИСХОДНИКУ ──────────────────────────────────────────────────────
|
||||
# ⚠⚠ ПОЧЕМУ ЭТО ОТДЕЛЬНЫЙ АРМ, А НЕ ПОЧИНКА ЭКСТРАКТОРА. Наш английский экстрактор
|
||||
# (`pair_en.raw_text`) сносит из epub ВСЕ теги регексом, включая `</p>`: в книге 11 447 абзацев
|
||||
# медианой по 62 знака, а модели едет один сплошной блок. Это наш баг, и чинить его надо.
|
||||
# НО uid единицы есть `sha1(source.strip())` (`bakeoff.uid_of`), то есть ЛЮБАЯ правка пробелов
|
||||
# переименовывает все единицы. Цена такой правки замерена: **289 оплаченных клеток на $2.64**
|
||||
# теряют адресата, плюс семь файлов слепых ключей и все судейские ответы английской пары.
|
||||
# Манифест это поймает и остановит покупку — гейт работает, — но история оси всё равно умрёт.
|
||||
#
|
||||
# ⇒ Починка делается НА ПОДАЧЕ: тот же текст той же единицы, с тем же uid, но с возвращёнными
|
||||
# границами абзацев. Функция взята у `podacha.py`, а не написана второй раз.
|
||||
# ⇒ И это ОТДЕЛЬНЫЙ АРМ, потому что иначе замер стал бы некорректен: если `RN` получит
|
||||
# восстановленный исходник, а его база `ZP` — плоский, контраст смешает эффект ПРОМТА с эффектом
|
||||
# ПОДАЧИ, и разделить их будет нечем. Поэтому:
|
||||
# RN / ZP — эффект ПРОМТА (вход у обоих ОДИНАКОВЫЙ, плоский)
|
||||
# RA / RN — эффект ПОДАЧИ (промт у обоих ОДИНАКОВЫЙ, новый)
|
||||
# ⚠ У китайской пары арма `RA` нет и быть не может: её исходник абзацы не терял (78 строк по
|
||||
# 24 знака), возвращать нечего, и клетка была бы побайтным близнецом `RN`.
|
||||
POD = _load("podacha_r", ZONE / "podacha.py")
|
||||
|
||||
|
||||
def _src_en(src: str) -> str:
|
||||
r = POD.restore_paragraphs(src, POD.EPUB_EN)
|
||||
if not r.strip():
|
||||
raise SystemExit("⛔ авторские абзацы для единицы не нашлись — арм RA стал бы близнецом "
|
||||
"RN, покупка отказана")
|
||||
return r
|
||||
|
||||
|
||||
PAIRS["en"]["restore"] = _src_en
|
||||
PAIRS["zh"]["restore"] = None
|
||||
|
||||
# арм → (модель, номер генерации, подача исходника). Генерация нужна, чтобы два прогона ОДНОЙ
|
||||
# модели не схлопнулись в один тег: кэш `purchase` смотрит на существование файла и вернул бы
|
||||
# первую клетку второй раз.
|
||||
ARMS = {"RN": ("deepseek-v4-pro", 1), "RN2": ("deepseek-v4-pro", 2),
|
||||
"RG": ("glm-5", 1), "RK": ("grok-4.3", 1)}
|
||||
"RG": ("glm-5", 1), "RK": ("grok-4.3", 1),
|
||||
"RA": ("deepseek-v4-pro", 1)} # тот же промт, ВОССТАНОВЛЕННЫЕ абзацы исходника
|
||||
RESTORE_ARMS = ("RA",)
|
||||
FREE = ("ZD", "Z0", "ZP")
|
||||
PRIMARY = ("RN", "ZP") # единственный несущий контраст, см. шапку
|
||||
|
||||
|
|
@ -169,32 +204,38 @@ COVERAGE: tuple[tuple[str, str, str, str], ...] = (
|
|||
("translator", "Хонорифики:", "промт", "Хонорифики"),
|
||||
("translator", "Баланс форенизация", "промт", "форенизация/доместикация"),
|
||||
("translator", "Сноски:", "промт", "Сноски"),
|
||||
("translator", "ничего не пропускай", "промт", "выбрасывать нельзя"),
|
||||
("translator", "не добавляй от себя", "промт", "не добавляй от себя"),
|
||||
("translator", "ничего не пропускай", "промт", "выбрасывать и досочинять нельзя"),
|
||||
("translator", "не добавляй от себя", "промт", "появилось событие, которого там не было"),
|
||||
# ⚠ «смысл» — правило покрыто ПО СОДЕРЖАНИЮ другим, более полным местом промта, но не
|
||||
# дословно. Диспозиция заведена отдельно от «блок» намеренно: смешивать «перенесено буквой» и
|
||||
# «покрыто смыслом» значит потерять различие, на котором и погорела первая редакция.
|
||||
("translator", "НЕ копируй построчную разбивку", "смысл",
|
||||
"Один повествовательный ход оформляй ОДНИМ русским абзацем"),
|
||||
("translator", "избегай канцелярита", "промт", "канцелярита"),
|
||||
("translator", "Единицы и приёмы", "блок", "Единицы и приёмы"),
|
||||
("translator", "избегай канцелярита", "промт", "канцеляр"),
|
||||
("translator", "Выведи ТОЛЬКО перевод", "промт", "Выведи ТОЛЬКО перевод"),
|
||||
("editor", "искажения смысла", "промт", "Перевирать факт"),
|
||||
("editor", "искажения смысла", "промт", "Перевран факт"),
|
||||
("editor", "неверно понятые реалии", "промт", "реалию сперва пойми"),
|
||||
("editor", "кальки жестов и идиом", "промт", "жест, идиому и реалию"),
|
||||
("editor", "кальки жестов и идиом", "промт", "подстрочник врёт"),
|
||||
("editor", "а не подстрочником", "промт", "подстрочник врёт"),
|
||||
("editor", "лишние повторы", "промт", "разнообразь"),
|
||||
# ⚠ СЕРТИФИЦИРУЮЩЕЕ СЛОВО БЫЛО ВЗЯТО ИЗ ПРАВИЛА ПРОТИВОПОЛОЖНОГО СМЫСЛА. Строка
|
||||
# «убирай лишние повторы» покрывалась словом «разнообразь», которое стоит в
|
||||
# рефрен-границе — то есть там, где повторы убирать ЗАПРЕЩЕНО. Гейт печатал
|
||||
# «покрыто», а требования убирать немотивированный повтор в промте не было вовсе.
|
||||
# Поймано ревью Fable-5, воспроизведено исполнением. Сертификат пере-привязан.
|
||||
("editor", "лишние повторы", "промт", "немотивированные повторы убирай"),
|
||||
("editor", "авторский рефрен", "промт", "авторский рефрен"),
|
||||
("editor", "СТРОГО по приведённому глоссарию", "промт", "Менять канон"),
|
||||
("editor", "СТРОГО по приведённому глоссарию", "промт", "КАНОН"),
|
||||
("editor", "склоняя по контексту", "промт", "склоняя по контексту"),
|
||||
("editor", "не придумывай иных вариантов", "промт", "Своих вариантов не придумывай"),
|
||||
("editor", "не придумывай иных вариантов", "промт", "вариантов не придумывай"),
|
||||
("editor", "естественные русские абзацы", "смысл",
|
||||
"Один повествовательный ход оформляй ОДНИМ русским абзацем"),
|
||||
("editor", "Реплики прямой речи", "смысл",
|
||||
"Каждую новую реплику начинай с нового абзаца через тире"),
|
||||
("editor", "Сохраняй ПОЛНОТУ", "промт", "Терять смысловые ходы"),
|
||||
("editor", "Сохраняй ПОЛНОТУ", "промт", "Пропал смысловой ход"),
|
||||
("editor", "не пересочиняй сцены", "промт", "сцены не пересочиняй"),
|
||||
("editor", "ДИСКУРС-ПЕРЕВЁРСТКА", "блок", "ДИСКУРС-ПЕРЕВЁРСТКА"),
|
||||
# ⚠ Строк-ЗАГОЛОВКОВ обоих блоков в таблице нет намеренно: они не правила и не
|
||||
# сканируются, а как строки таблицы были мёртвыми — то есть создавали видимость
|
||||
# покрытия. Присутствие самих блоков сторожат отдельные проверки селфтеста.
|
||||
# строки, которые приезжают ПАР-БЛОКАМИ дословно (их тело — данные пары, не наше)
|
||||
("translator", "МЕРЫ", "блок", "Единицы и приёмы"),
|
||||
("translator", "СТИХИ и названия классики", "блок", "Единицы и приёмы"),
|
||||
|
|
@ -230,6 +271,12 @@ COVERAGE: tuple[tuple[str, str, str, str], ...] = (
|
|||
_RULE_MARK = ("- ", "1.", "2.", "3.", "4.", "Ты —", "Жанр", "Твой мандат", "Выведи")
|
||||
|
||||
|
||||
# ⚠ МЁРТВАЯ СТРОКА ТАБЛИЦЫ НЕОТЛИЧИМА ОТ ЖИВОЙ — и это дефект гейта, а не косметика:
|
||||
# строка, не срабатывающая ни разу, создаёт видимость покрытия там, где его никто не проверял.
|
||||
# Поймано ревью Fable-5. `fired` наполняется при разборе и сверяется селфтестом.
|
||||
fired: set = set()
|
||||
|
||||
|
||||
def coverage_report(p: str) -> list[tuple[str, str, str]]:
|
||||
"""[(файл, строка боевого промта, вердикт)] — что покрыто, чем и что не покрыто вовсе."""
|
||||
cfg = PAIRS[p]
|
||||
|
|
@ -239,13 +286,17 @@ def coverage_report(p: str) -> list[tuple[str, str, str]]:
|
|||
sysall = "\n".join(x["content"] for x in rol_msgs(p, u["source"]) if x["role"] == "system")
|
||||
ub, rb = pair_blocks(p)
|
||||
out = []
|
||||
fired.clear()
|
||||
for fname in ("translator", "editor"):
|
||||
core, _f, _us = PR.load_template(d / f"{fname}.md")
|
||||
for ln in core.splitlines():
|
||||
s = ln.strip()
|
||||
if len(s) < 25 or not s.startswith(_RULE_MARK):
|
||||
# ⚠ ПОРОГ ДЛИНЫ БЫЛ СЛЕПЫМ ПЯТНОМ: правило «- Сноски: {{footnotes}}» короче
|
||||
# 25 знаков и не сканировалось ВООБЩЕ, то есть могло пропасть незаметно.
|
||||
if len(s) < 10 or not s.startswith(_RULE_MARK):
|
||||
continue
|
||||
hits = [c for c in COVERAGE if c[0] == fname and c[1] in s]
|
||||
fired.update(c[:3] for c in hits)
|
||||
if not hits:
|
||||
out.append((fname, s[:90], "⛔ НЕ ОБЪЯВЛЕНО НИ ОДНОЙ ДИСПОЗИЦИЕЙ"))
|
||||
continue
|
||||
|
|
@ -268,6 +319,17 @@ def coverage_report(p: str) -> list[tuple[str, str, str]]:
|
|||
return out
|
||||
|
||||
|
||||
def src_of(p: str, arm: str, u: dict) -> str:
|
||||
"""Исходник, который видит арм. ОДНА точка правды — иначе смета и покупка разъедутся.
|
||||
|
||||
⚠ Все армы, кроме `RA`, видят ПЛОСКИЙ исходник — тот же самый, что видела база `ZP`. Иначе
|
||||
несущий контраст `RN/ZP` смешал бы эффект промта с эффектом подачи.
|
||||
"""
|
||||
if arm in RESTORE_ARMS and PAIRS[p]["restore"]:
|
||||
return PAIRS[p]["restore"](u["source"])
|
||||
return u["source"]
|
||||
|
||||
|
||||
def tag(p: str, arm: str, uid: str) -> str:
|
||||
return f"dv-{PAIRS[p]['letter']}-{arm.lower()}-{uid}"
|
||||
|
||||
|
|
@ -307,6 +369,9 @@ def buy(p: str, which: list[str]) -> None:
|
|||
print(f"\nпара {p} · глав {len(us)} · армы {' '.join(which)} · касса {cfg['phase']}")
|
||||
for arm in which:
|
||||
model, gen = ARMS[arm]
|
||||
if arm in RESTORE_ARMS and not cfg["restore"]:
|
||||
print(f" {arm} на паре {p} невозможен: исходник абзацев не терял — пропуск")
|
||||
continue
|
||||
for u in us:
|
||||
tg = tag(p, arm, u["uid"])
|
||||
if cell(tg).strip():
|
||||
|
|
@ -315,8 +380,9 @@ def buy(p: str, which: list[str]) -> None:
|
|||
# часами, и пересёкший 16:00 UTC цикл писал бы вызовы по вчерашнему прайсу.
|
||||
ZK.price_gate(quiet=True)
|
||||
rec = MONEY.purchase(led, tg, model, ED22.client(model),
|
||||
call_kwargs(model, rol_msgs(p, u["source"])),
|
||||
role="onepass", uid=u["uid"], arm=arm, gen=gen, pair=p)
|
||||
call_kwargs(model, rol_msgs(p, src_of(p, arm, u))),
|
||||
role="onepass", uid=u["uid"], arm=arm, gen=gen, pair=p,
|
||||
podacha="абзацы" if arm in RESTORE_ARMS else "плоско")
|
||||
if rec.get("skipped"):
|
||||
raise SystemExit(f"⛔ ГАРД ОТКАЗАЛ на клетке {tg} — прогон ОСТАНОВЛЕН.\n"
|
||||
" Резать панель или поднимать потолок решением сессии ЗАПРЕЩЕНО.\n"
|
||||
|
|
@ -405,6 +471,9 @@ def cmd_dry() -> None:
|
|||
f"({_sys_len(p) / max(_sys_len_old(p), 1):.2f}×)")
|
||||
sub = 0.0
|
||||
for arm, (model, _g) in ARMS.items():
|
||||
if arm in RESTORE_ARMS and not cfg["restore"]:
|
||||
print(f" {arm:4s} {'—':18s} на этой паре невозможен (исходник абзацев не терял)")
|
||||
continue
|
||||
need = sum(1 for u in us if not cell(tag(p, arm, u["uid"])).strip())
|
||||
pr = price_of(p, model)
|
||||
sub += need * pr
|
||||
|
|
@ -447,7 +516,7 @@ def cmd_selftest() -> int:
|
|||
ck(f"{p}: вольность РАЗРЕШЕНА явно", "ТЫ ВПРАВЕ" in sysall)
|
||||
ck(f"{p}: регистр назван отдельной рамкой", "РЕГИСТР" in sysall)
|
||||
ck(f"{p}: критерии проверки напечатаны в промте",
|
||||
"ПО КАКИМ КРИТЕРИЯМ" in sysall and "ошибкой НЕ СЧИТАЕТСЯ" in sysall)
|
||||
"ПО ЧЕМУ БУДЕТ ПРОВЕРЕН" in sysall and "ошибкой НЕ СЧИТАЕТСЯ" in sysall)
|
||||
ck(f"{p}: черновик в промт НЕ попадает (однопроходка)",
|
||||
"ЧЕРНОВ" not in sysall.upper() and "черновик" not in sysall.lower())
|
||||
ck(f"{p}: банк-закон на месте либо термов в главе нет",
|
||||
|
|
@ -479,6 +548,16 @@ def cmd_selftest() -> int:
|
|||
miss = [r for r in rep if r[2].startswith("⛔")]
|
||||
ck(f"{p}: КАЖДОЕ правило боевых промтов имеет диспозицию и она исполнена",
|
||||
not miss, f"не покрыто {len(miss)} из {len(rep)}: " + " | ".join(r[1][:50] for r in miss[:3]))
|
||||
# ⚠ И ОБРАТНАЯ СТОРОНА: строка таблицы, не сработавшая НИ РАЗУ, создаёт видимость покрытия
|
||||
# там, где никто ничего не проверял. Считается по ОБЕИМ парам сразу: часть правил живёт
|
||||
# только у английской (французский слой, рамочный рассказ).
|
||||
seen: set = set()
|
||||
for p in PAIRS:
|
||||
coverage_report(p)
|
||||
seen |= fired
|
||||
dead = [c for c in COVERAGE if c[:3] not in seen]
|
||||
ck("в таблице COVERAGE нет МЁРТВЫХ строк (каждая срабатывает хотя бы раз)",
|
||||
not dead, " | ".join(f"{c[0]}:«{c[1]}»" for c in dead[:4]))
|
||||
ck("пары не делят буквы тегов", len({v["letter"] for v in PAIRS.values()}) == len(PAIRS))
|
||||
ck("несущий контраст объявлен один", len(PRIMARY) == 2 and PRIMARY == ("RN", "ZP"))
|
||||
for a, (mo, _g) in ARMS.items():
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue