From 00d171564ee6f7a2363cdd6426f6070434cd7ec4 Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Tue, 6 Oct 2026 21:19:08 +0400 Subject: [PATCH] =?UTF-8?q?docs(2b):=2006-test-data=20(=D0=BF=D0=BE=D0=B4?= =?UTF-8?q?=D1=80=D0=B0=D0=B7=D0=B4=D0=B5=D0=BB=204=D0=B1,=20=D1=81=D0=B8?= =?UTF-8?q?=D0=BD=D1=82=D0=B5=D1=82=D0=B8=D0=BA=D0=B0=20st01=E2=80=93st06,?= =?UTF-8?q?=20golden=20=D1=84=D0=BE=D1=80=D0=BC),=2003-architecture=20(Boo?= =?UTF-8?q?kParser,=20=D0=BA=D0=BE=D0=BD=D1=82=D1=80=D0=B0=D0=BA=D1=82=20v?= =?UTF-8?q?1.2),=2009-delivery-plan=20(=D0=A1=D1=80=D0=B5=D0=B7=202b)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refs #26 Co-Authored-By: Claude Sonnet 5.5 Claude-Session: cedd7915-2efe-41e5-988c-a9117033598b --- context/03-architecture.md | 10 +++++--- context/06-test-data.md | 49 +++++++++++++++++++++++++++++++------ context/09-delivery-plan.md | 15 ++++++++++-- 3 files changed, 62 insertions(+), 12 deletions(-) diff --git a/context/03-architecture.md b/context/03-architecture.md index f779d63..03d6e94 100644 --- a/context/03-architecture.md +++ b/context/03-architecture.md @@ -27,8 +27,12 @@ - **arm-core** — монолитное ядро: справочники, файлы/документы, маппинг результатов распознавания, консолидация (вычисления), согласование, ЕХ, аудит, права. Один Alembic, одна схема. - **tabular-parser** — читает файл из S3 `uploads/`, находит на листах ОСВ (§1) и формы ББ/ОФР (§12) **по содержимому**, - пишет нормализованный JSON в S3 `results/`, публикует `recognition.result`. Контракт результата — - `docs/contracts/recognition-result.schema.json` (контракт v1.0). Правила распознавания + пишет нормализованный JSON в S3 `results/`, публикует `recognition.result`. Внутри — `BookParser` (оркестратор книги: + лимиты, чтение, дедлайн, вид каждого листа): листы ОСВ идут в конвейер `osv.py`, листы форм — в конвейер форм + `statements/` (блоки форм, колонки, шапка, строки, соотношения §7, склейка продолжений); документы результата — сначала + ОСВ, затем формы. Контракт результата — + `docs/contracts/recognition-result.schema.json` (контракт v1.2: `DISCLOSURE` в `document_type`, `statementDocument.checks`, + границы периода ОСВ `period_start/period_end/is_from_year_start`). Правила распознавания заголовков/группировок — `rules/*.yaml`. - **scan-gateway** — адаптер ИС Сканирование: переименовать файл `UUID_имя.pdf`, положить в «Входящие», опросить «Исходящие/UUID/UUID.json», сохранить сырой ответ в S3 `raw/{job_id}.json` (в БД не регистрируется), нормализовать @@ -82,7 +86,7 @@ работе или обработанного, он не публикуется (его `Nats-Msg-Id` совпал бы с настоящим результатом). Нечитаемый запрос (не JSON, нет `job_id`) остаётся без ответа: сервис пишет лог и отбрасывает его, ядро не получает ack, повторяет публикацию `OUTBOX_MAX_REPUBLISH` раз и закрывает задание в `dlq` с `error_code = NO_ACK`. Новый MINOR раскатывается сначала на потребителей - (ядро и сервисы обновляют копии схем раньше, чем производитель начнёт слать 1.1), потому что закрытые enum отвергают + (ядро и сервисы обновляют копии схем раньше, чем производитель начнёт слать новый MINOR, например 1.2), потому что закрытые enum отвергают незнакомые значения. - Сервисы не знают про БД и про бизнес-смысл: они возвращают структуру файла; нормализация счетов, резолв контрагентов, проверки — в ядре. diff --git a/context/06-test-data.md b/context/06-test-data.md index c7b3d39..381046c 100644 --- a/context/06-test-data.md +++ b/context/06-test-data.md @@ -139,7 +139,7 @@ JSON `aep_*.json` — реальные ответы Content AI: структур |---|---|---| | ОСВ шести организаций (29 файлов, 34 МБ) | `osv-dkb//<период>/` | АО «Цимлянский» (7 ОСВ за 9 мес. 2025), ООО «Новороссийский Кислородный завод» (2 книги по 3 листа), ООО «Новоросметалл» (2 книги по 3 листа + ОСВ 63; вариант 2, «сумма»), ООО «Новороссийский прокатный завод» (2 книги; одна за окт. 2024 – сент. 2025), ООО «Приманыческий» (7 ОСВ), АО фирма «Агрокомплекс» им. Ткачева (4 ОСВ по 50 тыс. строк, 9–14 МБ) | | «Анализ счета» (3 книги) | `analysis-reports//2025-Q3/` | отчёт 1С по корреспонденции счетов с отбором по контрагентам группы; не ОСВ, парсер отвечает `INVALID_DOCUMENT` | -| ББ/ОФР в Excel (15 файлов) | `statements-xlsx//<период>/` | АО «Унагранде Ко» и «Унагранде Групп» (постраничные xls из 1С, 2024 г. и 9 мес. 2025), ООО НПЗ (одна книга ФСБУ 4/2023 с подстроками «в том числе»), ООО «Агрофирма Межениновская» (TDSheet, 66н); `templates/` — пустые формы 66н и ФСБУ 4/2023 | +| ББ/ОФР в Excel (18 файлов: 14 форм и 4 шаблона) | `statements-xlsx//<период>/` | АО «Унагранде Ко» и «Унагранде Групп» (постраничные xls из 1С, 2024 г. и 9 мес. 2025), ООО НПЗ (одна книга ФСБУ 4/2023 с подстроками «в том числе»), ООО «Агрофирма Межениновская» (TDSheet, 66н); `templates/` — пустые формы 66н и ФСБУ 4/2023 | | План счетов 1С заказчика | `directories/chart-of-accounts-1c.xlsx` | 488 строк: код, наименование, субконто 1–3, вид; в seed 122 кода, все есть в выгрузке; 366 кодов выгрузки в seed нет (OQ-113, arm-core#23) | `osv-dkb/` намеренно вне `osv/`: fixtures-тесты парсера и ядра требуют один лист `Лист_1`, `done`, период = каталогу и @@ -149,6 +149,40 @@ golden-снимок на каждый файл, а здесь многолист «Итого развернутое», листы с именем кода счёта, нет валюты в шапке, объём) — `fixtures/osv-dkb/README.md`, arm-tabular-parser#11, OQ-131…OQ-135. ИНН организаций в шапках ОСВ нет (OQ-136). +## 4б. Формы ББ/ОФР в Excel: манифест, golden, синтетика (Срез 2b) + +**Раскладки реальных файлов** (`fixtures/statements-xlsx/`, README там же; 18 файлов: 14 форм и 4 шаблона): + +| Раскладка | Где | Особенности | +|---|---|---| +| 1С-xls постранично | `unagrande-co/2024-FY/`, `unagrande-group/{2024-FY,2025-Q3}/` | один файл = одна страница формы; на странице 1 титул (организация, ИНН, ОКПО, единица «в тыс. рублей», ОКЕИ, аудитор со **своим** ИНН), на странице 2 только «Форма 0710001 с.2»; пятизначные подстроки (11101…), значения в рублях с копейками при «тыс. руб.»; страниц 1 ББ в пакете нет (только актив) и ОФР Групп 2024 — только стр. 2 | +| Многолистовая xlsx ФСБУ 4/2023 | `npz/2025-Q3/` | ББ на 5 листах, ОФР на 3; заголовок формы после титула; листы 2–5 без заголовка и маркера; строка 1105 «Гудвил», подстроки 12301…, строка без кода с суммами; лист ОФР с одной строкой 2400 (OQ-142) | +| `TDSheet` 66н | `mezheninovskaya/2025-Q3/` | вся форма на одном листе, дата тройкой «30 \| 9 \| 2025» числами, двухстрочные заголовки, строка нумерации колонок, значения — тысячи с дробной частью | +| Шаблоны | `templates/` | 66н и ФСБУ 4/2023: коды есть, сумм и шапки нет; заголовок на две строки; в шаблоне ФСБУ «АКТИВ» и код 1110 в одной строке (OQ-141) | + +**Манифест** `tabular-parser/api/tests/statements_manifest.yaml`: для каждого из 18 файлов — организация, ИНН, документы по +порядку (`type`, `status`, `complete`, причина `needs_check`, `failed_relations`). Статусы сверены прогоном: `done` только +у Межениновской (ББ и ОФР); остальные — `needs_check` по данным файла (рубли с копейками при шапке «тыс. руб.», только +актив, страница 2 без шапки, шаблон без сумм), НПЗ — `1600 = 1700` расходится на 1.94 / 9.02 / 2.48 руб. (OQ-137). +Таблица «файл → статус» — README `tabular-parser`, «Парсер форм». + +**Golden** `fixtures/golden/expected_statement_parse/<тот же путь без расширения>.json` — 24 снимка (18 реальных файлов + +6 синтетических); регрессия, не оракул; обновление — `make golden-update` (один для ОСВ и форм), просмотр diff, отдельный +коммит в `fixtures`. Оракулы корректности — манифест и property-тест `tests/fixtures/test_statements_property.py` (манифест покрывает +каждый файл; тип, статус и видимая причина `needs_check` каждого документа совпадают с ожиданиями). + +**Синтетика** `fixtures/statements-xlsx/synthetic/st01…st06` (генератор `tabular-parser/api/tests/synthetic/generate_statements.py`, +ожидания — `tests/synthetic/test_statement_synthetic_files.py`, описание — README каталога): + +| Файл | Что проверяет | +|---|---| +| `st01_both_forms_one_sheet` | ББ и ОФР на одном листе, два документа с одним `locator` (§12 п. 1) | +| `st02_csv_like` | csv-подобный лист: двухстрочный заголовок, коды без подписи «Код», отрицательное значение, единиц нет | +| `st03_no_codes` | форма без колонки кодов → `needs_check`, «колонка кодов не найдена» (OQ-138) | +| `st04_mixed_book` | книга: лист ОСВ + лист баланса, порядок документов ОСВ → форма | +| `st05_continuation_only` | одинокая страница 2 → `needs_check`, предупреждение о продолжении (OQ-139) | +| `st06_header_variants` | варианты, которых нет в реальных файлах: старое название ОФР, расшифровки/ОДДС/ОИК, форма только с ОКУД 0710xxx, организация в «», «Адрес», «Пояснения», числовая дата (покрытие словарей `statement_*.yaml`) | + ## 5. Прочее - `Форма расшифровки к отчетности 01 10 24 / 01 04 25 / 01 07 25.xlsx` — расшифровки АО РБ (кредиты и займы, дебиторы и @@ -224,8 +258,8 @@ fixtures/ ## 7. Синтетика, golden-снимки и xlsb парсера ОСВ (Срез 2a) Реальные файлы покрывают один вариант шапки и один формат файла (xls/xlsx из 1С 8.3). Остальное — синтетика -`fixtures/osv/synthetic/` (вымышленные организации и ИНН; суммы согласованы арифметически, поэтому все файлы, кроме -`s10`, дают `done`). Файлы собирает детерминированный генератор `tabular-parser/api/tests/synthetic/generate.py` +`fixtures/osv/synthetic/` (вымышленные организации и ИНН; суммы согласованы арифметически, поэтому листы ОСВ, кроме +`s10`, дают `done`; книги `s12`/`s14` с формами — `needs_check` по формам). Файлы собирает детерминированный генератор `tabular-parser/api/tests/synthetic/generate.py` (повторный запуск даёт те же байты), ожидания по каждому файлу — в `tests/synthetic/test_synthetic_files.py`, описание — в `fixtures/osv/synthetic/README.md`. @@ -242,13 +276,14 @@ fixtures/ | `s09_subaccount_names` | названия субсчетов в строке счёта, буквенный субсчёт | | `s10_broken_sum` | испорченная сумма → `needs_check` с номерами строк | | `s11_header_repeat` | заголовок таблицы повторяется внутри данных (печать по страницам) | -| `s12_statement_and_osv` | лист ББ и лист ОСВ в одной книге: форма пропускается с предупреждением | +| `s12_statement_and_osv` | лист ББ и лист ОСВ в одной книге: с Среза 2b — два документа (ОСВ, затем ББ без колонки кодов → `needs_check`) | | `s13_no_outline_indent` (`.xlsb`) | та же ведомость, что `s02`, в xlsb | -| `s14_statement_forms` | три формы ББ/ОФР, каждая опознаётся своим признаком | +| `s14_statement_forms` | три формы ББ/ОФР, каждая опознаётся своим признаком (с 2b — документы `BALANCE`/`INCOME` без колонки кодов, `needs_check`) | | `s15_group_title_synonyms` | названия групп сумм «Входящее/Начальное/Конечное/Исходящее сальдо» | | `s16_header_synonyms` | шапка без организации, период «2024», группировки `OTHER`, «Детализация: …» | -`s14`–`s16` добавлены тестом покрытия словарей: каждая запись `rules/*.yaml` обязана сработать на реальных или +`s17`–`s19` — извлечение ИНН (из текста названия, из строки над названием, «ИНН/КПП» в одной ячейке; OQ-100, OQ-130). +Формы ББ/ОФР из Excel — подраздел 4б. `s14`–`s16` добавлены тестом покрытия словарей: каждая запись `rules/*.yaml` обязана сработать на реальных или синтетических файлах. **xlsb рукописный.** `s13` собран **не Excel и не LibreOffice** (их нет на машине разработки), а минимальным писателем @@ -257,7 +292,7 @@ BIFF12 `tabular-parser/api/tests/synthetic/xlsb_writer.py`: он пишет то файл из Excel (OQ-102). Инструкция замены — в README `fixtures/osv/synthetic/`. **Golden-снимки.** Для каждого файла `fixtures/osv/**` (включая синтетику) в `fixtures/golden/expected_osv_parse/` лежит -результат разбора (88 файлов = 72 реальных + 16 синтетических): файл результата контракта v1.1 без `job_id`, `file_id`, +результат разбора (113 файлов, из них 19 синтетических `s01`–`s19`): файл результата контракта v1.2 (v1.1 — до Среза 2b) без `job_id`, `file_id`, `service.version`, `metrics.duration_ms`. Снимок — **регрессия, не оракул**: он фиксирует, что парсер выдаёт сейчас. Обновление — только `make golden-update` в `tabular-parser` с просмотром diff и отдельным коммитом в `fixtures`; `make golden-check` (код возврата 1 при расхождении) — без записи. Оракулы корректности — property-тест diff --git a/context/09-delivery-plan.md b/context/09-delivery-plan.md index 72f318a..a492254 100644 --- a/context/09-delivery-plan.md +++ b/context/09-delivery-plan.md @@ -104,15 +104,26 @@ Change'и: `tabular-parser/openspec/changes/slice-2a-osv-parser` (issue arm-tabu - **Документы:** `05-domain-rules.md` §1 приведён к факту (§1.6 — результат парсера), `06-test-data.md` §7 (синтетика, golden, рукописный xlsb), OQ-98…OQ-105 открыты (финальное ревью добавило OQ-106…OQ-109), OQ-81 частично закрыт (в `tabular-parser` `ARM_KA_FIXTURES` поддержан; `scan-gateway` — follow-up). - **Не сделано / риски:** xlsb проверен только рукописным синтетическим файлом (OQ-102); ИНН из текста строки не - извлекается (OQ-100); счета с буквой в первом сегменте не распознаются (OQ-103). + извлекался (OQ-100; реализовано позже в tabular-parser PR #15); счета с буквой в первом сегменте не распознаются (OQ-103). -### Срез 2b — ББ/ОФР из Excel (`tabular-parser`) +### Срез 2b — ББ/ОФР из Excel (`tabular-parser`) — реализован, ожидает ревью и merge - `tabular-parser`: ББ/ОФР из Excel по §12 в тот же `statementDocument`; поиск листов с формами по содержимому (в 2a такие листы пропускаются с предупреждением `skipped: statement (slice 2b)`); словари синонимов `rules/*.yaml`; контрольные формулы §7 для форм. - Тесты: property-тесты инвариантов на **всех** файлах `fixtures/statements-xlsx/**` (формулы §7) + синтетика (ББ+ОФР на одном листе, csv-подобный лист); снимки результата — рядом с golden ОСВ. +- **Реализовано (ветка `feat/14-slice-2b-statement-parser`, tabular-parser PR #16; docs — PR #27, ветка + `feat/26-slice-2b-statement-parser`):** `BookParser` + конвейер форм `statements/`, словари `statement_*.yaml`, контракт + v1.2 (`DISCLOSURE`, `checks`, границы периода ОСВ; копии схемы в arm-core PR #27 и scan-gateway PR #5), `§12`/`§7` по + решениям владельца. Тесты: манифест `api/tests/statements_manifest.yaml` (18 реальных файлов), синтетика `st01`–`st06`, + golden `expected_statement_parse/` (18 + 6 снимков), интеграционный тест воркера на книге НПЗ. +- **Объём:** парсер + контракт v1.2; **маппинг форм в ядре (`statementDocument → document + financial_statement_*`) — + Срез 3**. По факту `done` только у форм Межениновской; остальные `needs_check` по данным файлов (рубли с копейками при + «тыс. руб.», страницы без шапки, шаблоны) — OQ-137, OQ-139. +- **Не сделано / риски:** единицы не нормализуются (OQ-137); форма без колонки кодов не разбирается (OQ-138); страницы + в разных файлах не склеиваются (OQ-139); строки без кода не хранятся (OQ-140); лист ОФР НПЗ с единственной строкой 2400 + теряется (OQ-142); после контракта v1.2 `docs/contracts/**` заморожены (копии схемы в arm-core и scan-gateway равны им байт в байт); косметика T1.1/T2.x уходит в следующий MINOR. ### Срез 2c — Загрузка файлов и маппинг результата в ядре (`arm-core`) — реализован, ожидает ревью и merge