-- ProspectIQ Supabase PostgreSQL Database Schema -- Enable pgvector if not already enabled (for future vector functionality) CREATE EXTENSION IF NOT EXISTS vector; -- Drop tables if they exist (for a clean setup, but we'll use IF NOT EXISTS) -- 1. CRM Schema Tables CREATE TABLE IF NOT EXISTS tenants ( id SERIAL PRIMARY KEY, name VARCHAR(255) NOT NULL, brand_color VARCHAR(50), logo_url TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS users ( id SERIAL PRIMARY KEY, tenant_id INTEGER NOT NULL REFERENCES tenants(id), email VARCHAR(255) UNIQUE NOT NULL, name VARCHAR(255) NOT NULL, role VARCHAR(50) NOT NULL CHECK(role IN ('Super Admin', 'Admin', 'BD Rep', 'Viewer')), is_active INTEGER DEFAULT 1, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS stages ( id SERIAL PRIMARY KEY, name VARCHAR(255) NOT NULL, order_index INTEGER NOT NULL UNIQUE, color_bg VARCHAR(50), color_text VARCHAR(50), is_active INTEGER DEFAULT 1 ); CREATE TABLE IF NOT EXISTS import_batches ( id SERIAL PRIMARY KEY, user_id INTEGER NOT NULL REFERENCES users(id), filename VARCHAR(255), status VARCHAR(50), total_rows INTEGER, added INTEGER, updated INTEGER, skipped INTEGER, errored INTEGER, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, completed_at TIMESTAMP ); CREATE TABLE IF NOT EXISTS companies ( id SERIAL PRIMARY KEY, tenant_id INTEGER NOT NULL REFERENCES tenants(id), name VARCHAR(255) NOT NULL, legal_name VARCHAR(255), website TEXT, website_link TEXT, phone VARCHAR(50), nic_code VARCHAR(50), industry VARCHAR(255), street TEXT, city VARCHAR(255), province VARCHAR(255), postal_code VARCHAR(50), country VARCHAR(255), size_staff INTEGER, revenue_band VARCHAR(255), business_type VARCHAR(50) CHECK(business_type IN ('OEM', 'distributor', 'other', 'OEM / distributor', 'OEM / Distributor')), products_made TEXT, parts_sourced TEXT, current_supplier_notes TEXT, maplempss_fit_score REAL DEFAULT 0.0, stage_id INTEGER NOT NULL REFERENCES stages(id), assigned_rep_id INTEGER REFERENCES users(id), created_by INTEGER REFERENCES users(id), updated_by INTEGER REFERENCES users(id), import_batch_id INTEGER, -- Not enforced by constraint to prevent cyclic dependencies easily created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS contacts ( id SERIAL PRIMARY KEY, company_id INTEGER NOT NULL REFERENCES companies(id) ON DELETE CASCADE, first_name VARCHAR(255) NOT NULL, last_name VARCHAR(255) NOT NULL, title VARCHAR(255), email VARCHAR(255), phone VARCHAR(50), linkedin TEXT, is_primary INTEGER DEFAULT 0 CHECK(is_primary IN (0, 1)), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS activities ( id SERIAL PRIMARY KEY, company_id INTEGER NOT NULL REFERENCES companies(id) ON DELETE CASCADE, contact_id INTEGER REFERENCES contacts(id) ON DELETE SET NULL, user_id INTEGER NOT NULL REFERENCES users(id), type VARCHAR(50) NOT NULL CHECK(type IN ('Cold Call', 'Email', 'LinkedIn Message', 'Meeting', 'Follow-Up', 'Note')), outcome VARCHAR(255), notes TEXT, next_action TEXT, next_action_date VARCHAR(50), occurred_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS notes ( id SERIAL PRIMARY KEY, company_id INTEGER NOT NULL REFERENCES companies(id) ON DELETE CASCADE, contact_id INTEGER REFERENCES contacts(id) ON DELETE SET NULL, user_id INTEGER NOT NULL REFERENCES users(id), body TEXT NOT NULL, is_pinned INTEGER DEFAULT 0 CHECK(is_pinned IN (0, 1)), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS stage_changes ( id SERIAL PRIMARY KEY, company_id INTEGER NOT NULL REFERENCES companies(id) ON DELETE CASCADE, from_stage_id INTEGER REFERENCES stages(id), to_stage_id INTEGER NOT NULL REFERENCES stages(id), changed_by_user_id INTEGER NOT NULL REFERENCES users(id), source VARCHAR(50) NOT NULL CHECK(source IN ('manual', 'ai_suggested', 'ai_confirmed')), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS ai_field_values ( id SERIAL PRIMARY KEY, company_id INTEGER NOT NULL REFERENCES companies(id) ON DELETE CASCADE, field_name VARCHAR(255) NOT NULL, value TEXT, source_url TEXT, confidence_score REAL, enriched_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, confirmed_by_user_id INTEGER REFERENCES users(id), confirmed_at TIMESTAMP ); -- Indices for fast searching and filtering CREATE INDEX IF NOT EXISTS idx_companies_stage ON companies(stage_id); CREATE INDEX IF NOT EXISTS idx_companies_tenant ON companies(tenant_id); CREATE INDEX IF NOT EXISTS idx_companies_rep ON companies(assigned_rep_id); CREATE INDEX IF NOT EXISTS idx_contacts_company ON contacts(company_id); CREATE INDEX IF NOT EXISTS idx_activities_company ON activities(company_id); CREATE INDEX IF NOT EXISTS idx_notes_company ON notes(company_id); -- 2. AI / Agent Interaction History Table CREATE TABLE IF NOT EXISTS interactions ( id VARCHAR(255) PRIMARY KEY, kind VARCHAR(50) NOT NULL, title VARCHAR(255) NOT NULL, status VARCHAR(50) NOT NULL, input_json TEXT NOT NULL DEFAULT '{}', output_json TEXT NOT NULL DEFAULT '{}', result_text TEXT NOT NULL DEFAULT '', logs_json TEXT NOT NULL DEFAULT '[]', error TEXT NOT NULL DEFAULT '', run_dir TEXT NOT NULL DEFAULT '', profile_path TEXT NOT NULL DEFAULT '', command_json TEXT NOT NULL DEFAULT '[]', returncode INTEGER, pid INTEGER, created_at VARCHAR(50) NOT NULL, started_at VARCHAR(50) NOT NULL DEFAULT '', finished_at VARCHAR(50) NOT NULL DEFAULT '', updated_at VARCHAR(50) NOT NULL ); CREATE INDEX IF NOT EXISTS idx_interactions_created_at ON interactions(created_at DESC); CREATE INDEX IF NOT EXISTS idx_interactions_kind ON interactions(kind); CREATE INDEX IF NOT EXISTS idx_interactions_kind_created ON interactions(kind, created_at DESC); -- 3. Restructured RAG Playbook Tables CREATE TABLE IF NOT EXISTS rag_collections ( id VARCHAR(255) PRIMARY KEY, name VARCHAR(255) NOT NULL, user_id VARCHAR(255), visibility VARCHAR(50) DEFAULT 'private', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS rag_documents ( id VARCHAR(255) PRIMARY KEY, collection_id VARCHAR(255) REFERENCES rag_collections(id) ON DELETE CASCADE, filename VARCHAR(255) NOT NULL, original_type VARCHAR(50), pages INTEGER DEFAULT 0, chunks INTEGER DEFAULT 0, uploaded_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, upload_path TEXT ); CREATE TABLE IF NOT EXISTS rag_sessions ( id VARCHAR(255) PRIMARY KEY, collection_id VARCHAR(255) REFERENCES rag_collections(id) ON DELETE CASCADE, name VARCHAR(255) NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS rag_messages ( id SERIAL PRIMARY KEY, collection_id VARCHAR(255) REFERENCES rag_collections(id) ON DELETE CASCADE, session_id VARCHAR(255) REFERENCES rag_sessions(id) ON DELETE CASCADE, role VARCHAR(50) NOT NULL, content TEXT NOT NULL, sources JSONB DEFAULT '[]'::jsonb, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP );