@ -1208,7 +1208,7 @@ OQ — менять по ответу.
|
||||
| VERIFICATION_RF | CONSOLIDATION_RF | РКА РФ | вернул (комментарий обязателен) |
|
||||
| CONSOLIDATION_GO | CONSOLIDATION_RF | КА ГО | вернул на доработку (комментарий) |
|
||||
| CONSOLIDATION_GO | VERIFICATION_GO | КА ГО | если инициатор — КА ГО, верифицирует РКА ГО [открыто] |
|
||||
| CONSOLIDATION_GO / VERIFICATION_GO | VERIFIED | КА ГО / РКА ГО | внесены № и дата решения УОБ; версия read-only; пишется `ex_snapshot` |
|
||||
| CONSOLIDATION_GO / VERIFICATION_GO | VERIFIED | КА ГО / РКА ГО | внесены № и дата решения УОБ; версия read-only; пишется `ex_snapshot` (§15) |
|
||||
| любой нефинальный | INITIATED | КА | нужен другой периметр/сценарий/комплект |
|
||||
| любой нефинальный | CANCELLED | КА | аннулировано |
|
||||
| VERIFIED | — | — | только «новая версия» (`previous_version_id`) |
|
||||
@ -1708,9 +1708,6 @@ OQ-92 (пустые «Организация»/«Группа компаний»
|
||||
|
||||
Все мутации пишут `audit_event` (`GROUP_CHANGE`, `ASSIGNMENT_CHANGE`, `MEMBERSHIP_CHANGE`, `LEGAL_ENTITY_CHANGE` — v5.14, `DIRECTORY_IMPORT`) с `before/after`.
|
||||
|
||||
---
|
||||
|
||||
|
||||
---
|
||||
|
||||
## §11. Нормализация форм ББ/ОФР — общая цепочка (scan-gateway и tabular-parser)
|
||||
@ -2396,3 +2393,62 @@ tabular-parser, поэтому у Excel любая ошибка распозна
|
||||
считается цифровым (`is_scan = false`) — слой есть, но получен постобработкой, а не исходно цифровым документом;
|
||||
различить программно эти два случая без эвристик за пределами среза не планируется. Для не-PDF (`pipeline = tabular`)
|
||||
`is_scan = NULL`.
|
||||
|
||||
---
|
||||
|
||||
## §15. Срез ЕХ при верификации (DOC-274687, DOC-274686, DOC-274685; ЕХ ТЗ 5.2, 6.2–6.4; AURORA-1560 — запись, AURORA-2001 — чтение и архив, AURORA-2002 — переход; ещё не реализовано)
|
||||
|
||||
Срез — неизменяемая самодостаточная заморозка верифицированной версии: его читают и выгружают, когда рабочих таблиц
|
||||
консолидации уже нет (решение команды 28–29.09.2026). Схема данных — `contracts/ex-payload-v1.schema.json` (генерируется
|
||||
`scripts/kfo_contract/gen_ex_payload.py`, примеры — `contracts/examples/ex-payload-*.json`).
|
||||
|
||||
1. **Когда.** Один раз — при переходе версии в «Верифицирован», после фиксации ГСК версии (AURORA-2002). Один срез на
|
||||
версию (`ex_snapshot.source_version_id` уникален; повторная попытка второй срез не пишет). Другие переходы, в том
|
||||
числе «Отменён», срез не пишут. Срез по монозаёмщику не формируется (первичка хранится без среза).
|
||||
2. **Номер и связи.** `version_no` — следующий по (ГСК, год, квартал) — уникальный индекс модели; новая ГСК после смены
|
||||
периметра начинает с 1. Переход в «Верифицирован» уже проверил, что у той же **ГК** за ту же отчётную дату нет
|
||||
актуального среза (архив прежнего ручной, решение 27.10.2026, Р-35) — поэтому после записи актуальный срез по ГК и
|
||||
дате ровно один, новый, `status = ACTUAL`. Запись прежний срез в архив **не переводит**; архивным срезам той же ГК и
|
||||
даты без ссылки ставится `superseded_by_snapshot_id` нового (по ГК, а не ГСК, — чтобы ссылку получил и срез прежней
|
||||
ГСК); разархивирование ссылку снимает (AURORA-2001). Ключ «ГК × дата» или «ГСК × дата» — вопрос заказчику (OQ-196):
|
||||
при «ГСК» меняются проверка 2002 и это правило, схема payload — нет.
|
||||
3. **Состав.**
|
||||
- `ex_snapshot` — метаданные: ГСК, ГК (на дату), отчётная дата, год, квартал, номер, статус, источник
|
||||
(`source_version_id` без FK, `source_version_no`), `verified_at`, `written_at`, `written_by` (кто верифицировал);
|
||||
- `ex_snapshot_member` — по строке на ЮЛ версии (`consolidation_member`) с ИНН и наименованием на момент верификации;
|
||||
- `ex_snapshot_item`: `primary_document` — по элементу на документ `consolidation_document` (ссылка `document_id`,
|
||||
`legal_entity_id`, без payload); остальные типы — по одному элементу с payload: `summary_osv`, `vgo_pairs`,
|
||||
`chess`, `kfo_balance`, `kfo_income`, `corrections`, `version_card`; `goodwill` — только при `goodwill_enabled`;
|
||||
`other_report` — только при `scenario = CONFIRM`.
|
||||
4. **Что в payload.** Конверт `{schema_version: "1.0", item_type, units, generated_at, source_version_no, data}`;
|
||||
`data` — **ответ API экрана рабочей версии на момент верификации, теми же DTO** (сводная ОСВ, таблица корректировок,
|
||||
шахматка, КФО, гудвилл), без полей редактирования и с тем, что нужно выгрузке xlsx рабочей версии (пробелы и способ
|
||||
сопоставления свода, строки ОСВ агрегатов, сводка корректировок КФО) — срез выгружается на лету из payload и должен
|
||||
совпадать с выгрузкой рабочей версии. Шахматка — режим «все счета», пресет каждого правила ББ версии правил и
|
||||
результирующая таблица; ручной выбор счетов не замораживается. Корректировки — только применённые (`APPLIED`;
|
||||
`REVOKED` нет, `DRAFT` не пишется; агрегат с неприменённой корректировкой — с `correction = null`). Сводная ОСВ —
|
||||
весь свод без фильтров и страниц, в порядке API. Карточка версии (`version_card`) — участники маршрута с ФИО,
|
||||
решение УОБ, причины отклонений, состав с ИНН: версия КФО удаляется раньше среза. Пользователи в DTO — как в API
|
||||
(`Ref {id, name}`, ФИО на дату верификации); в карточке — `UserRef {user_id, full_name}`.
|
||||
**Ошибка экрана при сборке.** Пресет правила, для которого экран отвечает 422 (`INVALID_RULE`,
|
||||
`ACCOUNTS_REQUIRED`), в `chess.presets` не пишется; любая другая ошибка сборки обязательного элемента (в том числе
|
||||
`RESULT_ACCOUNTS_MISSING` результирующей таблицы) — сбой записи среза (п. 9).
|
||||
**Проверка.** Писатель проверяет каждый payload схемой `ex-payload-v1` с проверкой форматов (`uuid`, `date`,
|
||||
`date-time`); ядро держит копию схемы у себя, как схемы контракта распознавания (копия + проверка идентичности).
|
||||
5. **Не входят:** строки форм без кода (документ — целиком, ссылкой; решение 17.10.2026), документы, не привязанные к
|
||||
версии, и документы заменённых файлов, черновики корректировок.
|
||||
6. **Самодостаточность.** Ссылки допускаются только на то, что живёт не меньше среза: `document`, `legal_entity`,
|
||||
`related_group`, `company_group`, `app_user`. Id строк ОСВ, правил, корректировок в payload — справочно, для
|
||||
рендера не нужны; имена и ИНН компаний — внутри DTO.
|
||||
7. **Суммы и единицы** — как в источнике, строками с двумя знаками, без перевода: ОСВ, агрегаты, шахматка, гудвилл,
|
||||
корректировки — `rub`; КФО и сравнение с клиентом — `thousand_rub` (§8 «КФО»); карточка — `none`.
|
||||
8. **Заморозка.** Производные (свод, агрегаты, шахматка, КФО) считаются в последний раз и кладутся в срез;
|
||||
`kfo_result.is_frozen = true`; верифицированная версия — только чтение, в том числе после архивирования среза.
|
||||
Срез после записи не меняется; меняются только служебные поля жизненного цикла (статус, ссылка на следующий срез,
|
||||
отметки data lake).
|
||||
9. **Сбой записи** не отменяет верификацию и не откатывает фиксацию ГСК (Excel статусно-ролевой модели, 13.10.2026).
|
||||
Повтор записи — вопрос заказчику (OQ-197); на старте — версия «Верифицирован» без среза считается действующей
|
||||
верифицированной КФО (блокирует финал следующей версии той же ГК и даты, DOC-274686), а признак «не передана в ЕХ»
|
||||
вычисляется: `VERIFIED` без `ex_snapshot` с её `source_version_id`.
|
||||
10. **Аудит** — `EX_SNAPSHOT_WRITE` (раздел `KFO`, `object_type = ex_snapshot`, `after` — номер среза, ГСК, дата, число
|
||||
элементов по типам). PDF «Консолидированная отчётность» не формируется и не хранится (OQ-39, OQ-198).
|
||||
|
||||
@ -199,3 +199,7 @@
|
||||
| OQ-192 | Пересчёт КФО: автоматически после каждого изменения (Excel Р-11, Р-23), по кнопке «Рассчитать КФО» с пометкой «устарела» (FNR03) или только при открытии? | DOC-274636 «Расчёт КФО» (черновик), «Что решить», вопрос 16; AURORA-1417 | §8 «КФО» п. 12 | открыт, вопрос заказчику; допущение принято (docs #206): автоматически: чтение пересчитывает устаревший результат, кнопки нет |
|
||||
| OQ-193 | Правило ББ 1 (76.7.1 / 76.7.9) автоматически по DOC-274230: обе суммы — у компании 2 (Кт 76.7.1 − Дт 76.7.9, каскад 1150 → 1450 → 1550), а сторона агрегата §5 устроена «компания 1 — сторона 1». Как переложить правило в seed (стороны, режим)? | DOC-274230 правило 1; `arm-core/api/seed/core/elimination_rules_v1.yaml` | §5 п. 1, §8 «КФО» п. 4 | открыт, задача команды (follow-up AURORA-1546); до решения правило 1 — `manual`, только корректировкой |
|
||||
| OQ-194 | КФО при неполном комплекте документов (обязательный комплект и проверки перед расчётом отложены, Excel В-01, В-08): считать по тому, что подтверждено и привязано, с предупреждением, или не считать, пока комплект не полон? | DOC-274636 «Расчёт КФО» (черновик), «Что решить», допущение 17 | §8 «КФО» п. 2 | открыт, вопрос заказчику; допущение принято (docs #206): считать, у компании без формы — нули и `FORM_MISSING` |
|
||||
| OQ-195 | Кто переводит прежний срез ЕХ в архив: вручную до верификации новой версии (Excel статусно-ролевой модели, Р-35) или автоматически при записи нового среза (ЕХ ТЗ 6.4)? | DOC-274686 «Версии и прошлые периоды», вопрос 1; AURORA-1397 | §15 п. 2 | открыт, вопрос заказчику (аналитик — AURORA-1397); допущение принято (docs #208): вручную, запись нового среза прежний не архивирует |
|
||||
| OQ-196 | Ключ «того же периода» для одного актуального среза и блокировки верификации: ГК × дата (решения Excel Р-35/Р-36) или ГСК × дата (ЕХ ТЗ 6.2, 6.4)? | DOC-274686, вопрос 2; AURORA-1397 | §15 п. 2; AURORA-2002 | открыт, вопрос заказчику; допущение: ГК × дата для «один актуальный» и `superseded_by_snapshot_id`, нумерация — по ГСК (индекс модели) |
|
||||
| OQ-197 | Срез при верификации не записался: повторять запись с признаком «не передана в ЕХ», делать верификацию и запись одной операцией или не повторять? | DOC-274687, вопрос 6; Excel 13.10.2026 («механизм повторной передачи не определён») | §15 п. 9 | открыт, вопрос заказчику; допущение: верификация остаётся, признак вычисляется (VERIFIED без среза), такая версия блокирует финал следующей |
|
||||
| OQ-198 | Нужен ли PDF «Консолидированная отчётность» в ЕХ (макет [83-01]) и что в нём? | DOC-274687, вопрос 2; макет [83-01]; ЕХ ТЗ 6.7.3 | §15 п. 10 | открыт, вопрос заказчику (продолжение OQ-39); допущение: не формируется и не хранится, xlsx — из payload на лету |
|
||||
|
||||
@ -284,7 +284,7 @@ DATA_CORRECTION, истечение блокировки, `period_check`, пор
|
||||
## Срез 7 — Маршрут согласования, ЕХ, журнал, выгрузки
|
||||
|
||||
- Полная машина статусов §9.1 с `consolidation_status_history`, policy по ролям/филиалам; № и дата УОБ; новая версия.
|
||||
- `VERIFIED` → `ex_snapshot/_member/_item` (payload по схеме `docs/contracts/ex-payload-v1.schema.json`); read-only.
|
||||
- `VERIFIED` → `ex_snapshot/_member/_item` (payload по схеме `docs/contracts/ex-payload-v1.schema.json`, правила — `05-domain-rules.md` §15); read-only.
|
||||
- ЛК ЕХ: дерево ГК → ГСК → организации, просмотр payload, скачивание первички, выгрузка xlsx на лету.
|
||||
- Журнал действий: экран с фильтрами по разделам/объектам/полям.
|
||||
- Экспорт: настраиваемые колонки, `audit_event(EXPORT)`, S3 `exports/` 7 дней.
|
||||
|
||||
2397
contracts/ex-payload-v1.schema.json
Normal file
2397
contracts/ex-payload-v1.schema.json
Normal file
File diff suppressed because it is too large
Load Diff
166
contracts/examples/ex-payload-chess.json
Normal file
166
contracts/examples/ex-payload-chess.json
Normal file
@ -0,0 +1,166 @@
|
||||
{
|
||||
"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
|
||||
}
|
||||
],
|
||||
"total_debit": {
|
||||
"11111111-1111-4111-8111-111111111111": "102000000.00",
|
||||
"22222222-2222-4222-8222-222222222222": "0.00",
|
||||
"33333333-3333-4333-8333-333333333333": "0.00"
|
||||
},
|
||||
"total_credit": {
|
||||
"11111111-1111-4111-8111-111111111111": "0.00",
|
||||
"22222222-2222-4222-8222-222222222222": "100000400.50",
|
||||
"33333333-3333-4333-8333-333333333333": "0.00"
|
||||
}
|
||||
},
|
||||
"presets": [
|
||||
{
|
||||
"rule_no": 6,
|
||||
"statement_type": "balance",
|
||||
"rules_version_no": 1,
|
||||
"rule": {
|
||||
"id": "66666666-6666-4666-8666-000000000006",
|
||||
"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"
|
||||
]
|
||||
},
|
||||
"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
|
||||
}
|
||||
],
|
||||
"total_debit": {
|
||||
"11111111-1111-4111-8111-111111111111": "102000000.00",
|
||||
"22222222-2222-4222-8222-222222222222": "0.00",
|
||||
"33333333-3333-4333-8333-333333333333": "0.00"
|
||||
},
|
||||
"total_credit": {
|
||||
"11111111-1111-4111-8111-111111111111": "0.00",
|
||||
"22222222-2222-4222-8222-222222222222": "100000400.50",
|
||||
"33333333-3333-4333-8333-333333333333": "0.00"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"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": "142000000.00",
|
||||
"amounts": [
|
||||
{
|
||||
"company_id": "11111111-1111-4111-8111-111111111111",
|
||||
"amount": "132000000.00"
|
||||
},
|
||||
{
|
||||
"company_id": "22222222-2222-4222-8222-222222222222",
|
||||
"amount": "10000000.00"
|
||||
},
|
||||
{
|
||||
"company_id": "33333333-3333-4333-8333-333333333333",
|
||||
"amount": "0.00"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
101
contracts/examples/ex-payload-corrections.json
Normal file
101
contracts/examples/ex-payload-corrections.json
Normal file
@ -0,0 +1,101 @@
|
||||
{
|
||||
"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": "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,
|
||||
"balance": {
|
||||
"debit_total": "25000000.00",
|
||||
"credit_total": "22500000.00",
|
||||
"difference": "2500000.00",
|
||||
"difference_percent": "11.11",
|
||||
"remainder_1": "5000000.00",
|
||||
"remainder_2": "6600000.00"
|
||||
},
|
||||
"updated_by": {
|
||||
"id": "44444444-4444-4444-8444-444444444444",
|
||||
"name": "Петрова Мария Ивановна"
|
||||
},
|
||||
"updated_at": "2025-11-13T09:10:00Z"
|
||||
}
|
||||
},
|
||||
{
|
||||
"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,
|
||||
"balance": {
|
||||
"debit_total": "10000000.00",
|
||||
"credit_total": "10000000.00",
|
||||
"difference": "0.00",
|
||||
"difference_percent": "0.00",
|
||||
"remainder_1": "0.00",
|
||||
"remainder_2": "40000000.00"
|
||||
},
|
||||
"updated_by": {
|
||||
"id": "44444444-4444-4444-8444-444444444444",
|
||||
"name": "Петрова Мария Ивановна"
|
||||
},
|
||||
"updated_at": "2025-11-12T14:30: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": {
|
||||
"id": "44444444-4444-4444-8444-444444444444",
|
||||
"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
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
112
contracts/examples/ex-payload-summary-osv.json
Normal file
112
contracts/examples/ex-payload-summary-osv.json
Normal file
@ -0,0 +1,112 @@
|
||||
{
|
||||
"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": [
|
||||
"aaaaaaaa-0000-4000-8000-000000000013"
|
||||
],
|
||||
"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": [
|
||||
"aaaaaaaa-0000-4000-8000-000000000014"
|
||||
],
|
||||
"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": [
|
||||
"aaaaaaaa-0000-4000-8000-000000000012"
|
||||
],
|
||||
"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": [
|
||||
"Q1"
|
||||
]
|
||||
}
|
||||
],
|
||||
"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": "Q3",
|
||||
"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": "Отклонений от Методики нет"
|
||||
}
|
||||
}
|
||||
522
contracts/examples/ex-payload-vgo-pairs.json
Normal file
522
contracts/examples/ex-payload-vgo-pairs.json
Normal file
@ -0,0 +1,522 @@
|
||||
{
|
||||
"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": [
|
||||
{
|
||||
"id": "66666666-6666-4666-8666-000000000002",
|
||||
"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": []
|
||||
},
|
||||
{
|
||||
"id": "66666666-6666-4666-8666-000000000006",
|
||||
"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"
|
||||
]
|
||||
}
|
||||
],
|
||||
"aggregates": [
|
||||
{
|
||||
"rule_id": "66666666-6666-4666-8666-000000000002",
|
||||
"rule_no": 2,
|
||||
"company_1": {
|
||||
"id": "11111111-1111-4111-8111-111111111111",
|
||||
"name": "ООО «Альфа»"
|
||||
},
|
||||
"company_2": {
|
||||
"id": "22222222-2222-4222-8222-222222222222",
|
||||
"name": "ООО «Бета»"
|
||||
},
|
||||
"side_1": {
|
||||
"article_line": "1170",
|
||||
"accounts": [
|
||||
"58.01"
|
||||
],
|
||||
"amount": "50000000.00",
|
||||
"entries": [
|
||||
{
|
||||
"entry_ids": [
|
||||
"aaaaaaaa-0000-4000-8000-000000000003"
|
||||
],
|
||||
"legal_entity_id": "11111111-1111-4111-8111-111111111111",
|
||||
"counterparty_id": "22222222-2222-4222-8222-222222222222",
|
||||
"account_code": "58.01",
|
||||
"side": "D",
|
||||
"amount": "50000000.00",
|
||||
"decision": "AUTO",
|
||||
"candidate_rule_ids": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"side_2": {
|
||||
"article_line": "1310/1350/1370",
|
||||
"accounts": [
|
||||
"80"
|
||||
],
|
||||
"amount": "40000000.00",
|
||||
"entries": [
|
||||
{
|
||||
"entry_ids": [
|
||||
"aaaaaaaa-0000-4000-8000-000000000004"
|
||||
],
|
||||
"legal_entity_id": "22222222-2222-4222-8222-222222222222",
|
||||
"counterparty_id": "11111111-1111-4111-8111-111111111111",
|
||||
"account_code": "80",
|
||||
"side": "K",
|
||||
"amount": "40000000.00",
|
||||
"decision": "AUTO",
|
||||
"candidate_rule_ids": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"pairs": [],
|
||||
"deviation_amount": "10000000.00",
|
||||
"deviation_percent": "25.00",
|
||||
"exceeds_threshold": false,
|
||||
"status": "AUTO",
|
||||
"eliminated_amount": "40000000.00",
|
||||
"composition_changed": false,
|
||||
"correction": null
|
||||
},
|
||||
{
|
||||
"rule_id": "66666666-6666-4666-8666-000000000002",
|
||||
"rule_no": 2,
|
||||
"company_1": {
|
||||
"id": "11111111-1111-4111-8111-111111111111",
|
||||
"name": "ООО «Альфа»"
|
||||
},
|
||||
"company_2": {
|
||||
"id": "33333333-3333-4333-8333-333333333333",
|
||||
"name": "ООО «Гамма»"
|
||||
},
|
||||
"side_1": {
|
||||
"article_line": "1170",
|
||||
"accounts": [
|
||||
"58.01"
|
||||
],
|
||||
"amount": "15000000.00",
|
||||
"entries": [
|
||||
{
|
||||
"entry_ids": [
|
||||
"aaaaaaaa-0000-4000-8000-000000000005"
|
||||
],
|
||||
"legal_entity_id": "11111111-1111-4111-8111-111111111111",
|
||||
"counterparty_id": "33333333-3333-4333-8333-333333333333",
|
||||
"account_code": "58.01",
|
||||
"side": "D",
|
||||
"amount": "15000000.00",
|
||||
"decision": "AUTO",
|
||||
"candidate_rule_ids": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"side_2": {
|
||||
"article_line": "1310/1350/1370",
|
||||
"accounts": [],
|
||||
"amount": "0.00",
|
||||
"entries": []
|
||||
},
|
||||
"pairs": [],
|
||||
"deviation_amount": "15000000.00",
|
||||
"deviation_percent": null,
|
||||
"exceeds_threshold": false,
|
||||
"status": "ONE_SIDED",
|
||||
"eliminated_amount": "15000000.00",
|
||||
"composition_changed": false,
|
||||
"correction": null
|
||||
},
|
||||
{
|
||||
"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": [
|
||||
"aaaaaaaa-0000-4000-8000-000000000001"
|
||||
],
|
||||
"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": [
|
||||
"aaaaaaaa-0000-4000-8000-000000000002"
|
||||
],
|
||||
"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": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"pairs": [
|
||||
{
|
||||
"side_1": {
|
||||
"article_line": "1230",
|
||||
"accounts": [
|
||||
"62.01"
|
||||
],
|
||||
"amount": "102000000.00",
|
||||
"entries": [
|
||||
{
|
||||
"entry_ids": [
|
||||
"aaaaaaaa-0000-4000-8000-000000000001"
|
||||
],
|
||||
"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": [
|
||||
"aaaaaaaa-0000-4000-8000-000000000002"
|
||||
],
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"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": "11111111-1111-4111-8111-111111111111",
|
||||
"name": "ООО «Альфа»"
|
||||
},
|
||||
"company_2": {
|
||||
"id": "33333333-3333-4333-8333-333333333333",
|
||||
"name": "ООО «Гамма»"
|
||||
},
|
||||
"side_1": {
|
||||
"article_line": "1230",
|
||||
"accounts": [
|
||||
"62.01"
|
||||
],
|
||||
"amount": "30000000.00",
|
||||
"entries": [
|
||||
{
|
||||
"entry_ids": [
|
||||
"aaaaaaaa-0000-4000-8000-000000000006"
|
||||
],
|
||||
"legal_entity_id": "11111111-1111-4111-8111-111111111111",
|
||||
"counterparty_id": "33333333-3333-4333-8333-333333333333",
|
||||
"account_code": "62.01",
|
||||
"side": "D",
|
||||
"amount": "30000000.00",
|
||||
"decision": "AUTO",
|
||||
"candidate_rule_ids": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"side_2": {
|
||||
"article_line": "1520",
|
||||
"accounts": [
|
||||
"60.01"
|
||||
],
|
||||
"amount": "29100000.00",
|
||||
"entries": [
|
||||
{
|
||||
"entry_ids": [
|
||||
"aaaaaaaa-0000-4000-8000-000000000007"
|
||||
],
|
||||
"legal_entity_id": "33333333-3333-4333-8333-333333333333",
|
||||
"counterparty_id": "11111111-1111-4111-8111-111111111111",
|
||||
"account_code": "60.01",
|
||||
"side": "K",
|
||||
"amount": "29100000.00",
|
||||
"decision": "AUTO",
|
||||
"candidate_rule_ids": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"pairs": [],
|
||||
"deviation_amount": "900000.00",
|
||||
"deviation_percent": "3.09",
|
||||
"exceeds_threshold": false,
|
||||
"status": "AUTO",
|
||||
"eliminated_amount": "29100000.00",
|
||||
"composition_changed": false,
|
||||
"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,
|
||||
"balance": {
|
||||
"debit_total": "25000000.00",
|
||||
"credit_total": "22500000.00",
|
||||
"difference": "2500000.00",
|
||||
"difference_percent": "11.11",
|
||||
"remainder_1": "5000000.00",
|
||||
"remainder_2": "6600000.00"
|
||||
},
|
||||
"updated_by": {
|
||||
"id": "44444444-4444-4444-8444-444444444444",
|
||||
"name": "Петрова Мария Ивановна"
|
||||
},
|
||||
"updated_at": "2025-11-13T09:10:00Z"
|
||||
}
|
||||
},
|
||||
{
|
||||
"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": [
|
||||
"aaaaaaaa-0000-4000-8000-000000000008"
|
||||
],
|
||||
"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": [
|
||||
"aaaaaaaa-0000-4000-8000-000000000009"
|
||||
],
|
||||
"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": [
|
||||
"aaaaaaaa-0000-4000-8000-000000000010"
|
||||
],
|
||||
"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": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"pairs": [],
|
||||
"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,
|
||||
"balance": {
|
||||
"debit_total": "10000000.00",
|
||||
"credit_total": "10000000.00",
|
||||
"difference": "0.00",
|
||||
"difference_percent": "0.00",
|
||||
"remainder_1": "0.00",
|
||||
"remainder_2": "40000000.00"
|
||||
},
|
||||
"updated_by": {
|
||||
"id": "44444444-4444-4444-8444-444444444444",
|
||||
"name": "Петрова Мария Ивановна"
|
||||
},
|
||||
"updated_at": "2025-11-12T14:30:00Z"
|
||||
}
|
||||
}
|
||||
],
|
||||
"unallocated": [
|
||||
{
|
||||
"entry_ids": [
|
||||
"aaaaaaaa-0000-4000-8000-000000000011"
|
||||
],
|
||||
"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": [
|
||||
"66666666-6666-4666-8666-000000000002",
|
||||
"66666666-6666-4666-8666-000000000006"
|
||||
],
|
||||
"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": null
|
||||
}
|
||||
}
|
||||
@ -19,8 +19,14 @@
|
||||
// 5. kfo_result_line: пояснения приведены к §8 «КФО» (единицы — тыс. руб.; eliminated_auto / eliminated_manual — от
|
||||
// агрегатов без / с корректировкой, по одному агрегату не складываются; goodwill_adjust — у GOODWILL и итогов;
|
||||
// consolidated_amount = sum − vgo + goodwill_adjust). Структура не меняется.
|
||||
// Миграция: новые колонки nullable, значение enum; откат удаляет колонки, KFO_EDIT при наличии событий не
|
||||
// откатывается (как FILE_REPROCESS в v5.21).
|
||||
// 6. ex_snapshot_item.item_type: + version_card — карточка версии на дату верификации (участники маршрута с ФИО, решение
|
||||
// УОБ, обоснования, состав); payload — конверт contracts/ex-payload-v1.schema.json (у primary_document — null),
|
||||
// payload_schema_version = "1.0" (RSHB/arm-ka-docs#208, AURORA-1560; context/05-domain-rules.md §15).
|
||||
// 7. audit_action: + EX_SNAPSHOT_WRITE — запись среза ЕХ при «Верифицирован», раздел KFO.
|
||||
// 8. ex_snapshot.status: пояснение под ручной архив (решение 27.10.2026) — ACTUAL «не архивирован», а не «последняя
|
||||
// верифицированная»; запись среза прежний в архив не переводит. Структура не меняется.
|
||||
// Миграция: новые колонки nullable, значения enum (KFO_EDIT — AURORA-1546, EX_SNAPSHOT_WRITE — AURORA-1560); откат
|
||||
// удаляет колонки, значения enum при наличии событий не откатываются (как FILE_REPROCESS в v5.21).
|
||||
//
|
||||
// v5.25 (AURORA-1759, RSHB/arm-ka-docs#202; миграция в arm-core — отдельный change arm-core#190):
|
||||
// 1. Три новые таблицы фактов качества распознавания первички: quality_intake_fact, quality_outcome_fact,
|
||||
@ -571,6 +577,7 @@ Enum audit_action {
|
||||
CORRECTION_EDIT
|
||||
CORRECTION_REVOKE
|
||||
GOODWILL_EDIT
|
||||
EX_SNAPSHOT_WRITE [note: 'v5.26: запись среза ЕХ при «Верифицирован» (§15); раздел KFO, object_type = ex_snapshot']
|
||||
KFO_EDIT [note: 'v5.26: ручные поля КФО — комментарии строк, обоснование порога, причины отклонений от Методики; раздел KFO']
|
||||
EXPORT
|
||||
DIRECTORY_IMPORT
|
||||
@ -1582,13 +1589,13 @@ Table ex_snapshot {
|
||||
year smallint [not null]
|
||||
quarter period_quarter [not null]
|
||||
version_no int [not null]
|
||||
status ex_snapshot_status [not null, note: 'ACTUAL — последняя верифицированная; предыдущие → ARCHIVE']
|
||||
status ex_snapshot_status [not null, note: 'v5.26: ACTUAL — не архивирован; ARCHIVE — архивирован пользователем в ЕХ (ручной архив, решение 27.10.2026); запись нового среза прежний не архивирует (§15)']
|
||||
source_version_id uuid [not null, unique, note: 'v5.12: id consolidation_version-источника БЕЗ FK — источник удаляется по сроку, срез нет']
|
||||
source_version_no int [not null]
|
||||
superseded_by_snapshot_id uuid [ref: > ex_snapshot.id, note: 'v5.12: для ARCHIVE — ссылка на актуальный срез, который его заместил']
|
||||
verified_at timestamptz [not null]
|
||||
written_at timestamptz [not null]
|
||||
written_by uuid [ref: > app_user.id, note: 'инициатор записи']
|
||||
written_by uuid [ref: > app_user.id, note: 'инициатор записи = пользователь, выполнивший переход «Верифицирован» (v5.26, §15)']
|
||||
archived_at timestamptz
|
||||
data_lake_ref varchar [note: 'ссылка на архив в data lake']
|
||||
Indexes {
|
||||
@ -1622,11 +1629,11 @@ Table ex_snapshot_member {
|
||||
Table ex_snapshot_item {
|
||||
id uuid [pk]
|
||||
ex_snapshot_id uuid [not null, ref: > ex_snapshot.id]
|
||||
item_type varchar [not null, note: 'primary_document | summary_osv | vgo_pairs | chess | kfo_balance | kfo_income | goodwill | corrections | other_report']
|
||||
item_type varchar [not null, note: 'primary_document | summary_osv | vgo_pairs | chess | kfo_balance | kfo_income | goodwill | corrections | other_report | version_card (v5.26)']
|
||||
legal_entity_id uuid [ref: > legal_entity.id, note: 'для первички — организация']
|
||||
document_id uuid [ref: > document.id, note: 'v5.13: только для primary_document — исходники хранятся весь срок, FK допустим; «Скачать» — через document.stored_file → s3_object']
|
||||
payload jsonb [note: 'v5.12 DECISION-5: замороженные данные для всех остальных типов; самодостаточно, без ссылок на таблицы консолидации']
|
||||
payload_schema_version varchar
|
||||
payload jsonb [note: 'v5.12 DECISION-5: замороженные данные для всех остальных типов; самодостаточно, без ссылок на таблицы консолидации; v5.26: конверт contracts/ex-payload-v1.schema.json, у primary_document — null']
|
||||
payload_schema_version varchar [note: 'v5.26: "1.0" — версия схемы ex-payload; у primary_document — null']
|
||||
Indexes { (ex_snapshot_id, item_type) }
|
||||
Note: 'Состав среза: primary_document → document; всё остальное → payload. «Просмотр» в ЛК ЕХ рендерит payload по схеме; «Выгрузка» собирает xlsx из payload на лету'
|
||||
}
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 644 KiB After Width: | Height: | Size: 651 KiB |
File diff suppressed because it is too large
Load Diff
|
Before Width: | Height: | Size: 86 KiB After Width: | Height: | Size: 86 KiB |
@ -11,6 +11,9 @@
|
||||
| 3 | `audit_action` + `KFO_EDIT` | Сохранение ручных полей КФО (комментарии строк, обоснование порога, причины отклонений от Методики) — раздел `KFO`; подходящего действия не было |
|
||||
| 4 | `elimination_rule.elimination_mode` — значение `min_or_full` | Правило ББ 2 по DOC-274230: `min` при обеих сторонах, сторона 1 при пустой стороне 2, без порога 5 %; строки ДО — каскадом 1310 → 1350 → 1370. Поле varchar — схема не меняется; seed v1 правится на месте в AURORA-1546 |
|
||||
| 5 | Пояснения `kfo_result_line`, `kfo_result` | Приведены к §8 «КФО»: всё в тыс. руб.; корректировка агрегата заменяет его автосумму (`eliminated_auto` и `eliminated_manual` по одному агрегату не складываются); `GOODWILL` — служебная строка раздела I, входит в 1100/1600 |
|
||||
| 6 | `ex_snapshot_item.item_type` + `version_card`; payload — по `contracts/ex-payload-v1.schema.json` | Карточка версии (участники маршрута с ФИО, решение УОБ, обоснования, состав) — элементом среза, а не колонками `ex_snapshot`: версия КФО удаляется раньше среза, а схему таблицы не трогаем. Схема payload — конверт и данные по каждому типу (`context/05-domain-rules.md` §15; docs #208) |
|
||||
| 7 | `audit_action` + `EX_SNAPSHOT_WRITE` | Запись среза при «Верифицирован» — раздел `KFO` (раздела `EX` нет) |
|
||||
| 8 | Пояснение `ex_snapshot.status` | Архив прежнего среза ручной (решение 27.10.2026): `ACTUAL` — «не архивирован», а не «последняя верифицированная» |
|
||||
|
||||
Остальные таблицы и enum — как в v5.25.
|
||||
|
||||
|
||||
2
openspec/changes/ex-payload-schema/.openspec.yaml
Normal file
2
openspec/changes/ex-payload-schema/.openspec.yaml
Normal file
@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-11-06
|
||||
129
openspec/changes/ex-payload-schema/design.md
Normal file
129
openspec/changes/ex-payload-schema/design.md
Normal file
@ -0,0 +1,129 @@
|
||||
# Design: схема среза ЕХ ex-payload-v1
|
||||
|
||||
Change `ex-payload-schema`, issue RSHB/arm-ka-docs#208, ветка `feat/208-ex-payload` (от `feat/206-kfo-contract`).
|
||||
Читатель — Потапова (AURORA-1560, запись среза), следом — исполнитель AURORA-2001 (чтение среза) и тестировщик 1562.
|
||||
Читают как постановку, без созвона.
|
||||
|
||||
## 1. Зачем
|
||||
|
||||
Срез ЕХ — самодостаточная заморозка верифицированной версии: его читают и выгружают, когда рабочих таблиц консолидации
|
||||
уже нет. Всё производное (сводная ОСВ, агрегаты ВГО, шахматка, КФО, гудвилл, корректировки, сравнение с клиентом,
|
||||
карточка версии) лежит в `ex_snapshot_item.payload` (jsonb). Без схемы payload каждый, кто пишет и читает срез, решает
|
||||
формат сам — и просмотр среза разойдётся с экраном рабочей версии.
|
||||
|
||||
Успех: Потапова пишет срез, проверяя каждый элемент схемой; исполнитель 2001 рендерит и выгружает срез по той же схеме,
|
||||
не заглядывая в таблицы консолидации.
|
||||
|
||||
## 2. Цели, не цели, анти-цели
|
||||
|
||||
**Цели** — схема конверта и данных по каждому `item_type`, примеры, проверка в `make check`; правила среза в §15; правки
|
||||
DBML, без которых схему не записать (`version_card`, действие журнала).
|
||||
|
||||
**Не цели** — `proposal.md`.
|
||||
|
||||
**Анти-цели**
|
||||
- Payload, который без таблиц консолидации не прочитать: ссылки допустимы только на то, что живёт не меньше среза —
|
||||
`document`, `legal_entity`, `related_group`, `company_group`, `app_user` (DOC-274687 правило 4); остальные id (строк
|
||||
ОСВ, корректировок, правил) — справочно, для рендера не нужны.
|
||||
- Схема, которая заставляет фронт ЕХ пересчитывать: в payload лежат готовые значения так, как их видел аналитик.
|
||||
- Повторный перевод единиц: суммы в payload — как в источнике (DOC-274687 правило 5), единица — рядом.
|
||||
|
||||
## 3. Ограничения
|
||||
|
||||
- JSON Schema 2020-12, как остальные контракты; проверка примеров — `scripts/validate_contracts.py` (`make check`).
|
||||
- Деньги — строки с двумя знаками (`^-?\d{1,16}\.\d{2}$`), как в API; `null` — «не рассчитано».
|
||||
- Схема версионируется: `schema_version` в конверте и `ex_snapshot_item.payload_schema_version` = `"1.0"`;
|
||||
несовместимое изменение — `ex-payload-v2.schema.json` рядом, старые срезы не переписываются.
|
||||
- Модель — DBML v5.26 из #207: эта ветка дописывает в неё пункты changelog (версия ещё не слита, номер не тратим).
|
||||
|
||||
## 4. Что решено
|
||||
|
||||
### 4.1 Конверт элемента
|
||||
|
||||
Каждый `payload` (у всех `item_type`, кроме `primary_document`, у которого `payload = null` и есть `document_id`):
|
||||
|
||||
```
|
||||
{schema_version: "1.0", item_type, units: "rub" | "thousand_rub" | "none", generated_at (date-time),
|
||||
source_version_no (int), data: {...}}
|
||||
```
|
||||
|
||||
`item_type` в конверте совпадает с колонкой; `units` — единица всех денег в `data`; `generated_at` — момент расчёта
|
||||
перед записью. Конверт — `additionalProperties: false`.
|
||||
|
||||
### 4.2 Данные по типам
|
||||
|
||||
Общий принцип (**главнее любой таблицы ниже**): `data` — **ответ API экрана рабочей версии в момент верификации, теми же
|
||||
DTO, именами и вложенностью**, без полей редактирования (`editable`, `read_only_reason`, `lock`) и плюс то, что нужно
|
||||
**выгрузке xlsx рабочей версии** (срез выгружается на лету из payload и должен совпадать с выгрузкой рабочей версии —
|
||||
proposal, AURORA-1562). Обязательные поля задаёт схема; лишние поля допускаются (`additionalProperties: true`), рендер
|
||||
опирается только на обязательные. Ссылки на компании — как в DTO (`Ref {id, name}` или `legal_entity_id` +
|
||||
`legal_entity_name`/`_inn`): имена в DTO уже есть, отдельный `CompanyRef` не вводится; список компаний периметра с ИНН —
|
||||
в `version_card.members`. Id строк ОСВ, корректировок и правил остаются в данных справочно.
|
||||
|
||||
| `item_type` | `units` | `data` (DTO) |
|
||||
|---|---|---|
|
||||
| `summary_osv` | `rub` | `{rows: ConsolidatedRowOut[], summary: OsvSummaryOut}` (`dev/api-contract-slice-4.md` §3) — строки свода, включая `match_kind` (колонка «Способ сопоставления» выгрузки), и `summary.gaps[]` (лист «Пробелы») |
|
||||
| `vgo_pairs` | `rub` | `{balance: CorrectionTableOut, income: CorrectionTableOut \| null}` (`dev/api-contract-corrections.*`) — агрегаты таблицы корректировок со сторонами и строками ОСВ (`entries`, нужны выгрузке), нераспределённые строки, итоги; без `editable`/`read_only_reason`; `income` — `null`, пока агрегатов ОФР нет (AURORA-1999). Это вход КФО, не прежний экран «Пары ВГО» |
|
||||
| `chess` | `rub` | `{matrix_all: IntercompanyMatrixOut, presets: [{rule_no, matrix: IntercompanyMatrixOut}], result_table}` (`dev/frontend-api-guide.md` §5.7.1) — «все счета», пресет каждого правила ББ версии правил, результирующая таблица пяти статей |
|
||||
| `kfo_balance` / `kfo_income` | `thousand_rub` | `KfoResultOut` (`dev/api-contract-kfo.md`) без `editable`/`read_only_reason`, с `balance` **или** `income` своего отчёта (второй отчёт — в своём элементе); `requisites` (в т. ч. `corrections_balance`/`_income` для формы 2.03.01.01с), `summary`, `goodwill`, обоснование, `warnings` — в обоих элементах одинаковые |
|
||||
| `goodwill` | `rub` | только при `goodwill_enabled`: `GoodwillTableOut` (`dev/api-contract-goodwill.*`) без `editable`/`read_only_reason` — строки, группы, итоги |
|
||||
| `corrections` | `rub` | применённые корректировки (`APPLIED`; `REVOKED` нет, `DRAFT` не пишется): `{items: [{statement_type, rule_no, company_1: Ref, company_2: Ref, correction: CorrectionOut}]}` — `CorrectionOut` как в контракте корректировок (`updated_by`/`updated_at` — кто и когда сохранил) |
|
||||
| `other_report` | `thousand_rub` | только при `scenario = CONFIRM`: `{report_kind: "client_kfo_comparison", lines[] {statement_type, line_code, line_name, client_amount, client_vgo_amount, bank_amount, bank_vgo_amount, deviation_amount, deviation_percent, deviation_reason, comment}}` по `client_kfo_line` (контракта API ещё нет — AURORA-1549; имена — как в DBML) |
|
||||
| `version_card` | `none` | один на срез, **новый тип**: `company_group {id, name}`, `related_group {id, name}`, `reporting_date`, `year`, `quarter`, `version_no`, `scenario`, `approach`, `goodwill_enabled`, `initiator` (UserRef); `quarter` — `"Q1"…"Q4"`, `initiator_office {id, name}`, `members[]` `{legal_entity_id, name, inn, is_head_entity}`, `history[]` как `VersionCard.history` (`{from_status, to_status, changed_by (UserRef), changed_at, comment}`), `verified_by` (UserRef), `verified_at`, `uob_decision_no`, `uob_decision_date`, `methodology_deviations` |
|
||||
|
||||
`UserRef {user_id, full_name}` — ФИО на дату верификации (пользователь может быть отключён, но не удаляется).
|
||||
|
||||
### 4.3 Правила среза — §15 `05-domain-rules.md`
|
||||
|
||||
Выжимка DOC-274687 под нашу модель: срез пишется один раз при «Верифицирован» (после фиксации ГСК, AURORA-2002), один на
|
||||
версию (`source_version_id` уникален); состав — таблица 4.2 + `primary_document` по каждому документу
|
||||
`consolidation_document` + `ex_snapshot_member` по каждому ЮЛ версии; строки форм без кода, непривязанные документы и
|
||||
черновики корректировок не входят; `goodwill` — только при `goodwill_enabled`, `other_report` — только при `CONFIRM`;
|
||||
номер среза — следующий по (ГСК, год, квартал) (уникальный индекс DBML; новая ГСК после смены периметра начинает
|
||||
с 1); прежний срез запись в архив не переводит (архив ручной, решение 27.10.2026): переход в «Верифицирован» уже
|
||||
проверил, что у той же **ГК** за ту же дату актуального среза нет (AURORA-2002), поэтому после записи актуальный срез по
|
||||
ГК и дате ровно один — новый; архивным срезам той же ГК и даты без ссылки ставится `superseded_by_snapshot_id` нового
|
||||
(ГК, а не ГСК — чтобы ссылку получил и срез прежней ГСК); разархивирование ссылку снимает (AURORA-2001). Если заказчик
|
||||
выберет ключ «ГСК × дата» (вопрос AURORA-1397) — меняются проверка 2002 и это правило связи, схема payload — нет; производные считаются в
|
||||
последний раз и замораживаются, `kfo_result.is_frozen = true`; сбой записи не отменяет верификацию (повтор — вопрос
|
||||
заказчику, OQ); аудит записи — `EX_SNAPSHOT_WRITE`. Вопросы заказчику (кто архивирует, ключ ГК или ГСК, повтор при
|
||||
сбое, PDF) — OQ со ссылкой на AURORA-1397.
|
||||
|
||||
### 4.4 DBML v5.26 (дописать в changelog #207)
|
||||
|
||||
- `ex_snapshot_item.item_type` — + `version_card`; `payload` — «конверт `contracts/ex-payload-v1.schema.json`, у
|
||||
`primary_document` — null»; `payload_schema_version` — `"1.0"`.
|
||||
- `audit_action` + `EX_SNAPSHOT_WRITE` (раздел `KFO`, `object_type = ex_snapshot`, `after` — номер среза, ГСК, дата,
|
||||
число элементов).
|
||||
- `ex_snapshot.status` — «ACTUAL — не архивирован; ARCHIVE — архивирован пользователем в ЕХ» (сейчас — «предыдущие →
|
||||
ARCHIVE», противоречит ручному архиву). Миграция колонок не нужна; значение enum — в миграции AURORA-1560.
|
||||
|
||||
### 4.5 Вложения в Jira
|
||||
|
||||
После открытия PR: AURORA-1560 — схема, §15 (`05-domain-rules.md`), DBML; комментарий со ссылкой на PR. AURORA-2001 —
|
||||
ссылка в комментарии (чтение — по той же схеме).
|
||||
|
||||
## 5. Что остаётся исполнителю плана
|
||||
|
||||
Точная разметка схемы (`$defs`, имена), тексты §15, числа примеров (из `scripts/kfo_contract/kfo_example.py`), какие
|
||||
элементы показать примерами (минимум: `kfo_balance`, `version_card`, `corrections`, `summary_osv`).
|
||||
|
||||
## 6. Decisions made without discussion
|
||||
|
||||
| # | Дата | Решение | Обоснование | Цена ошибки |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 06.11.2026 | `data` = замороженный ответ экрана теми же именами полей, обязательные поля — схемой, лишние допускаются | Рендер ЕХ переиспользует экраны рабочей версии; исполнителю не надо вырезать поля | Лишние поля раздувают срез; рендер, опирающийся на необязательное поле, сломается на старом срезе |
|
||||
| 2 | 06.11.2026 | `vgo_pairs` — агрегаты таблицы корректировок, не прежний экран «Пары ВГО» | Это вход КФО и то, что аналитик видит в UI | Если ЕХ нужна детализация упаковки по строкам — новый тип элемента |
|
||||
| 3 | 06.11.2026 | `chess` — «все счета» + пресет каждого правила ББ + результирующая таблица; ручной выбор счетов не замораживается | Ручной режим — исследование, его состояние не хранится | В ЕХ нельзя посмотреть шахматку по произвольным счетам |
|
||||
| 4 | 06.11.2026 | Карточка версии — новый `item_type version_card` с ФИО, а не колонки `ex_snapshot` | DOC-274687 оставляет выбор команде; элемент не трогает схему таблицы | Фильтры ЕХ по участникам маршрута пойдут по jsonb |
|
||||
| 5 | 06.11.2026 | В срез — только `APPLIED` корректировки (DOC-274687 предполагал и `REVOKED`) | `REVOKED` в системе нет: корректировку меняют пересохранением (ответ Лысенко п. 9) | История правок корректировки — только в аудите |
|
||||
| 6 | 06.11.2026 | DBML-правки — в ту же неслитую v5.26, что в #207 | Одна версия на пакет КФО + ЕХ; номер не тратим | Если #207 сольют раньше и v5.26 «закроется» — перенести правки в v5.27 |
|
||||
| 7 | 06.11.2026 | `EX_SNAPSHOT_WRITE` — раздел журнала `KFO` | Раздела `EX` в `audit_section` нет; запись среза — часть верификации КФО | Фильтр журнала по ЕХ не найдёт событие |
|
||||
| 9 | 06.11.2026 | Payload покрывает и выгрузку xlsx рабочей версии (пробелы, способ сопоставления, строки ОСВ агрегатов, сводка корректировок), не только экран | Срез выгружается на лету из payload, выгрузка должна совпасть с рабочей (1562); ответ на вопрос проверки исполнителем 1 | Срез заметно больше (строки ОСВ агрегатов) |
|
||||
| 10 | 06.11.2026 | `data` — DTO API без изменений имён и вложенности; таблица §4.2 подчинена принципу | Рендер и выгрузка ЕХ переиспользуют виджеты и выгрузки рабочей версии без маппинга; вопрос проверки 2 | Изменение DTO API после 1.0 требует новой версии схемы или совместимого расширения |
|
||||
| 11 | 06.11.2026 | Нумерация среза — по ГСК; «один актуальный» и `superseded_by` — по ГК и дате | Как в DBML и DOC-274686 (решения Р-35/Р-36 по ГК); вопрос проверки 3 | При ключе «ГСК» у заказчика — правка правила связи и проверки 2002 |
|
||||
| 12 | 06.11.2026 | Схема обязывает то, что читают выгрузки xlsx рабочей версии: итоги шахматки `total_debit`/`total_credit`, правило пресета и `rules` таблицы корректировок, `pairs`, `balance` корректировки, `entry_ids` строк | Ревью C/I: без них выгрузка среза не совпадёт с рабочей (решение #9) | Больше обязательных полей — писателю нельзя «урезать» DTO |
|
||||
| 13 | 06.11.2026 | Проценты к меньшей стороне (шахматка, агрегаты, корректировки) — без ограничения разрядов; квартал — `"Q1"…"Q4"`; `updated_by` — `Ref`, как в DTO | Ревью C-1, C-2, I-2: настоящие ответы API не проходили схему | — |
|
||||
| 14 | 06.11.2026 | КФО в срезе — только замороженная и `VERIFIED`; карточка — непустые состав и история | Ревью M-1: пустой или незамороженный payload не должен проходить | Срез версии без истории маршрута (миграция старых данных) схему не пройдёт |
|
||||
| 15 | 06.11.2026 | Ошибка пресета правила (422 `INVALID_RULE`/`ACCOUNTS_REQUIRED`) — пресет пропускается; другая ошибка сборки — сбой записи среза | Ревью M-5 | Срез без пресета правила, которое на экране тоже не строится |
|
||||
| 8 | 06.11.2026 | Без отдельного согласования дизайна с владельцем: схема описана в чате 06.11 («все так»), владелец сказал «делай схему» | Срок 12.11, владелец просил не останавливаться | Расхождение с ожиданием владельца видно только в PR |
|
||||
@ -0,0 +1,69 @@
|
||||
# Схема среза ЕХ ex-payload-v1 — план
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** схема `contracts/ex-payload-v1.schema.json` с примерами и проверкой, §15 правил и правки DBML v5.26 — к старту
|
||||
AURORA-1560 (12.11.2026).
|
||||
|
||||
**Architecture:** JSON Schema 2020-12: конверт + `data` по `item_type` через `$defs`; DTO-определения повторяют
|
||||
обязательные поля контрактов API (`additionalProperties: true` в `data`). Примеры — на числах
|
||||
`scripts/kfo_contract/kfo_example.py` (КФО) и ручных данных той же группы «Альфа/Бета/Гамма».
|
||||
|
||||
**Tech Stack:** JSON Schema 2020-12, `scripts/validate_contracts.py` (jsonschema), Markdown, DBML, OpenSpec.
|
||||
|
||||
**Spec:** `openspec/changes/ex-payload-schema/design.md`, `proposal.md`.
|
||||
|
||||
Пути — от корня worktree `/Users/andrey.epifancev/projects/awp-ca/.wt/ex-payload/docs`.
|
||||
|
||||
## Global Constraints
|
||||
|
||||
- `schema_version = "1.0"`; `$id` — `ex-payload-v1.schema.json`; `units ∈ {rub, thousand_rub, none}`.
|
||||
- `item_type ∈ {summary_osv, vgo_pairs, chess, kfo_balance, kfo_income, goodwill, corrections, other_report, version_card}`;
|
||||
у `primary_document` payload нет.
|
||||
- Деньги — `^-?\d{1,16}\.\d{2}$`; конверт — `additionalProperties: false`, `data` — `true`.
|
||||
- `audit_action.EX_SNAPSHOT_WRITE` (раздел `KFO`); `ex_snapshot_item.item_type` + `version_card`; пояснение `ex_snapshot.status`.
|
||||
- Новые OQ — с OQ-195.
|
||||
|
||||
## Review Focus
|
||||
|
||||
1. Конверт с `item_type`, не совпадающим с `data` (например, `kfo_balance` с данными гудвилла) — схема отклоняет.
|
||||
2. `kfo_balance` без `requisites.corrections_balance` — отклоняется (нужно форме 2.03.01.01с).
|
||||
3. `goodwill` при выключенном гудвилле не пишется — правило в §15, не в схеме; пример — только включённый.
|
||||
4. Деньги числом (`1000.5`) вместо строки — отклоняется.
|
||||
5. Пример `kfo_balance` совпадает с примером `goodwill_exceeded` контракта КФО по строкам и сводке.
|
||||
|
||||
Пункты 1, 2, 4 — отрицательные примеры в тесте Task 1; 5 — генерацией примера из того же скрипта.
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Схема, примеры, проверка
|
||||
|
||||
**Files:** Create `contracts/ex-payload-v1.schema.json`, `contracts/examples/ex-payload-*.json` (минимум `kfo_balance`,
|
||||
`version_card`, `corrections`, `summary_osv`, `vgo_pairs`, `chess`, `goodwill`), `scripts/kfo_contract/gen_ex_examples.py`;
|
||||
Modify `scripts/validate_contracts.py` (+ схема `ex-payload`, шаблон `ex-payload-`), `scripts/kfo_contract/` (тест отрицательных случаев).
|
||||
|
||||
- [ ] Step 1: тест `scripts/kfo_contract/test_ex_payload.py` — положительные примеры проходят; отрицательные (Review
|
||||
Focus 1, 2, 4) — нет. Run: `.venv/bin/python -I scripts/kfo_contract/test_ex_payload.py` — Expected: FAIL (схемы нет).
|
||||
- [ ] Step 2: схема + генератор примеров (КФО — из `kfo_example.py`/`gen_kfo_openapi.result()`) + регистрация в
|
||||
`validate_contracts.py`.
|
||||
- [ ] Step 3: Run тест и `make validate-contracts` — Expected: PASS, все `ex-payload-*` OK.
|
||||
- [ ] Step 4: commit.
|
||||
|
||||
### Task 2: §15, DBML, OQ
|
||||
|
||||
**Files:** Modify `context/05-domain-rules.md` (+ §15 после §13), `model/data-model-v5.26.dbml`, `model/entities-v5.26.md`,
|
||||
`context/07-open-questions.md` (OQ-195…), `context/09-delivery-plan.md:287` (ссылка на §15).
|
||||
|
||||
- [ ] Step 1: Run `grep -c "EX_SNAPSHOT_WRITE\|version_card" model/data-model-v5.26.dbml` — Expected: 0 (RED).
|
||||
- [ ] Step 2: §15 по design §4.3; DBML и entities по §4.4 (пункты changelog 6–8 v5.26); OQ: кто архивирует (ссылка
|
||||
на 1397), ключ ГК/ГСК, повтор при сбое, PDF.
|
||||
- [ ] Step 3: Run тот же grep — Expected: ≥ 2; `make check` — зелёный.
|
||||
- [ ] Step 4: commit.
|
||||
|
||||
### Task 3: Spec delta, ревью, PR, Jira
|
||||
|
||||
- [ ] Step 1: `specs/arm-core/spec.md` (ADDED: «Срез ЕХ при верификации», «Payload элемента среза»), `tasks.md`;
|
||||
`make validate-openspec CHANGE=ex-payload-schema` — valid.
|
||||
- [ ] Step 2: финальное ревью (opus); Critical/Important — исправить.
|
||||
- [ ] Step 3: push, PR (base `main`, «сливать после #207»), вложения в AURORA-1560 (схема, `05-domain-rules.md`, DBML),
|
||||
комментарий в 1560 и 2001.
|
||||
36
openspec/changes/ex-payload-schema/proposal.md
Normal file
36
openspec/changes/ex-payload-schema/proposal.md
Normal file
@ -0,0 +1,36 @@
|
||||
# Proposal: схема среза ЕХ ex-payload-v1 (docs #208)
|
||||
|
||||
## Why
|
||||
|
||||
AURORA-1560 (запись среза ЕХ при «Верифицирован», Потапова, разработка 12–18.11.2026, тест заказчика с 19.11) в
|
||||
«Нужно до старта» называет схему замороженных данных `docs/contracts/ex-payload-v1.schema.json` — её нет, поля по типам
|
||||
элементов среза не описаны. Без неё нельзя ни записать срез, ни проверить, что просмотр и выгрузка из среза совпадают с
|
||||
рабочей версией (AURORA-2001 — чтение среза, AURORA-1562 — тест).
|
||||
|
||||
Источники: DOC-274687 «Срез при верификации», DOC-274686 «Версии и прошлые периоды», DOC-274685 «Архив» (черновики
|
||||
команды, Старовойтов); ЕХ ТЗ 5.2, 6.2–6.4; решения команды 28–29.09.2026 (самодостаточный срез, первичка ссылкой, файлы
|
||||
выгрузок не хранятся, PDF не делать); DBML `ex_snapshot*`; контракт КФО (`dev/api-contract-kfo.md`, PR #207).
|
||||
|
||||
## What changes
|
||||
|
||||
- `contracts/ex-payload-v1.schema.json` — JSON Schema 2020-12: конверт элемента среза и данные по `item_type`
|
||||
(`summary_osv`, `vgo_pairs`, `chess`, `kfo_balance`, `kfo_income`, `goodwill`, `corrections`, `other_report`,
|
||||
`version_card`); примеры `contracts/examples/ex-payload-*.json` на числах сквозного примера §8 «КФО»; проверка в
|
||||
`scripts/validate_contracts.py`.
|
||||
- `context/05-domain-rules.md` — новый §15 «Срез ЕХ при верификации»: когда пишется, что входит, самодостаточность,
|
||||
суммы и единицы, сбой записи, корректировки только `APPLIED`, карточка версии.
|
||||
- DBML v5.26 (та же неслитая версия, что в #207) — `ex_snapshot_item.item_type` + `version_card`, версия схемы
|
||||
`"1.0"`; `audit_action` + `EX_SNAPSHOT_WRITE`; пояснение `ex_snapshot.status` под ручной архив.
|
||||
- `context/07-open-questions.md` — открытые вопросы среза.
|
||||
|
||||
Срез плана: Срез 7 (`09-delivery-plan.md`), Гант 3.3. Дочерний change — `arm-core` (AURORA-1560), заводит исполнитель.
|
||||
Строится на ветке #207 (форма КФО), сливается после него.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Код записи среза — AURORA-1560; чтение, архив и разархивирование — AURORA-2001; переход «Верифицирован» — AURORA-2002.
|
||||
- Копия файлов первички в отдельный префикс S3 — AURORA-1935.
|
||||
- PDF «Консолидированная отчётность» — не делаем до решения заказчика.
|
||||
- Перенос в data lake и сроки хранения — страница «Архив», не схема.
|
||||
- Вопросы заказчику по ЕХ (ручной или автоматический архив, ключ ГК или ГСК) — AURORA-1397 у аналитика; схема от них не
|
||||
зависит.
|
||||
42
openspec/changes/ex-payload-schema/specs/arm-core/spec.md
Normal file
42
openspec/changes/ex-payload-schema/specs/arm-core/spec.md
Normal file
@ -0,0 +1,42 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Срез ЕХ при верификации
|
||||
При переходе версии консолидации в «Верифицирован» ядро SHALL один раз записать срез по `context/05-domain-rules.md`
|
||||
§15: `ex_snapshot` со `status = ACTUAL` и номером, следующим по (ГСК, год, квартал); `ex_snapshot_member` по каждому ЮЛ
|
||||
версии; `ex_snapshot_item` — `primary_document` по каждому привязанному документу и по одному элементу `summary_osv`,
|
||||
`vgo_pairs`, `chess`, `kfo_balance`, `kfo_income`, `corrections`, `version_card`, плюс `goodwill` при `goodwill_enabled`
|
||||
и `other_report` при `scenario = CONFIRM`. Запись SHALL не переводить прежний срез в архив и SHALL ставить
|
||||
`superseded_by_snapshot_id` архивным срезам той же ГК и даты без ссылки. Повторная попытка по той же версии второй срез
|
||||
не пишет. Запись SHALL писать `audit_event(EX_SNAPSHOT_WRITE)`.
|
||||
|
||||
#### Scenario: Первая верификация
|
||||
- **WHEN** версия № 1 ГСК за 01.10.2025 переходит в «Верифицирован», срезов этой ГСК и даты нет
|
||||
- **THEN** записан срез № 1 `ACTUAL` с элементами по §15 п. 3, `kfo_result.is_frozen = true`, событие `EX_SNAPSHOT_WRITE`
|
||||
|
||||
#### Scenario: Новая версия после ручного архива
|
||||
- **WHEN** срез № 1 той же ГК и даты заархивирован вручную, и версия № 3 переходит в «Верифицирован»
|
||||
- **THEN** записан срез № 2 `ACTUAL`, срез № 1 остаётся `ARCHIVE` и получает `superseded_by_snapshot_id` среза № 2
|
||||
|
||||
#### Scenario: Гудвилл выключен
|
||||
- **WHEN** у версии `goodwill_enabled = false`
|
||||
- **THEN** элемента `goodwill` в срезе нет
|
||||
|
||||
### Requirement: Payload элемента среза
|
||||
`ex_snapshot_item.payload` всех типов, кроме `primary_document`, SHALL проходить `contracts/ex-payload-v1.schema.json`:
|
||||
конверт `{schema_version: "1.0", item_type, units, generated_at, source_version_no, data}`, где `item_type` совпадает с
|
||||
колонкой, `units` — единица типа (`rub` / `thousand_rub` / `none`), `data` — ответ API экрана рабочей версии на момент
|
||||
верификации без полей редактирования; `payload_schema_version = "1.0"`. Чтение и выгрузка среза SHALL опираться только на
|
||||
payload и ссылки на `document`, `legal_entity`, `related_group`, `company_group`, `app_user`.
|
||||
|
||||
#### Scenario: Элемент КФО
|
||||
- **WHEN** пишется элемент `kfo_balance`
|
||||
- **THEN** его payload — `units = "thousand_rub"`, `data` с `balance.lines`, `summary`, `requisites.corrections_balance`
|
||||
и без `income`; схема его принимает
|
||||
|
||||
#### Scenario: Несоответствие типа
|
||||
- **WHEN** у элемента `item_type = kfo_balance`, а `data` — таблица гудвилла
|
||||
- **THEN** схема payload его отклоняет
|
||||
|
||||
#### Scenario: Чтение без таблиц консолидации
|
||||
- **WHEN** рабочие таблицы версии удалены по сроку
|
||||
- **THEN** срез открывается и выгружается: строки КФО, шахматка, агрегаты ВГО — из payload, первичка — по `document_id`
|
||||
10
openspec/changes/ex-payload-schema/tasks.md
Normal file
10
openspec/changes/ex-payload-schema/tasks.md
Normal file
@ -0,0 +1,10 @@
|
||||
# Tasks: ex-payload-schema (docs #208)
|
||||
|
||||
План — `plans/2026-11-06-ex-payload.md`.
|
||||
|
||||
- [x] 1. `contracts/ex-payload-v1.schema.json`, примеры `contracts/examples/ex-payload-*.json`, генератор и тест `scripts/kfo_contract/`, `validate_contracts.py`
|
||||
- [x] 2. `05-domain-rules.md` §15, DBML v5.26 (пункты 6–8), `entities-v5.26.md`, OQ-195…198, `09-delivery-plan.md`
|
||||
- [x] 3. Spec delta, финальное ревью, PR (после #207), вложения в AURORA-1560
|
||||
- [ ] 4. Документы контекста после merge кода 1560/2001: `09-delivery-plan` (Срез 7)
|
||||
|
||||
Дочерний change — `arm-core` (AURORA-1560); заводит исполнитель.
|
||||
408
scripts/kfo_contract/gen_ex_payload.py
Normal file
408
scripts/kfo_contract/gen_ex_payload.py
Normal file
@ -0,0 +1,408 @@
|
||||
"""Генерирует 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 §15). 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()
|
||||
108
scripts/kfo_contract/test_ex_payload.py
Normal file
108
scripts/kfo_contract/test_ex_payload.py
Normal file
@ -0,0 +1,108 @@
|
||||
"""Проверка схемы среза ЕХ 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]
|
||||
sys.path.insert(0, str(ROOT / "scripts"))
|
||||
from validate_contracts import make_format_checker # noqa: E402 — та же проверка форматов, что в make check
|
||||
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, format_checker=make_format_checker())
|
||||
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 в тысячах")
|
||||
# ревью #208: легитимные ответы API проходят
|
||||
ok = copy.deepcopy(by_type["summary_osv"])
|
||||
ok["data"]["summary"]["gaps"][0]["missing_quarters"] = ["Q1", "Q2"]
|
||||
assert not list(v.iter_errors(ok)), "C-1: пробелы свода как в API (Q1…Q4)"
|
||||
ok = copy.deepcopy(by_type["chess"])
|
||||
ok["data"]["matrix_all"]["cells"][0]["variance_percent"] = "10199959.95"
|
||||
assert not list(v.iter_errors(ok)), "C-2: большой процент шахматки"
|
||||
ok = copy.deepcopy(by_type["vgo_pairs"])
|
||||
ok["data"]["balance"]["aggregates"][0]["deviation_percent"] = "10199959.95"
|
||||
assert not list(v.iter_errors(ok)), "C-2: большой процент агрегата"
|
||||
# ревью #208: то, что нужно выгрузкам, обязательно
|
||||
for path, why in [(("chess", "matrix_all", "total_debit"), "I-1 итоги шахматки"),
|
||||
(("vgo_pairs", "balance", "rules"), "I-1 правила таблицы корректировок"),
|
||||
(("chess", "presets", 0, "rule"), "I-1 правило пресета")]:
|
||||
bad = copy.deepcopy(by_type[path[0]])
|
||||
node = bad["data"]
|
||||
for k in path[1:-1]:
|
||||
node = node[k]
|
||||
del node[path[-1]]
|
||||
rejected(bad, why)
|
||||
bad = copy.deepcopy(by_type["corrections"])
|
||||
del bad["data"]["items"][0]["correction"]["balance"]
|
||||
rejected(bad, "I-1 balance корректировки")
|
||||
bad = copy.deepcopy(by_type["vgo_pairs"])
|
||||
bad["data"]["balance"]["aggregates"][0]["side_1"]["entries"][0]["entry_ids"] = []
|
||||
rejected(bad, "I-1 строка агрегата без entry_ids")
|
||||
bad = copy.deepcopy(by_type["corrections"])
|
||||
bad["data"]["items"][0]["correction"]["updated_by"] = {"user_id": "x", "full_name": "y"}
|
||||
rejected(bad, "I-2 updated_by не Ref")
|
||||
# ревью #208: пустой и незамороженный payload
|
||||
bad = copy.deepcopy(by_type["version_card"])
|
||||
bad["data"]["members"] = []
|
||||
rejected(bad, "M-1 карточка без состава")
|
||||
bad = copy.deepcopy(by_type["kfo_balance"])
|
||||
bad["data"]["is_frozen"] = False
|
||||
rejected(bad, "M-1 КФО не заморожена")
|
||||
bad = copy.deepcopy(by_type["version_card"])
|
||||
bad["data"]["quarter"] = 3
|
||||
rejected(bad, "M-3 квартал числом")
|
||||
# форматы проверяются
|
||||
bad = copy.deepcopy(by_type["version_card"])
|
||||
bad["data"]["verified_at"] = "вчера"
|
||||
rejected(bad, "M-6 date-time")
|
||||
print(f"OK: примеров {len(EXAMPLES)}, отрицательных случаев 17, положительных граничных 3")
|
||||
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