docs(#116): третий раунд — ревью вслепую на Opus (H-1…H-26)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BJKMLHdxMwSUeYcdkwqX4W
This commit is contained in:
parent
41c328f2ab
commit
f55f05efa1
@ -0,0 +1,220 @@
|
|||||||
|
# Финальное ревью 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.
|
||||||
|
- Сценарий 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` с НМА в строке «Результаты исследований».
|
||||||
|
- Как закрыть: решение владельца, варианты:
|
||||||
|
- для кода с уверенностью < 100 сделать условием `RECOGNIZED` в ядре совпадение распознанного наименования со справочным по коду (открытый вопрос ядра перестаёт быть открытым — это знание ядра, как квартал);
|
||||||
|
- либо дубль с уверенностью < 100 и подмена в «дыру» бланка → `needs_review`.
|
||||||
|
- В любом случае признать риск в тексте: сейчас текст обещает защиту, которой нет.
|
||||||
|
|
||||||
|
## 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` сервиса правкой снимается только «колонка без даты». После PATCH `report_date` остаются «период не определён» и «дата не разобрана», после PATCH `inn` — «ни организации, ни ИНН». Статус после правки навсегда `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`).
|
||||||
|
1. Аналитик вводит суммы в достроенную шаблонную 1110. Ядро создаёт её в БД (`line_code_original_value = NULL`; адресация по `line_code` — `arm-core/api/src/services/statements.py:219-245`).
|
||||||
|
2. Потом аналитик назначает код 1110 строке `unreadable`.
|
||||||
|
3. Возможны два исхода: обе строки `ok` и суммируются по Р-4 (двойной счёт), либо «замена» стирает ручные суммы (аудит?).
|
||||||
|
- Также не определены:
|
||||||
|
- `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 не выполняются».
|
||||||
|
- Как закрыть: структурный признак для ОФР (есть 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_hint` BALANCE — одновременно «`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`.
|
||||||
|
- Файлы не правил.
|
||||||
Loading…
x
Reference in New Issue
Block a user