Решение владельца 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
7.0 KiB
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: <сообщение>