Merge pull request 'Правила ОСВ: строки ИНН рядом с контрагентом — восемь раскладок, OQ-163 (tabular-parser #35)' (#102) from feat/35-inn-rows-around-counterparty into main

Reviewed-on: #102
This commit is contained in:
aep 2026-10-13 16:24:37 +03:00
commit 161f0d4f45
4 changed files with 52 additions and 20 deletions

View File

@ -75,10 +75,12 @@ r11 Договоры ←
**Реквизит субконто в списке группировок (OQ-162, решение владельца 13.10.2026).** Элемент вида `<группировка>.ИНН`
(вариант 2 — «Детализация по субсчетам, субконто: Контрагенты, Контрагенты.ИНН»; вариант 1 — строка `Контрагенты.ИНН` в
блоке заголовка под строкой `Контрагенты`; суффикс после последней точки — по словарю `service_columns.inn`, префикс —
имя одной из предыдущих группировок `COUNTERPARTY`) — не уровень аналитики, а реквизит
имя группировки `COUNTERPARTY` этой же шапки; реквизит может стоять до или после владельца) — не уровень аналитики, а реквизит
этой группировки. В таблице 1С выводит его отдельной строкой **под** контрагентом (уровень глубже, те же суммы, ИНН часто
числом). В `group_schema` такой элемент не попадает, у группировки-владельца `has_inn = true`; строки этого уровня
склеиваются с родителем по §1.3.
числом); элемент может стоять и **перед** владельцем («субконто: Контрагенты.ИНН, Контрагенты» / строка
`Контрагенты.ИНН` выше строки `Контрагенты` в варианте 1): тогда 1С выводит ИНН **над** наименованием. В `group_schema`
такой элемент не попадает, у группировки-владельца `has_inn = true`; элемент — подсказка для строк ИНН (§1.3, OQ-163):
задаёт направление и снимает проверку контрольной суммы ИНН. Строки ИНН склеиваются с наименованием по §1.3.
Нормализация значений шапки в типы:
@ -116,20 +118,43 @@ r11 Договоры ←
`2370012036.0` — артефакт чтения, а не значение файла), шесть сумм, ИНН (**только из колонки ИНН**; в ячейке оставляются цифры, результат
только при 10 или 12 цифрах — «ИНН 352819549855» → `352819549855`, иначе NULL; число из 9/11 цифр — ИНН региона 01–09
с потерянным ведущим нулём — при извлечении из выбранной колонки дополняется нулём слева, R-24; ИНН контрагента — в порядке
приоритета: колонка ИНН (в том числе склеенная «ИНН/КПП»), затем строка без сумм **прямо над** названием на том же уровне
оформления (в `rows` не попадает, предупреждение «ИНН … над строкой … отнесён к ней»), затем конец текста названия
приоритета: колонка ИНН (в том числе склеенная «ИНН/КПП»), затем **строка ИНН** рядом с наименованием (ниже, §1.3 «Строки ИНН», OQ-163), затем конец текста названия
(«Полиграф, 3528315710», «ООО Альфа (ИНН 7701234567)» — 10 или 12 цифр после запятой, скобки, «/», пробела или метки «ИНН»).
Отдельно — строка реквизита `<группировка>.ИНН` **под** контрагентом (вариант 1 или 2 шапки, §1.1, OQ-162): ИНН из её ячейки
извлекается как из колонки ИНН (R-24) и переходит к родительской строке `COUNTERPARTY`, сама строка в `rows` не попадает,
её дети (договоры) переходят к контрагенту на уровень выше; предупреждение «ИНН … (строка N) отнесён к контрагенту в
строке M». Склейка — только если у родителя ровно одна такая строка, её шесть сумм равны суммам родителя, ИНН
извлечён и не противоречит уже найденному ИНН родителя; иначе строка остаётся с `group_type = OTHER` (в свод не
попадает), предупреждение с причиной, документ — `needs_check`. Пустая ячейка ИНН (физлицо без ИНН — 1С выводит строку
реквизита и без значения) — «ИНН не указан»: строка склеивается, ИНН родителя остаётся NULL, `needs_check` не ставится
(рулинг R-1 #32: иначе весь документ уходил бы на проверку из-за трёх физлиц). **Реализовано** (tabular-parser #32).
**Строки ИНН (OQ-163, решение владельца 13.10.2026; заменяет правила OQ-130 и OQ-162).**
Строка, подпись которой целиком — ИНН (`inn_only_row`: 10/12 цифр числом или текстом, «ИНН …», «ИНН/КПП»; число из 9/11
цифр дополняется нулём слева — R-24). Без подсказки `<группировка>.ИНН` в шапке ИНН строки **должен пройти контрольную
сумму ИНН** (алгоритм ФНС); строка из 10/12 цифр с неверной суммой рядом с наименованием остаётся как есть (обычная
строка), предупреждение «строка N: похожа на ИНН X, но контрольная сумма не сходится — строка оставлена как есть», документ —
`needs_check`
(решение H-1: синтетика `s28`). С подсказкой сумма не проверяется, пустая строка-ребёнок наименования — «ИНН не указан»
(рулинг R-1 #32), непустая не-ИНН на этом месте — «ИНН не распознан» (такой ребёнок бывает только у наименования, которое по
схеме контрагент; у подразделения единственный ребёнок — наименование). Восемь раскладок — ИНН над / под наименованием × в
дереве / не в дереве × с суммами / без:
| Положение строки ИНН | Пара |
|---|---|
| у неё есть дети (над, в дереве) | единственный ребёнок-наименование, он поднимается на её место; несколько детей — без пары. Если подсказки в шапке нет, в `group_schema` больше одной группировки и в листе нет строк глубже схемы (R-3), строка — наименование по схеме (контрагент, названный ИНН) |
| ребёнок наименования (под, в дереве) | родитель; её дети — к нему; несколько строк ИНН у одного наименования — без пары. Родитель — пара только при подсказке «под» или если он по схеме контрагент (R-2); строка ИНН с детьми под контрагентом остаётся у него и при подсказке «над» (с предупреждением о противоречии) |
| сосед того же родителя (не в дереве) | с суммами — сосед с теми же суммами; без сумм (или совпали оба) — сосед по направлению листа |
Суммы строки ИНН, если есть, должны совпасть с суммами пары по всем шести колонкам (пусто = 0). Направление листа —
подсказка шапки (порядок элемента `<группировка>.ИНН`), а без неё — сведения пар соседей по суммам и блоков детей одного
родителя вместе (начинается строкой ИНН — над, кончается — под, начинается и кончается — блок сломан); противоречие
сведений или их отсутствие — направление неизвестно, строки без сумм без пары. Две строки ИНН на одно наименование — обе без пары. ИНН колонки, отличный от ИНН строки,
остаётся, документ — `needs_check`. Склеенная строка в `rows` не попадает, предупреждение «строка N: ИНН … отнесён к
контрагенту в строке M»; **без пары** — строка в `rows` с `group_type = OTHER` (в свод не попадает), предупреждение с
причиной, документ — `needs_check`.
**Противоречие подсказке шапки (решение H-2).** Если пара найдена, но фактическая раскладка противоречит направлению
подсказки `<группировка>.ИНН` (подсказка «над», а строка ИНН — ребёнок наименования или сосед ниже него; подсказка «под»,
а строка ИНН — родитель наименования или сосед выше него), строка всё равно склеивается, но добавляется отдельное
предупреждение «строка N: ИНН … стоит <под/над> наименованием в строке M, а шапка («…») указывает <над/под> —
проверьте», документ — `needs_check`. Раскладка, совпадающая с подсказкой (в том числе «под, в дереве» в 1С),
предупреждения не даёт; для соседей без сумм направление берётся из подсказки, конфликта нет.
**Реализовано** (tabular-parser #35, модуль `inn_rows.py`; синтетика `s18`, `s20`–`s28`).
Из текста ИНН отрезается всегда: `counterparty_name` без ИНН и разделителя, `row_value` — исходный текст; только для строк
`COUNTERPARTY`, договоры и прочее не трогаются; правила — `osv_headers.yaml` (`counterparty_inn_suffix`, `inn_only_row`,
`inn_kpp_cell`); **реализовано**, OQ-100, OQ-130 (tabular-parser #9, PR #15; синтетика `s17`–`s19`),
`inn_kpp_cell`); **реализовано**, OQ-100, OQ-163 (строки ИНН; ранее OQ-130, tabular-parser #9, PR #15; синтетика `s17`–`s19`),
`data_type` (строки «USD», «Вал.», «Кол.» под контрагентом → CURRENCY/QUANTITY; по умолчанию — из шапки).
Строки «Вал.», «Кол.» и трёхбуквенный код валюты («USD») — не уровень группировки, а вид сумм родителя:

View File

@ -286,7 +286,10 @@ fixtures/
| `s15_group_title_synonyms` | названия групп сумм «Входящее/Начальное/Конечное/Исходящее сальдо» |
| `s16_header_synonyms` | шапка без организации, период «2024», группировки `OTHER`, «Детализация: …» |
`s17`–`s19` — извлечение ИНН (из текста названия, из строки над названием, «ИНН/КПП» в одной ячейке; OQ-100, OQ-130).
`s17`–`s19` — извлечение ИНН (из текста названия, из строки над названием, «ИНН/КПП» в одной ячейке; OQ-100, OQ-130);
`s20`–`s27` — по одной на раскладку строки ИНН рядом с наименованием (над / под × в дереве / не в дереве × с суммами / без;
ИНН с верной контрольной суммой), `s28` — строка ИНН с неверной контрольной суммой (остаётся строкой, `needs_check`);
правило — `05-domain-rules.md` §1.3, OQ-163.
Формы ББ/ОФР из Excel — подраздел 4б. `s14`–`s16` добавлены тестом покрытия словарей: каждая запись `rules/*.yaml` обязана сработать на реальных или
синтетических файлах.

View File

@ -135,7 +135,7 @@
| OQ-127 | Рекомендация кандидата (`recommended`) — только точное совпадение `normalize_alias` имени строки с нормализованным именем/алиасом ЮЛ. На реальных данных «Юг-Финанс / ПЕКАРНЯ СЧАСТЛИВОЧНАЯ» (так записан контрагент в ОСВ РБ) рекомендации не даёт: аналитик находит ЮЛ поиском по `q`. Нужна ли нечёткая рекомендация (токены, порядок слов, префикс «Юг-Финанс /») или достаточно ручного выбора и накопления алиасов | arm-core Срез 4: Task 13 (O-3), design D-11 | `GET …/counterparty-candidates` | допущение принято: рекомендация точная; после первого ручного сопоставления алиас закрепляет имя |
| OQ-128 | Неоднозначность дорезолва на лету при тёзке по имени: после ручного сопоставления создаётся алиас, но `CounterpartyIndex.resolve` на шаге «нормализованное имя/алиас» возвращает контрагента только при единственном кандидате — если ЮЛ-тёзка (по нормализованному `name`) есть рядом с владельцем нового алиаса, кандидатов двое, резолв неоднозначен и строки без сохранённого `counterparty_id` остаются несопоставленными. ТЗ DOC-221278: «алиас однозначен» — решает ли это, или алиас должен иметь приоритет над именем | arm-core Срез 4: Task 11 (T11.9) | дорезолв свода (D-3), `CounterpartyIndex` | **закрыт решением владельца 07.10.2026 (вариант Б):** совпадение по алиасу приоритетнее совпадения по нормализованному имени — алиас уникален (`409 ALIAS_TAKEN`), поэтому однозначен; несколько совпадений только по имени → `NULL`. Правило — `05-domain-rules.md` §3 п. 4; реализация — RSHB/arm-core#57 |
| OQ-129 | Формулировка сценария «сопоставление и автоприменение» (spec consolidated-osv): «`unmapped_total` падает на 1 + propagated» неверна на реальных данных. `propagated` считает записи `osv_entry` (включая пары-пробелы и строки, не дающие строк свода), а свод склеивает кварталы, поэтому `unmapped_total` падает на число **склеенных** строк свода (R-15, design №80). Уточнить текст spec при архивации | arm-core Срез 4: Task 13 (T13.M1, R-15) | spec `consolidated-osv` при архивации change | допущение принято: тест сверяет обе величины отдельно; текст spec уточнён в change, `openspec/specs/` — при архивации |
| OQ-130 | ОСВ произвольного формата: ИНН в ячейке **на строку выше** названия контрагента (строка только с ИНН, без сумм). Сейчас такая строка уходит в OTHER, ИНН для резолва теряется. Решение владельца 07.10.2026: склеивать со следующей строкой контрагента; синтетические фикстуры на этот случай, на ИНН в тексте и на «ИНН/КПП» в одной ячейке | владелец, 07.10.2026 | tabular-parser §1.0 п. 4, §1.3; резолв контрагента в ядре | **реализовано** (RSHB/arm-tabular-parser#9, PR #15): строка только с ИНН над названием склеивается со следующей строкой контрагента (`inn_only_row`, предупреждение «ИНН … над строкой … отнесён к ней»), «ИНН/КПП» в одной ячейке (`inn_kpp_cell`); тесты — `test_classify.py` (`test_inn_only_row_above_is_glued_to_next_counterparty`, `test_inn_with_kpp_in_one_column_cell_gives_inn`), синтетика `s18_inn_row_above`, `s19_inn_kpp_cell`; `05-domain-rules.md` §1.3 |
| OQ-130 | ОСВ произвольного формата: ИНН в ячейке **на строку выше** названия контрагента (строка только с ИНН, без сумм). Сейчас такая строка уходит в OTHER, ИНН для резолва теряется. Решение владельца 07.10.2026: склеивать со следующей строкой контрагента; синтетические фикстуры на этот случай, на ИНН в тексте и на «ИНН/КПП» в одной ячейке | владелец, 07.10.2026 | tabular-parser §1.0 п. 4, §1.3; резолв контрагента в ядре | **реализовано** (RSHB/arm-tabular-parser#9, PR #15): строка только с ИНН над названием склеивается со следующей строкой контрагента (`inn_only_row`, предупреждение «ИНН … над строкой … отнесён к ней»), «ИНН/КПП» в одной ячейке (`inn_kpp_cell`); тесты — `test_classify.py` (`test_inn_only_row_above_is_glued_to_next_counterparty`, `test_inn_with_kpp_in_one_column_cell_gives_inn`), синтетика `s18_inn_row_above`, `s19_inn_kpp_cell`; `05-domain-rules.md` §1.3. Правило строки ИНН заменено OQ-163 (13.10.2026). |
| OQ-131 | Скользящий период ОСВ «Октябрь 2024 г. – Сентябрь 2025 г.» (НПЗ 60/62/76, Агрокомплекс 76, `osv-dkb/`): парсер берёт первый месяц → `2024 Q4`, нарастающий. Нужен разбор диапазона дат и признак «не с начала года»; как ядру относиться к такому документу при привязке к версии (не нарастающий и не квартальный) | пакет ДКБ 06.10.2026, прогон парсера | tabular-parser `periods.yaml`/`header.py`; ядро `fits` (D-5 Среза 4) | **реализовано в парсере** (tabular-parser PR #13 + Срез 2b): диапазон месяцев разбирается (`periods.yaml`: `months`, `range`), границы `period_start`/`period_end`/`is_from_year_start` — в контракте v1.2 (docs change 2b), скользящий период → `period_quarter = null`, `is_cumulative = null` и предупреждение «не с начала года» (`05-domain-rules.md` §1.5). **Открыто для ядра:** привязка скользящего документа к версии (допущение — не привязывать, показывать в чек-листе как «период не соответствует») |
| OQ-132 | Тождество сальдо по строкам (§1.4) на счёте 91 (и, вероятно, 90, 99): строки аналитики доходов/расходов несут только обороты, сальдо закрывается на 91.09 → `needs_check` на каждой ОСВ 91 (Цимлянский, Приманыческий). Исключать ли счета без сальдо по аналитике из проверки тождества строк | пакет ДКБ 06.10.2026 | tabular-parser `checks.py`, статус документа | **закрыт решением владельца 07.10.2026 (вариант Б):** счета без сальдо по аналитике исключаются из тождества строк §1.4; список — данные `rules/*.yaml` tabular-parser, начальный состав 91 и 99 (90 не включать: внутри года у субсчетов 90 сальдо есть); «Итого» и сумма детей = родитель сверяются. Реализация — arm-tabular-parser#11 п. 2 |
| OQ-133 | ОСВ варианта 2 без строки корневого счёта: верхний уровень — субсчета (58.01, 58.03), «Итого» = их сумме, парсер сверяет «Итого» с первым корнем → `needs_check` на каждом листе (Новоросметалл, Агрокомплекс; уточняет OQ-106). Сверять «Итого» с суммой строк уровня 0 | пакет ДКБ 06.10.2026 | tabular-parser `checks.py` | **реализовано** (tabular-parser PR #13, arm-tabular-parser#11 п. 3): при нескольких корнях уровня 0 «Итого» сверяется с суммой строк уровня 0 (сальдо по колонкам либо по нетто, как у родителя, R-21); `checks.py`, `05-domain-rules.md` §1.4 |
@ -167,7 +167,8 @@
| OQ-159 | Note DBML `recognition_job.reply_deadline` «таймаут ожидания (OCR — 5 мин)» расходится с решением владельца 09.10.2026: срок ответа OCR — 20 мин (`DEFAULT_REPLY_TIMEOUT` в arm-core, `SCAN_TIMEOUT_SECONDS=1200` в scan-gateway). Поправить note при следующей версии DBML | решение владельца 09.10.2026 (не выпускать версию DBML ради note); arm-core #87, arm-scan-gateway #26, arm-ka-docs #84 | DBML `recognition_job`, `05-domain-rules.md` §9.4 | **закрыт 09.10.2026:** note исправлен в DBML v5.18 (docs #90) |
| OQ-160 | Целевая модель версий консолидации (владелец, 09.10.2026): несколько рабочих версий одного периода с **разным периметром** делят первичку (рабочий стол консоли, `stored_file.consolidation_id`); после `VERIFIED` новая версия (`previous_version_id`) может менять документы при **том же** периметре. Ядро сейчас создаёт только версию 1 (`POST /consolidations`), «новая версия» и параллельные версии не реализованы. Нужны правила: создание второй рабочей версии (обязательно другой периметр?), создание версии после `VERIFIED` (копия периметра, подхват привязок), что видит панель [39] у верифицированной версии до Среза 7 (ЕХ) | brainstorming docs #90, `openspec/changes/console-files/design.md` R-6, R-7 | срез «версии консолидации» (после Среза 4), Срез 7 | открыт: модель файлов совместима; реализация — отдельный срез |
| OQ-161 | Документ, период которого не совпадает с периодом консолидации, молча не привязывается к версии: ручное подтверждение проходит (200), а `fits` (`period_year` = год версии, D-5 Среза 4) его отбрасывает, `period_check` остаётся `UNKNOWN` (OQ-120) — свод пуст без объяснения (владелец 12.10.2026: ОСВ за 1 кв. 2025 в консолидации 2026 Q1). Период ОСВ берётся из шапки и через API не правится. Нужно ли (1) предупреждение «документ не подходит ни одной версии» при загрузке или подтверждении, (2) правка периода документа аналитиком (рядом с первичкой, как `original_*`), (3) постоянный режим «период из консолидации» для файлов, загруженных в неё | разбор владельца 12.10.2026; RSHB/arm-core#103 | `fits`, свод, чек-лист (§2, §9.3), карточка документа | **временное допущение (решение владельца 12.10.2026):** настройка `CONSOLIDATION_PERIOD_FROM_VERSION` (по умолчанию `false`; в контуре — `OPENBAO__APP__CONSOLIDATION_PERIOD_FROM_VERSION`) — ОСВ, загруженная в консолидацию (`stored_file.consolidation_id`) и не подходящая ей по периоду шапки, считается нарастающей за год и квартал консолидации для привязки, свода, чек-листа и колонки «ОСВ»; документ не меняется; ББ/ОФР и файлы с рабочего стола — по шапке; склейки кварталов нет (один слот на счёт ЮЛ). Только для тестов; открыт выбор постоянного решения (1)–(3), после него режим снять |
| OQ-162 | ОСВ 1С, шапка варианта 2 «Детализация по субсчетам, субконто: Контрагенты, Контрагенты.ИНН» (ООО «Зерновая компания „Новопетровская“», ОСВ 60 за 9 мес. 2025, замечание банка 13.10.2026): ИНН выводится **отдельной строкой под** контрагентом (уровень глубже, те же суммы, ИНН числом). Парсер принимал «Контрагенты.ИНН» за второй уровень контрагентов: в «Контрагент / аналитика» — `2370012036.0`, наименования нет, ИНН у строк `null` → нет признака ГК/ГСК/Внешний, суммы контрагента в своде дважды | замечание банка 13.10.2026; RSHB/arm-tabular-parser#32 | tabular-parser §1.1, §1.3; свод и резолв контрагента в ядре | **решено владельцем 13.10.2026:** `<группировка>.ИНН` — реквизит, не уровень; строка ИНН склеивается с родительским контрагентом (ИНН — родителю, строка из `rows` убирается, дети — к контрагенту), только если она единственная у родителя и суммы равны; иначе строка `OTHER` и `needs_check`; пустая ячейка ИНН (физлицо) — склейка без ИНН (рулинг R-1); целое число из ячейки xls — без «.0». `05-domain-rules.md` §1.1, §1.3; **реализовано** RSHB/arm-tabular-parser#32 (713 из 716 контрагентов файла получили ИНН, 3 физлица без ИНН) |
| OQ-162 | ОСВ 1С, шапка варианта 2 «Детализация по субсчетам, субконто: Контрагенты, Контрагенты.ИНН» (ООО «Зерновая компания „Новопетровская“», ОСВ 60 за 9 мес. 2025, замечание банка 13.10.2026): ИНН выводится **отдельной строкой под** контрагентом (уровень глубже, те же суммы, ИНН числом). Парсер принимал «Контрагенты.ИНН» за второй уровень контрагентов: в «Контрагент / аналитика» — `2370012036.0`, наименования нет, ИНН у строк `null` → нет признака ГК/ГСК/Внешний, суммы контрагента в своде дважды | замечание банка 13.10.2026; RSHB/arm-tabular-parser#32 | tabular-parser §1.1, §1.3; свод и резолв контрагента в ядре | **решено владельцем 13.10.2026:** `<группировка>.ИНН` — реквизит, не уровень; строка ИНН склеивается с родительским контрагентом (ИНН — родителю, строка из `rows` убирается, дети — к контрагенту), только если она единственная у родителя и суммы равны; иначе строка `OTHER` и `needs_check`; пустая ячейка ИНН (физлицо) — склейка без ИНН (рулинг R-1); целое число из ячейки xls — без «.0». `05-domain-rules.md` §1.1, §1.3; **реализовано** RSHB/arm-tabular-parser#32 (713 из 716 контрагентов файла получили ИНН, 3 физлица без ИНН). Правило строки ИНН заменено OQ-163 (13.10.2026). |
| OQ-163 | ОСВ, где ИНН контрагента выведен **отдельной строкой** рядом с наименованием, в любой раскладке: над / под × в дереве / не в дереве × с суммами / без (требование владельца 13.10.2026). До решения парсер склеивал только «над, не в дереве, без сумм» (OQ-130) и «под, в дереве» при подсказке «Контрагенты.ИНН» (OQ-162); «под, не в дереве, без сумм» отдавал ИНН следующему контрагенту | владелец, 13.10.2026; RSHB/arm-tabular-parser#35, #34 | tabular-parser §1.1, §1.3 | **решено владельцем 13.10.2026:** без подсказки в шапке строка ИНН — только с верной контрольной суммой ИНН (во всех раскладках, включая OQ-130); с подсказкой — без проверки, порядок элементов задаёт направление; «над, в дереве» — только единственный ребёнок; соседи — по суммам, без сумм — по направлению листа (шапка → пары по суммам → однозначные блоки); неоднозначное — строка `OTHER`, `needs_check`; ведущий ноль числовой строки ИНН (#34). Уточнения владельца (H-1): строка из 10/12 цифр с неверной контрольной суммой не склеивается, остаётся обычной строкой, предупреждение «похожа на ИНН …», `needs_check` (синтетика `s28`); (H-2): если пара найдена, но раскладка противоречит направлению подсказки `<группировка>.ИНН`, строка склеивается, но выдаётся отдельное предупреждение «стоит под/над наименованием … а шапка указывает над/под — проверьте» и `needs_check`. `05-domain-rules.md` §1.3; **реализовано** RSHB/arm-tabular-parser#35 |
Служебные идентификаторы в DBML/аналитике: `ANL-nnn` — замечания аналитика к макету; `ARCH-nnn` — архитектурные решения;
`DECISION-n` — решения по модели; `FIX-nn` — исправления дефектов v4.

View File

@ -463,10 +463,13 @@ ENV_FILE=.env.test .venv/bin/python -m src.parser.cli "/путь/к/файлу.x
только «Сальдо на начало».
- Заголовок с незнакомым синонимом («Нач. остаток») даёт `needs_check`, суммы уходят в `extra`. Это не ошибка кода, а
словарь: присылайте файл, синоним добавляется строкой в `rules/osv_headers.yaml`.
- ИНН внутри текста контрагента («Полиграф, 3528315710»), в ячейке над названием и склеенный «ИНН/КПП» парсер извлекает
(OQ-100, OQ-130, arm-tabular-parser#9): `counterparty_inn` заполнен, `counterparty_name` без ИНН, исходный текст — в `row_value`.
- ИНН внутри текста контрагента («Полиграф, 3528315710»), склеенный «ИНН/КПП» и отдельная строка ИНН рядом с названием (любая из
восьми раскладок: над / под, в дереве / соседом, с суммами / без; `s20`–`s28`, OQ-163, arm-tabular-parser#35) парсер
извлекает: `counterparty_inn` заполнен, `counterparty_name` без ИНН, исходный текст — в `row_value`. Без подсказки в шапке
строка считается строкой ИНН только при верной контрольной сумме; неверная — остаётся строкой, `needs_check`.
- ИНН в колонке и ИНН в хвосте названия различаются — берётся колонка, предупреждения нет; строка-ИНН над счётом или договором
(не контрагентом) склеивается, но её ИНН теряется молча (arm-tabular-parser#18, A9-2/A9-3).
(не контрагентом) склеивается, но с предупреждением «ИНН … отнесён к строке M, но она не контрагент — проверьте» и `needs_check`
(arm-tabular-parser#18, A9-2/A9-3; #35).
- Счёт 91 (и 99): строки аналитики несут только обороты, сальдо закрыто на 91.09 → `needs_check` на каждой ОСВ 91. Решение
владельца по OQ-132 — исключить 91 и 99 из тождества строк (список в `rules/*.yaml`); до реализации (arm-tabular-parser#11
п. 2) `needs_check` — ожидаемое поведение, не баг. Аналитика без сальдо на конец (магазины Агрокомплекса в 62/76) — тоже `needs_check` по данным файла.