From f8a584af15d6f3e94980c4c74de52621f59c8eaf Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Sun, 4 Oct 2026 02:38:17 +0400 Subject: [PATCH] =?UTF-8?q?chore:=20=D0=BA=D0=BE=D0=BF=D0=B8=D0=B8=20?= =?UTF-8?q?=D1=81=D1=85=D0=B5=D0=BC=20=D0=BA=D0=BE=D0=BD=D1=82=D1=80=D0=B0?= =?UTF-8?q?=D0=BA=D1=82=D0=B0=20v1.0=20=D0=B8=20=D0=BF=D1=80=D0=B8=D0=BC?= =?UTF-8?q?=D0=B5=D1=80=D0=BE=D0=B2=20=D0=B2=20api/contracts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Скопировано из ../docs/contracts байт в байт (cp, diff -r чистый). Co-Authored-By: Claude Fable 5.1 --- api/contracts/examples/request-image.json | 14 ++ api/contracts/examples/request-tabular.json | 14 ++ api/contracts/examples/result-error.json | 12 ++ .../examples/result-message-done.json | 21 ++ .../examples/result-message-error.json | 11 + .../examples/result-message-processing.json | 7 + .../examples/result-osv-minimal.json | 79 +++++++ .../examples/result-statement-minimal.json | 50 +++++ api/contracts/recognition-request.schema.json | 28 +++ .../recognition-result-message.schema.json | 70 +++++++ api/contracts/recognition-result.schema.json | 197 ++++++++++++++++++ 11 files changed, 503 insertions(+) create mode 100644 api/contracts/examples/request-image.json create mode 100644 api/contracts/examples/request-tabular.json create mode 100644 api/contracts/examples/result-error.json create mode 100644 api/contracts/examples/result-message-done.json create mode 100644 api/contracts/examples/result-message-error.json create mode 100644 api/contracts/examples/result-message-processing.json create mode 100644 api/contracts/examples/result-osv-minimal.json create mode 100644 api/contracts/examples/result-statement-minimal.json create mode 100644 api/contracts/recognition-request.schema.json create mode 100644 api/contracts/recognition-result-message.schema.json create mode 100644 api/contracts/recognition-result.schema.json diff --git a/api/contracts/examples/request-image.json b/api/contracts/examples/request-image.json new file mode 100644 index 0000000..2de9cd5 --- /dev/null +++ b/api/contracts/examples/request-image.json @@ -0,0 +1,14 @@ +{ + "schema_version": "1.0", + "job_id": "8d6b1f30-2e94-4a7c-b5d8-0f1e9c3a6b03", + "file_id": "c7e2a9b4-1d58-4630-a2f7-6b4d8e0c1f04", + "pipeline": "image", + "file_type": "PDF", + "original_name": "bb_2025_q3.pdf", + "s3": { + "bucket": "arm-ka", + "key": "uploads/c7e2a9b4-1d58-4630-a2f7-6b4d8e0c1f04/8d6b1f30-2e94-4a7c-b5d8-0f1e9c3a6b03/bb_2025_q3.pdf" + }, + "reply_deadline": "2026-10-03T12:45:00Z", + "attempt": 1 +} diff --git a/api/contracts/examples/request-tabular.json b/api/contracts/examples/request-tabular.json new file mode 100644 index 0000000..e1a26c1 --- /dev/null +++ b/api/contracts/examples/request-tabular.json @@ -0,0 +1,14 @@ +{ + "schema_version": "1.0", + "job_id": "3f2a9c1e-7b4d-4e0a-9d51-2c8e6a1b0f01", + "file_id": "a1c4e7d2-5b36-4f89-8e12-9d0c3b7a4e02", + "pipeline": "tabular", + "file_type": "XLSX", + "original_name": "osv_60_2025_q1.xlsx", + "s3": { + "bucket": "arm-ka", + "key": "uploads/a1c4e7d2-5b36-4f89-8e12-9d0c3b7a4e02/3f2a9c1e-7b4d-4e0a-9d51-2c8e6a1b0f01/osv_60_2025_q1.xlsx" + }, + "reply_deadline": "2026-10-03T12:30:00Z", + "attempt": 1 +} diff --git a/api/contracts/examples/result-error.json b/api/contracts/examples/result-error.json new file mode 100644 index 0000000..54af7cb --- /dev/null +++ b/api/contracts/examples/result-error.json @@ -0,0 +1,12 @@ +{ + "schema_version": "1.0", + "job_id": "3f2a9c1e-7b4d-4e0a-9d51-2c8e6a1b0f01", + "file_id": "a1c4e7d2-5b36-4f89-8e12-9d0c3b7a4e02", + "service": { "name": "tabular-parser", "version": "0.1.0" }, + "status": "error", + "error": { + "code": "NOT_IMPLEMENTED", + "message": "Разбор ОСВ пока не реализован / OSV parsing is not implemented yet" + }, + "documents": [] +} diff --git a/api/contracts/examples/result-message-done.json b/api/contracts/examples/result-message-done.json new file mode 100644 index 0000000..6d56f17 --- /dev/null +++ b/api/contracts/examples/result-message-done.json @@ -0,0 +1,21 @@ +{ + "schema_version": "1.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" }, + "result_s3": { + "bucket": "arm-ka", + "key": "results/c7e2a9b4-1d58-4630-a2f7-6b4d8e0c1f04/8d6b1f30-2e94-4a7c-b5d8-0f1e9c3a6b03/result.json" + }, + "result_schema_version": "1.0", + "raw_s3_key": "raw/8d6b1f30-2e94-4a7c-b5d8-0f1e9c3a6b03.json", + "metrics": { + "duration_ms": 18400, + "pages": 2, + "fields_total": 4, + "fields_low_confidence": 0, + "fields_unrecognized": 0, + "warnings": [] + } +} diff --git a/api/contracts/examples/result-message-error.json b/api/contracts/examples/result-message-error.json new file mode 100644 index 0000000..f5ec77a --- /dev/null +++ b/api/contracts/examples/result-message-error.json @@ -0,0 +1,11 @@ +{ + "schema_version": "1.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" }, + "error": { + "code": "NOT_IMPLEMENTED", + "message": "Разбор ОСВ пока не реализован / OSV parsing is not implemented yet" + } +} diff --git a/api/contracts/examples/result-message-processing.json b/api/contracts/examples/result-message-processing.json new file mode 100644 index 0000000..2087150 --- /dev/null +++ b/api/contracts/examples/result-message-processing.json @@ -0,0 +1,7 @@ +{ + "schema_version": "1.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" } +} diff --git a/api/contracts/examples/result-osv-minimal.json b/api/contracts/examples/result-osv-minimal.json new file mode 100644 index 0000000..6b6d122 --- /dev/null +++ b/api/contracts/examples/result-osv-minimal.json @@ -0,0 +1,79 @@ +{ + "schema_version": "1.0", + "job_id": "3f2a9c1e-7b4d-4e0a-9d51-2c8e6a1b0f01", + "file_id": "a1c4e7d2-5b36-4f89-8e12-9d0c3b7a4e02", + "service": { "name": "tabular-parser", "version": "0.1.0" }, + "status": "done", + "documents": [ + { + "document_type": "OSV", + "locator": "sheet:Лист_1", + "header": { + "organization_name": "ООО Ромашка", + "organization_inn": "7700000000", + "account_code_raw": "60", + "period_text": "1 квартал 2025 г.", + "period_year": 2025, + "period_quarter": "Q1", + "is_cumulative": false, + "data_type": "BU", + "currency": "RUB", + "header_variant": 1 + }, + "group_schema": [ + { "type": "COUNTERPARTY", "name": "Контрагенты", "has_inn": true } + ], + "columns": { + "opening_debit": "C", + "opening_credit": "D", + "turnover_debit": "E", + "turnover_credit": "F", + "closing_debit": "G", + "closing_credit": "H" + }, + "rows": [ + { + "row_number": 1, + "level": 0, + "parent_row_number": null, + "row_value": "60", + "group_type": "ACCOUNT", + "group_name": null, + "account_code_raw": "60", + "counterparty_name": null, + "counterparty_inn": null, + "data_type": "BU", + "opening_debit": "0.00", + "opening_credit": "1000.00", + "turnover_debit": "500.00", + "turnover_credit": "200.50", + "closing_debit": "0.00", + "closing_credit": "700.50" + }, + { + "row_number": 2, + "level": 1, + "parent_row_number": 1, + "row_value": "ООО Лютик, ИНН 7711111111", + "group_type": "COUNTERPARTY", + "group_name": "Контрагенты", + "account_code_raw": "60", + "counterparty_name": "ООО Лютик", + "counterparty_inn": "7711111111", + "data_type": "BU", + "opening_debit": "0.00", + "opening_credit": "1000.00", + "turnover_debit": "500.00", + "turnover_credit": "200.50", + "closing_debit": "0.00", + "closing_credit": "700.50" + } + ], + "checks": { + "total_row_matches_root": true, + "balance_identity_violations": [], + "parent_sum_violations": [] + } + } + ] +} diff --git a/api/contracts/examples/result-statement-minimal.json b/api/contracts/examples/result-statement-minimal.json new file mode 100644 index 0000000..484f222 --- /dev/null +++ b/api/contracts/examples/result-statement-minimal.json @@ -0,0 +1,50 @@ +{ + "schema_version": "1.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", + "raw_s3_key": "raw/8d6b1f30-2e94-4a7c-b5d8-0f1e9c3a6b03.json", + "documents": [ + { + "document_type": "BALANCE", + "locator": "pages:1-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": "тыс. руб." + }, + "columns": [ + { "column_index": 0, "label": "На 30 сентября 2025 г.", "period_date": "2025-09-30" }, + { "column_index": 1, "label": "На 31 декабря 2024 г.", "period_date": "2024-12-31" } + ], + "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 } + ] + } + ] + } + ] +} diff --git a/api/contracts/recognition-request.schema.json b/api/contracts/recognition-request.schema.json new file mode 100644 index 0000000..780573c --- /dev/null +++ b/api/contracts/recognition-request.schema.json @@ -0,0 +1,28 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "arm-ka/recognition-request/v1", + "title": "Запрос распознавания файла (arm-core → tabular-parser / scan-gateway), контракт v1.0", + "description": "Тело сообщения на subject recognition.tabular.request или recognition.image.request; subject должен соответствовать полю pipeline (tabular → recognition.tabular.request, image → recognition.image.request). Заголовок Nats-Msg-Id = : (не поле тела). Один файл (stored_file) → один job.", + "$comment": "Версионирование: schema_version = MAJOR.MINOR. MINOR (1.1) — необязательные поля и новые значения enum; MAJOR (2.0) — удаление/переименование полей, смена типов или семантики. Потребитель принимает тот же MAJOR и любой MINOR. Новый MINOR раскатывается сначала на потребителей (ядро и сервисы обновляют копии схем раньше, чем производитель начнёт слать 1.1), потому что закрытые enum отвергают незнакомые значения.", + "type": "object", + "required": ["schema_version", "job_id", "file_id", "pipeline", "file_type", "original_name", "s3", "reply_deadline", "attempt"], + "properties": { + "schema_version": { "type": "string", "pattern": "^1\\.\\d+$", "description": "MAJOR.MINOR; производитель контракта v1.0 пишет \"1.0\"" }, + "job_id": { "type": "string", "format": "uuid", "description": "recognition_job.id" }, + "file_id": { "type": "string", "format": "uuid", "description": "stored_file.id" }, + "pipeline": { "enum": ["tabular", "image"], "description": "конвейер распознавания; определяет subject запроса" }, + "file_type": { "enum": ["XLS", "XLSX", "XLSB", "PDF", "PNG", "JPG"] }, + "original_name": { "type": "string", "minLength": 1, "description": "имя файла, как загрузил пользователь" }, + "s3": { + "type": "object", + "description": "исходный файл в роли uploads", + "required": ["bucket", "key"], + "properties": { + "bucket": { "type": "string", "minLength": 1 }, + "key": { "type": "string", "minLength": 1 } + } + }, + "reply_deadline": { "type": "string", "format": "date-time", "description": "срок ответа, UTC" }, + "attempt": { "type": "integer", "minimum": 1, "description": "номер попытки, с 1" } + } +} diff --git a/api/contracts/recognition-result-message.schema.json b/api/contracts/recognition-result-message.schema.json new file mode 100644 index 0000000..1f20727 --- /dev/null +++ b/api/contracts/recognition-result-message.schema.json @@ -0,0 +1,70 @@ +{ + "$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", + "description": "Тело сообщения на subject recognition.result. Заголовок Nats-Msg-Id (не поле тела) на этом subject имеет две формы: ack:: — для status = processing; result:: — для done, needs_check и error. Форма запроса : описана в схеме 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 отвергают незнакомые значения.", + "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\"" }, + "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"] }, + "service": { + "type": "object", + "required": ["name", "version"], + "properties": { + "name": { "enum": ["tabular-parser", "scan-gateway"] }, + "version": { "type": "string" } + } + }, + "result_s3": { + "type": "object", + "description": "файл результата (recognition-result): results/{file_id}/{job_id}/result.json", + "required": ["bucket", "key"], + "properties": { + "bucket": { "type": "string", "minLength": 1 }, + "key": { "type": "string", "minLength": 1 } + } + }, + "result_schema_version": { "type": "string", "pattern": "^\\d+\\.\\d+$", "description": "schema_version файла результата, MAJOR.MINOR" }, + "error": { + "type": "object", + "required": ["code", "message"], + "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" } + } + }, + "raw_s3_key": { "type": ["string", "null"], "description": "scan-gateway: raw/{job_id}.json — сырой ответ ИС Сканирование; в БД не регистрируется" }, + "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": { "type": "string" } } + } + } + }, + "allOf": [ + { + "if": { + "properties": { "status": { "enum": ["done", "needs_check"] } }, + "required": ["status"] + }, + "then": { "required": ["result_s3", "result_schema_version"] } + }, + { + "if": { + "properties": { "status": { "const": "error" } }, + "required": ["status"] + }, + "then": { "required": ["error"] } + } + ] +} diff --git a/api/contracts/recognition-result.schema.json b/api/contracts/recognition-result.schema.json new file mode 100644 index 0000000..c5eea87 --- /dev/null +++ b/api/contracts/recognition-result.schema.json @@ -0,0 +1,197 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "arm-ka/recognition-result/v1", + "title": "Результат распознавания файла (tabular-parser / scan-gateway), контракт v1.0", + "description": "Один файл (stored_file) → один job → N документов. Сервисы не знают БД: только структура файла. Нормализация счетов, резолв контрагентов, проверки — в arm-core.", + "$comment": "Версионирование: schema_version = MAJOR.MINOR. MINOR (1.1) — необязательные поля и новые значения enum; MAJOR (2.0) — удаление/переименование полей, смена типов или семантики. Потребитель принимает тот же MAJOR и любой MINOR. Новый MINOR раскатывается сначала на потребителей (ядро и сервисы обновляют копии схем раньше, чем производитель начнёт слать 1.1), потому что закрытые enum отвергают незнакомые значения.", + "type": "object", + "required": ["schema_version", "job_id", "file_id", "service", "status", "documents"], + "properties": { + "schema_version": { "type": "string", "pattern": "^1\\.\\d+$", "description": "MAJOR.MINOR; производитель контракта v1.0 пишет \"1.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" } + } + }, + "status": { "enum": ["done", "needs_check", "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": { "type": "string" } } + } + }, + "raw_s3_key": { "type": ["string", "null"], "description": "scan-gateway: raw/{job_id}.json — сырой ответ ИС Сканирование; в БД не регистрируется" }, + "documents": { + "type": "array", + "items": { "oneOf": [ { "$ref": "#/$defs/osvDocument" }, { "$ref": "#/$defs/statementDocument" } ] } + } + }, + "if": { + "properties": { "status": { "enum": ["done", "needs_check"] } }, + "required": ["status"] + }, + "then": { + "properties": { "documents": { "minItems": 1 } } + }, + "$defs": { + "money": { "type": ["string", "null"], "pattern": "^-?\\d+(\\.\\d{1,2})?$", "description": "Decimal как строка, чтобы не терять копейки в JSON" }, + "osvDocument": { + "type": "object", + "required": ["document_type", "locator", "header", "group_schema", "rows"], + "properties": { + "document_type": { "const": "OSV" }, + "locator": { "type": "string", "examples": ["sheet:Лист_1"] }, + "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)" }, + "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" } + } + } + }, + "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" } } + } + } + } + }, + "statementDocument": { + "type": "object", + "required": ["document_type", "locator", "header", "columns", "lines"], + "properties": { + "document_type": { "enum": ["BALANCE", "INCOME", "OTHER"] }, + "locator": { "type": "string", "examples": ["pages:1-2"] }, + "header": { + "type": "object", + "properties": { + "form_name": { "type": "string" }, + "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"] }, + "report_date": { "type": ["string", "null"], "format": "date" }, + "units": { "type": ["string", "null"], "examples": ["тыс. руб."] }, + "okud": { "type": ["string", "null"] }, + "okpo": { "type": ["string", "null"] }, + "okopf": { "type": ["string", "null"] }, + "okfs": { "type": ["string", "null"] }, + "address": { "type": ["string", "null"] } + } + }, + "columns": { + "type": "array", + "items": { + "type": "object", + "required": ["column_index", "label"], + "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" } + } + } + } + } + } + } + } + } + } +}