- копия схем и примеров контракта v2.0 (запрос остаётся v1.0), модели result/common/message/request; ResultWarning/FileWarning, статус и предупреждения у документов, строки forms v2.0, service.statements_version - statements/pipeline.py: кандидаты адаптера -> normalize_candidate -> to_contract / warning_to_contract - BookParser: статус книги через envelope_status, SHEET_OTHER / FILE_TYPE_MISMATCH / XLS_NO_CODEPAGE как предупреждения файла, OSV_* и OSV_XLSB_NO_OUTLINE в документах ОСВ, тексты иерархии по спеке - воркер, app и CLI получают statements_version из require_statements_version() - старые модели документа формы вынесены в statements/legacy_models.py до удаления старого конвейера (задача 9) - OpenSpec: MODIFIED «Парсер — заменяемый интерфейс» в дельте tabular-recognition-service Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BJKMLHdxMwSUeYcdkwqX4W
75 lines
8.2 KiB
Markdown
75 lines
8.2 KiB
Markdown
## MODIFIED Requirements
|
||
|
||
### Requirement: Результат по контракту
|
||
После подтверждения сервис SHALL прочитать объект `s3.key` из бакета `s3.bucket`, вызвать парсер, записать файл результата
|
||
(по `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:<job_id>:<attempt>`. Ack `processing` SHALL нести `schema_version = "2.0"`; запрос
|
||
`recognition-request` остаётся v1.0. `service.statements_version` файла результата SHALL быть коммитом копии пакета
|
||
`arm_statements` (файл `VERSION` копии; сервис без него не стартует) и записываться при наличии `statementDocument`.
|
||
`metrics.warnings` конверта SHALL содержать предупреждения уровня файла структурами `{code, severity, message, locator?}`,
|
||
не строками. Статус конверта — по общему правилу: `done` ⇔ все документы `done` ∧ нет предупреждений файла уровня
|
||
`error`. При неудаче публикации сервис SHALL повторить её до трёх раз с паузой, затем залогировать ошибку.
|
||
|
||
#### Scenario: Запрос с заглушкой парсера (Срез 0)
|
||
- **WHEN** воркеру явно передана заглушка `NullParser` (только тесты воркера; реализация по умолчанию — реальный парсер),
|
||
запрос подтверждён и объект существует в `uploads/`
|
||
- **THEN** в `files.tabular_parsed` появляется конверт `status = error`, `error.code = NOT_IMPLEMENTED`, `job_id`/`file_id`
|
||
из запроса, `metrics.duration_ms ≥ 0`; файл результата может отсутствовать
|
||
|
||
#### Scenario: Запрос с реальным парсером
|
||
- **WHEN** запрос подтверждён и объект в `uploads/` существует, но не является книгой Excel (сигнатура не XLS/XLSX/XLSB)
|
||
- **THEN** в `files.tabular_parsed` появляется конверт `status = error`, `error.code = UNSUPPORTED_FORMAT` (повреждённая
|
||
книга — `UNREADABLE`), `job_id`/`file_id` из запроса, `metrics.duration_ms ≥ 0`; файл результата может отсутствовать;
|
||
корректная ОСВ в том же потоке даёт `done` (см. «Парсер — заменяемый интерфейс»)
|
||
|
||
#### 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`
|
||
|
||
#### Scenario: Предупреждение файла структурой
|
||
- **WHEN** в книге лист «Примечания» без признаков ОСВ и формы
|
||
- **THEN** `metrics.warnings` конверта содержит `{code: "SHEET_OTHER", severity: "info", message, locator: "sheet:Примечания"}`
|
||
|
||
#### Scenario: Повтор публикации
|
||
- **WHEN** первая публикация конверта результата завершается ошибкой соединения, вторая — успешно
|
||
- **THEN** конверт опубликован один раз с тем же `Nats-Msg-Id`, обработка не повторяется
|
||
|
||
### Requirement: Парсер — заменяемый интерфейс
|
||
Разбор файла SHALL выполняться через единый интерфейс «байты + тип файла → документы/метрики/ошибка», не знающий о
|
||
NATS и S3; воркер MUST NOT зависеть от конкретной реализации. Реализация по умолчанию (`get_parser()`) SHALL быть
|
||
оркестратором книги `BookParser`: он читает книгу один раз, классифицирует листы по содержимому и отдаёт листы ОСВ
|
||
конвейеру спеки `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`) и README; словарей форм ББ/ОФР в сервисе нет — они часть копии `arm_statements`.
|
||
|
||
#### Scenario: Подмена реализации в тесте
|
||
- **WHEN** в тесте воркеру передан парсер, возвращающий один `osvDocument`
|
||
- **THEN** файл результата с этим документом записан в S3, конверт имеет `status = done` и `result_s3` на этот файл
|
||
|
||
#### Scenario: Реальная ОСВ через воркер
|
||
- **WHEN** в интеграционном тесте в S3 лежит `rb/2024-FY/ОСВ_66 за 2024.xls` и опубликован запрос `file_type = XLS`
|
||
- **THEN** после ack публикуется конверт `done` с `result_schema_version = "2.0"`, файл результата проходит схему v2.0 и
|
||
содержит один `osvDocument` со `status = done` и `account_code_raw = "66"`
|
||
|
||
#### Scenario: Реальные формы через воркер
|
||
- **WHEN** в интеграционном тесте в S3 лежит книга `statements-xlsx/npz/2025-Q3/…xlsx` и опубликован запрос `file_type = XLSX`
|
||
- **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` равен коммиту копии пакета
|
||
|
||
#### Scenario: Книга без ОСВ и форм
|
||
- **WHEN** в S3 лежит книга Excel, ни один лист которой не имеет признаков ОСВ или формы
|
||
- **THEN** публикуется конверт `error`, `error.code = INVALID_DOCUMENT`, сообщение называет число просмотренных листов
|
||
|
||
#### Scenario: Сервис без версии общего кода не стартует
|
||
- **WHEN** в копии `arm_statements` нет файла `VERSION`
|
||
- **THEN** `require_statements_version()` бросает `RuntimeError` с текстом «копия arm_statements без VERSION», сервис не
|
||
стартует
|