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

7.0 KiB
Raw Blame History

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