arm-ka-docs/contracts/recognition-result.schema.json

311 lines
25 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$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, header.okei (+ _text, _confidence), смысл is_recognized у OCR отвязан от confidence, statementDocument.form_variant обязателен, OTHER удалён из statementDocument.document_type. Схема 1.2 на окне перехода лежит рядом — recognition-result.v1.schema.json (примеры — examples/v1/); удаляется вместе с маппером 1.x ядра после перехода обоих сервисов.",
"type": "object",
"required": ["schema_version", "job_id", "file_id", "service", "status", "documents"],
"properties": {
"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": {
"type": "object",
"required": ["name", "version"],
"properties": {
"name": { "enum": ["tabular-parser", "scan-gateway"] },
"version": { "type": "string" },
"statements_version": { "type": "string", "minLength": 1, "description": "маркер VERSION библиотеки arm_statements, если он есть, иначе версия установленного дистрибутива pylib-ai-arm-statements; обязателен, если в documents есть statementDocument" }
}
},
"status": { "enum": ["done", "needs_check", "error"], "description": "done ⇔ все документы done и нет предупреждений файла уровня error; error — документов нет" },
"error": {
"type": ["object", "null"],
"properties": {
"code": { "enum": ["UNSUPPORTED_FORMAT", "UNREADABLE", "TIMEOUT", "SCHEMA_MISMATCH", "NOT_IMPLEMENTED", "FILE_READ_ERROR", "INVALID_FORMAT", "INVALID_DOCUMENT", "RECOGNITION_FAILED", "UNKNOWN_ERROR"] },
"message": { "type": "string" }
}
},
"metrics": {
"type": "object",
"properties": {
"duration_ms": { "type": "integer" },
"pages": { "type": "integer" },
"rows": { "type": "integer" },
"fields_total": { "type": "integer" },
"fields_low_confidence": { "type": "integer" },
"fields_unrecognized": { "type": "integer" },
"warnings": { "type": "array", "items": { "$ref": "#/$defs/fileWarning" }, "description": "предупреждения уровня файла (ошибочный json набора ИС, потерянная таблица, формат файла)" }
}
},
"raw_s3_key": { "type": ["string", "null"], "description": "scan-gateway: raw/{job_id}.json — сырой ответ ИС Сканирование; в БД не регистрируется" },
"documents": {
"type": "array",
"items": { "oneOf": [ { "$ref": "#/$defs/osvDocument" }, { "$ref": "#/$defs/statementDocument" } ] }
}
},
"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"] } }, "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", "okei", "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[]; строки ролей title / header / numbering в line_refs не попадают (о них — message)" },
"column_index": { "type": "integer", "description": "столбец сетки кандидата (как columns[].column_index); у COLUMN_SKIPPED и других предупреждений о столбце вне columns[] — номер этого столбца" },
"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", "codes", "expected", "actual"],
"properties": {
"column_index": { "type": "integer" },
"relation": { "type": "string", "examples": ["1600 = 1100 + 1200"] },
"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", "status", "warnings"],
"properties": {
"document_type": { "const": "OSV" },
"locator": { "type": "string", "examples": ["sheet:Лист_1"] },
"status": { "$ref": "#/$defs/documentStatus", "description": "done — контрольные суммы §1.4 листа сошлись, иначе needs_check (05-domain-rules.md §1.6)" },
"warnings": { "type": "array", "items": { "$ref": "#/$defs/warning" }, "description": "предупреждения листа ОСВ; все уровня info, код с префиксом OSV_" },
"header": {
"type": "object",
"required": ["organization_name", "account_code_raw", "period_text"],
"properties": {
"organization_name": { "type": "string", "description": "как в r1 файла" },
"organization_inn": { "type": ["string", "null"] },
"account_code_raw": { "type": "string", "examples": ["60", "58.03"] },
"period_text": { "type": "string", "examples": ["1 квартал 2025 г.", "2024 г."] },
"period_year": { "type": ["integer", "null"] },
"period_quarter": { "enum": ["Q1", "Q2", "Q3", "Q4", null] },
"is_cumulative": { "type": ["boolean", "null"], "description": "по тексту; ядро может переопределить по данным (OQ-02)" },
"period_start": { "type": ["string", "null"], "format": "date", "description": "начало периода по шапке: первое число квартала/года либо начало диапазона месяцев (OQ-131)" },
"period_end": { "type": ["string", "null"], "format": "date", "description": "конец периода по шапке" },
"is_from_year_start": { "type": ["boolean", "null"], "description": "period_start — 1 января; is_cumulative не переопределяет" },
"data_type": { "enum": ["BU", "NU", "SUM", "CURRENCY", "QUANTITY", null] },
"currency": { "type": ["string", "null"], "examples": ["RUB", "руб."] },
"header_variant": { "enum": [1, 2] }
}
},
"group_schema": {
"type": "array",
"description": "порядок группировок из шапки, после первого ACCOUNT",
"items": {
"type": "object",
"required": ["type"],
"properties": {
"type": { "enum": ["ACCOUNT", "DEPARTMENT", "COUNTERPARTY", "CONTRACT", "OTHER"] },
"name": { "type": "string", "description": "исходный текст из шапки" },
"has_inn": { "type": "boolean" }
}
}
},
"columns": {
"type": "object",
"description": "диагностика: какие колонки листа были приняты за шесть сумм и ИНН",
"additionalProperties": { "type": "string" }
},
"rows": {
"type": "array",
"items": {
"type": "object",
"required": ["row_number", "level", "row_value", "group_type"],
"properties": {
"row_number": { "type": "integer" },
"level": { "type": "integer" },
"parent_row_number": { "type": ["integer", "null"] },
"row_value": { "type": "string" },
"group_type": { "enum": ["ACCOUNT", "DEPARTMENT", "COUNTERPARTY", "CONTRACT", "OTHER", "TOTAL"] },
"group_name": { "type": ["string", "null"] },
"account_code_raw": { "type": ["string", "null"], "description": "код текущей ветки, как в файле (60.1)" },
"counterparty_name": { "type": ["string", "null"] },
"counterparty_inn": { "type": ["string", "null"] },
"data_type": { "enum": ["BU", "NU", "SUM", "CURRENCY", "QUANTITY", null] },
"opening_debit": { "$ref": "#/$defs/money" },
"opening_credit": { "$ref": "#/$defs/money" },
"turnover_debit": { "$ref": "#/$defs/money" },
"turnover_credit": { "$ref": "#/$defs/money" },
"closing_debit": { "$ref": "#/$defs/money" },
"closing_credit": { "$ref": "#/$defs/money" },
"extra": { "type": ["object", "null"], "description": "нераспознанные колонки внутри таблицы: заголовок колонки → текст ячейки (§1.2)", "additionalProperties": { "type": ["string", "null"] } }
}
}
},
"checks": {
"type": "object",
"properties": {
"total_row_matches_root": { "type": "boolean" },
"balance_identity_violations": { "type": "array", "items": { "type": "integer" }, "description": "row_number строк, где opening−turnover≠closing" },
"parent_sum_violations": { "type": "array", "items": { "type": "integer" } }
}
}
}
},
"statementValue": {
"type": "object",
"required": ["column_index", "amount", "text", "confidence", "is_recognized", "needs_review"],
"properties": {
"column_index": { "type": "integer", "description": "колонка значений — columns[].column_index" },
"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", "'Бухгалтерский баланс. Лист 1'!C14"], "description": "положение в источнике; уникален в документе. OCR — row:<row_index ИС>[#k]; Excel — <лист>!<A1> по правилу Excel: имя листа в одинарных кавычках, если в нём есть что-то кроме букв, цифр и _ или оно начинается с цифры; апостроф внутри имени удваивается" },
"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", "form_variant", "locator", "header", "columns", "lines", "status", "warnings"],
"properties": {
"document_type": { "enum": ["BALANCE", "INCOME", "DISCLOSURE"], "description": "OTHER в v2.0 не порождается: лист «прочее» — предупреждение файла SHEET_OTHER, документа нет (значение OTHER — только в схеме 1.x)" },
"locator": { "type": "string", "examples": ["file:aep_3.json;table:0", "sheet:Баланс,Баланс (2)"] },
"status": { "$ref": "#/$defs/documentStatus" },
"form_variant": { "enum": ["full", "simplified", null], "description": "по структуре кодов строк ok (§11); null — DISCLOSURE; обязателен" },
"warnings": { "type": "array", "items": { "$ref": "#/$defs/warning" } },
"header": {
"type": "object",
"properties": {
"form_name": { "type": ["string", "null"], "description": "заголовок формы как в источнике" },
"organization_name": { "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"] },
"okfs_text": { "type": ["string", "null"] },
"okfs_confidence": { "$ref": "#/$defs/confidence" },
"okei": { "type": ["string", "null"], "examples": ["384"], "description": "код ОКЕИ единицы (384 — тыс. руб., 383 — руб., 385 — млн руб.); запасной источник units (§11.2)" },
"okei_text": { "type": ["string", "null"] },
"okei_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_check (сейчас — только неуверенная дата отчёта, не подтверждённая датами колонок); сомнения в ИНН и организации — предупреждения info с field, сюда не входят (§11.9)" }
}
},
"columns": {
"type": "array",
"items": {
"type": "object",
"required": ["column_index", "label", "period_kind"],
"properties": {
"column_index": { "type": "integer", "minimum": 0, "description": "номер столбца в сетке кандидата шага 1 (у OCR — column_index ИС, не обязательно подряд; у Excel — номер столбца листа первого сегмента, с 0); уникален в документе; колонки сегментов без шапки сопоставляются с колонками первого сегмента по порядку и несут их номер" },
"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" }, "description": "у документа без строк (DISCLOSURE, форма без столбца кодов) — пустой массив" },
"checks": {
"type": "object",
"required": ["relations_failed", "soft_relations_failed"],
"description": "Самопроверка формы по соотношениям §7 общим кодом (оба конвейера). Есть у BALANCE/INCOME со строками, нет у документа без строк. Пустые массивы — «проверено, сошлось».",
"properties": {
"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"] } }
}
]
}
}
}