ЕХ: схема ex-payload-v1, примеры, проверка в validate_contracts (#208)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
9086c064e4
commit
013f7b673a
2108
contracts/ex-payload-v1.schema.json
Normal file
2108
contracts/ex-payload-v1.schema.json
Normal file
File diff suppressed because it is too large
Load Diff
105
contracts/examples/ex-payload-chess.json
Normal file
105
contracts/examples/ex-payload-chess.json
Normal file
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
85
contracts/examples/ex-payload-corrections.json
Normal file
85
contracts/examples/ex-payload-corrections.json
Normal file
@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
60
contracts/examples/ex-payload-goodwill.json
Normal file
60
contracts/examples/ex-payload-goodwill.json
Normal file
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
637
contracts/examples/ex-payload-kfo-balance.json
Normal file
637
contracts/examples/ex-payload-kfo-balance.json
Normal file
@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
317
contracts/examples/ex-payload-kfo-income.json
Normal file
317
contracts/examples/ex-payload-kfo-income.json
Normal file
@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
25
contracts/examples/ex-payload-other-report.json
Normal file
25
contracts/examples/ex-payload-other-report.json
Normal file
@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
106
contracts/examples/ex-payload-summary-osv.json
Normal file
106
contracts/examples/ex-payload-summary-osv.json
Normal file
@ -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": []
|
||||
}
|
||||
}
|
||||
}
|
||||
82
contracts/examples/ex-payload-version-card.json
Normal file
82
contracts/examples/ex-payload-version-card.json
Normal file
@ -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": "Отклонений от Методики нет"
|
||||
}
|
||||
}
|
||||
192
contracts/examples/ex-payload-vgo-pairs.json
Normal file
192
contracts/examples/ex-payload-vgo-pairs.json
Normal file
@ -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
|
||||
}
|
||||
}
|
||||
343
scripts/kfo_contract/gen_ex_payload.py
Normal file
343
scripts/kfo_contract/gen_ex_payload.py
Normal file
@ -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()
|
||||
63
scripts/kfo_contract/test_ex_payload.py
Normal file
63
scripts/kfo_contract/test_ex_payload.py
Normal file
@ -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())
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user