From 013f7b673a1633efdf70c7c44d55ea2ccc252a97 Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Fri, 6 Nov 2026 23:05:15 +0400 Subject: [PATCH] =?UTF-8?q?=D0=95=D0=A5:=20=D1=81=D1=85=D0=B5=D0=BC=D0=B0?= =?UTF-8?q?=20ex-payload-v1,=20=D0=BF=D1=80=D0=B8=D0=BC=D0=B5=D1=80=D1=8B,?= =?UTF-8?q?=20=D0=BF=D1=80=D0=BE=D0=B2=D0=B5=D1=80=D0=BA=D0=B0=20=D0=B2=20?= =?UTF-8?q?validate=5Fcontracts=20(#208)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- contracts/ex-payload-v1.schema.json | 2108 +++++++++++++++++ contracts/examples/ex-payload-chess.json | 105 + .../examples/ex-payload-corrections.json | 85 + contracts/examples/ex-payload-goodwill.json | 60 + .../examples/ex-payload-kfo-balance.json | 637 +++++ contracts/examples/ex-payload-kfo-income.json | 317 +++ .../examples/ex-payload-other-report.json | 25 + .../examples/ex-payload-summary-osv.json | 106 + .../examples/ex-payload-version-card.json | 82 + contracts/examples/ex-payload-vgo-pairs.json | 192 ++ scripts/kfo_contract/gen_ex_payload.py | 343 +++ scripts/kfo_contract/test_ex_payload.py | 63 + scripts/validate_contracts.py | 6 +- 13 files changed, 4127 insertions(+), 2 deletions(-) create mode 100644 contracts/ex-payload-v1.schema.json create mode 100644 contracts/examples/ex-payload-chess.json create mode 100644 contracts/examples/ex-payload-corrections.json create mode 100644 contracts/examples/ex-payload-goodwill.json create mode 100644 contracts/examples/ex-payload-kfo-balance.json create mode 100644 contracts/examples/ex-payload-kfo-income.json create mode 100644 contracts/examples/ex-payload-other-report.json create mode 100644 contracts/examples/ex-payload-summary-osv.json create mode 100644 contracts/examples/ex-payload-version-card.json create mode 100644 contracts/examples/ex-payload-vgo-pairs.json create mode 100644 scripts/kfo_contract/gen_ex_payload.py create mode 100644 scripts/kfo_contract/test_ex_payload.py diff --git a/contracts/ex-payload-v1.schema.json b/contracts/ex-payload-v1.schema.json new file mode 100644 index 0000000..1aae387 --- /dev/null +++ b/contracts/ex-payload-v1.schema.json @@ -0,0 +1,2108 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "ex-payload-v1.schema.json", + "title": "Элемент среза ЕХ: ex_snapshot_item.payload, версия 1.0", + "description": "Конверт замороженных данных элемента среза ЕХ (context/05-domain-rules.md §14). data — ответ API экрана рабочей версии на момент верификации без полей редактирования; у primary_document payload нет. Сгенерировано scripts/kfo_contract/gen_ex_payload.py — руками не править.", + "type": "object", + "additionalProperties": false, + "required": [ + "schema_version", + "item_type", + "units", + "generated_at", + "source_version_no", + "data" + ], + "properties": { + "schema_version": { + "const": "1.0" + }, + "item_type": { + "type": "string", + "enum": [ + "summary_osv", + "vgo_pairs", + "chess", + "kfo_balance", + "kfo_income", + "goodwill", + "corrections", + "other_report", + "version_card" + ] + }, + "units": { + "type": "string", + "enum": [ + "rub", + "thousand_rub", + "none" + ] + }, + "generated_at": { + "type": "string", + "format": "date-time" + }, + "source_version_no": { + "type": "integer", + "minimum": 1 + }, + "data": { + "type": "object" + } + }, + "allOf": [ + { + "if": { + "properties": { + "item_type": { + "const": "summary_osv" + } + } + }, + "then": { + "properties": { + "units": { + "const": "rub" + }, + "data": { + "$ref": "#/$defs/SummaryOsvData" + } + } + } + }, + { + "if": { + "properties": { + "item_type": { + "const": "vgo_pairs" + } + } + }, + "then": { + "properties": { + "units": { + "const": "rub" + }, + "data": { + "$ref": "#/$defs/VgoPairsData" + } + } + } + }, + { + "if": { + "properties": { + "item_type": { + "const": "chess" + } + } + }, + "then": { + "properties": { + "units": { + "const": "rub" + }, + "data": { + "$ref": "#/$defs/ChessData" + } + } + } + }, + { + "if": { + "properties": { + "item_type": { + "const": "kfo_balance" + } + } + }, + "then": { + "properties": { + "units": { + "const": "thousand_rub" + }, + "data": { + "$ref": "#/$defs/KfoBalanceData" + } + } + } + }, + { + "if": { + "properties": { + "item_type": { + "const": "kfo_income" + } + } + }, + "then": { + "properties": { + "units": { + "const": "thousand_rub" + }, + "data": { + "$ref": "#/$defs/KfoIncomeData" + } + } + } + }, + { + "if": { + "properties": { + "item_type": { + "const": "goodwill" + } + } + }, + "then": { + "properties": { + "units": { + "const": "rub" + }, + "data": { + "$ref": "#/$defs/GoodwillData" + } + } + } + }, + { + "if": { + "properties": { + "item_type": { + "const": "corrections" + } + } + }, + "then": { + "properties": { + "units": { + "const": "rub" + }, + "data": { + "$ref": "#/$defs/CorrectionsData" + } + } + } + }, + { + "if": { + "properties": { + "item_type": { + "const": "other_report" + } + } + }, + "then": { + "properties": { + "units": { + "const": "thousand_rub" + }, + "data": { + "$ref": "#/$defs/OtherReportData" + } + } + } + }, + { + "if": { + "properties": { + "item_type": { + "const": "version_card" + } + } + }, + "then": { + "properties": { + "units": { + "const": "none" + }, + "data": { + "$ref": "#/$defs/VersionCardData" + } + } + } + } + ], + "$defs": { + "KfoLineCompanyOut": { + "type": "object", + "additionalProperties": true, + "properties": { + "legal_entity_id": { + "type": "string", + "format": "uuid" + }, + "client_amount": { + "type": "string", + "pattern": "^-?\\d{1,16}\\.\\d{2}$", + "examples": [ + "588500.00" + ], + "description": "«По данным клиента», тыс. руб.; строки расходов приведены к −|x| (§8 «КФО» п. 2)" + }, + "vgo_amount": { + "type": "string", + "pattern": "^-?\\d{1,16}\\.\\d{2}$", + "examples": [ + "588500.00" + ], + "description": "«В т. ч. взаимные», тыс. руб. до целых, со знаком строки (п. 6)" + }, + "cleaned_amount": { + "type": "string", + "pattern": "^-?\\d{1,16}\\.\\d{2}$", + "examples": [ + "588500.00" + ], + "description": "«Очищенные показатели» = client_amount − vgo_amount" + }, + "share_percent": { + "type": [ + "string", + "null" + ], + "pattern": "^-?\\d{1,3}\\.\\d{2}$", + "description": "«Доля в показателе по группе, %» от bank_amount строки, ROUND_HALF_UP; null при нулевом итоге или |доля| ≥ 1000 %" + } + }, + "required": [ + "legal_entity_id", + "client_amount", + "vgo_amount", + "cleaned_amount", + "share_percent" + ] + }, + "KfoLineOut": { + "type": "object", + "additionalProperties": true, + "properties": { + "line_code": { + "type": "string", + "description": "код строки бланка редакции на отчётную дату или GOODWILL" + }, + "line_name": { + "type": "string" + }, + "ordinal": { + "type": "integer", + "description": "порядок строки в бланке" + }, + "is_total": { + "type": "boolean", + "description": "итоговая строка бланка (1100, 1600, 2100 …)" + }, + "is_threshold_base": { + "type": "boolean", + "description": "строка — база порога (1300, 1600, 2110 или 2200), подсветка при превышении" + }, + "companies": { + "type": "array", + "items": { + "$ref": "#/$defs/KfoLineCompanyOut" + }, + "description": "по компаниям в порядке KfoResultOut.companies; у GOODWILL — пусто" + }, + "sum_amount": { + "type": "string", + "pattern": "^-?\\d{1,16}\\.\\d{2}$", + "examples": [ + "588500.00" + ], + "description": "Σ client_amount" + }, + "vgo_amount": { + "type": "string", + "pattern": "^-?\\d{1,16}\\.\\d{2}$", + "examples": [ + "588500.00" + ], + "description": "Σ vgo_amount = eliminated_auto + eliminated_manual" + }, + "eliminated_auto": { + "type": "string", + "pattern": "^-?\\d{1,16}\\.\\d{2}$", + "examples": [ + "588500.00" + ] + }, + "eliminated_manual": { + "type": "string", + "pattern": "^-?\\d{1,16}\\.\\d{2}$", + "examples": [ + "588500.00" + ] + }, + "goodwill_adjust": { + "type": "string", + "pattern": "^-?\\d{1,16}\\.\\d{2}$", + "examples": [ + "588500.00" + ], + "description": "Σ G — у GOODWILL и итогов 1100, 1600" + }, + "bank_amount": { + "type": "string", + "pattern": "^-?\\d{1,16}\\.\\d{2}$", + "examples": [ + "588500.00" + ], + "description": "«КФО, подготовленная банком» = sum_amount − vgo_amount + goodwill_adjust" + }, + "deviation_amount": { + "type": [ + "string", + "null" + ], + "pattern": "^-?\\d{1,16}\\.\\d{2}$", + "description": "«Расхождения» (§8 «КФО» п. 9); у итогов — null" + }, + "comment": { + "type": [ + "string", + "null" + ], + "maxLength": 2000 + } + }, + "required": [ + "line_code", + "line_name", + "ordinal", + "is_total", + "is_threshold_base", + "companies", + "sum_amount", + "vgo_amount", + "eliminated_auto", + "eliminated_manual", + "goodwill_adjust", + "bank_amount", + "deviation_amount", + "comment" + ] + }, + "KfoStatementOut": { + "type": "object", + "additionalProperties": true, + "properties": { + "lines": { + "type": "array", + "items": { + "$ref": "#/$defs/KfoLineOut" + } + } + }, + "required": [ + "lines" + ] + }, + "KfoThresholdOut": { + "type": "object", + "additionalProperties": true, + "properties": { + "deviation_total": { + "type": "string", + "pattern": "^-?\\d{1,16}\\.\\d{2}$", + "examples": [ + "588500.00" + ] + }, + "base_kind": { + "type": "string", + "enum": [ + "NET_ASSETS", + "BALANCE_TOTAL", + "SALES_PROFIT", + "REVENUE" + ] + }, + "base_amount": { + "type": "string", + "pattern": "^-?\\d{1,16}\\.\\d{2}$", + "examples": [ + "588500.00" + ] + }, + "limit_percent": { + "type": "string", + "enum": [ + "10.00", + "1.00" + ] + }, + "limit_amount": { + "type": "string", + "pattern": "^-?\\d{1,16}\\.\\d{2}$", + "examples": [ + "588500.00" + ] + }, + "deviation_percent": { + "type": [ + "string", + "null" + ], + "pattern": "^-?\\d{1,5}\\.\\d{2}$", + "description": "deviation_total / base_amount × 100; null при нулевой базе" + }, + "exceeded": { + "type": "boolean", + "description": "deviation_total > limit_amount (равно — норма)" + } + }, + "required": [ + "deviation_total", + "base_kind", + "base_amount", + "limit_percent", + "limit_amount", + "deviation_percent", + "exceeded" + ] + }, + "KfoSummaryOut": { + "type": "object", + "additionalProperties": true, + "properties": { + "balance": { + "$ref": "#/$defs/KfoThresholdOut" + }, + "income": { + "$ref": "#/$defs/KfoThresholdOut" + } + }, + "required": [ + "balance", + "income" + ] + }, + "KfoCompanyOut": { + "type": "object", + "additionalProperties": true, + "properties": { + "legal_entity_id": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + }, + "inn": { + "type": "string" + } + }, + "required": [ + "legal_entity_id", + "name", + "inn" + ] + }, + "KfoCorrectionsOut": { + "type": "object", + "additionalProperties": true, + "properties": { + "count": { + "type": "integer", + "minimum": 0 + }, + "amount": { + "type": "string", + "pattern": "^-?\\d{1,16}\\.\\d{2}$", + "examples": [ + "588500.00" + ] + } + }, + "required": [ + "count", + "amount" + ], + "description": "агрегаты с применённой корректировкой и Σ их исключений, тыс. руб." + }, + "KfoRequisitesOut": { + "type": "object", + "additionalProperties": true, + "properties": { + "company_group_name": { + "type": "string" + }, + "related_group_name": { + "type": [ + "string", + "null" + ] + }, + "reporting_date": { + "type": "string", + "format": "date" + }, + "version_no": { + "type": "integer" + }, + "version_status": { + "type": "string", + "description": "consolidation_version.status (enum consolidation_status DBML)", + "enum": [ + "INITIATED", + "DOCUMENTS_PROCESSING", + "DATA_CORRECTION", + "CONSOLIDATION_RF", + "ADJUSTMENT", + "VERIFICATION_RF", + "CONSOLIDATION_GO", + "VERIFICATION_GO", + "VERIFIED", + "CANCELLED", + "REJECTED", + "ARCHIVED" + ] + }, + "scenario": { + "type": "string", + "enum": [ + "CREATE", + "CONFIRM" + ] + }, + "approach": { + "type": "string", + "enum": [ + "BASIC", + "EXTENDED" + ] + }, + "methodology_deviations": { + "type": [ + "string", + "null" + ], + "maxLength": 4000 + }, + "uob_decision_no": { + "type": [ + "string", + "null" + ] + }, + "uob_decision_date": { + "type": [ + "string", + "null" + ], + "format": "date" + }, + "corrections_balance": { + "$ref": "#/$defs/KfoCorrectionsOut" + }, + "corrections_income": { + "$ref": "#/$defs/KfoCorrectionsOut" + } + }, + "required": [ + "company_group_name", + "related_group_name", + "reporting_date", + "version_no", + "version_status", + "scenario", + "approach", + "methodology_deviations", + "uob_decision_no", + "uob_decision_date", + "corrections_balance", + "corrections_income" + ] + }, + "KfoGoodwillOut": { + "type": "object", + "additionalProperties": true, + "properties": { + "enabled": { + "type": "boolean" + }, + "total": { + "type": "string", + "pattern": "^-?\\d{1,16}\\.\\d{2}$", + "examples": [ + "588500.00" + ] + } + }, + "required": [ + "enabled", + "total" + ] + }, + "KfoWarningOut": { + "type": "object", + "additionalProperties": true, + "properties": { + "code": { + "type": "string", + "enum": [ + "FORM_MISSING", + "FORM_AMBIGUOUS", + "FORM_UNITS_UNKNOWN", + "LINE_OUTSIDE_BLANK", + "UNALLOCATED_ROWS", + "INCOME_RULES_MISSING", + "GOODWILL_NOT_APPLIED", + "GOODWILL_ROW_REASSIGNED", + "CORRECTION_SUPERSEDED_BY_GOODWILL" + ] + }, + "message": { + "type": "string" + }, + "legal_entity_id": { + "type": [ + "string", + "null" + ], + "format": "uuid" + }, + "statement_type": { + "type": [ + "string", + "null" + ], + "enum": [ + "balance", + "income", + null + ] + }, + "count": { + "type": [ + "integer", + "null" + ] + } + }, + "required": [ + "code", + "message" + ] + }, + "KfoBalanceData": { + "type": "object", + "properties": { + "version_id": { + "type": "string", + "format": "uuid" + }, + "computed_at": { + "type": "string", + "format": "date-time" + }, + "is_frozen": { + "type": "boolean" + }, + "units": { + "type": "string", + "const": "thousand_rub" + }, + "companies": { + "type": "array", + "items": { + "$ref": "#/$defs/KfoCompanyOut" + } + }, + "requisites": { + "$ref": "#/$defs/KfoRequisitesOut" + }, + "summary": { + "$ref": "#/$defs/KfoSummaryOut" + }, + "justification_required": { + "type": "boolean" + }, + "threshold_justification": { + "type": [ + "string", + "null" + ], + "maxLength": 4000 + }, + "goodwill": { + "$ref": "#/$defs/KfoGoodwillOut" + }, + "warnings": { + "type": "array", + "items": { + "$ref": "#/$defs/KfoWarningOut" + } + }, + "balance": { + "$ref": "#/$defs/KfoStatementOut" + } + }, + "required": [ + "version_id", + "computed_at", + "is_frozen", + "units", + "companies", + "requisites", + "summary", + "justification_required", + "threshold_justification", + "goodwill", + "warnings", + "balance" + ], + "additionalProperties": true, + "description": "KfoResultOut без editable/read_only_reason, только отчёт ББ", + "not": { + "required": [ + "income" + ] + } + }, + "KfoIncomeData": { + "type": "object", + "properties": { + "version_id": { + "type": "string", + "format": "uuid" + }, + "computed_at": { + "type": "string", + "format": "date-time" + }, + "is_frozen": { + "type": "boolean" + }, + "units": { + "type": "string", + "const": "thousand_rub" + }, + "companies": { + "type": "array", + "items": { + "$ref": "#/$defs/KfoCompanyOut" + } + }, + "requisites": { + "$ref": "#/$defs/KfoRequisitesOut" + }, + "summary": { + "$ref": "#/$defs/KfoSummaryOut" + }, + "justification_required": { + "type": "boolean" + }, + "threshold_justification": { + "type": [ + "string", + "null" + ], + "maxLength": 4000 + }, + "goodwill": { + "$ref": "#/$defs/KfoGoodwillOut" + }, + "warnings": { + "type": "array", + "items": { + "$ref": "#/$defs/KfoWarningOut" + } + }, + "income": { + "$ref": "#/$defs/KfoStatementOut" + } + }, + "required": [ + "version_id", + "computed_at", + "is_frozen", + "units", + "companies", + "requisites", + "summary", + "justification_required", + "threshold_justification", + "goodwill", + "warnings", + "income" + ], + "additionalProperties": true, + "description": "KfoResultOut без editable/read_only_reason, только отчёт ОФР", + "not": { + "required": [ + "balance" + ] + } + }, + "Ref": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + } + }, + "required": [ + "id", + "name" + ], + "additionalProperties": true + }, + "UserRef": { + "type": "object", + "properties": { + "user_id": { + "type": [ + "string", + "null" + ], + "format": "uuid" + }, + "full_name": { + "type": "string" + } + }, + "required": [ + "user_id", + "full_name" + ], + "additionalProperties": true, + "description": "Пользователь с ФИО на дату верификации (user_id — справочно, app_user не удаляется)" + }, + "ConsolidatedRowOut": { + "type": "object", + "properties": { + "legal_entity_id": { + "type": "string", + "format": "uuid" + }, + "legal_entity_name": { + "type": "string" + }, + "legal_entity_inn": { + "type": [ + "string", + "null" + ] + }, + "account_code": { + "type": [ + "string", + "null" + ] + }, + "period": { + "type": "string" + }, + "is_cumulative": { + "type": "boolean" + }, + "counterparty_name": { + "type": [ + "string", + "null" + ] + }, + "counterparty_inn": { + "type": [ + "string", + "null" + ] + }, + "counterparty_id": { + "type": [ + "string", + "null" + ], + "format": "uuid" + }, + "counterparty_entity_name": { + "type": [ + "string", + "null" + ] + }, + "counterparty_entity_inn": { + "type": [ + "string", + "null" + ] + }, + "counterparty_type": { + "type": "string", + "enum": [ + "GSK", + "GK", + "EXTERNAL" + ] + }, + "match_kind": { + "type": [ + "string", + "null" + ] + }, + "opening_debit": { + "type": [ + "string", + "null" + ], + "pattern": "^-?\\d{1,16}\\.\\d{2}$" + }, + "opening_credit": { + "type": [ + "string", + "null" + ], + "pattern": "^-?\\d{1,16}\\.\\d{2}$" + }, + "turnover_debit": { + "type": [ + "string", + "null" + ], + "pattern": "^-?\\d{1,16}\\.\\d{2}$" + }, + "turnover_credit": { + "type": [ + "string", + "null" + ], + "pattern": "^-?\\d{1,16}\\.\\d{2}$" + }, + "closing_debit": { + "type": [ + "string", + "null" + ], + "pattern": "^-?\\d{1,16}\\.\\d{2}$" + }, + "closing_credit": { + "type": [ + "string", + "null" + ], + "pattern": "^-?\\d{1,16}\\.\\d{2}$" + } + }, + "required": [ + "legal_entity_id", + "legal_entity_name", + "legal_entity_inn", + "account_code", + "period", + "is_cumulative", + "counterparty_name", + "counterparty_inn", + "counterparty_id", + "counterparty_entity_name", + "counterparty_entity_inn", + "counterparty_type", + "match_kind", + "opening_debit", + "opening_credit", + "turnover_debit", + "turnover_credit", + "closing_debit", + "closing_credit" + ], + "additionalProperties": true, + "description": "ConsolidatedRowOut (dev/api-contract-slice-4.md §3); entry_ids, warnings — справочно, необязательны" + }, + "OsvGapOut": { + "type": "object", + "properties": { + "legal_entity_id": { + "type": "string", + "format": "uuid" + }, + "legal_entity_name": { + "type": "string" + }, + "account_code": { + "type": "string" + }, + "missing_quarters": { + "type": "array", + "items": { + "type": "integer", + "minimum": 1, + "maximum": 4 + } + } + }, + "required": [ + "legal_entity_id", + "legal_entity_name", + "account_code", + "missing_quarters" + ], + "additionalProperties": true + }, + "OsvSummaryOut": { + "type": "object", + "properties": { + "rows_total": { + "type": "integer" + }, + "gaps": { + "type": "array", + "items": { + "$ref": "#/$defs/OsvGapOut" + } + } + }, + "required": [ + "rows_total", + "gaps" + ], + "additionalProperties": true + }, + "SummaryOsvData": { + "type": "object", + "properties": { + "rows": { + "type": "array", + "items": { + "$ref": "#/$defs/ConsolidatedRowOut" + } + }, + "summary": { + "$ref": "#/$defs/OsvSummaryOut" + } + }, + "required": [ + "rows", + "summary" + ], + "additionalProperties": true + }, + "CorrectionEntryOut": { + "type": "object", + "properties": { + "legal_entity_id": { + "type": "string", + "format": "uuid" + }, + "counterparty_id": { + "type": "string", + "format": "uuid" + }, + "account_code": { + "type": "string" + }, + "side": { + "type": "string", + "enum": [ + "D", + "K" + ] + }, + "amount": { + "type": "string", + "pattern": "^-?\\d{1,16}\\.\\d{2}$" + }, + "decision": { + "type": "string" + } + }, + "required": [ + "legal_entity_id", + "counterparty_id", + "account_code", + "side", + "amount", + "decision" + ], + "additionalProperties": true, + "description": "строка ОСВ агрегата (нужна выгрузке таблицы корректировок)" + }, + "AggregateSideOut": { + "type": "object", + "properties": { + "article_line": { + "type": [ + "string", + "null" + ] + }, + "accounts": { + "type": "array", + "items": { + "type": "string" + } + }, + "amount": { + "type": "string", + "pattern": "^-?\\d{1,16}\\.\\d{2}$" + }, + "entries": { + "type": "array", + "items": { + "$ref": "#/$defs/CorrectionEntryOut" + } + } + }, + "required": [ + "article_line", + "accounts", + "amount", + "entries" + ], + "additionalProperties": true + }, + "CorrectionLineOut": { + "type": "object", + "properties": { + "line_code": { + "type": "string" + }, + "amount": { + "type": "string", + "pattern": "^-?\\d{1,16}\\.\\d{2}$" + } + }, + "required": [ + "line_code", + "amount" + ], + "additionalProperties": true + }, + "CorrectionOut": { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": [ + "APPLIED" + ] + }, + "version_no": { + "type": "integer" + }, + "debit": { + "type": "array", + "items": { + "$ref": "#/$defs/CorrectionLineOut" + } + }, + "credit": { + "type": "array", + "items": { + "$ref": "#/$defs/CorrectionLineOut" + } + }, + "comment": { + "type": "string" + }, + "updated_by": {}, + "updated_at": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "status", + "version_no", + "debit", + "credit", + "comment", + "updated_by", + "updated_at" + ], + "additionalProperties": true, + "description": "CorrectionOut; в срезе только APPLIED" + }, + "CorrectionAggregateOut": { + "type": "object", + "properties": { + "rule_no": { + "type": "integer" + }, + "company_1": { + "$ref": "#/$defs/Ref" + }, + "company_2": { + "$ref": "#/$defs/Ref" + }, + "side_1": { + "$ref": "#/$defs/AggregateSideOut" + }, + "side_2": { + "$ref": "#/$defs/AggregateSideOut" + }, + "deviation_amount": { + "type": "string", + "pattern": "^-?\\d{1,16}\\.\\d{2}$" + }, + "deviation_percent": { + "type": [ + "string", + "null" + ], + "pattern": "^-?\\d{1,5}\\.\\d{2}$" + }, + "exceeds_threshold": { + "type": "boolean" + }, + "status": { + "type": "string", + "enum": [ + "AUTO", + "DISCREPANCY", + "ONE_SIDED", + "MANUAL" + ] + }, + "eliminated_amount": { + "type": "string", + "pattern": "^-?\\d{1,16}\\.\\d{2}$" + }, + "correction": { + "anyOf": [ + { + "$ref": "#/$defs/CorrectionOut" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "rule_no", + "company_1", + "company_2", + "side_1", + "side_2", + "deviation_amount", + "deviation_percent", + "exceeds_threshold", + "status", + "eliminated_amount", + "correction" + ], + "additionalProperties": true + }, + "UnallocatedEntryOut": { + "type": "object", + "properties": { + "owner": { + "$ref": "#/$defs/Ref" + }, + "counterparty": { + "$ref": "#/$defs/Ref" + }, + "account_code": { + "type": "string" + }, + "side": { + "type": "string", + "enum": [ + "D", + "K" + ] + }, + "amount": { + "type": "string", + "pattern": "^-?\\d{1,16}\\.\\d{2}$" + }, + "reason": { + "type": "string" + }, + "excluded": { + "type": "boolean" + } + }, + "required": [ + "owner", + "counterparty", + "account_code", + "side", + "amount", + "reason", + "excluded" + ], + "additionalProperties": true + }, + "CorrectionTotalsOut": { + "type": "object", + "properties": { + "amount_1": { + "type": "string", + "pattern": "^-?\\d{1,16}\\.\\d{2}$" + }, + "amount_2": { + "type": "string", + "pattern": "^-?\\d{1,16}\\.\\d{2}$" + }, + "deviation_amount": { + "type": "string", + "pattern": "^-?\\d{1,16}\\.\\d{2}$" + }, + "correction_debit": { + "type": "string", + "pattern": "^-?\\d{1,16}\\.\\d{2}$" + }, + "correction_credit": { + "type": "string", + "pattern": "^-?\\d{1,16}\\.\\d{2}$" + } + }, + "required": [ + "amount_1", + "amount_2", + "deviation_amount", + "correction_debit", + "correction_credit" + ], + "additionalProperties": true + }, + "CorrectionTableData": { + "type": "object", + "properties": { + "statement_type": { + "type": "string", + "enum": [ + "balance", + "income" + ] + }, + "rules_version_no": { + "type": "integer" + }, + "aggregates": { + "type": "array", + "items": { + "$ref": "#/$defs/CorrectionAggregateOut" + } + }, + "unallocated": { + "type": "array", + "items": { + "$ref": "#/$defs/UnallocatedEntryOut" + } + }, + "totals": { + "$ref": "#/$defs/CorrectionTotalsOut" + } + }, + "required": [ + "statement_type", + "rules_version_no", + "aggregates", + "unallocated", + "totals" + ], + "additionalProperties": true, + "description": "CorrectionTableOut без editable/read_only_reason" + }, + "VgoPairsData": { + "type": "object", + "properties": { + "balance": { + "$ref": "#/$defs/CorrectionTableData" + }, + "income": { + "anyOf": [ + { + "$ref": "#/$defs/CorrectionTableData" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "balance", + "income" + ], + "additionalProperties": true + }, + "CorrectionsData": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "statement_type": { + "type": "string", + "enum": [ + "balance", + "income" + ] + }, + "rule_no": { + "type": "integer" + }, + "company_1": { + "$ref": "#/$defs/Ref" + }, + "company_2": { + "$ref": "#/$defs/Ref" + }, + "correction": { + "$ref": "#/$defs/CorrectionOut" + } + }, + "required": [ + "statement_type", + "rule_no", + "company_1", + "company_2", + "correction" + ], + "additionalProperties": true + } + } + }, + "required": [ + "items" + ], + "additionalProperties": true + }, + "MatrixCellOut": { + "type": "object", + "properties": { + "company_a_id": { + "type": "string", + "format": "uuid" + }, + "company_b_id": { + "type": "string", + "format": "uuid" + }, + "amount_a": { + "type": "string", + "pattern": "^-?\\d{1,16}\\.\\d{2}$" + }, + "amount_b": { + "type": "string", + "pattern": "^-?\\d{1,16}\\.\\d{2}$" + }, + "variance_amount": { + "type": "string", + "pattern": "^-?\\d{1,16}\\.\\d{2}$" + }, + "variance_percent": { + "type": [ + "string", + "null" + ], + "pattern": "^-?\\d{1,5}\\.\\d{2}$" + }, + "exceeds_threshold": { + "type": "boolean" + }, + "one_sided": { + "type": "boolean" + } + }, + "required": [ + "company_a_id", + "company_b_id", + "amount_a", + "amount_b", + "variance_amount", + "variance_percent", + "exceeds_threshold", + "one_sided" + ], + "additionalProperties": true + }, + "IntercompanyMatrixOut": { + "type": "object", + "properties": { + "companies": { + "type": "array", + "items": { + "$ref": "#/$defs/Ref" + } + }, + "cells": { + "type": "array", + "items": { + "$ref": "#/$defs/MatrixCellOut" + } + } + }, + "required": [ + "companies", + "cells" + ], + "additionalProperties": true, + "description": "IntercompanyMatrixOut; osv_entry_ids_* — справочно" + }, + "ResultTableOut": { + "type": "object", + "properties": { + "companies": { + "type": "array", + "items": { + "$ref": "#/$defs/Ref" + } + }, + "articles": { + "type": "array", + "items": { + "type": "object", + "properties": { + "code": { + "type": "string" + }, + "name": { + "type": "string" + }, + "amounts": { + "type": "array", + "items": { + "type": "object", + "properties": { + "company_id": { + "type": "string", + "format": "uuid" + }, + "amount": { + "type": "string", + "pattern": "^-?\\d{1,16}\\.\\d{2}$" + } + }, + "required": [ + "company_id", + "amount" + ], + "additionalProperties": true + } + }, + "group_total": { + "type": "string", + "pattern": "^-?\\d{1,16}\\.\\d{2}$" + } + }, + "required": [ + "code", + "name", + "amounts", + "group_total" + ], + "additionalProperties": true + } + } + }, + "required": [ + "companies", + "articles" + ], + "additionalProperties": true + }, + "ChessData": { + "type": "object", + "properties": { + "matrix_all": { + "$ref": "#/$defs/IntercompanyMatrixOut" + }, + "presets": { + "type": "array", + "items": { + "type": "object", + "properties": { + "rule_no": { + "type": "integer" + }, + "matrix": { + "$ref": "#/$defs/IntercompanyMatrixOut" + } + }, + "required": [ + "rule_no", + "matrix" + ], + "additionalProperties": true + } + }, + "result_table": { + "$ref": "#/$defs/ResultTableOut" + } + }, + "required": [ + "matrix_all", + "presets", + "result_table" + ], + "additionalProperties": true + }, + "GoodwillTotalsOut": { + "type": "object", + "properties": { + "financial_investment_amount": { + "type": [ + "string", + "null" + ], + "pattern": "^-?\\d{1,16}\\.\\d{2}$" + }, + "charter_capital_amount": { + "type": [ + "string", + "null" + ], + "pattern": "^-?\\d{1,16}\\.\\d{2}$" + }, + "participation_amount": { + "type": [ + "string", + "null" + ], + "pattern": "^-?\\d{1,16}\\.\\d{2}$" + }, + "goodwill_amount": { + "type": [ + "string", + "null" + ], + "pattern": "^-?\\d{1,16}\\.\\d{2}$" + }, + "goodwill_in_kfo_amount": { + "type": [ + "string", + "null" + ], + "pattern": "^-?\\d{1,16}\\.\\d{2}$" + } + }, + "required": [ + "financial_investment_amount", + "charter_capital_amount", + "participation_amount", + "goodwill_amount", + "goodwill_in_kfo_amount" + ], + "additionalProperties": true + }, + "GoodwillRowOut": { + "type": "object", + "properties": { + "head_legal_entity": { + "$ref": "#/$defs/Ref" + }, + "subsidiary_legal_entity": { + "$ref": "#/$defs/Ref" + }, + "ownership_share": { + "type": [ + "string", + "null" + ], + "pattern": "^-?\\d{1,5}\\.\\d{2}$" + }, + "financial_investment_amount": { + "type": [ + "string", + "null" + ], + "pattern": "^-?\\d{1,16}\\.\\d{2}$" + }, + "charter_capital_amount": { + "type": [ + "string", + "null" + ], + "pattern": "^-?\\d{1,16}\\.\\d{2}$" + }, + "additional_capital_amount": { + "type": [ + "string", + "null" + ], + "pattern": "^-?\\d{1,16}\\.\\d{2}$" + }, + "participation_amount": { + "type": [ + "string", + "null" + ], + "pattern": "^-?\\d{1,16}\\.\\d{2}$" + }, + "goodwill_amount": { + "type": [ + "string", + "null" + ], + "pattern": "^-?\\d{1,16}\\.\\d{2}$" + }, + "include_in_kfo": { + "type": "boolean" + }, + "comment": { + "type": [ + "string", + "null" + ] + }, + "has_investments": { + "type": "boolean" + } + }, + "required": [ + "head_legal_entity", + "subsidiary_legal_entity", + "ownership_share", + "financial_investment_amount", + "charter_capital_amount", + "additional_capital_amount", + "participation_amount", + "goodwill_amount", + "include_in_kfo", + "comment", + "has_investments" + ], + "additionalProperties": true + }, + "GoodwillData": { + "type": "object", + "properties": { + "goodwill_enabled": { + "const": true + }, + "rows": { + "type": "array", + "items": { + "$ref": "#/$defs/GoodwillRowOut" + } + }, + "groups": { + "type": "array", + "items": { + "type": "object", + "properties": { + "head_legal_entity": { + "$ref": "#/$defs/Ref" + }, + "totals": { + "$ref": "#/$defs/GoodwillTotalsOut" + } + }, + "required": [ + "head_legal_entity", + "totals" + ], + "additionalProperties": true + } + }, + "totals": { + "$ref": "#/$defs/GoodwillTotalsOut" + } + }, + "required": [ + "goodwill_enabled", + "rows", + "groups", + "totals" + ], + "additionalProperties": true, + "description": "GoodwillTableOut без editable/read_only_reason; пишется только при goodwill_enabled" + }, + "OtherReportData": { + "type": "object", + "properties": { + "report_kind": { + "const": "client_kfo_comparison" + }, + "lines": { + "type": "array", + "items": { + "type": "object", + "properties": { + "statement_type": { + "type": "string", + "enum": [ + "balance", + "income" + ] + }, + "line_code": { + "type": "string" + }, + "line_name": { + "type": "string" + }, + "client_amount": { + "type": [ + "string", + "null" + ], + "pattern": "^-?\\d{1,16}\\.\\d{2}$" + }, + "client_vgo_amount": { + "type": [ + "string", + "null" + ], + "pattern": "^-?\\d{1,16}\\.\\d{2}$" + }, + "bank_amount": { + "type": [ + "string", + "null" + ], + "pattern": "^-?\\d{1,16}\\.\\d{2}$" + }, + "bank_vgo_amount": { + "type": [ + "string", + "null" + ], + "pattern": "^-?\\d{1,16}\\.\\d{2}$" + }, + "deviation_amount": { + "type": [ + "string", + "null" + ], + "pattern": "^-?\\d{1,16}\\.\\d{2}$" + }, + "deviation_percent": { + "type": [ + "string", + "null" + ], + "pattern": "^-?\\d{1,5}\\.\\d{2}$" + }, + "deviation_reason": { + "type": [ + "string", + "null" + ] + }, + "comment": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "statement_type", + "line_code", + "line_name", + "client_amount", + "client_vgo_amount", + "bank_amount", + "bank_vgo_amount", + "deviation_amount", + "deviation_percent", + "deviation_reason", + "comment" + ], + "additionalProperties": true + } + } + }, + "required": [ + "report_kind", + "lines" + ], + "additionalProperties": true + }, + "VersionCardData": { + "type": "object", + "properties": { + "company_group": { + "$ref": "#/$defs/Ref" + }, + "related_group": { + "$ref": "#/$defs/Ref" + }, + "reporting_date": { + "type": "string", + "format": "date" + }, + "year": { + "type": "integer" + }, + "quarter": { + "type": "integer", + "minimum": 1, + "maximum": 4 + }, + "version_no": { + "type": "integer" + }, + "scenario": { + "type": "string", + "enum": [ + "CREATE", + "CONFIRM" + ] + }, + "approach": { + "type": "string", + "enum": [ + "BASIC", + "EXTENDED" + ] + }, + "goodwill_enabled": { + "type": "boolean" + }, + "initiator": { + "$ref": "#/$defs/UserRef" + }, + "initiator_office": { + "$ref": "#/$defs/Ref" + }, + "members": { + "type": "array", + "items": { + "type": "object", + "properties": { + "legal_entity_id": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + }, + "inn": { + "type": "string" + }, + "is_head_entity": { + "type": "boolean" + } + }, + "required": [ + "legal_entity_id", + "name", + "inn", + "is_head_entity" + ], + "additionalProperties": true + } + }, + "history": { + "type": "array", + "items": { + "type": "object", + "properties": { + "from_status": { + "type": [ + "string", + "null" + ], + "enum": [ + "INITIATED", + "DOCUMENTS_PROCESSING", + "DATA_CORRECTION", + "CONSOLIDATION_RF", + "ADJUSTMENT", + "VERIFICATION_RF", + "CONSOLIDATION_GO", + "VERIFICATION_GO", + "VERIFIED", + "CANCELLED", + "REJECTED", + "ARCHIVED", + null + ] + }, + "to_status": { + "type": "string", + "enum": [ + "INITIATED", + "DOCUMENTS_PROCESSING", + "DATA_CORRECTION", + "CONSOLIDATION_RF", + "ADJUSTMENT", + "VERIFICATION_RF", + "CONSOLIDATION_GO", + "VERIFICATION_GO", + "VERIFIED", + "CANCELLED", + "REJECTED", + "ARCHIVED" + ] + }, + "changed_by": { + "$ref": "#/$defs/UserRef" + }, + "changed_at": { + "type": "string", + "format": "date-time" + }, + "comment": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "from_status", + "to_status", + "changed_by", + "changed_at", + "comment" + ], + "additionalProperties": true + } + }, + "verified_by": { + "$ref": "#/$defs/UserRef" + }, + "verified_at": { + "type": "string", + "format": "date-time" + }, + "uob_decision_no": { + "type": [ + "string", + "null" + ] + }, + "uob_decision_date": { + "type": [ + "string", + "null" + ], + "format": "date" + }, + "methodology_deviations": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "company_group", + "related_group", + "reporting_date", + "year", + "quarter", + "version_no", + "scenario", + "approach", + "goodwill_enabled", + "initiator", + "initiator_office", + "members", + "history", + "verified_by", + "verified_at", + "uob_decision_no", + "uob_decision_date", + "methodology_deviations" + ], + "additionalProperties": true, + "description": "Карточка версии на дату верификации (DOC-274687 доп. 4; новый item_type v5.26)" + } + } +} diff --git a/contracts/examples/ex-payload-chess.json b/contracts/examples/ex-payload-chess.json new file mode 100644 index 0000000..2ae86c3 --- /dev/null +++ b/contracts/examples/ex-payload-chess.json @@ -0,0 +1,105 @@ +{ + "schema_version": "1.0", + "item_type": "chess", + "units": "rub", + "generated_at": "2025-11-20T10:05:00Z", + "source_version_no": 1, + "data": { + "matrix_all": { + "companies": [ + { + "id": "11111111-1111-4111-8111-111111111111", + "name": "ООО «Альфа»" + }, + { + "id": "22222222-2222-4222-8222-222222222222", + "name": "ООО «Бета»" + }, + { + "id": "33333333-3333-4333-8333-333333333333", + "name": "ООО «Гамма»" + } + ], + "cells": [ + { + "company_a_id": "11111111-1111-4111-8111-111111111111", + "company_b_id": "22222222-2222-4222-8222-222222222222", + "amount_a": "102000000.00", + "amount_b": "100000400.50", + "variance_amount": "1999599.50", + "variance_percent": "2.00", + "exceeds_threshold": false, + "one_sided": false, + "skipped_osv_entry_count": 0 + } + ] + }, + "presets": [ + { + "rule_no": 6, + "matrix": { + "companies": [ + { + "id": "11111111-1111-4111-8111-111111111111", + "name": "ООО «Альфа»" + }, + { + "id": "22222222-2222-4222-8222-222222222222", + "name": "ООО «Бета»" + }, + { + "id": "33333333-3333-4333-8333-333333333333", + "name": "ООО «Гамма»" + } + ], + "cells": [ + { + "company_a_id": "11111111-1111-4111-8111-111111111111", + "company_b_id": "22222222-2222-4222-8222-222222222222", + "amount_a": "102000000.00", + "amount_b": "100000400.50", + "variance_amount": "1999599.50", + "variance_percent": "2.00", + "exceeds_threshold": false, + "one_sided": false, + "skipped_osv_entry_count": 0 + } + ] + } + } + ], + "result_table": { + "companies": [ + { + "id": "11111111-1111-4111-8111-111111111111", + "name": "ООО «Альфа»" + }, + { + "id": "22222222-2222-4222-8222-222222222222", + "name": "ООО «Бета»" + }, + { + "id": "33333333-3333-4333-8333-333333333333", + "name": "ООО «Гамма»" + } + ], + "articles": [ + { + "code": "1230", + "name": "Дебиторская задолженность", + "group_total": "112000000.00", + "amounts": [ + { + "company_id": "11111111-1111-4111-8111-111111111111", + "amount": "102000000.00" + }, + { + "company_id": "22222222-2222-4222-8222-222222222222", + "amount": "10000000.00" + } + ] + } + ] + } + } +} diff --git a/contracts/examples/ex-payload-corrections.json b/contracts/examples/ex-payload-corrections.json new file mode 100644 index 0000000..387a6ca --- /dev/null +++ b/contracts/examples/ex-payload-corrections.json @@ -0,0 +1,85 @@ +{ + "schema_version": "1.0", + "item_type": "corrections", + "units": "rub", + "generated_at": "2025-11-20T10:05:00Z", + "source_version_no": 1, + "data": { + "items": [ + { + "statement_type": "balance", + "rule_no": 6, + "company_1": { + "id": "22222222-2222-4222-8222-222222222222", + "name": "ООО «Бета»" + }, + "company_2": { + "id": "11111111-1111-4111-8111-111111111111", + "name": "ООО «Альфа»" + }, + "correction": { + "status": "APPLIED", + "version_no": 1, + "debit": [ + { + "line_code": "1230", + "amount": "10000000.00" + } + ], + "credit": [ + { + "line_code": "1520", + "amount": "10000000.00" + } + ], + "comment": "Сверка Бета → Альфа: разница — аванс, исключаем подтверждённое", + "reason_code": null, + "updated_by": { + "user_id": "44444444-4444-4444-8444-444444444444", + "full_name": "Петрова Мария Ивановна" + }, + "updated_at": "2025-11-12T14:30:00Z" + } + }, + { + "statement_type": "balance", + "rule_no": 6, + "company_1": { + "id": "11111111-1111-4111-8111-111111111111", + "name": "ООО «Альфа»" + }, + "company_2": { + "id": "33333333-3333-4333-8333-333333333333", + "name": "ООО «Гамма»" + }, + "correction": { + "status": "APPLIED", + "version_no": 2, + "debit": [ + { + "line_code": "1230", + "amount": "20000000.00" + }, + { + "line_code": "1170", + "amount": "5000000.00" + } + ], + "credit": [ + { + "line_code": "1520", + "amount": "22500000.00" + } + ], + "comment": "Часть долга Гаммы переведена в займ — разнесено по статьям", + "reason_code": null, + "updated_by": { + "user_id": "44444444-4444-4444-8444-444444444444", + "full_name": "Петрова Мария Ивановна" + }, + "updated_at": "2025-11-13T09:10:00Z" + } + } + ] + } +} diff --git a/contracts/examples/ex-payload-goodwill.json b/contracts/examples/ex-payload-goodwill.json new file mode 100644 index 0000000..6b285d3 --- /dev/null +++ b/contracts/examples/ex-payload-goodwill.json @@ -0,0 +1,60 @@ +{ + "schema_version": "1.0", + "item_type": "goodwill", + "units": "rub", + "generated_at": "2025-11-20T10:05:00Z", + "source_version_no": 1, + "data": { + "version_id": "99999999-9999-4999-8999-999999999999", + "goodwill_enabled": true, + "rows": [ + { + "head_legal_entity": { + "id": "11111111-1111-4111-8111-111111111111", + "name": "ООО «Альфа»" + }, + "subsidiary_legal_entity": { + "id": "22222222-2222-4222-8222-222222222222", + "name": "ООО «Бета»" + }, + "ownership_share": "80.00", + "financial_investment_amount": "50000000.00", + "charter_capital_amount": "40000000.00", + "additional_capital_amount": "0.00", + "participation_amount": "32000000.00", + "goodwill_amount": "18000000.00", + "include_in_kfo": true, + "comment": "Доля 80 % по выписке ЕГРЮЛ на 01.10.2025", + "has_investments": true, + "warnings": [], + "updated_by": { + "user_id": "44444444-4444-4444-8444-444444444444", + "full_name": "Петрова Мария Ивановна" + }, + "updated_at": "2025-11-12T11:00:00Z" + } + ], + "groups": [ + { + "head_legal_entity": { + "id": "11111111-1111-4111-8111-111111111111", + "name": "ООО «Альфа»" + }, + "totals": { + "financial_investment_amount": "50000000.00", + "charter_capital_amount": "40000000.00", + "participation_amount": "32000000.00", + "goodwill_amount": "18000000.00", + "goodwill_in_kfo_amount": "18000000.00" + } + } + ], + "totals": { + "financial_investment_amount": "50000000.00", + "charter_capital_amount": "40000000.00", + "participation_amount": "32000000.00", + "goodwill_amount": "18000000.00", + "goodwill_in_kfo_amount": "18000000.00" + } + } +} diff --git a/contracts/examples/ex-payload-kfo-balance.json b/contracts/examples/ex-payload-kfo-balance.json new file mode 100644 index 0000000..844ddeb --- /dev/null +++ b/contracts/examples/ex-payload-kfo-balance.json @@ -0,0 +1,637 @@ +{ + "schema_version": "1.0", + "item_type": "kfo_balance", + "units": "thousand_rub", + "generated_at": "2025-11-20T10:05:00Z", + "source_version_no": 1, + "data": { + "version_id": "99999999-9999-4999-8999-999999999999", + "computed_at": "2026-11-10T09:15:00Z", + "is_frozen": true, + "units": "thousand_rub", + "companies": [ + { + "legal_entity_id": "11111111-1111-4111-8111-111111111111", + "name": "ООО «Альфа»", + "inn": "7701000001" + }, + { + "legal_entity_id": "22222222-2222-4222-8222-222222222222", + "name": "ООО «Бета»", + "inn": "7701000002" + }, + { + "legal_entity_id": "33333333-3333-4333-8333-333333333333", + "name": "ООО «Гамма»", + "inn": "7701000003" + } + ], + "requisites": { + "company_group_name": "ГК «Альфа»", + "related_group_name": "ГСК «Альфа»", + "reporting_date": "2025-10-01", + "version_no": 1, + "version_status": "VERIFIED", + "scenario": "CREATE", + "approach": "BASIC", + "methodology_deviations": "Отклонений от Методики нет", + "uob_decision_no": "УОБ-118", + "uob_decision_date": "2025-11-20", + "corrections_balance": { + "count": 2, + "amount": "65000.00" + }, + "corrections_income": { + "count": 0, + "amount": "0.00" + } + }, + "summary": { + "balance": { + "deviation_total": "42900.00", + "base_kind": "NET_ASSETS", + "base_amount": "371000.00", + "limit_percent": "10.00", + "limit_amount": "37100.00", + "deviation_percent": "11.56", + "exceeded": true + }, + "income": { + "deviation_total": "0.00", + "base_kind": "SALES_PROFIT", + "base_amount": "65000.00", + "limit_percent": "10.00", + "limit_amount": "6500.00", + "deviation_percent": "0.00", + "exceeded": false + } + }, + "justification_required": true, + "threshold_justification": "Расхождение по 1520 — Бета → Альфа, сверка в работе; риск принят", + "goodwill": { + "enabled": true, + "total": "18000.00" + }, + "balance": { + "lines": [ + { + "line_code": "1170", + "line_name": "Финансовые вложения", + "ordinal": 10, + "is_total": false, + "is_threshold_base": false, + "companies": [ + { + "legal_entity_id": "11111111-1111-4111-8111-111111111111", + "client_amount": "70000.00", + "vgo_amount": "67500.00", + "cleaned_amount": "2500.00", + "share_percent": "100.00" + }, + { + "legal_entity_id": "22222222-2222-4222-8222-222222222222", + "client_amount": "0.00", + "vgo_amount": "0.00", + "cleaned_amount": "0.00", + "share_percent": "0.00" + }, + { + "legal_entity_id": "33333333-3333-4333-8333-333333333333", + "client_amount": "0.00", + "vgo_amount": "0.00", + "cleaned_amount": "0.00", + "share_percent": "0.00" + } + ], + "sum_amount": "70000.00", + "vgo_amount": "67500.00", + "eliminated_auto": "65000.00", + "eliminated_manual": "2500.00", + "goodwill_adjust": "0.00", + "bank_amount": "2500.00", + "deviation_amount": "0.00", + "comment": null + }, + { + "line_code": "GOODWILL", + "line_name": "Гудвилл", + "ordinal": 20, + "is_total": false, + "is_threshold_base": false, + "companies": [], + "sum_amount": "0.00", + "vgo_amount": "0.00", + "eliminated_auto": "0.00", + "eliminated_manual": "0.00", + "goodwill_adjust": "18000.00", + "bank_amount": "18000.00", + "deviation_amount": "0.00", + "comment": null + }, + { + "line_code": "1100", + "line_name": "Итого по разделу I", + "ordinal": 30, + "is_total": true, + "is_threshold_base": false, + "companies": [ + { + "legal_entity_id": "11111111-1111-4111-8111-111111111111", + "client_amount": "70000.00", + "vgo_amount": "67500.00", + "cleaned_amount": "2500.00", + "share_percent": "12.20" + }, + { + "legal_entity_id": "22222222-2222-4222-8222-222222222222", + "client_amount": "0.00", + "vgo_amount": "0.00", + "cleaned_amount": "0.00", + "share_percent": "0.00" + }, + { + "legal_entity_id": "33333333-3333-4333-8333-333333333333", + "client_amount": "0.00", + "vgo_amount": "0.00", + "cleaned_amount": "0.00", + "share_percent": "0.00" + } + ], + "sum_amount": "70000.00", + "vgo_amount": "67500.00", + "eliminated_auto": "65000.00", + "eliminated_manual": "2500.00", + "goodwill_adjust": "18000.00", + "bank_amount": "20500.00", + "deviation_amount": null, + "comment": null + }, + { + "line_code": "1230", + "line_name": "Дебиторская задолженность", + "ordinal": 40, + "is_total": false, + "is_threshold_base": false, + "companies": [ + { + "legal_entity_id": "11111111-1111-4111-8111-111111111111", + "client_amount": "520000.00", + "vgo_amount": "120000.00", + "cleaned_amount": "400000.00", + "share_percent": "70.42" + }, + { + "legal_entity_id": "22222222-2222-4222-8222-222222222222", + "client_amount": "130000.00", + "vgo_amount": "10000.00", + "cleaned_amount": "120000.00", + "share_percent": "21.13" + }, + { + "legal_entity_id": "33333333-3333-4333-8333-333333333333", + "client_amount": "48000.00", + "vgo_amount": "0.00", + "cleaned_amount": "48000.00", + "share_percent": "8.45" + } + ], + "sum_amount": "698000.00", + "vgo_amount": "130000.00", + "eliminated_auto": "100000.00", + "eliminated_manual": "30000.00", + "goodwill_adjust": "0.00", + "bank_amount": "568000.00", + "deviation_amount": "2900.00", + "comment": null + }, + { + "line_code": "1200", + "line_name": "Итого по разделу II", + "ordinal": 50, + "is_total": true, + "is_threshold_base": false, + "companies": [ + { + "legal_entity_id": "11111111-1111-4111-8111-111111111111", + "client_amount": "520000.00", + "vgo_amount": "120000.00", + "cleaned_amount": "400000.00", + "share_percent": "70.42" + }, + { + "legal_entity_id": "22222222-2222-4222-8222-222222222222", + "client_amount": "130000.00", + "vgo_amount": "10000.00", + "cleaned_amount": "120000.00", + "share_percent": "21.13" + }, + { + "legal_entity_id": "33333333-3333-4333-8333-333333333333", + "client_amount": "48000.00", + "vgo_amount": "0.00", + "cleaned_amount": "48000.00", + "share_percent": "8.45" + } + ], + "sum_amount": "698000.00", + "vgo_amount": "130000.00", + "eliminated_auto": "100000.00", + "eliminated_manual": "30000.00", + "goodwill_adjust": "0.00", + "bank_amount": "568000.00", + "deviation_amount": null, + "comment": null + }, + { + "line_code": "1600", + "line_name": "БАЛАНС", + "ordinal": 60, + "is_total": true, + "is_threshold_base": false, + "companies": [ + { + "legal_entity_id": "11111111-1111-4111-8111-111111111111", + "client_amount": "590000.00", + "vgo_amount": "187500.00", + "cleaned_amount": "402500.00", + "share_percent": "68.39" + }, + { + "legal_entity_id": "22222222-2222-4222-8222-222222222222", + "client_amount": "130000.00", + "vgo_amount": "10000.00", + "cleaned_amount": "120000.00", + "share_percent": "20.39" + }, + { + "legal_entity_id": "33333333-3333-4333-8333-333333333333", + "client_amount": "48000.00", + "vgo_amount": "0.00", + "cleaned_amount": "48000.00", + "share_percent": "8.16" + } + ], + "sum_amount": "768000.00", + "vgo_amount": "197500.00", + "eliminated_auto": "165000.00", + "eliminated_manual": "32500.00", + "goodwill_adjust": "18000.00", + "bank_amount": "588500.00", + "deviation_amount": null, + "comment": null + }, + { + "line_code": "1310", + "line_name": "Уставный капитал", + "ordinal": 70, + "is_total": false, + "is_threshold_base": false, + "companies": [ + { + "legal_entity_id": "11111111-1111-4111-8111-111111111111", + "client_amount": "100000.00", + "vgo_amount": "0.00", + "cleaned_amount": "100000.00", + "share_percent": "92.59" + }, + { + "legal_entity_id": "22222222-2222-4222-8222-222222222222", + "client_amount": "40000.00", + "vgo_amount": "32000.00", + "cleaned_amount": "8000.00", + "share_percent": "7.41" + }, + { + "legal_entity_id": "33333333-3333-4333-8333-333333333333", + "client_amount": "10000.00", + "vgo_amount": "10000.00", + "cleaned_amount": "0.00", + "share_percent": "0.00" + } + ], + "sum_amount": "150000.00", + "vgo_amount": "42000.00", + "eliminated_auto": "42000.00", + "eliminated_manual": "0.00", + "goodwill_adjust": "0.00", + "bank_amount": "108000.00", + "deviation_amount": "0.00", + "comment": null + }, + { + "line_code": "1350", + "line_name": "Добавочный капитал (без переоценки)", + "ordinal": 80, + "is_total": false, + "is_threshold_base": false, + "companies": [ + { + "legal_entity_id": "11111111-1111-4111-8111-111111111111", + "client_amount": "0.00", + "vgo_amount": "0.00", + "cleaned_amount": "0.00", + "share_percent": null + }, + { + "legal_entity_id": "22222222-2222-4222-8222-222222222222", + "client_amount": "0.00", + "vgo_amount": "0.00", + "cleaned_amount": "0.00", + "share_percent": null + }, + { + "legal_entity_id": "33333333-3333-4333-8333-333333333333", + "client_amount": "3000.00", + "vgo_amount": "3000.00", + "cleaned_amount": "0.00", + "share_percent": null + } + ], + "sum_amount": "3000.00", + "vgo_amount": "3000.00", + "eliminated_auto": "3000.00", + "eliminated_manual": "0.00", + "goodwill_adjust": "0.00", + "bank_amount": "0.00", + "deviation_amount": "0.00", + "comment": null + }, + { + "line_code": "1370", + "line_name": "Нераспределенная прибыль (непокрытый убыток)", + "ordinal": 90, + "is_total": false, + "is_threshold_base": false, + "companies": [ + { + "legal_entity_id": "11111111-1111-4111-8111-111111111111", + "client_amount": "280000.00", + "vgo_amount": "0.00", + "cleaned_amount": "280000.00", + "share_percent": "106.46" + }, + { + "legal_entity_id": "22222222-2222-4222-8222-222222222222", + "client_amount": "-20000.00", + "vgo_amount": "0.00", + "cleaned_amount": "-20000.00", + "share_percent": "-7.60" + }, + { + "legal_entity_id": "33333333-3333-4333-8333-333333333333", + "client_amount": "5000.00", + "vgo_amount": "2000.00", + "cleaned_amount": "3000.00", + "share_percent": "1.14" + } + ], + "sum_amount": "265000.00", + "vgo_amount": "2000.00", + "eliminated_auto": "2000.00", + "eliminated_manual": "0.00", + "goodwill_adjust": "0.00", + "bank_amount": "263000.00", + "deviation_amount": "0.00", + "comment": null + }, + { + "line_code": "1300", + "line_name": "Итого по разделу III", + "ordinal": 100, + "is_total": true, + "is_threshold_base": true, + "companies": [ + { + "legal_entity_id": "11111111-1111-4111-8111-111111111111", + "client_amount": "380000.00", + "vgo_amount": "0.00", + "cleaned_amount": "380000.00", + "share_percent": "102.43" + }, + { + "legal_entity_id": "22222222-2222-4222-8222-222222222222", + "client_amount": "20000.00", + "vgo_amount": "32000.00", + "cleaned_amount": "-12000.00", + "share_percent": "-3.23" + }, + { + "legal_entity_id": "33333333-3333-4333-8333-333333333333", + "client_amount": "18000.00", + "vgo_amount": "15000.00", + "cleaned_amount": "3000.00", + "share_percent": "0.81" + } + ], + "sum_amount": "418000.00", + "vgo_amount": "47000.00", + "eliminated_auto": "47000.00", + "eliminated_manual": "0.00", + "goodwill_adjust": "0.00", + "bank_amount": "371000.00", + "deviation_amount": null, + "comment": null + }, + { + "line_code": "1410", + "line_name": "Заемные средства", + "ordinal": 110, + "is_total": false, + "is_threshold_base": false, + "companies": [ + { + "legal_entity_id": "11111111-1111-4111-8111-111111111111", + "client_amount": "50000.00", + "vgo_amount": "0.00", + "cleaned_amount": "50000.00", + "share_percent": "100.00" + }, + { + "legal_entity_id": "22222222-2222-4222-8222-222222222222", + "client_amount": "0.00", + "vgo_amount": "0.00", + "cleaned_amount": "0.00", + "share_percent": "0.00" + }, + { + "legal_entity_id": "33333333-3333-4333-8333-333333333333", + "client_amount": "0.00", + "vgo_amount": "0.00", + "cleaned_amount": "0.00", + "share_percent": "0.00" + } + ], + "sum_amount": "50000.00", + "vgo_amount": "0.00", + "eliminated_auto": "0.00", + "eliminated_manual": "0.00", + "goodwill_adjust": "0.00", + "bank_amount": "50000.00", + "deviation_amount": "0.00", + "comment": null + }, + { + "line_code": "1400", + "line_name": "Итого по разделу IV", + "ordinal": 120, + "is_total": true, + "is_threshold_base": false, + "companies": [ + { + "legal_entity_id": "11111111-1111-4111-8111-111111111111", + "client_amount": "50000.00", + "vgo_amount": "0.00", + "cleaned_amount": "50000.00", + "share_percent": "100.00" + }, + { + "legal_entity_id": "22222222-2222-4222-8222-222222222222", + "client_amount": "0.00", + "vgo_amount": "0.00", + "cleaned_amount": "0.00", + "share_percent": "0.00" + }, + { + "legal_entity_id": "33333333-3333-4333-8333-333333333333", + "client_amount": "0.00", + "vgo_amount": "0.00", + "cleaned_amount": "0.00", + "share_percent": "0.00" + } + ], + "sum_amount": "50000.00", + "vgo_amount": "0.00", + "eliminated_auto": "0.00", + "eliminated_manual": "0.00", + "goodwill_adjust": "0.00", + "bank_amount": "50000.00", + "deviation_amount": null, + "comment": null + }, + { + "line_code": "1520", + "line_name": "Кредиторская задолженность", + "ordinal": 130, + "is_total": false, + "is_threshold_base": false, + "companies": [ + { + "legal_entity_id": "11111111-1111-4111-8111-111111111111", + "client_amount": "160000.00", + "vgo_amount": "10000.00", + "cleaned_amount": "150000.00", + "share_percent": "89.55" + }, + { + "legal_entity_id": "22222222-2222-4222-8222-222222222222", + "client_amount": "110000.00", + "vgo_amount": "100000.00", + "cleaned_amount": "10000.00", + "share_percent": "5.97" + }, + { + "legal_entity_id": "33333333-3333-4333-8333-333333333333", + "client_amount": "30000.00", + "vgo_amount": "22500.00", + "cleaned_amount": "7500.00", + "share_percent": "4.48" + } + ], + "sum_amount": "300000.00", + "vgo_amount": "132500.00", + "eliminated_auto": "100000.00", + "eliminated_manual": "32500.00", + "goodwill_adjust": "0.00", + "bank_amount": "167500.00", + "deviation_amount": "40000.00", + "comment": "Расхождение Бета → Альфа по 62/60: акт сверки на 01.10 не получен" + }, + { + "line_code": "1500", + "line_name": "Итого по разделу V", + "ordinal": 140, + "is_total": true, + "is_threshold_base": false, + "companies": [ + { + "legal_entity_id": "11111111-1111-4111-8111-111111111111", + "client_amount": "160000.00", + "vgo_amount": "10000.00", + "cleaned_amount": "150000.00", + "share_percent": "89.55" + }, + { + "legal_entity_id": "22222222-2222-4222-8222-222222222222", + "client_amount": "110000.00", + "vgo_amount": "100000.00", + "cleaned_amount": "10000.00", + "share_percent": "5.97" + }, + { + "legal_entity_id": "33333333-3333-4333-8333-333333333333", + "client_amount": "30000.00", + "vgo_amount": "22500.00", + "cleaned_amount": "7500.00", + "share_percent": "4.48" + } + ], + "sum_amount": "300000.00", + "vgo_amount": "132500.00", + "eliminated_auto": "100000.00", + "eliminated_manual": "32500.00", + "goodwill_adjust": "0.00", + "bank_amount": "167500.00", + "deviation_amount": null, + "comment": null + }, + { + "line_code": "1700", + "line_name": "БАЛАНС", + "ordinal": 150, + "is_total": true, + "is_threshold_base": false, + "companies": [ + { + "legal_entity_id": "11111111-1111-4111-8111-111111111111", + "client_amount": "590000.00", + "vgo_amount": "10000.00", + "cleaned_amount": "580000.00", + "share_percent": "98.56" + }, + { + "legal_entity_id": "22222222-2222-4222-8222-222222222222", + "client_amount": "130000.00", + "vgo_amount": "132000.00", + "cleaned_amount": "-2000.00", + "share_percent": "-0.34" + }, + { + "legal_entity_id": "33333333-3333-4333-8333-333333333333", + "client_amount": "48000.00", + "vgo_amount": "37500.00", + "cleaned_amount": "10500.00", + "share_percent": "1.78" + } + ], + "sum_amount": "768000.00", + "vgo_amount": "179500.00", + "eliminated_auto": "147000.00", + "eliminated_manual": "32500.00", + "goodwill_adjust": "0.00", + "bank_amount": "588500.00", + "deviation_amount": null, + "comment": null + } + ] + }, + "warnings": [ + { + "code": "FORM_MISSING", + "message": "Нет подтверждённой формы ОФР у ООО «Гамма»", + "legal_entity_id": "33333333-3333-4333-8333-333333333333", + "statement_type": "income", + "count": null + } + ] + } +} diff --git a/contracts/examples/ex-payload-kfo-income.json b/contracts/examples/ex-payload-kfo-income.json new file mode 100644 index 0000000..3175055 --- /dev/null +++ b/contracts/examples/ex-payload-kfo-income.json @@ -0,0 +1,317 @@ +{ + "schema_version": "1.0", + "item_type": "kfo_income", + "units": "thousand_rub", + "generated_at": "2025-11-20T10:05:00Z", + "source_version_no": 1, + "data": { + "version_id": "99999999-9999-4999-8999-999999999999", + "computed_at": "2026-11-10T09:15:00Z", + "is_frozen": true, + "units": "thousand_rub", + "companies": [ + { + "legal_entity_id": "11111111-1111-4111-8111-111111111111", + "name": "ООО «Альфа»", + "inn": "7701000001" + }, + { + "legal_entity_id": "22222222-2222-4222-8222-222222222222", + "name": "ООО «Бета»", + "inn": "7701000002" + }, + { + "legal_entity_id": "33333333-3333-4333-8333-333333333333", + "name": "ООО «Гамма»", + "inn": "7701000003" + } + ], + "requisites": { + "company_group_name": "ГК «Альфа»", + "related_group_name": "ГСК «Альфа»", + "reporting_date": "2025-10-01", + "version_no": 1, + "version_status": "VERIFIED", + "scenario": "CREATE", + "approach": "BASIC", + "methodology_deviations": "Отклонений от Методики нет", + "uob_decision_no": "УОБ-118", + "uob_decision_date": "2025-11-20", + "corrections_balance": { + "count": 2, + "amount": "65000.00" + }, + "corrections_income": { + "count": 0, + "amount": "0.00" + } + }, + "summary": { + "balance": { + "deviation_total": "42900.00", + "base_kind": "NET_ASSETS", + "base_amount": "371000.00", + "limit_percent": "10.00", + "limit_amount": "37100.00", + "deviation_percent": "11.56", + "exceeded": true + }, + "income": { + "deviation_total": "0.00", + "base_kind": "SALES_PROFIT", + "base_amount": "65000.00", + "limit_percent": "10.00", + "limit_amount": "6500.00", + "deviation_percent": "0.00", + "exceeded": false + } + }, + "justification_required": true, + "threshold_justification": "Расхождение по 1520 — Бета → Альфа, сверка в работе; риск принят", + "goodwill": { + "enabled": true, + "total": "18000.00" + }, + "income": { + "lines": [ + { + "line_code": "2110", + "line_name": "Выручка", + "ordinal": 10, + "is_total": false, + "is_threshold_base": false, + "companies": [ + { + "legal_entity_id": "11111111-1111-4111-8111-111111111111", + "client_amount": "200000.00", + "vgo_amount": "10000.00", + "cleaned_amount": "190000.00", + "share_percent": "70.37" + }, + { + "legal_entity_id": "22222222-2222-4222-8222-222222222222", + "client_amount": "80000.00", + "vgo_amount": "0.00", + "cleaned_amount": "80000.00", + "share_percent": "29.63" + }, + { + "legal_entity_id": "33333333-3333-4333-8333-333333333333", + "client_amount": "0.00", + "vgo_amount": "0.00", + "cleaned_amount": "0.00", + "share_percent": "0.00" + } + ], + "sum_amount": "280000.00", + "vgo_amount": "10000.00", + "eliminated_auto": "10000.00", + "eliminated_manual": "0.00", + "goodwill_adjust": "0.00", + "bank_amount": "270000.00", + "deviation_amount": "0.00", + "comment": null + }, + { + "line_code": "2120", + "line_name": "Себестоимость продаж", + "ordinal": 20, + "is_total": false, + "is_threshold_base": false, + "companies": [ + { + "legal_entity_id": "11111111-1111-4111-8111-111111111111", + "client_amount": "-120000.00", + "vgo_amount": "0.00", + "cleaned_amount": "-120000.00", + "share_percent": "70.59" + }, + { + "legal_entity_id": "22222222-2222-4222-8222-222222222222", + "client_amount": "-60000.00", + "vgo_amount": "-10000.00", + "cleaned_amount": "-50000.00", + "share_percent": "29.41" + }, + { + "legal_entity_id": "33333333-3333-4333-8333-333333333333", + "client_amount": "0.00", + "vgo_amount": "0.00", + "cleaned_amount": "0.00", + "share_percent": "0.00" + } + ], + "sum_amount": "-180000.00", + "vgo_amount": "-10000.00", + "eliminated_auto": "-10000.00", + "eliminated_manual": "0.00", + "goodwill_adjust": "0.00", + "bank_amount": "-170000.00", + "deviation_amount": "0.00", + "comment": null + }, + { + "line_code": "2100", + "line_name": "Валовая прибыль (убыток)", + "ordinal": 30, + "is_total": true, + "is_threshold_base": false, + "companies": [ + { + "legal_entity_id": "11111111-1111-4111-8111-111111111111", + "client_amount": "80000.00", + "vgo_amount": "10000.00", + "cleaned_amount": "70000.00", + "share_percent": "70.00" + }, + { + "legal_entity_id": "22222222-2222-4222-8222-222222222222", + "client_amount": "20000.00", + "vgo_amount": "-10000.00", + "cleaned_amount": "30000.00", + "share_percent": "30.00" + }, + { + "legal_entity_id": "33333333-3333-4333-8333-333333333333", + "client_amount": "0.00", + "vgo_amount": "0.00", + "cleaned_amount": "0.00", + "share_percent": "0.00" + } + ], + "sum_amount": "100000.00", + "vgo_amount": "0.00", + "eliminated_auto": "0.00", + "eliminated_manual": "0.00", + "goodwill_adjust": "0.00", + "bank_amount": "100000.00", + "deviation_amount": null, + "comment": null + }, + { + "line_code": "2210", + "line_name": "Коммерческие расходы", + "ordinal": 40, + "is_total": false, + "is_threshold_base": false, + "companies": [ + { + "legal_entity_id": "11111111-1111-4111-8111-111111111111", + "client_amount": "-20000.00", + "vgo_amount": "0.00", + "cleaned_amount": "-20000.00", + "share_percent": "80.00" + }, + { + "legal_entity_id": "22222222-2222-4222-8222-222222222222", + "client_amount": "-5000.00", + "vgo_amount": "0.00", + "cleaned_amount": "-5000.00", + "share_percent": "20.00" + }, + { + "legal_entity_id": "33333333-3333-4333-8333-333333333333", + "client_amount": "0.00", + "vgo_amount": "0.00", + "cleaned_amount": "0.00", + "share_percent": "0.00" + } + ], + "sum_amount": "-25000.00", + "vgo_amount": "0.00", + "eliminated_auto": "0.00", + "eliminated_manual": "0.00", + "goodwill_adjust": "0.00", + "bank_amount": "-25000.00", + "deviation_amount": "0.00", + "comment": null + }, + { + "line_code": "2220", + "line_name": "Управленческие расходы", + "ordinal": 50, + "is_total": false, + "is_threshold_base": false, + "companies": [ + { + "legal_entity_id": "11111111-1111-4111-8111-111111111111", + "client_amount": "-10000.00", + "vgo_amount": "0.00", + "cleaned_amount": "-10000.00", + "share_percent": "100.00" + }, + { + "legal_entity_id": "22222222-2222-4222-8222-222222222222", + "client_amount": "0.00", + "vgo_amount": "0.00", + "cleaned_amount": "0.00", + "share_percent": "0.00" + }, + { + "legal_entity_id": "33333333-3333-4333-8333-333333333333", + "client_amount": "0.00", + "vgo_amount": "0.00", + "cleaned_amount": "0.00", + "share_percent": "0.00" + } + ], + "sum_amount": "-10000.00", + "vgo_amount": "0.00", + "eliminated_auto": "0.00", + "eliminated_manual": "0.00", + "goodwill_adjust": "0.00", + "bank_amount": "-10000.00", + "deviation_amount": "0.00", + "comment": null + }, + { + "line_code": "2200", + "line_name": "Прибыль (убыток) от продаж", + "ordinal": 60, + "is_total": true, + "is_threshold_base": false, + "companies": [ + { + "legal_entity_id": "11111111-1111-4111-8111-111111111111", + "client_amount": "50000.00", + "vgo_amount": "10000.00", + "cleaned_amount": "40000.00", + "share_percent": "61.54" + }, + { + "legal_entity_id": "22222222-2222-4222-8222-222222222222", + "client_amount": "15000.00", + "vgo_amount": "-10000.00", + "cleaned_amount": "25000.00", + "share_percent": "38.46" + }, + { + "legal_entity_id": "33333333-3333-4333-8333-333333333333", + "client_amount": "0.00", + "vgo_amount": "0.00", + "cleaned_amount": "0.00", + "share_percent": "0.00" + } + ], + "sum_amount": "65000.00", + "vgo_amount": "0.00", + "eliminated_auto": "0.00", + "eliminated_manual": "0.00", + "goodwill_adjust": "0.00", + "bank_amount": "65000.00", + "deviation_amount": null, + "comment": null + } + ] + }, + "warnings": [ + { + "code": "FORM_MISSING", + "message": "Нет подтверждённой формы ОФР у ООО «Гамма»", + "legal_entity_id": "33333333-3333-4333-8333-333333333333", + "statement_type": "income", + "count": null + } + ] + } +} diff --git a/contracts/examples/ex-payload-other-report.json b/contracts/examples/ex-payload-other-report.json new file mode 100644 index 0000000..ebdc4d9 --- /dev/null +++ b/contracts/examples/ex-payload-other-report.json @@ -0,0 +1,25 @@ +{ + "schema_version": "1.0", + "item_type": "other_report", + "units": "thousand_rub", + "generated_at": "2025-11-20T10:05:00Z", + "source_version_no": 1, + "data": { + "report_kind": "client_kfo_comparison", + "lines": [ + { + "statement_type": "balance", + "line_code": "1600", + "line_name": "БАЛАНС", + "client_amount": "590000.00", + "client_vgo_amount": "180000.00", + "bank_amount": "588500.00", + "bank_vgo_amount": "179500.00", + "deviation_amount": "1500.00", + "deviation_percent": "0.25", + "deviation_reason": "Гудвилл у клиента не выделен", + "comment": null + } + ] + } +} diff --git a/contracts/examples/ex-payload-summary-osv.json b/contracts/examples/ex-payload-summary-osv.json new file mode 100644 index 0000000..8e60ef0 --- /dev/null +++ b/contracts/examples/ex-payload-summary-osv.json @@ -0,0 +1,106 @@ +{ + "schema_version": "1.0", + "item_type": "summary_osv", + "units": "rub", + "generated_at": "2025-11-20T10:05:00Z", + "source_version_no": 1, + "data": { + "rows": [ + { + "entry_ids": [], + "legal_entity_id": "11111111-1111-4111-8111-111111111111", + "legal_entity_name": "ООО «Альфа»", + "legal_entity_inn": "7701000001", + "account_code": "62.01", + "account_root": "62", + "period": "2025 Q3", + "is_cumulative": false, + "row_number": null, + "counterparty_name": "ООО «Бета»", + "counterparty_inn": "7701000002", + "counterparty_id": "22222222-2222-4222-8222-222222222222", + "counterparty_entity_name": "ООО «Бета»", + "counterparty_entity_inn": "7701000002", + "counterparty_type": "GSK", + "match_kind": "INN", + "match_stored": true, + "opening_debit": null, + "opening_credit": null, + "turnover_debit": null, + "turnover_credit": null, + "closing_debit": "102000000.00", + "closing_credit": null, + "warnings": [] + }, + { + "entry_ids": [], + "legal_entity_id": "22222222-2222-4222-8222-222222222222", + "legal_entity_name": "ООО «Бета»", + "legal_entity_inn": "7701000002", + "account_code": "60.01", + "account_root": "60", + "period": "2025 Q3", + "is_cumulative": false, + "row_number": null, + "counterparty_name": "ООО «Альфа»", + "counterparty_inn": "7701000001", + "counterparty_id": "11111111-1111-4111-8111-111111111111", + "counterparty_entity_name": "ООО «Альфа»", + "counterparty_entity_inn": "7701000001", + "counterparty_type": "GSK", + "match_kind": "INN", + "match_stored": true, + "opening_debit": null, + "opening_credit": null, + "turnover_debit": null, + "turnover_credit": null, + "closing_debit": null, + "closing_credit": "100000400.50", + "warnings": [] + }, + { + "entry_ids": [], + "legal_entity_id": "11111111-1111-4111-8111-111111111111", + "legal_entity_name": "ООО «Альфа»", + "legal_entity_inn": "7701000001", + "account_code": "60.01", + "account_root": "60", + "period": "2025 Q3", + "is_cumulative": false, + "row_number": null, + "counterparty_name": "ООО «Поставщик»", + "counterparty_inn": "7702999999", + "counterparty_id": null, + "counterparty_entity_name": null, + "counterparty_entity_inn": null, + "counterparty_type": "EXTERNAL", + "match_kind": null, + "match_stored": true, + "opening_debit": null, + "opening_credit": null, + "turnover_debit": null, + "turnover_credit": null, + "closing_debit": null, + "closing_credit": "5400000.00", + "warnings": [] + } + ], + "summary": { + "rows_total": 3, + "unmapped_total": 0, + "auto_matched_total": 0, + "documents_total": 9, + "gaps": [ + { + "legal_entity_id": "33333333-3333-4333-8333-333333333333", + "legal_entity_name": "ООО «Гамма»", + "account_code": "76", + "missing_quarters": [ + 1 + ] + } + ], + "counter_pairs": [] + } + } +} diff --git a/contracts/examples/ex-payload-version-card.json b/contracts/examples/ex-payload-version-card.json new file mode 100644 index 0000000..451ec87 --- /dev/null +++ b/contracts/examples/ex-payload-version-card.json @@ -0,0 +1,82 @@ +{ + "schema_version": "1.0", + "item_type": "version_card", + "units": "none", + "generated_at": "2025-11-20T10:05:00Z", + "source_version_no": 1, + "data": { + "company_group": { + "id": "77777777-7777-4777-8777-777777777777", + "name": "ГК «Альфа»" + }, + "related_group": { + "id": "88888888-8888-4888-8888-888888888888", + "name": "ГСК «Альфа»" + }, + "reporting_date": "2025-10-01", + "year": 2025, + "quarter": 3, + "version_no": 1, + "scenario": "CREATE", + "approach": "BASIC", + "goodwill_enabled": true, + "initiator": { + "user_id": "44444444-4444-4444-8444-444444444444", + "full_name": "Петрова Мария Ивановна" + }, + "initiator_office": { + "id": "99999999-0000-4000-8000-000000000001", + "name": "Ростовский РФ" + }, + "members": [ + { + "legal_entity_id": "11111111-1111-4111-8111-111111111111", + "name": "ООО «Альфа»", + "inn": "7701000001", + "is_head_entity": true + }, + { + "legal_entity_id": "22222222-2222-4222-8222-222222222222", + "name": "ООО «Бета»", + "inn": "7701000002", + "is_head_entity": false + }, + { + "legal_entity_id": "33333333-3333-4333-8333-333333333333", + "name": "ООО «Гамма»", + "inn": "7701000003", + "is_head_entity": false + } + ], + "history": [ + { + "from_status": null, + "to_status": "INITIATED", + "changed_by": { + "user_id": "44444444-4444-4444-8444-444444444444", + "full_name": "Петрова Мария Ивановна" + }, + "changed_at": "2025-11-05T09:00:00Z", + "comment": null + }, + { + "from_status": "VERIFICATION_GO", + "to_status": "VERIFIED", + "changed_by": { + "user_id": "55555555-5555-4555-8555-555555555555", + "full_name": "Соколов Игорь Петрович" + }, + "changed_at": "2025-11-20T10:00:00Z", + "comment": "Решение УОБ-118" + } + ], + "verified_by": { + "user_id": "55555555-5555-4555-8555-555555555555", + "full_name": "Соколов Игорь Петрович" + }, + "verified_at": "2025-11-20T10:00:00Z", + "uob_decision_no": "УОБ-118", + "uob_decision_date": "2025-11-20", + "methodology_deviations": "Отклонений от Методики нет" + } +} diff --git a/contracts/examples/ex-payload-vgo-pairs.json b/contracts/examples/ex-payload-vgo-pairs.json new file mode 100644 index 0000000..1bba063 --- /dev/null +++ b/contracts/examples/ex-payload-vgo-pairs.json @@ -0,0 +1,192 @@ +{ + "schema_version": "1.0", + "item_type": "vgo_pairs", + "units": "rub", + "generated_at": "2025-11-20T10:05:00Z", + "source_version_no": 1, + "data": { + "balance": { + "version_id": "99999999-9999-4999-8999-999999999999", + "statement_type": "balance", + "rules_version_no": 1, + "rules": [], + "aggregates": [ + { + "rule_id": "66666666-6666-4666-8666-000000000006", + "rule_no": 6, + "company_1": { + "id": "11111111-1111-4111-8111-111111111111", + "name": "ООО «Альфа»" + }, + "company_2": { + "id": "22222222-2222-4222-8222-222222222222", + "name": "ООО «Бета»" + }, + "side_1": { + "article_line": "1230", + "accounts": [ + "62.01" + ], + "amount": "102000000.00", + "entries": [ + { + "entry_ids": [], + "legal_entity_id": "11111111-1111-4111-8111-111111111111", + "counterparty_id": "22222222-2222-4222-8222-222222222222", + "account_code": "62.01", + "side": "D", + "amount": "102000000.00", + "decision": "AUTO", + "candidate_rule_ids": [] + } + ] + }, + "side_2": { + "article_line": "1520", + "accounts": [ + "60.01" + ], + "amount": "100000400.50", + "entries": [ + { + "entry_ids": [], + "legal_entity_id": "22222222-2222-4222-8222-222222222222", + "counterparty_id": "11111111-1111-4111-8111-111111111111", + "account_code": "60.01", + "side": "K", + "amount": "100000400.50", + "decision": "AUTO", + "candidate_rule_ids": [] + } + ] + }, + "deviation_amount": "1999599.50", + "deviation_percent": "2.00", + "exceeds_threshold": false, + "status": "AUTO", + "eliminated_amount": "100000400.50", + "composition_changed": false, + "correction": null + }, + { + "rule_id": "66666666-6666-4666-8666-000000000006", + "rule_no": 6, + "company_1": { + "id": "22222222-2222-4222-8222-222222222222", + "name": "ООО «Бета»" + }, + "company_2": { + "id": "11111111-1111-4111-8111-111111111111", + "name": "ООО «Альфа»" + }, + "side_1": { + "article_line": "1230", + "accounts": [ + "62.01" + ], + "amount": "10000000.00", + "entries": [ + { + "entry_ids": [], + "legal_entity_id": "22222222-2222-4222-8222-222222222222", + "counterparty_id": "11111111-1111-4111-8111-111111111111", + "account_code": "62.01", + "side": "D", + "amount": "10000000.00", + "decision": "AUTO", + "candidate_rule_ids": [] + } + ] + }, + "side_2": { + "article_line": "1520", + "accounts": [ + "60.01", + "76.05" + ], + "amount": "50000000.00", + "entries": [ + { + "entry_ids": [], + "legal_entity_id": "11111111-1111-4111-8111-111111111111", + "counterparty_id": "22222222-2222-4222-8222-222222222222", + "account_code": "60.01", + "side": "K", + "amount": "12000000.00", + "decision": "AUTO", + "candidate_rule_ids": [] + }, + { + "entry_ids": [], + "legal_entity_id": "11111111-1111-4111-8111-111111111111", + "counterparty_id": "22222222-2222-4222-8222-222222222222", + "account_code": "76.05", + "side": "K", + "amount": "38000000.00", + "decision": "AUTO", + "candidate_rule_ids": [] + } + ] + }, + "deviation_amount": "40000000.00", + "deviation_percent": "400.00", + "exceeds_threshold": true, + "status": "DISCREPANCY", + "eliminated_amount": "0.00", + "composition_changed": false, + "correction": { + "status": "APPLIED", + "version_no": 1, + "debit": [ + { + "line_code": "1230", + "amount": "10000000.00" + } + ], + "credit": [ + { + "line_code": "1520", + "amount": "10000000.00" + } + ], + "comment": "Сверка Бета → Альфа: разница — аванс, исключаем подтверждённое", + "reason_code": null, + "updated_by": { + "user_id": "44444444-4444-4444-8444-444444444444", + "full_name": "Петрова Мария Ивановна" + }, + "updated_at": "2025-11-12T14:30:00Z" + } + } + ], + "unallocated": [ + { + "entry_ids": [], + "owner": { + "id": "33333333-3333-4333-8333-333333333333", + "name": "ООО «Гамма»" + }, + "counterparty": { + "id": "11111111-1111-4111-8111-111111111111", + "name": "ООО «Альфа»" + }, + "account_code": "76.09", + "line_code": null, + "side": "D", + "amount": "350000.00", + "candidate_rule_ids": [], + "reason": "MULTIPLE_RULES", + "excluded": true + } + ], + "totals": { + "amount_1": "112000000.00", + "amount_2": "150000400.50", + "deviation_amount": "41999599.50", + "correction_debit": "10000000.00", + "correction_credit": "10000000.00" + } + }, + "income": null + } +} diff --git a/scripts/kfo_contract/gen_ex_payload.py b/scripts/kfo_contract/gen_ex_payload.py new file mode 100644 index 0000000..b41b409 --- /dev/null +++ b/scripts/kfo_contract/gen_ex_payload.py @@ -0,0 +1,343 @@ +"""Генерирует contracts/ex-payload-v1.schema.json и contracts/examples/ex-payload-*.json (docs #208). + +Запуск из корня репозитория: .venv/bin/python -I scripts/kfo_contract/gen_ex_payload.py +Руками схему и примеры не править: правка здесь → перегенерация → make check. +Примеры — группа «Альфа / Бета / Гамма» сквозного примера §8 «КФО» (kfo_example.py, gen_kfo_openapi.py). +""" +import copy +import json +import sys +from pathlib import Path + +HERE = Path(__file__).resolve().parent +ROOT = HERE.parents[1] +sys.path.insert(0, str(HERE)) +import gen_kfo_openapi as kfo # noqa: E402 + +MONEY_RE = r"^-?\d{1,16}\.\d{2}$" +M = {"type": "string", "pattern": MONEY_RE} +MN = {"type": ["string", "null"], "pattern": MONEY_RE} +PN = {"type": ["string", "null"], "pattern": r"^-?\d{1,5}\.\d{2}$"} +UUID = {"type": "string", "format": "uuid"} +UUIDN = {"type": ["string", "null"], "format": "uuid"} +STR = {"type": "string"} +STRN = {"type": ["string", "null"]} +DATE = {"type": "string", "format": "date"} +DT = {"type": "string", "format": "date-time"} + + +def d(name): + return {"$ref": f"#/$defs/{name}"} + + +def arr(x): + return {"type": "array", "items": x} + + +def obj(props, required=None, closed=False, desc=None): + o = {"type": "object", "properties": props, "required": list(props) if required is None else required, + "additionalProperties": not closed} + if desc: + o["description"] = desc + return o + + +TYPES = { # item_type → (единица, $def данных) + "summary_osv": ("rub", "SummaryOsvData"), + "vgo_pairs": ("rub", "VgoPairsData"), + "chess": ("rub", "ChessData"), + "kfo_balance": ("thousand_rub", "KfoBalanceData"), + "kfo_income": ("thousand_rub", "KfoIncomeData"), + "goodwill": ("rub", "GoodwillData"), + "corrections": ("rub", "CorrectionsData"), + "other_report": ("thousand_rub", "OtherReportData"), + "version_card": ("none", "VersionCardData"), +} + + +def kfo_defs(): + """$defs КФО — из контракта КФО (gen_kfo_openapi.SCHEMAS), ссылки переведены на $defs, данные открыты.""" + raw = json.loads(json.dumps(kfo.SCHEMAS).replace("#/components/schemas/", "#/$defs/")) + out = {} + for name, sch in raw.items(): + if name in ("KfoAnnotationsIn", "KfoCommentIn", "ErrorDetail", "ErrorOut", "KfoResultOut"): + continue + sch = copy.deepcopy(sch) + sch["additionalProperties"] = True + out[name] = sch + return out + + +def defs(): + out = kfo_defs() + res = kfo.SCHEMAS["KfoResultOut"]["properties"] + base = {k: json.loads(json.dumps(v).replace("#/components/schemas/", "#/$defs/")) + for k, v in res.items() if k not in ("editable", "read_only_reason", "balance", "income")} + out["KfoBalanceData"] = obj({**base, "balance": d("KfoStatementOut")}, + desc="KfoResultOut без editable/read_only_reason, только отчёт ББ") + out["KfoBalanceData"]["not"] = {"required": ["income"]} + out["KfoIncomeData"] = obj({**base, "income": d("KfoStatementOut")}, + desc="KfoResultOut без editable/read_only_reason, только отчёт ОФР") + out["KfoIncomeData"]["not"] = {"required": ["balance"]} + out["Ref"] = obj({"id": UUID, "name": STR}) + out["UserRef"] = obj({"user_id": UUIDN, "full_name": STR}, + desc="Пользователь с ФИО на дату верификации (user_id — справочно, app_user не удаляется)") + # Сводная ОСВ — dev/api-contract-slice-4.md §3 + out["ConsolidatedRowOut"] = obj({ + "legal_entity_id": UUID, "legal_entity_name": STR, "legal_entity_inn": STRN, + "account_code": STRN, "period": STR, "is_cumulative": {"type": "boolean"}, + "counterparty_name": STRN, "counterparty_inn": STRN, "counterparty_id": UUIDN, + "counterparty_entity_name": STRN, "counterparty_entity_inn": STRN, + "counterparty_type": {"type": "string", "enum": ["GSK", "GK", "EXTERNAL"]}, + "match_kind": STRN, + "opening_debit": MN, "opening_credit": MN, "turnover_debit": MN, "turnover_credit": MN, + "closing_debit": MN, "closing_credit": MN, + }, desc="ConsolidatedRowOut (dev/api-contract-slice-4.md §3); entry_ids, warnings — справочно, необязательны") + out["OsvGapOut"] = obj({"legal_entity_id": UUID, "legal_entity_name": STR, "account_code": STR, + "missing_quarters": arr({"type": "integer", "minimum": 1, "maximum": 4})}) + out["OsvSummaryOut"] = obj({"rows_total": {"type": "integer"}, "gaps": arr(d("OsvGapOut"))}) + out["SummaryOsvData"] = obj({"rows": arr(d("ConsolidatedRowOut")), "summary": d("OsvSummaryOut")}) + # Таблица корректировок — dev/api-contract-corrections.* + out["CorrectionEntryOut"] = obj({"legal_entity_id": UUID, "counterparty_id": UUID, "account_code": STR, + "side": {"type": "string", "enum": ["D", "K"]}, "amount": M, "decision": STR}, + desc="строка ОСВ агрегата (нужна выгрузке таблицы корректировок)") + out["AggregateSideOut"] = obj({"article_line": STRN, "accounts": arr(STR), "amount": M, + "entries": arr(d("CorrectionEntryOut"))}) + out["CorrectionLineOut"] = obj({"line_code": STR, "amount": M}) + out["CorrectionOut"] = obj({"status": {"type": "string", "enum": ["APPLIED"]}, "version_no": {"type": "integer"}, + "debit": arr(d("CorrectionLineOut")), "credit": arr(d("CorrectionLineOut")), + "comment": STR, "updated_by": {}, "updated_at": DT}, + desc="CorrectionOut; в срезе только APPLIED") + out["CorrectionAggregateOut"] = obj({ + "rule_no": {"type": "integer"}, "company_1": d("Ref"), "company_2": d("Ref"), + "side_1": d("AggregateSideOut"), "side_2": d("AggregateSideOut"), + "deviation_amount": M, "deviation_percent": PN, "exceeds_threshold": {"type": "boolean"}, + "status": {"type": "string", "enum": ["AUTO", "DISCREPANCY", "ONE_SIDED", "MANUAL"]}, + "eliminated_amount": M, + "correction": {"anyOf": [d("CorrectionOut"), {"type": "null"}]}, + }) + out["UnallocatedEntryOut"] = obj({"owner": d("Ref"), "counterparty": d("Ref"), "account_code": STR, + "side": {"type": "string", "enum": ["D", "K"]}, "amount": M, + "reason": STR, "excluded": {"type": "boolean"}}) + out["CorrectionTotalsOut"] = obj({"amount_1": M, "amount_2": M, "deviation_amount": M, + "correction_debit": M, "correction_credit": M}) + out["CorrectionTableData"] = obj({ + "statement_type": {"type": "string", "enum": ["balance", "income"]}, "rules_version_no": {"type": "integer"}, + "aggregates": arr(d("CorrectionAggregateOut")), "unallocated": arr(d("UnallocatedEntryOut")), + "totals": d("CorrectionTotalsOut"), + }, desc="CorrectionTableOut без editable/read_only_reason") + out["VgoPairsData"] = obj({"balance": d("CorrectionTableData"), + "income": {"anyOf": [d("CorrectionTableData"), {"type": "null"}]}}) + out["CorrectionsData"] = obj({"items": arr(obj({ + "statement_type": {"type": "string", "enum": ["balance", "income"]}, "rule_no": {"type": "integer"}, + "company_1": d("Ref"), "company_2": d("Ref"), "correction": d("CorrectionOut")}))}) + # Шахматка — dev/frontend-api-guide.md §5.7.1 + out["MatrixCellOut"] = obj({"company_a_id": UUID, "company_b_id": UUID, "amount_a": M, "amount_b": M, + "variance_amount": M, "variance_percent": PN, "exceeds_threshold": {"type": "boolean"}, + "one_sided": {"type": "boolean"}}) + out["IntercompanyMatrixOut"] = obj({"companies": arr(d("Ref")), "cells": arr(d("MatrixCellOut"))}, + desc="IntercompanyMatrixOut; osv_entry_ids_* — справочно") + out["ResultTableOut"] = obj({"companies": arr(d("Ref")), "articles": arr(obj({ + "code": STR, "name": STR, "amounts": arr(obj({"company_id": UUID, "amount": M})), "group_total": M}))}) + out["ChessData"] = obj({"matrix_all": d("IntercompanyMatrixOut"), + "presets": arr(obj({"rule_no": {"type": "integer"}, "matrix": d("IntercompanyMatrixOut")})), + "result_table": d("ResultTableOut")}) + # Гудвилл — dev/api-contract-goodwill.* + out["GoodwillTotalsOut"] = obj({"financial_investment_amount": MN, "charter_capital_amount": MN, + "participation_amount": MN, "goodwill_amount": MN, "goodwill_in_kfo_amount": MN}) + out["GoodwillRowOut"] = obj({ + "head_legal_entity": d("Ref"), "subsidiary_legal_entity": d("Ref"), "ownership_share": PN, + "financial_investment_amount": MN, "charter_capital_amount": MN, "additional_capital_amount": MN, + "participation_amount": MN, "goodwill_amount": MN, "include_in_kfo": {"type": "boolean"}, "comment": STRN, + "has_investments": {"type": "boolean"}}) + out["GoodwillData"] = obj({"goodwill_enabled": {"const": True}, "rows": arr(d("GoodwillRowOut")), + "groups": arr(obj({"head_legal_entity": d("Ref"), "totals": d("GoodwillTotalsOut")})), + "totals": d("GoodwillTotalsOut")}, + desc="GoodwillTableOut без editable/read_only_reason; пишется только при goodwill_enabled") + out["OtherReportData"] = obj({"report_kind": {"const": "client_kfo_comparison"}, "lines": arr(obj({ + "statement_type": {"type": "string", "enum": ["balance", "income"]}, "line_code": STR, "line_name": STR, + "client_amount": MN, "client_vgo_amount": MN, "bank_amount": MN, "bank_vgo_amount": MN, + "deviation_amount": MN, "deviation_percent": PN, "deviation_reason": STRN, "comment": STRN}))}) + statuses = kfo.SCHEMAS["KfoRequisitesOut"]["properties"]["version_status"]["enum"] + out["VersionCardData"] = obj({ + "company_group": d("Ref"), "related_group": d("Ref"), "reporting_date": DATE, + "year": {"type": "integer"}, "quarter": {"type": "integer", "minimum": 1, "maximum": 4}, + "version_no": {"type": "integer"}, "scenario": {"type": "string", "enum": ["CREATE", "CONFIRM"]}, + "approach": {"type": "string", "enum": ["BASIC", "EXTENDED"]}, "goodwill_enabled": {"type": "boolean"}, + "initiator": d("UserRef"), "initiator_office": d("Ref"), + "members": arr(obj({"legal_entity_id": UUID, "name": STR, "inn": STR, "is_head_entity": {"type": "boolean"}})), + "history": arr(obj({"from_status": {"type": ["string", "null"], "enum": statuses + [None]}, + "to_status": {"type": "string", "enum": statuses}, + "changed_by": d("UserRef"), "changed_at": DT, "comment": STRN})), + "verified_by": d("UserRef"), "verified_at": DT, + "uob_decision_no": STRN, "uob_decision_date": {"type": ["string", "null"], "format": "date"}, + "methodology_deviations": STRN, + }, desc="Карточка версии на дату верификации (DOC-274687 доп. 4; новый item_type v5.26)") + return out + + +def schema(): + branches = [] + for t, (units, data_def) in TYPES.items(): + branches.append({"if": {"properties": {"item_type": {"const": t}}}, + "then": {"properties": {"units": {"const": units}, "data": d(data_def)}}}) + return { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "ex-payload-v1.schema.json", + "title": "Элемент среза ЕХ: ex_snapshot_item.payload, версия 1.0", + "description": "Конверт замороженных данных элемента среза ЕХ (context/05-domain-rules.md §14). data — ответ API " + "экрана рабочей версии на момент верификации без полей редактирования; у primary_document " + "payload нет. Сгенерировано scripts/kfo_contract/gen_ex_payload.py — руками не править.", + "type": "object", + "additionalProperties": False, + "required": ["schema_version", "item_type", "units", "generated_at", "source_version_no", "data"], + "properties": { + "schema_version": {"const": "1.0"}, + "item_type": {"type": "string", "enum": list(TYPES)}, + "units": {"type": "string", "enum": ["rub", "thousand_rub", "none"]}, + "generated_at": DT, + "source_version_no": {"type": "integer", "minimum": 1}, + "data": {"type": "object"}, + }, + "allOf": branches, + "$defs": defs(), + } + + +# ---------- примеры ---------- +IDS = kfo.IDS +REF = {c: {"id": IDS[c], "name": kfo.ex.NAMES[c]} for c in kfo.ex.COMPANIES} +USER = {"user_id": "44444444-4444-4444-8444-444444444444", "full_name": "Петрова Мария Ивановна"} +USER2 = {"user_id": "55555555-5555-4555-8555-555555555555", "full_name": "Соколов Игорь Петрович"} +GEN = "2025-11-20T10:05:00Z" + + +def env(t, data): + return {"schema_version": "1.0", "item_type": t, "units": TYPES[t][0], "generated_at": GEN, + "source_version_no": 1, "data": data} + + +def examples(): + res = kfo.result(True, frozen=True) + for k in ("editable", "read_only_reason"): + res.pop(k) + kb = {k: v for k, v in res.items() if k != "income"} + ki = {k: v for k, v in res.items() if k != "balance"} + out = {"kfo_balance": env("kfo_balance", kb), "kfo_income": env("kfo_income", ki)} + + def entry(c, cp, acc, side, amt): + return {"entry_ids": [], "legal_entity_id": IDS[c], "counterparty_id": IDS[cp], "account_code": acc, + "side": side, "amount": amt, "decision": "AUTO", "candidate_rule_ids": []} + corr_ba = {"status": "APPLIED", "version_no": 1, "debit": [{"line_code": "1230", "amount": "10000000.00"}], + "credit": [{"line_code": "1520", "amount": "10000000.00"}], + "comment": "Сверка Бета → Альфа: разница — аванс, исключаем подтверждённое", "reason_code": None, + "updated_by": USER, "updated_at": "2025-11-12T14:30:00Z"} + corr_ac = {"status": "APPLIED", "version_no": 2, + "debit": [{"line_code": "1230", "amount": "20000000.00"}, {"line_code": "1170", "amount": "5000000.00"}], + "credit": [{"line_code": "1520", "amount": "22500000.00"}], + "comment": "Часть долга Гаммы переведена в займ — разнесено по статьям", "reason_code": None, + "updated_by": USER, "updated_at": "2025-11-13T09:10:00Z"} + aggs = [ + {"rule_id": "66666666-6666-4666-8666-000000000006", "rule_no": 6, "company_1": REF["A"], "company_2": REF["B"], + "side_1": {"article_line": "1230", "accounts": ["62.01"], "amount": "102000000.00", + "entries": [entry("A", "B", "62.01", "D", "102000000.00")]}, + "side_2": {"article_line": "1520", "accounts": ["60.01"], "amount": "100000400.50", + "entries": [entry("B", "A", "60.01", "K", "100000400.50")]}, + "deviation_amount": "1999599.50", "deviation_percent": "2.00", "exceeds_threshold": False, "status": "AUTO", + "eliminated_amount": "100000400.50", "composition_changed": False, "correction": None}, + {"rule_id": "66666666-6666-4666-8666-000000000006", "rule_no": 6, "company_1": REF["B"], "company_2": REF["A"], + "side_1": {"article_line": "1230", "accounts": ["62.01"], "amount": "10000000.00", + "entries": [entry("B", "A", "62.01", "D", "10000000.00")]}, + "side_2": {"article_line": "1520", "accounts": ["60.01", "76.05"], "amount": "50000000.00", + "entries": [entry("A", "B", "60.01", "K", "12000000.00"), entry("A", "B", "76.05", "K", "38000000.00")]}, + "deviation_amount": "40000000.00", "deviation_percent": "400.00", "exceeds_threshold": True, + "status": "DISCREPANCY", "eliminated_amount": "0.00", "composition_changed": False, "correction": corr_ba}, + ] + vgo = {"balance": {"version_id": kfo.VERSION, "statement_type": "balance", "rules_version_no": 1, "rules": [], + "aggregates": aggs, + "unallocated": [{"entry_ids": [], "owner": REF["C"], "counterparty": REF["A"], + "account_code": "76.09", "line_code": None, "side": "D", "amount": "350000.00", + "candidate_rule_ids": [], "reason": "MULTIPLE_RULES", "excluded": True}], + "totals": {"amount_1": "112000000.00", "amount_2": "150000400.50", + "deviation_amount": "41999599.50", "correction_debit": "10000000.00", + "correction_credit": "10000000.00"}}, + "income": None} + out["vgo_pairs"] = env("vgo_pairs", vgo) + out["corrections"] = env("corrections", {"items": [ + {"statement_type": "balance", "rule_no": 6, "company_1": REF["B"], "company_2": REF["A"], "correction": corr_ba}, + {"statement_type": "balance", "rule_no": 6, "company_1": REF["A"], "company_2": REF["C"], "correction": corr_ac}, + ]}) + + def row(c, acc, cp, ctype, cd=None, cc=None): + return {"entry_ids": [], "legal_entity_id": IDS[c], "legal_entity_name": kfo.ex.NAMES[c], + "legal_entity_inn": kfo.ex.INN[c], "account_code": acc, "account_root": acc.split(".")[0], + "period": "2025 Q3", "is_cumulative": False, "row_number": None, + "counterparty_name": kfo.ex.NAMES.get(cp, cp), "counterparty_inn": kfo.ex.INN.get(cp), + "counterparty_id": IDS.get(cp), "counterparty_entity_name": kfo.ex.NAMES.get(cp), + "counterparty_entity_inn": kfo.ex.INN.get(cp), "counterparty_type": ctype, + "match_kind": "INN" if cp in IDS else None, "match_stored": True, + "opening_debit": None, "opening_credit": None, "turnover_debit": None, "turnover_credit": None, + "closing_debit": cd, "closing_credit": cc, "warnings": []} + ext = row("A", "60.01", "ООО «Поставщик»", "EXTERNAL", cc="5400000.00") + ext.update({"counterparty_inn": "7702999999", "counterparty_id": None, "counterparty_entity_name": None, + "counterparty_entity_inn": None}) + out["summary_osv"] = env("summary_osv", { + "rows": [row("A", "62.01", "B", "GSK", cd="102000000.00"), row("B", "60.01", "A", "GSK", cc="100000400.50"), ext], + "summary": {"rows_total": 3, "unmapped_total": 0, "auto_matched_total": 0, "documents_total": 9, + "gaps": [{"legal_entity_id": IDS["C"], "legal_entity_name": kfo.ex.NAMES["C"], + "account_code": "76", "missing_quarters": [1]}], "counter_pairs": []}}) + cell = {"company_a_id": IDS["A"], "company_b_id": IDS["B"], "amount_a": "102000000.00", + "amount_b": "100000400.50", "variance_amount": "1999599.50", "variance_percent": "2.00", + "exceeds_threshold": False, "one_sided": False, "skipped_osv_entry_count": 0} + matrix = {"companies": [REF[c] for c in kfo.ex.COMPANIES], "cells": [cell]} + out["chess"] = env("chess", {"matrix_all": matrix, "presets": [{"rule_no": 6, "matrix": matrix}], + "result_table": {"companies": [REF[c] for c in kfo.ex.COMPANIES], "articles": [ + {"code": "1230", "name": "Дебиторская задолженность", "group_total": "112000000.00", + "amounts": [{"company_id": IDS["A"], "amount": "102000000.00"}, + {"company_id": IDS["B"], "amount": "10000000.00"}]}]}}) + gw_tot = {"financial_investment_amount": "50000000.00", "charter_capital_amount": "40000000.00", + "participation_amount": "32000000.00", "goodwill_amount": "18000000.00", + "goodwill_in_kfo_amount": "18000000.00"} + out["goodwill"] = env("goodwill", { + "version_id": kfo.VERSION, "goodwill_enabled": True, + "rows": [{"head_legal_entity": REF["A"], "subsidiary_legal_entity": REF["B"], "ownership_share": "80.00", + "financial_investment_amount": "50000000.00", "charter_capital_amount": "40000000.00", + "additional_capital_amount": "0.00", "participation_amount": "32000000.00", + "goodwill_amount": "18000000.00", "include_in_kfo": True, + "comment": "Доля 80 % по выписке ЕГРЮЛ на 01.10.2025", "has_investments": True, "warnings": [], + "updated_by": USER, "updated_at": "2025-11-12T11:00:00Z"}], + "groups": [{"head_legal_entity": REF["A"], "totals": gw_tot}], "totals": gw_tot}) + out["version_card"] = env("version_card", { + "company_group": {"id": "77777777-7777-4777-8777-777777777777", "name": "ГК «Альфа»"}, + "related_group": {"id": "88888888-8888-4888-8888-888888888888", "name": "ГСК «Альфа»"}, + "reporting_date": "2025-10-01", "year": 2025, "quarter": 3, "version_no": 1, "scenario": "CREATE", + "approach": "BASIC", "goodwill_enabled": True, "initiator": USER, + "initiator_office": {"id": "99999999-0000-4000-8000-000000000001", "name": "Ростовский РФ"}, + "members": [{"legal_entity_id": IDS[c], "name": kfo.ex.NAMES[c], "inn": kfo.ex.INN[c], + "is_head_entity": c == "A"} for c in kfo.ex.COMPANIES], + "history": [{"from_status": None, "to_status": "INITIATED", "changed_by": USER, + "changed_at": "2025-11-05T09:00:00Z", "comment": None}, + {"from_status": "VERIFICATION_GO", "to_status": "VERIFIED", "changed_by": USER2, + "changed_at": "2025-11-20T10:00:00Z", "comment": "Решение УОБ-118"}], + "verified_by": USER2, "verified_at": "2025-11-20T10:00:00Z", + "uob_decision_no": "УОБ-118", "uob_decision_date": "2025-11-20", + "methodology_deviations": "Отклонений от Методики нет"}) + out["other_report"] = env("other_report", {"report_kind": "client_kfo_comparison", "lines": [ + {"statement_type": "balance", "line_code": "1600", "line_name": "БАЛАНС", "client_amount": "590000.00", + "client_vgo_amount": "180000.00", "bank_amount": "588500.00", "bank_vgo_amount": "179500.00", + "deviation_amount": "1500.00", "deviation_percent": "0.25", "deviation_reason": "Гудвилл у клиента не выделен", + "comment": None}]}) + return out + + +def main(): + (ROOT / "contracts" / "ex-payload-v1.schema.json").write_text( + json.dumps(schema(), ensure_ascii=False, indent=2) + "\n", encoding="utf-8") + for t, doc in examples().items(): + (ROOT / "contracts" / "examples" / f"ex-payload-{t.replace('_', '-')}.json").write_text( + json.dumps(doc, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") + print("ok") + + +if __name__ == "__main__": + main() diff --git a/scripts/kfo_contract/test_ex_payload.py b/scripts/kfo_contract/test_ex_payload.py new file mode 100644 index 0000000..34bd786 --- /dev/null +++ b/scripts/kfo_contract/test_ex_payload.py @@ -0,0 +1,63 @@ +"""Проверка схемы среза ЕХ ex-payload-v1 (docs #208): примеры проходят, типовые ошибки — нет. + +Запуск: .venv/bin/python -I scripts/kfo_contract/test_ex_payload.py +""" +import copy +import json +import sys +from pathlib import Path + +from jsonschema import Draft202012Validator + +ROOT = Path(__file__).resolve().parents[2] +SCHEMA = ROOT / "contracts" / "ex-payload-v1.schema.json" +EXAMPLES = sorted((ROOT / "contracts" / "examples").glob("ex-payload-*.json")) +REQUIRED_TYPES = {"summary_osv", "vgo_pairs", "chess", "kfo_balance", "kfo_income", "goodwill", "corrections", + "version_card"} + + +def main(): + schema = json.loads(SCHEMA.read_text(encoding="utf-8")) + Draft202012Validator.check_schema(schema) + v = Draft202012Validator(schema) + by_type = {} + for p in EXAMPLES: + doc = json.loads(p.read_text(encoding="utf-8")) + errs = list(v.iter_errors(doc)) + assert not errs, f"{p.name}: {errs[0].message[:200]}" + by_type[doc["item_type"]] = doc + missing = REQUIRED_TYPES - set(by_type) + assert not missing, f"нет примеров: {sorted(missing)}" + + def rejected(doc, why): + assert list(v.iter_errors(doc)), f"схема пропустила: {why}" + + # 1. item_type конверта не совпадает с формой data + bad = copy.deepcopy(by_type["kfo_balance"]) + bad["data"] = copy.deepcopy(by_type["goodwill"]["data"]) + rejected(bad, "kfo_balance с данными гудвилла") + # 2. kfo_balance без сводки корректировок в реквизитах + bad = copy.deepcopy(by_type["kfo_balance"]) + del bad["data"]["requisites"]["corrections_balance"] + rejected(bad, "нет requisites.corrections_balance") + # 4. деньги числом + bad = copy.deepcopy(by_type["kfo_balance"]) + bad["data"]["balance"]["lines"][0]["bank_amount"] = 2500.0 + rejected(bad, "деньги числом") + # конверт: лишнее поле и неизвестная единица + bad = copy.deepcopy(by_type["version_card"]) + bad["extra"] = 1 + rejected(bad, "лишнее поле конверта") + bad = copy.deepcopy(by_type["summary_osv"]) + bad["units"] = "mln_rub" + rejected(bad, "неизвестная единица") + # единица не соответствует типу + bad = copy.deepcopy(by_type["summary_osv"]) + bad["units"] = "thousand_rub" + rejected(bad, "summary_osv в тысячах") + print(f"OK: примеров {len(EXAMPLES)}, отрицательных случаев 6") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/validate_contracts.py b/scripts/validate_contracts.py index e811fb3..5ea25c4 100644 --- a/scripts/validate_contracts.py +++ b/scripts/validate_contracts.py @@ -27,6 +27,7 @@ SCHEMAS = { "result": CONTRACTS / "recognition-result.schema.json", "message.v1": CONTRACTS / "recognition-result-message.v1.schema.json", "result.v1": CONTRACTS / "recognition-result.v1.schema.json", + "ex-payload": CONTRACTS / "ex-payload-v1.schema.json", } # Каталог примеров → суффикс вида схемы: examples/ — текущий MAJOR, examples/v1/ — прежний (окно перехода). @@ -37,6 +38,7 @@ CODE_RE = re.compile(r"^\d{4}$") # в перечне только четырё # Порядок важен: result-message-* проверяется раньше result-*. PATTERNS = [ + ("ex-payload-", "ex-payload"), ("request-", "request"), ("result-message-", "message"), ("result-", "result"), @@ -90,9 +92,9 @@ def main(): examples = [(example, suffix) for folder, suffix in EXAMPLE_DIRS for example in sorted(folder.glob("*.json"))] for example, suffix in examples: base = next((k for prefix, k in PATTERNS if example.name.startswith(prefix)), None) - kind = None if base is None else (base if base == "request" else base + suffix) + kind = None if base is None else (base if base in ("request", "ex-payload") else base + suffix) if kind is None or kind not in SCHEMAS: - print(f"FAIL {example.name}: имя не подходит ни под один шаблон (request-*, result-message-*, result-*)") + print(f"FAIL {example.name}: имя не подходит ни под один шаблон (request-*, result-message-*, result-*, ex-payload-*)") failed = True continue counts[kind] += 1