🗃 Модель данных Capital

Канонические ORM-модели живут в capital_shared/models.py (единый источник схемы, общий для backoffice и cabinet). 38 таблиц (main 5127b2b, 16.09.2026), схема ведётся Alembic. Ниже - смысловая карта: назначение таблиц, ключевые поля и инварианты. Точные типы колонок - в коде; здесь - что важно понимать про данные. В ветке feature/xbo-integration дополнительно: xbo_deposit_addresses, xbo_transactions (см. 07/08 в architecture) - в main пока нет.

Идентичность и клиенты

Operator (operators)

Оператор банка (back-office). Логин login (unique, lowercase), password_hash (bcrypt), role (ceo/operator/compliance/viewer), status. Локаут: failed_login_attempts + locked_until. Опциональная 2FA - totp_secret. Последний вход - last_login_at/last_login_ip.

CabinetUser (cabinet_users)

Логин-идентичность клиента (кабинет). email (unique), password_hash, email_verified, 2FA (totp_secret/totp_enabled/totp_last_counter). Пароль хранится ТОЛЬКО здесь (не на Company - иначе копия дрейфует при смене пароля).

Membership (memberships)

Связь «пользователь ↔ компания» (один логин → несколько компаний). user_id, company_id, role (default owner), is_default. Unique (user_id, company_id). Это linchpin изоляции кабинета: активная компания в сессии обязана быть в списке membership'ов пользователя.

Company (companies)

Клиент банка (компания). external_id (unique, capp-…), реквизиты (name, registration_number, country, contact_email, директор, телефон, адрес, registration_date), KYB-поля онбординга (income_volume, nace_codes). Жизненный цикл: status (onboardingapplicationkyc_pendingactive), kyc_status (none/pending/…), kyc_applicant_id (SumSub). Risk: risk_score, risk_level, risk_note, risk_reviewed_*. Провижн в рельсе: rail_client_id. Справочники: branch_id, referral_agent_id.

С миграцией KYC-9-блоков (b1d4f7a9c2e6, 16.09) добавлены анкетные поля многоблочной регистрации (~37 шт. по корпоративной анкете Stefa Pay): тип аккаунта account_kind, черновик onboarding_step, tax id / сайт компании, данные заявителя (DOB, tax country/id), развилки applicant_is_director / applicant_is_sole_owner, AML-профиль (обороты in/out, число транзакций, страны операций, описание деятельности, источники дохода), preferred_tariff. Детали - processes/01 - Онбординг клиента.md и спека регистрации.

CompanyPerson (company_persons)

Директор / акционер / UBO компании (KYB-ядро, блок 5 анкеты «Ownership and control»). Одна Company → N персон: role (director/shareholder/ubo), person_type (private/legal), имя или юр-название + рег. номер, DOB, страна резидентства, tax country/id. Появилась с KYC-9-блоков (16.09).

Счета, кошельки, ledger

Account (accounts)

Счёт компании. iban (unique), currency, status (active/blocked/closed), label, is_primary, opening_fee_due (долг комиссии за открытие). Лимит числа счетов на компанию задаётся в коде кабинета (_MAX_ACCOUNTS_PER_COMPANY).

ClientWallet (client_wallets)

Балансовый кошелёк. client_id, опционально account_id (баланс на конкретный IBAN), currency, kind (default deposit), available_balance. Два частичных unique-индекса: на (account_id, kind) при заданном account_id, и на (client_id, currency, kind) при пустом account_id (валюто-адресованный баланс).

ClientLedgerEntry (client_ledger_entries)

Проводка (append-only ledger). wallet_id, client_id, account_id, currency, direction (credit/debit), amount, entry_type (topup/fee/fx_out/fx_in/ account_opening_fee/…), status (default posted), balance_after. Идемпотентность - idempotency_key (unique): повторный ключ = запись не создаётся. Связь с источником - related_entity_type/id, external_ref, JSON metadata.

ClientOtp (client_otps)

OTP-коды клиента (login и др.). code_hash, purpose, expires_at, used, attempts.

Платежи

Beneficiary (beneficiaries)

Получатель платежа компании. name, iban (валидируется ibanutil), bank_name, bank_bic, country.

PaymentOrder (payment_orders)

Платёжное поручение. company_id, account_id, beneficiary_id, currency, amount, status (default submitted), reference. Связь с рельсом: rail_transaction_id, rail_status.

PaymentApproval (payment_approvals)

4-eyes-одобрение платежа. order_id, operator_id. Unique (order_id, operator_id) - один оператор считается один раз; 2 разных оператора выше dual_approval_threshold → исполнение.

FX и тарифы/выручка

FxRate (fx_rates)

Курс. base_ccy, quote_ccy, rate, updated_at. Unique (base_ccy, quote_ccy).

Tariff (tariffs) / ClientTariff (client_tariffs)

