contracts: результат и конверт v2.0 — статусы строк, needs_review, структурные предупреждения (#116)

- recognition-result.schema.json 2.0: line_code nullable + line_status, row_index, ref, parent_ref,
  line_code_text, needs_review у значения и строки, values[].text, <поле>_text/_confidence шапки,
  needs_review_fields, period_kind, label_confidence, document.status/warnings у всех документов
  (включая ОСВ), form_variant, checks обязателен у ББ/ОФР со строками, нарушения с codes, без
  duplicate_line_codes, service.statements_version; changelog — в $comment
- recognition-result-message.schema.json 2.0: metrics.warnings — структуры
- примеры переведены на 2.0 (Excel: detail-строка и нарушение пустой цели; OCR: догадка, покрытая §7,
  строка unreadable, разлепленная строка)
- 03-architecture: общий код arm-statements, контракт v2.0
- спека: у разлепленных строк line_code_text и text — k-е части

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BJKMLHdxMwSUeYcdkwqX4W
This commit is contained in:
Andrey Epifancev 2026-10-18 00:44:15 +04:00
parent 90ea429642
commit 9b1a4ccbfd
12 changed files with 367 additions and 150 deletions

View File

@ -31,8 +31,9 @@
лимиты, чтение, дедлайн, вид каждого листа): листы ОСВ идут в конвейер `osv.py`, листы форм — в конвейер форм
`statements/` (блоки форм, колонки, шапка, строки, соотношения §7, склейка продолжений); документы результата — сначала
ОСВ, затем формы. Контракт результата —
`docs/contracts/recognition-result.schema.json` (контракт v1.2: `DISCLOSURE` в `document_type`, `statementDocument.checks`,
границы периода ОСВ `period_start/period_end/is_from_year_start`). Правила распознавания
`docs/contracts/recognition-result.schema.json` (контракт v2.0 — статусы строк, `needs_review`, `document.status` и
структурные предупреждения, change `unified-statement-normalization`; до перехода сервис пишет v1.2: `DISCLOSURE` в
`document_type`, `statementDocument.checks`, границы периода ОСВ `period_start/period_end/is_from_year_start`). Правила распознавания
заголовков/группировок — `rules/*.yaml`.
- **scan-gateway** — адаптер ИС Сканирование: положить файл в горячую папку `Input/<job_id>.<ext>` (расширение —
суффикс `original_name` в нижнем регистре; файл пишется сразу в `Input` с перезаписью существующего, без временного
@ -163,6 +164,18 @@ advisory-lock по `(ЮЛ, sha256)` (двойной клик не проскак
- **Нагрузка и транзакции.** Переход версии после загрузки выполняется в отдельной короткой транзакции
(`session_transaction`), сбой не отменяет принятые файлы (R-10). Чтение (`GET …/osv`, сводка, экспорт) блокировок не берёт.
### Общий код нормализации ББ/ОФР — `arm-statements` (change `unified-statement-normalization`, целевое)
Шаги 2–8 нормализации форм (раскладка, статусы строк, значения, код и наименование, сборка документа, проверки §7,
`needs_review`, статус и предупреждения) и их словари живут в отдельном репозитории `arm-statements` пакетом
`arm_statements/` (Р-1: платформа не собирает Python-пакеты). В tabular-parser, scan-gateway и arm-core пакет лежит
**копией** с тестами, импортируется по имени `arm_statements`; копию раскладывает и проверяет `sync-statements` в docs
(`--check`, код 20 при прямой правке; вызывается из `make check-full`, не из `make check`). Сервисы держат только адаптер
шага 1 (json ИС / лист Excel → `FormCandidate`); ядро считает тем же кодом после правок аналитика и не держит своих
копий словаря §7. Словарь «код → наименование» — только в ядре. Правила — `05-domain-rules.md` §11; маркер версии
копии — `service.statements_version` результата → `recognition_job.statements_version`. Порядок выкладки — «сначала
ядро» (ядро принимает 1.x и 2.0). Пункты ниже описывают поведение до перехода (Срез 3b).
### Формы ББ/ОФР, проверки §7 и API экрана проверки (Срез 3b)
Формы (`statementDocument` контракта v1.2) приходят от обоих сервисов распознавания в одном формате; ядро — единственное

View File

@ -1,8 +1,11 @@
{
"schema_version": "1.0",
"schema_version": "2.0",
"job_id": "3f2a9c1e-7b4d-4e0a-9d51-2c8e6a1b0f01",
"file_id": "a1c4e7d2-5b36-4f89-8e12-9d0c3b7a4e02",
"service": { "name": "tabular-parser", "version": "0.1.0" },
"service": {
"name": "tabular-parser",
"version": "0.1.0"
},
"status": "error",
"error": {
"code": "NOT_IMPLEMENTED",

View File

@ -1,14 +1,17 @@
{
"schema_version": "1.0",
"schema_version": "2.0",
"job_id": "8d6b1f30-2e94-4a7c-b5d8-0f1e9c3a6b03",
"file_id": "c7e2a9b4-1d58-4630-a2f7-6b4d8e0c1f04",
"status": "done",
"service": { "name": "scan-gateway", "version": "0.1.0" },
"service": {
"name": "scan-gateway",
"version": "0.1.0"
},
"result_s3": {
"bucket": "arm-ka",
"key": "results/c7e2a9b4-1d58-4630-a2f7-6b4d8e0c1f04/8d6b1f30-2e94-4a7c-b5d8-0f1e9c3a6b03/result.json"
},
"result_schema_version": "1.0",
"result_schema_version": "2.0",
"raw_s3_key": "raw/8d6b1f30-2e94-4a7c-b5d8-0f1e9c3a6b03.json",
"metrics": {
"duration_ms": 18400,

View File

@ -1,9 +1,12 @@
{
"schema_version": "1.0",
"schema_version": "2.0",
"job_id": "3f2a9c1e-7b4d-4e0a-9d51-2c8e6a1b0f01",
"file_id": "a1c4e7d2-5b36-4f89-8e12-9d0c3b7a4e02",
"status": "error",
"service": { "name": "tabular-parser", "version": "0.1.0" },
"service": {
"name": "tabular-parser",
"version": "0.1.0"
},
"error": {
"code": "NOT_IMPLEMENTED",
"message": "Разбор ОСВ пока не реализован / OSV parsing is not implemented yet"

View File

@ -1,7 +1,10 @@
{
"schema_version": "1.0",
"schema_version": "2.0",
"job_id": "3f2a9c1e-7b4d-4e0a-9d51-2c8e6a1b0f01",
"file_id": "a1c4e7d2-5b36-4f89-8e12-9d0c3b7a4e02",
"status": "processing",
"service": { "name": "tabular-parser", "version": "0.1.0" }
"service": {
"name": "tabular-parser",
"version": "0.1.0"
}
}

View File

@ -1,8 +1,11 @@
{
"schema_version": "1.2",
"schema_version": "2.0",
"job_id": "3f2a9c1e-7b4d-4e0a-9d51-2c8e6a1b0f01",
"file_id": "a1c4e7d2-5b36-4f89-8e12-9d0c3b7a4e02",
"service": { "name": "tabular-parser", "version": "0.1.0" },
"service": {
"name": "tabular-parser",
"version": "0.1.0"
},
"status": "done",
"documents": [
{
@ -24,7 +27,11 @@
"header_variant": 1
},
"group_schema": [
{ "type": "COUNTERPARTY", "name": "Контрагенты", "has_inn": true }
{
"type": "COUNTERPARTY",
"name": "Контрагенты",
"has_inn": true
}
],
"columns": {
"opening_debit": "C",
@ -70,14 +77,18 @@
"turnover_credit": "200.50",
"closing_debit": "0.00",
"closing_credit": "700.50",
"extra": { "КПП": "352801001" }
"extra": {
"КПП": "352801001"
}
}
],
"checks": {
"total_row_matches_root": true,
"balance_identity_violations": [],
"parent_sum_violations": []
}
},
"status": "done",
"warnings": []
}
]
}

View File

@ -1,15 +1,15 @@
{
"schema_version": "1.1",
"schema_version": "2.0",
"job_id": "3f2a9c1e-7b4d-4e0a-9d51-2c8e6a1b0f01",
"file_id": "a1c4e7d2-5b36-4f89-8e12-9d0c3b7a4e02",
"service": { "name": "tabular-parser", "version": "1.0.0" },
"service": {
"name": "tabular-parser",
"version": "1.0.0"
},
"status": "needs_check",
"metrics": {
"rows": 3,
"warnings": [
"sheet=Лист_1 hierarchy=outline",
"§1.4: сальдо на начало + обороты ≠ сальдо на конец (±0.01): строки 3"
]
"warnings": []
},
"documents": [
{
@ -28,8 +28,15 @@
"header_variant": 1
},
"group_schema": [
{ "type": "ACCOUNT", "name": "Счет" },
{ "type": "COUNTERPARTY", "name": "Контрагенты", "has_inn": true }
{
"type": "ACCOUNT",
"name": "Счет"
},
{
"type": "COUNTERPARTY",
"name": "Контрагенты",
"has_inn": true
}
],
"columns": {
"opening_debit": "D",
@ -76,7 +83,9 @@
"turnover_credit": "200.50",
"closing_debit": "0.00",
"closing_credit": "700.50",
"extra": { "КПП": "352801001" }
"extra": {
"КПП": "352801001"
}
},
{
"row_number": 3,
@ -117,9 +126,24 @@
],
"checks": {
"total_row_matches_root": true,
"balance_identity_violations": [3],
"balance_identity_violations": [
3
],
"parent_sum_violations": []
}
},
"status": "needs_check",
"warnings": [
{
"code": "OSV_HIERARCHY_BY_OUTLINE",
"severity": "info",
"message": "иерархия строк — по группировке листа (outline)"
},
{
"code": "OSV_BALANCE_IDENTITY",
"severity": "info",
"message": "§1.4: сальдо на начало + обороты ≠ сальдо на конец (±0.01): строки 3"
}
]
}
]
}

View File

@ -1,44 +1,98 @@
{
"schema_version": "1.2",
"schema_version": "2.0",
"job_id": "3f1c7a2e-5b64-4d0a-9c1e-8a7b6d5e4f30",
"file_id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
"service": { "name": "tabular-parser", "version": "0.3.0" },
"status": "done",
"metrics": { "duration_ms": 412, "rows": 7, "warnings": ["sheet=Бухгалтерский баланс. Лист 1 значений с дробной частью при единице тыс. руб.: 3"] },
"service": { "name": "tabular-parser", "version": "0.4.0", "statements_version": "abc1234" },
"status": "needs_check",
"metrics": { "duration_ms": 412, "rows": 12, "warnings": [] },
"documents": [
{
"document_type": "BALANCE",
"locator": "sheet:Бухгалтерский баланс. Лист 1,Бухгалтерский баланс. Лист 2",
"header": { "form_name": "Бухгалтерский баланс", "organization_name": "ООО \"Пример\"", "organization_name_confidence": 100, "inn": "7700000000", "inn_confidence": 100, "report_date_text": "на 30 сентября 2025 г.", "report_date": "2025-09-30", "units": "тыс. руб.", "okud": "0710001", "okpo": "12345678" },
"status": "done",
"form_variant": "full",
"warnings": [
{ "code": "FRACTIONAL_IN_THOUSANDS", "severity": "info", "message": "значений с дробной частью при единице тыс. руб.: 6 из 14" }
],
"header": {
"form_name": "Бухгалтерский баланс",
"organization_name": "ООО \"Пример\"", "organization_name_text": "ООО \"Пример\"", "organization_name_confidence": 100,
"inn": "7700000000", "inn_text": "7700000000", "inn_confidence": 100,
"report_date": "2025-09-30", "report_date_text": "на 30 сентября 2025 г.", "report_date_confidence": 100,
"units": "тыс. руб.", "units_text": "в тыс. рублей", "units_confidence": 100,
"okud": "0710001", "okud_text": "0710001", "okud_confidence": 100,
"okpo": "12345678", "okpo_text": "12345678", "okpo_confidence": 100,
"needs_review_fields": []
},
"columns": [
{ "column_index": 0, "label": "На 30 сентября 2025 г.", "period_date": "2025-09-30" },
{ "column_index": 1, "label": "На 31 декабря 2024 г.", "period_date": "2024-12-31" }
{ "column_index": 0, "label": "На 30 сентября 2025 г.", "label_confidence": 100, "period_date": "2025-09-30", "period_kind": "absolute" },
{ "column_index": 1, "label": "На 31 декабря 2024 г.", "label_confidence": 100, "period_date": "2024-12-31", "period_kind": "absolute" }
],
"lines": [
{ "line_code": "1110", "line_code_confidence": 100, "line_name": "Нематериальные активы", "line_name_confidence": 100, "values": [ { "column_index": 0, "amount": "586260.26", "confidence": 100, "is_recognized": true }, { "column_index": 1, "amount": "905786.99", "confidence": 100, "is_recognized": true } ] },
{ "line_code": "11101", "line_code_confidence": 100, "line_name": "Нематериальные активы, готовые к использованию", "line_name_confidence": 100, "values": [ { "column_index": 0, "amount": "586260.26", "confidence": 100, "is_recognized": true }, { "column_index": 1, "amount": "900000.00", "confidence": 100, "is_recognized": true } ] },
{ "line_code": "1100", "line_code_confidence": 100, "line_name": "Итого по разделу I", "line_name_confidence": 100, "values": [ { "column_index": 0, "amount": "586260.26", "confidence": 100, "is_recognized": true }, { "column_index": 1, "amount": "905786.99", "confidence": 100, "is_recognized": true } ] },
{ "line_code": "1200", "line_code_confidence": 100, "line_name": "Итого по разделу II", "line_name_confidence": 100, "values": [ { "column_index": 0, "amount": null, "confidence": 100, "is_recognized": true }, { "column_index": 1, "amount": null, "confidence": 100, "is_recognized": true } ] },
{ "line_code": "1600", "line_code_confidence": 100, "line_name": "БАЛАНС", "line_name_confidence": 100, "values": [ { "column_index": 0, "amount": "586260.26", "confidence": 100, "is_recognized": true }, { "column_index": 1, "amount": "905786.99", "confidence": 100, "is_recognized": true } ] }
{ "row_index": 0, "ref": "Бухгалтерский баланс. Лист 1!C14", "line_status": "ok", "line_code": "1110", "line_code_text": "1110", "line_code_confidence": 100, "line_name": "Нематериальные активы", "line_name_confidence": 100, "needs_review": false,
"values": [ { "column_index": 0, "amount": "586260.26", "text": "586 260,26", "confidence": 100, "is_recognized": true, "needs_review": false }, { "column_index": 1, "amount": "905786.99", "text": "905 786,99", "confidence": 100, "is_recognized": true, "needs_review": false } ] },
{ "row_index": 1, "ref": "Бухгалтерский баланс. Лист 1!C15", "line_status": "ok", "line_code": "11101", "line_code_text": "11101", "line_code_confidence": 100, "line_name": "Нематериальные активы, готовые к использованию", "line_name_confidence": 100, "needs_review": false,
"values": [ { "column_index": 0, "amount": "586260.26", "text": "586 260,26", "confidence": 100, "is_recognized": true, "needs_review": false }, { "column_index": 1, "amount": "900000.00", "text": "900 000,00", "confidence": 100, "is_recognized": true, "needs_review": false } ] },
{ "row_index": 2, "ref": "Бухгалтерский баланс. Лист 1!C16", "line_status": "ok", "line_code": "1100", "line_code_text": "1100", "line_code_confidence": 100, "line_name": "Итого по разделу I", "line_name_confidence": 100, "needs_review": false,
"values": [ { "column_index": 0, "amount": "586260.26", "text": "586 260,26", "confidence": 100, "is_recognized": true, "needs_review": false }, { "column_index": 1, "amount": "905786.99", "text": "905 786,99", "confidence": 100, "is_recognized": true, "needs_review": false } ] },
{ "row_index": 3, "ref": "Бухгалтерский баланс. Лист 1!C17", "line_status": "ok", "line_code": "1200", "line_code_text": "1200", "line_code_confidence": 100, "line_name": "Итого по разделу II", "line_name_confidence": 100, "needs_review": false,
"values": [ { "column_index": 0, "amount": null, "text": "-", "confidence": 100, "is_recognized": true, "needs_review": false }, { "column_index": 1, "amount": null, "text": "", "confidence": 100, "is_recognized": true, "needs_review": false } ] },
{ "row_index": 4, "ref": "Бухгалтерский баланс. Лист 1!C18", "line_status": "ok", "line_code": "1600", "line_code_text": "1600", "line_code_confidence": 100, "line_name": "БАЛАНС", "line_name_confidence": 100, "needs_review": false,
"values": [ { "column_index": 0, "amount": "586260.26", "text": "586 260,26", "confidence": 100, "is_recognized": true, "needs_review": false }, { "column_index": 1, "amount": "905786.99", "text": "905 786,99", "confidence": 100, "is_recognized": true, "needs_review": false } ] },
{ "row_index": 5, "ref": "Бухгалтерский баланс. Лист 2!C5", "line_status": "ok", "line_code": "1370", "line_code_text": "1370", "line_code_confidence": 100, "line_name": "Нераспределенная прибыль (непокрытый убыток)", "line_name_confidence": 100, "needs_review": false,
"values": [ { "column_index": 0, "amount": "586260.26", "text": "586 260,26", "confidence": 100, "is_recognized": true, "needs_review": false }, { "column_index": 1, "amount": "905786.99", "text": "905 786,99", "confidence": 100, "is_recognized": true, "needs_review": false } ] },
{ "row_index": 6, "ref": "Бухгалтерский баланс. Лист 2!C6", "line_status": "ok", "line_code": "1300", "line_code_text": "1300", "line_code_confidence": 100, "line_name": "Итого по разделу III", "line_name_confidence": 100, "needs_review": false,
"values": [ { "column_index": 0, "amount": "586260.26", "text": "586 260,26", "confidence": 100, "is_recognized": true, "needs_review": false }, { "column_index": 1, "amount": "905786.99", "text": "905 786,99", "confidence": 100, "is_recognized": true, "needs_review": false } ] },
{ "row_index": 7, "ref": "Бухгалтерский баланс. Лист 2!C7", "line_status": "ok", "line_code": "1700", "line_code_text": "1700", "line_code_confidence": 100, "line_name": "БАЛАНС", "line_name_confidence": 100, "needs_review": false,
"values": [ { "column_index": 0, "amount": "586260.26", "text": "586 260,26", "confidence": 100, "is_recognized": true, "needs_review": false }, { "column_index": 1, "amount": "905786.99", "text": "905 786,99", "confidence": 100, "is_recognized": true, "needs_review": false } ] }
],
"checks": { "relations_failed": [], "soft_relations_failed": [ { "column_index": 1, "relation": "1110 = 11101", "expected": "905786.99", "actual": "900000.00" } ], "duplicate_line_codes": [] }
"checks": {
"relations_failed": [],
"soft_relations_failed": [ { "column_index": 1, "relation": "1110 = 11101", "codes": ["1110", "11101"], "expected": "905786.99", "actual": "900000.00" } ]
}
},
{
"document_type": "INCOME",
"locator": "sheet:Отчет о финансовых результатах",
"header": { "form_name": "Отчет о финансовых результатах", "organization_name": "ООО \"Пример\"", "organization_name_confidence": 100, "report_date_text": "за январь - сентябрь 2025 г.", "report_date": "2025-09-30", "units": "тыс. руб.", "okud": "0710002" },
"columns": [ { "column_index": 0, "label": "За январь - сентябрь 2025 г.", "period_date": "2025-09-30" } ],
"lines": [
{ "line_code": "2110", "line_code_confidence": 100, "line_name": "Выручка", "line_name_confidence": 100, "values": [ { "column_index": 0, "amount": "1000.00", "confidence": 100, "is_recognized": true } ] },
{ "line_code": "2120", "line_code_confidence": 100, "line_name": "Себестоимость продаж", "line_name_confidence": 100, "values": [ { "column_index": 0, "amount": "-600.00", "confidence": 100, "is_recognized": true } ] },
{ "line_code": "2100", "line_code_confidence": 100, "line_name": "Валовая прибыль (убыток)", "line_name_confidence": 100, "values": [ { "column_index": 0, "amount": "400.00", "confidence": 100, "is_recognized": true } ] }
"status": "needs_check",
"form_variant": "full",
"warnings": [
{ "code": "INN_NOT_FOUND", "severity": "info", "message": "в шапке не найден ИНН", "field": "inn" }
],
"checks": { "relations_failed": [], "soft_relations_failed": [], "duplicate_line_codes": [] }
"header": {
"form_name": "Отчет о финансовых результатах",
"organization_name": "ООО \"Пример\"", "organization_name_text": "ООО \"Пример\"", "organization_name_confidence": 100,
"inn": null, "inn_text": null, "inn_confidence": null,
"report_date": "2025-09-30", "report_date_text": "за январь - сентябрь 2025 г.", "report_date_confidence": 100,
"units": "тыс. руб.", "units_text": "в тыс. рублей", "units_confidence": 100,
"okud": "0710002", "okud_text": "0710002", "okud_confidence": 100,
"needs_review_fields": []
},
"columns": [
{ "column_index": 0, "label": "За январь - сентябрь 2025 г.", "label_confidence": 100, "period_date": "2025-09-30", "period_kind": "absolute" }
],
"lines": [
{ "row_index": 0, "ref": "Отчет о финансовых результатах!C12", "line_status": "ok", "line_code": "2110", "line_code_text": "2110", "line_code_confidence": 100, "line_name": "Выручка", "line_name_confidence": 100, "needs_review": false,
"values": [ { "column_index": 0, "amount": "1000.00", "text": "1 000", "confidence": 100, "is_recognized": true, "needs_review": false } ] },
{ "row_index": 1, "ref": "Отчет о финансовых результатах!C13", "line_status": "detail", "line_code": null, "line_code_text": "", "line_code_confidence": 100, "line_name": "в том числе от продажи продукции", "line_name_confidence": 100, "parent_ref": "Отчет о финансовых результатах!C12", "needs_review": false,
"values": [ { "column_index": 0, "amount": "800.00", "text": "800", "confidence": 100, "is_recognized": true, "needs_review": false } ] },
{ "row_index": 2, "ref": "Отчет о финансовых результатах!C14", "line_status": "ok", "line_code": "2120", "line_code_text": "2120", "line_code_confidence": 100, "line_name": "Себестоимость продаж", "line_name_confidence": 100, "needs_review": false,
"values": [ { "column_index": 0, "amount": "-600.00", "text": "(600)", "confidence": 100, "is_recognized": true, "needs_review": false } ] },
{ "row_index": 3, "ref": "Отчет о финансовых результатах!C15", "line_status": "ok", "line_code": "2100", "line_code_text": "2100", "line_code_confidence": 100, "line_name": "Валовая прибыль (убыток)", "line_name_confidence": 100, "needs_review": false,
"values": [ { "column_index": 0, "amount": "400.00", "text": "400", "confidence": 100, "is_recognized": true, "needs_review": true } ] }
],
"checks": {
"relations_failed": [ { "column_index": 0, "relation": "2200 = 2100 - 2210 - 2220", "codes": ["2200", "2100", "2210", "2220"], "expected": null, "actual": "400.00" } ],
"soft_relations_failed": []
}
},
{
"document_type": "DISCLOSURE",
"locator": "sheet:Пояснения",
"header": { "form_name": "Пояснения к бухгалтерскому балансу и отчету о финансовых результатах" },
"status": "done",
"form_variant": null,
"warnings": [],
"header": { "form_name": "Пояснения к бухгалтерскому балансу и отчету о финансовых результатах", "needs_review_fields": [] },
"columns": [],
"lines": []
}

View File

@ -1,50 +1,51 @@
{
"schema_version": "1.2",
"schema_version": "2.0",
"job_id": "8d6b1f30-2e94-4a7c-b5d8-0f1e9c3a6b03",
"file_id": "c7e2a9b4-1d58-4630-a2f7-6b4d8e0c1f04",
"service": { "name": "scan-gateway", "version": "0.1.0" },
"status": "done",
"service": { "name": "scan-gateway", "version": "0.2.0", "statements_version": "abc1234" },
"status": "needs_check",
"raw_s3_key": "raw/8d6b1f30-2e94-4a7c-b5d8-0f1e9c3a6b03.json",
"metrics": { "duration_ms": 18400, "pages": 2, "warnings": [] },
"documents": [
{
"document_type": "BALANCE",
"locator": "pages:1-2",
"locator": "file:8d6b1f30-2e94-4a7c-b5d8-0f1e9c3a6b03.json;table:0",
"status": "needs_check",
"form_variant": "full",
"warnings": [
{ "code": "ORGANIZATION_UNCERTAIN", "severity": "info", "message": "организация распознана неуверенно", "field": "organization_name" },
{ "code": "ROW_SPLIT", "severity": "info", "message": "строка с несколькими кодами разлеплена: 1370, 1300", "line_refs": ["row:14#1", "row:14#2"] }
],
"header": {
"form_name": "Бухгалтерский баланс",
"organization_name": "ООО Ромашка",
"organization_name_confidence": 97.5,
"inn": "7700000000",
"inn_confidence": 99,
"report_date_text": "30 сентября 2025 г.",
"report_date": "2025-09-30",
"units": "тыс. руб."
"organization_name": "ООО Ромашка", "organization_name_text": "ООО Ромашка", "organization_name_confidence": 97.5,
"inn": "7700000000", "inn_text": "7700000000", "inn_confidence": 100,
"report_date": "2025-09-30", "report_date_text": "30 сентября 2025 г.", "report_date_confidence": 100,
"units": "тыс. руб.", "units_text": "тыс. руб.", "units_confidence": 100,
"needs_review_fields": []
},
"columns": [
{ "column_index": 0, "label": "На 30 сентября 2025 г.", "period_date": "2025-09-30" },
{ "column_index": 1, "label": "На 31 декабря 2024 г.", "period_date": "2024-12-31" }
{ "column_index": 0, "label": "На 30 сентября 2025 г.", "label_confidence": 100, "period_date": "2025-09-30", "period_kind": "absolute" }
],
"lines": [
{
"line_code": "1100",
"line_code_confidence": 99,
"line_name": "Итого по разделу I",
"line_name_confidence": 96,
"values": [
{ "column_index": 0, "amount": "1234.50", "confidence": 95, "is_recognized": true },
{ "column_index": 1, "amount": "1100", "confidence": 93, "is_recognized": true }
]
},
{
"line_code": "1600",
"line_code_confidence": 99,
"line_name": "БАЛАНС (актив)",
"line_name_confidence": 98,
"values": [
{ "column_index": 0, "amount": "5678.00", "confidence": 94.5, "is_recognized": true },
{ "column_index": 1, "amount": "-12.30", "confidence": 90, "is_recognized": true }
]
}
]
{ "row_index": 0, "ref": "row:1", "line_status": "ok", "line_code": "1210", "line_code_text": "1210", "line_code_confidence": 100, "line_name": "Запасы", "line_name_confidence": 100, "needs_review": false,
"values": [ { "column_index": 0, "amount": "100", "text": "100", "confidence": 100, "is_recognized": true, "needs_review": false } ] },
{ "row_index": 1, "ref": "row:2", "line_status": "ok", "line_code": "1230", "line_code_text": "1230", "line_code_confidence": 100, "line_name": "Дебиторская задолжен-ность", "line_name_confidence": 80, "needs_review": false,
"values": [ { "column_index": 0, "amount": "428311", "string_value": null, "text": "42831 1", "confidence": 100, "is_recognized": false, "needs_review": false } ] },
{ "row_index": 2, "ref": "row:3", "line_status": "unreadable", "line_code": null, "line_code_text": "ИЧ,250", "line_code_confidence": 25, "line_name": "Финансовые вложения", "line_name_confidence": 67, "needs_review": false,
"values": [ { "column_index": 0, "amount": "50", "text": "50", "confidence": 67, "is_recognized": true, "needs_review": true } ] },
{ "row_index": 3, "ref": "row:4", "line_status": "ok", "line_code": "1200", "line_code_text": "1200", "line_code_confidence": 100, "line_name": "Итого по разделу II", "line_name_confidence": 100, "needs_review": false,
"values": [ { "column_index": 0, "amount": "428411", "text": "428 411", "confidence": 100, "is_recognized": true, "needs_review": false } ] },
{ "row_index": 4, "ref": "row:5", "line_status": "ok", "line_code": "1600", "line_code_text": "1600", "line_code_confidence": 100, "line_name": "БАЛАНС", "line_name_confidence": 100, "needs_review": false,
"values": [ { "column_index": 0, "amount": "428411", "text": "428 411", "confidence": 100, "is_recognized": true, "needs_review": false } ] },
{ "row_index": 5, "ref": "row:14#1", "line_status": "ok", "line_code": "1370", "line_code_text": "1370", "line_code_confidence": 100, "line_name": "Нераспределенная прибыль (непокрытый убыток)", "line_name_confidence": 100, "needs_review": false,
"values": [ { "column_index": 0, "amount": "428411", "text": "428 411", "confidence": 100, "is_recognized": false, "needs_review": false } ] },
{ "row_index": 6, "ref": "row:14#2", "line_status": "ok", "line_code": "1300", "line_code_text": "1300", "line_code_confidence": 100, "line_name": "Итого по разделу III", "line_name_confidence": 100, "needs_review": false,
"values": [ { "column_index": 0, "amount": "428411", "text": "428 411", "confidence": 100, "is_recognized": false, "needs_review": false } ] },
{ "row_index": 7, "ref": "row:15", "line_status": "ok", "line_code": "1700", "line_code_text": "1700", "line_code_confidence": 100, "line_name": "БАЛАНС", "line_name_confidence": 100, "needs_review": false,
"values": [ { "column_index": 0, "amount": "428411", "text": "428 411", "confidence": 100, "is_recognized": true, "needs_review": false } ] }
],
"checks": { "relations_failed": [], "soft_relations_failed": [] }
}
]
}

View File

@ -1,13 +1,13 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "arm-ka/recognition-result-message/v1",
"title": "Сообщение результата распознавания (tabular-parser / scan-gateway → arm-core), контракт v1.0",
"$id": "arm-ka/recognition-result-message/v2",
"title": "Сообщение результата распознавания (tabular-parser / scan-gateway → arm-core), контракт v2.0",
"description": "Тело сообщения на subject ответа (настройка окружения NATS_RESULT_SUBJECT, по умолчанию files.tabular_parsed). Заголовок Nats-Msg-Id (не поле тела) на этом subject имеет две формы: ack:<job_id>:<attempt> — для status = processing; result:<job_id>:<attempt> — для done, needs_check и error. Форма запроса <job_id>:<attempt> описана в схеме recognition-request. status = processing — ack «взял в работу», публикуется до чтения файла из S3. group_schema в конверт не входит: источник — файл результата (recognition-result).",
"$comment": "Версионирование: schema_version = MAJOR.MINOR. MINOR (1.1) — необязательные поля и новые значения enum; MAJOR (2.0) — удаление/переименование полей, смена типов или семантики. Потребитель принимает тот же MAJOR и любой MINOR. Новый MINOR раскатывается сначала на потребителей (ядро и сервисы обновляют копии схем раньше, чем производитель начнёт слать 1.1), потому что закрытые enum отвергают незнакомые значения.",
"$comment": "Версионирование: schema_version = MAJOR.MINOR. MINOR (2.1) — необязательные поля и новые значения enum; MAJOR (3.0) — удаление/переименование полей, смена типов или семантики. Потребитель принимает известный MAJOR и любой MINOR. Новый MINOR раскатывается сначала на потребителей (ядро и сервисы обновляют копии схем раньше, чем производитель начнёт слать новый MINOR), потому что закрытые enum отвергают незнакомые значения; новый MAJOR — «сначала ядро»: ядро на время перехода принимает конверты (включая ack processing) и прежнего, и нового MAJOR. Changelog: 1.0 — исходный контракт; 2.0 (RSHB/arm-ka-docs#116, change unified-statement-normalization) — metrics.warnings[] стали структурами {code, severity, message, line_refs?, column_index?, field?, locator?} (как metrics.warnings файла результата), производитель пишет в конверт тот же MAJOR, что и в файл результата.",
"type": "object",
"required": ["schema_version", "job_id", "file_id", "status", "service"],
"properties": {
"schema_version": { "type": "string", "pattern": "^1\\.\\d+$", "description": "MAJOR.MINOR; производитель контракта v1.0 пишет \"1.0\"" },
"schema_version": { "type": "string", "pattern": "^2\\.\\d+$", "description": "MAJOR.MINOR; производитель контракта v2.0 пишет \"2.0\"" },
"job_id": { "type": "string", "format": "uuid", "description": "recognition_job.id" },
"file_id": { "type": "string", "format": "uuid", "description": "stored_file.id" },
"status": { "enum": ["processing", "done", "needs_check", "error"] },
@ -47,7 +47,22 @@
"fields_total": { "type": "integer" },
"fields_low_confidence": { "type": "integer" },
"fields_unrecognized": { "type": "integer" },
"warnings": { "type": "array", "items": { "type": "string" } }
"warnings": { "type": "array", "items": { "$ref": "#/$defs/fileWarning" }, "description": "предупреждения уровня файла — те же, что metrics.warnings файла результата" }
}
}
},
"$defs": {
"fileWarning": {
"type": "object",
"required": ["code", "severity", "message"],
"properties": {
"code": { "type": "string", "pattern": "^[A-Z][A-Z0-9_]*$" },
"severity": { "enum": ["error", "info"] },
"message": { "type": "string" },
"line_refs": { "type": "array", "items": { "type": "string" } },
"column_index": { "type": "integer" },
"field": { "enum": ["organization_name", "inn", "report_date", "units", "okud", "okpo", "okopf", "okfs", "address"] },
"locator": { "type": "string" }
}
}
},

View File

@ -1,13 +1,13 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "arm-ka/recognition-result/v1",
"title": "Результат распознавания файла (tabular-parser / scan-gateway), контракт v1.2",
"description": "Один файл (stored_file) → один job → N документов. Сервисы не знают БД: только структура файла. Нормализация счетов, резолв контрагентов, проверки — в arm-core.",
"$comment": "Версионирование: schema_version = MAJOR.MINOR. MINOR (1.1, 1.2, …) — необязательные поля и новые значения enum; MAJOR (2.0) — удаление/переименование полей, смена типов или семантики. Потребитель принимает тот же MAJOR и любой MINOR. Новый MINOR раскатывается сначала на потребителей (ядро и сервисы обновляют копии схем раньше, чем производитель начнёт слать новый MINOR), потому что закрытые enum отвергают незнакомые значения.",
"$id": "arm-ka/recognition-result/v2",
"title": "Результат распознавания файла (tabular-parser / scan-gateway), контракт v2.0",
"description": "Один файл (stored_file) → один job → N документов. Сервисы не знают БД: только структура файла. Формы ББ/ОФР нормализуются общим кодом arm_statements (05-domain-rules.md §11): статусы строк, needs_review, проверки §7, статус документа и структурные предупреждения приходят готовыми; ядро хранит их как пришли и пересчитывает тем же кодом только после правок аналитика. Нормализация счетов ОСВ, резолв контрагентов, сверка ИНН и периода — в arm-core.",
"$comment": "Версионирование: schema_version = MAJOR.MINOR. MINOR (2.1, 2.2, …) — необязательные поля и новые значения enum; MAJOR (3.0) — удаление/переименование полей, смена типов или семантики. Потребитель принимает известный MAJOR и любой MINOR. Новый MINOR раскатывается сначала на потребителей (ядро и сервисы обновляют копии схем раньше, чем производитель начнёт слать новый MINOR), потому что закрытые enum отвергают незнакомые значения. Новый MAJOR — порядок «сначала ядро»: ядро на время перехода принимает и прежний, и новый MAJOR (два маппера), сервисы переходят после выкладки ядра. Changelog: 1.0 — исходный контракт; 1.1 — osvDocument.rows[].extra; 1.2 — DISCLOSURE, statementDocument.checks, границы периода ОСВ; 2.0 (RSHB/arm-ka-docs#116, change unified-statement-normalization) — line_code nullable и line_status (ok | unreadable | missing | detail), row_index, ref, parent_ref, line_code_text, needs_review у значения и строки, values[].text, header.<поле>_text и <поле>_confidence у всех полей шапки, header.needs_review_fields, columns[].period_kind и label_confidence, document.status и document.warnings у всех документов (включая ОСВ), statementDocument.form_variant, metrics.warnings и warnings — структуры с уровнем и привязкой, checks обязателен у BALANCE/INCOME со строками, нарушения с codes, удалён checks.duplicate_line_codes, service.statements_version, смысл is_recognized у OCR отвязан от confidence. Схема 1.2 — в истории git (коммит 43b4b80 репозитория arm-ka-docs) и в моделях маппера 1.x ядра.",
"type": "object",
"required": ["schema_version", "job_id", "file_id", "service", "status", "documents"],
"properties": {
"schema_version": { "type": "string", "pattern": "^1\\.\\d+$", "description": "MAJOR.MINOR; производитель контракта v1.2 пишет \"1.2\"" },
"schema_version": { "type": "string", "pattern": "^2\\.\\d+$", "description": "MAJOR.MINOR; производитель контракта v2.0 пишет \"2.0\"" },
"job_id": { "type": "string", "format": "uuid" },
"file_id": { "type": "string", "format": "uuid" },
"service": {
@ -15,10 +15,11 @@
"required": ["name", "version"],
"properties": {
"name": { "enum": ["tabular-parser", "scan-gateway"] },
"version": { "type": "string" }
"version": { "type": "string" },
"statements_version": { "type": "string", "minLength": 1, "description": "коммит репозитория arm-statements, из которого разложена копия общего кода; обязателен, если в documents есть statementDocument" }
}
},
"status": { "enum": ["done", "needs_check", "error"] },
"status": { "enum": ["done", "needs_check", "error"], "description": "done ⇔ все документы done и нет предупреждений файла уровня error; error — документов нет" },
"error": {
"type": ["object", "null"],
"properties": {
@ -35,7 +36,7 @@
"fields_total": { "type": "integer" },
"fields_low_confidence": { "type": "integer" },
"fields_unrecognized": { "type": "integer" },
"warnings": { "type": "array", "items": { "type": "string" } }
"warnings": { "type": "array", "items": { "$ref": "#/$defs/fileWarning" }, "description": "предупреждения уровня файла (ошибочный json набора ИС, потерянная таблица, формат файла)" }
}
},
"raw_s3_key": { "type": ["string", "null"], "description": "scan-gateway: raw/{job_id}.json — сырой ответ ИС Сканирование; в БД не регистрируется" },
@ -44,31 +45,68 @@
"items": { "oneOf": [ { "$ref": "#/$defs/osvDocument" }, { "$ref": "#/$defs/statementDocument" } ] }
}
},
"if": {
"properties": { "status": { "enum": ["done", "needs_check"] } },
"required": ["status"]
},
"then": {
"properties": { "documents": { "minItems": 1 } }
},
"allOf": [
{
"if": {
"properties": { "status": { "enum": ["done", "needs_check"] } },
"required": ["status"]
},
"then": {
"properties": { "documents": { "minItems": 1 } }
}
},
{
"if": {
"properties": { "documents": { "contains": { "properties": { "document_type": { "enum": ["BALANCE", "INCOME", "DISCLOSURE", "OTHER"] } }, "required": ["document_type"] } } },
"required": ["documents"]
},
"then": {
"properties": { "service": { "required": ["name", "version", "statements_version"] } }
}
}
],
"$defs": {
"money": { "type": ["string", "null"], "pattern": "^-?\\d+(\\.\\d{1,2})?$", "description": "Decimal как строка, чтобы не терять копейки в JSON" },
"confidence": { "type": ["number", "null"], "minimum": 0, "maximum": 100 },
"documentStatus": { "enum": ["done", "needs_check"] },
"headerField": { "enum": ["organization_name", "inn", "report_date", "units", "okud", "okpo", "okopf", "okfs", "address"] },
"warning": {
"type": "object",
"required": ["code", "severity", "message"],
"properties": {
"code": { "type": "string", "pattern": "^[A-Z][A-Z0-9_]*$", "examples": ["COLUMN_WITHOUT_DATE", "ROW_SPLIT"], "description": "код по таблице 05-domain-rules.md §11; не перечисление — новый код не меняет MAJOR" },
"severity": { "enum": ["error", "info"], "description": "error → документ needs_check; info на статус не влияет" },
"message": { "type": "string" },
"line_refs": { "type": "array", "items": { "type": "string" }, "description": "ref строк документа (statementDocument.lines[].ref)" },
"column_index": { "type": "integer" },
"field": { "$ref": "#/$defs/headerField" }
}
},
"fileWarning": {
"allOf": [ { "$ref": "#/$defs/warning" } ],
"properties": {
"locator": { "type": "string", "examples": ["file:aep_3_ofr.json", "sheet:Лист3"], "description": "место в файле, к которому относится предупреждение" }
}
},
"failedRelation": {
"type": "object",
"required": ["column_index", "relation", "expected", "actual"],
"required": ["column_index", "relation", "codes", "expected", "actual"],
"properties": {
"column_index": { "type": "integer" },
"relation": { "type": "string", "examples": ["1600 = 1100 + 1200"] },
"expected": { "$ref": "#/$defs/money", "description": "левая часть соотношения как в файле" },
"actual": { "$ref": "#/$defs/money", "description": "правая часть, вычисленная по строкам" }
"codes": { "type": "array", "minItems": 1, "items": { "type": "string", "pattern": "^\\d{4,5}$" }, "description": "коды цели и членов соотношения: нарушение подсвечивает все строки этих кодов в колонке" },
"expected": { "$ref": "#/$defs/money", "description": "левая часть соотношения (цель) по строкам ok; null — цели нет" },
"actual": { "$ref": "#/$defs/money", "description": "правая часть, вычисленная по строкам ok" }
}
},
"osvDocument": {
"type": "object",
"required": ["document_type", "locator", "header", "group_schema", "rows"],
"required": ["document_type", "locator", "header", "group_schema", "rows", "status", "warnings"],
"properties": {
"document_type": { "const": "OSV" },
"locator": { "type": "string", "examples": ["sheet:Лист_1"] },
"status": { "$ref": "#/$defs/documentStatus", "description": "done — ведомость разобрана (§1.6)" },
"warnings": { "type": "array", "items": { "$ref": "#/$defs/warning" }, "description": "предупреждения листа ОСВ; все уровня info, код с префиксом OSV_" },
"header": {
"type": "object",
"required": ["organization_name", "account_code_raw", "period_text"],
@ -142,79 +180,128 @@
}
}
},
"statementValue": {
"type": "object",
"required": ["column_index", "amount", "text", "confidence", "is_recognized", "needs_review"],
"properties": {
"column_index": { "type": "integer" },
"amount": { "$ref": "#/$defs/money" },
"string_value": { "type": ["string", "null"], "description": "текст ячейки, только когда amount = null и текст непуст и не прочерк" },
"text": { "type": ["string", "null"], "description": "сырой текст источника всегда (у Excel — как показывает числовой формат); null — позиции нет" },
"confidence": { "$ref": "#/$defs/confidence", "description": "уверенность источника (у Excel 100; позиции нет — 100)" },
"is_recognized": { "type": "boolean", "description": "текст разобран как число без выброшенных и исправленных символов; от confidence не зависит" },
"needs_review": { "type": "boolean", "description": "сводный признак: нуждается в покрытии §7 и не покрыто, не подтверждён знак, или в несошедшемся жёстком соотношении (§11)" }
}
},
"statementLine": {
"type": "object",
"required": ["row_index", "ref", "line_status", "line_code", "line_code_text", "line_name", "needs_review", "values"],
"properties": {
"row_index": { "type": "integer", "minimum": 0, "description": "порядковый номер строки в документе с 0, по порядку сетки после разлепления" },
"ref": { "type": "string", "minLength": 1, "examples": ["row:8", "row:14#2", "Баланс!C27"], "description": "положение в источнике; уникален в документе" },
"line_status": { "enum": ["ok", "unreadable", "missing", "detail"] },
"line_code": { "type": ["string", "null"], "pattern": "^\\d{4,5}$", "description": "разобранный код; null ⇔ line_status ≠ ok" },
"line_code_text": { "type": ["string", "null"], "description": "сырой текст ячейки кода" },
"line_code_confidence": { "$ref": "#/$defs/confidence", "description": "от источника; у кода с заменой двойника — не выше 99" },
"line_name": { "type": ["string", "null"], "description": "распознанное наименование как в источнике (не из справочника)" },
"line_name_confidence": { "$ref": "#/$defs/confidence" },
"parent_ref": { "type": ["string", "null"], "description": "ref строки-родителя у detail" },
"needs_review": { "type": "boolean", "description": "неуверенный код не подтверждён структурой или повтор кода с неуверенной копией" },
"values": { "type": "array", "items": { "$ref": "#/$defs/statementValue" } }
},
"allOf": [
{
"if": { "properties": { "line_status": { "const": "ok" } }, "required": ["line_status"] },
"then": { "properties": { "line_code": { "type": "string" } } },
"else": { "properties": { "line_code": { "type": "null" } } }
},
{
"if": { "properties": { "line_status": { "const": "detail" } }, "required": ["line_status"] },
"then": { "required": ["parent_ref"], "properties": { "parent_ref": { "type": "string" } } }
}
]
},
"statementDocument": {
"type": "object",
"required": ["document_type", "locator", "header", "columns", "lines"],
"required": ["document_type", "locator", "header", "columns", "lines", "status", "warnings"],
"properties": {
"document_type": { "enum": ["BALANCE", "INCOME", "DISCLOSURE", "OTHER"] },
"locator": { "type": "string", "examples": ["pages:1-2"] },
"locator": { "type": "string", "examples": ["file:aep_3.json;table:0", "sheet:Баланс,Баланс (2)"] },
"status": { "$ref": "#/$defs/documentStatus" },
"form_variant": { "enum": ["full", "simplified", null], "description": "по структуре кодов (§11); null — DISCLOSURE/OTHER" },
"warnings": { "type": "array", "items": { "$ref": "#/$defs/warning" } },
"header": {
"type": "object",
"properties": {
"form_name": { "type": "string" },
"form_name": { "type": ["string", "null"], "description": "заголовок формы как в источнике" },
"organization_name": { "type": ["string", "null"] },
"organization_name_confidence": { "type": ["number", "null"], "minimum": 0, "maximum": 100 },
"inn": { "type": ["string", "null"] },
"inn_confidence": { "type": ["number", "null"], "minimum": 0, "maximum": 100 },
"report_date_text": { "type": ["string", "null"] },
"organization_name_text": { "type": ["string", "null"] },
"organization_name_confidence": { "$ref": "#/$defs/confidence" },
"inn": { "type": ["string", "null"], "pattern": "^(\\d{10}|\\d{12})$" },
"inn_text": { "type": ["string", "null"] },
"inn_confidence": { "$ref": "#/$defs/confidence" },
"report_date": { "type": ["string", "null"], "format": "date" },
"report_date_text": { "type": ["string", "null"] },
"report_date_confidence": { "$ref": "#/$defs/confidence" },
"units": { "type": ["string", "null"], "examples": ["тыс. руб."] },
"units_text": { "type": ["string", "null"] },
"units_confidence": { "$ref": "#/$defs/confidence" },
"okud": { "type": ["string", "null"] },
"okud_text": { "type": ["string", "null"] },
"okud_confidence": { "$ref": "#/$defs/confidence" },
"okpo": { "type": ["string", "null"] },
"okpo_text": { "type": ["string", "null"] },
"okpo_confidence": { "$ref": "#/$defs/confidence" },
"okopf": { "type": ["string", "null"] },
"okopf_text": { "type": ["string", "null"] },
"okopf_confidence": { "$ref": "#/$defs/confidence" },
"okfs": { "type": ["string", "null"] },
"address": { "type": ["string", "null"] }
"okfs_text": { "type": ["string", "null"] },
"okfs_confidence": { "$ref": "#/$defs/confidence" },
"address": { "type": ["string", "null"] },
"address_text": { "type": ["string", "null"] },
"address_confidence": { "$ref": "#/$defs/confidence" },
"needs_review_fields": { "type": "array", "items": { "$ref": "#/$defs/headerField" }, "uniqueItems": true, "description": "поля шапки с needs_review = true (сейчас — только report_date)" }
}
},
"columns": {
"type": "array",
"items": {
"type": "object",
"required": ["column_index", "label"],
"required": ["column_index", "label", "period_kind"],
"properties": {
"column_index": { "type": "integer" },
"label": { "type": "string", "examples": ["На 30 сентября 2025 г."] },
"period_date": { "type": ["string", "null"], "format": "date" }
}
}
},
"lines": {
"type": "array",
"items": {
"type": "object",
"required": ["line_code", "line_name", "values"],
"properties": {
"line_code": { "type": "string" },
"line_code_confidence": { "type": ["number", "null"], "minimum": 0, "maximum": 100 },
"line_name": { "type": "string" },
"line_name_confidence": { "type": ["number", "null"], "minimum": 0, "maximum": 100 },
"values": {
"type": "array",
"items": {
"type": "object",
"required": ["column_index"],
"properties": {
"column_index": { "type": "integer" },
"amount": { "$ref": "#/$defs/money" },
"string_value": { "type": ["string", "null"] },
"confidence": { "type": ["number", "null"], "minimum": 0, "maximum": 100 },
"is_recognized": { "type": "boolean" }
}
}
}
"label": { "type": "string", "examples": ["На 30 сентября 2025 г.", "На отчетную дату"] },
"label_confidence": { "$ref": "#/$defs/confidence", "description": "у Excel 100; подписи нет — null" },
"period_date": { "type": ["string", "null"], "format": "date", "description": "конец периода колонки (period_end); у относительной подписи — от эффективной даты документа" },
"period_kind": { "enum": ["absolute", "relative:current", "relative:prev_year_end", "relative:prev_prev_year_end", "relative:prev_period", "none"] }
}
}
},
"lines": { "type": "array", "items": { "$ref": "#/$defs/statementLine" } },
"checks": {
"type": "object",
"description": "Результаты самопроверки формы по соотношениям §7 (tabular-parser). Отсутствие объекта — «не проверялось» (OCR-путь); пустые массивы — «проверено, сошлось».",
"required": ["relations_failed", "soft_relations_failed"],
"description": "Самопроверка формы по соотношениям §7 общим кодом (оба конвейера). Есть у BALANCE/INCOME со строками, нет у документа без строк. Пустые массивы — «проверено, сошлось».",
"properties": {
"relations_failed": { "type": "array", "items": { "$ref": "#/$defs/failedRelation" }, "description": "нарушения жёстких соотношений: документ needs_check" },
"soft_relations_failed": { "type": "array", "items": { "$ref": "#/$defs/failedRelation" }, "description": "нарушения мягких соотношений (OQ-12, подстроки «в том числе») и жёстких в пределах округления итогов (OQ-138): только предупреждение" },
"duplicate_line_codes": { "type": "array", "items": { "type": "string" }, "description": "коды строк, встретившиеся в документе больше одного раза (осталась первая строка)" }
"relations_failed": { "type": "array", "items": { "$ref": "#/$defs/failedRelation" }, "description": "жёсткие соотношения, не сошедшиеся вне допуска ±1 и округления 0,001 %: документ needs_check" },
"soft_relations_failed": { "type": "array", "items": { "$ref": "#/$defs/failedRelation" }, "description": "мягкие соотношения (OQ-12, подстроки) и жёсткие в пределах округления (OQ-138): info" }
}
}
}
},
"allOf": [
{
"if": {
"properties": { "document_type": { "enum": ["BALANCE", "INCOME"] }, "lines": { "minItems": 1 } },
"required": ["document_type", "lines"]
},
"then": { "required": ["checks"] }
},
{
"if": { "properties": { "lines": { "maxItems": 0 } }, "required": ["lines"] },
"then": { "not": { "required": ["checks"] } }
}
]
}
}
}

View File

@ -257,7 +257,7 @@ tabular-parser и шаблоны `dates.py` scan-gateway; строки `test_dat
До разбора кода SHALL выполняться разлепление: ячейка кода содержит N ≥ 2 частей через перевод строки, и каждая
непустая ячейка значений — ровно N частей; разлепление делается, если хотя бы одна часть кода разобралась как код
диапазона столбца. Получается N строк с `ref` `row:<i>#<k>`; нечитаемая часть кода — строка `unreadable`; наименование —
k-я из непустых частей, если их тоже N, иначе целиком у каждой. Значения разлепленных строк получают
k-я из непустых частей, если их тоже N, иначе целиком у каждой; `line_code_text` и `text` значений — k-е части. Значения разлепленных строк получают
`is_recognized = false`; разлепление — предупреждение `ROW_SPLIT` (`info`). Число частей не совпало или ни одна часть
не код — одна строка, многострочное значение → `amount = null`, `string_value` = текст, `is_recognized = false`.