rules/ — словари синонимов парсера

Словари rules/*.yaml, по которым tabular-parser ищет структуру файла по содержимому: заголовки колонок, группировки, периоды, типы данных. Словари — данные, а не код: новый синоним добавляется строкой в YAML без правки парсера. Парсер не привязан к координатам (буквы колонок, номера строк, имена листов); это проверяет AST-линтер tests/unit/parser/test_no_coordinates.py.

Путь указан относительно api/ (рабочего каталога команд сервиса): api/rules/. Загрузчик — src/parser/rules.py (load_rules()), источник содержимого — docs/context/05-domain-rules.md §1.0–§1.5.

Файл Ключи Что описывает
osv_headers.yaml sheet_markers, groups (opening/turnover/closing), debit, credit, service_columns (только inn), total_row, total_row_secondary («Итого развернутое»), column_captions («Код»/«Наименование» под названием группировки), counterparty_inn_suffix / inn_only_row / inn_kpp_cell (ИНН в тексте названия, строкой выше, «ИНН/КПП» в ячейке; обязательные именованные группы tail, inn), header_markers (account — «по счету», «Счет Поставщика/Клиента», period, data_type, currency, org_params, subkonto) признак листа ОСВ; группы сумм (начало / обороты / конец); «Дебет»/«Кредит»; колонка ИНН (прочие колонки таблицы попадают в rows[].extra под своим заголовком и в словаре не нужны); строка «Итого»; метки шапки
osv_groups.yaml mapping, default название группировки из шапки -> ACCOUNT / DEPARTMENT / COUNTERPARTY / CONTRACT / OTHER (default — для всего прочего)
osv_checks.yaml accounts_without_analytic_balance коды счетов верхнего уровня без сальдо по аналитике (состав 90, 91, 99; OQ-132, 90 добавлен 19.10.2026): тождество строки «начало + обороты = конец» на них не проверяется (субсчета включены, 910 — нет); сверки «сумма детей = родитель» по оборотам и «Итого» сохраняются. Не Pattern: записи — коды счетов, а не регулярные выражения
periods.yaml rules (pattern, quarter, is_cumulative), year, end, months (названия месяцев, группы m01…m12), not_single_month (тексты, где один месяц не задаёт границы периода: дата на число, квартал, «N месяцев»), range (диапазон месяцев с годами, группы m1, y1, m2, y2), standalone (период отдельной ячейкой без метки) текст периода -> period_quarter и is_cumulative (§1.5, ASSUMPTION(OQ-02)); year — паттерн года, end — конец текста периода в строке шапки («г.», «год»)
data_types.yaml header, standalone_header (тип данных отдельной ячейкой без метки), row, currency_codes, currency_aliases «Выводимые данные» -> BU / NU / SUM; строки «Вал.» / «Кол.», «Кол-во», «Кол-во, <единица>» -> CURRENCY / QUANTITY; метки колонки «Показатели» (indicator_column: «Кол.», «Кол. доп.» -> QUANTITY); коды валют ISO 4217 (строка, равная коду, — валютная); написания валюты в шапке (руб. -> RUB; ключ алиаса обязан быть в currency_codes)

Словари форм ББ/ОФР (statement_*.yaml в прежней версии) удалены из сервиса: их держит пакет arm_statements (дистрибутив pylib_ai_arm_statements), сервис их напрямую не читает.

Как писать паттерны

  • Паттерн — регулярное выражение Python. Он ищется (search) в нормализованном тексте ячейки и применяется без учёта регистра.
  • Нормализация текста (normalize_text): нижний регистр, «ё» -> «е», дефисы, тире, неразрывные пробелы и переносы строк -> один пробел, пробелы схлопываются. Пример: Оборотно-сальдовая\nведомость -> оборотно сальдовая ведомость.
  • Поэтому в паттернах нет «ё», дефисов, тире и диапазонов [а-я]: пишите оборотно\s*сальдовая, д т (для «Д-т»), \w вместо диапазона. Загрузчик отвергает такой паттерн с указанием файла и ключа.
  • Порядок записей в списках mapping, rules (периоды), header, row — приоритет: побеждает первое совпадение, от специфичных правил к общим.
  • В osv_groups.yaml известные группировки OTHER («Основные средства», «Номенклатура», «Статьи затрат», …) перечислены в mapping явно, хотя default и так даёт OTHER: детектор заголовка таблицы (src/parser/detect.py) считает строку под «Дебет/Кредит» частью блока заголовка, только если её текст совпал с записью mapping, с ИНН или с «субконто». Новая группировка в отдельной строке заголовка = новая строка в mapping.
  • Ошибка в словаре (нет обязательного ключа, неизвестный ключ, неверный регэксп или тип) — RuntimeError при загрузке с именем файла и ключом.

Покрытие словарей

Каждая запись распознавания (один Pattern в любом из словарей: sheet_markers, groups, debit, credit, service_columns, total_row, header_markers, mapping, правила и year/end периодов, header/row/currency_aliases типов данных) должна сработать хотя бы раз на реальных ОСВ или синтетике: это проверяет tests/fixtures/test_rules_coverage.py (счётчик совпадений Pattern, сообщение — файл, ключ и паттерн несработавших записей). Перечень currency_codes — список значений ISO 4217, а не синонимы, в покрытие не входит (решение R-22). Несработавшая запись — либо мёртвый синоним (удалить из YAML вместе с тестом словаря), либо пробел в фикстурах (добавить файл синтетики s1x_… с описанием в README fixtures). Так удалены service_columns.kpp|code|quantity|currency: код читает только inn, остальные колонки идут в extra. Синтетика s14–s16 закрывает остальные записи, которых нет в реальных ОСВ (формы ББ/ОФР по одному признаку, названия групп сумм «Входящее/Начальное/Конечное/Исходящее сальдо», шапка без организации, период «2024», «Детализация: …», группировки OTHER).

Как добавить синоним

  1. Найдите словарь и ключ по таблице выше; добавьте строку-паттерн в нужный список (первое совпадение побеждает — ставьте специфичное выше общего). Пример: заголовок «Нач. остаток» вместо «Сальдо на начало» — groups.opening в osv_headers.yaml: - 'нач\.\s*остаток'.
  2. Положите файл с этим форматом в ../fixtures/osv/ (реальный) или добавьте синтетику в tests/synthetic/generate.py (с описанием в README fixtures); иначе тест покрытия сочтёт запись мёртвой.
  3. make golden-update, просмотрите diff, закоммитьте снимки в репозитории fixtures. Код парсера не меняется.

Что намеренно не в штатном словаре

Синоним «нач. остаток» / «начальный остаток» для группы opening не включён: он нужен, чтобы показать, что новый формат подключается строкой в YAML (тест сценария «Новый синоним»).