← back to Patty
db/orbit-schema.sql
123 lines
-- Orbit: Kalshi <-> Petition Bridge + RSS News Intelligence
-- Part of Patty - The Petition Specialist
-- Database: postgresql://dw_admin@127.0.0.1:5432/ # password in .env.local (DATABASE_URL)
-- patty
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
-- Kalshi prediction markets synced from Ken agent
CREATE TABLE IF NOT EXISTS orbit_markets (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
ticker TEXT NOT NULL UNIQUE,
event_ticker TEXT,
title TEXT NOT NULL,
subtitle TEXT,
category TEXT,
yes_bid INT,
yes_ask INT,
no_bid INT,
no_ask INT,
last_price INT,
volume INT,
volume_24h INT,
open_interest INT,
status TEXT DEFAULT 'open',
close_time TIMESTAMPTZ,
petition_relevance REAL DEFAULT 0, -- 0-100 AI-scored relevance to advocacy
suggested_petition TEXT, -- AI-generated petition angle
suggested_target TEXT,
raw_data JSONB,
last_synced_at TIMESTAMPTZ DEFAULT NOW(),
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- Links between markets and petitions
CREATE TABLE IF NOT EXISTS orbit_links (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
market_id UUID REFERENCES orbit_markets(id) ON DELETE CASCADE,
petition_id UUID REFERENCES petitions(id) ON DELETE CASCADE,
link_type TEXT NOT NULL DEFAULT 'influence', -- influence, counter, support
strength REAL DEFAULT 0.5, -- 0-1 connection strength
ai_reasoning TEXT,
created_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(market_id, petition_id)
);
-- RSS/Atom feed sources
CREATE TABLE IF NOT EXISTS orbit_feeds (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
name TEXT NOT NULL,
url TEXT NOT NULL UNIQUE,
feed_type TEXT NOT NULL DEFAULT 'rss', -- rss, atom, json
category TEXT DEFAULT 'national', -- national, local, wire
station TEXT, -- CNN, Fox, NBC, local call sign
market TEXT, -- DMA market for local (New York, Los Angeles, etc.)
is_active BOOLEAN DEFAULT TRUE,
last_fetched_at TIMESTAMPTZ,
fetch_interval_min INT DEFAULT 30,
article_count INT DEFAULT 0,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Articles fetched from RSS feeds
CREATE TABLE IF NOT EXISTS orbit_articles (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
feed_id UUID REFERENCES orbit_feeds(id) ON DELETE CASCADE,
title TEXT NOT NULL,
url TEXT NOT NULL,
summary TEXT,
published_at TIMESTAMPTZ,
sentiment TEXT, -- positive, negative, neutral, mixed
sentiment_score REAL, -- -1 to 1
relevance_score REAL DEFAULT 0, -- 0-100 relevance to petition topics
matched_markets TEXT[], -- Kalshi market tickers this article relates to
matched_petitions UUID[], -- Petition IDs this article relates to
tags TEXT[],
raw_data JSONB,
created_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(feed_id, url)
);
-- Cross-platform petition posts
CREATE TABLE IF NOT EXISTS orbit_posts (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
petition_id UUID REFERENCES petitions(id) ON DELETE CASCADE,
platform TEXT NOT NULL, -- moveon, change_org, we_the_people, custom
external_url TEXT,
status TEXT DEFAULT 'draft', -- draft, posted, failed, archived
post_data JSONB, -- platform-specific payload
response_data JSONB, -- platform response
posted_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Indexes
CREATE INDEX IF NOT EXISTS idx_orbit_markets_category ON orbit_markets(category);
CREATE INDEX IF NOT EXISTS idx_orbit_markets_relevance ON orbit_markets(petition_relevance DESC);
CREATE INDEX IF NOT EXISTS idx_orbit_markets_status ON orbit_markets(status);
CREATE INDEX IF NOT EXISTS idx_orbit_links_market ON orbit_links(market_id);
CREATE INDEX IF NOT EXISTS idx_orbit_links_petition ON orbit_links(petition_id);
CREATE INDEX IF NOT EXISTS idx_orbit_articles_sentiment ON orbit_articles(sentiment_score);
CREATE INDEX IF NOT EXISTS idx_orbit_articles_published ON orbit_articles(published_at DESC);
CREATE INDEX IF NOT EXISTS idx_orbit_articles_feed ON orbit_articles(feed_id);
CREATE INDEX IF NOT EXISTS idx_orbit_feeds_active ON orbit_feeds(is_active);
CREATE INDEX IF NOT EXISTS idx_orbit_posts_platform ON orbit_posts(platform);
CREATE INDEX IF NOT EXISTS idx_orbit_posts_petition ON orbit_posts(petition_id);
-- Trigger for orbit_markets updated_at
CREATE OR REPLACE FUNCTION update_updated_at_column() RETURNS TRIGGER AS $$
BEGIN NEW.updated_at = NOW(); RETURN NEW; END;
$$ LANGUAGE plpgsql;
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_trigger WHERE tgname = 'tr_orbit_markets_updated'
) THEN
CREATE TRIGGER tr_orbit_markets_updated
BEFORE UPDATE ON orbit_markets
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
END IF;
END $$;