Модель данных БД
Описание схемы PostgreSQL приложения llmgw. Источник истины — SQLAlchemy-модели в backend/app/models/. Миграции — Alembic (backend/alembic/versions/).
Обзор
База хранит:
- Пользователей и организации — B2B-модель: кошелёк привязан к организации, пользователь может быть участником нескольких организаций.
- API-ключи — виртуальные ключи LiteLLM с лимитами и статусами.
- Биллинг — платежи ЮKassa, безналичные пополнения, append-only журнал баланса, бонусы.
- Прайсинг — правила ценообразования моделей и история курса USD/RUB.
- Экономика — ручной учёт затрат OpenRouter, налогов и целевой маржи (админка).
- Инфраструктура — outbox для at-least-once обработки событий.
Все денежные суммы в копейках (BigInteger), кроме полей в USD (Numeric).
Общие соглашения
| Соглашение | Значение |
|---|---|
| PK | UUID v4 в String(36) |
| Временные метки | created_at, updated_at — DateTime(timezone=True), UTC |
| Миксин | TimestampMixin в app.models.base |
| Именование FK/индексов | SQLAlchemy naming convention в Base.metadata |
ER-диаграмма
erDiagram
users ||--o| organizations : "billing_organization_id"
users ||--o{ organization_members : "user_id"
organizations ||--o{ organization_members : "organization_id"
organizations ||--o{ api_keys : "organization_id"
users ||--o{ api_keys : "user_id"
users ||--o{ payments : "user_id"
users ||--o{ balance_ledger : "user_id"
organizations ||--o{ balance_ledger : "organization_id"
users ||--o{ bonuses : "user_id"
users ||--o{ referrals : "referrer / referred"
organizations ||--o{ organization_bank_topups : "organization_id"
balance_ledger ||--o| organization_bank_topups : "ledger_entry_id"
users ||--o{ openrouter_topups : "created_by"
users ||--o{ economics_expenses : "created_by"
users ||--o| economics_settings : "updated_by"
users {
uuid id PK
string email UK
string password_hash
string google_sub UK
string yandex_sub UK
enum role
enum status
uuid billing_organization_id FK
}
organizations {
uuid id PK
string name
uuid owner_user_id FK
string litellm_team_id UK
bigint balance_kopecks
bigint total_topped_up_kopecks
}
organization_members {
uuid id PK
uuid organization_id FK
uuid user_id FK
enum role
}
api_keys {
uuid id PK
uuid user_id FK
uuid organization_id FK
string key_hash UK
enum status
}
balance_ledger {
uuid id PK
uuid organization_id FK
uuid user_id FK
bigint delta_kopecks
enum reason
string ref_id
}
payments {
uuid id PK
uuid user_id FK
string yookassa_id UK
enum status
}Таблицы
users
Пользователь кабинета. Баланс не хранится на пользователе — он вычисляется через billing_organization.
| Колонка | Тип | Описание |
|---|---|---|
id | String(36) PK | UUID |
email | String(255) UNIQUE | Логин, индекс |
password_hash | String(255) NULL | NULL для OAuth-only аккаунтов |
google_sub | String(255) UNIQUE NULL | Subject Google OAuth |
yandex_sub | String(255) UNIQUE NULL | Subject Yandex OAuth |
role | user_role | user | admin |
status | user_status | active | pending_email | blocked |
email_verified | Boolean | Подтверждён ли email |
email_verified_at | DateTime(tz) NULL | |
billing_organization_id | String(36) FK → organizations NULL | Кошелёк; nullable на первом flush при регистрации |
totp_secret | String(64) NULL | 2FA |
totp_enabled | Boolean | |
telegram_id | BigInteger UNIQUE NULL | Зарезервировано |
telegram_username | String(64) NULL | |
referral_code | String(16) UNIQUE NULL | Код для приглашений |
referred_by | String(36) NULL | ID пригласившего (без FK) |
accepted_offer_at | DateTime(tz) NULL | Согласие с офертой (152-ФЗ) |
accepted_pdn_at | DateTime(tz) NULL | Согласие на обработку ПДн |
accepted_offer_version | String(16) NULL | Версия документа оферты |
accepted_pdn_version | String(16) NULL | Версия политики ПДн |
accepted_offer_ip | String(64) NULL | IP при принятии |
accepted_user_agent | String(512) NULL | User-Agent при принятии |
last_login_at | DateTime(tz) NULL | |
notes | Text NULL | Админ-заметки |
created_at, updated_at | DateTime(tz) |
Связи: api_keys, payments, balance_ledger, bonuses (cascade delete); billing_organization → organizations.
organizations
Организация (B2B): единый кошелёк, участники, связка с LiteLLM Team.
| Колонка | Тип | Описание |
|---|---|---|
id | String(36) PK | UUID |
name | String(160) | Название |
owner_user_id | String(36) FK → users RESTRICT | Владелец |
litellm_team_id | String(128) UNIQUE NULL | ID команды в LiteLLM |
requisites | Text NULL | Реквизиты для безнала |
balance_kopecks | BigInteger | Текущий баланс |
total_topped_up_kopecks | BigInteger | Сумма всех пополнений |
created_at, updated_at | DateTime(tz) |
Связи: organization_members (cascade delete).
organization_members
Членство пользователя в организации. Уникальная пара (organization_id, user_id).
| Колонка | Тип | Описание |
|---|---|---|
id | String(36) PK | |
organization_id | String(36) FK CASCADE | |
user_id | String(36) FK CASCADE | |
role | org_member_role | owner | admin | member | finance | technical |
created_at, updated_at | DateTime(tz) |
api_keys
Виртуальные ключи доступа к API (проксируются в LiteLLM).
| Колонка | Тип | Описание |
|---|---|---|
id | String(36) PK | |
user_id | String(36) FK CASCADE | Создатель |
organization_id | String(36) FK CASCADE | Организация-кошелёк |
name | String(80) | Отображаемое имя |
litellm_key_id | String(128) UNIQUE NULL | ID ключа в LiteLLM |
masked_key | String(64) | Маска вида sk-llmgw-...XXXX |
key_hash | String(128) UNIQUE | SHA-256 полного ключа |
status | api_key_status | см. enum ниже |
daily_limit_kopecks | BigInteger NULL | Дневной лимит расхода |
monthly_limit_kopecks | BigInteger NULL | Месячный лимит |
rpm_limit | Integer NULL | Requests per minute |
tpm_limit | Integer NULL | Tokens per minute |
allowed_models | JSON | Список моделей; [] = все |
last_used_at | DateTime(tz) NULL | |
expires_at | DateTime(tz) NULL | |
created_at, updated_at | DateTime(tz) |
balance_ledger
Append-only журнал изменений баланса организации. Каждая запись фиксирует delta_kopecks и balance_after_kopecks.
| Колонка | Тип | Описание |
|---|---|---|
id | String(36) PK | |
organization_id | String(36) FK CASCADE | |
user_id | String(36) FK CASCADE | Пользователь-инициатор / владелец операции |
delta_kopecks | BigInteger | +/- к балансу |
balance_after_kopecks | BigInteger | Снимок баланса после операции |
reason | ledger_reason | Тип операции |
ref_id | String(128) NULL | Идемпотентность: payment_id, LiteLLM request_id, … |
description | Text NULL | |
extra | JSON NULL | Произвольные метаданные |
created_at, updated_at | DateTime(tz) |
Индексы: (user_id, created_at), (organization_id, created_at), (reason, ref_id).
payments
Платежи через ЮKassa.
| Колонка | Тип | Описание |
|---|---|---|
id | String(36) PK | |
user_id | String(36) FK CASCADE | |
yookassa_id | String(64) UNIQUE NULL | ID в ЮKassa |
idempotency_key | String(64) UNIQUE | Ключ идемпотентности |
amount_kopecks | BigInteger | |
currency | String(3) | По умолчанию RUB |
status | payment_status | |
method | String(32) NULL | bank_card, sbp, … |
description | String(255) NULL | |
confirmation_url | Text NULL | URL оплаты |
receipt_payload | JSON NULL | Чек 54-ФЗ |
raw_payload | JSON NULL | Полный ответ ЮKassa |
paid_at | DateTime(tz) NULL | |
refunded_at | DateTime(tz) NULL | |
created_at, updated_at | DateTime(tz) |
organization_bank_topups
Пополнения по безналу (платёжное поручение), вносятся админом.
| Колонка | Тип | Описание |
|---|---|---|
id | String(36) PK | |
organization_id | String(36) FK CASCADE | |
amount_kopecks | BigInteger | |
payment_date | Date | Дата платежа |
payment_order_number | String(64) UNIQUE | Номер платёжного поручения |
note | Text NULL | |
ledger_entry_id | String(36) FK → balance_ledger SET NULL UNIQUE | Связанная запись журнала |
created_by_user_id | String(36) FK → users SET NULL | |
created_at, updated_at | DateTime(tz) |
bonuses
Начисленные бонусы (триал, welcome, промо, реферал, админ).
| Колонка | Тип | Описание |
|---|---|---|
id | String(36) PK | |
user_id | String(36) FK CASCADE | |
bonus_type | bonus_type | |
amount_kopecks | BigInteger | |
promo_code | String(32) NULL | |
extra | JSON NULL | |
applied_at | DateTime(tz) NULL | Когда зачислен на баланс |
created_at, updated_at | DateTime(tz) |
referrals
Реферальная программа (post-MVP placeholder).
| Колонка | Тип | Описание |
|---|---|---|
id | String(36) PK | |
referrer_id | String(36) FK CASCADE | Пригласивший |
referred_id | String(36) FK CASCADE UNIQUE | Приглашённый (один раз) |
code | String(16) | Использованный код |
reward_kopecks | BigInteger | Начисленное вознаграждение |
created_at, updated_at | DateTime(tz) |
pricing_rules
Правила ценообразования по модели. PK — имя модели в LiteLLM.
| Колонка | Тип | Описание |
|---|---|---|
model_name | String(128) PK | Например anthropic/claude-sonnet-4.6 |
display_name | String(128) NULL | Для UI |
provider | String(32) NULL | anthropic, openai, … |
family | String(32) NULL | claude, gpt, … |
base_input_usd_per_mtok | Numeric(12,6) | База OpenRouter, USD / 1M input |
base_output_usd_per_mtok | Numeric(12,6) | USD / 1M output |
base_cached_input_usd_per_mtok | Numeric(12,6) NULL | Кешированный input |
markup_pct | Numeric(6,2) | Наценка платформы, % |
context_tokens | Integer NULL | Размер контекста |
max_output_tokens | Integer NULL | |
features | JSON NULL | vision, tools, … |
description | Text NULL | |
visible | Boolean | Показывать в каталоге |
trial_allowed | Boolean | Доступна без пополнения |
created_at, updated_at | DateTime(tz) |
Формула цены в ₽:base_*_usd × (1 + markup_pct/100) × usd_rub × (1 + spread_pct/100).
fx_rates
История курсов USD/RUB (ЦБ + спред).
| Колонка | Тип | Описание |
|---|---|---|
rate_date | Date PK | Календарная дата |
usd_rub | Numeric(10,4) | Курс ЦБ |
spread_pct | Numeric(5,2) | Спред платформы |
source | String(32) | По умолчанию cbr |
fetched_at | DateTime(tz) | Время загрузки |
openrouter_topups
Ручной учёт пополнений баланса OpenRouter (затраты в USD).
| Колонка | Тип | Описание |
|---|---|---|
id | String(36) PK | |
topup_date | Date | |
amount_usd | Numeric(16,6) | |
manual_usd_rub | Numeric(12,4) NULL | Ручной курс; иначе ЦБ+спред на дату |
note | Text NULL | |
created_by_user_id | String(36) FK SET NULL | |
created_at, updated_at | DateTime(tz) |
economics_expenses
Ручной учёт налогов и комиссий конвертации.
| Колонка | Тип | Описание |
|---|---|---|
id | String(36) PK | |
expense_date | Date | |
kind | economics_expense_kind | tax | conversion_fee |
amount_kopecks | BigInteger | |
note | Text NULL | |
created_by_user_id | String(36) FK SET NULL | |
created_at, updated_at | DateTime(tz) |
economics_settings
Singleton настроек экономической аналитики (id = 'default').
| Колонка | Тип | Описание |
|---|---|---|
id | String(16) PK | Фиксированное 'default' |
target_margin_pct | Numeric(6,2) | Целевая маржа, % от выручки |
updated_at | DateTime(tz) | |
updated_by_user_id | String(36) FK SET NULL |
outbox
Transactional outbox для at-least-once доставки (webhook платежей и др.).
| Колонка | Тип | Описание |
|---|---|---|
id | String(36) PK | |
topic | String(64) | Тип события |
ref_id | String(128) NULL | Внешний идентификатор |
payload | JSON | Тело события |
status | outbox_status | |
attempts | Integer | Число попыток |
next_attempt_at | DateTime(tz) NULL | Следующий retry |
last_error | Text NULL | |
processed_at | DateTime(tz) NULL | |
created_at, updated_at | DateTime(tz) |
Индексы: (status, next_attempt_at), (topic, ref_id).
Перечисления (PostgreSQL ENUM)
user_role
user · admin
user_status
active · pending_email · blocked
org_member_role
owner · admin · member · finance · technical
api_key_status
| Значение | Смысл |
|---|---|
active | Рабочий ключ |
blocked | Ручная заморозка / исчерпан дневной лимит |
insufficient_funds | Авто-блокировка при balance ≤ 0 |
deleted | Удалён |
payment_status
pending · waiting_for_capture · succeeded · canceled · refunded · failed
ledger_reason
| Значение | Смысл |
|---|---|
topup | Пополнение (ЮKassa / безнал) |
usage | Списание за usage LiteLLM |
bonus_signup | Триал при подтверждении email |
bonus_topup | Welcome-бонус |
bonus_referral | Реферальный бонус |
bonus_promo | Промокод |
refund | Возврат |
admin_adjustment | Корректировка админом |
chargeback | Чарджбэк |
bonus_type
email_trial · welcome_topup · referral · promo · admin
outbox_status
pending · processing · done · failed · dead
economics_expense_kind
tax · conversion_fee
Ключевые бизнес-правила
Кошелёк на организации.
users.balance_kopecks— вычисляемое свойство черезbilling_organization. Единственный источник баланса —organizations.balance_kopecks.Журнал append-only. Любое изменение баланса порождает запись в
balance_ledgerсbalance_after_kopecks. Откат — новая запись с противоположнымdelta_kopecks.Идемпотентность. Поле
balance_ledger.ref_idпредотвращает двойное списание/зачисление (например, одинpayment_idили LiteLLMrequest_id).OAuth.
password_hashможет быть NULL; идентификация черезgoogle_sub/yandex_sub(уникальные).API-ключи привязаны к организации. Списание usage идёт с баланса
organization_idключа, а не личного кошелька пользователя.Outbox. Критичные побочные эффекты (зачисление после webhook) пишутся в одной транзакции с основной операцией; воркер обрабатывает
outboxс ретраями.
Карта модулей → таблицы
Модуль (backend/app/models/) | Таблица |
|---|---|
users.py | users |
organizations.py | organizations, organization_members |
api_keys.py | api_keys |
balance_ledger.py | balance_ledger |
payments.py | payments |
bank_topups.py | organization_bank_topups |
bonuses.py | bonuses |
referrals.py | referrals |
pricing.py | pricing_rules, fx_rates |
economics.py | openrouter_topups, economics_expenses, economics_settings |
outbox.py | outbox |
Миграции (хронология)
| Revision | Файл | Описание |
|---|---|---|
0001_initial | 20260427_000000_initial_schema.py | Базовая схема |
0002_consent_audit | 20260427_000100_consent_audit_fields.py | Аудит согласий 152-ФЗ |
0002_orgs | 20260428_120000_organizations_litellm_teams.py | Организации, перенос баланса, LiteLLM teams |
0003_org_roles_ft | 20260429_130000_org_roles_finance_technical.py | Роли finance, technical |
0004_economics | 20260430_120000_economics_analytics.py | Экономическая аналитика |
0005_org_requisites | 20260501_120000_org_requisites.py | organizations.requisites |
20260519_100000 | 20260519_100000_users_billing_org_nullable_on_create.py | Nullable billing_organization_id |
20260519_110000 | 20260519_110000_organization_bank_topups.py | Безналичные пополнения |
20260519_120000 | 20260519_120000_google_oauth.py | google_sub, nullable password_hash |
20260519_130000 | 20260519_130000_yandex_oauth.py | yandex_sub |