Skip to content

lawref.reanchor

Перевешивание якорей между редакциями — рычаг формализации предшествующей редакции (LAYERS-NEXT, спутник О-2).

Задача, ради которой модуль существует. Нормы заякорены в НОВУЮ редакцию акта; предшествующая редакция не заформализована. Механизм миграций §32.1 здесь не помогает: он бежит вперёд (apply(previous, operations) ≡ next), операция replace несёт только новый текст, repeal удаляет — восстановить старую редакцию из новой нечем. Тем более он не применим к ЗАМЕНЕ акта (новая конституция взамен старой): там нет ребра revision, там два разных source_work.

Рычаг — постатейное совпадение текстов. Статья, чей текст в обеих редакциях совпадает побайтово, не требует переформализации: правило, заякоренное в неё, перевешивается на редакцию-цель механически. Работа сжимается с «весь акт» до «статьи, где текст разошёлся».

ЧТО МОДУЛЬ НЕ УТВЕРЖДАЕТ. Совпадение текста статьи НЕ доказывает совпадения нормы: правило про ст. 5 может опираться на определение из ст. 2, а оно изменилось. Поэтому выход называется КАНДИДАТАМИ, а не решением, и транзитивные зависимости здесь намеренно не считаются — это была бы семантика в инструменте (инвариант CLAUDE.md №5). Проверяется кандидат исполнением: перевесить, прогнать корпус через impact-editions и посмотреть, где исходы разошлись неожиданно.

Сравнение — побайтовое. Нормализация пробелов не делается: она молча решала бы за автора, что «тот же текст», а §194 держит тексты байтами.

Attributes

NameDescription
ANCHORED_KINDSNo description.
CHANGEDNo description.
DEFAULT_MIN_MARGINNo description.
DEFAULT_MIN_SCORENo description.
IDENTICALNo description.
ONLY_SOURCENo description.
ONLY_TARGETNo description.
SCORE_SCALENo description.

Classes

NameDescription
ReanchorErrorОтказ разбора: редакция не найдена либо языки пары не совпадают.

Functions

NameDescription
apply_movesДокумент с перевешенными якорями кандидатов; вход не мутируется.
compare_editionsПостатейное сравнение двух редакций одного языка.
locator_matchesКандидаты соответствия локаторов МЕЖДУ номерами — переписка с перенумерацией.
reanchor_candidatesКандидаты на механическое перевешивание по списку пар (источник, цель).
render_reportЧеловекочитаемая сводка.

ANCHORED_KINDSattributemodule attribute#

ANCHORED_KINDS = ('assertion', 'constraint', 'norm_template', 'priority_rule', 'rule')

CHANGEDattributemodule attribute#

CHANGED = 'changed'

DEFAULT_MIN_MARGINattributemodule attribute#

DEFAULT_MIN_MARGIN = 500

DEFAULT_MIN_SCOREattributemodule attribute#

DEFAULT_MIN_SCORE = 6000

IDENTICALattributemodule attribute#

IDENTICAL = 'identical'

ONLY_SOURCEattributemodule attribute#

ONLY_SOURCE = 'only_source'

ONLY_TARGETattributemodule attribute#

ONLY_TARGET = 'only_target'

SCORE_SCALEattributemodule attribute#

SCORE_SCALE = 10000

ReanchorErrorclass#

class ReanchorError(ValueError)

Bases: ValueError

Отказ разбора: редакция не найдена либо языки пары не совпадают.

apply_movesfunction#

def apply_moves(document: dict, report: dict) -> dict

Документ с перевешенными якорями кандидатов; вход не мутируется.

Порождает ВАРИАНТ для проверки исполнением, а не готовую формализацию: решение о переносе нормы остаётся за автором, и коммитить выход этой функции без прогона корпуса значило бы поверить кандидату на слово.

compare_editionsfunction#

def compare_editions(document: dict, source: str, target: str) -> dict[str, Any]

Постатейное сравнение двух редакций одного языка.

