19 KiB
Raw Permalink Blame History

Spec Delta

MODIFIED Requirements

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 = "2.0" — CONTRACT_VERSION сервиса; запрос recognition-request остаётся 1.x, запрос 2.0 → SCHEMA_MISMATCH) в 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». Конверт (включая ack processing) и файл результата несут schema_version = "2.0", result_schema_version = "2.0"; service.statements_version (версия установленной библиотеки, полученная через statements_version()) SHALL быть в файле результата, в конверте service — {name, version}. Если ошибочны все файлы — status = error, error.code и error.message берутся у первого ошибочного файла по имени (для parse_error и не-объекта — INVALID_FORMAT); если код ИС этого файла не входит в перечень кодов контракта, error.code = UNKNOWN_ERROR, а исходный код SHALL быть указан в error.message. Если есть хотя бы один успешный файл — документы собираются из успешных файлов по требованию «Нормализация ответа ИС в statementDocument», а статус определяется по требованию «Статус результата нормализации». metrics.warnings SHALL быть списком структур предупреждений (code, severity, message, locator, …) по схеме v2.0 — одним и тем же в конверте и в файле результата (предел 100 структур, message до 1000 символов; при переполнении остаются все error, затем info). Каждый ошибочный json набора SHALL давать предупреждение JSON_FAILED (error, locator = file:<имя json>, message = "<код ИС>: <сообщение ИС>"; для parse_error и не-объекта — код INVALID_FORMAT), в том числе когда ошибочны все json; исключение при разборе таблицы — TABLE_PARSE_ERROR (error, locator = file:<имя json>;table:<i>, message = "BACKEND_PARSING_ERROR: <тип>: <текст>"). Успешный json без data_ocr, без table_structure или с пустым table_structure SHALL давать JSON_FAILED. Если предупреждений нет, поле 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 содержит одну структуру JSON_FAILED (error, locator = file:<job_id>.json, message = 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 содержит JSON_FAILED (error, locator = file:<job_id>_ofr.json, message = INVALID_DOCUMENT: <сообщение>) и предупреждения пакета; ядро не автоподтверждает документы файла

Scenario: Все документы ошибочны

  • WHEN оба файла ответа ошибочны (первый по имени — INVALID_DOCUMENT, второй — не JSON)
  • THEN конверт status = error, error.code = INVALID_DOCUMENT (код первого ошибочного файла по имени), metrics.warnings содержит два JSON_FAILED (по одному на json); у файла не JSON сообщение начинается с INVALID_FORMAT

Scenario: Все файлы не JSON

  • WHEN единственный файл ответа не разбирается как JSON
  • THEN конверт status = error, error.code = INVALID_FORMAT, metrics.warnings содержит один JSON_FAILED с message = 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, JSON_FAILED в metrics.warnings с message = SOME_NEW_CODE: <сообщение>

Scenario: Все json набора ошибочны

  • WHEN оба json набора ошибочны (первый по имени — INVALID_DOCUMENT)
  • THEN конверт status = error, error.code = INVALID_DOCUMENT (код и сообщение первого ошибочного json), metrics.warnings содержит JSON_FAILED каждого json

Scenario: Успешный json без таблиц

  • WHEN успешный json не содержит data_ocr.table_structure или table_structure: []
  • THEN этот json даёт JSON_FAILED; если успешных json с таблицами нет — конверт status = error

Scenario: Версия установленной библиотеки в результате

  • WHEN установлен пакет pylib_ai_arm_statements==0.1.1 и успешно нормализуется aep_6.json
  • THEN файл результата содержит service.statements_version = "0.1.1", конверт содержит только service.name и service.version; чтение локальной копии api/arm_statements/VERSION не требуется

Requirement: Нормализация ответа ИС в statementDocument

Шлюз SHALL оставить у себя обмен с ИС Сканирование (горячие папки, опрос, срок, склейку набора json в raw/{job_id}.json до нормализации, очистку папки) и адаптер «таблица table_structure[i] json → FormCandidate»; шаги 2–8 SHALL выполняться установленной библиотекой arm_statements (дистрибутив pylib_ai_arm_statements). Адаптер SHALL: создавать кандидата на каждую таблицу каждого успешного json; копировать шапку json (organization, inn, units, date) в шапку каждой своей таблицы как Cell (text = form_name, confidence ИС); передавать label.form_name таблицы как заголовок в evidence (тип — общим правилом шага 1 по словарю заголовков пакета, type_hint(okud_text, title_text, rules), у OCR okud_text = None); строить плотную сетку по row_index и column_index ИС (пустые позиции — null), Cell.ref = row:<row_index>; locator = file:<имя json>;table:<i>. value у OCR-ячеек всегда null, source = OCR. Нарушенная структура таблицы ИС SHALL давать исключение адаптера и TABLE_PARSE_ERROR всей таблицы, остальные таблицы разбираются. Статус конверта SHALL определяться только envelope_status пакета; перевод документов и предупреждений в контракт — только to_contract / warning_to_contract пакета.

Scenario: Один json — ББ и ОФР

  • WHEN json aep_3.json содержит две таблицы — «Бухгалтерский баланс» и «Отчет о финансовых результатах» — под общей шапкой
  • THEN два кандидата с locator file:aep_3.json;table:0 и …;table:1, у каждого копия шапки json

Scenario: Таблица упала при разборе

  • WHEN разбор table:1 json aep_3.json бросил исключение, table:0 разобрана
  • THEN документ BALANCE из table:0; в metrics.warnings — TABLE_PARSE_ERROR уровня error с locator = "file:aep_3.json;table:1"; конверт needs_check

Scenario: Таблица ИС только с текстом

  • WHEN в json есть таблица без столбца кодов и без распознанного типа, в ячейках только текст подписей
  • THEN документа нет, в metrics.warnings — TABLE_SKIPPED уровня info с locator таблицы; конверт из-за неё не needs_check

Scenario: Чистый скан с шапкой (aep_6.json)

  • WHEN нормализуется aep_6.json (две таблицы: «Бухгалтерский баланс» и «Отчет о финансовых результатах»)
  • THEN два кандидата, из них два документа BALANCE и INCOME по шагам 2–8 пакета; поля, колонки и значения — по golden fixtures/ocr/expected_normalize/; header.units = "тыс. руб."; статус определяет envelope_status пакета

Scenario: Зашумлённый скан без шапки (aep_3.json)

  • WHEN нормализуется aep_3.json (таблица «Бухгалтерский баланс» без строки-шапки, коды с мусором)
  • THEN разбор столбца кода, синтез шапки, предупреждения и статус документа — правилами пакета (шаги 2–8), шлюз их не дублирует; header.inn и report_date — по правилам очистки пакета; строки без кода сохраняются со статусом строки

Scenario: Суммы в скобках и с пробелами (aep_3.json, ОФР)

  • WHEN ячейка значения содержит (7744536} с confidence 88.9, другая — (6032409), третья — 13 070
  • THEN разбор сумм и is_recognized — общие правила пакета (шаг 4); шлюз передаёт текст ячейки как есть (value = None), без собственного разбора

Scenario: Нечисловая ячейка значения

  • WHEN ячейка значения содержит не **** с confidence 5.6
  • THEN amount = null, string_value = "не ****", is_recognized = false (правило шага 4 пакета)

Scenario: Повтор кода строки

  • WHEN в таблице баланса две строки с кодом 1150
  • THEN поведение определяет шаг пакета для повтора кода; шлюз ничего не отбрасывает до шагов 2–8, предупреждение приходит от пакета структурой metrics.warnings

Scenario: Тип по кодам при зашумлённом label

  • WHEN label.form_name таблицы — Бvxгалтерский баланс (не сопоставлен словарём заголовков), коды строк 1110…1700
  • THEN тип определяется общим правилом шага 1 пакета (по кодам), предупреждение — структурой пакета

Scenario: Label противоречит единодушным кодам

  • WHEN label.form_name таблицы — Бухгалтерский баланс, все коды строк данных 2110…2910
  • THEN тип и предупреждение о расхождении — по правилам пакета; header.form_name — текст label как распознан; статус needs_check

Scenario: Label противоречит смешанным кодам

  • WHEN label.form_name таблицы — Отчет о финансовых результатах, коды строк данных 1150, 1100 и 2110
  • THEN тип и предупреждение о расхождении — по правилам пакета; статус needs_check

Scenario: Таблица без столбца кода

  • WHEN в таблице нет строки с ячейкой «Код» и ни в одном столбце нет кодов
  • THEN документа нет, в metrics.warnings — TABLE_SKIPPED с locator таблицы; остальные таблицы файла разобраны

ADDED Requirements

Requirement: Локальная установка библиотеки нормализации

Окружение сервиса SHALL устанавливать pylib_ai_arm_statements из локального источника без обращения за ним в PyPI. requirements.txt SHALL фиксировать версию 0.1.1; make venv SHALL исключать pylib_ai* из установки по этому файлу и устанавливать библиотеку из STATEMENTS_DIR (по умолчанию ../pylib_ai_arm_statements). make install SHALL переустанавливать её в окружение backend. Сервис MUST NOT содержать копию api/arm_statements/.

Scenario: Создание окружения

  • WHEN выполнен make venv при доступной локальной библиотеке версии 0.1.1
  • THEN в api/.venv установлена библиотека, импорт arm_statements работает без копии в проекте, пакет pylib_ai_arm_statements не запрашивается в PyPI

Scenario: Переустановка библиотеки из указанного пути

  • WHEN выполнен make install STATEMENTS_DIR=<локальный путь> с доступным исходным пакетом
  • THEN пакет установлен или переустановлен в api/.venv, окружение создано при его отсутствии

Scenario: Локальный источник недоступен

  • WHEN выполняется make venv или make install, а STATEMENTS_DIR не содержит доступного устанавливаемого пакета
  • THEN команда завершается ошибкой и не заменяет локальный источник загрузкой библиотеки из PyPI

Requirement: Образ содержит установленную библиотеку

make docker-build SHALL собирать образ с библиотекой из STATEMENTS_DIR, переданной дополнительным контекстом сборки. Образ SHALL поддерживать импорт arm_statements и запуск нормализатора без доступа к каталогу исходников библиотеки на хосте во время работы.

Scenario: Автономный образ

  • WHEN образ собран через make docker-build с локальной библиотекой версии 0.1.1
  • THEN в образе доступны импорт arm_statements, словари и версия 0.1.1; монтирование исходников библиотеки при запуске не требуется

Requirement: Недоступная версия библиотеки препятствует старту

Сервис MUST NOT стартовать, если версия библиотеки нормализации не определена. Ошибка SHALL указывать на установленную библиотеку и способ её установки, без инструкции синхронизации копии исходников.

Scenario: Версия не определена

  • WHEN при создании нормализатора API библиотеки не возвращает версию и явная версия не задана
  • THEN создание приложения завершается ошибкой с указанием выполнить make install