"""KOL module — Substack/Twitter post ingestion + AI analysis. Standalone module, no FK into Trump posts/trades. First slice (B-tier): - kol_posts: raw ingested posts from Substack/Twitter, keyed by URL - (kol_holdings + kol_wallets reserved for A-tier in later migration) Each post carries its own AI analysis inline (1:1, no separate table): summary (one-sentence), tickers_json (list of {ticker, action, conviction, quote}), model, analysis_version. Revision ID: 017 Revises: 016 Create Date: 2026-05-23 00:00:00.000000 """ from typing import Sequence, Union import sqlalchemy as sa from alembic import op revision: str = "017" down_revision: Union[str, None] = "016" branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None def upgrade() -> None: op.create_table( "kol_posts", sa.Column("id", sa.Integer(), primary_key=True), sa.Column("kol_handle", sa.String(64), nullable=False, index=True), sa.Column("source", sa.String(16), nullable=False), # substack | twitter sa.Column("external_id", sa.String(512), nullable=False), # canonical URL / tweet id sa.Column("title", sa.String(512), nullable=True), sa.Column("url", sa.String(512), nullable=False), sa.Column("published_at", sa.DateTime(), nullable=False, index=True), sa.Column("raw_text", sa.Text(), nullable=False), sa.Column("content_hash", sa.String(64), nullable=False), # ── Inline analysis (1:1, populated by extractor) ───────────── sa.Column("summary", sa.Text(), nullable=True), sa.Column("tickers_json", sa.Text(), nullable=True), # JSON list sa.Column("analyzed_at", sa.DateTime(), nullable=True), sa.Column("analysis_model", sa.String(64), nullable=True), sa.Column("analysis_version", sa.String(16), nullable=True), sa.Column("created_at", sa.DateTime(), nullable=False, server_default=sa.func.current_timestamp()), sa.UniqueConstraint("source", "external_id", name="uq_kol_post_src_extid"), ) def downgrade() -> None: op.drop_table("kol_posts")