18 KiB
Raw Blame History

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 (содержимое api/arm_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

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

Шлюз SHALL оставить у себя обмен с ИС Сканирование (горячие папки, опрос, срок, склейку набора json в raw/{job_id}.json до нормализации, очистку папки) и адаптер «таблица table_structure[i] json → FormCandidate»; шаги 2–8 SHALL выполняться копией пакета arm_statements (api/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 таблицы; остальные таблицы файла разобраны

Requirement: Статус результата нормализации

Статус конверта SHALL определяться функцией envelope_status пакета arm_statements по документам и предупреждениям. Если из успешных файлов не собран ни один документ — status = error, error.code = RECOGNITION_FAILED, error.message = "ни одной таблицы не разобрано", причины по таблицам — в metrics.warnings (TABLE_PARSE_ERROR, TABLE_SKIPPED, TABLE_LOST); если все json ошибочны — код и сообщение первого ошибочного json. Если документы есть — done или needs_check по envelope_status; потерянный json набора (JSON_FAILED) или таблица (TABLE_PARSE_ERROR, TABLE_LOST) исключают done. metrics.rows — число строк lines всех документов, включая строки без кода.

Scenario: Ни одной таблицы

  • WHEN единственный успешный файл содержит table_structure: []
  • THEN json даёт JSON_FAILED, конверт status = error, raw_s3_key заполнен

Scenario: Все таблицы упали при разборе

  • WHEN в успешном json разбор каждой таблицы бросил исключение
  • THEN конверт status = error, error.code = RECOGNITION_FAILED, error.message = "ни одной таблицы не разобрано", metrics.warnings содержит TABLE_PARSE_ERROR каждой таблицы

Scenario: Потерянный документ набора

  • WHEN <job_id>.json успешен, <job_id>_ofr.json ошибочен
  • THEN статус не done, а needs_check

Scenario: Всё распознано

  • WHEN синтетический ответ, где все ячейки распознаны уверенно, шапка и даты разобраны, предупреждений нет
  • THEN status = done, поле metrics.warnings отсутствует

Scenario: Исключение в одной таблице

  • WHEN разбор table_structure[1] бросает исключение, table_structure[0] разбирается
  • THEN один документ, TABLE_PARSE_ERROR (error, locator = file:<имя json>;table:1, message = BACKEND_PARSING_ERROR: <класс>: <сообщение>), статус needs_check

REMOVED Requirements

Requirement: Справочники нормализации — данные с редакциями форм

Reason: справочник строк (statement_lines.yaml) и выбор редакции удалены; двойники (ocr_lookalikes.yaml) и заголовки форм (statement_labels.yaml) — словари пакета arm_statements (default_rules()). Migration: api/rules/ и настройка SCAN_RULES_DIR удалены; словари меняются через репозиторий arm-statements и sync-statements.