- design.md: Р-20 (законные повторы кода в перечне, синонимы в словаре имён), Р-21 (мягкое соотношение, сошедшееся точно, подтверждает неуверенное значение), Р-22 (S-1…S-25 приняты с оговорками; отмечены утверждённые владельцем); решения при доработке S-26…S-52 с ценой ошибки - statement-normalization: отдельное требование sync-statements (пути, VERSION, коды 0 / 10 / 20 / 2), словарь единиц и ОКЕИ, ref Excel с кавычками, нумерация с двойниками и пропуском, номер колонки, содержимое строки, столбец кодов у DISCLOSURE, упрощённый 2400 мягкий, Р-20 / Р-21 со сценариями, обе редакции перечня, интерфейс функции статуса, TABLE_PARSE_ERROR / SHEET_OTHER уровня файла - arm-core: признак строки правки row_index IS NULL, header_source, формы без checks (1.x и миграция) — §7 ядром в document_issue, codes / column_index, RESULT_STATUS_INCONSISTENT, синонимы и «итого по разделу», PATCH organization_name / confirmed: false / подтверждение даты, LINE_CODE_TAKEN, акцепт и подтверждения, LINES_UNRESOLVED с текстом, «того же периода», откат миграции - recognition-contract: MODIFIED «Копия контракта … идентична» — сверка по MAJOR копии, *.v1.schema.json на окне; 1.x — MAPPING_WARNING / LEGACY_WARNING; okei, column_index, пустые массивы DISCLOSURE, статус ОСВ одной формулировкой - quality-checks (новая дельта): make check охватывает копию arm_statements, make check-full — statements-check - tabular-recognition-service: манифест statements_manifest.yaml — failed_relations страниц 2 баланса (K-25) - scan-gateway, arm-web: TABLE_PARSE_ERROR файла; подсказки шапки, снятие подтверждений, LINE_CODE_TAKEN - proposal: шаг 0 выкладки, перечни кодов, ожидания на реальных сканах, Impact Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BJKMLHdxMwSUeYcdkwqX4W
17 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.
Предупреждение уровня файла пишется там, где документа нет (потерянный json, таблица ИС, исключение разбора таблицы,
лист «прочее»).
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?} (line_refs — только ref строк из lines[]);
у osvDocument все предупреждения — info, status = done, когда контрольные суммы §1.4 листа сошлись, иначе
needs_check. Остальное в osvDocument — как в v1.2: у строки rows[] необязательное поле extra — объект «заголовок
колонки → строка или null» для колонок внутри таблицы, не сопоставленных с шестью суммами и ИНН (§1.2 «не терять»); у
шапки — 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, okei, address и для каждого <поле>_text и
<поле>_confidence, плюс needs_review_fields[]; columns[] — column_index (номер столбца в сетке кандидата: у OCR
— column_index ИС, у Excel — номер столбца листа с 0), 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 (у Excel <лист>!<A1>,
имя листа в кавычках по правилу Excel), 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; у документа
без строк columns и lines — пустые массивы. 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). На окне перехода схемы прежнего MAJOR SHALL лежать
в docs/contracts/ рядом с текущими как recognition-result.v1.schema.json и recognition-result-message.v1.schema.json
(байтовые копии 1.2 и 1.0), их примеры — в docs/contracts/examples/v1/; scripts/validate_contracts.py проверяет оба
набора. Они удаляются тем же изменением, что удаляет маппер 1.x ядра.
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, повтор не выполняется (как для любого результата не по схеме)
Requirement: Копия контракта в сервисе идентична источнику
Каждый сервис, читающий или пишущий контракт, SHALL хранить копию файлов схем в api/contracts/ и проверять в make check
их байтовую идентичность файлам docs/contracts/ (../docs или путь ARM_KA_DOCS; при недоступности — пропуск с предупреждением).
На окне перехода между MAJOR тест идентичности SHALL сверять копию со схемой того MAJOR, который сервис пишет (или,
у ядра, читает): копия с $id, оканчивающимся на /v1, — с docs/contracts/<имя>.v1.schema.json, иначе — с основным
файлом docs/contracts/<имя>.schema.json; ядро держит копии обоих MAJOR. Правило теста вводится в tabular-parser и
scan-gateway до слияния контракта v2.0 в docs, иначе их make check упадёт на окне перехода.
Scenario: Копия устарела
- WHEN в
docs/contracts/изменена схема, а копия в сервисе — нет - THEN тест идентичности в
make checkсервиса падает с указанием имени файла
Scenario: Сервис ещё пишет 1.x после слияния v2.0 в docs
- WHEN в docs основной
recognition-result.schema.jsonуже v2.0, а копия вtabular-parser— v1.2 ($id…/v1) - THEN тест идентичности сверяет копию с
recognition-result.v1.schema.jsonи проходит; посторонний PR в сервис на окне перехода не краснеет
Scenario: Сервис перешёл на 2.0
- WHEN
scan-gatewayв PR шага 5 обновил копию до v2.0 ($id…/v2) - THEN тест сверяет её с основным файлом docs
ADDED Requirements
Requirement: Переход на контракт v2.0 — «сначала ядро»
Ядро SHALL до перехода любого сервиса принимать конверты (включая ack processing) и файлы результата и 1.x, и
2.0, с двумя мапперами файла результата. При приёме 1.x статус документа формы SHALL считаться по прежнему правилу
D-6, где «проверки §7 сошлись» — по расчёту ядра копией arm_statements (полный набор), записанному в document_issue,
а поля v2.0 заполняться по умолчанию: line_status = ok, row_index — по порядку строк в результате с 0, ref = null, line_code_original_value = line_code, text = string_value, needs_review значения — по прежнему values_ok
(is_recognized не false и confidence 100 или пуста), needs_review строки — false, period_kind — absolute при
period_date, иначе none, form_variant = null; строки metrics.warnings — в recognition_job.metrics.warnings
структурами {code: "LEGACY_WARNING", severity: "info", message}; предупреждения маппера по документу —
document_issue MAPPING_WARNING (info); statements_version задания — null. Сервисы SHALL переходить на 2.0
только после выкладки ядра, принимающего 2.0. Маппер 1.x и схемы *.v1.schema.json удаляются после перехода обоих
сервисов отдельным изменением.
Scenario: Сервис ещё на v1.2
- WHEN после выкладки ядра tabular-parser присылает результат
1.2с повтором кода иchecks.duplicate_line_codes - THEN ядро принимает его маппером
1.x, обе строки кода — по прежнему правилу «остаётся первая» сMAPPING_WARNING, нарушения §7 — вdocument_issueот ядра, статус документа — по 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) — поэтому порядок «сначала ядро» обязателен