896 lines
43 KiB
PL/PgSQL
896 lines
43 KiB
PL/PgSQL
-- =============================================================================
|
|
-- schema_crm_canonical.sql
|
|
-- Project Velocity — Canonical CRM and Platform Schema
|
|
-- =============================================================================
|
|
-- Covers: crm_*, intel_*, inventory_*, workflow_* canonical domains
|
|
-- as specified in Doc 09: Database Schema and Root API Spec
|
|
-- and Doc 07: Contracts and Schema Blueprint
|
|
--
|
|
-- Run AFTER schema.sql and schema_addendum.sql
|
|
-- psql -U velocity_user -d velocity_db -f schema_crm_canonical.sql
|
|
--
|
|
-- Existing tables: users_and_roles, leads_intelligence, velocity_vault_assets,
|
|
-- omnichannel_logs, consent_log, video_scene_maps,
|
|
-- perception_sessions, cctv_events, leads, chat_logs
|
|
-- These are treated as legacy feeders per the reconciliation matrix.
|
|
-- =============================================================================
|
|
|
|
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
|
|
CREATE EXTENSION IF NOT EXISTS "pg_trgm";
|
|
|
|
-- ─────────────────────────────────────────────────────────────────────────────
|
|
-- ENUM TYPES — Canonical Domain
|
|
-- ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
DO $$ BEGIN
|
|
CREATE TYPE crm_lead_status AS ENUM (
|
|
'new', 'contacted', 'qualified', 'site_visit_scheduled', 'site_visited',
|
|
'negotiation', 'booking_initiated', 'booked', 'lost', 'dormant'
|
|
);
|
|
EXCEPTION WHEN duplicate_object THEN NULL;
|
|
END $$;
|
|
|
|
DO $$ BEGIN
|
|
CREATE TYPE crm_opportunity_stage AS ENUM (
|
|
'prospect', 'qualified', 'proposal', 'site_visit', 'negotiation',
|
|
'booking', 'agreement', 'closed_won', 'closed_lost'
|
|
);
|
|
EXCEPTION WHEN duplicate_object THEN NULL;
|
|
END $$;
|
|
|
|
DO $$ BEGIN
|
|
CREATE TYPE crm_account_type AS ENUM (
|
|
'individual', 'company', 'broker', 'developer', 'referral_partner', 'nri_family'
|
|
);
|
|
EXCEPTION WHEN duplicate_object THEN NULL;
|
|
END $$;
|
|
|
|
DO $$ BEGIN
|
|
CREATE TYPE crm_relationship_type AS ENUM (
|
|
'spouse', 'parent', 'sibling', 'business_partner', 'broker_referral',
|
|
'co_buyer', 'family_member', 'advisor'
|
|
);
|
|
EXCEPTION WHEN duplicate_object THEN NULL;
|
|
END $$;
|
|
|
|
DO $$ BEGIN
|
|
CREATE TYPE intel_channel AS ENUM (
|
|
'whatsapp', 'phone', 'email', 'site_visit', 'office_meeting',
|
|
'video_call', 'cctv', 'perception_session', 'system'
|
|
);
|
|
EXCEPTION WHEN duplicate_object THEN NULL;
|
|
END $$;
|
|
|
|
DO $$ BEGIN
|
|
CREATE TYPE intel_call_direction AS ENUM ('inbound', 'outbound');
|
|
EXCEPTION WHEN duplicate_object THEN NULL;
|
|
END $$;
|
|
|
|
DO $$ BEGIN
|
|
CREATE TYPE wf_status AS ENUM (
|
|
'pending', 'review_required', 'approved', 'rejected', 'executed', 'failed', 'cancelled'
|
|
);
|
|
EXCEPTION WHEN duplicate_object THEN NULL;
|
|
END $$;
|
|
|
|
DO $$ BEGIN
|
|
CREATE TYPE import_lifecycle AS ENUM (
|
|
'uploaded', 'parsed', 'mapped', 'proposed', 'approved', 'committed', 'failed'
|
|
);
|
|
EXCEPTION WHEN duplicate_object THEN NULL;
|
|
END $$;
|
|
|
|
-- ─────────────────────────────────────────────────────────────────────────────
|
|
-- SECTION 1: CRM CORE DOMAIN (crm_*)
|
|
-- ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
-- TABLE: crm_people
|
|
-- Purpose: canonical person-level contact identity
|
|
CREATE TABLE IF NOT EXISTS crm_people (
|
|
person_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
full_name TEXT NOT NULL,
|
|
primary_email TEXT,
|
|
primary_phone TEXT,
|
|
secondary_phone TEXT,
|
|
linkedin_url TEXT,
|
|
city TEXT,
|
|
nationality TEXT,
|
|
buyer_type TEXT, -- high_intent, slow_burn_investor, nri, etc.
|
|
persona_labels JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
source_confidence FLOAT CHECK (source_confidence BETWEEN 0.0 AND 1.0),
|
|
-- Legacy feeder references (migration linkage)
|
|
legacy_lead_id TEXT, -- links to old leads.id
|
|
legacy_li_id UUID, -- links to leads_intelligence.id
|
|
-- Metadata
|
|
metadata_json JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_crm_people_email ON crm_people (primary_email);
|
|
CREATE INDEX IF NOT EXISTS idx_crm_people_phone ON crm_people (primary_phone);
|
|
CREATE INDEX IF NOT EXISTS idx_crm_people_name_trgm ON crm_people USING GIN (full_name gin_trgm_ops);
|
|
CREATE INDEX IF NOT EXISTS idx_crm_people_buyer_type ON crm_people (buyer_type);
|
|
|
|
-- TABLE: crm_accounts
|
|
-- Purpose: company, employer, brokerage, or client organization
|
|
CREATE TABLE IF NOT EXISTS crm_accounts (
|
|
account_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
account_name TEXT NOT NULL,
|
|
parent_account_id UUID REFERENCES crm_accounts(account_id) ON DELETE SET NULL,
|
|
account_type crm_account_type NOT NULL DEFAULT 'company',
|
|
industry TEXT,
|
|
location_ref TEXT,
|
|
website TEXT,
|
|
metadata_json JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_crm_accounts_name ON crm_accounts (account_name);
|
|
CREATE INDEX IF NOT EXISTS idx_crm_accounts_type ON crm_accounts (account_type);
|
|
|
|
-- TABLE: crm_households
|
|
-- Purpose: family or co-buyer unit grouping
|
|
CREATE TABLE IF NOT EXISTS crm_households (
|
|
household_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
household_name TEXT NOT NULL,
|
|
primary_person_id UUID REFERENCES crm_people(person_id) ON DELETE SET NULL,
|
|
notes TEXT,
|
|
metadata_json JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
-- TABLE: crm_relationships
|
|
-- Purpose: person-to-person relationship graph
|
|
CREATE TABLE IF NOT EXISTS crm_relationships (
|
|
relationship_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
person_a_id UUID NOT NULL REFERENCES crm_people(person_id) ON DELETE CASCADE,
|
|
person_b_id UUID NOT NULL REFERENCES crm_people(person_id) ON DELETE CASCADE,
|
|
relationship_type crm_relationship_type NOT NULL,
|
|
household_id UUID REFERENCES crm_households(household_id) ON DELETE SET NULL,
|
|
notes TEXT,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
UNIQUE (person_a_id, person_b_id, relationship_type)
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_crm_rel_a ON crm_relationships (person_a_id);
|
|
CREATE INDEX IF NOT EXISTS idx_crm_rel_b ON crm_relationships (person_b_id);
|
|
|
|
-- TABLE: crm_leads
|
|
-- Purpose: funnel-stage commercial qualification layer
|
|
CREATE TABLE IF NOT EXISTS crm_leads (
|
|
lead_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
person_id UUID NOT NULL REFERENCES crm_people(person_id) ON DELETE CASCADE,
|
|
account_id UUID REFERENCES crm_accounts(account_id) ON DELETE SET NULL,
|
|
source_system TEXT DEFAULT 'velocity',
|
|
status crm_lead_status NOT NULL DEFAULT 'new',
|
|
budget_band TEXT,
|
|
urgency TEXT, -- low, medium, high, critical
|
|
financing_posture TEXT, -- cash, loan, nri_remittance, emi
|
|
timeline_to_decision TEXT,
|
|
objections JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
motivations JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
assigned_user_id UUID REFERENCES users_and_roles(id) ON DELETE SET NULL,
|
|
-- Legacy feeder
|
|
legacy_lead_id TEXT,
|
|
metadata_json JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_crm_leads_person ON crm_leads (person_id);
|
|
CREATE INDEX IF NOT EXISTS idx_crm_leads_status ON crm_leads (status);
|
|
CREATE INDEX IF NOT EXISTS idx_crm_leads_assigned ON crm_leads (assigned_user_id);
|
|
CREATE INDEX IF NOT EXISTS idx_crm_leads_source ON crm_leads (source_system);
|
|
|
|
-- TABLE: crm_opportunities
|
|
-- Purpose: deal pipeline objects
|
|
CREATE TABLE IF NOT EXISTS crm_opportunities (
|
|
opportunity_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
lead_id UUID NOT NULL REFERENCES crm_leads(lead_id) ON DELETE CASCADE,
|
|
project_id UUID, -- references inventory_projects
|
|
unit_id UUID, -- references inventory_units
|
|
stage crm_opportunity_stage NOT NULL DEFAULT 'prospect',
|
|
value DECIMAL(15, 2),
|
|
probability INTEGER CHECK (probability BETWEEN 0 AND 100),
|
|
expected_close_date DATE,
|
|
next_action TEXT,
|
|
notes TEXT,
|
|
metadata_json JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_crm_opp_lead ON crm_opportunities (lead_id);
|
|
CREATE INDEX IF NOT EXISTS idx_crm_opp_stage ON crm_opportunities (stage);
|
|
CREATE INDEX IF NOT EXISTS idx_crm_opp_project ON crm_opportunities (project_id);
|
|
|
|
-- TABLE: crm_property_interests
|
|
-- Purpose: project and unit interest linking per client
|
|
CREATE TABLE IF NOT EXISTS crm_property_interests (
|
|
interest_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
person_id UUID NOT NULL REFERENCES crm_people(person_id) ON DELETE CASCADE,
|
|
lead_id UUID REFERENCES crm_leads(lead_id) ON DELETE SET NULL,
|
|
project_id UUID,
|
|
project_name TEXT NOT NULL,
|
|
unit_preference TEXT,
|
|
configuration TEXT, -- 2BHK, 3BHK, Penthouse, etc.
|
|
budget_min DECIMAL(15, 2),
|
|
budget_max DECIMAL(15, 2),
|
|
priority INTEGER DEFAULT 1, -- 1 = primary, 2 = secondary
|
|
notes TEXT,
|
|
metadata_json JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
ALTER TABLE crm_property_interests
|
|
ADD COLUMN IF NOT EXISTS metadata_json JSONB NOT NULL DEFAULT '{}'::jsonb;
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_crm_pi_person ON crm_property_interests (person_id);
|
|
CREATE INDEX IF NOT EXISTS idx_crm_pi_project ON crm_property_interests (project_id);
|
|
|
|
-- TABLE: crm_stage_history
|
|
-- Purpose: canonical audit trail of lead stage transitions
|
|
CREATE TABLE IF NOT EXISTS crm_stage_history (
|
|
history_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
lead_id UUID NOT NULL REFERENCES crm_leads(lead_id) ON DELETE CASCADE,
|
|
from_status TEXT,
|
|
to_status TEXT NOT NULL,
|
|
changed_by UUID REFERENCES users_and_roles(id) ON DELETE SET NULL,
|
|
changed_by_type TEXT DEFAULT 'human', -- human, ai, system
|
|
notes TEXT,
|
|
happened_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_crm_stage_lead ON crm_stage_history (lead_id, happened_at DESC);
|
|
|
|
-- ─────────────────────────────────────────────────────────────────────────────
|
|
-- SECTION 2: INTERACTION AND EVIDENCE GRAPH (intel_*)
|
|
-- ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
-- TABLE: intel_interactions
|
|
-- Purpose: umbrella interaction event record
|
|
CREATE TABLE IF NOT EXISTS intel_interactions (
|
|
interaction_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
person_id UUID NOT NULL REFERENCES crm_people(person_id) ON DELETE CASCADE,
|
|
lead_id UUID REFERENCES crm_leads(lead_id) ON DELETE SET NULL,
|
|
channel intel_channel NOT NULL,
|
|
interaction_type TEXT NOT NULL, -- message, call, visit, email, note
|
|
happened_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
summary TEXT,
|
|
source_ref TEXT,
|
|
metadata_json JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_intel_int_person ON intel_interactions (person_id, happened_at DESC);
|
|
CREATE INDEX IF NOT EXISTS idx_intel_int_lead ON intel_interactions (lead_id, happened_at DESC);
|
|
CREATE INDEX IF NOT EXISTS idx_intel_int_channel ON intel_interactions (channel);
|
|
|
|
-- TABLE: intel_messages
|
|
-- Purpose: text-level message records (WhatsApp, chat)
|
|
CREATE TABLE IF NOT EXISTS intel_messages (
|
|
message_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
interaction_id UUID NOT NULL REFERENCES intel_interactions(interaction_id) ON DELETE CASCADE,
|
|
thread_id UUID,
|
|
sender_role TEXT NOT NULL, -- lead, broker, system, oracle
|
|
sender_name TEXT,
|
|
message_text TEXT NOT NULL,
|
|
delivered_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
metadata_json JSONB NOT NULL DEFAULT '{}'::jsonb
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_intel_msg_interaction ON intel_messages (interaction_id, delivered_at DESC);
|
|
|
|
-- TABLE: intel_whatsapp_threads
|
|
-- Purpose: WhatsApp thread-level summaries
|
|
CREATE TABLE IF NOT EXISTS intel_whatsapp_threads (
|
|
thread_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
person_id UUID NOT NULL REFERENCES crm_people(person_id) ON DELETE CASCADE,
|
|
lead_id UUID REFERENCES crm_leads(lead_id) ON DELETE SET NULL,
|
|
phone_number TEXT,
|
|
thread_summary TEXT,
|
|
message_count INTEGER DEFAULT 0,
|
|
last_message_at TIMESTAMPTZ,
|
|
metadata_json JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_intel_wa_person ON intel_whatsapp_threads (person_id);
|
|
|
|
-- TABLE: intel_calls
|
|
-- Purpose: voice call records
|
|
CREATE TABLE IF NOT EXISTS intel_calls (
|
|
call_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
interaction_id UUID NOT NULL REFERENCES intel_interactions(interaction_id) ON DELETE CASCADE,
|
|
call_direction intel_call_direction NOT NULL DEFAULT 'outbound',
|
|
duration_seconds INTEGER,
|
|
recording_ref TEXT, -- storage path or URL to recording
|
|
transcript_ref TEXT, -- path to transcript JSON
|
|
call_outcome TEXT, -- connected, no_answer, voicemail, dropped
|
|
called_number TEXT,
|
|
metadata_json JSONB NOT NULL DEFAULT '{}'::jsonb
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_intel_call_interaction ON intel_calls (interaction_id);
|
|
|
|
-- TABLE: intel_transcripts
|
|
-- Purpose: transcript and speaker segmentation storage
|
|
CREATE TABLE IF NOT EXISTS intel_transcripts (
|
|
transcript_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
call_id UUID REFERENCES intel_calls(call_id) ON DELETE SET NULL,
|
|
interaction_id UUID REFERENCES intel_interactions(interaction_id) ON DELETE SET NULL,
|
|
language TEXT DEFAULT 'en',
|
|
full_text TEXT,
|
|
speaker_segments_json JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
confidence FLOAT CHECK (confidence BETWEEN 0.0 AND 1.0),
|
|
word_count INTEGER,
|
|
metadata_json JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_intel_transcript_call ON intel_transcripts (call_id);
|
|
CREATE INDEX IF NOT EXISTS idx_intel_transcript_interaction ON intel_transcripts (interaction_id);
|
|
|
|
-- TABLE: intel_emails
|
|
-- Purpose: email thread records
|
|
CREATE TABLE IF NOT EXISTS intel_emails (
|
|
email_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
interaction_id UUID NOT NULL REFERENCES intel_interactions(interaction_id) ON DELETE CASCADE,
|
|
from_address TEXT,
|
|
to_addresses JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
subject TEXT,
|
|
body_text TEXT,
|
|
has_attachments BOOLEAN DEFAULT FALSE,
|
|
sent_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
metadata_json JSONB NOT NULL DEFAULT '{}'::jsonb
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_intel_email_interaction ON intel_emails (interaction_id);
|
|
|
|
-- TABLE: intel_visits
|
|
-- Purpose: site visit and meeting records
|
|
CREATE TABLE IF NOT EXISTS intel_visits (
|
|
visit_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
person_id UUID NOT NULL REFERENCES crm_people(person_id) ON DELETE CASCADE,
|
|
lead_id UUID REFERENCES crm_leads(lead_id) ON DELETE SET NULL,
|
|
project_id UUID,
|
|
project_name TEXT,
|
|
unit_id UUID,
|
|
visited_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
visit_notes TEXT,
|
|
host_user_id UUID REFERENCES users_and_roles(id) ON DELETE SET NULL,
|
|
revisit_intent TEXT, -- very_likely, likely, uncertain, unlikely
|
|
cctv_session_ref TEXT,
|
|
perception_session_ref TEXT,
|
|
metadata_json JSONB NOT NULL DEFAULT '{}'::jsonb
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_intel_visits_person ON intel_visits (person_id, visited_at DESC);
|
|
CREATE INDEX IF NOT EXISTS idx_intel_visits_project ON intel_visits (project_id);
|
|
|
|
-- TABLE: intel_reminders
|
|
-- Purpose: reminders and follow-up task chains
|
|
CREATE TABLE IF NOT EXISTS intel_reminders (
|
|
reminder_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
person_id UUID NOT NULL REFERENCES crm_people(person_id) ON DELETE CASCADE,
|
|
lead_id UUID REFERENCES crm_leads(lead_id) ON DELETE SET NULL,
|
|
opportunity_id UUID REFERENCES crm_opportunities(opportunity_id) ON DELETE SET NULL,
|
|
reminder_type TEXT NOT NULL, -- call_back, follow_up, site_visit, document, negotiation
|
|
title TEXT NOT NULL,
|
|
notes TEXT,
|
|
due_at TIMESTAMPTZ,
|
|
completed_at TIMESTAMPTZ,
|
|
status TEXT NOT NULL DEFAULT 'pending', -- pending, done, snoozed, cancelled
|
|
assigned_to UUID REFERENCES users_and_roles(id) ON DELETE SET NULL,
|
|
created_by_type TEXT DEFAULT 'human', -- human, ai, system
|
|
priority TEXT DEFAULT 'normal', -- low, normal, high, urgent
|
|
metadata_json JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_intel_reminder_person ON intel_reminders (person_id, due_at);
|
|
CREATE INDEX IF NOT EXISTS idx_intel_reminder_status ON intel_reminders (status, due_at);
|
|
CREATE INDEX IF NOT EXISTS idx_intel_reminder_assigned ON intel_reminders (assigned_to, due_at);
|
|
|
|
-- TABLE: intel_qd_scores
|
|
-- Purpose: latest meaningful QD summary by client
|
|
CREATE TABLE IF NOT EXISTS intel_qd_scores (
|
|
qd_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
person_id UUID NOT NULL REFERENCES crm_people(person_id) ON DELETE CASCADE,
|
|
score_type TEXT NOT NULL, -- intent_score, urgency_score, engagement_score
|
|
current_value FLOAT NOT NULL CHECK (current_value BETWEEN 0.0 AND 1.0),
|
|
computed_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
evidence_refs_json JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
reasoning TEXT,
|
|
metadata_json JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
UNIQUE (person_id, score_type)
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_intel_qd_person ON intel_qd_scores (person_id);
|
|
|
|
-- TABLE: intel_qd_timeseries
|
|
-- Purpose: time-series QD propagation and shifts
|
|
CREATE TABLE IF NOT EXISTS intel_qd_timeseries (
|
|
timeseries_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
person_id UUID NOT NULL REFERENCES crm_people(person_id) ON DELETE CASCADE,
|
|
score_type TEXT NOT NULL,
|
|
signal_source TEXT,
|
|
timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
value FLOAT NOT NULL CHECK (value BETWEEN 0.0 AND 1.0),
|
|
delta FLOAT,
|
|
evidence_ref TEXT,
|
|
metadata_json JSONB NOT NULL DEFAULT '{}'::jsonb
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_intel_qd_ts_person ON intel_qd_timeseries (person_id, timestamp DESC);
|
|
CREATE INDEX IF NOT EXISTS idx_intel_qd_ts_type ON intel_qd_timeseries (score_type, timestamp DESC);
|
|
|
|
-- TABLE: intel_vehicle_events
|
|
-- Purpose: number-plate and vehicle detection events
|
|
CREATE TABLE IF NOT EXISTS intel_vehicle_events (
|
|
event_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
person_id UUID REFERENCES crm_people(person_id) ON DELETE SET NULL,
|
|
visit_id UUID REFERENCES intel_visits(visit_id) ON DELETE SET NULL,
|
|
zone TEXT,
|
|
license_plate_hash TEXT, -- hashed for privacy
|
|
vehicle_class TEXT, -- luxury, standard, unknown
|
|
wealth_indicator TEXT, -- HNI, standard, unknown
|
|
cctv_ref TEXT,
|
|
captured_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
metadata_json JSONB NOT NULL DEFAULT '{}'::jsonb
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_intel_vehicle_person ON intel_vehicle_events (person_id);
|
|
|
|
-- TABLE: intel_perception_events
|
|
-- Purpose: behavioral and dwell-time intelligence from perception sessions
|
|
CREATE TABLE IF NOT EXISTS intel_perception_events (
|
|
perception_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
person_id UUID REFERENCES crm_people(person_id) ON DELETE SET NULL,
|
|
visit_id UUID REFERENCES intel_visits(visit_id) ON DELETE SET NULL,
|
|
session_ref TEXT, -- perception_sessions.id linkage
|
|
event_type TEXT NOT NULL, -- room_dwell, engagement_spike, exit
|
|
rooms_visited JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
dwell_time_seconds INTEGER,
|
|
engagement_score FLOAT CHECK (engagement_score BETWEEN 0.0 AND 1.0),
|
|
camera_id TEXT,
|
|
media_ref TEXT,
|
|
happened_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
metadata_json JSONB NOT NULL DEFAULT '{}'::jsonb
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_intel_perception_person ON intel_perception_events (person_id);
|
|
|
|
-- TABLE: intel_cctv_links
|
|
-- Purpose: CCTV evidence references linked to client/visit contexts
|
|
CREATE TABLE IF NOT EXISTS intel_cctv_links (
|
|
link_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
person_id UUID REFERENCES crm_people(person_id) ON DELETE SET NULL,
|
|
visit_id UUID REFERENCES intel_visits(visit_id) ON DELETE SET NULL,
|
|
cctv_event_id UUID REFERENCES cctv_events(id) ON DELETE SET NULL,
|
|
clip_ref TEXT,
|
|
camera_zone TEXT,
|
|
confidence FLOAT CHECK (confidence BETWEEN 0.0 AND 1.0),
|
|
linked_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
metadata_json JSONB NOT NULL DEFAULT '{}'::jsonb
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_intel_cctv_person ON intel_cctv_links (person_id);
|
|
|
|
-- ─────────────────────────────────────────────────────────────────────────────
|
|
-- SECTION 3: INVENTORY DOMAIN (inventory_*)
|
|
-- ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
-- TABLE: inventory_projects
|
|
-- Purpose: project-level inventory master
|
|
CREATE TABLE IF NOT EXISTS inventory_projects (
|
|
project_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
project_name TEXT NOT NULL UNIQUE,
|
|
developer_name TEXT NOT NULL,
|
|
city TEXT NOT NULL DEFAULT 'Kolkata',
|
|
micro_market TEXT,
|
|
address TEXT,
|
|
total_units INTEGER,
|
|
rera_number TEXT,
|
|
project_status TEXT DEFAULT 'active', -- active, sold_out, upcoming
|
|
launch_date DATE,
|
|
possession_date DATE,
|
|
location_json JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
amenities_json JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
metadata_json JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_inv_projects_name ON inventory_projects (project_name);
|
|
CREATE INDEX IF NOT EXISTS idx_inv_projects_market ON inventory_projects (micro_market);
|
|
|
|
-- TABLE: inventory_units
|
|
-- Purpose: unit-level availability and attributes
|
|
CREATE TABLE IF NOT EXISTS inventory_units (
|
|
unit_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
project_id UUID NOT NULL REFERENCES inventory_projects(project_id) ON DELETE CASCADE,
|
|
unit_label TEXT NOT NULL,
|
|
configuration TEXT NOT NULL, -- 2BHK, 3BHK, Penthouse, etc.
|
|
area_sqft DECIMAL(10, 2),
|
|
price_current DECIMAL(15, 2),
|
|
price_psf DECIMAL(10, 2),
|
|
status TEXT NOT NULL DEFAULT 'available', -- available, reserved, sold, hold
|
|
floor INTEGER,
|
|
tower TEXT,
|
|
facing TEXT,
|
|
has_attached_amenities JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
metadata_json JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
UNIQUE (project_id, unit_label)
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_inv_units_project ON inventory_units (project_id);
|
|
CREATE INDEX IF NOT EXISTS idx_inv_units_status ON inventory_units (status);
|
|
CREATE INDEX IF NOT EXISTS idx_inv_units_config ON inventory_units (configuration);
|
|
|
|
-- TABLE: inventory_import_jobs
|
|
-- Purpose: track inventory CSV import operations
|
|
CREATE TABLE IF NOT EXISTS inventory_import_jobs (
|
|
job_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
project_id UUID REFERENCES inventory_projects(project_id) ON DELETE SET NULL,
|
|
filename TEXT NOT NULL,
|
|
row_count INTEGER,
|
|
imported_by UUID REFERENCES users_and_roles(id) ON DELETE SET NULL,
|
|
status TEXT NOT NULL DEFAULT 'pending',
|
|
errors_json JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
completed_at TIMESTAMPTZ
|
|
);
|
|
|
|
-- ─────────────────────────────────────────────────────────────────────────────
|
|
-- SECTION 4: AI WORKFLOW AND GOVERNANCE (workflow_*)
|
|
-- ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
-- TABLE: workflow_actions
|
|
-- Purpose: track proposed AI/human actions before approval
|
|
CREATE TABLE IF NOT EXISTS workflow_actions (
|
|
action_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
action_type TEXT NOT NULL, -- import_review, merge_proposal, writeback, enrichment
|
|
target_domain TEXT NOT NULL, -- crm, intel, inventory
|
|
target_entity_ref TEXT,
|
|
proposal_payload JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
reasoning_summary TEXT,
|
|
evidence_refs JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
confidence FLOAT CHECK (confidence BETWEEN 0.0 AND 1.0),
|
|
status wf_status NOT NULL DEFAULT 'pending',
|
|
approval_required BOOLEAN NOT NULL DEFAULT TRUE,
|
|
created_by_agent TEXT,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_wf_actions_status ON workflow_actions (status, created_at DESC);
|
|
CREATE INDEX IF NOT EXISTS idx_wf_actions_domain ON workflow_actions (target_domain);
|
|
|
|
-- TABLE: workflow_approvals
|
|
-- Purpose: explicit human review decisions
|
|
CREATE TABLE IF NOT EXISTS workflow_approvals (
|
|
decision_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
action_id UUID NOT NULL REFERENCES workflow_actions(action_id) ON DELETE CASCADE,
|
|
reviewer_id UUID REFERENCES users_and_roles(id) ON DELETE SET NULL,
|
|
decision TEXT NOT NULL, -- approved, rejected, needs_more_info
|
|
decision_notes TEXT,
|
|
decided_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_wf_approvals_action ON workflow_approvals (action_id);
|
|
|
|
-- TABLE: workflow_writebacks
|
|
-- Purpose: track AI-suggested and approved canonical mutations
|
|
CREATE TABLE IF NOT EXISTS workflow_writebacks (
|
|
writeback_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
action_id UUID REFERENCES workflow_actions(action_id) ON DELETE SET NULL,
|
|
approval_id UUID REFERENCES workflow_approvals(decision_id) ON DELETE SET NULL,
|
|
target_domain TEXT NOT NULL,
|
|
target_entity_ref TEXT NOT NULL,
|
|
change_payload JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
status wf_status NOT NULL DEFAULT 'pending',
|
|
approved_by UUID REFERENCES users_and_roles(id) ON DELETE SET NULL,
|
|
executed_at TIMESTAMPTZ,
|
|
error_detail TEXT,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_wf_wb_status ON workflow_writebacks (status, created_at DESC);
|
|
CREATE INDEX IF NOT EXISTS idx_wf_wb_domain ON workflow_writebacks (target_domain);
|
|
|
|
-- TABLE: workflow_import_batches
|
|
-- Purpose: CRM import batch lifecycle tracking (RawImportBatch contract)
|
|
CREATE TABLE IF NOT EXISTS workflow_import_batches (
|
|
batch_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
source_system TEXT NOT NULL, -- csv_upload, salesforce, hubspot, manual
|
|
uploaded_filename TEXT,
|
|
mime_type TEXT DEFAULT 'text/csv',
|
|
storage_ref TEXT,
|
|
row_count INTEGER,
|
|
mapped_count INTEGER DEFAULT 0,
|
|
unresolved_count INTEGER DEFAULT 0,
|
|
canonical_count INTEGER DEFAULT 0,
|
|
uploaded_by UUID REFERENCES users_and_roles(id) ON DELETE SET NULL,
|
|
lifecycle import_lifecycle NOT NULL DEFAULT 'uploaded',
|
|
mapping_manifest JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
errors_json JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
metadata_json JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_wf_import_lifecycle ON workflow_import_batches (lifecycle, created_at DESC);
|
|
CREATE INDEX IF NOT EXISTS idx_wf_import_user ON workflow_import_batches (uploaded_by);
|
|
|
|
-- TABLE: workflow_agent_runs
|
|
-- Purpose: track NemoClaw and AI agent invocation logs
|
|
CREATE TABLE IF NOT EXISTS workflow_agent_runs (
|
|
run_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
agent_name TEXT NOT NULL, -- nemoclaw, import_mapper, enrichment_engine
|
|
trigger_type TEXT NOT NULL, -- import, enrichment, qd_update, writeback
|
|
trigger_ref TEXT,
|
|
input_payload JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
output_payload JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
status TEXT NOT NULL DEFAULT 'running', -- running, completed, failed
|
|
duration_ms INTEGER,
|
|
error_detail TEXT,
|
|
started_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
completed_at TIMESTAMPTZ
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_wf_agent_runs_agent ON workflow_agent_runs (agent_name, started_at DESC);
|
|
CREATE INDEX IF NOT EXISTS idx_wf_agent_runs_status ON workflow_agent_runs (status);
|
|
|
|
-- ─────────────────────────────────────────────────────────────────────────────
|
|
-- TRIGGERS: auto-update updated_at
|
|
-- ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
CREATE OR REPLACE FUNCTION set_canonical_updated_at()
|
|
RETURNS TRIGGER LANGUAGE plpgsql AS $$
|
|
BEGIN
|
|
NEW.updated_at = NOW();
|
|
RETURN NEW;
|
|
END;
|
|
$$;
|
|
|
|
DO $$ DECLARE
|
|
t TEXT;
|
|
BEGIN
|
|
FOREACH t IN ARRAY ARRAY[
|
|
'crm_people', 'crm_accounts', 'crm_leads', 'crm_opportunities',
|
|
'inventory_projects', 'inventory_units',
|
|
'workflow_actions', 'workflow_import_batches'
|
|
] LOOP
|
|
EXECUTE format(
|
|
'DROP TRIGGER IF EXISTS trg_%s_updated_at ON %s;
|
|
CREATE TRIGGER trg_%s_updated_at
|
|
BEFORE UPDATE ON %s
|
|
FOR EACH ROW EXECUTE FUNCTION set_canonical_updated_at();',
|
|
t, t, t, t
|
|
);
|
|
END LOOP;
|
|
END $$;
|
|
|
|
-- ─────────────────────────────────────────────────────────────────────────────
|
|
-- INVENTORY SEED: 14 Canonical Kolkata Projects
|
|
-- ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
INSERT INTO inventory_projects (project_id, project_name, developer_name, city, micro_market)
|
|
VALUES
|
|
(gen_random_uuid(), 'Eden Devprayag', 'Eden Group', 'Kolkata', 'Rajarhat'),
|
|
(gen_random_uuid(), 'Sugam Prakriti', 'Sugam Homes', 'Kolkata', 'Barasat'),
|
|
(gen_random_uuid(), 'Atri Aqua', 'Atri Developers', 'Kolkata', 'New Town'),
|
|
(gen_random_uuid(), 'Atri Surya Toron', 'Atri Developers', 'Kolkata', 'Rajarhat'),
|
|
(gen_random_uuid(), 'Siddha Suburbia Bungalow', 'Siddha Group', 'Kolkata', 'Madanpur'),
|
|
(gen_random_uuid(), 'Merlin Avana', 'Merlin Group', 'Kolkata', 'Tangra'),
|
|
(gen_random_uuid(), 'DTC Good Earth', 'DTC Projects', 'Kolkata', 'New Town'),
|
|
(gen_random_uuid(), 'Siddha Serena', 'Siddha Group', 'Kolkata', 'New Town'),
|
|
(gen_random_uuid(), 'Siddha Sky Waterfront', 'Siddha Group', 'Kolkata', 'Beliaghata'),
|
|
(gen_random_uuid(), 'Godrej Blue', 'Godrej Properties', 'Kolkata', 'New Town'),
|
|
(gen_random_uuid(), 'DTC Sojon', 'DTC Projects', 'Kolkata', 'Rajarhat'),
|
|
(gen_random_uuid(), 'Shriram Grand City', 'Shriram Properties', 'Kolkata', 'Howrah'),
|
|
(gen_random_uuid(), 'Godrej Elevate', 'Godrej Properties', 'Kolkata', 'Dum Dum'),
|
|
(gen_random_uuid(), 'Ambuja Utpaala', 'Ambuja Neotia', 'Kolkata', 'Tollygunge')
|
|
ON CONFLICT (project_name) DO NOTHING;
|
|
|
|
-- ─────────────────────────────────────────────────────────────────────────────
|
|
-- COMMENTS
|
|
-- ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
COMMENT ON TABLE crm_people IS 'Canonical person-level contact identity. Primary join key across all CRM tables.';
|
|
COMMENT ON TABLE crm_leads IS 'Funnel-stage commercial qualification. One person may have multiple lead contexts.';
|
|
COMMENT ON TABLE crm_opportunities IS 'Deal pipeline objects linked to leads and inventory.';
|
|
COMMENT ON TABLE intel_interactions IS 'Umbrella interaction event. All channels (WhatsApp, call, email, visit) link here.';
|
|
COMMENT ON TABLE intel_transcripts IS 'Speaker-segmented call transcripts. speaker_segments_json is first-class data.';
|
|
COMMENT ON TABLE intel_qd_scores IS 'Latest QD summary by score_type per client. UNIQUE constraint enforces one row per type.';
|
|
COMMENT ON TABLE inventory_projects IS 'Master project catalog. 14 canonical Kolkata projects seeded.';
|
|
COMMENT ON TABLE workflow_import_batches IS 'RawImportBatch contract. Immutable after upload.';
|
|
COMMENT ON TABLE workflow_writebacks IS 'AI-proposed canonical mutations. Never auto-execute without approval.';
|
|
|
|
-- -----------------------------------------------------------------------------
|
|
-- Synthetic CRM v2 enrichment columns and Oracle read models
|
|
-- -----------------------------------------------------------------------------
|
|
|
|
ALTER TABLE crm_people
|
|
ADD COLUMN IF NOT EXISTS broker_id TEXT,
|
|
ADD COLUMN IF NOT EXISTS broker_name TEXT,
|
|
ADD COLUMN IF NOT EXISTS engagement_score FLOAT,
|
|
ADD COLUMN IF NOT EXISTS communication_preference TEXT,
|
|
ADD COLUMN IF NOT EXISTS best_contact_time TEXT;
|
|
|
|
ALTER TABLE crm_households
|
|
ADD COLUMN IF NOT EXISTS size INTEGER,
|
|
ADD COLUMN IF NOT EXISTS combined_budget_band TEXT,
|
|
ADD COLUMN IF NOT EXISTS decision_maker_id UUID REFERENCES crm_people(person_id) ON DELETE SET NULL;
|
|
|
|
ALTER TABLE crm_leads
|
|
ADD COLUMN IF NOT EXISTS stage TEXT,
|
|
ADD COLUMN IF NOT EXISTS broker_name TEXT,
|
|
ADD COLUMN IF NOT EXISTS broker_team TEXT,
|
|
ADD COLUMN IF NOT EXISTS engagement_score FLOAT,
|
|
ADD COLUMN IF NOT EXISTS last_activity_at TIMESTAMPTZ;
|
|
|
|
ALTER TABLE crm_opportunities
|
|
ADD COLUMN IF NOT EXISTS broker_id TEXT,
|
|
ADD COLUMN IF NOT EXISTS broker_name TEXT,
|
|
ADD COLUMN IF NOT EXISTS deal_velocity TEXT,
|
|
ADD COLUMN IF NOT EXISTS risk_factors JSONB NOT NULL DEFAULT '[]'::jsonb;
|
|
|
|
ALTER TABLE crm_property_interests
|
|
ADD COLUMN IF NOT EXISTS broker_id TEXT,
|
|
ADD COLUMN IF NOT EXISTS broker_name TEXT,
|
|
ADD COLUMN IF NOT EXISTS last_discussed_at TIMESTAMPTZ;
|
|
|
|
ALTER TABLE crm_stage_history
|
|
ADD COLUMN IF NOT EXISTS broker_name TEXT,
|
|
ADD COLUMN IF NOT EXISTS transition_duration_days INTEGER;
|
|
|
|
ALTER TABLE intel_interactions
|
|
ADD COLUMN IF NOT EXISTS broker_id TEXT,
|
|
ADD COLUMN IF NOT EXISTS broker_name TEXT,
|
|
ADD COLUMN IF NOT EXISTS broker_team TEXT,
|
|
ADD COLUMN IF NOT EXISTS sentiment TEXT,
|
|
ADD COLUMN IF NOT EXISTS sentiment_score FLOAT,
|
|
ADD COLUMN IF NOT EXISTS intent_label TEXT,
|
|
ADD COLUMN IF NOT EXISTS emotion_tags JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
ADD COLUMN IF NOT EXISTS client_engagement_level TEXT;
|
|
|
|
ALTER TABLE intel_calls
|
|
ADD COLUMN IF NOT EXISTS objection_tags JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
ADD COLUMN IF NOT EXISTS outcome_summary TEXT,
|
|
ADD COLUMN IF NOT EXISTS follow_up_actions JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
ADD COLUMN IF NOT EXISTS broker_id TEXT,
|
|
ADD COLUMN IF NOT EXISTS broker_name TEXT,
|
|
ADD COLUMN IF NOT EXISTS call_quality_score FLOAT;
|
|
|
|
ALTER TABLE intel_emails
|
|
ADD COLUMN IF NOT EXISTS sentiment TEXT,
|
|
ADD COLUMN IF NOT EXISTS intent_label TEXT,
|
|
ADD COLUMN IF NOT EXISTS engagement_score FLOAT,
|
|
ADD COLUMN IF NOT EXISTS broker_id TEXT,
|
|
ADD COLUMN IF NOT EXISTS broker_name TEXT,
|
|
ADD COLUMN IF NOT EXISTS response_expected BOOLEAN;
|
|
|
|
ALTER TABLE intel_reminders
|
|
ADD COLUMN IF NOT EXISTS interaction_id UUID REFERENCES intel_interactions(interaction_id) ON DELETE SET NULL,
|
|
ADD COLUMN IF NOT EXISTS context_snippet TEXT,
|
|
ADD COLUMN IF NOT EXISTS completion_percentage INTEGER,
|
|
ADD COLUMN IF NOT EXISTS overdue_days INTEGER,
|
|
ADD COLUMN IF NOT EXISTS outcome_notes TEXT;
|
|
|
|
ALTER TABLE intel_transcripts
|
|
ADD COLUMN IF NOT EXISTS call_outcome TEXT,
|
|
ADD COLUMN IF NOT EXISTS follow_up_required BOOLEAN,
|
|
ADD COLUMN IF NOT EXISTS emotion_tags JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
ADD COLUMN IF NOT EXISTS call_summary TEXT;
|
|
|
|
ALTER TABLE intel_visits
|
|
ADD COLUMN IF NOT EXISTS outcome_type TEXT,
|
|
ADD COLUMN IF NOT EXISTS visit_duration_minutes INTEGER,
|
|
ADD COLUMN IF NOT EXISTS interest_signals JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
ADD COLUMN IF NOT EXISTS interest_score FLOAT,
|
|
ADD COLUMN IF NOT EXISTS companion_type TEXT,
|
|
ADD COLUMN IF NOT EXISTS companion_count INTEGER,
|
|
ADD COLUMN IF NOT EXISTS objections_raised JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
ADD COLUMN IF NOT EXISTS follow_up_required BOOLEAN,
|
|
ADD COLUMN IF NOT EXISTS next_steps TEXT,
|
|
ADD COLUMN IF NOT EXISTS broker_notes TEXT,
|
|
ADD COLUMN IF NOT EXISTS broker_id TEXT,
|
|
ADD COLUMN IF NOT EXISTS broker_name TEXT;
|
|
|
|
ALTER TABLE intel_whatsapp_threads
|
|
ADD COLUMN IF NOT EXISTS broker_id TEXT,
|
|
ADD COLUMN IF NOT EXISTS broker_name TEXT,
|
|
ADD COLUMN IF NOT EXISTS topic_category TEXT,
|
|
ADD COLUMN IF NOT EXISTS sentiment_direction TEXT,
|
|
ADD COLUMN IF NOT EXISTS resolution_status TEXT;
|
|
|
|
ALTER TABLE intel_qd_scores
|
|
ADD COLUMN IF NOT EXISTS score_drivers JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
ADD COLUMN IF NOT EXISTS trend_direction TEXT,
|
|
ADD COLUMN IF NOT EXISTS explanation TEXT,
|
|
ADD COLUMN IF NOT EXISTS confidence FLOAT;
|
|
|
|
ALTER TABLE intel_qd_timeseries
|
|
ADD COLUMN IF NOT EXISTS broker_id TEXT;
|
|
|
|
CREATE TABLE IF NOT EXISTS intel_email_threads (
|
|
thread_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
subject TEXT,
|
|
first_email_at TIMESTAMPTZ,
|
|
last_email_at TIMESTAMPTZ,
|
|
email_count INTEGER DEFAULT 0,
|
|
participants JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
status TEXT,
|
|
broker_id TEXT,
|
|
metadata_json JSONB NOT NULL DEFAULT '{}'::jsonb
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS intel_call_objections (
|
|
objection_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
call_id UUID REFERENCES intel_calls(call_id) ON DELETE CASCADE,
|
|
objection_type TEXT,
|
|
category TEXT,
|
|
severity TEXT,
|
|
status TEXT,
|
|
client_quote TEXT,
|
|
agent_response TEXT,
|
|
resolution_strategy TEXT,
|
|
extracted_at TIMESTAMPTZ,
|
|
confidence_score FLOAT,
|
|
metadata_json JSONB NOT NULL DEFAULT '{}'::jsonb
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS intel_extracted_facts (
|
|
fact_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
interaction_id UUID REFERENCES intel_interactions(interaction_id) ON DELETE SET NULL,
|
|
person_id UUID REFERENCES crm_people(person_id) ON DELETE CASCADE,
|
|
fact_type TEXT NOT NULL,
|
|
fact_value TEXT,
|
|
confidence FLOAT,
|
|
extracted_from TEXT,
|
|
source_context TEXT,
|
|
extracted_at TIMESTAMPTZ,
|
|
metadata_json JSONB NOT NULL DEFAULT '{}'::jsonb
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS read_last_contacted (
|
|
person_id UUID PRIMARY KEY REFERENCES crm_people(person_id) ON DELETE CASCADE,
|
|
last_contact_at TIMESTAMPTZ,
|
|
last_channel TEXT,
|
|
last_interaction_type TEXT,
|
|
days_since_contact INTEGER,
|
|
interactions_last_7d INTEGER,
|
|
interactions_last_30d INTEGER,
|
|
interactions_last_90d INTEGER,
|
|
total_interactions INTEGER,
|
|
current_stage TEXT,
|
|
broker_id TEXT,
|
|
broker_name TEXT,
|
|
computed_at TIMESTAMPTZ,
|
|
metadata_json JSONB NOT NULL DEFAULT '{}'::jsonb
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS read_next_best_action (
|
|
person_id UUID PRIMARY KEY REFERENCES crm_people(person_id) ON DELETE CASCADE,
|
|
recommended_action TEXT,
|
|
priority TEXT,
|
|
rationale TEXT,
|
|
suggested_channel TEXT,
|
|
due_within_days INTEGER,
|
|
broker_id TEXT,
|
|
broker_name TEXT,
|
|
opportunity_context TEXT,
|
|
computed_at TIMESTAMPTZ,
|
|
metadata_json JSONB NOT NULL DEFAULT '{}'::jsonb
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_read_last_contacted_at ON read_last_contacted (last_contact_at DESC);
|
|
CREATE INDEX IF NOT EXISTS idx_read_next_best_priority ON read_next_best_action (priority);
|
|
CREATE INDEX IF NOT EXISTS idx_intel_facts_person ON intel_extracted_facts (person_id, extracted_at DESC);
|
|
CREATE INDEX IF NOT EXISTS idx_intel_objections_call ON intel_call_objections (call_id);
|