From e63e83235b5d33d219208de13600de5429b215b0 Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Tue, 10 Nov 2026 17:02:55 +0400 Subject: [PATCH] =?UTF-8?q?openspec:=20=D0=B0=D1=80=D1=85=D0=B8=D0=B2=20us?= =?UTF-8?q?e-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/image-recognition-gateway/spec.md | 0 .../tasks.md | 0 .../verification.md | 0 .../specs/image-recognition-gateway/spec.md | 37 ++++++++++++++++++- 7 files changed, 35 insertions(+), 2 deletions(-) rename openspec/changes/{use-statements-library => archive/2026-11-10-use-statements-library}/.openspec.yaml (100%) rename openspec/changes/{use-statements-library => archive/2026-11-10-use-statements-library}/design.md (100%) rename openspec/changes/{use-statements-library => archive/2026-11-10-use-statements-library}/proposal.md (100%) rename openspec/changes/{use-statements-library => archive/2026-11-10-use-statements-library}/specs/image-recognition-gateway/spec.md (100%) rename openspec/changes/{use-statements-library => archive/2026-11-10-use-statements-library}/tasks.md (100%) rename openspec/changes/{use-statements-library => archive/2026-11-10-use-statements-library}/verification.md (100%) diff --git a/openspec/changes/use-statements-library/.openspec.yaml b/openspec/changes/archive/2026-11-10-use-statements-library/.openspec.yaml similarity index 100% rename from openspec/changes/use-statements-library/.openspec.yaml rename to openspec/changes/archive/2026-11-10-use-statements-library/.openspec.yaml diff --git a/openspec/changes/use-statements-library/design.md b/openspec/changes/archive/2026-11-10-use-statements-library/design.md similarity index 100% rename from openspec/changes/use-statements-library/design.md rename to openspec/changes/archive/2026-11-10-use-statements-library/design.md diff --git a/openspec/changes/use-statements-library/proposal.md b/openspec/changes/archive/2026-11-10-use-statements-library/proposal.md similarity index 100% rename from openspec/changes/use-statements-library/proposal.md rename to openspec/changes/archive/2026-11-10-use-statements-library/proposal.md diff --git a/openspec/changes/use-statements-library/specs/image-recognition-gateway/spec.md b/openspec/changes/archive/2026-11-10-use-statements-library/specs/image-recognition-gateway/spec.md similarity index 100% rename from openspec/changes/use-statements-library/specs/image-recognition-gateway/spec.md rename to openspec/changes/archive/2026-11-10-use-statements-library/specs/image-recognition-gateway/spec.md diff --git a/openspec/changes/use-statements-library/tasks.md b/openspec/changes/archive/2026-11-10-use-statements-library/tasks.md similarity index 100% rename from openspec/changes/use-statements-library/tasks.md rename to openspec/changes/archive/2026-11-10-use-statements-library/tasks.md diff --git a/openspec/changes/use-statements-library/verification.md b/openspec/changes/archive/2026-11-10-use-statements-library/verification.md similarity index 100% rename from openspec/changes/use-statements-library/verification.md rename to openspec/changes/archive/2026-11-10-use-statements-library/verification.md diff --git a/openspec/specs/image-recognition-gateway/spec.md b/openspec/specs/image-recognition-gateway/spec.md index 7925d11..afbdc6a 100644 --- a/openspec/specs/image-recognition-gateway/spec.md +++ b/openspec/specs/image-recognition-gateway/spec.md @@ -98,7 +98,7 @@ SHALL хватать `OPENBAO__MINIO__*`, `OPENBAO__NATS__NATS_URL`, `SCAN_HOTFO успешным, если `error.code == ""` (поле `error` реального успешного ответа — `{"code": "", "message": ""}`, а не `null`; отсутствующий или `null` `error` ошибкой не считается), и ошибочным, если `error.code` непуст, `parse_error` не `null` либо корень ответа файла не является объектом JSON (ошибка этого файла — `INVALID_FORMAT`); успех MUST NOT определяться -проверкой «`error` не `null`». Конверт (включая ack `processing`) и файл результата несут `schema_version = "2.0"`, `result_schema_version = "2.0"`; `service.statements_version` (содержимое `api/arm_statements/VERSION`) SHALL быть в файле результата, в конверте `service` — `{name, version}`. Если ошибочны **все** файлы — `status = error`, `error.code` и `error.message` берутся у +проверкой «`error` не `null`». Конверт (включая ack `processing`) и файл результата несут `schema_version = "2.0"`, `result_schema_version = "2.0"`; `service.statements_version` (версия установленной библиотеки, полученная через `statements_version()`) SHALL быть в файле результата, в конверте `service` — `{name, version}`. Если ошибочны **все** файлы — `status = error`, `error.code` и `error.message` берутся у первого ошибочного файла по имени (для `parse_error` и не-объекта — `INVALID_FORMAT`); если код ИС этого файла не входит в перечень кодов контракта, `error.code = UNKNOWN_ERROR`, а исходный код SHALL быть указан в `error.message`. Если есть хотя бы один успешный файл — документы собираются из успешных файлов по требованию «Нормализация ответа ИС в `statementDocument`», а статус определяется по требованию «Статус результата нормализации». `metrics.warnings` SHALL быть списком структур предупреждений (`code`, `severity`, `message`, `locator`, …) по схеме v2.0 — одним и тем же в конверте и в файле результата (предел 100 структур, `message` до 1000 символов; при переполнении остаются все `error`, затем `info`). Каждый ошибочный json набора SHALL давать предупреждение `JSON_FAILED` (`error`, `locator = file:<имя json>`, `message = "<код ИС>: <сообщение ИС>"`; для `parse_error` и не-объекта — код `INVALID_FORMAT`), в том числе когда ошибочны все json; исключение при разборе таблицы — `TABLE_PARSE_ERROR` (`error`, `locator = file:<имя json>;table:`, `message = "BACKEND_PARSING_ERROR: <тип>: <текст>"`). Успешный json без `data_ocr`, без `table_structure` или с пустым `table_structure` SHALL давать `JSON_FAILED`. Если предупреждений нет, поле `warnings` SHALL быть опущено. @@ -143,6 +143,10 @@ SHALL хватать `OPENBAO__MINIO__*`, `OPENBAO__NATS__NATS_URL`, `SCAN_HOTFO - **WHEN** успешный json не содержит `data_ocr.table_structure` или `table_structure: []` - **THEN** этот json даёт `JSON_FAILED`; если успешных json с таблицами нет — конверт `status = error` +#### Scenario: Версия установленной библиотеки в результате +- **WHEN** установлен пакет `pylib_ai_arm_statements==0.1.1` и успешно нормализуется `aep_6.json` +- **THEN** файл результата содержит `service.statements_version = "0.1.1"`, конверт содержит только `service.name` и `service.version`; чтение локальной копии `api/arm_statements/VERSION` не требуется + ### Requirement: Таймаут ожидания ответа Если готовый набор ответа (см. «Передача файла…») не появился до дедлайна, сервис SHALL опубликовать конверт `status = error`, `error.code = TIMEOUT`; повторная отправка файла в ИС MUST NOT выполняться. @@ -231,7 +235,7 @@ SHALL отражать состояние. - **THEN** опубликован `TIMEOUT`, входящий файл и каталог `Output//` (если есть) не тронуты ### Requirement: Нормализация ответа ИС в `statementDocument` -Шлюз SHALL оставить у себя обмен с ИС Сканирование (горячие папки, опрос, срок, склейку набора json в `raw/{job_id}.json` до нормализации, очистку папки) и адаптер «таблица `table_structure[i]` json → `FormCandidate`»; шаги 2–8 SHALL выполняться копией пакета `arm_statements` (`api/arm_statements/`, правке не подлежит). Адаптер SHALL: создавать кандидата на каждую таблицу каждого успешного json; копировать шапку json (`organization`, `inn`, `units`, `date`) в шапку каждой своей таблицы как `Cell` (`text = form_name`, `confidence` ИС); передавать `label.form_name` таблицы как заголовок в `evidence` (тип — общим правилом шага 1 по словарю заголовков пакета, `type_hint(okud_text, title_text, rules)`, у OCR `okud_text = None`); строить плотную сетку по `row_index` и `column_index` ИС (пустые позиции — `null`), `Cell.ref = row:`; `locator = file:<имя json>;table:`. `value` у OCR-ячеек всегда `null`, `source = OCR`. Нарушенная структура таблицы ИС SHALL давать исключение адаптера и `TABLE_PARSE_ERROR` всей таблицы, остальные таблицы разбираются. Статус конверта SHALL определяться только `envelope_status` пакета; перевод документов и предупреждений в контракт — только `to_contract` / `warning_to_contract` пакета. +Шлюз SHALL оставить у себя обмен с ИС Сканирование (горячие папки, опрос, срок, склейку набора json в `raw/{job_id}.json` до нормализации, очистку папки) и адаптер «таблица `table_structure[i]` json → `FormCandidate`»; шаги 2–8 SHALL выполняться установленной библиотекой `arm_statements` (дистрибутив `pylib_ai_arm_statements`). Адаптер SHALL: создавать кандидата на каждую таблицу каждого успешного json; копировать шапку json (`organization`, `inn`, `units`, `date`) в шапку каждой своей таблицы как `Cell` (`text = form_name`, `confidence` ИС); передавать `label.form_name` таблицы как заголовок в `evidence` (тип — общим правилом шага 1 по словарю заголовков пакета, `type_hint(okud_text, title_text, rules)`, у OCR `okud_text = None`); строить плотную сетку по `row_index` и `column_index` ИС (пустые позиции — `null`), `Cell.ref = row:`; `locator = file:<имя json>;table:`. `value` у OCR-ячеек всегда `null`, `source = OCR`. Нарушенная структура таблицы ИС SHALL давать исключение адаптера и `TABLE_PARSE_ERROR` всей таблицы, остальные таблицы разбираются. Статус конверта SHALL определяться только `envelope_status` пакета; перевод документов и предупреждений в контракт — только `to_contract` / `warning_to_contract` пакета. #### Scenario: Один json — ББ и ОФР - **WHEN** json `aep_3.json` содержит две таблицы — «Бухгалтерский баланс» и «Отчет о финансовых результатах» — под общей шапкой @@ -333,3 +337,32 @@ SHALL отражать состояние. #### Scenario: Тесты на своих subject'ах - **WHEN** тесты интеграционные (`tests/integration/`) запускаются при работающих контейнерах стенда на том же NATS - **THEN** тесты используют уникальные subject'ы из настроек и не получают и не забирают сообщения стенда + +### Requirement: Локальная установка библиотеки нормализации +Окружение сервиса SHALL устанавливать `pylib_ai_arm_statements` из локального источника без обращения за ним в PyPI. `requirements.txt` SHALL фиксировать версию `0.1.1`; `make venv` SHALL исключать `pylib_ai*` из установки по этому файлу и устанавливать библиотеку из `STATEMENTS_DIR` (по умолчанию `../pylib_ai_arm_statements`). `make install` SHALL переустанавливать её в окружение backend. Сервис MUST NOT содержать копию `api/arm_statements/`. + +#### Scenario: Создание окружения +- **WHEN** выполнен `make venv` при доступной локальной библиотеке версии `0.1.1` +- **THEN** в `api/.venv` установлена библиотека, импорт `arm_statements` работает без копии в проекте, пакет `pylib_ai_arm_statements` не запрашивается в PyPI + +#### Scenario: Переустановка библиотеки из указанного пути +- **WHEN** выполнен `make install STATEMENTS_DIR=<локальный путь>` с доступным исходным пакетом +- **THEN** пакет установлен или переустановлен в `api/.venv`, окружение создано при его отсутствии + +#### Scenario: Локальный источник недоступен +- **WHEN** выполняется `make venv` или `make install`, а `STATEMENTS_DIR` не содержит доступного устанавливаемого пакета +- **THEN** команда завершается ошибкой и не заменяет локальный источник загрузкой библиотеки из PyPI + +### Requirement: Образ содержит установленную библиотеку +`make docker-build` SHALL собирать образ с библиотекой из `STATEMENTS_DIR`, переданной дополнительным контекстом сборки. Образ SHALL поддерживать импорт `arm_statements` и запуск нормализатора без доступа к каталогу исходников библиотеки на хосте во время работы. + +#### Scenario: Автономный образ +- **WHEN** образ собран через `make docker-build` с локальной библиотекой версии `0.1.1` +- **THEN** в образе доступны импорт `arm_statements`, словари и версия `0.1.1`; монтирование исходников библиотеки при запуске не требуется + +### Requirement: Недоступная версия библиотеки препятствует старту +Сервис MUST NOT стартовать, если версия библиотеки нормализации не определена. Ошибка SHALL указывать на установленную библиотеку и способ её установки, без инструкции синхронизации копии исходников. + +#### Scenario: Версия не определена +- **WHEN** при создании нормализатора API библиотеки не возвращает версию и явная версия не задана +- **THEN** создание приложения завершается ошибкой с указанием выполнить `make install`