Files
Project_Velocity/backend/db/schema.sql
2026-04-28 11:32:56 +05:30

182 lines
9.0 KiB
PL/PgSQL

-- backend/db/schema.sql - Velocity PostgreSQL schema
-- Omnichannel Intelligence Graph for The Sentinel.
--
-- Run via:
-- psql -U velocity_user -d velocity_db -f schema.sql
--
-- Or via Alembic (preferred - see backend/alembic/).
-- Enable UUID generation
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
-- ────────────────────────────────────────────────────────────────────────────
-- ENUM TYPES
-- ────────────────────────────────────────────────────────────────────────────
DO $$ BEGIN
CREATE TYPE role_enum AS ENUM (
'ADMIN',
'SALES_DIRECTOR',
'SENIOR_BROKER',
'JUNIOR_BROKER'
);
EXCEPTION WHEN duplicate_object THEN NULL;
END $$;
DO $$ BEGIN
CREATE TYPE source_enum AS ENUM ('whatsapp', 'website', 'walkin');
EXCEPTION WHEN duplicate_object THEN NULL;
END $$;
DO $$ BEGIN
CREATE TYPE lead_status_enum AS ENUM ('new', 'engaged', 'qualified', 'hot', 'closed');
EXCEPTION WHEN duplicate_object THEN NULL;
END $$;
DO $$ BEGIN
CREATE TYPE qualification_enum AS ENUM ('whale', 'potential', 'tire_kicker');
EXCEPTION WHEN duplicate_object THEN NULL;
END $$;
DO $$ BEGIN
CREATE TYPE log_event_enum AS ENUM (
'CALL_LOGGED',
'ASSET_VIEWED',
'SENTIMENT_SPIKE',
'QD_UPDATED',
'LEAD_TAGGED',
'WS_ASSET_OPENED'
);
EXCEPTION WHEN duplicate_object THEN NULL;
END $$;
-- ────────────────────────────────────────────────────────────────────────────
-- TABLE: users_and_roles (Manual RBAC backbone)
-- ────────────────────────────────────────────────────────────────────────────
CREATE TABLE IF NOT EXISTS users_and_roles (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email TEXT UNIQUE NOT NULL,
password_hash TEXT NOT NULL,
role role_enum NOT NULL DEFAULT 'JUNIOR_BROKER',
tenant_id TEXT NOT NULL DEFAULT 'tenant_velocity',
full_name TEXT,
avatar_url TEXT,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
last_login TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Index for login lookups
CREATE INDEX IF NOT EXISTS idx_users_email ON users_and_roles (email);
CREATE INDEX IF NOT EXISTS idx_users_tenant_active ON users_and_roles (tenant_id, is_active);
-- ────────────────────────────────────────────────────────────────────────────
-- TABLE: leads_intelligence (CRM core with QD scoring)
-- ────────────────────────────────────────────────────────────────────────────
CREATE TABLE IF NOT EXISTS leads_intelligence (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT NOT NULL,
phone TEXT,
email TEXT,
source source_enum NOT NULL,
status lead_status_enum NOT NULL DEFAULT 'new',
qualification qualification_enum,
budget TEXT,
interest TEXT,
-- Quantum Dynamics Score: 1-100, updated in real-time by NemoClaw
quantum_dynamics_score INTEGER CHECK (quantum_dynamics_score BETWEEN 1 AND 100),
-- Polymorphic CRM intelligence tags e.g. ['HNI', 'NRI', 'Hot Lead']
tags TEXT[] NOT NULL DEFAULT '{}',
assigned_to UUID REFERENCES users_and_roles(id) ON DELETE SET NULL,
last_message TEXT,
last_active TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Auto-update updated_at on every modification
CREATE OR REPLACE FUNCTION set_updated_at()
RETURNS TRIGGER LANGUAGE plpgsql AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$;
DROP TRIGGER IF EXISTS trg_leads_updated_at ON leads_intelligence;
CREATE TRIGGER trg_leads_updated_at
BEFORE UPDATE ON leads_intelligence
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
CREATE INDEX IF NOT EXISTS idx_leads_status ON leads_intelligence (status);
CREATE INDEX IF NOT EXISTS idx_leads_assigned ON leads_intelligence (assigned_to);
-- GIN index for efficient tag array queries (e.g. WHERE 'HNI' = ANY(tags))
CREATE INDEX IF NOT EXISTS idx_leads_tags ON leads_intelligence USING GIN (tags);
-- ────────────────────────────────────────────────────────────────────────────
-- TABLE: velocity_vault_assets (File Tracking Engine)
-- ────────────────────────────────────────────────────────────────────────────
CREATE TABLE IF NOT EXISTS velocity_vault_assets (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
asset_name TEXT NOT NULL,
asset_type TEXT NOT NULL, -- 'pdf', 'image', 'video'
storage_path TEXT NOT NULL, -- relative to /opt/dlami/nvme/assets/
-- Unique cryptographic string for every share instance
tracking_hash VARCHAR(64) UNIQUE NOT NULL,
lead_id UUID REFERENCES leads_intelligence(id) ON DELETE CASCADE,
created_by UUID REFERENCES users_and_roles(id),
-- Array of open timestamps; one entry appended per distinct open event
opened_at TIMESTAMPTZ[] NOT NULL DEFAULT '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_vault_hash ON velocity_vault_assets (tracking_hash);
CREATE INDEX IF NOT EXISTS idx_vault_lead ON velocity_vault_assets (lead_id);
-- ────────────────────────────────────────────────────────────────────────────
-- TABLE: omnichannel_logs (Polymorphic event ingestion + sentimental history)
-- ────────────────────────────────────────────────────────────────────────────
CREATE TABLE IF NOT EXISTS omnichannel_logs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
event_type log_event_enum NOT NULL,
lead_id UUID REFERENCES leads_intelligence(id) ON DELETE CASCADE,
-- JSONB payload — schema varies by event_type:
-- SENTIMENT_SPIKE: {blend_shapes, qd_before, qd_after}
-- WS_ASSET_OPENED: {ip, user_agent, tracking_hash}
-- QD_UPDATED: {qd_score, reasoning, confidence}
-- LEAD_TAGGED: {tags_added, tags_removed}
payload JSONB NOT NULL DEFAULT '{}',
-- For MediaPipe-correlated entries: exact ms offset in the stimulus video
video_timestamp_ms BIGINT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Composite index supporting time-series sentiment history queries
CREATE INDEX IF NOT EXISTS idx_logs_lead_type_time
ON omnichannel_logs (lead_id, event_type, created_at DESC);
-- Partial index for fast SENTIMENT_SPIKE lookups
CREATE INDEX IF NOT EXISTS idx_logs_sentiment_spikes
ON omnichannel_logs (lead_id, created_at DESC)
WHERE event_type = 'SENTIMENT_SPIKE';
-- ────────────────────────────────────────────────────────────────────────────
-- TABLE: consent_log (GDPR biometric consent tracking)
-- ────────────────────────────────────────────────────────────────────────────
CREATE TABLE IF NOT EXISTS consent_log (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
lead_id UUID REFERENCES leads_intelligence(id) ON DELETE CASCADE,
consented_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
ip_address TEXT,
user_agent TEXT,
-- 'granted' or 'revoked'
action TEXT NOT NULL DEFAULT 'granted'
);
CREATE INDEX IF NOT EXISTS idx_consent_lead ON consent_log (lead_id, consented_at DESC);