forked from sagnik/Project_Velocity
Co-authored-by: Sayan Datta <sayan@Sayans-MacBook-Air.local> Reviewed-on: sagnik/Project_Velocity#41
182 lines
9.0 KiB
PL/PgSQL
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);
|