Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BJKMLHdxMwSUeYcdkwqX4W
38 KiB
Проверка закрытия V-1…V-13 и G-1…G-30 (17.10.2026, третий раунд, Sonnet)
Ревьюер: отдельный агент (модель Sonnet), без права правки; получил design.md после коммита e6400bf, отчёты
review-2026-10-17-closure.md (V-n) и review-2026-10-17-blind.md (G-n). Маркеры новых проблем — W-n, наблюдения — O-n.
Отчёт агента ниже без сокращений.
Проверка закрытия V-1…V-13 и G-1…G-30 в обновлённом design.md (коммит e6400bf)
Файлы не правил и ничего не коммитил. Читал design.md целиком (850 строк), оба отчёта ревью и фикстуры. Сценарии из находок прогонял по новым правилам. Данные проверял скриптами python3 -I, для xls — venv парсера с -I.
Итог: 43 находки.
closed— 31.closed, new issue— 9: V-3, V-13, G-2, G-12, G-16, G-17, G-19, G-25 и ещё G-1 в связке с W-6.partial— 3: V-4, V-12, G-8.not closed— нет.
Новых противоречий найдено 16 (W-1…W-16). Серьёзные из них:
- W-1 — регрессия детектора «только актив», три файла из манифеста.
- W-2 —
confirmedнигде не хранится. - W-4 — наследование столбца кодов ломает лист «_2» НПЗ.
- W-3, W-5, W-6, W-9 — средней серьёзности.
1. Таблица
| Находка | Где закрыто | Статус | Что осталось / новая проблема |
|---|---|---|---|
| V-1 | Шаг 2 п. 1 (а): подпись принимается при доле кодов ≥ 0,6 или кодах ≥ 3; при нескольких кандидатах — больше кодов; «документ из двух строк» = 2 из 2; решение № 3 | closed | Проверил на данных. Унагранде ББ Лист 1: «Коды» (5,33) даёт 1 код из 9, доля 0,11; «Код» (27,21) — 43 из 43. ОФР Лист 1 3кв.25: «Коды» 1 из 25, «Код» 17 из 17. Титульный столбец отсекается, форма берётся верно |
| V-2 | «Код и наименование строки»: пятизначный код подтверждён через четырёхзначного родителя; применяется только к кодам с уверенностью < 100 (Р-12); Excel и чистые OCR-коды дают info ядра |
closed | Формулировки уточнить в спеке: W-12 (правило родителя), W-13 (границы алгоритма) |
| V-3 | Шаг 4 (values[].text); DBML v5.19 source_text; конвенция знака «от сохранённого text» |
closed, new issue | W-8: у правленой ячейки text устаревает и участвует в голосовании за конвенцию колонки |
| V-4 | Шаг 2 п. 2: сегмент без шапки и с кодами < 3 «наследует столбец кодов» предыдущего сегмента | partial | W-4: у НПЗ столбец кодов на каждом листе свой, наследование по индексу не работает |
| V-5 | DBML v5.19: financial_statement_line.needs_review; PATCH: line_code_modified ⇒ needs_review строки = false |
closed | — |
| V-6 | Шаг 8 п. 2: «ИНН не найден при найденной организации» — info, «ни организации, ни ИНН» — error; «формат не совпал» — info |
closed | Согласовано с критерием done п. 1 и решением № 1. НПЗ ОФР без ИНН остаётся done |
| V-7 | Р-15; «Что уходит из ядра»: вторая форма не автоподтверждается, подтверждение второй — 409; вопрос о склейке — в 07-open-questions.md |
closed | Решено владельцем. Формулировка 409 без оговорки «из одного файла», но между файлами работает автозамена (§12 п. 3), поэтому на практике это только внутри файла |
| V-8 | Шаг 2: роль title для всего выше строки заголовка; шаг 3: missing только между заголовком и первой строкой с кодом |
closed | Межениновская: «Дата 30 | 9 | 2025» теперь title. Повторная шапка «Коды» (стр. 48 Формы 1) открывает новый сегмент по п. 2, а не unreadable |
| V-9 | Шаг 3 п. 2: разлепление «до разбора кода»; шаг 2 п. 1: многострочная ячейка — код, если каждая часть код | closed | — |
| V-10 | DBML v5.19: recognition_job.statements_version |
closed | — |
| V-11 | Принцип 4, шаг 5 п. 2, «Что уходит из ядра»: словарь ядра хранит только имена, редакции берутся из копии общего кода, sync-statements --check |
closed | Побочно: коды перечня без имён (1105, 1530, …) дают info «не в словаре имён» — безвредно |
| V-12 | PATCH lines: переходы, 422 для пары «ok без кода», пересчёт родителя detail, аудит |
partial | W-11: не сказано про line_code_confidence после назначения; у строк нет аналога confirmed |
| V-13 | «Подтверждение проверкой»: чистые значения в покрытии не нуждаются | closed, new issue | W-7: фраза «ни одним правилом ниже не подсвечиваются» противоречит следующему пункту про непройденное жёсткое соотношение |
| G-1 | Р-12 + «Код и наименование строки»: источник перечня — коды шаблонов банка плюс коды печатных форм 1С; пятизначные через родителя; для Excel — info |
closed | Перечень всегда неполон: в ББ. Лист 1 (unagrande-group) есть 1231, которой нет ни в словаре ядра, ни в названных 1С-кодах. Для Excel и чистого OCR это info, для кодов с уверенностью < 100 — needs_review, что принято. Связанная дыра — W-6 |
| G-2 | Шаг 7: «цель отсутствует/null, а хотя бы один член непуст — жёсткое нарушение» |
closed, new issue | W-1: симметричный случай (цель есть, все члены пусты) не считается — три фикстуры «только актив» станут done. O-2: остаточный риск для строк вне §7 |
| G-3 | То же, что V-3: values[].text в контракте v2.0, source_text в DBML, string_value в прежнем смысле |
closed | Семантика string_value теперь одна (шаг 4, вводная) |
| G-4 | Шаг 2 п. 3: словарь периодов объединяет statement_periods.yaml и шаблоны dates.py; test_parses_real_forms — обязательные тесты |
closed | — |
| G-5 | То же, что V-1 | closed | — |
| G-6 | То же, что V-6 | closed | — |
| G-7 | Шаг 8 п. 3 и «Что уходит из ядра»: RECOGNIZED ⇔ конверт done ∧ document.status = done |
closed | O-1: document.status = done избыточен, так как конверт done уже означает «все документы done». Прежнее поведение сохранено |
| G-8 | Шаг 1 п. 6: слабое продолжение присоединяется, только если коды в диапазоне type_hint; иначе отдельный кандидат + error; шаг 2 п. 2 |
partial | W-4 (наследование); W-10 (отдельный кандидат: «не документ info» против error) |
| G-9 | Решение № 16: ref OCR = row:<row_index>[#<k>], Excel = <лист>!<A1>, уникален в документе |
closed | Мелочь, W-15: для Cell.ref у OCR позиция без столбца не уникальна на уровне ячейки |
| G-10 | Р-13: 409 LINES_UNRESOLVED при unreadable / missing с непустым amount; detail не попадает в ЕХ |
closed | — |
| G-11 | DBML v5.19: line.needs_review, recognition_job.statements_version |
closed | Маркера G-11 в тексте нет, но содержание внесено |
| G-12 | PATCH values[].confirmed: needs_review = false, is_modified не ставится, аудит |
closed, new issue | W-2: флаг подтверждения нигде не хранится, следующий пересчёт его теряет |
| G-13 | PATCH columns: [{column_id, period_date}]: снимает error «колонка без даты», пересчитывает is_current; report_date: null обнуляет относительные |
closed | W-16: исключение из правила F-8 «error сохраняются» нужно оформить явным списком |
| G-14 | Р-1: пакет владеет своими типами, хост конвертирует; golden пакета на синтетике; sync-statements --check в make check-full |
closed | — |
| G-15 | Р-14: раскладка по коду остаётся; строки со статусами — под ближайшей предыдущей размещённой; detail скрыты |
closed | W-15: не определено место missing-строки, у которой нет предыдущей размещённой (aep_3 ОФР, «Выручка» — первая строка таблицы) |
| G-16 | Шаг 4 п. 1: parse_amount(text, source), «−» как знак, тонкие пробелы, запятая по источнику |
closed, new issue | W-14: квантование тихо округляет OCR-мусор вроде «12,3456»; правило точки-тысяч (sg#37) не названо |
| G-17 | Шаг 6 п. 1: form_variant = simplified (ОКУД 0710096 или структура), свой набор соотношений и перечень |
closed, new issue | W-5: в statement_headers.yaml ОКУД 0710096 уже мапится на DISCLOSURE |
| G-18 | Принцип 5: ИНН — только цифры; шаг 2 п. 1: порядок «пробелы и переносы → двойники → проверка», | → 1 только если результат попадает в диапазон |
closed | — |
| G-19 | Шаг 6 п. 1: DISCLOSURE по type_hint — без шагов 2–7; по кодам — шаги 2–5 и отбрасывание строк |
closed, new issue | W-9: диапазоны «1xxx / 2xxx / 3xxx–6xxx» шире словарных |
| G-20 | Шаг 8 п. 2: уровни; «организация не найдена при найденном ИНН» — info, «ни того ни другого» — error |
closed | — |
| G-21 | Шаг 3 п. 2: разлепление, только если хотя бы одна часть — код | closed | При известном type_hint «44 30» (=4430) вне диапазона ББ, не множится. При неизвестном 4430 станет кодом из-за расширения диапазонов (W-9) |
| G-22 | Шаг 2 п. 1: «краевая пунктуация» («1220 ,» → 1220) | closed | — |
| G-23 | PATCH: report_date: null обнуляет period_end относительных колонок |
closed | — |
| G-24 | Р-15 | closed | — |
| G-25 | line_refs — список; document_issue: список line_id, очистка при перезагрузке, core-записи заменяются |
closed, new issue | W-3: устаревшие нарушения источника service после PATCH |
| G-26 | Принцип 3: ядро добавляет только info по словарю имён и перечню |
closed | — |
| G-27 | Утверждение «в трёх из четырёх» в тексте дизайна отсутствует | closed | Маркера нет, исправлять нечего |
| G-28 | Шаг 2 п. 1: словарь подписей расширен («Код строки») | closed | — |
| G-29 | Шаг 2 п. 7: одно правило столбца наименования | closed | В запасной ветке («ближайшая текстовая левее кода») пропало «кроме Пояснения»; для столбцов левее кода из 66н не критично |
| G-30 | PATCH: аудит RECOGNIZED_DATA_EDIT с before / after по коду, статусу, родителю; назначенный код снимает needs_review |
closed | — |
2. Разбор незакрытых проблем и новых противоречий
W-1 (important). «Посчитано, если хотя бы один член непуст» (F-2) + G-2 теряют детектор односторонней формы
Где: шаг 7, пункты «цель отсутствует» и «соотношение считается посчитанным».
В G-2 «член» — это правая часть соотношения, отдельно от цели. Значит, цель есть, а все члены пусты — «не проверяется».
Сценарий: «ББ. Лист 1» с одним активом. 1600 есть, 1700 нет.
1600 = 1700: цель 1600 непуста, член 1700 отсутствует, соотношение не считается.1700 = 1300 + 1400 + 1500: цели нет, членов нет, тоже пропуск.- Остальные соотношения сходятся, org/ИНН и период есть, строки
ok, значения чистые — получаетсяdone.
Сегодня эти файлы needs_check, потому что отсутствующий член считается нулём (tabular-parser/api/src/parser/statements/checks.py ~122; arm-core/api/src/domain/statement_checks.py::run_checks). Манифест tabular-parser/api/tests/statements_manifest.yaml: строки 11, 24, 32 — три файла (unagrande-co 2024-FY, unagrande-group 2024-FY и 3кв.25), failed_relations: ["1600 = 1700"]. Половина баланса автоподтвердилась бы.
Предложение: соотношение посчитано, если непуста цель или хотя бы один член; пропуск только когда непусто ничего. Тогда правило «все null — ничего не подтверждает» (F-2) сохраняется, а G-2 становится его зеркалом. Обновить golden и манифест.
W-2 (important). confirmed (G-12) не хранится, пересчёт его сбросит
Где: PATCH values[].confirmed, «Подтверждение проверкой», список DBML v5.19.
Для правки есть is_modified, для акцепта есть checks_accepted_*. Для confirmed в v5.19 колонки нет: ни флага, ни confirmed_by/at, только audit_event. Пересчёт общим кодом «после PATCH» применяется ко всем значениям.
Сценарий: ячейка с уверенностью 71, верная, в строке вне §7. Аналитик подтвердил её, needs_review = false. Любой следующий PATCH другой ячейки пересчитывает её обратно: «нуждается в покрытии и не покрыто» даёт true. Правило G-12 не работает дольше одного PATCH.
Предложение: добавить в financial_statement_value признак (review_confirmed_by/at или is_confirmed) и вход «подтверждено» в общую функцию needs_review. Альтернатива: пересчёт монотонен для подтверждённых, но тогда сломанное соотношение должно возвращать true.
W-3 (medium). document_issue: устаревшие нарушения источника service
Где: «Что уходит из ядра», DBML v5.19.
Сказано только, что записи источника core заменяются при каждом пересчёте. Нарушения §7, пришедшие от сервиса (kind relation/soft, source = service), после правки значения остаются и показываются как действующие. Рядом появляются новые core-записи — дубли и устаревшее.
Предложение: при пересчёте после PATCH заменять все записи relation / soft независимо от источника. Предупреждения warning источника service сохранять, кроме явно снимаемых (W-16).
W-4 (important). V-4 / G-8: «наследование столбца кодов» не работает на геометрии НПЗ
Где: шаг 2 п. 2, решение № 3.
У НПЗ столбец кодов на каждом листе свой. Проверено openpyxl, номера столбцов с 1:
- ББ: листы 1–5 — 13, 7, 7, 7, 12.
- ОФР: лист 1 — 10, лист
_2— 6, лист_3— 12. - Лист
_2(единственная строка 2400) шириной 9 столбцов.
Если «наследуется» значит «тот же индекс, что в предыдущем сегменте» (столбец 10), то в _2 его просто нет. Получается ошибка сегмента и документ needs_check вместо done. То же с st07_single_code_continuation (там индексы совпадают, но принцип тот же). Прежний relaxed (statement.py _continuation, min_codes_continuation: 1 в statement_headers.yaml) ищет столбец по содержимому, а не наследует индекс.
Предложение: заменить формулировку на ослабленный поиск для сегмента не первого, без шапки, с кодами < 3: столбец с наибольшим числом кодов из диапазона формы, минимум 1. Оставить предупреждение error, если и так не найден. Сам индекс предыдущего сегмента не использовать. Сохранить проверку диапазона type_hint (_weak_blocker).
W-5 (medium). ОКУД 0710096 как признак упрощённой формы конфликтует со словарём типов
Где: шаг 6 п. 1 (G-17), evidence.
В tabular-parser/api/rules/statement_headers.yaml (okud: DISCLOSURE: '\b07100(0(3|…|9)|(1|…|9)\d)\b') значение 0710096 попадает в DISCLOSURE. По шагу 1 п. 4 type_hint = DISCLOSURE, коды строк 1xxx → по шагу 6 это «противоречие» → тип по кодам + error → needs_check. Пока словарь не правится, упрощённая форма с этим ОКУД никогда не будет done, как бы ни работал вариант G-17.
В фикстурах 0710096 нет вообще (только в admin/source/СУЖЦ…xls). Утверждение, что упрощённые ББ/ОФР несут именно этот ОКУД, ничем не подтверждено. По памяти упрощённые бланки 66н идут с обычными 0710001/0710002, а 0710096 относится к комплекту отчётности. Это надо подтвердить на образце или у владельца; сам я не верифицировал.
Предложение: либо убрать ОКУД из признаков упрощённой формы (оставить структурный: есть 1600, нет 1100 и 1200), либо явно исключить 0710096 из DISCLOSURE в словаре. Образец упрощённой формы — в фикстуры.
W-6 (medium). Р-11 vs Р-12: код, исправленный из двойников с уверенностью 100, не проходит ни одной проверки
Где: принцип 5 / Р-11 и «Код и наименование строки» / Р-12.
Р-11 велит заменять двойники и в кодах, а для суммы вводит is_recognized = false, чтобы её проверил §7. У кода такого флага нет. Структурное подтверждение применяется только при line_code_confidence < 100. Код «I140» или «1l50», прочитанный ИС с уверенностью 100, получает ok без needs_review. §7 перепутанные соседи не ловит (сумма раздела та же), поэтому запись сохраняется без проверки.
Предложение: считать код после замены двойника «неуверенным» (эффективная уверенность < 100, или признак code_corrected) и пропускать через структурную проверку. Это одна строка в «Код и наименование строки».
W-7 (minor). Внутреннее противоречие в «Подтверждении проверкой»
Первый пункт: чистые значения «ни одним правилом ниже не подсвечиваются (V-13)». Пятый пункт: true «у всех ячеек жёсткого соотношения … независимо от уверенности». Второе включает чистые Excel-значения, поэтому первое надо сузить: «ни правилом покрытия, ни правилом знака».
W-8 (minor). Устаревший text правленой ячейки в голосовании за конвенцию знака
Конвенция — форма записи у большинства членов abs_terms с is_recognized = true, считается от сохранённого source_text. Если аналитик исправил знак у члена, source_text прежний, а amount новый, и ячейка голосует старой формой. Что делает PATCH с is_recognized, не сказано.
Предложение: исключить из голосования ячейки с is_modified (или считать форму от знака amount).
W-9 (medium). G-19: диапазоны кодов шире словарных
Где: шаг 2 п. 1, шаг 6 п. 1.
Словарь парсера: 1100–1799, 2100–2999, 5000–5999 (tabular-parser/api/rules/statement_headers.yaml, line_codes.ranges). В дизайне «1xxx — ББ, 2xxx — ОФР, 3xxx–6xxx — прочие», при неизвестном type_hint объединение.
Два риска:
- Годы в шапках (2024, 2025) попадают в 2xxx. В НПЗ ОФР в столбце 16 стоит «2025».
- Значения в тысячах от 3000 до 6999 считаются кодами, а столбец сумм начинает конкурировать со столбцом кодов за «наибольшее число» (кодов ≥ 3, доля ≥ 0,6 — слабая защита). Для разлепления G-21 «44 30» (=4430) тоже становится кодом, если
type_hintнеизвестен.
Предложение: в дизайне явно записать числовые диапазоны (оставить 1100–1799, 2100–2999, расширить DISCLOSURE до 3000–6999 только при неизвестном type_hint и тестами) вместо «1xxx / 2xxx».
W-10 (minor/medium). Одинокий лист-продолжение: «не документ, info» против error
Где: шаг 6 п. 1 и шаг 8 п. 2.
Лист «Справка» с кодом 1370 после ОФР не присоединяется (шаг 1 п. 6) и становится отдельным кандидатом. У него кодов < 3 и type_hint нет. Шаг 6 п. 1 говорит «не документ, info», шаг 8 п. 2 — «сегмент с содержимым без колонки кодов (в том числе потерянный лист-продолжение) — error». Данные листа теряются, done у соседних документов возможен: нарушается принцип 2.
Предложение: в шаге 6 п. 1 добавить исключение — лист с числами и не присоединившийся продолжением даёт файловое error (и строки сохраняются как unreadable / detail, если возможно).
W-11 (minor). Остатки V-12 / G-30
- После назначения кода (
unreadable/missing/detail→ok) не сказано, чему равнаline_code_confidence(100?). Сейчас остаётся прежняя, возможно 25. - У строки есть
needs_review(структура кода), но нетlines[].confirmed. Код с уверенностью 50, стоящий не по порядку, но верный, снимается только повторным назначением того же кода, которое в тексте как «другой код» не описано (аналог G-12 для строк).
W-12 (minor). Правило родителя пятизначного кода
В «Код и наименование строки»: «ближайшая строка выше с теми же четырьмя первыми цифрами, иначе код из первых четырёх цифр — правило subline_parents». В реальном коде (arm-core/api/src/domain/statement_checks.py::subline_parents) ближайшая четырёхзначная строка выше с теми же тремя цифрами, иначе код из первых четырёх. Первая половина фразы при буквальном чтении матчится на пятизначного соседа (11101 выше 11102). Привести к тексту кода.
W-13 (minor). Границы алгоритма подтверждения структурой
- Наибольшая подпоследовательность не единственна. Для двух поменявшихся соседей (1230, 1220) подтверждается один из двух, и не определено, какой остаётся без подтверждения. Если выпавший код имеет уверенность 100, он освобождён от проверки, а второй числится «подтверждённым».
- «Хотя бы в одной из проверяемых редакций»: берётся объединение подпоследовательностей двух редакций или одна общая? Правка: выбирать редакцию с наибольшей подпоследовательностью.
- Родитель с уверенностью 100 (освобождён) для пятизначного дочернего кода считается «подтверждённым» или нет? Записать.
W-14 (minor). OCR: тихое округление и правило точки
Решение № 13 («2 знака, half-up») в общем parse_amount для OCR-текста. Сегодня шлюз отвергает такие значения: scan-gateway/api/src/normalizer/ocr/document.py:~143-150, решение № 30: «0,125», «12.3456» — артефакт, amount = null, текст в string_value. По дизайну «12,3456» станет 12,35, причём is_recognized не оговорён. Оставить квантование только для числового value (Excel), а для OCR-текста сохранить null + string_value.
Правило точки-тысяч (837.994, 1234.567 — sg#37, amounts.py _DOT_THOUSANDS_RE) в шаге 4 п. 1 не названо. Указать, что оно сохраняется для ocr и как ведёт себя для excel.
W-15 (minor). Экран и ref
- Р-14: у
missing-строки без предыдущей размещённой (aep_3 ОФР, «Выручка» — первая строка таблицы) место не задано. Предложение: в начало первого раздела. Cell.refу OCR —row:<row_index>без столбца: уникально для строки, но не для ячейки. В шаге 1 сказано «уникальная в документе», уточнить, что этоRawLine.ref.
W-16 (minor). Исключения из «error сохраняются после правки» (F-8)
PATCH columns снимает error «колонка без даты» (G-13). Остальные error сервиса (несовпадение колонок сегмента, «строка заголовка не найдена», тип по кодам и др.) сохраняются. Записать явный перечень «какая операция PATCH какое error снимает», иначе реализация разойдётся.
Наблюдения (не противоречия)
- O-1. Вторая часть
RECOGNIZED(«document.status = done») избыточна при требовании «конвертdone»: конвертdoneозначает «все документыdone». Один плохой документ блокирует автоподтверждение всех остальных в файле. Так же было и раньше (job_doneвstatement_status). Если нужна независимость документов, это отдельное решение. - O-2. Строки вне §7 с утерянным кодом (справочные 25xx / 29xx и подобные) уходят в
detailбез нарушения. Раньше это был счётчик, но любое предупреждение шлюза давалоneeds_check. Теперь такая строка скрыта, а документ может статьdoneи автоподтвердиться. Принято Р-8, но шапка Р-13 ограниченаunreadable/missing, а неdetail. - O-3. В критерии
doneнет уверенности полей шапки. Шлюз требовал 100 для ИНН и организации. Остаётся сверка ядра (MISMATCH). Намеренно ли это ослабление, в дизайне не сказано.
3. Согласованность правок между собой
Проверил и признал согласованным:
- Р-12, G-1, V-2 и перечень V-11. Перечень (superset словаря ядра) — в общем коде, имена — только в ядре,
--checkв одну сторону (словарь ⊂ перечень) достаточен. Пятизначные коды, 1105, 1530, 2420, 2460, 2930 покрыты; Excel и чистые OCR-коды даютinfo. Оговорки: W-6 (двойники), W-12 и W-13. - G-2 + Р-8 + G-17. Для «БАЛАНС» без кода:
detail→ цель 1600 отсутствует → жёсткое нарушение, подсвечивается родитель. Для сегмента «Лист 2» цель 1600 отсутствует, член 1700 непуст → нарушение, документneeds_check(как в golden). Упрощённая форма использует свой набор. Оговорки: W-1 (симметричный случай), W-5 (ОКУД), O-2. - G-3 и V-3.
textиstring_valueс разными ролями;source_textв DBML; контракт и DBML соответствуют. Оговорка: W-8. - V-1 и G-5 «из двух строк». Для двухстрочного документа 2/2 = 1,0, титульный столбец отсекается: проверено на Унагранде (доли 0,11, 0,12, 0,04).
- Уровни V-6 / G-6 / G-20 и критерий
done. Списокerrorиinfoсогласован между п. 1 и п. 2 шага 8 и решением № 1. НПЗ ОФР без ИНН —done. - Статус конверта в
RECOGNIZED(G-7). Согласовано с шагом 8 п. 3. Замечание — O-1. - PATCH
lines/columns/confirmedи аудит. Один PATCH — один пересчёт, аудитRECOGNIZED_DATA_EDITсbefore / after. Оговорки: W-2, W-11, W-16. - DBML v5.19 и контракт v2.0. Проверил соответствие:
line_status,line_code_text,row_index,ref→source_ref,parent_ref→parent_row_index,needs_reviewстроки и значения;values[].text→source_text,columns[].period_kind,form_variantв meta;warnings[]иchecks→document_issue,statements_version→recognition_job;- снятие уникальности
documentи(meta_id, line_code)плюс порядковый номер формы. Не хватает только флага подтверждения (W-2).
- Раскладка экрана (Р-14). Совместима с Р-4 (дубли кодов в порядке файла), шаблонными строками и
detailпод родителем. Оговорка — W-15. - Р-15 и автозамена. Автозамена между файлами (§12 п. 3) убирает дубли ББ/ОФР между файлами, поэтому Р-15 действует только внутри файла.
4. Что смотрел
Дизайн и ревью (.wt/normalization/docs/openspec/changes/unified-statement-normalization/):
design.mdцеликом, включая «Целевую картину», «Решения владельца Р-1…Р-15» и «Decisions made without discussion» 1–19;review-2026-10-17-closure.md(V-1…V-13) иreview-2026-10-17-blind.md(G-1…G-30) целиком.
Правила и модель:
docs/context/05-domain-rules.md§12 п. 3 (автозамена), строки 680–695;docs/model/data-model-v5.18.dbml:document(757–780),recognition_job,financial_statement_meta/column/line/value.
tabular-parser:
api/rules/statement_checks.yaml,statement_headers.yaml(okud,line_codes);api/src/parser/statements/checks.py(run_statement_checks);api/tests/statements_manifest.yaml;CLAUDE.md.
scan-gateway: api/src/normalizer/ocr/amounts.py (parse_amount), document.py (_value).
arm-core:
api/src/domain/statement_checks.py(subline_parents,_subline_rules,run_checks);api/src/services/statements.py(PATCH-цикл, пропуск несуществующей ячейки);api/rules/statement_lines.yaml.
Фикстуры:
fixtures/golden/expected_statement_parse/**:Б.Б. Лист 2,ОФР. Лист 2,st05— статусы и предупреждения;fixtures/ocr/expected_normalize/*.jsonи golden BALANCE/INCOME — коды вне словаря (найдены 1105, 1231, 2930);fixtures/statements-xlsx/npz/2025-Q3/*.xlsx— столбцы кодов по листам;fixtures/statements-xlsx/mezheninovskaya/2025-Q3/1_Форма1_бух_09.xlsx— повторная шапка и title;fixtures/statements-xlsx/synthetic/*.xlsx;unagrande-co/2024-FY/Б.Б Лист 1.xls,unagrande-group/2025-Q3/ОФР. Лист 1 3кв.25.xls,ББ. Лист 1 3кв.25.xls— подписи «Код» / «Коды» и доли кодов.
Скрипты — в scratchpad (npz.py, g2.py, ux.py, xl.py, codes.py). Не смотрел: arm-web; services/documents.py и auto_confirm.py — для Р-13 и Р-15 опирался на текст дизайна, а не на код.