From f5b73d21461f9cd04ce6e11e0621c1b91c94d46c Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Wed, 28 Oct 2026 19:52:43 +0400 Subject: [PATCH] =?UTF-8?q?spec:=20=D0=9E=D0=A1=D0=92=20=D0=B4=D1=80=D1=83?= =?UTF-8?q?=D0=B3=D0=B8=D1=85=20=D1=83=D1=87=D1=91=D1=82=D0=BD=D1=8B=D1=85?= =?UTF-8?q?=20=D1=81=D0=B8=D1=81=D1=82=D0=B5=D0=BC=20=E2=80=94=20=D0=BF?= =?UTF-8?q?=D1=80=D0=B8=D0=B7=D0=BD=D0=B0=D0=BA=D0=B8=20=D0=BB=D0=B8=D1=81?= =?UTF-8?q?=D1=82=D0=B0,=20=D0=B3=D1=80=D1=83=D0=BF=D0=BF=D1=8B,=20=C2=AB?= =?UTF-8?q?=D0=92=D0=A1=D0=95=D0=93=D0=9E=C2=BB=20(#81)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AURORA-1717, AURORA-1719, AURORA-1732. Co-Authored-By: Claude Opus 5.5 --- .../changes/osv-other-systems/.openspec.yaml | 2 + openspec/changes/osv-other-systems/design.md | 129 ++++++++++++++++++ .../changes/osv-other-systems/proposal.md | 42 ++++++ .../specs/osv-parsing/spec.md | 79 +++++++++++ 4 files changed, 252 insertions(+) create mode 100644 openspec/changes/osv-other-systems/.openspec.yaml create mode 100644 openspec/changes/osv-other-systems/design.md create mode 100644 openspec/changes/osv-other-systems/proposal.md create mode 100644 openspec/changes/osv-other-systems/specs/osv-parsing/spec.md diff --git a/openspec/changes/osv-other-systems/.openspec.yaml b/openspec/changes/osv-other-systems/.openspec.yaml new file mode 100644 index 0000000..1bb6456 --- /dev/null +++ b/openspec/changes/osv-other-systems/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-10-28 diff --git a/openspec/changes/osv-other-systems/design.md b/openspec/changes/osv-other-systems/design.md new file mode 100644 index 0000000..54b688e --- /dev/null +++ b/openspec/changes/osv-other-systems/design.md @@ -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 | diff --git a/openspec/changes/osv-other-systems/proposal.md b/openspec/changes/osv-other-systems/proposal.md new file mode 100644 index 0000000..55e4901 --- /dev/null +++ b/openspec/changes/osv-other-systems/proposal.md @@ -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). diff --git a/openspec/changes/osv-other-systems/specs/osv-parsing/spec.md b/openspec/changes/osv-other-systems/specs/osv-parsing/spec.md new file mode 100644 index 0000000..644a002 --- /dev/null +++ b/openspec/changes/osv-other-systems/specs/osv-parsing/spec.md @@ -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`, предупреждения «строка «Итого» не найдена» нет