From d23f3cf3bf621f69442e313f139572a6271ec57b Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Tue, 10 Nov 2026 17:02:50 +0400 Subject: [PATCH] =?UTF-8?q?openspec:=20=D0=B0=D1=80=D1=85=D0=B8=D0=B2=20us?= =?UTF-8?q?e-arm-statements-library=20(AURORA-1677)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- .../.openspec.yaml | 0 .../design.md | 0 .../proposal.md | 0 .../specs/statement-parsing/spec.md | 0 .../specs/tabular-recognition-service/spec.md | 0 .../tasks.md | 0 openspec/specs/statement-parsing/spec.md | 10 ++--- .../specs/tabular-recognition-service/spec.md | 44 +++++++++++++++---- 8 files changed, 40 insertions(+), 14 deletions(-) rename openspec/changes/{use-arm-statements-library => archive/2026-11-10-use-arm-statements-library}/.openspec.yaml (100%) rename openspec/changes/{use-arm-statements-library => archive/2026-11-10-use-arm-statements-library}/design.md (100%) rename openspec/changes/{use-arm-statements-library => archive/2026-11-10-use-arm-statements-library}/proposal.md (100%) rename openspec/changes/{use-arm-statements-library => archive/2026-11-10-use-arm-statements-library}/specs/statement-parsing/spec.md (100%) rename openspec/changes/{use-arm-statements-library => archive/2026-11-10-use-arm-statements-library}/specs/tabular-recognition-service/spec.md (100%) rename openspec/changes/{use-arm-statements-library => archive/2026-11-10-use-arm-statements-library}/tasks.md (100%) diff --git a/openspec/changes/use-arm-statements-library/.openspec.yaml b/openspec/changes/archive/2026-11-10-use-arm-statements-library/.openspec.yaml similarity index 100% rename from openspec/changes/use-arm-statements-library/.openspec.yaml rename to openspec/changes/archive/2026-11-10-use-arm-statements-library/.openspec.yaml diff --git a/openspec/changes/use-arm-statements-library/design.md b/openspec/changes/archive/2026-11-10-use-arm-statements-library/design.md similarity index 100% rename from openspec/changes/use-arm-statements-library/design.md rename to openspec/changes/archive/2026-11-10-use-arm-statements-library/design.md diff --git a/openspec/changes/use-arm-statements-library/proposal.md b/openspec/changes/archive/2026-11-10-use-arm-statements-library/proposal.md similarity index 100% rename from openspec/changes/use-arm-statements-library/proposal.md rename to openspec/changes/archive/2026-11-10-use-arm-statements-library/proposal.md diff --git a/openspec/changes/use-arm-statements-library/specs/statement-parsing/spec.md b/openspec/changes/archive/2026-11-10-use-arm-statements-library/specs/statement-parsing/spec.md similarity index 100% rename from openspec/changes/use-arm-statements-library/specs/statement-parsing/spec.md rename to openspec/changes/archive/2026-11-10-use-arm-statements-library/specs/statement-parsing/spec.md diff --git a/openspec/changes/use-arm-statements-library/specs/tabular-recognition-service/spec.md b/openspec/changes/archive/2026-11-10-use-arm-statements-library/specs/tabular-recognition-service/spec.md similarity index 100% rename from openspec/changes/use-arm-statements-library/specs/tabular-recognition-service/spec.md rename to openspec/changes/archive/2026-11-10-use-arm-statements-library/specs/tabular-recognition-service/spec.md diff --git a/openspec/changes/use-arm-statements-library/tasks.md b/openspec/changes/archive/2026-11-10-use-arm-statements-library/tasks.md similarity index 100% rename from openspec/changes/use-arm-statements-library/tasks.md rename to openspec/changes/archive/2026-11-10-use-arm-statements-library/tasks.md diff --git a/openspec/specs/statement-parsing/spec.md b/openspec/specs/statement-parsing/spec.md index 810d23a..45a400c 100644 --- a/openspec/specs/statement-parsing/spec.md +++ b/openspec/specs/statement-parsing/spec.md @@ -123,7 +123,7 @@ - **THEN** результат содержит `osvDocument` и `statementDocument`, статус книги по обоим ### Requirement: Словари форм, координаты, golden -Словари форм (заголовки и ОКУД, периоды, соотношения §7, единицы, перечни кодов) SHALL жить в пакете `arm_statements` и в +Словари форм (заголовки и ОКУД, периоды, соотношения §7, единицы, перечни кодов) SHALL жить в установленной библиотеке `arm_statements` и в tabular-parser MUST NOT дублироваться: файлы `api/rules/statement_*.yaml` удалены. Код `src/parser/statements/**` и `src/parser/book.py` MUST NOT содержать литералов координат (линтер `test_no_coordinates`); `ref` ячейки собирается из индексов функцией `grid.cell_ref`. Снимки `../fixtures/golden/expected_statement_parse/**` SHALL покрывать все файлы @@ -132,13 +132,13 @@ Property-тест по манифесту `tests/statements_manifest.yaml` SHALL жёстких соотношений для полных форм и, где задано, множество кодов предупреждений `error` документа. #### Scenario: Новый синоним без релиза кода -- **WHEN** в словарь заголовков пакета `arm-statements` добавлен синоним заголовка «Баланс предприятия» и копия пакета - обновлена скриптом `sync-statements` +- **WHEN** в словарь заголовков библиотеки `arm_statements` добавлен синоним заголовка «Баланс предприятия» и библиотека + переустановлена командой `make install` - **THEN** лист классифицирован как `BALANCE` без изменения кода tabular-parser; собственного словаря форм в сервисе нет #### Scenario: Мёртвая запись словаря - **WHEN** запись словаря форм не срабатывает ни на одном файле -- **THEN** это проверяет тест покрытия словарей пакета `arm-statements` (внутри пакета, на его данных); тест покрытия +- **THEN** это проверяет тест покрытия словарей библиотеки `arm_statements` (внутри пакета, на его данных); тест покрытия tabular-parser словари форм не охватывает #### Scenario: Регрессия по снимкам @@ -149,7 +149,7 @@ Property-тест по манифесту `tests/statements_manifest.yaml` SHALL tabular-parser SHALL оставить у себя чтение книги (лимиты `PARSE_MAX_BYTES`, `PARSE_TIMEOUT_SECONDS`, формат по сигнатуре, лимиты zip и объединённых ячеек, `UNREADABLE`), классификацию листов (ОСВ — отдельный путь; форма; продолжение; прочее) и адаптер «блок формы на листе → `FormCandidate`» общего кода; шаги 2–8 по формам SHALL -выполняться копией пакета `arm_statements`. Адаптер SHALL: выделять блоки форм на листе по заголовку или ОКУД (без +выполняться установленной библиотекой `arm_statements`. Адаптер SHALL: выделять блоки форм на листе по заголовку или ОКУД (без кодов между ними блоки сливаются, заголовок после кодов — новая форма); собирать шапку каждого блока в его границах (организация, ИНН без ИНН аудитора, ОКПО, ОКОПФ, ОКФС, единицы, дата, адрес) как `Cell` с `confidence = 100`; заполнять у каждой ячейки `text` по числовому формату (tp#42) и `value`, `ref = <лист>!` (имя листа в одинарных diff --git a/openspec/specs/tabular-recognition-service/spec.md b/openspec/specs/tabular-recognition-service/spec.md index 6e8c432..f1b77d7 100644 --- a/openspec/specs/tabular-recognition-service/spec.md +++ b/openspec/specs/tabular-recognition-service/spec.md @@ -36,8 +36,8 @@ Stateless-сервис разбора табличных файлов (XLS/XLSX/ (по `recognition-result.schema.json` v2.0, `schema_version = "2.0"`) в `results/{file_id}/{job_id}/result.json` и опубликовать конверт результата (по `recognition-result-message.schema.json` v2.0, `result_schema_version = "2.0"`) с заголовком `Nats-Msg-Id = result::`. Ack `processing` SHALL нести `schema_version = "2.0"`; запрос -`recognition-request` остаётся v1.0. `service.statements_version` файла результата SHALL быть коммитом копии пакета -`arm_statements` (файл `VERSION` копии; сервис без него не стартует) и записываться при наличии `statementDocument`. +`recognition-request` остаётся v1.0. `service.statements_version` файла результата SHALL быть версией библиотеки +`arm_statements` (VERSION или метаданные установленного дистрибутива; без версии сервис не стартует) и записываться при наличии `statementDocument`. `metrics.warnings` конверта SHALL содержать предупреждения уровня файла структурами `{code, severity, message, locator?}`, не строками. Статус конверта — по общему правилу: `done` ⇔ все документы `done` ∧ нет предупреждений файла уровня `error`. При неудаче публикации сервис SHALL повторить её до трёх раз с паузой, затем залогировать ошибку. @@ -57,7 +57,7 @@ Stateless-сервис разбора табличных файлов (XLS/XLSX/ #### Scenario: Результат и ack версии 2.0 - **WHEN** обработан запрос `schema_version = "1.0"` с книгой, содержащей баланс - **THEN** ack `processing` и конверт результата несут `schema_version = "2.0"`, `result_schema_version = "2.0"`, файл - результата проходит схему v2.0, `service.statements_version` равен коммиту копии `arm_statements` + результата проходит схему v2.0, `service.statements_version` равен версии библиотеки `arm_statements` #### Scenario: Предупреждение файла структурой - **WHEN** в книге лист «Примечания» без признаков ОСВ и формы @@ -102,13 +102,13 @@ Stateless-сервис разбора табличных файлов (XLS/XLSX/ Разбор файла SHALL выполняться через единый интерфейс «байты + тип файла → документы/метрики/ошибка», не знающий о NATS и S3; воркер MUST NOT зависеть от конкретной реализации. Реализация по умолчанию (`get_parser()`) SHALL быть оркестратором книги `BookParser`: он читает книгу один раз, классифицирует листы по содержимому и отдаёт листы ОСВ -конвейеру спеки `osv-parsing`, листы форм и их продолжения — конвейеру спеки `statement-parsing` (адаптер Excel и копия -пакета `arm_statements`); прочие листы дают предупреждение файла `SHEET_OTHER`. Файл результата SHALL писаться с +конвейеру спеки `osv-parsing`, листы форм и их продолжения — конвейеру спеки `statement-parsing` (адаптер Excel и установленная библиотека + `arm_statements`); прочие листы дают предупреждение файла `SHEET_OTHER`. Файл результата SHALL писаться с `schema_version = "2.0"`; документы книги — ОСВ в порядке листов, затем формы в порядке листов. Заглушка `NullParser` (`error NOT_IMPLEMENTED`) SHALL использоваться только в тестах воркера и MUST NOT быть реализацией по умолчанию. Реализация по умолчанию SHALL создаваться один раз при старте приложения (словари ОСВ читаются и проверяются там же, словари форм — `default_rules()` пакета). Каталог `api/rules/` SHALL содержать словари ОСВ (`osv_headers`, `osv_groups`, -`periods`, `data_types`, `osv_checks`) и README; словарей форм ББ/ОФР в сервисе нет — они часть копии `arm_statements`. +`periods`, `data_types`, `osv_checks`) и README; словарей форм ББ/ОФР в сервисе нет — они часть библиотеки `arm_statements`. #### Scenario: Подмена реализации в тесте - **WHEN** в тесте воркеру передан парсер, возвращающий один `osvDocument` @@ -124,17 +124,21 @@ NATS и S3; воркер MUST NOT зависеть от конкретной р - **THEN** после ack публикуется конверт `done` с `result_schema_version = "2.0"`, файл результата проходит схему v2.0 и содержит два `statementDocument` (`BALANCE`, `INCOME`) со строками `line_status`, `ref`, `checks` и пустыми `checks.relations_failed`; расхождения итогов в пределах округления (`1600 = 1700` на 1.94 / 9.02 / 2.48, `2100`/`2200`/ - `2300`) — в `soft_relations_failed` (OQ-138); `service.statements_version` равен коммиту копии пакета + `2300`) — в `soft_relations_failed` (OQ-138); `service.statements_version` равен версии библиотеки #### Scenario: Книга без ОСВ и форм - **WHEN** в S3 лежит книга Excel, ни один лист которой не имеет признаков ОСВ или формы - **THEN** публикуется конверт `error`, `error.code = INVALID_DOCUMENT`, сообщение называет число просмотренных листов #### Scenario: Сервис без версии общего кода не стартует -- **WHEN** в копии `arm_statements` нет файла `VERSION` -- **THEN** `require_statements_version()` бросает `RuntimeError` с текстом «копия arm_statements без VERSION», сервис не +- **WHEN** у библиотеки `arm_statements` нет ни VERSION, ни версии установленного дистрибутива +- **THEN** `require_statements_version()` бросает `RuntimeError` с текстом «библиотека arm_statements без VERSION и версии пакета: выполните make install», сервис не стартует +#### Scenario: Установленный дистрибутив без VERSION +- **WHEN** библиотека установлена как `pylib_ai_arm_statements==0.1.1`, файл VERSION отсутствует, а метаданные дистрибутива доступны +- **THEN** сервис стартует и указывает `service.statements_version = "0.1.1"` при обработке реальной книги `statements-xlsx/npz/2025-Q3/…xlsx` + ### Requirement: Копия контракта идентична источнику Каждый файл `api/contracts/**/*.json` SHALL быть байт в байт равен своей паре в `../docs/contracts/` (или в `contracts` каталога из `ARM_KA_DOCS`) по правилу спеки docs `recognition-contract`, требование «Копия контракта в сервисе @@ -195,3 +199,25 @@ JSON, проходящий схему копии. Проверка входит #### Scenario: Тесты на своих subject'ах - **WHEN** тесты roundtrip (`tests/integration/test_roundtrip.py`) запускаются при работающих контейнерах стенда на том же NATS - **THEN** тесты используют уникальные subject'ы из настроек и не получают и не забирают сообщения стенда + +### Requirement: Локальная поставка библиотеки форм +Окружение сервиса SHALL использовать установленную библиотеку `pylib_ai_arm_statements==0.1.1` с импортом `arm_statements`. `make venv` SHALL пропускать строки `pylib_ai*` и `raisa_*` при установке из requirements через индекс и устанавливать библиотеку из `STATEMENTS_DIR` (по умолчанию `../pylib_ai_arm_statements`). `make install` SHALL создавать окружение при необходимости и переустанавливать библиотеку из этого пути. + +#### Scenario: Подготовка окружения +- **WHEN** разработчик выполняет `make venv` при доступной локальной библиотеке +- **THEN** runtime- и dev-зависимости установлены в `api/.venv`, библиотека импортируется из установленного пакета; запрос на скачивание `pylib_ai*` из PyPI не выполняется + +#### Scenario: Переустановка после изменения библиотеки +- **WHEN** разработчик выполняет `make install STATEMENTS_DIR=<путь-к-библиотеке>` после изменения её исходников без смены версии +- **THEN** библиотека переустановлена из указанного пути в `api/.venv` и изменения доступны сервису + +### Requirement: Библиотека форм в контейнерном образе +`make docker-build` SHALL передавать `STATEMENTS_DIR` именованным контекстом BuildKit `statements`. Сборка SHALL устанавливать библиотеку из этого контекста вместе с публичными зависимостями, без загрузки `pylib_ai*` из PyPI. Образ SHALL содержать установленный пакет и его словари, доступные при запуске без исходной папки хоста. + +#### Scenario: Сборка и запуск с локальной библиотекой +- **WHEN** образ собран через `make docker-build` при доступном BuildKit и локальной библиотеке +- **THEN** внутри образа импортируется `arm_statements`, доступны словари и версия `0.1.1`; исходная папка хоста при запуске не требуется + +#### Scenario: Прямая сборка +- **WHEN** сборка запускается напрямую командой `docker build` +- **THEN** вызывающая сторона SHALL передать `--build-context statements=<путь-к-библиотеке>`; отсутствие контекста не заменяется загрузкой библиотеки из PyPI -- 2.47.2