spec: ОСВ других учётных систем — признаки листа, группы, «ВСЕГО» (#81)

AURORA-1717, AURORA-1719, AURORA-1732.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Andrey Epifancev 2026-10-28 19:52:43 +04:00
parent a8c9605499
commit f5b73d2146
4 changed files with 252 additions and 0 deletions

View File

@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-10-28

View File

@ -0,0 +1,129 @@
## Context
Классификация листа (`detect.classify_sheet`): ОСВ, если в первых `SHEET_MARKER_ROWS` (30) строках есть ячейка,
совпадающая с `sheet_markers`; иначе вид листа определяет пакет `arm_statements` (форма / продолжение / прочее).
`sheet_markers` сейчас один: `оборотно\s*сальдовая\s+ведомость`. Заголовок таблицы (`_find_group_row`) — первая строка,
где есть группы «обороты» и «сальдо на конец». Сторона ищется в начале текста ячейки (`^(дебет|дт|д т)\b`) — в
отдельной ячейке под группой либо в остатке ячейки группы после совпадения (`_group_cells`: «Сальдо на начало Дт»).
Файлы — банк первичных документов `awp-ca/doc-bank/` (индекс `index/index.csv`, колонки `parse_status`, `parse_error`,
`parse_bug`, `sha256`; в git не кладутся). Списки затронутых файлов с ГК, ЮЛ и sha256 — вложения `список_файлов.csv`
к AURORA-1717 и AURORA-1719. Образцы во вложениях задач:
| Задача | Файл | Что показывает |
|---|---|---|
| 1717, 1719 | «ОСВ по внб счету 009 за 9 мес 2025.xlsx» (Промлит) | «Оборотная ведомость», «Остаток на начало/конец» |
| 1717, 1719 | «ОСВ 66 67 01-06 2025.xlsx» (Виола) | «Отчет по Главной книге», SAP «Сальдо вх Дт / Сальдо исх Кт» |
| 1717 | «67 9 мес. 25.xlsx» (Айсберри) | Navision «Фин. Обороты Листок», «НачалоДт / КонецКт» |
| 1719 | «ОСВсч62_3кв2025.xls» (НМЖК) | SAP «Сальдо НачальноеДТВВ / КонечноеКТВВ» |
| 1732 | «ОСВ по внб сч 009 за 3 кв 2025.xlsx» (КТЗ) | итог «ВСЕГО» |
Эксперимент автора задач на копии словарей (20.10.2026, синонимы 1717 + 1719 без «ДТВВ» и без запасного признака): из
129 файлов 1717 — 43 «Готово», 43 «Требует проверки», 43 упираются в группы и иерархию; из 159 файлов 1719 — 53
«Готово», 58 «Требует проверки», 48 — другое (AURORA-1720, 1721, 1723). Остальные ОСВ банка статусы не ухудшили.
## Goals / Non-Goals
**Goals:**
- Листы с перечисленными названиями и листы без названия с полным заголовком таблицы ОСВ распознаются как ОСВ.
- Перечисленные написания групп и сторон находятся; заголовок таблицы этих файлов не падает с `INVALID_FORMAT`
«нет групп сумм».
- «ВСЕГО» — итоговая строка листа.
- Ни один документ банка первички и ни одна фикстура не ухудшает результат (см. «Регрессия»).
**Non-Goals:**
- Довести все затронутые файлы до «Готово»: дальше их держат иерархия (AURORA-1721, 1730), одинаковые подписи начала и
конца (AURORA-1720), обороты без подзаголовков в варианте НОВО-МАР и SAP со стороной перед группой (AURORA-1722),
ведомости ОС/ТМЦ (AURORA-1723, 1731). Эти файлы могут остаться `needs_check` или `INVALID_FORMAT` по другой причине.
- Отчёт 1С «Обороты счета» (1 файл, ГК Сейма): внутри «Оборот Дт/Кт» колонки корреспондирующих счетов, это отдельная
форма. Синоним `обороты\s+счета` не добавляется.
- Правило «расшифровка без кодов ББ не может быть Готово» (вторая половина AURORA-1718) — пакет `arm_statements`,
ждёт AURORA-1677.
**Anti-goals (провал, даже если тесты зелёные):**
- Лист формы ББ/ОФР, её продолжения или расшифровки становится ОСВ по запасному признаку или по новому названию
ведомости, хотя ведомостью не является. Ведомость, которую сегодня ложно забирает конвейер форм (10 файлов «Оборотный
баланс», AURORA-1718), становится ОСВ по названию — это цель, а не ухудшение (признак по названию проверяется раньше
форм, как сейчас).
- `if` под файл, организацию, систему-источник или имя листа вместо синонима в YAML.
## Decisions
### D1. Запасной признак листа — последним шагом (решение владельца 28.10.2026)
Порядок классификации: признак ОСВ по названию (`sheet_markers`) → форма / продолжение / прочее (пакет). Только если
пакет вернул «прочее», лист проверяется запасным признаком: в первых `SHEET_MARKER_ROWS` строках есть строка групп, где
найдены **все три** группы (начало, обороты, конец), и у каждой группы найдены **обе** стороны — Дебет и Кредит
(в ячейках под группой или в самой ячейке группы). Свёрнутое сальдо (одна колонка в группе) запасной признак не даёт.
Нашёлся — лист `OSV`, дальше обычный конвейер ОСВ; нет — `SHEET_OTHER`, как сейчас.
Почему так: расшифровки ББ с колонками «Остаток на начало / … / Остаток на конец» забирает классификатор форм раньше,
до запасного признака они не доходят; поиск заголовка выполняется только на листах «прочее», а не на каждом листе книги
с формами. Требование трёх групп и обеих сторон строже обычного заголовка ОСВ («начало» там необязательно), поэтому
ложное срабатывание требует почти полной таблицы ОСВ.
### D2. Новые записи словарей
Ориентир — рекомендации из AURORA-1717/1719/1732; окончательные паттерны подбирает исполнитель по образцам, каждая
запись — с синтетикой, где она срабатывает (R-22). Паттерны пишутся по нормализованному тексту (нижний регистр, «ё» →
«е», дефисы и переносы → пробел).
| Ключ | Написания |
|---|---|
| `sheet_markers` | «Оборотная ведомость …», «ОСВ по сч. …» (с начала ячейки), «Оборотный баланс», «Фин. Обороты Листок», «Отчет по Главной книге» |
| `groups.opening` | «Остаток на начало (периода)», «Начальное сальдо» (уже есть `\bнач(альн\w*)?\.?\s*сальдо`), «Сальдо начальное…», «Сальдо вх …», «НачалоДт / НачалоКт» |
| `groups.closing` | симметрично: «Остаток на конец», «Конечное сальдо», «Сальдо конечное…», «Сальдо исх …», «КонецДт / КонецКт» |
| `debit` / `credit` | «по дебету» / «по кредиту» (в остатке ячейки группы «Оборот по дебету»); сторона, приклеенная к названию без границы слова («…ДТВВ», «…КТВВ», «НачалоДт») |
| `total_row_exact` | «ВСЕГО» |
Если сторону без границы слова нельзя выразить записью словаря при текущем `_side` (поиск с начала остатка ячейки),
допустима правка поиска стороны в `detect.py`, общая для всех написаний, без упоминания конкретной системы.
### D3. Регрессия по банку первички
До изменений и после — прогон парсера (CLI, `ENV_FILE=.env.test`) по всем файлам банка с `route = парсер` (xls, xlsx,
xlsb) на main `a8c9605` и на ветке. Сравнение по каждому документу: вид (ОСВ / форма / прочее), статус, код ошибки.
Ухудшение — `done` → `needs_check`/ошибка, `needs_check` → ошибка, любой переход форма/расшифровка → ОСВ через запасной
признак, переход форма/расшифровка → ОСВ по названию ведомости или «прочее» → ОСВ, если лист не ведомость (каждый такой
переход по названию просматривается вручную: ожидаемы «Оборотный баланс» и другие ведомости из списков 1717/1719).
Каждое ухудшение разбирается до PR: либо исправляется, либо выносится владельцу с образцом.
Банк — `awp-ca/doc-bank/` (в worktree — симлинк `.wt/osv-dict/doc-bank`), только чтение. Базовый прогон на main
`a8c9605` снимается до первой правки кода. Скрипт прогона и сравнения и его результаты — `awp-ca/admin/osv-other-systems-regress/`
(не репозиторий; в PR — только итоговая таблица).
Приёмка: сценарии спеки на синтетике, зелёный `make check-full`, нет ухудшений D3. Число файлов банка, перешедших в
«Готово» / «Требует проверки», — отчёт, а не порог. Реальные безымянные листы, которые конвейер форм относит к форме или
продолжению (до запасного признака они не доходят), остаются как есть и перечисляются в PR как остаток. Итоговая
таблица переходов статусов (было → стало, число файлов) — в описание PR и комментарий к AURORA-1717/1719. Плюс обычные
`make check-full` (golden, property, покрытие словарей) и `tests/fixtures/test_osv_dkb.py`.
### D4. Правило в 05-domain-rules.md
§1.1: признак листа — название ведомости по словарю `sheet_markers` (примеры написаний) или, если лист не опознан как
форма ББ/ОФР или её продолжение, полный заголовок таблицы ОСВ в первых 30 строках (D1). §1.2: варианты написания групп
и сторон дополняются написаниями из D2. PR в arm-ka-docs из ветки `feat/81-osv-other-systems` worktree
`.wt/osv-dict/docs`, ссылка на tabular-parser#81.
## Risks / Trade-offs
- [`^осв\b` и «оборотн… ведомост» в тексте формы] → проверка ОСВ идёт первой; регрессия D3 ловит листы форм, ставшие
ОСВ; паттерн «ОСВ» — только с начала ячейки.
- [Расшифровка, не узнанная пакетом, проходит запасной признак] → D1 требует три группы и обе стороны; регрессия D3.
- [Новые синонимы групп матчат преамбулу шапки («Остаток на начало: …») как строку групп] → в такой строке нет
«оборотов», `_find_group_row` её пропускает (R-12); синтетика с преамбулой.
- [Сторона без границы слова («дт» внутри слова)] → искать её только в остатке ячейки группы после совпадения группы, не
в произвольных ячейках.
## Decisions made without discussion
| Дата | Решение | Обоснование | Цена ошибки |
|---|---|---|---|
| 2026-10-28 | R-1. Отчёт 1С «Обороты счета» вне объёма, синоним не добавляется | автор AURORA-1717 проверил: с синонимом файл даёт «Требует проверки» с договорами в контрагентах — нужна отдельная форма | 1 файл остаётся `INVALID_DOCUMENT`; отдельная задача |
| 2026-10-28 | R-2. Запасной признак ищет строку групп только в первых `SHEET_MARKER_ROWS` (30) строках | у безымянных листов таблица начинается сразу; ограничение снижает время и ложные срабатывания на длинных листах «прочее» | безымянный лист с преамбулой длиннее 30 строк не распознаётся; поднимется регрессией D3 |
| 2026-10-28 | R-3. Запасной признак не принимает свёрнутое сальдо (одна колонка в группе) | строгость против ложных ОСВ (D1) | ОСВ без названия со свёрнутым сальдо не распознаётся |
| 2026-10-28 | R-5. Новые `sheet_markers` якорятся к началу ячейки (`^…`); якорь снимается для конкретного названия, только если по спискам 1717 файлы с ним иначе не находятся | «Оборотная ведомость», «Оборотный баланс» могут встретиться в примечаниях к ББ; признак по названию проверяется раньше форм | часть ведомостей с текстом перед названием не распознаётся; видно в регрессии D3 |
| 2026-10-28 | R-6. Запасной признак переиспользует поиск строки групп и колонок обычного заголовка (`_find_group_row`, `_columns_of_group`) в строгом режиме, а не отдельную функцию | запасной признак и конвейер не должны расходиться в том, что считать заголовком | — |
| 2026-10-28 | R-7. Порядок PR: `fixtures` (golden синтетики, отдельный коммит `make golden-update`) → tabular-parser → docs | golden-тесты tabular-parser читают `../fixtures` | — |
| 2026-10-28 | R-8. Приёмка — синтетика + `make check-full` + нет ухудшений D3; число исправленных файлов банка — отчёт, не порог | эксперимент автора задач — ориентир, а не обязательство; остаток держат 1720–1723 | — |
| 2026-10-28 | R-4. AURORA-1722 (SAP со стороной перед группой, НОВО-МАР) не входит | в задачах указан порядок: после 1717/1719; причина варианта НОВО-МАР не разобрана | эти файлы остаются с ошибкой до 1722 |

View File

@ -0,0 +1,42 @@
## Why
Прогон банка первичных документов (AURORA-1706, архивы «ДКБ отчетность» 20.10.2026, 1681 табличная ОСВ) показал, что
парсер знает только ведомости 1С с названием «Оборотно-сальдовая ведомость» и написания групп сумм 1С. ОСВ других
учётных систем (SAP, Navision, выгрузки КТЗ) и отчёты 1С с другим названием отклоняются целиком:
- AURORA-1717 — 119 файлов `INVALID_DOCUMENT` «в книге нет листов ОСВ или форм»: «Оборотная ведомость», «ОСВ по сч.»,
«Оборотный баланс», «Фин. Обороты Листок» (Navision), «Отчет по Главной книге», листы без названия; ещё 10 файлов
«Оборотный баланс» получают ложное «Готово» как расшифровка формы (часть AURORA-1718);
- AURORA-1719 — 159 файлов `INVALID_FORMAT` «не найден заголовок таблицы ОСВ»: «Остаток на начало/конец», «Начальное /
Конечное сальдо», «Сальдо вх/исх Дт» (SAP), «НачалоДт/КонецКт» (Navision), «Оборот по дебету/кредиту» (группа и
сторона в одной ячейке), «Сальдо НачальноеДТВВ» (сторона приклеена к названию);
- AURORA-1732 — итог листа «ВСЕГО» (ГК КТЗ, сч. 009) не распознаётся как «Итого» («ИТОГ» и «ИТОГ по …» уже добавлены
AURORA-1714).
Gitea: RSHB/arm-tabular-parser#81.
## What Changes
- Признаки листа ОСВ (`sheet_markers`): синонимы названий ведомости других систем и отчётов 1С.
- Запасной признак листа ОСВ: лист, который не опознан ни как ОСВ по названию, ни как форма ББ/ОФР или её продолжение,
считается ОСВ, если в его первых строках найден полный заголовок таблицы ОСВ (три группы сумм, у каждой — Дебет и
Кредит). Проверка выполняется последней, только для листов, которые иначе ушли бы в «прочее» (`SHEET_OTHER`).
- Синонимы групп сумм (`groups.opening`, `groups.closing`) и сторон (`debit`, `credit`), включая сторону в одной
ячейке с группой («Оборот по дебету», «НачалоДт») и приклеенную к названию группы («НачальноеДТВВ»).
- Итоговая строка «ВСЕГО» (`total_row_exact`).
- Синтетика на каждое новое написание (правило покрытия словарей R-22).
- `05-domain-rules.md` §1.1–§1.2 — правило поиска листа и написания групп (отдельный PR в arm-ka-docs, та же ветка
`feat/81-osv-other-systems`).
## Capabilities
### Modified Capabilities
- `osv-parsing`: признак листа ОСВ — название ведомости или (запасной путь) полный заголовок таблицы; написания групп и
сторон других учётных систем.
## Impact
- `api/rules/osv_headers.yaml`, `api/src/parser/detect.py` (классификация листа, поиск стороны), синтетика
`tests/synthetic/generate.py`, golden-снимки синтетики (отдельный коммит в `fixtures`).
- Контракт результата не меняется. arm-core не меняется.
- Пакет `arm_statements` не меняется (не ждёт AURORA-1677).

View File

@ -0,0 +1,79 @@
## MODIFIED Requirements
### Requirement: Листы ОСВ находятся по содержимому
Парсер SHALL считать листом ОСВ любой лист, где ячейка в первых 30 строках листа (`SHEET_MARKER_ROWS`: шапка ведомости
всегда над таблицей) матчит признак ОСВ из `rules/osv_headers.yaml` (без учёта регистра, «ё/е», переносов строк и
дефисов). Признаки ОСВ — названия ведомости разных учётных систем («Оборотно-сальдовая ведомость», «Оборотная
ведомость», «ОСВ по сч.», «Оборотный баланс», «Фин. Обороты», «Отчет по Главной книге»). Лист с признаками формы ББ/ОФР
или её продолжения уходит в конвейер форм (спека `statement-parsing`). Лист, не опознанный ни как ОСВ по признаку, ни
как форма или её продолжение, SHALL считаться листом ОСВ по запасному признаку, если в первых `SHEET_MARKER_ROWS`
строках есть строка групп сумм со всеми тремя группами («начало», «обороты», «конец») и у каждой группы найдены обе
стороны — «Дебет» и «Кредит»; свёрнутое сальдо (одна колонка в группе) запасного признака не даёт. Запасной признак
MUST проверяться только после классификации форм и MUST NOT отнимать у конвейера форм лист формы, её продолжения или
расшифровки. Прочие листы SHALL пропускаться с предупреждением файла `SHEET_OTHER` (`severity = info`,
`locator = sheet:<имя>`) в `metrics.warnings`. Книга без единого листа ОСВ и без форм → `status = error`,
`error.code = INVALID_DOCUMENT` («в книге нет листов ОСВ или форм»). Несколько листов ОСВ → несколько `documents` с
`locator = sheet:<имя>`. Имя листа MUST NOT использоваться для поиска.
#### Scenario: Один лист — одна ОСВ (все реальные фикстуры)
- **WHEN** разбирается любой файл из `fixtures/osv/**`
- **THEN** `documents` содержит ровно один `osvDocument` с `locator = "sheet:Лист_1"`, статус `done`
#### Scenario: Два листа ОСВ и посторонний лист
- **WHEN** разбирается синтетический файл с листами «ОСВ 60», «Справка», «ОСВ 62»
- **THEN** `documents` содержит два документа в порядке листов, в `metrics.warnings` есть `SHEET_OTHER` с `locator = "sheet:Справка"`
#### Scenario: Нет ОСВ
- **WHEN** разбирается книга с одним листом произвольной таблицы
- **THEN** `status = error`, `error.code = INVALID_DOCUMENT`, сообщение называет число просмотренных листов
#### Scenario: Название ведомости другой учётной системы
- **WHEN** разбирается синтетическая ОСВ, у которой в шапке вместо «Оборотно-сальдовая ведомость» стоит одно из
названий «Оборотная ведомость по счету 009», «ОСВ по сч. 41 за 3 кв.», «Оборотный баланс», «Фин. Обороты Листок»,
«Отчет по Главной книге»
- **THEN** лист разобран как ОСВ (`osvDocument`), `SHEET_OTHER` для него нет
#### Scenario: Лист без названия ведомости — по заголовку таблицы
- **WHEN** разбирается синтетический лист без названия ведомости, таблица начинается в первых строках с групп
«НачалоДт | НачалоКт | ОборотДт | ОборотКт | КонецДт | КонецКт» (Navision)
- **THEN** лист разобран как ОСВ по запасному признаку
#### Scenario: Расшифровка формы не становится ОСВ
- **WHEN** разбирается книга с формой ББ и листом расшифровки с колонками «Остаток на начало | Поступило | Выбыло |
Остаток на конец»
- **THEN** вид листа расшифровки тот же, что без запасного признака (форма или продолжение конвейера форм либо
`SHEET_OTHER`), документа ОСВ из него нет
#### Scenario: Неполная таблица не даёт запасного признака
- **WHEN** лист без названия ведомости содержит группы «обороты» и «конец», но без группы «начало» или со свёрнутым
сальдо (одна колонка в группе)
- **THEN** лист пропускается с `SHEET_OTHER`
## ADDED Requirements
### Requirement: Написания групп и сторон других учётных систем
Словари `groups`, `debit`, `credit` SHALL знать написания групп и сторон выгрузок SAP, Navision и других систем: «Остаток
на начало/конец (периода)», «Начальное/Конечное сальдо», «Сальдо начальное/конечное», «Сальдо вх/исх», «НачалоДт/Кт»,
«КонецДт/Кт». Сторона SHALL находиться и в отдельной ячейке под группой, и в самой ячейке группы после её названия — в
том числе в форме «по дебету»/«по кредиту» («Оборот по дебету») и приклеенной к названию без пробела («Сальдо
НачальноеДТВВ», «НачалоДт»). Сторона без границы слова SHALL искаться только в остатке ячейки группы после совпадения
группы. Итоговая строка «ВСЕГО» SHALL распознаваться как итог листа наравне с «Итого» и «ИТОГ».
#### Scenario: Остаток и оборот по дебету
- **WHEN** разбирается синтетическая ОСВ с группами «Остаток на начало периода | Оборот по дебету | Оборот по кредиту |
Остаток на конец периода» и подзаголовками «Дебет | Кредит» под остатками
- **THEN** найдены все шесть колонок сумм; обороты — из ячеек «Оборот по дебету» и «Оборот по кредиту»
#### Scenario: Сторона приклеена к названию
- **WHEN** разбирается синтетическая ОСВ с заголовком «Сальдо НачальноеДТВВ | Сальдо НачальноеКТВВ | Оборот ДТ | Оборот
КТ | Сальдо КонечноеДТВВ | Сальдо КонечноеКТВВ»
- **THEN** найдены все шесть колонок сумм с верными сторонами
#### Scenario: SAP «Сальдо вх/исх»
- **WHEN** разбирается синтетическая ОСВ с группами «Сальдо вх Дт | Сальдо вх Кт | Оборот Дт | Оборот Кт | Сальдо исх Дт
| Сальдо исх Кт»
- **THEN** найдены все шесть колонок сумм
#### Scenario: Итог «ВСЕГО»
- **WHEN** разбирается синтетическая ОСВ, итоговая строка которой подписана «ВСЕГО»
- **THEN** строка — `TOTAL`, предупреждения «строка «Итого» не найдена» нет