Data model¶
MarkAnn's durable state is in PostgreSQL (SQLAlchemy 2 async models in database/models.py); its transient/operational state is in Redis. This page is the reference for both.
PostgreSQL tables¶
Identity & auth¶
users — accounts and roles.
| Column | Type | Notes |
|---|---|---|
id |
int PK | |
email |
str, unique | login identity |
password_hash |
str | bcrypt |
role |
str | trader · admin · superuser (default trader) |
is_active |
bool | |
first_name, last_name |
str | |
created_by |
int FK → users.id | who created this account |
created_at |
datetime |
A partial unique index enforces at most one superuser.
refresh_tokens — server-side refresh tokens, rotated on use (backs the auth flow).
Domain¶
user_watchlist — which users watch which symbols.
user_channel — a user's delivery channels (e.g. Telegram).
announcements — processed corporate announcements. Keyed by seq_id; stores symbol, company, category, announcement_text, summary, processing_mode (multimodal / text), attachment_url, announced_at. Written by the corp_ann processor.
engine_config — engine-level key/value settings.
Component registry¶
The tables that make components data. See Component Registry.
poller_config
| Column | Type | Notes |
|---|---|---|
id |
int PK | |
module |
str, unique | import path, e.g. engine.pollers.corp_ann |
api_name |
str, unique | stable identifier, e.g. corp_ann |
output_schema |
text (JSON) | JSON Schema of emitted items |
config |
text (JSON) | merged defaults + overrides |
enabled |
bool | loaded only if true |
created_at, updated_at |
datetime |
processor_config — same shape, with input_schema instead of output_schema.
processor_poller_link — many-to-many (processor_id, poller_id) composite PK linking processors to their source poller(s).
erDiagram
poller_config ||--o{ processor_poller_link : "feeds"
processor_config ||--o{ processor_poller_link : "consumes"
users ||--o{ refresh_tokens : has
users ||--o{ user_watchlist : has
users ||--o{ user_channel : has
Redis keys¶
Full map with types and TTLs in Data Flow & Redis. Summary:
| Family | Keys | Role |
|---|---|---|
| Queue & dedup | queue:{api}, inflight:{api}:{item_id}, dedup:{api}:{seq_id} |
work distribution + two-level dedup |
| Results & delivery | result:{date}:{symbol}:{seq_id}, alerts:{symbol} (pub/sub), watch:{symbol}, user:{id}:channels |
processed payloads + live alerts |
| Poller health | poller:{api}:heartbeat / :last_success / :status / :error_count / :interval |
liveness + state |
| Processor health | processor:{api}:status |
state |
| Events & control | engine:events (list), engine:control (pub/sub) |
log + commands |
Migrations¶
Schema changes are Alembic migrations in database/migrations/versions/. Alembic requires a single linear head.
# create
alembic -c database/migrations/alembic.ini revision --autogenerate -m "description"
# apply
alembic -c database/migrations/alembic.ini upgrade head
# check heads (should be exactly one)
alembic -c database/migrations/alembic.ini heads
In Compose, the one-shot migrate service runs upgrade head before anything else starts. See Local Development for the branch/merge caveat.