Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BJKMLHdxMwSUeYcdkwqX4W
18 KiB
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 два кандидата с
locatorfile:aep_3.json;table:0и…;table:1, у каждого копия шапки json
Scenario: Таблица упала при разборе
- WHEN разбор
table:1jsonaep_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 пакета; поля, колонки и значения — по goldenfixtures/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.