19 KiB
Raw Blame History

MODIFIED Requirements

Requirement: Шапка документа и схема группировок

Парсер SHALL извлечь из области выше заголовка таблицы: organization_name (первая непустая текстовая строка, trim), account_code_raw (после «по счету»/«по счёту», как в файле: «60», «58.03»), period_text (после «за» до «г.»/конца), data_type («Выводимые данные: БУ/НУ/сумма»), currency («Валюта …: RUB/руб.» → RUB), header_variant (1 — группировки строками первой колонки блока заголовка; 2 — перечисление «субконто: …»). group_schema SHALL начинаться с ACCOUNT и далее перечислять группировки в порядке вложенности по словарю rules/osv_groups.yaml; ячейка ИНН в строке группировки → has_inn = true. Элемент вида <группировка>.ИНН (суффикс после последней точки — по словарю service_columns.inn, префикс — имя группировки COUNTERPARTY этой же шапки; вариант 2 — элемент списка «субконто: …», вариант 1 — строка блока заголовка) MUST NOT попадать в group_schema: группировка-владелец получает has_inn = true, а элемент служит подсказкой для строк ИНН (требование «Строки ИНН рядом с контрагентом»): стоит после владельца — ИНН под наименованием, перед владельцем — над. period_quarter/is_cumulative SHALL вычисляться только по тексту по rules/periods.yaml (§1.5, ASSUMPTION(OQ-02)), period_year — четыре цифры года. Отсутствие любого поля шапки, кроме обязательных по контракту, — null и предупреждение; отсутствие organization_name/period_text — пустая строка и предупреждение (контракт требует строку).

Scenario: Вариант 1 с тремя строками группировок

  • WHEN разбирается schastl/2024-Q3/…62 за 3 квартал 2024 г..xls
  • THEN group_schema = [ACCOUNT, DEPARTMENT, COUNTERPARTY(has_inn=true)], header_variant = 1, account_code_raw = "62", period_text = "3 квартал 2024 г.", period_quarter = "Q3", is_cumulative = false, data_type = "BU", currency = "RUB"

Scenario: ОСВ по субсчёту и годовой период

  • WHEN разбираются rb/2024-Q3/сч 583_3 кв 2024.xlsx и rb/2024-FY/ОСВ_66 за 2024.xls
  • THEN у первого account_code_raw = "58.03"; у второго period_quarter = "Q4", is_cumulative = true

Scenario: Вариант 2

  • WHEN разбирается синтетическая ОСВ с шапкой «Период: 1 квартал 2026 г.», «Детализация по субсчетам, субконто: Контрагенты, Договоры», заголовком «Субконто»
  • THEN header_variant = 2, group_schema = [ACCOUNT, COUNTERPARTY, CONTRACT], period_quarter = "Q1"

Scenario: Реквизит ИНН после группировки

  • WHEN разбирается osv-dkb/novopetrovskaya/2025-Q3/60_ 9мес.2025г.xls (шапка «субконто: Контрагенты, Контрагенты.ИНН»)
  • THEN group_schema = [ACCOUNT, COUNTERPARTY(has_inn=true)], подсказка «ИНН под наименованием»

Scenario: Реквизит ИНН перед группировкой

  • WHEN в синтетической ОСВ шапка «субконто: Контрагенты.ИНН, Контрагенты»
  • THEN group_schema = [ACCOUNT, COUNTERPARTY(has_inn=true)], подсказка «ИНН над наименованием»

Requirement: Классификация строк

Классификация SHALL выполняться над деревом, из которого уже убраны склеенные строки ИНН (требование «Строки ИНН рядом с контрагентом»). Каждая строка данных SHALL получить group_type: ACCOUNT — текст матчит ACCOUNT_RE (регистр учитывается, IGNORECASE нет): две-три цифры и до двух сегментов субсчёта через точку, где сегмент — \d{1,2} или 1–3 заглавные буквы кириллицы/латиницы («76.АВ», «76.ВА», «76.ОТ», R-16), далее опционально запятая и название: ^\d{2,3}(\.(\d{1,2}|[А-ЯЁA-Z]{1,3})){0,2}(,\s*.*)?$ с флагами re.ASCII | re.DOTALL (после запятой допустим любой пробел или его отсутствие, R-18; цифры — только 0–9; название после запятой может занимать несколько строк ячейки — M-1); account_code_raw — код как в файле, без названия после запятой. Не-счёт — группировка из group_schema по позиции (число не-счётных предков после последнего ACCOUNT + 1), при выходе за схему — OTHER; строка ИНН без пары — OTHER; последняя строка уровня 0 с текстом из словаря «Итого» — TOTAL.