Тариф пер-валюта: fixed_fee, percent_bps, min_fee, max_fee (0 = без cap), себестоимость cost_fixed/cost_bps, account_opening_fee + opening_fee_mode (prepay/first_topup), fx_fee_bps, dual_approval_threshold. Unique по валюте. ClientTariff - индивидуальный override на клиента (unique (client_id, currency)); эффективный тариф = per-client override, иначе глобальный.

RevenueEntry (revenue_entries)

Проводка выручки. client_id, payment_order_id, currency, fee_amount, cost_amount, source (payment_fee/account_opening/fx_fee/ payment_fee_reversal). Partial-unique (payment_order_id, source) при заданном order (защита от двойной проводки на реверсе).

ClientLimit (client_limits)

Лимит платежей клиента. limit_type (per_transaction/daily_outgoing), currency, amount. Unique (client_id, limit_type, currency).

Комплаенс: AML / Risk / Sanctions / SAR

AmlCase (aml_cases)

Кейс риска. client_id, subject_type (customer/payment), subject_id, reason, risk_level, required_approval_level (1/2/3 = low/medium/high), approvals_count, status (default open), raised_by (в т.ч. kyt).

AmlApproval (aml_approvals)

Одобрение AML-кейса. case_id, operator_id. Unique (case_id, operator_id) - один оператор = один голос (4-eyes/6-eyes по уровню).

SarReport (sar_reports)

Suspicious Activity Report поверх AML-кейсов. uuid, client_id, aml_case_id, reporter, assigned_to, priority (low/medium/high), status (draft→submitted→filed→closed), subject_summary, narrative, filed_at.

SanctionSource (sanction_sources) / SanctionEntry (sanction_entries)

Списки санкций/watchlist (OFAC/EU/UN и синтетические демо). Source: name (unique), kind, entry_count. Entry: full_name, normalized_name (индекс для match), entity_type, country, program.

Учёт (Accounting)

ChartOfAccount (chart_of_accounts)

План счетов (иерархия FINREP). code (unique), name, parent_id, account_class (asset/liability/equity), normal_side (debit/credit), ledger_source (маппинг на источник в client-ledger), sort_order, is_system (системные защищены от удаления). Баланс - производный из ledger, см. accounting.py.

Справочники

Отчёты, документы, сообщения, аудит

Report (reports)

Формальный отчёт (EOD/Journal). report_type, title, JSON params/summary, row_count, generated_by. CSV регенерится из params - без блобов в БД.

ClientDocument (client_documents)

Обмен документами. client_id, doc_type, status (requested→uploaded→approved/ rejected), file_name, content_type, size_bytes, content (BLOB LargeBinary; cap 5MB + allowlist типов + sanitize имени; изоляция по client_id; план - вынести в B2).

Message (messages)

Переписка оператор↔клиент. client_id, direction (to_client/to_bank), subject, body, read_by_client/read_by_operator.

Invoice (invoices)

Счёт, который клиент выставляет своему плательщику. client_id, number, payer_name/payer_email, amount, currency, issue_date/due_date, status (draft/sent/paid/cancelled). Unique (client_id, number).

AuditLog (audit_log)

Действия операторов. operator_id/operator_login, action, entity_type/ entity_id, detail, created_at (индекс). Целевое - hash-chain + WORM-дайджесты (см. Архитектура Capital v1.0.md).

Карты (NS Cards)

CardProgram (card_programs)

Multibrand-программа выпуска. code (unique), name, issuer (default nscards), ns_account_ref, base_url, JSON currencies, лимиты по умолчанию (default_per_transaction/daily/monthly), max_cards_per_company.

Card (cards)

Карта. company_id, account_id (привязка к счёту), program_id, issuer, issuer_card_id (opaque ID эмитента), brand, form_factor (virtual/physical), currency, holder_name, pan_last4 (только last4), expiry_*, status (requested/active/frozen/closed). 🔒 Полный PAN НЕ персистится.

CardTransaction (card_transactions)

Транзакция по карте. card_id, company_id, issuer_tx_id, amount, currency, merchant_name, mcc, kind (authorization/refund/…), status. Unique (card_id, issuer_tx_id) - идемпотентность вебхука.

CardLimit (card_limits)

Спенд-лимит карты. card_id, limit_type (per_transaction/daily/monthly), amount. Unique (card_id, limit_type).

CardRevealAudit (card_reveal_audit)

Аудит показа PAN. card_id, actor_type (operator/client), actor, ip, created_at. Пишется только после успешного reveal (у клиента - после 3DS).

CardFundingRequest (card_funding_requests)

Заявка на фондирование выпуска/пополнения карты, матчится к входящему платежу по уникальной сумме (base + «хвост» в центах): NS-аккаунт бренда общий на клиентов, поэтому дискриминатор - сумма. Клиенту говорят заплатить ровно expected_amount (например 10.37 при заявке на 10.00); матчер атрибутирует входящий платёж этой суммы+валюты к заявке. expected_amount уникальна среди активных (pending, неистёкших) заявок той же программы+валюты. kind (issue/topup), card_id (null для нового выпуска), account_id (куда кредитуется фондирование). В ledger - entry_type card_funding. Смержено 16.09 (миграция e1d7a4c9b2f6).