Agent-Logs-Url: https://github.com/genichin/portfolio-hub/sessions/64fc2cec-78f5-41d1-99a4-45f868781edd Co-authored-by: genichin <4282227+genichin@users.noreply.github.com>
331 lines
12 KiB
Markdown
331 lines
12 KiB
Markdown
# PortfolioHub DB Schema Specification
|
|
|
|
## 1. Purpose
|
|
|
|
This document defines the database schema in an AI-agent-friendly format.
|
|
|
|
- Source of truth: Alembic revisions in `migrations/versions`
|
|
- Current baseline revision: `20260410_0009`
|
|
- Baseline files:
|
|
- `migrations/versions/20260403_0001_initial_portfolio_schema.py`
|
|
- `migrations/versions/20260403_0002_strategy_set_columns.py`
|
|
- `migrations/versions/20260403_0003_portfolio_strategy_refs.py`
|
|
- `migrations/versions/20260403_0004_users_table.py`
|
|
- `migrations/versions/20260403_0005_auth_user_fields_and_tokens.py`
|
|
- `migrations/versions/20260403_0006_bot_principals_and_bot_tokens.py`
|
|
- `migrations/versions/20260403_0007_bot_principal_permission_only.py`
|
|
- `migrations/versions/20260403_0008_portfolio_name_column.py`
|
|
- `migrations/versions/20260410_0009_portfolio_comments_table.py`
|
|
|
|
## 2. Scope
|
|
|
|
Schema objects covered by baseline revision:
|
|
|
|
- `strategy_sets`
|
|
- `users`
|
|
- `auth_tokens`
|
|
- `bot_principals`
|
|
- `portfolios`
|
|
- `portfolio_ledger`
|
|
- `portfolio_comments`
|
|
- secondary indexes
|
|
- Timescale hypertable conversion rule for `portfolio_ledger`
|
|
|
|
## 3. Conventions
|
|
|
|
- Data type names follow SQLAlchemy/Alembic declaration.
|
|
- `NULL` means nullable.
|
|
- Default means DB-level `server_default` where defined.
|
|
- Relationship columns are documented even when no FK constraint exists.
|
|
|
|
## 4. Tables
|
|
|
|
### 4.1 users
|
|
|
|
Purpose:
|
|
|
|
- Stores admin-managed user records for operational ownership and future identity mapping.
|
|
|
|
Columns:
|
|
|
|
| Column | Type | Null | Default | Constraints / Notes |
|
|
|---|---|---|---|---|
|
|
| id | Text | NO | - | Primary Key |
|
|
| username | Text | NO | - | Unique username |
|
|
| email | Text | YES | - | Unique when present |
|
|
| permission | Text | NO | "read" | User permission level |
|
|
| is_active | Boolean | NO | TRUE | Whether login is allowed |
|
|
| password_hash | Text | YES | - | Scrypt password hash |
|
|
| created_at | DateTime(timezone=True) | NO | NOW() | Creation timestamp |
|
|
| updated_at | DateTime(timezone=True) | NO | NOW() | Last update timestamp |
|
|
| last_login_at | DateTime(timezone=True) | YES | - | Last successful login timestamp |
|
|
|
|
Constraints:
|
|
|
|
- Primary key: `id`
|
|
- Unique: `uq_users_username` on (`username`)
|
|
- Unique: `uq_users_email` on (`email`)
|
|
|
|
### 4.2 auth_tokens
|
|
|
|
Purpose:
|
|
|
|
- Stores DB-issued session tokens and client tokens.
|
|
|
|
Columns:
|
|
|
|
| Column | Type | Null | Default | Constraints / Notes |
|
|
|---|---|---|---|---|
|
|
| id | Text | NO | - | Primary Key |
|
|
| user_id | Text | NO | - | Logical user reference |
|
|
| name | Text | NO | - | Human-readable token name |
|
|
| token_prefix | Text | NO | - | Display prefix for issued token |
|
|
| token_hash | Text | NO | - | Unique SHA-256 token hash |
|
|
| permission | Text | NO | - | Effective token permission |
|
|
| token_type | Text | NO | - | `session`, `client`, or `bot` |
|
|
| bot_principal_id | Text | YES | - | Logical reference to `bot_principals.id` |
|
|
| expires_at | DateTime(timezone=True) | YES | - | Expiration time |
|
|
| last_used_at | DateTime(timezone=True) | YES | - | Last successful use |
|
|
| revoked_at | DateTime(timezone=True) | YES | - | Revocation time |
|
|
| created_at | DateTime(timezone=True) | NO | NOW() | Creation timestamp |
|
|
|
|
Constraints:
|
|
|
|
- Primary key: `id`
|
|
- Unique: `uq_auth_tokens_token_hash` on (`token_hash`)
|
|
|
|
### 4.3 bot_principals
|
|
|
|
Purpose:
|
|
|
|
- Stores external bot identities for admin-managed API key issuance and lifecycle control.
|
|
|
|
Columns:
|
|
|
|
| Column | Type | Null | Default | Constraints / Notes |
|
|
|---|---|---|---|---|
|
|
| id | Text | NO | - | Primary Key |
|
|
| name | Text | NO | - | Unique bot name |
|
|
| permission | Text | NO | - | Bot permission ceiling |
|
|
| description | Text | YES | - | Optional operator description |
|
|
| is_active | Boolean | NO | TRUE | When false, bot tokens are rejected |
|
|
| created_at | DateTime(timezone=True) | NO | NOW() | Creation timestamp |
|
|
| updated_at | DateTime(timezone=True) | NO | NOW() | Last update timestamp |
|
|
| deactivated_at | DateTime(timezone=True) | YES | - | Deactivation time |
|
|
|
|
Constraints:
|
|
|
|
- Primary key: `id`
|
|
- Unique: `uq_bot_principals_name` on (`name`)
|
|
|
|
### 4.3 strategy_sets
|
|
|
|
Purpose:
|
|
|
|
- Stores normalized strategy configuration snapshots.
|
|
|
|
Columns:
|
|
|
|
| Column | Type | Null | Default | Constraints / Notes |
|
|
|---|---|---|---|---|
|
|
| id | Text | NO | - | Primary Key |
|
|
| name | Text | NO | - | Strategy name, part of unique pair |
|
|
| config | Text | NO | "" | Strategy config payload, part of unique pair |
|
|
| nick | Text | NO | - | Unique strategy set nickname |
|
|
| created_at | DateTime(timezone=True) | NO | NOW() | Creation timestamp |
|
|
|
|
Constraints:
|
|
|
|
- Primary key: `id`
|
|
- Unique: `uq_strategy_sets_name_config` on (`name`, `config`)
|
|
- Unique: `uq_strategy_sets_nick` on (`nick`)
|
|
|
|
### 4.4 portfolios
|
|
|
|
Purpose:
|
|
|
|
- Stores current portfolio state and strategy linkage.
|
|
|
|
Current baseline note:
|
|
|
|
- The columns below describe the currently migrated schema through revision `20260403_0008`.
|
|
|
|
Columns:
|
|
|
|
| Column | Type | Null | Default | Constraints / Notes |
|
|
|---|---|---|---|---|
|
|
| portfolio_id | Text | NO | - | Primary Key |
|
|
| broker | Text | NO | "kis" | Broker code |
|
|
| status | Text | NO | "enabled" | Portfolio status |
|
|
| name | Text | NO | - | Asset name |
|
|
| symbol | Text | NO | - | Trading symbol |
|
|
| cash_balance | Float | NO | - | Current cash balance |
|
|
| asset_quantity | Float | NO | - | Current asset quantity |
|
|
| asset_price | Float | NO | 0 | Current price snapshot |
|
|
| profit_buffer | Float | NO | 0 | Profit buffer amount |
|
|
| active_strategy_id | Text | YES | - | Logical reference to `strategy_sets.id` |
|
|
| avabile_strategies | Text[] | YES | - | Nullable array of `strategy_sets.id` values |
|
|
| created_at | DateTime(timezone=True) | NO | NOW() | Creation timestamp |
|
|
| updated_at | DateTime(timezone=True) | NO | NOW() | Last update timestamp |
|
|
| last_trade_at | DateTime(timezone=True) | YES | - | Last trade time |
|
|
|
|
Constraints:
|
|
|
|
- Primary key: `portfolio_id`
|
|
- Foreign key constraints: none in baseline
|
|
|
|
Constraint notes:
|
|
|
|
- `active_strategy_id` is a nullable logical reference to `strategy_sets.id`
|
|
- `avabile_strategies` stores zero or more selectable strategy set IDs when present
|
|
|
|
### 4.5 portfolio_ledger
|
|
|
|
Purpose:
|
|
|
|
- Stores append-like ledger snapshots per portfolio action.
|
|
|
|
Columns:
|
|
|
|
| Column | Type | Null | Default | Constraints / Notes |
|
|
|---|---|---|---|---|
|
|
| portfolio_id | Text | NO | - | Logical reference to `portfolios.portfolio_id` (no FK) |
|
|
| recorded_at | DateTime(timezone=True) | NO | - | Event timestamp |
|
|
| action | Text | NO | - | Action type |
|
|
| price | Float | NO | - | Trade/valuation unit price |
|
|
| quantity | Float | NO | - | Quantity delta |
|
|
| fee_amount | Float | NO | 0 | Fees |
|
|
| tax_amount | Float | NO | 0 | Taxes |
|
|
| cash_balance_after | Float | NO | - | Post-action cash |
|
|
| asset_quantity_after | Float | NO | - | Post-action asset quantity |
|
|
| total_value_after | Float | NO | - | Post-action total value |
|
|
| profit_buffer_after | Float | NO | 0 | Post-action profit buffer |
|
|
| strategy_set_id | Text | NO | "legacy" | Logical strategy set reference |
|
|
| note | Text | NO | "" | Operator/system note |
|
|
|
|
Constraints:
|
|
|
|
- Primary key: none in baseline
|
|
- Foreign key constraints: none in baseline
|
|
|
|
### 4.6 portfolio_comments
|
|
|
|
Purpose:
|
|
|
|
- Stores user-authored comment history per portfolio for memo/annotation tracking.
|
|
- Supports multiline text content.
|
|
- Comments are isolated per portfolio and listed in reverse chronological order.
|
|
|
|
Columns:
|
|
|
|
| Column | Type | Null | Default | Constraints / Notes |
|
|
|---|---|---|---|---|
|
|
| id | Text | NO | - | Primary Key (UUID) |
|
|
| portfolio_id | Text | NO | - | Logical reference to `portfolios.portfolio_id` (no FK) |
|
|
| content | Text | NO | - | Comment text; supports multiline |
|
|
| created_at | DateTime(timezone=True) | NO | NOW() | Creation timestamp |
|
|
| updated_at | DateTime(timezone=True) | NO | NOW() | Last update timestamp |
|
|
|
|
Constraints:
|
|
|
|
- Primary key: `id`
|
|
- Foreign key constraints: none
|
|
|
|
## 5. Indexes
|
|
|
|
### 5.1 ix_users_created_at
|
|
|
|
- Table: `users`
|
|
- Columns/expressions: (`created_at`)
|
|
- Unique: no
|
|
- Intent: speed operational listing by recency.
|
|
|
|
### 5.2 idx_auth_tokens_user_created
|
|
|
|
- Table: `auth_tokens`
|
|
- Columns/expressions: (`user_id`, `created_at`)
|
|
- Unique: no
|
|
- Intent: fetch current user's issued tokens efficiently.
|
|
|
|
### 5.3 idx_auth_tokens_hash_active
|
|
|
|
- Table: `auth_tokens`
|
|
- Columns/expressions: (`token_hash`)
|
|
- Unique: no
|
|
- Intent: resolve bearer tokens efficiently.
|
|
|
|
### 5.4 idx_auth_tokens_bot_created
|
|
|
|
- Table: `auth_tokens`
|
|
- Columns/expressions: (`bot_principal_id`, `created_at`)
|
|
- Unique: no
|
|
- Intent: fetch bot-issued token lists efficiently.
|
|
|
|
### 5.5 idx_bot_principals_active_created
|
|
|
|
- Table: `bot_principals`
|
|
- Columns/expressions: (`is_active`, `created_at`)
|
|
- Unique: no
|
|
- Intent: speed operational filtering of active/inactive bots.
|
|
|
|
### 5.6 idx_portfolio_ledger_portfolio_time
|
|
|
|
- Table: `portfolio_ledger`
|
|
- Columns/expressions: (`portfolio_id`, `recorded_at DESC`)
|
|
- Unique: no
|
|
- Intent: fetch recent ledger entries per portfolio efficiently.
|
|
|
|
### 5.7 idx_portfolios_broker_status_updated
|
|
|
|
- Table: `portfolios`
|
|
- Columns/expressions: (`broker`, `status`, `updated_at DESC`)
|
|
- Unique: no
|
|
- Intent: speed list/filter queries by broker/status and recency.
|
|
|
|
### 5.8 idx_portfolio_comments_portfolio_created
|
|
|
|
- Table: `portfolio_comments`
|
|
- Columns/expressions: (`portfolio_id`, `created_at DESC`)
|
|
- Unique: no
|
|
- Intent: fetch comments per portfolio efficiently in reverse chronological order.
|
|
|
|
Conditional behavior in migration upgrade:
|
|
|
|
- Checks whether extension `timescaledb` exists.
|
|
- If present, executes:
|
|
|
|
`create_hypertable('portfolio_ledger', 'recorded_at', if_not_exists => TRUE)`
|
|
|
|
Implication:
|
|
|
|
- `portfolio_ledger` acts as a hypertable in environments with TimescaleDB extension enabled.
|
|
|
|
## 7. Integrity Notes
|
|
|
|
Current baseline intentionally has no DB-level FK constraints.
|
|
|
|
- Logical relations are managed by application/repository logic.
|
|
- When adding FK constraints in future migrations, update this document and include migration IDs.
|
|
|
|
## 8. Change Management Rule
|
|
|
|
When schema changes:
|
|
|
|
1. Add/modify Alembic revision under `migrations/versions`.
|
|
2. Update this document in the same commit.
|
|
3. Keep revision mapping table below in sync.
|
|
|
|
## 9. Revision Mapping
|
|
|
|
| Revision ID | File | Summary |
|
|
|---|---|---|
|
|
| 20260403_0001 | migrations/versions/20260403_0001_initial_portfolio_schema.py | Initial schema for strategy sets, portfolios, ledger, indexes, optional hypertable |
|
|
| 20260403_0002 | migrations/versions/20260403_0002_strategy_set_columns.py | Rename `strategy_name`->`name`, `strategy_config`->`config`, add unique `nick`, handle legacy unnamed unique constraint |
|
|
| 20260403_0003 | migrations/versions/20260403_0003_portfolio_strategy_refs.py | Remove embedded portfolio strategy fields, add `active_strategy_id` and `avabile_strategies` |
|
|
| 20260403_0004 | migrations/versions/20260403_0004_users_table.py | Add `users` table with unique username/email and created_at index |
|
|
| 20260403_0005 | migrations/versions/20260403_0005_auth_user_fields_and_tokens.py | Add password/permission/login fields to `users` and create `auth_tokens` table |
|
|
| 20260403_0006 | migrations/versions/20260403_0006_bot_principals_and_bot_tokens.py | Add `bot_principals` table and `auth_tokens.bot_principal_id` for bot API key management |
|
|
| 20260403_0007 | migrations/versions/20260403_0007_bot_principal_permission_only.py | Remove `bot_principals.owner_user_id` and enforce bot-local `permission` model |
|
|
| 20260403_0008 | migrations/versions/20260403_0008_portfolio_name_column.py | Add non-null `portfolios.name` (asset display name), backfilled from `symbol` |
|
|
| 20260410_0009 | migrations/versions/20260410_0009_portfolio_comments_table.py | Add `portfolio_comments` table for per-portfolio memo/comment history |
|