Для COUNTERPARTY counterparty_name SHALL быть текстом строки без ИНН в конце и разделителя перед ним (row_value — исходный текст; OQ-100), а counterparty_inn — первым найденным из:

  1. колонки ИНН: цифры текстовой ячейки («ИНН 352819549855» → «352819549855», R-15), склеенная «ИНН/КПП» (inn_kpp_cell) → ИНН; результат только при 10 или 12 цифрах, иначе null; число из 9/11 цифр (ИНН регионов 01–09 с потерянным ведущим нулём) дополняется нулём слева (R-24);
  2. строки ИНН, склеенной с этой строкой (требование «Строки ИНН рядом с контрагентом»);
  3. конца текста названия: 10 или 12 цифр после запятой, скобки, «/», пробела или метки «ИНН» (counterparty_inn_suffix; «Полиграф, 3528315710», «ООО Альфа (ИНН 7701234567)»). Контрольная сумма ИНН из колонки и из названия MUST NOT проверяться. Строки «Вал.», «Кол.», трёхбуквенного кода валюты SHALL получать group_type = OTHER, data_type CURRENCY/QUANTITY, MUST NOT занимать позицию в group_schema и MUST NOT наследовать counterparty_name/counterparty_inn (account_code_raw наследуется от ветки, R-19); их суммы остаются в шести полях. Остальные строки получают data_type шапки. Все строки, включая TOTAL, SHALL присутствовать в rows (ASSUMPTION(OQ-01): хранить ли «Итого», решает ядро); склеенные строки ИНН в rows MUST NOT попадать.

Scenario: Подразделение, контрагент с ИНН, физлицо

  • WHEN разбирается schastl/2024-Q3/…62 за 3 квартал 2024 г..xls
  • THEN «БП Аренда» → DEPARTMENT; «МАЛЕЧКИНСКАЯ ПТИЦЕФАБРИКА ООО» → COUNTERPARTY с counterparty_inn из 10 цифр; «Ведомость в счет з/платы» → COUNTERPARTY с counterparty_inn = null

Scenario: ИНН в конце названия

  • WHEN разбирается синтетическая ОСВ s17 (ИНН в тексте названия с разными разделителями)
  • THEN у контрагентов counterparty_name без ИНН, counterparty_inn — ИНН из текста, row_value — исходный текст

Scenario: «ИНН/КПП» в одной ячейке колонки ИНН

  • WHEN разбирается синтетическая ОСВ s19
  • THEN counterparty_inn — 10 цифр до «/»

Scenario: Название субсчёта в строке счёта

  • WHEN в синтетической ОСВ строка «60.01, Расчеты с поставщиками»
  • THEN group_type = ACCOUNT, account_code_raw = "60.01", row_value — исходный текст

Scenario: Буквенный субсчёт

  • WHEN в ОСВ 76 строка «76.АВ» (реальные файлы РБ и Счастливочной) либо «60.АВ, …» (синтетика)
  • THEN group_type = ACCOUNT, строка наследуется детьми как счёт ветки; «76.ав» (строчные) счётом не считается

Scenario: Строки Вал./Кол.

  • WHEN в синтетической ОСВ под контрагентом строки «USD» и «Кол.»
  • THEN group_type = OTHER, data_type = CURRENCY и QUANTITY, counterparty_name/counterparty_inn равны null, account_code_raw совпадает с родителем, строки исключены из проверки «сумма детей = родитель», статус done

ADDED Requirements

Requirement: Строки ИНН рядом с контрагентом

Между построением иерархии и классификацией парсер SHALL найти строки ИНН и сопоставить каждую с её строкой-наименованием в любой из восьми раскладок: ИНН над / под наименованием × в дереве (родитель или ребёнок наименования) / не в дереве (сосед того же родителя) × с суммами / без сумм.

