Решение владельца 08.10.2026: recognition.tabular.request → files.tabular_uploaded, recognition.image.request → files.img_uploaded, recognition.result → files.tabular_parsed (общий subject ack и результатов; files.img_parsed не используется). Тела сообщений, Nats-Msg-Id, queue group'ы и schema_version не меняются. Константы api/src/contract/subjects.py, докстринги, тесты, README, CLAUDE.md; копии схем api/contracts/ из docs/contracts/ побайтно; change nats-files-subjects с дельтой спеки. Сквозной change — RSHB/arm-ka-docs#74. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
64 lines
7.0 KiB
Markdown
64 lines
7.0 KiB
Markdown
## MODIFIED Requirements
|
||
|
||
### Requirement: Подтверждение запроса до обработки
|
||
Сервис SHALL быть подписан на `files.img_uploaded` в queue group `scan-gateway`. Получив запрос, проходящий схему,
|
||
сервис SHALL до чтения файла из S3 опубликовать в `files.tabular_parsed` конверт `status = processing`
|
||
(`service.name = "scan-gateway"`, `Nats-Msg-Id = ack:<job_id>:<attempt>`). Повторный запрос с `job_id`, который уже
|
||
обрабатывается или обработан этим процессом, SHALL получить повторный ack, и второй файл во входящий каталог MUST NOT записываться.
|
||
|
||
#### Scenario: Ack раньше файла
|
||
- **WHEN** получен запрос для `aep_5.pdf`
|
||
- **THEN** конверт `processing` опубликован раньше, чем файл появился во входящем каталоге
|
||
|
||
#### Scenario: Повторный запрос
|
||
- **WHEN** тот же запрос доставлен повторно во время ожидания ответа ИС
|
||
- **THEN** публикуется ещё один ack, во входящем каталоге остаётся один файл `<job_id>.pdf`
|
||
|
||
### Requirement: Результат по контракту
|
||
Сервис SHALL публиковать в `files.tabular_parsed` конверт по `recognition-result-message.schema.json` с
|
||
`service.name = "scan-gateway"`, `Nats-Msg-Id = result:<job_id>:<attempt>`; при `status ∈ {done, needs_check}` — файл
|
||
результата (`recognition-result.schema.json`, `schema_version = "1.0"` — `CONTRACT_VERSION` сервиса, решение №14; схема допускает любой MINOR) в `results/{file_id}/{job_id}/result.json` и `result_s3`;
|
||
при неудаче публикации SHALL повторить её до трёх раз. Нормализатор получает склеенный ответ и SHALL считать файл
|
||
успешным, если `error.code == ""` (поле `error` реального успешного ответа — `{"code": "", "message": ""}`, а не `null`;
|
||
отсутствующий или `null` `error` ошибкой не считается), и ошибочным, если `error.code` непуст, `parse_error` не `null`
|
||
либо корень ответа файла не является объектом JSON (ошибка этого файла — `INVALID_FORMAT`); успех MUST NOT определяться
|
||
проверкой «`error` не `null`». Если ошибочны **все** файлы — `status = error`, `error.code` и `error.message` берутся у
|
||
первого ошибочного файла по имени (для `parse_error` и не-объекта — `INVALID_FORMAT`); если код ИС этого файла не
|
||
входит в перечень кодов контракта, `error.code = UNKNOWN_ERROR`, а исходный код SHALL быть указан в `error.message`.
|
||
Если есть хотя бы один успешный файл — документы собираются из успешных файлов по требованию «Нормализация ответа ИС»,
|
||
а статус определяется по требованию «Статус результата нормализации». В обоих случаях `metrics.warnings` SHALL
|
||
содержать по одной строке `<имя файла>: <код>: <сообщение>` на каждый ошибочный файл, где `<код>` — исходный код ИС как
|
||
получен (для `parse_error` и не-объекта — `INVALID_FORMAT`); если предупреждений нет, поле `warnings` SHALL быть опущено.
|
||
|
||
#### Scenario: Успешный ответ ИС в Срезе 0
|
||
- **WHEN** ответ из одного файла с `error = {"code": "", "message": ""}` и непустым `data_ocr` (по образцу `aep_6.json`)
|
||
- **THEN** начиная со Среза 3a конверт больше не `NOT_IMPLEMENTED`: поведение — по сценарию «Успешный ответ ИС» ниже
|
||
|
||
#### Scenario: Успешный ответ ИС
|
||
- **WHEN** ответ из одного файла с `error = {"code": "", "message": ""}` и непустым `data_ocr` (`aep_6.json`)
|
||
- **THEN** конверт `status = needs_check`, `raw_s3_key = raw/<job_id>.json`, `result_s3` указывает на файл результата с двумя документами (`BALANCE`, `INCOME`)
|
||
|
||
#### Scenario: Ошибка ИС
|
||
- **WHEN** единственный файл ответа `<job_id>.json` содержит `error.code = INVALID_DOCUMENT`
|
||
- **THEN** конверт `status = error`, `error.code = INVALID_DOCUMENT`, `error.message` из ответа, `raw_s3_key` заполнен, `metrics.warnings` содержит одну строку `<job_id>.json: INVALID_DOCUMENT: <сообщение>`
|
||
|
||
#### Scenario: Ошибка одного из документов
|
||
- **WHEN** ответ состоит из `<job_id>.json` (успех, `aep_3.json`) и `<job_id>_ofr.json` (`INVALID_DOCUMENT`, `aep_3_ofr.json`)
|
||
- **THEN** конверт `status = needs_check`, документы собраны из `<job_id>.json`, `metrics.warnings` содержит строку `<job_id>_ofr.json: INVALID_DOCUMENT: <сообщение>` и предупреждения нормализации
|
||
|
||
#### Scenario: Все документы ошибочны
|
||
- **WHEN** оба файла ответа ошибочны (первый по имени — `INVALID_DOCUMENT`, второй — не JSON)
|
||
- **THEN** конверт `status = error`, `error.code = INVALID_DOCUMENT` (код первого ошибочного файла по имени), `metrics.warnings` содержит две строки; файл не JSON в warnings несёт код `INVALID_FORMAT`
|
||
|
||
#### Scenario: Все файлы не JSON
|
||
- **WHEN** единственный файл ответа не разбирается как JSON
|
||
- **THEN** конверт `status = error`, `error.code = INVALID_FORMAT`, `metrics.warnings` содержит одну строку `<job_id>.json: INVALID_FORMAT: <сообщение разбора>`
|
||
|
||
#### Scenario: Корень ответа не объект
|
||
- **WHEN** файл ответа — корректный JSON, но корень — массив
|
||
- **THEN** этот файл ошибочен с кодом `INVALID_FORMAT`; остальные файлы оцениваются по общим правилам
|
||
|
||
#### Scenario: Код ошибки ИС вне перечня контракта
|
||
- **WHEN** единственный файл ответа содержит `error.code = SOME_NEW_CODE`
|
||
- **THEN** конверт `status = error`, `error.code = UNKNOWN_ERROR`, `error.message` содержит `SOME_NEW_CODE`, строка в `metrics.warnings` — `<job_id>.json: SOME_NEW_CODE: <сообщение>`
|