File size: 11,515 Bytes
bd28470
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
-- ============================================================
-- AI Client Acquisition System β€” Supabase Schema
-- Run this in Supabase SQL Editor
-- ============================================================

-- Enable pgcrypto for UUID generation
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
CREATE EXTENSION IF NOT EXISTS "pg_trgm"; -- for fuzzy name matching

-- ─── ENUMS ──────────────────────────────────────────────────

CREATE TYPE company_status AS ENUM (
  'discovered', 'researching', 'profiled',
  'qualified', 'nurture', 'archived', 'suppressed'
);

CREATE TYPE contact_status AS ENUM (
  'found', 'email_verified', 'email_invalid',
  'linkedin_only', 'suppressed'
);

CREATE TYPE lead_tier AS ENUM ('hot', 'warm', 'nurture', 'archive');

CREATE TYPE outreach_channel AS ENUM ('email', 'linkedin');

CREATE TYPE outreach_status AS ENUM (
  'queued', 'sent', 'opened', 'replied',
  'bounced', 'failed', 'review_needed'
);

CREATE TYPE intent_type AS ENUM (
  'interested', 'question', 'not_now',
  'not_interested', 'out_of_office', 'wrong_person', 'unknown'
);

CREATE TYPE review_status AS ENUM ('pending', 'approved', 'rejected', 'edited');

-- ─── CORE TABLES ────────────────────────────────────────────