Строка ИНН — не счёт, не «Итого», не строка Вал./Кол., подпись которой целиком — ИНН по словарю inn_only_row (10 или 12 цифр числом или текстом, «ИНН 7701234567», «ИНН: 7701234567 / 770101001»); числовая ячейка из 9/11 цифр дополняется нулём слева (R-24, #34). Без подсказки <группировка>.ИНН в шапке ИНН MUST пройти контрольную сумму ИНН (алгоритм ФНС); строка из 10/12 цифр с неверной контрольной суммой рядом с наименованием MUST NOT считаться строкой ИНН: она классифицируется как обычно, в warnings — «строка N похожа на ИНН …, контрольная сумма не сходится», документ — needs_check. С подсказкой контрольная сумма не проверяется, а пустая подпись строки-ребёнка наименования — строка ИНН «ИНН не указан» (склейка без ИНН, без needs_check, R-1 #32). Наименование — строка не счёт, не «Итого», не Вал./Кол., не строка ИНН. С подсказкой «под» единственный ребёнок наименования с теми же суммами и непустой подписью, которая не ИНН, — строка ИНН с нераспознанным ИНН (без пары, «ИНН не распознан»).

Пара строки ИНН (связь в дереве важнее соседства):

  • у строки ИНН есть дети → пара — её единственный ребёнок-наименование; несколько детей — пары нет. Если в group_schema больше одной группировки и ни одна строка листа (кроме Вал./Кол.) не стоит глубже схемы, строка ИНН не склеивается и без предупреждения классифицируется по схеме;
  • родитель строки ИНН — наименование → пара — родитель; несколько строк ИНН у одного наименования — пары нет. Пара — родитель только при подсказке «под» или если родитель по схеме — COUNTERPARTY (или позиция вне схемы); иначе у строки ИНН с детьми действует правило «есть дети», а без детей она — сосед (следующий пункт);
  • иначе — непосредственный сосед того же родителя: при суммах — сосед с теми же суммами (совпали оба — по направлению); без сумм — сосед по направлению листа (над → следующий, под → предыдущий).

Если у строки ИНН есть суммы, все шесть MUST совпасть с суммами пары (пустая ячейка = 0), иначе пары нет. Направление листа — первое определённое из: порядок <группировка>.ИНН в шапке; пары соседей, найденные по суммам; однозначные блоки детей одного родителя из несопоставленных строк ИНН и наименований (начинается строкой ИНН — над; кончается строкой ИНН — под; начинается и кончается — блок сломан, его строки ИНН без пары). Противоречие сведений или их отсутствие — направление неизвестно, строки ИНН без сумм, которым оно нужно, остаются без пары. Две строки ИНН с одной парой — обе без пары.

Склейка: строка ИНН MUST NOT попадать в rows, её ИНН — наименованию (источник 2 требования «Классификация строк»), её дети переходят к наименованию, при «над, в дереве» наименование с поддеревом поднимается на её место; в warnings — «строка N: ИНН … отнесён к контрагенту в строке M». ИНН колонки ИНН у наименования, отличный от ИНН строки, остаётся, в warnings — расхождение, документ — needs_check.

Без пары: строка ИНН остаётся в rows с group_type = OTHER (в свод ядра не попадает), в warnings — «строка N: ИНН … без пары — <причина>», документ — needs_check. Структура под склейку MUST NOT подгоняться.

Scenario: Восемь раскладок без подсказки

  • WHEN разбираются синтетические ОСВ s20…s27 (по одной на раскладку; ИНН с верной контрольной суммой; шапка без Контрагенты.ИНН)
  • THEN у каждого контрагента counterparty_inn — ИНН его строки ИНН, строк ИНН в rows нет, контрагентов столько же, сколько наименований, контрольные суммы сходятся, статус done

Scenario: ИНН под наименованием не уходит следующему контрагенту

  • WHEN в синтетической ОСВ s27 (ИНН под наименованием, не в дереве, без сумм) идут «ООО Альфа», ИНН Альфы, «ООО Бета», ИНН Беты
  • THEN ИНН Альфы — у «ООО Альфа», ИНН Беты — у «ООО Бета»

Scenario: ИНН над наименованием (OQ-130)

  • WHEN разбирается синтетическая ОСВ s18 (строка ИНН без сумм над наименованием, ИНН с верной контрольной суммой)
  • THEN ИНН — у контрагента под строкой, строк ИНН в rows нет, статус done

Scenario: Реквизит «Контрагенты.ИНН» (OQ-162)

  • WHEN разбирается osv-dkb/novopetrovskaya/2025-Q3/60_ 9мес.2025г.xls
  • THEN результат совпадает с golden до изменения: 713 из 716 контрагентов с ИНН (включая ИНН с неверной контрольной суммой), 3 физлица без ИНН, статус done

Scenario: Неверная контрольная сумма без подсказки

  • WHEN в синтетической ОСВ без подсказки над наименованием строка «7701234567» (контрольная сумма не сходится)
  • THEN строка не склеивается, в warnings — «похожа на ИНН, контрольная сумма не сходится», статус needs_check

Scenario: Ведущий ноль у числовой строки ИНН

  • WHEN строка ИНН без подсказки — числовая ячейка 123456788 (9 цифр; 0123456788 проходит контрольную сумму)
  • THEN counterparty_inn = "0123456788"

Scenario: Неоднозначное направление

  • WHEN под счётом блок «наименование, ИНН без сумм, наименование» и других сведений о направлении нет
  • THEN строка ИНН без пары, group_type = OTHER, предупреждение «направление неизвестно», статус needs_check

Scenario: Несколько детей у строки ИНН

  • WHEN у строки ИНН в дереве два ребёнка-наименования
  • THEN строка ИНН без пары, group_type = OTHER, статус needs_check