Andrey Epifancev 6c287ef4bc nats: subject'ы как в старом АРМ — files.*_uploaded, files.tabular_parsed (#22)
Решение владельца 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
2026-10-08 23:46:19 +04:00

64 lines
7.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

## 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: <сообщение>`