## 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::`. 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`, обработка не повторяется