"""Генерирует 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}$"} # доля, % (ограничена) PF = {"type": ["string", "null"], "pattern": r"^-?\d+\.\d{2}$"} # процент к меньшей стороне — не ограничен QUARTER = {"type": "string", "enum": ["Q1", "Q2", "Q3", "Q4"]} UUID = {"type": "string", "format": "uuid"} UUIDN = {"type": ["string", "null"], "format": "uuid"} UUIDS1 = {"type": "array", "items": UUID, "minItems": 1} 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"]} frozen = {"properties": {"is_frozen": {"const": True}, "companies": {"minItems": 1}, "requisites": {"properties": {"version_status": {"const": "VERIFIED"}}}}} out["KfoBalanceData"] = {"allOf": [out["KfoBalanceData"], frozen]} out["KfoIncomeData"] = obj({**base, "income": d("KfoStatementOut")}, desc="KfoResultOut без editable/read_only_reason, только отчёт ОФР") out["KfoIncomeData"]["not"] = {"required": ["balance"]} out["KfoIncomeData"] = {"allOf": [out["KfoIncomeData"], frozen]} 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(QUARTER)}) 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({"entry_ids": UUIDS1, "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["CorrectionBalanceOut"] = obj({"debit_total": M, "credit_total": M, "difference": M, "difference_percent": PF, "remainder_1": MN, "remainder_2": MN}) out["CorrectionPairOut"] = obj({"side_1": d("AggregateSideOut"), "side_2": d("AggregateSideOut"), "deviation_amount": M, "deviation_percent": PF}) out["CorrectionRuleOut"] = obj({"id": UUID, "rule_no": {"type": "integer"}, "article_1_line": STRN, "article_2_line": STRN, "side_1": {"type": "string", "enum": ["D", "K"]}, "measure": STR, "company_1_accounts": arr(STR), "company_2_accounts": arr(STR), "company_1_excluded": arr(STR), "company_2_excluded": arr(STR)}, desc="Правило элиминации на дату верификации (CorrectionRuleOut)") out["CorrectionOut"] = obj({"status": {"type": "string", "enum": ["APPLIED"]}, "version_no": {"type": "integer"}, "debit": arr(d("CorrectionLineOut")), "credit": arr(d("CorrectionLineOut")), "comment": STR, "balance": d("CorrectionBalanceOut"), "updated_by": {"anyOf": [d("Ref"), {"type": "null"}]}, "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"), "pairs": arr(d("CorrectionPairOut")), "deviation_amount": M, "deviation_percent": PF, "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({"entry_ids": UUIDS1, "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"}, "rules": {"type": "array", "items": d("CorrectionRuleOut"), "minItems": 1}, "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": PF, "exceeds_threshold": {"type": "boolean"}, "one_sided": {"type": "boolean"}, "skipped_osv_entry_count": {"type": "integer"}}) totals = {"type": "object", "propertyNames": {"format": "uuid"}, "additionalProperties": M} out["IntercompanyMatrixOut"] = obj({"companies": {"type": "array", "items": d("Ref"), "minItems": 1}, "cells": arr(d("MatrixCellOut")), "total_debit": totals, "total_credit": totals}, 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"}, "statement_type": {"type": "string", "enum": ["balance", "income"]}, "rules_version_no": {"type": "integer"}, "rule": d("CorrectionRuleOut"), "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": QUARTER, "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": {"type": "array", "minItems": 1, "items": obj({"legal_entity_id": UUID, "name": STR, "inn": STR, "is_head_entity": {"type": "boolean"}})}, "history": {"type": "array", "minItems": 1, "items": 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)} eid = iter(f"aaaaaaaa-0000-4000-8000-{n:012d}" for n in range(1, 1000)) R2 = "66666666-6666-4666-8666-000000000002" R6 = "66666666-6666-4666-8666-000000000006" REFU = {"id": USER["user_id"], "name": USER["full_name"]} # updated_by — Ref, как в DTO API def entry(c, cp, acc, side, amt): return {"entry_ids": [next(eid)], "legal_entity_id": IDS[c], "counterparty_id": IDS[cp], "account_code": acc, "side": side, "amount": amt, "decision": "AUTO", "candidate_rule_ids": []} def side(line, accs, amt, entries): return {"article_line": line, "accounts": accs, "amount": amt, "entries": entries} def bal(dt, kt, a1, a2): from decimal import Decimal as Dd dt, kt, a1, a2 = Dd(dt), Dd(kt), Dd(a1), Dd(a2) diff = abs(dt - kt) return {"debit_total": f"{dt:.2f}", "credit_total": f"{kt:.2f}", "difference": f"{diff:.2f}", "difference_percent": f"{diff / min(dt, kt) * 100:.2f}" if min(dt, kt) > 0 else None, "remainder_1": f"{a1 - dt:.2f}", "remainder_2": f"{a2 - kt:.2f}"} 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, "balance": bal("10000000", "10000000", "10000000", "50000000"), "updated_by": REFU, "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, "balance": bal("25000000", "22500000", "30000000", "29100000"), "updated_by": REFU, "updated_at": "2025-11-13T09:10:00Z"} def agg(rule_id, rule_no, c1, c2, s1, s2, dev, pct, exc, status, elim, corr): return {"rule_id": rule_id, "rule_no": rule_no, "company_1": REF[c1], "company_2": REF[c2], "side_1": s1, "side_2": s2, "pairs": [], "deviation_amount": dev, "deviation_percent": pct, "exceeds_threshold": exc, "status": status, "eliminated_amount": elim, "composition_changed": False, "correction": corr} ab_1 = side("1230", ["62.01"], "102000000.00", [entry("A", "B", "62.01", "D", "102000000.00")]) ab_2 = side("1520", ["60.01"], "100000400.50", [entry("B", "A", "60.01", "K", "100000400.50")]) a_ab = agg(R6, 6, "A", "B", ab_1, ab_2, "1999599.50", "2.00", False, "AUTO", "100000400.50", None) a_ab["pairs"] = [{"side_1": ab_1, "side_2": ab_2, "deviation_amount": "1999599.50", "deviation_percent": "2.00"}] aggs = [ agg(R2, 2, "A", "B", side("1170", ["58.01"], "50000000.00", [entry("A", "B", "58.01", "D", "50000000.00")]), side("1310/1350/1370", ["80"], "40000000.00", [entry("B", "A", "80", "K", "40000000.00")]), "10000000.00", "25.00", False, "AUTO", "40000000.00", None), agg(R2, 2, "A", "C", side("1170", ["58.01"], "15000000.00", [entry("A", "C", "58.01", "D", "15000000.00")]), side("1310/1350/1370", [], "0.00", []), "15000000.00", None, False, "ONE_SIDED", "15000000.00", None), a_ab, agg(R6, 6, "A", "C", side("1230", ["62.01"], "30000000.00", [entry("A", "C", "62.01", "D", "30000000.00")]), side("1520", ["60.01"], "29100000.00", [entry("C", "A", "60.01", "K", "29100000.00")]), "900000.00", "3.09", False, "AUTO", "29100000.00", corr_ac), agg(R6, 6, "B", "A", side("1230", ["62.01"], "10000000.00", [entry("B", "A", "62.01", "D", "10000000.00")]), side("1520", ["60.01", "76.05"], "50000000.00", [entry("A", "B", "60.01", "K", "12000000.00"), entry("A", "B", "76.05", "K", "38000000.00")]), "40000000.00", "400.00", True, "DISCREPANCY", "0.00", corr_ba), ] rule2 = {"id": R2, "rule_no": 2, "article_1_line": "1170", "article_2_line": "1310/1350/1370", "side_1": "D", "measure": "closing", "company_1_accounts": ["58.01"], "company_2_accounts": ["80", "83"], "company_1_excluded": [], "company_2_excluded": []} rule6 = {"id": R6, "rule_no": 6, "article_1_line": "1230", "article_2_line": "1520", "side_1": "D", "measure": "closing", "company_1_accounts": ["60.02", "60.22", "60.32", "62.01", "62.03", "62.21", "62.31", "76"], "company_2_accounts": ["60.01", "60.03", "60.21", "60.31", "62.02", "62.22", "62.32", "76"], "company_1_excluded": ["76.7.5", "76.7.9"], "company_2_excluded": ["76.7.1"]} vgo = {"balance": {"version_id": kfo.VERSION, "statement_type": "balance", "rules_version_no": 1, "rules": [rule2, rule6], "aggregates": aggs, "unallocated": [{"entry_ids": [next(eid)], "owner": REF["C"], "counterparty": REF["A"], "account_code": "76.09", "line_code": None, "side": "D", "amount": "350000.00", "candidate_rule_ids": [R2, R6], "reason": "MULTIPLE_RULES", "excluded": True}], "totals": {"amount_1": "207000000.00", "amount_2": "219100400.50", "deviation_amount": "67899599.50", "correction_debit": "35000000.00", "correction_credit": "32500000.00"}}, "income": None} out["vgo_pairs"] = env("vgo_pairs", vgo) out["corrections"] = env("corrections", {"items": [ {"statement_type": "balance", "rule_no": 6, "company_1": REF["A"], "company_2": REF["C"], "correction": corr_ac}, {"statement_type": "balance", "rule_no": 6, "company_1": REF["B"], "company_2": REF["A"], "correction": corr_ba}, ]}) def row(c, acc, cp, ctype, cd=None, cc=None): return {"entry_ids": [next(eid)], "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": ["Q1"]}], "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} tot_d = {IDS["A"]: "102000000.00", IDS["B"]: "0.00", IDS["C"]: "0.00"} tot_k = {IDS["A"]: "0.00", IDS["B"]: "100000400.50", IDS["C"]: "0.00"} matrix = {"companies": [REF[c] for c in kfo.ex.COMPANIES], "cells": [cell], "total_debit": tot_d, "total_credit": tot_k} out["chess"] = env("chess", { "matrix_all": matrix, "presets": [{"rule_no": 6, "statement_type": "balance", "rules_version_no": 1, "rule": rule6, "matrix": matrix}], "result_table": {"companies": [REF[c] for c in kfo.ex.COMPANIES], "articles": [ {"code": "1230", "name": "Дебиторская задолженность", "group_total": "142000000.00", "amounts": [{"company_id": IDS["A"], "amount": "132000000.00"}, {"company_id": IDS["B"], "amount": "10000000.00"}, {"company_id": IDS["C"], "amount": "0.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": REFU, "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": "Q3", "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()