- header: период отдельной ячейкой (periods.yaml: standalone), тип данных отдельной ячейкой («Бухгалтерский», data_types.yaml: standalone_header), организация — хвост названия ведомости; код счёта — из первой строки-счёта таблицы (с предупреждением), если «по счету» в шапке нет. - classify/detect: текст строки может стоять правее колонки текста (колонка задаёт уровень), подписи «Код»/ «Наименование» (osv_headers.yaml: column_captions) — не группировки, строка сумм без подписи в конце таблицы — «Итого» (с предупреждением). - checks: сумма уровня 0 сверяется с «Итого» по правилу R-21 (свёрнутое сальдо «Итого» — по нетто), как у родителя. - tests: osv_dkb_files, покрытие словарей включает osv-dkb/**; README. Refs #11 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> Claude-Session: cedd7915-2efe-41e5-988c-a9117033598b
61 lines
8.4 KiB
Markdown
61 lines
8.4 KiB
Markdown
# 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`, `statement_markers`, `groups` (`opening`/`turnover`/`closing`), `debit`, `credit`, `service_columns` (только `inn`), `total_row`, `total_row_secondary` («Итого развернутое»), `column_captions` («Код»/«Наименование» под названием группировки), `header_markers` (`account`, `period`, `data_type`, `currency`, `org_params`, `subkonto`) | признак листа ОСВ и листов форм ББ/ОФР; группы сумм (начало / обороты / конец); «Дебет»/«Кредит»; колонка ИНН (прочие колонки таблицы попадают в `rows[].extra` под своим заголовком и в словаре не нужны); строка «Итого»; метки шапки |
|
||
| `osv_groups.yaml` | `mapping`, `default` | название группировки из шапки -> `ACCOUNT` / `DEPARTMENT` / `COUNTERPARTY` / `CONTRACT` / `OTHER` (`default` — для всего прочего) |
|
||
| `periods.yaml` | `rules` (`pattern`, `quarter`, `is_cumulative`), `year`, `end`, `months` (названия месяцев, группы `m01…m12`), `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`; коды валют ISO 4217 (строка, равная коду, — валютная); написания валюты в шапке (`руб.` -> `RUB`; ключ алиаса обязан быть в `currency_codes`) |
|
||
|
||
## Как писать паттерны
|
||
|
||
- Паттерн — регулярное выражение 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`, `statement_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 (тест сценария «Новый синоним»).
|