- proposal: зачем, что меняется, порядок выкладки «сначала ядро», non-goals - statement-normalization: шаги 1–8 общего кода arm_statements (кандидат, раскладка, статусы строк, значения, §7, needs_review, структурная проверка кода, уровни предупреждений, статус) - recognition-contract: файл результата и конверт v2.0, переход 1.x → 2.0 - tabular-recognition-service, scan-gateway: адаптеры шага 1 и что уходит из сервисов - arm-core: хранение v2.0, агрегат по коду, document_issue, RECOGNIZED, PATCH lines/columns/confirmed, порядок 409, миграция v5.19, crm_id ГСК/ГСЗ - arm-web: экран проверки формы по новым полям - design.md: автозамена между файлами — §9.5 п. 3, а не §12 п. 3 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BJKMLHdxMwSUeYcdkwqX4W
12 KiB
MODIFIED Requirements
Requirement: Сообщение результата распознавания
Тело сообщения на subject ответа (настройка окружения NATS_RESULT_SUBJECT, по умолчанию files.tabular_parsed)
SHALL соответствовать docs/contracts/recognition-result-message.schema.json
v2.0: обязательные поля schema_version ("2.0"), job_id, file_id, status (processing | done | needs_check |
error), service {name, version}; при status ∈ {done, needs_check} обязательны result_s3 {bucket, key} и
result_schema_version; при status = error обязателен error {code, message}; опциональны raw_s3_key
(только scan-gateway) и metrics. metrics.warnings[] — структуры warning (как в файле результата), не строки.
Поле group_schema в конверт NOT включается — источник recognition_job.group_schema — файл результата.
Производитель v2.0 SHALL писать в конверт тот же MAJOR, что и в файл результата.
Scenario: Успешный результат
- WHEN сервис завершил обработку файла
- THEN файл результата записан в S3 по ключу
results/{file_id}/{job_id}/result.json, а затем опубликован конверт соstatus = done | needs_check,result_s3указывает на этот файл,result_schema_version = "2.0", заголовокNats-Msg-Id=result:<job_id>:<attempt>
Scenario: Ошибка обработки
- WHEN сервис не смог обработать файл (нечитаем, таймаут внешней системы, тип не поддерживается)
- THEN публикуется конверт со
status = errorиerror.codeиз перечня схемы (UNSUPPORTED_FORMAT,UNREADABLE,TIMEOUT,SCHEMA_MISMATCH,NOT_IMPLEMENTED,FILE_READ_ERROR,INVALID_FORMAT,INVALID_DOCUMENT,RECOGNITION_FAILED,UNKNOWN_ERROR);result_s3может отсутствовать
Scenario: Предупреждения в конверте
- WHEN у файла есть предупреждение уровня файла (ошибочный json набора ИС)
- THEN
metrics.warningsконверта содержит объект{code: "JSON_FAILED", severity: "error", message, locator}
Requirement: Файл результата
Файл результата SHALL соответствовать docs/contracts/recognition-result.schema.json v2.0: один файл (stored_file)
→ один job_id → массив documents из osvDocument и/или statementDocument; денежные значения — строки
^-?\d+(\.\d{1,2})?$; confidence — 0–100. Пустой массив documents допустим только при status = error.
service.statements_version — коммит копии общего кода arm_statements, которым посчитан результат (обязателен при
наличии statementDocument). metrics.warnings[] — предупреждения уровня файла (структура warning с locator).
Каждый документ (и osvDocument, и statementDocument) SHALL иметь status (done | needs_check) и warnings[] —
структуры {code, severity, message, line_refs?, column_index?, field?}; у osvDocument все предупреждения — info,
status = done, когда ведомость разобрана. Остальное в osvDocument — как в v1.2 (extra, period_start,
period_end, is_from_year_start).
statementDocument SHALL иметь form_variant (full | simplified | null); header — поля organization_name,
inn, report_date, units, okud, okpo, okopf, okfs, address и для каждого <поле>_text и
<поле>_confidence, плюс needs_review_fields[]; columns[] — column_index, label, label_confidence,
period_date, period_kind (absolute | relative:current | relative:prev_year_end |
relative:prev_prev_year_end | relative:prev_period | none); lines[] — row_index, ref, line_status (ok |
unreadable | missing | detail), line_code (строка 4–5 цифр или null; null ⇔ статус не ok),
line_code_text, line_code_confidence, line_name (строка или null), line_name_confidence, parent_ref (у
detail), needs_review, values[] — column_index, amount, string_value, text, confidence,
is_recognized, needs_review. checks SHALL присутствовать у BALANCE / INCOME со строками и отсутствовать у
документа без строк; relations_failed и soft_relations_failed — {column_index, relation, codes[], expected, actual}; duplicate_line_codes удалён. document_type — BALANCE | INCOME | DISCLOSURE | OTHER. ref
уникален в документе.
Scenario: Результат валидируется примерами
- WHEN CI сервиса запускает тесты контракта
- THEN все файлы
docs/contracts/examples/result-*.jsonпроходят схему файла результата v2.0, а всеrequest-*.json/result-message-*.json— схемы сообщений
Scenario: Лишняя колонка сохранена
- WHEN в ОСВ между ИНН и суммами есть колонка «КПП»
- THEN у строк контрагентов
extra = {"КПП": "<значение>"}, файл проходит схему v2.0
Scenario: Форма из Excel с проверками
- WHEN tabular-parser разобрал книгу с балансом на двух листах, ОФР со строкой без кода под 2110 и пояснениями
(
examples/result-statement-excel.json) - THEN файл проходит схему v2.0:
BALANCEсlocatorиз двух листов,status,checksи мягким нарушением;INCOMEсо строкойdetail(line_code = null,parent_ref);DISCLOSUREбезcolumns/linesи безchecks
Scenario: Форма из OCR с сомнительными значениями
- WHEN scan-gateway разобрал ББ, где одна сумма собрана из грязного текста и покрыта соотношением, а одна строка
unreadable(examples/result-statement-minimal.json) - THEN файл проходит схему v2.0; у покрытого значения
is_recognized = false,needs_review = false,text— сырой; у строкиunreadableline_code = null,line_code_text— сырой текст; документneeds_check
Scenario: Границы периода ОСВ
- WHEN в шапке ОСВ период «Октябрь 2024 г. – Сентябрь 2025 г.»
- THEN
period_start = "2024-10-01",period_end = "2025-09-30",is_from_year_start = false; документ ОСВ сstatusиwarnings, файл проходит схему v2.0
Scenario: Строка без кода с кодом в поле
- WHEN у строки
line_status = unreadableпередан непустойline_code - THEN файл не проходит схему v2.0
Requirement: Версионирование контракта
Поле schema_version SHALL быть строкой MAJOR.MINOR. Добавление необязательных полей или значений enum —
MINOR (обратно совместимо); удаление/переименование полей, смена типов или семантики — MAJOR.
Сервис SHALL отвергать запрос с неизвестным MAJOR (error.code = SCHEMA_MISMATCH); ядро SHALL принимать результат
с известным MAJOR и любым известным или новым MINOR. Запрос recognition-request остаётся v1.0; файл результата и
конверт — v2.0 (MAJOR поднят: line_code стал nullable, metrics.warnings и document.warnings — структуры, удалён
checks.duplicate_line_codes, изменён смысл is_recognized у OCR).
Scenario: Несовместимая версия запроса
- WHEN сервис, поддерживающий запрос
1.x, получает запросschema_version = "2.0" - THEN публикуется результат
status = error,error.code = SCHEMA_MISMATCHбез ackprocessing: ответом на запрос служит сам финальный конверт, ядро закрывает им задание (published → error), и повторной доставки запроса не будет. Конверт публикуется только если из тела извлекаются корректныеjob_idиfile_id; иначе — как для нечитаемого запроса (ответа нет, ядро закроет задание поNO_ACK/dlq).attemptдляNats-Msg-Idберётся из тела запроса; при его отсутствии реализации расходятся (tabular-parserберётattemptиз заголовкаNats-Msg-Idзапроса или не отвечает,scan-gatewayподставляет 1) — см. OQ-71
Scenario: Ядро получает MAJOR, которого не знает
- WHEN ядро, знающее
1.xи2.x, получает конверт или файл результатаschema_version = "3.0" - THEN результат не проходит проверку схемы, файл получает
ERRORс причиной вmetrics.mapping_error, повтор не выполняется (как для любого результата не по схеме)
ADDED Requirements
Requirement: Переход на контракт v2.0 — «сначала ядро»
Ядро SHALL до перехода любого сервиса принимать конверты (включая ack processing) и файлы результата и 1.x, и
2.0, с двумя мапперами файла результата. При приёме 1.x статус документа формы SHALL считаться по прежнему правилу
D-6, а поля v2.0 заполняться по умолчанию: line_status = ok, row_index — по порядку строк в результате, ref = null, text = string_value, needs_review значения — по прежнему values_ok (is_recognized не false и
confidence 100 или пуста), needs_review строки — false, period_kind — absolute при period_date, иначе none,
form_variant = null, предупреждения-строки — warning {code: "LEGACY_WARNING", severity: "info", message}.
Сервисы SHALL переходить на 2.0 только после выкладки ядра, принимающего 2.0. Маппер 1.x удаляется после
перехода обоих сервисов отдельным изменением.
Scenario: Сервис ещё на v1.2
- WHEN после выкладки ядра tabular-parser присылает результат
1.2с повтором кода иchecks.duplicate_line_codes - THEN ядро принимает его маппером
1.x, статус документа — по D-6, файл неERROR
Scenario: Ack нового сервиса
- WHEN scan-gateway после перехода публикует ack
processingсоschema_version = "2.0" - THEN ядро переводит задание в
processing, как для ack1.0
Scenario: Порядок нарушен
- WHEN сервис прислал результат
2.0сline_code = nullядру, знающему только1.x - THEN результат не проходит проверку схемы, файл получает
ERROR(metrics.mapping_error) — поэтому порядок «сначала ядро» обязателен