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
| Name | Description |
|---|---|
ANCHORED_KINDS | No description. |
CHANGED | No description. |
DEFAULT_MIN_MARGIN | No description. |
DEFAULT_MIN_SCORE | No description. |
IDENTICAL | No description. |
ONLY_SOURCE | No description. |
ONLY_TARGET | No description. |
SCORE_SCALE | No description. |
Classes
| Name | Description |
|---|---|
ReanchorError | Отказ разбора: редакция не найдена либо языки пары не совпадают. |
Functions
| Name | Description |
|---|---|
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 = 500DEFAULT_MIN_SCOREattributemodule attribute#
DEFAULT_MIN_SCORE = 6000IDENTICALattributemodule attribute#
IDENTICAL = 'identical'ONLY_SOURCEattributemodule attribute#
ONLY_SOURCE = 'only_source'ONLY_TARGETattributemodule attribute#
ONLY_TARGET = 'only_target'SCORE_SCALEattributemodule attribute#
SCORE_SCALE = 10000ReanchorErrorclass#
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.