Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BJKMLHdxMwSUeYcdkwqX4W
38 KiB
Финальное ревью design.md вслепую — третий раунд (17.10.2026, Opus)
Ревьюер: отдельный агент (модель Opus), без права правки и без доступа к отчётам предыдущих раундов; получил
design.md после коммита e6400bf, правила, контракт, DBML, фикстуры и код; прогонял текущий парсер на Excel-фикстурах.
Маркеры находок — H-n.
Сверка перед передачей владельцу (сессия): подтверждены по коду и правилам H-1 (= W-1; манифест парсера, строки 11,
24, 32), H-2 (document.py:203-206 — шлюз требует уверенность 100 у ИНН и организации и распознанную дату),
H-8 (result_mapping.py R-1: первый документ при повторе типа — NEEDS_CHECK), H-10 (§11.1 п. 4, строка 1006:
«документ needs_check: разбивку подтверждает аналитик», решение владельца 08.10.2026), H-16 (= W-12;
subline_parents: «те же три цифры»). Пересечения с отчётом Sonnet: H-1 = W-1, H-5 = W-9, H-9 = W-10, H-11б = W-2,
H-11в = W-11, H-13б = W-5, H-16 = W-12, H-18 = W-14.
Отчёт агента ниже без сокращений.
Ревью дизайна unified-statement-normalization (свежий взгляд)
Документ: /Users/andrey.epifancev/projects/awp-ca/.wt/normalization/docs/openspec/changes/unified-statement-normalization/design.md. Номера строк дизайна ниже приблизительные (±2).
Critical
H-1 (critical). Лист только с активом снова получает done и автоподтверждается.
- Где: шаг 7, правило «соотношение посчитано, только если хотя бы один член непуст» (F-2, ~стр. 554); «Подтверждение проверкой».
- Дыра. Членом считается только правая часть: так прямо сказано по аналогии с AURORA-1527 и так же используется в правиле G-2. Правило
1600 = 1700при отсутствующей строке 1700 больше не считается. - Сценарий.
unagrande-co/2024-FY/Б.Б Лист 1.xls— в манифесте парсераneeds_check«только актив: 1600 = 1700 не сходится» (tabular-parser/api/tests/statements_manifest.yaml:8-11). Я прогнал парсер:- 1100, 1200, 1600 сходятся (1200 — в пределах ±1);
- 1700, 1300, 1400 и 1500 в файле нет;
- по новым правилам
1600 = 1700не считается, жёстких нарушений нет; - организация, ИНН и период есть, коды Excel структурно не проверяются →
done→ в ядреRECOGNIZED→ автоподтверждение половины баланса. - То же у
unagrande-group/*/ББ. Лист 1*.xls. - Сегодня правило проверяется при присутствующей цели, отсутствующие члены = 0 (
tabular-parser/api/src/parser/statements/checks.py:121-127; ядроarm-core/api/src/domain/statement_checks.py:200-205).
- Как закрыть: считать цель членом при проверке «посчитано» (пропускать только случай «все пусты, включая цель»). Добавить в обязательные тесты общего кода строки манифеста со статусом
needs_check— они должны остатьсяneeds_check.
H-2 (critical). Уверенность шапки выпала из критерия done: неверно прочитанная дата отчёта даёт неверный период и автоподтверждение.
- Где: шаг 8 п. 1 (критерий
done) и п. 2 (уровни предупреждений); «Подтверждение проверкой» — фраза «Не покрыто проверками и остаётся на уверенности: поля шапки» (~стр. 616); шаг 9 (список изменений контракта); шаг 6 («confidence→*_confidence»). - Дыра. Механизма для полей шапки нет:
needs_reviewесть только у значений и строк;- в критерии
doneуверенности шапки нет; - в уровнях есть только «ИНН не найден или неуверен при найденной организации» (
info) и «дата не разобрана» (info); случая «дата разобрана, но уверенность < 100» нет вовсе; - в контракте v2.0 не добавлены
report_date_confidence,units_confidence,*_text: шаг 6 обещает их, шаг 9 — нет. - Сегодня шлюз требует 100 у ИНН и организации и «распознана» у даты (
scan-gateway/api/src/normalizer/ocr/document.py:202-208,ocr/__init__.py:79-84,103). Ядро требует, чтобы конверт былdone.
- Сценарий. В шапке ИС ИНН и организация с уверенностью 100, а «30 сентября 2025» прочитано как «30 сентября 2024» с уверенностью 71. Таблица чистая.
- Эффективная дата = дата отчёта (она приоритетнее колонок), период 3 кв. 2024.
- Относительные колонки тоже считаются от неё.
- Итог:
done→ автоподтверждение и привязка к версиям 3 кв. 2024. - Автозамена (§9.5 п. 3) вытесняет чужой файл того же ключа, в том числе с подтверждённым документом.
- В фикстурах дата шапки часто мусор: aep_3 «2|0|2|4|»@75, у aep_6 и «БО 1 кв» в поле даты — название организации с уверенностью 100.
- Как закрыть:
- ввести признак «требует проверки» для полей шапки (как
needs_review); - дату отчёта с уверенностью < 100 подтверждать структурой: совпала с самой поздней абсолютной датой колонок — подтверждена, иначе
error; - то же для подписей колонок OCR с уверенностью < 100;
- добавить поля уверенности шапки в список изменений v2.0.
- ввести признак «требует проверки» для полей шапки (как
H-3 (critical). В сегменте без шапки роль title поглощает строки с суммами — данные теряются молча, missing недостижим.
- Где: шаг 2, определение ролей (
title— «а без заголовка — выше первой строки с кодом», ~стр. 410) и п. 4; шаг 3, определениеmissing(«между заголовком сегмента и первой строкой с кодом … потерян в самом начале таблицы», ~стр. 450). - Дыра. Для сегмента без шапки всё выше первой строки с кодом —
title, и в данные оно не попадает. Поэтому строка с потерянным кодом в начале такой таблицы исчезает. Это противоречит принципу 2 и определениюmissing. - Сценарий 1, aep_3 (в ИС нет строки шапки):
- table 0, row 0: «Нематериальные активы», код
'НИ'@0, сумма'30127'@60. Первая строка с кодом — row 1 (1120), поэтому row 0 —title, НМА пропадает; - table 1, row 0: «Выручка», код пуст,
'211(1 |'. Это выше2120, тожеtitle. - Сегодня обе строки хотя бы считаются в «строк без кода с суммами пропущено» (golden
fixtures/ocr/expected_normalize/aep_3.json). Теперь даже счётчика нет, Р-13 не блокирует подтверждение, ядро достраивает пустую шаблонную 1110.
- table 0, row 0: «Нематериальные активы», код
- Сценарий 2, Excel. Лист-продолжение без шапки, первая строка — расшифровка без кода с суммой. В НПЗ такие строки есть: «Депозитные счета» на Листе 3, «Авансы по инвестиционной деятельности» на Листе 1. Если разрыв страницы придётся на них, строка становится
titleи пропадает. - Как закрыть:
title— только выше строки заголовка (и титульный блок первого сегмента Excel). В сегменте без шапки строки выше первого кода, где в колонках значений есть содержимое:missing, а при непустой ячейке кода —unreadable.
H-4 (critical). Структурная проверка кода не ловит подмену на соседний или незапечатанный код — автоподтверждение с неверной разноской.
- Где: «Код и наименование строки» (LIS по порядку бланка, «Равные соседние коды допустимы (Р-4)»), Р-5, Р-12. Дизайн сам приводит мотив: «перепутанные соседи 1220 и 1230 дают ту же сумму раздела».
- Дыра. LIS ловит только нарушение порядка. Есть два пути мимо:
- (а) дубль. OCR прочитал 1230 как 1220@50. Последовательность 1210, 1220, 1220, 1240 допустима: равные соседи разрешены Р-4. Сумма раздела в
1200 = …та же → покрыто →done. Итог: 1220 = НДС + ДЗ, 1230 пуст. Такой дубль неотличим от законного: в «Бухгалтерская отчетность 1 кв.25.json» row 28 — настоящая вторая строка1220@50«НДС, начисленный по отгрузке». - (б) подмена на незапечатанный код того же раздела. 66н позволяет не печатать пустые строки; в том же файле 1120 нет: 1105, 1110, 1130. Чтение 1110 как 1120@50 даёт порядок 1105, 1120, 1130 — согласован. Сумма 1100 та же →
doneс НМА в строке «Результаты исследований».
- (а) дубль. OCR прочитал 1230 как 1220@50. Последовательность 1210, 1220, 1220, 1240 допустима: равные соседи разрешены Р-4. Сумма раздела в
- Как закрыть: решение владельца, варианты:
- для кода с уверенностью < 100 сделать условием
RECOGNIZEDв ядре совпадение распознанного наименования со справочным по коду (открытый вопрос ядра перестаёт быть открытым — это знание ядра, как квартал); - либо дубль с уверенностью < 100 и подмена в «дыру» бланка →
needs_review. - В любом случае признать риск в тексте: сейчас текст обещает защиту, которой нет.
- для кода с уверенностью < 100 сделать условием
Important
H-5 (important). Диапазоны кодов противоречат сами себе и молча расширены.
- Где: шаг 2 п. 1 («1xxx — ББ, 2xxx — ОФР, 3xxx–6xxx …;
type_hintнеизвестен — объединение»), пункт про tabular-parser («диапазон формы → объединение»), шаг 3 п. 2 (G-21: «"44 30\nI |" aep_6 не множится»), шаг 6 п. 1 (тип по большинству кодов). - Дыра:
- Если
type_hintизвестен и берётся только его диапазон, то в OCR-таблице сlabel«ББ» и кодами 2xxx колонка кодов не найдётся. Сверка «тип по кодам» в шаге 6 станет недостижимой, а сегодняшнее поведение шлюза #30 (единодушные коды) потеряется. - Если всегда объединение, то «4430» — код 4xxx, и пример G-21 неверен: строка разлепится. Строка aep_6 row 90
'4 400\nI #4У'даст строкуokс кодом 4400 в балансе. - Расширение до 1000–6999 по сравнению с сегодняшними 1100–1799 / 2100–2999 / 5000–5999 (
tabular-parser/api/rules/statement_headers.yaml:86-88) делает кодами годы 2000–2099 («2025» титульных блоков) и 4-значные суммы на 1–6.
- Если
- Как закрыть: явно задать «кодовые диапазоны» (объединение узких диапазонов, как сейчас, плюс 3xxx/4xxx/6xxx по точным границам форм) и «диапазон формы» (для наследования и слабых продолжений). Исправить пример G-21.
H-6 (important). Неясно, на каком уровне выдаётся error «строка заголовка не найдена»: если на сегмент — НПЗ теряет done.
- Где: шаг 2 п. 4 и п. 2.
- Сценарий. Книга НПЗ сегодня
done(оба документа). У листов «Бухгалтерский баланс. Лист 2/3/5» и «Отчет о финансовых результата_2» шапки нет: я выгрузил ячейки. Если предупреждение п. 4 выдаётся на каждый сегмент — документneeds_check, регрессия. - Дополнительно. Колонки сегмента без шапки «по числам» сопоставляются с первым сегментом по порядку. Если на листе-продолжении средняя колонка пустая или в прочерках, найдутся две колонки, и значения 2023 окажутся в колонке 2024. Документ
needs_check, но в БД данные лягут неверно. Сегодняjoin_blockerтакой лист не присоединяет (tabular-parser/api/src/parser/statements/merge.py:39-40). - Как закрыть:
error— только если заголовка нет ни у одного сегмента документа. Колонки сегмента без шапки сопоставлять по положению относительно столбца кода или по колонкам первого сегмента; при несовпадении значения сегмента не раскладывать.
H-7 (important). Сборка листов до раскладки снимает защиту по числу колонок.
- Где: шаг 1 п. 6 («по маркеру или по позиции листа»; «проверка числа колонок переезжает в шаг 2 п. 2»).
- Сценарий. После формы идёт лист без маркера и заголовка с 3+ кодами того же диапазона, например расшифровка или форма другого периода, с другим числом колонок.
- Сегодня это отдельное «одинокое продолжение».
- По дизайну он вливается сегментом. Его коды дублируют коды формы и по Р-4 суммируются. §7 падает, но разделить данные аналитик может только пометкой каждой строки как
detail.
- Как закрыть: оставить несовпадение числа колонок условием отказа в склейке (продолжение становится отдельным кандидатом с
error).
H-8 (important). Р-15 автоподтверждает первую форму, а сегодня первая — NEEDS_CHECK; аналитик может оказаться в тупике.
- Где: Р-15, «Что уходит из ядра» (
statement_status, «Подтверждение документа»). - Дыра:
- Сегодня при второй форме того же типа первая получает
NEEDS_CHECK(arm-core/api/src/services/result_mapping.py:352,364, R-1/OQ-77). - Дизайн запрещает автоподтверждение только второй. Первая может автоподтвердиться, а подтверждение второй — 409.
- Если неверна первая (двойная печать, половинка), выхода нет:
CONFIRMEDтолько для чтения, повторная загрузка того же файла отсекается дедупом по sha256, удаление часто запрещено (§9.5 п. 5). - Неоднозначно и «у ЮЛ» в 409: только тот же файл или любые файлы ЮЛ (после правки даты отчёта)?
- Сегодня при второй форме того же типа первая получает
- Как закрыть: при двух и более формах одного типа и периода в файле не автоподтверждать ни одну. Уточнить область 409.
H-9 (important). Противоречие: OCR-таблица без колонки кодов с неопознанным label пропадает с info.
- Где: шаг 6 п. 1 («
type_hintнет → не документ,info») и списокinfoв шаге 8 п. 2 («не документ безtype_hintи кодов») против спискаerror(«таблица или сегмент с содержимым без колонки кодов»). - Сценарий. Плохой скан:
labelОФР зашумлён (не сопоставился), колонка кодов не найдена. Таблица —info, а соседний ББdone→ конвертdone→ ББ автоподтверждён, потеря ОФР не видна. Сегодня шлюз выдаёт «нет шапки и столбца кода» →needs_check(scan-gateway/api/src/normalizer/ocr/__init__.py:96-98). - Как закрыть: для источника
ocrкаждая таблица ИС — кандидат, её потеря —error. Для Excel «прочее» —info.
H-10 (important). Разлепление склеенных строк молча меняет решение владельца по OQ-156.
- Где: шаг 3 п. 2; таблица уровней шага 8 п. 2.
- Дыра. В §11.1 п. 4 по решению владельца 08.10.2026: «документ
needs_check: разбивку подтверждает аналитик». В таблице уровней разлепления нет. В «Бухгалтерская отчетность 1 кв.25» строка1500\n1700по суммам сходится, и документ может статьdoneи автоподтвердиться. Решение не вынесено ни в Р-n, ни в «Decisions made without discussion». - Как закрыть: явно указать уровень и записать в решения.
H-11 (important). Пересчёт ядра после PATCH: устаревшие error сервиса и подтверждения, которые нигде не хранятся.
- Где: принцип 3; «Подтверждение проверкой» («предупреждения
errorдокумента сохраняются и после правки»); PATCH; DBML v5.19. - Дыры:
- (а) Из
errorсервиса правкой снимается только «колонка без даты». После PATCHreport_dateостаются «период не определён» и «дата не разобрана», после PATCHinn— «ни организации, ни ИНН». Статус после правки навсегдаNEEDS_CHECK, а экран показывает уже исправленные причины. - (б)
values[].confirmedнегде хранить: в списке изменений v5.19 уfinancial_statement_valueнет признака подтверждения. На следующем пересчётеneeds_reviewвычисляется заново из уверенности и §7, и подтверждение пропадает (G-12). - (в) Нет способа подтвердить верный код с низкой уверенностью без смены. Переход
ok → okописан только «с другим кодом», поэтомуneeds_reviewстроки снять нельзя.
- (а) Из
- Как закрыть: таблица «какое
errorсервиса снимается какой правкой»; колонкаis_confirmed(илиconfirmed_by/at) у значения и у строки в v5.19; переход «подтвердить код».
H-12 (important). Шаблонные строки и новые поля строки.
- Где: PATCH
lines(«шаблонная строка с таким кодом заменяется настоящей»), DBML v5.19. - Сценарий. Код строки НМА потерян (
unreadable).- Аналитик вводит суммы в достроенную шаблонную 1110. Ядро создаёт её в БД (
line_code_original_value = NULL; адресация поline_code—arm-core/api/src/services/statements.py:219-245). - Потом аналитик назначает код 1110 строке
unreadable. - Возможны два исхода: обе строки
okи суммируются по Р-4 (двойной счёт), либо «замена» стирает ручные суммы (аудит?).
- Аналитик вводит суммы в достроенную шаблонную 1110. Ядро создаёт её в БД (
- Также не определены:
row_index,source_ref,line_statusдля строк, созданных правкой;- заполнение этих полей для существующих строк при миграции;
- уникальность
source_refпри NULL. repositories/statements.py:74ключует вставку поline_code.
- Как закрыть: правило слияния шаблонной строки (перенос правок или запрет с 409), значения полей для строк из правки, заполнение при миграции.
H-13 (important). Упрощённые формы: регрессия ОФР и недостижимое определение.
- Где: шаг 6 п. 1 (
form_variant), шаг 7, решения 14–15. - Дыры:
- (а) Правило G-2 «цель отсутствует при непустых членах — жёсткое нарушение» делает упрощённый ОФР (2110, 2120, 2330, 2340, 2350, 2410, 2400 без 2100/2200/2300) вечным
needs_check. Структурный признак упрощённой формы задан только для баланса. Решение 14 утверждает «как и сегодня», но сегодня цель отсутствует — правило пропускается, и упрощённый ОФР может бытьdone. - (б) 0710096 — по моим сведениям КНД ФНС, а не ОКУД печатной формы; на бланке ББ/ОФР стоят 0710001/0710002. Проверить. Кроме того, 0710096 подпадает под шаблон DISCLOSURE словаря ОКУД (
statement_headers.yaml:26) →type_hint = DISCLOSURE, а для DISCLOSURE по подсказке шаги 2–7 не выполняются. До определения варианта дело не дойдёт. - Это частный случай противоречия «окончательно — по кодам строк в шаге 6, у обоих» и «DISCLOSURE по
type_hint— шаги 2–7 не выполняются».
- (а) Правило G-2 «цель отсутствует при непустых членах — жёсткое нарушение» делает упрощённый ОФР (2110, 2120, 2330, 2340, 2350, 2410, 2400 без 2100/2200/2300) вечным
- Как закрыть: структурный признак для ОФР (есть 2400/2110, нет 2100/2200/2300); снять 0710096 или поправить его разбор; оговорить исключение для DISCLOSURE.
H-14 (important). Объём контракта v2.0 и перехода не охватывает ОСВ и конверт.
- Где: шаг 9, шаг 8 п. 3.
- Дыры:
metrics.warningsстановится структурой. В том же результате tabular-parser отдаётosvDocumentи строковые предупреждения ОСВ; переделка пути ОСВ не описана.- «Конверт
done⇔ все документыdone» — у ОСВ нетdocument.status. Не определено, как агрегируется смешанная книга ОСВ + формы. - При приёме 1.x у документа нет
document.status, значит нужен откат к старому D-6 (job_done ∧ checks ∧ values_ok …). Сказано только проneeds_review. - ack тоже несёт
schema_version— ядро должно принимать ack 1.x/2.0 в переходный период. to_moneyиспользуется и ОСВ (tabular-parser/api/src/parser/classify.py:183). Замена его общимparse_amount(шаг 4 п. 1) без ограничения областью форм меняет разбор ОСВ: двойники, «лучшая догадка».
- Как закрыть: описать в спеке контракта ОСВ-часть и агрегацию; запасной статус для 1.x; ограничить замену
to_moneyформами.
H-15 (important). xlsb + «тыс. руб.» + дробные значения = вероятный масштаб ×1000, а уровень — info.
- Где: шаг 4 п. 4; шаг 8 п. 2 (
info: «дробные при тыс. руб.», «xlsb без числовых форматов»). - Сценарий. Выгрузка 1С в xlsb хранит рубли с копейками, а в тысячи их переводит формат, который для xlsb не читается. Значения в 1000 раз больше, §7 сходится при любом масштабе →
done→ автоподтверждение в КФО. Это унаследовано (сегодня статус тоже не меняется), но дизайн закрепляет это явно. - Как закрыть: сочетание «xlsb без форматов ∧ доля дробных ≥ порога при тыс.» →
error.
Minor
H-16 (minor). Правило родителя пятизначной подстроки описано неверно.
- Дизайн: «ближайшая строка выше с теми же четырьмя первыми цифрами, иначе код из первых четырёх цифр — правило
subline_parents». - Код: первые три цифры (M-9):
arm-core/api/src/domain/statement_checks.py:141-162,tabular-parser/.../checks.py:91-96; §12 п. 7 «12310 под 1230». - По тексту дизайна 12310–12314 НПЗ (Лист 2) теряют родителя. Для OCR-кодов с уверенностью < 100 это ещё и неподтверждённая структура →
needs_check; ломаются мягкая проверка и показ.
H-17 (minor). Неточности базовой линии.
- Таблица шага 2, строка 1: шлюз ищет столбец кодов по справочнику кодов (
infer_layout,scan-gateway/api/src/normalizer/ocr/layout.py:82), аint()— это классификация строк. - G-2 «сегодня пропуск»: верно только для отсутствующей цели. Цель с
nullпри непустых членах уже сегодня жёсткое нарушение (null = 0 при присутствующей строке).
H-18 (minor). parse_amount не доопределён.
- Не указано, как правило «точка — разделитель тысяч» (sg#37) работает для
excel. Сегодня Excel «1.500» = 1,5, при общем правиле станет 1500. - Не указана судьба текста OCR с более чем двумя знаками после запятой («0,125», решение №30:
amount = null). - Не определена многострочная сумма в строке с однострочным кодом: склейка «7А 794\n8 У 201» в одну «догадку». Правило «многострочное →
null» сформулировано только в контексте разлепления.
H-19 (minor). Р-2 и шаг 8 расходятся для пустого текста с уверенностью 0. aep_3 table 1 row 15: «пустая — без подсветки» или «непрочитанная — needs_review»?
H-20 (minor). Строки без кода, где в суммах только мусор, выбрасываются как «не строка». aep_3 row 8: код [190 :, значения 'Ё'@0, ' : ।'@0, '" :'@33. Ячейки с непустым текстом и уверенностью < 100 — признак потерянных сумм; по принципу 2 их стоит считать содержимым.
H-21 (minor). missing в начале нового сегмента или листа.
- Расшифровка, перенесённая через разрыв страницы, получает
missingвместоdetailи блокирует подтверждение (Р-13). - Предложение: родитель
detailпродолжается из предыдущего сегмента документа;missing— только в первом сегменте.
H-22 (minor). Мусор в ячейке кода, в которой нет ни одной цифры. aep_6 ОФР row 4: «в том числе: Крестьянская, 175», код '}'@0, сумма 'по\n(0 215)'. Строка становится unreadable с суммой и блокирует подтверждение, хотя это расшифровка. Стоит решить, считать ли такой код пустым (detail/missing).
H-23 (minor). Р-11 и вертикальные линии таблицы. «|» вплотную к цифрам в суммах — частый след границы таблицы (aep_3 '| : 122477'). Замена на 1 даёт лишнюю цифру: §7 подсветит, но ложных подсветок станет больше. Для сумм крайние «|» убирать, а не заменять.
H-24 (minor). Мелкие противоречия и пробелы.
- DISCLOSURE по кодам при
type_hintBALANCE — одновременно «error» (тип противоречит) и «DISCLOSURE всегдаdone». - Снятие уникальности
document(Р-10) не должно снимать её у ОСВ по счёту и у DISCLOSURE/OTHER — «один на тип на файл», §9.5 п. 2. Нужен частичный уникальный индекс. - Место
LINES_UNRESOLVEDв порядке 409 (§9.5 п. 4) не задано.
H-25 (minor). Сцепка через статус конверта (G-7) шире, чем нужно.
- Сценарий: в «Бухгалтерская отчетность 1 кв.25» ОФР —
needs_checkиз-за 2400@71, а 2400 есть только в мягких соотношениях. Из-за этого не автоподтверждается и чистый ББ того же файла. - При статусе на документ достаточно условия «
document.status = done∧ нетerrorуровня файла».
H-26 (minor). Р-1: сопровождение копии.
- Расхождение
statements_versionс копией ядра →errorпри пересчёте. Но предписанный порядок выкладки («сначала ядро») гарантирует расхождение на всё окно выкладки: правленые документы будут массовоNEEDS_CHECK. Лучшеinfoили сравнение хэша словарей. - Копия «со своими тестами и конфигом линтеров» попадает под ruff и pytest хоста: конфликты правил,
testpaths,conftest. - Срочное исправление требует трёх PR в разное время — нужен описанный порядок.
Проверено и признано согласованным
- Р-4 (сумма по коду) на реальных данных верна. «Бухгалтерская отчетность 1 кв.25»: 1220 = 19 062 + 9 273 сходится с 1200 = 1 677 325; 1520 = 771 605 + 89 221 сходится с 1500 = 917 775. Сегодняшнее «оставлена первая» здесь даёт ложное нарушение.
detailпо Р-8 на НПЗ («Авансы…», «Депозитные счета») и в «1 кв.25» («в том числе…») сохраняетdone, если строки стоят под строкой с кодом в том же сегменте.- Защита от титульного блока «Коды» (V-1/G-5) на НПЗ (кол. 23 против кол. 13) и Межениновской (кол. 16 против кол. 13) работает: под титульной подписью 0 кодов. Роль
titleвыше строки заголовка корректно убирает «Дата 30 | 9 | 2025». - Конвенция знака (F-1) на ОФР «1 кв.25» и aep_3: «(7744536}» → минус, «{38378)» → подсветка. Ведёт себя, как описано.
- Р-7 при посчитанных соотношениях: подтверждение
is_recognized = falseтребует компенсирующей ошибки — согласовано с Р-5 и V-13. - Межформенная проверка,
_line_amountиstatement_amountsперечислены в F-15. Привязка к версии (consolidation_documents._key+ «самый позднийconfirmed_at») согласуется с 409 по Р-15.
Что смотрел
- Дизайн целиком;
05-domain-rules.md§7, §9.2–9.5, §11, §11.1, §12; контракт ($comment,statementDocument,metrics,service,status); DBML:document,financial_statement_*,ex_snapshot_item;03-architecture.md(контракт и копии словарей). - scan-gateway:
normalizer/ocr/__init__.py,document.py(build_document,_resolve_type,_columns,_choose_edition,_value,_line,_statement_header),layout.py,rows.py(split_merged_row,parse_table_rows,_count_skipped),amounts.py,text.py,header.py,normalizer/rules.py,rules/ocr_lookalikes.yaml,statement_labels.yaml. - tabular-parser:
statements/columns.py,lines.py,merge.py,statement.py,checks.py,book.py,numbers.py(to_money,_parse_text,quantize_money),rules/statement_headers.yaml,statement_checks.yaml,tests/statements_manifest.yaml. - arm-core:
domain/statement_mapping.py(build_statement,statement_period,statement_status,values_ok),domain/statement_checks.py(run_checks,subline_parents,cross_form_check),services/statements.py(form_confirmable,_apply_values,_resolve_cells,_apply_header),services/documents.py(_check_statement),services/result_mapping.py(OQ-77/R-1),services/consolidation_documents.py(_key,bind_confirmed),repositories/statements.py,web/src/widgets/DocumentReview/statementGrid.ts. - Фикстуры: построчно
fixtures/ocr/aep_3.json,aep_6.json, «БО 1 кв 2025.json», «Бухгалтерская отчетность 1 кв.25.json»; golden-эталоныexpected_normalize/*. Прогон текущего парсера наunagrande-co/2024-FY/Б.Б Лист 1.xls,Б.Б. Лист 2.xls, НПЗ, Межениновская Форма1/Форма2; выгрузка ячеек листов НПЗ и Межениновской. Скрипты лежат в/private/tmp/claude-501/-Users-andrey-epifancev-projects-awp-ca/12de9a9a-5665-41d0-beea-0651270d88ec/scratchpad/runp.pyи/private/tmp/claude-501/-Users-andrey-epifancev-projects-awp-ca/12de9a9a-5665-41d0-beea-0651270d88ec/scratchpad/dump.py. - Файлы не правил.