Andrey Epifancev a1c6c10869 Спека: строки ИНН рядом с контрагентом — 8 раскладок (#35)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 16:27:02 +04:00

17 KiB
Raw Blame History

Context

Issue RSHB/arm-tabular-parser#35 (входит #34). Правило — ../docs/context/05-domain-rules.md §1.1 (реквизит <группировка>.ИНН, OQ-162) и §1.3 (ИНН контрагента: колонка → строка ИНН → хвост названия; OQ-100, OQ-130); новое правило заносится в §1.3 и новым OQ в 07-open-questions.md.

Восемь раскладок (ИНН над / под наименованием) × (в дереве / не в дереве) × (с суммами / без сумм). Поведение main на синтетике без подсказки в шапке:

Раскладка main
над, не в дереве, без сумм склеивается (OQ-130)
над, не в дереве, с суммами ИНН — отдельный контрагент, needs_check
над, в дереве, с суммами ИНН вместо наименования, done
над, в дереве, без сумм needs_check
под, в дереве, с суммами ИНН теряется, done (с подсказкой в шапке — склеивается, #32)
под, в дереве, без сумм needs_check
под, не в дереве, с суммами ИНН — отдельный контрагент, needs_check
под, не в дереве, без сумм ИНН первого контрагента у второго, done

Проверено при проектировании: стратегии иерархии outline, indent, account_pattern ставят строку ИНН (с суммами и без) на тот же уровень, что и наименования (OQ-130 отключался), то есть дерево до склейки корректно; стратегия sums не проверялась. Контрольная сумма ИНН на golden: реальные ИНН (≈ 5 852) верны, кроме трёх (физлица: Цимлянский, ОСВ 76, 611801003692; Новопетровская 233370449085; НПЗ 614002279560), вся синтетика (5001234567, 7701234567, 7812345678 и др.) неверна.

Decisions

Решения владельца в brainstorming 13.10.2026:

  1. Подход A — один проход по дереву. Новый модуль api/src/parser/inn_rows.py работает между resolve_levels и classify_rows. Он заменяет _glue_inn_rows (OQ-130, до иерархии) и glue_inn_attributes (#32, после классификации). Классификатор получает дерево без строк ИНН и ИНН для строк-наименований.
  2. Контрольная сумма. Без подсказки в шапке строка — строка ИНН только при верной контрольной сумме ИНН (алгоритм ФНС: 10 цифр — весовые коэффициенты 2, 4, 10, 3, 5, 9, 4, 6, 8; 12 цифр — два контрольных разряда, 7, 2, 4, 10, 3, 5, 9, 4, 6, 8 и 3, 7, 2, 4, 10, 3, 5, 9, 4, 6, 8), во всех восьми раскладках, включая прежнюю OQ-130. С подсказкой <группировка>.ИНН контрольная сумма не проверяется (файл банка #32 не меняется).
  3. Неверная контрольная сумма. Строка из 10/12 цифр (по inn_only_row) рядом с наименованием, не прошедшая контрольную сумму без подсказки, — не строка ИНН: классифицируется как обычно, предупреждение «строка N похожа на ИНН …, контрольная сумма не сходится», документ needs_check.
  4. Ведущий ноль (#34). Числовая ячейка строки ИНН из 9/11 цифр дополняется нулём слева (как R-24 для колонки), контрольная сумма проверяется после дополнения.
  5. «Над, в дереве» — склейка только с единственным ребёнком; несколько детей — не склеивать, needs_check.
  6. Направление для соседей (предложено в дизайне, не оспорено): пары с суммами — по совпадению сумм; без сумм — по направлению документа: порядок в шапке → пары по суммам → однозначные блоки. Противоречие или нет однозначных блоков — не склеивать, needs_check.
  7. «Под, в дереве» — несколько строк ИНН под одним наименованием — needs_check (как #32).
  8. Строка ИНН — только ИНН: 10/12 цифр числом или текстом, «ИНН …», «ИНН/КПП» (inn_only_row); счёт строкой ИНН не бывает.

Алгоритм inn_rows.py

Вход: строки BodyRow, Levels (уровень и родитель), подсказка шапки (InnHint: есть ли <группировка>.ИНН и направление по порядку элементов), позиции схемы группировок (для защиты, п. «Защита» ниже), словари. Выход: строки и Levels без склеенных строк ИНН, ИНН для строк-наименований (row → inn), множество строк ИНН без пары (классификатор ставит им OTHER), предупреждения, признак «всё склеено».

  1. Кандидаты. Строка ИНН — не счёт, не «Итого», не строка Вал./Кол.; подпись совпадает с inn_only_row (числовая ячейка из 9/11 цифр — с ведущим нулём); без подсказки ИНН проходит контрольную сумму (иначе п. 3 решений). С подсказкой пустая подпись строки-ребёнка наименования (только положение «под, в дереве») — тоже кандидат, «ИНН не указан» (R-1 #32).
  2. Наименование — строка не счёт, не «Итого», не Вал./Кол., не кандидат.
  3. Положение кандидата (по Levels):
    • есть дети → над, в дереве: пара — единственный ребёнок-наименование; он и его поддерево поднимаются на уровень вверх, родителем становится родитель кандидата;
    • родитель — наименование → под, в дереве: пара — родитель; дети кандидата переходят к родителю;
    • иначе (родитель — счёт или корень, детей нет) → не в дереве: пара — сосед того же родителя, непосредственно выше или ниже. Связь в дереве важнее соседства.
  4. Суммы. Если у кандидата есть суммы (_has_sums), шесть значений MUST совпасть с суммами пары (None = 0); иначе пары нет.
  5. Соседи с суммами: пара — сосед-наименование с равными суммами; совпали оба соседа — по направлению документа.
  6. Направление документа (лист ОСВ), первое определённое:
    1. подсказка шапки: <группировка>.ИНН после группировки — под; перед — над;
    2. пары соседей, найденные по суммам (п. 5, один совпавший сосед);
    3. блоки: последовательность детей одного родителя из кандидатов «не в дереве» (I) и наименований (N), ещё не сопоставленных; начинается с I и не кончается I — над; кончается I и не начинается I — под; начинается и кончается I — блок сломан (его кандидаты без пары); ни то ни другое — нет сведений. Сведения пп. 2–3 противоречат друг другу или их нет — направление неизвестно.
  7. Соседи без сумм: пара — соседнее наименование по направлению (над → следующий сосед, под → предыдущий). Направление неизвестно — пары нет.
  8. Конфликты: два кандидата претендуют на одно наименование — оба без пары. ИНН в колонке ИНН у наименования отличается от ИНН строки — колонка побеждает, предупреждение, needs_check. ИНН в хвосте названия — строка ИНН побеждает (прежний порядок §1.3).
  9. Склейка: строка ИНН уходит из строк и Levels, ИНН — наименованию, предупреждение «строка N: ИНН … отнесён к контрагенту в строке M» (пустой при подсказке — «ИНН пуст … без ИНН»).
  10. Без пары: строка остаётся в rows, group_type = OTHER (в свод не попадает, суммы не задваиваются, как #32), предупреждение «строка N: ИНН … без пары — <причина>», признак «всё склеено» = False → документ needs_check.

Защита «над, в дереве». Контрагент, названный ИНН, с одним договором неотличим по форме от строки ИНН над контрагентом. Если шапка задаёт группировки (позиции схемы) и глубина поддерева кандидата не превышает схему (лишнего уровня нет), кандидат не склеивается и классифицируется по схеме; без схемы — по пп. 1–10.

Изменения в коде

  • inn_rows.py — новый модуль (пп. 1–10); контрольная сумма ИНН — numbers.py (inn_checksum_ok).
  • header.py: InnAttribute и позиции-реквизиты удаляются; GroupSchema получает подсказку (inn_hint: владелец и направление); <группировка>.ИНН распознаётся и перед группировкой-владельцем (сейчас только после).
  • classify.py: удаляются _glue_inn_rows, glue_inn_attributes, _not_glued_reason, поля is_inn_attribute, attribute_inn; _classify_row берёт ИНН строки из результата inn_rows вместо inn_above и ставит OTHER строкам без пары.
  • hierarchy.py: поле BodyRow.inn_above удаляется.
  • osv.py: вызов inn_rows между resolve_levels и classify_rows; признак «всё склеено» и предупреждения — в статус и warnings.

Тесты

  • Юнит-тесты inn_rows.py на деревьях вручную: распознавание (10/12, число/текст, «ИНН …», «ИНН/КПП», ведущий ноль), контрольная сумма с подсказкой и без, все отказы пп. 4–8 и 10, защита «над, в дереве», направление (шапка, суммы, блоки, противоречие, нет сведений, сломанный блок), расхождение с колонкой, R-1.
  • Синтетика s20…s27 (по одной на раскладку, ИНН с верной контрольной суммой, шапка без подсказки), golden — после ручной проверки: ИНН у всех контрагентов, итоги сходятся, done.
  • s18: ИНН с верной контрольной суммой (только в s18, общие узлы генератора не меняются).
  • Сравнение результатов main и ветки на всех файлах fixtures и синтетике; ожидаемые отличия — s18, новые s20…s27, файлы со строкой ИНН с неверной контрольной суммой; Новопетровская без изменений. Каждое отличие согласуется с владельцем до make golden-update.

Decisions made without discussion

# Дата Решение Обоснование Цена ошибки
1 13.10.2026 Защита «над, в дереве»: при известной схеме группировок склейка только при лишнем уровне в дереве Контрагент, названный ИНН, с одним договором иначе потерял бы договор (договор стал бы контрагентом) Файл с подсказкой-схемой, где 1С не добавила уровень, — строка ИНН без пары, needs_check; правка условия
2 13.10.2026 Строка ИНН без пары — group_type = OTHER (в rows, не в своде) Как #32: суммы строки ИНН с суммами иначе задвоились бы в своде ядра; документ всё равно на проверке Аналитик не видит такую строку контрагентом и сопоставляет вручную; поменять тип — одна строка
3 13.10.2026 ИНН колонки расходится с ИНН строки — побеждает колонка, needs_check Колонка — явный реквизит отчёта, строка — вывод по соседству Неверный ИНН у контрагента до проверки аналитиком; документ на проверке
4 13.10.2026 Направление определяется на лист ОСВ (документ), не на файл и не на счёт Один лист — один отчёт 1С с одной настройкой вывода Смешанные раскладки в одном листе — needs_check вместо склейки
5 13.10.2026 Подсказка шапки задаёт направление сильнее пар по суммам; пары по суммам склеиваются независимо от направления Суммы однозначно указывают пару; шапка — явная настройка отчёта Противоречие шапки и сумм не поднимается как ошибка
6 13.10.2026 Суммы сравниваются с None = 0 (в #32 — точное равенство кортежей) Пустая ячейка и 0 в ОСВ 1С значат одно и то же Пара по суммам у строк, различающихся только пустотой ячейки; на Новопетровской не влияет (проверяется golden)
7 13.10.2026 Соседом считается только непосредственный сосед в списке детей того же родителя Иначе сопоставление через чужие строки неоднозначно ИНН через строку от наименования не склеивается, needs_check
8 13.10.2026 Пустая подпись — кандидат только с подсказкой и только в положении «под, в дереве» (R-1 #32) Без подсказки пустая строка ничего не значит; другое положение у пустого ИНН в 1С не встречалось Пустая строка ИНН «над» при подсказке — остаётся наименованием без текста