-- ICP Configuration (editable from dashboard)
CREATE TABLE icp_config (
  id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  name        TEXT NOT NULL DEFAULT 'default',
  min_employees INTEGER NOT NULL DEFAULT 50,
  industries  TEXT[] NOT NULL DEFAULT ARRAY['technology','manufacturing','logistics','healthcare','finance'],
  exclude_industries TEXT[] NOT NULL DEFAULT ARRAY['government','non-profit','education'],
  geographies TEXT[] NOT NULL DEFAULT ARRAY['US','UK','AU','UAE','SA'],
  keywords    TEXT[] NOT NULL DEFAULT ARRAY['automation','digital transformation','AI','operations'],
  tech_signals TEXT[] DEFAULT ARRAY['salesforce','hubspot','legacy_erp','sap'],
  score_threshold INTEGER NOT NULL DEFAULT 70,
  is_active   BOOLEAN NOT NULL DEFAULT true,
  created_at  TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  updated_at  TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- Weekly rotation state
CREATE TABLE rotation_state (
  id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  week_number INTEGER NOT NULL,          -- 1=USA, 2=UK, 3=AU, 4=Gulf
  region      TEXT NOT NULL,
  started_at  TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  completed_at TIMESTAMPTZ,
  companies_found INTEGER DEFAULT 0,
  leads_qualified INTEGER DEFAULT 0
);

-- Companies discovered
CREATE TABLE companies (
  id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  domain          TEXT NOT NULL UNIQUE,
  name            TEXT NOT NULL,
  industry        TEXT,
  employee_count  INTEGER,
  employee_range  TEXT,                  -- "50-200", "200-500" etc
  description     TEXT,
  website_url     TEXT,
  linkedin_url    TEXT,
  country         TEXT,
  region          TEXT,
  tech_stack      TEXT[],
  growth_signals  JSONB DEFAULT '[]',    -- job posts, news, funding
  raw_data        JSONB DEFAULT '{}',
  source          TEXT NOT NULL,         -- 'serper', 'linkedin', 'manual'
  status          company_status NOT NULL DEFAULT 'discovered',
  discovered_at   TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  updated_at      TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_companies_domain ON companies(domain);
CREATE INDEX idx_companies_status ON companies(status);
CREATE INDEX idx_companies_country ON companies(country);
CREATE INDEX idx_companies_name_trgm ON companies USING GIN (name gin_trgm_ops);

-- Contacts (decision-makers)
CREATE TABLE contacts (
  id                UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  company_id        UUID NOT NULL REFERENCES companies(id) ON DELETE CASCADE,
  full_name         TEXT NOT NULL,
  first_name        TEXT,
  last_name         TEXT,
  title             TEXT NOT NULL,
  seniority         TEXT,               -- 'c_suite','vp','director','manager'
  email             TEXT,
  email_verified    BOOLEAN DEFAULT FALSE,
  email_source      TEXT,              -- 'hunter','snov','pattern'
  linkedin_url      TEXT,
  linkedin_verified BOOLEAN DEFAULT FALSE,
  status            contact_status NOT NULL DEFAULT 'found',
  suppressed        BOOLEAN NOT NULL DEFAULT FALSE,
  suppressed_at     TIMESTAMPTZ,
  suppressed_reason TEXT,
  created_at        TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  updated_at        TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_contacts_company ON contacts(company_id);
CREATE INDEX idx_contacts_email ON contacts(email);
CREATE INDEX idx_contacts_suppressed ON contacts(suppressed);

-- Evidence gathered during research
CREATE TABLE evidence (
  id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  company_id  UUID NOT NULL REFERENCES companies(id) ON DELETE CASCADE,
  type        TEXT NOT NULL,            -- 'job_posting','news','social_post','website_text'
  content     TEXT NOT NULL,
  source_url  TEXT,
  ai_signal   BOOLEAN DEFAULT FALSE,   -- does this mention AI/automation?
  collected_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_evidence_company ON evidence(company_id);

-- Lead profiles (LLM output)
CREATE TABLE lead_profiles (
  id                  UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  company_id          UUID NOT NULL REFERENCES companies(id) ON DELETE CASCADE,
  profile_summary     TEXT NOT NULL,
  pain_points         TEXT[] DEFAULT '{}',
  ai_use_case         TEXT,
  ai_readiness        TEXT NOT NULL DEFAULT 'medium', -- low/medium/high
  outreach_angle      TEXT,
  llm_model           TEXT NOT NULL,
  llm_confidence      NUMERIC(3,2),
  is_fallback         BOOLEAN DEFAULT FALSE,
  created_at          TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- Lead scores
CREATE TABLE lead_scores (
  id                  UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  company_id          UUID NOT NULL REFERENCES companies(id) ON DELETE CASCADE,
  contact_id          UUID REFERENCES contacts(id),
  total_score         INTEGER NOT NULL CHECK (total_score BETWEEN 0 AND 100),
  tier                lead_tier NOT NULL,
  company_fit         INTEGER,          -- 0-25
  ai_readiness        INTEGER,          -- 0-25
  decision_maker      INTEGER,          -- 0-20
  growth_signal       INTEGER,          -- 0-15
  engagement_potential INTEGER,         -- 0-15
  score_reasoning     TEXT,
  scored_at           TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_scores_company ON lead_scores(company_id);
CREATE INDEX idx_scores_tier ON lead_scores(tier);
CREATE INDEX idx_scores_total ON lead_scores(total_score DESC);

-- ─── OUTREACH TABLES ────────────────────────────────────────

CREATE TABLE outreach_sequences (
  id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  company_id      UUID NOT NULL REFERENCES companies(id) ON DELETE CASCADE,
  contact_id      UUID NOT NULL REFERENCES contacts(id),
  current_step    INTEGER NOT NULL DEFAULT 0,
  total_steps     INTEGER NOT NULL DEFAULT 5,
  next_action_at  TIMESTAMPTZ,
  status          TEXT NOT NULL DEFAULT 'active', -- active/paused/completed/stopped
  stopped_reason  TEXT,
  created_at      TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  updated_at      TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE TABLE outreach_log (
  id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  sequence_id     UUID REFERENCES outreach_sequences(id),
  company_id      UUID NOT NULL REFERENCES companies(id),
  contact_id      UUID NOT NULL REFERENCES contacts(id),
  channel         outreach_channel NOT NULL,
  step_number     INTEGER NOT NULL,
  template_id     TEXT,
  message_hash    TEXT NOT NULL UNIQUE,  -- prevent duplicate sends
  subject         TEXT,
  status          outreach_status NOT NULL DEFAULT 'queued',
  provider_id     TEXT,                  -- external message ID from Resend/LinkedIn
  sent_at         TIMESTAMPTZ,
  opened_at       TIMESTAMPTZ,
  replied_at      TIMESTAMPTZ,
  bounced_at      TIMESTAMPTZ,
  created_at      TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_outreach_company ON outreach_log(company_id);
CREATE INDEX idx_outreach_hash ON outreach_log(message_hash);
CREATE INDEX idx_outreach_status ON outreach_log(status);

-- ─── ENGAGEMENT TABLES ──────────────────────────────────────

CREATE TABLE engagement_log (
  id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  outreach_id     UUID REFERENCES outreach_log(id),
  company_id      UUID NOT NULL REFERENCES companies(id),
  contact_id      UUID NOT NULL REFERENCES contacts(id),
  signal_type     TEXT NOT NULL,         -- 'open','reply','bounce','linkedin_accept'
  intent          intent_type,
  raw_content     TEXT,                  -- actual reply text (for NLP)
  detected_at     TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- ─── SYSTEM TABLES ──────────────────────────────────────────

CREATE TABLE suppression_list (
  id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  email       TEXT,
  domain      TEXT,
  reason      TEXT NOT NULL,             -- 'unsubscribed','bounced','manual'
  added_at    TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_suppression_email ON suppression_list(email);
CREATE INDEX idx_suppression_domain ON suppression_list(domain);

CREATE TABLE human_review_queue (
  id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  type            TEXT NOT NULL,         -- 'outreach_approval','score_anomaly','escalation'
  company_id      UUID REFERENCES companies(id),
  contact_id      UUID REFERENCES contacts(id),
  payload         JSONB NOT NULL,        -- full context for reviewer
  status          review_status NOT NULL DEFAULT 'pending',
  reviewer_notes  TEXT,
  resolved_at     TIMESTAMPTZ,
  created_at      TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_review_status ON human_review_queue(status);

CREATE TABLE api_usage_log (
  id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  provider    TEXT NOT NULL,             -- 'serper','hunter','snov','reoon'
  endpoint    TEXT,
  credits_used INTEGER DEFAULT 1,
  success     BOOLEAN NOT NULL,
  error_msg   TEXT,
  called_at   TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE TABLE audit_log (
  id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  action      TEXT NOT NULL,
  entity_type TEXT NOT NULL,
  entity_id   UUID,
  actor       TEXT NOT NULL DEFAULT 'system',
  details     JSONB DEFAULT '{}',
  created_at  TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- ─── SEED DATA ──────────────────────────────────────────────

INSERT INTO icp_config (name, min_employees, industries, geographies, keywords, score_threshold)
VALUES (
  'default',
  50,
  ARRAY['technology','software','manufacturing','logistics','supply_chain','healthcare','finance','real_estate_tech','retail_tech'],
  ARRAY['US','UK','AU','UAE','SA','SG'],
  ARRAY['automation','digital transformation','AI','machine learning','operations','workflow','efficiency'],
  70
);

INSERT INTO rotation_state (week_number, region)
VALUES (1, 'US');