source — редакция, в которую нормы заякорены СЕЙЧАС (новая); target — редакция, на которую их предстоит перевесить (старая).

Языки обязаны совпадать: сравнивать русскую редакцию с казахской по тексту бессмысленно, и молча выбрать одну — ровно то угадывание, которое §249 запрещает импортёрам.

locator_matchesfunction#

def locator_matches(document: dict, source: str, target: str, *, min_score: int = DEFAULT_MIN_SCORE, min_margin: int = DEFAULT_MIN_MARGIN) -> dict[str, Any]

Кандидаты соответствия локаторов МЕЖДУ номерами — переписка с перенумерацией.

Зачем нужно отдельно от compare_editions. Тот сравнивает статью с ОДНОИМЁННОЙ: article/5 новой редакции с article/5 старой. При переписке, которая перенумеровала акт, такое сравнение структурно слепо — все статьи объявляются changed, и рычаг пуст, хотя сами тексты в основном уцелели. Замер на Конституции 2026↔1995 (31.08.2026), ровно тот случай, ради которого рычаг заводился:

  • побайтово совпали 0 локаторов из 104, changed — 96;
  • но совпадение по номеру сохранила РОВНО ОДНА статья из 98: остальные 97 переехали, причём систематическим сдвигом на 1–3 (article/91 → 89, article/79 → 78, article/90 → 88 — счёт 0,99);
  • кросс-локаторно мешком слов находится 87 из 96 при счёте ≥ 0,6 и 71 при ≥ 0,7, медианный отрыв лучшего кандидата от второго — 0,32, спорных (отрыв < 0,05) всего 11.

То есть потеря была не в строгости сравнения ТЕКСТА, а в том, что сопоставления НОМЕРОВ не существовало вовсе. Мера здесь лексическая и встроенная (мешок слов, стдлиб): нейронная модель этот класс не открывает — она нужна там, где слова РАЗНЫЕ, а смысл один (слот §5 DECISION-0040, слой обнаружения law_search), тогда как переписка акта сохраняет словарь почти целиком.

Что функция НЕ утверждает — то же, что и весь модуль: сходство текста не доказывает тождества нормы. Выход — кандидаты со счётом, и спорные названы отдельно, а не утоплены в общем списке.

reanchor_candidatesfunction#

def reanchor_candidates(document: dict, pairs: list[tuple[str, str]], *, fuzzy: bool = False, min_score: int = DEFAULT_MIN_SCORE, min_margin: int = DEFAULT_MIN_MARGIN) -> dict[str, Any]

Кандидаты на механическое перевешивание по списку пар (источник, цель).

Пар обычно столько, сколько аутентичных языков у акта: правило конституции заякорено сразу в русский и казахский фрагменты одной статьи, и перевесить его можно только целиком — значит, обе пары обязаны быть заданы.

Узел — кандидат, если КАЖДЫЙ его якорь разрешается во фрагмент редакции из пары, у локатора есть двойник в редакции-цели, и тексты совпадают побайтово. Иначе узел попадает в blocked с причиной — молчаливый пропуск здесь был бы потерей покрытия того же класса, что молчаливый пропуск нормы.

fuzzy=True добавляет второй источник соответствия — locator_matches: двойник ищется не по номеру, а по тексту. Это ровно тот случай, ради которого рычаг заводился и на котором он был пуст: переписка с перенумерацией (замер — в докстринге locator_matches; на Конституции 2026↔1995 побайтовых двойников 0, текстовых 84). Режим по умолчанию ВЫКЛЮЧЕН и им остаётся: побайтовое совпадение — факт, а счёт сходства — предположение, и смешивать их в одном списке без признака было бы подменой. Поэтому у fuzzy-кандидата в moves стоит score, а у побайтового его нет вовсе.

render_reportfunction#

def render_report(report: dict) -> str

Человекочитаемая сводка.

Documentation for Arxo. Writings — blog.arxo.io.

Anonymous visit counts on stats.arxo.io, no cookies.