File size: 11,775 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
-- ============================================================
-- Migration 002 β€” Phase 1 Enhancements
-- Territory management, service profiles, social profiles,
-- discovery run tracking, pipeline checkpoints, LLM traces
-- ============================================================

-- ─── SERVICE PROFILES ────────────────────────────────────────
-- What services WE offer β†’ what industries β†’ what pain signals to look for

CREATE TABLE service_profiles (
  id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  service_name TEXT NOT NULL UNIQUE,          -- 'AI Receptionist'
  description TEXT,
  target_industries TEXT[] NOT NULL,          -- ['dental','medical','legal','salon']
  min_employees INTEGER DEFAULT 3,
  max_employees INTEGER DEFAULT 500,
  pain_signals TEXT[] NOT NULL,              -- website signals to detect
  score_boost INTEGER NOT NULL DEFAULT 15,   -- points added when matched
  outreach_keywords TEXT[],                  -- words to use in outreach
  is_active   BOOLEAN DEFAULT true,
  created_at  TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- Seed service profiles
INSERT INTO service_profiles (service_name, description, target_industries, min_employees, pain_signals, score_boost, outreach_keywords)
VALUES
  ('AI Receptionist',
   'Automated phone answering, appointment booking, 24/7 availability',
   ARRAY['dental','medical','veterinary','legal','salon','spa','real_estate','accounting','chiropractic'],
   3, 
   ARRAY['phone number prominent','book appointment','call us','receptionist','front desk','office hours','schedule a visit'],
   20,
   ARRAY['missed calls','after hours','appointment booking','front desk costs']),
  
  ('AI Customer Support',
   'Chatbot, ticket automation, FAQ automation',
   ARRAY['ecommerce','saas','retail','hospitality','travel','insurance','telecom'],
   10,
   ARRAY['contact form','support email','FAQ page','help center','no chatbot','submit a ticket'],
   15,
   ARRAY['support costs','response time','ticket volume','customer satisfaction']),
  
  ('AI Data Processing',
   'Document processing, report automation, ERP modernization',
   ARRAY['manufacturing','logistics','finance','healthcare','supply_chain','construction','energy'],
   50,
   ARRAY['legacy ERP','SAP','manual reporting','spreadsheet','data entry','compliance reporting'],
   25,
   ARRAY['manual processes','reporting overhead','data accuracy','compliance automation']),
  
  ('AI Sales Automation',
   'Outreach automation, CRM enrichment, lead scoring',
   ARRAY['b2b_saas','consulting','recruitment','insurance','financial_services','marketing_agency'],
   10,
   ARRAY['sales team','CRM','outbound','SDR','BDR','sales development','pipeline'],
   20,
   ARRAY['pipeline velocity','lead qualification','outbound efficiency','sales productivity']),
  
  ('AI Workflow Automation',
   'General process automation, integration, workflow optimization',
   ARRAY['technology','professional_services','education','media','nonprofit_large','government_contractor'],
   20,
   ARRAY['manual process','approval workflow','internal tools','legacy system','multiple platforms'],
   15,
   ARRAY['operational efficiency','process bottlenecks','tool consolidation','workflow speed']);


-- ─── TERRITORY GRID ──────────────────────────────────────────
-- Every city Γ— industry = one territory unit

CREATE TABLE territory_grid (
  id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  country     TEXT NOT NULL,
  country_code TEXT NOT NULL,
  city        TEXT NOT NULL,
  tier        INTEGER NOT NULL DEFAULT 1,   -- 1=major city, 2=mid, 3=small
  timezone    TEXT,                          -- 'America/New_York'
  is_active   BOOLEAN DEFAULT true,
  
  UNIQUE(country_code, city)
);

-- Seed US Tier 1 cities
INSERT INTO territory_grid (country, country_code, city, tier, timezone) VALUES
  ('United States', 'US', 'New York', 1, 'America/New_York'),
  ('United States', 'US', 'Los Angeles', 1, 'America/Los_Angeles'),
  ('United States', 'US', 'Chicago', 1, 'America/Chicago'),
  ('United States', 'US', 'Houston', 1, 'America/Chicago'),
  ('United States', 'US', 'Phoenix', 1, 'America/Phoenix'),
  ('United States', 'US', 'Philadelphia', 1, 'America/New_York'),
  ('United States', 'US', 'San Antonio', 1, 'America/Chicago'),
  ('United States', 'US', 'San Diego', 1, 'America/Los_Angeles'),
  ('United States', 'US', 'Dallas', 1, 'America/Chicago'),
  ('United States', 'US', 'Austin', 1, 'America/Chicago'),
  ('United States', 'US', 'San Francisco', 1, 'America/Los_Angeles'),
  ('United States', 'US', 'Seattle', 1, 'America/Los_Angeles'),
  ('United States', 'US', 'Denver', 1, 'America/Denver'),
  ('United States', 'US', 'Boston', 1, 'America/New_York'),
  ('United States', 'US', 'Miami', 1, 'America/New_York');

-- UK cities
INSERT INTO territory_grid (country, country_code, city, tier, timezone) VALUES
  ('United Kingdom', 'GB', 'London', 1, 'Europe/London'),
  ('United Kingdom', 'GB', 'Manchester', 1, 'Europe/London'),
  ('United Kingdom', 'GB', 'Birmingham', 1, 'Europe/London'),
  ('United Kingdom', 'GB', 'Leeds', 2, 'Europe/London'),
  ('United Kingdom', 'GB', 'Edinburgh', 2, 'Europe/London'),
  ('United Kingdom', 'GB', 'Bristol', 2, 'Europe/London'),
  ('United Kingdom', 'GB', 'Glasgow', 2, 'Europe/London');

-- Australia cities
INSERT INTO territory_grid (country, country_code, city, tier, timezone) VALUES
  ('Australia', 'AU', 'Sydney', 1, 'Australia/Sydney'),
  ('Australia', 'AU', 'Melbourne', 1, 'Australia/Melbourne'),
  ('Australia', 'AU', 'Brisbane', 1, 'Australia/Brisbane'),
  ('Australia', 'AU', 'Perth', 2, 'Australia/Perth'),
  ('Australia', 'AU', 'Adelaide', 2, 'Australia/Adelaide');

-- Gulf cities
INSERT INTO territory_grid (country, country_code, city, tier, timezone) VALUES
  ('United Arab Emirates', 'AE', 'Dubai', 1, 'Asia/Dubai'),
  ('United Arab Emirates', 'AE', 'Abu Dhabi', 1, 'Asia/Dubai'),
  ('Saudi Arabia', 'SA', 'Riyadh', 1, 'Asia/Riyadh'),
  ('Saudi Arabia', 'SA', 'Jeddah', 2, 'Asia/Riyadh'),
  ('Qatar', 'QA', 'Doha', 1, 'Asia/Qatar');


-- ─── DISCOVERY RUNS ──────────────────────────────────────────
-- Track every search execution β€” prevents duplicate searches

CREATE TABLE discovery_runs (
  id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  run_type        TEXT NOT NULL,          -- 'auto' | 'manual'
  territory_id    UUID REFERENCES territory_grid(id),
  country_code    TEXT NOT NULL,
  city            TEXT NOT NULL,
  industry        TEXT NOT NULL,
  search_queries  TEXT[],                 -- actual Google queries used
  companies_found INTEGER DEFAULT 0,
  companies_passed_gate1 INTEGER DEFAULT 0,
  companies_passed_gate2 INTEGER DEFAULT 0,
  leads_qualified INTEGER DEFAULT 0,
  quota_target    INTEGER DEFAULT 10,
  status          TEXT DEFAULT 'running', -- 'running','completed','failed','partial'
  triggered_by    TEXT DEFAULT 'system',  -- 'system' | 'slack:username'
  ran_at          TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  completed_at    TIMESTAMPTZ
);

CREATE INDEX idx_discovery_runs_territory ON discovery_runs(city, industry, ran_at DESC);


-- ─── TERRITORY PROGRESS ─────────────────────────────────────
-- Tracks which city+industry combos have been covered and when

CREATE TABLE territory_progress (
  id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  territory_id    UUID REFERENCES territory_grid(id),
  industry        TEXT NOT NULL,
  last_run_at     TIMESTAMPTZ NOT NULL,
  next_eligible_at TIMESTAMPTZ NOT NULL,  -- last_run + 30 days
  total_leads     INTEGER DEFAULT 0,
  
  UNIQUE(territory_id, industry)
);


-- ─── PIPELINE CHECKPOINTS ───────────────────────────────────
-- Allows pipeline to resume from failure point (idempotency)

CREATE TABLE pipeline_checkpoints (
  id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  run_id          UUID REFERENCES discovery_runs(id),
  company_domain  TEXT NOT NULL,
  stage           TEXT NOT NULL,           -- 'scraped','filtered','contacts_found','profiled','scored'
  stage_data      JSONB DEFAULT '{}',      -- intermediate data for resume
  completed       BOOLEAN DEFAULT false,
  created_at      TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  updated_at      TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  
  UNIQUE(run_id, company_domain)
);


-- ─── LLM CALL TRACES ────────────────────────────────────────
-- Every LLM call logged for cost tracking and debugging

CREATE TABLE llm_traces (
  id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  trace_id        TEXT NOT NULL,           -- pipeline run trace
  operation       TEXT NOT NULL,           -- 'profile','score','email_classify','pain_detect'
  model           TEXT NOT NULL,           -- 'meta/llama-3.3-70b-instruct'
  provider        TEXT NOT NULL DEFAULT 'nvidia',
  prompt_tokens   INTEGER,
  completion_tokens INTEGER,
  total_tokens    INTEGER,
  latency_ms      INTEGER,
  success         BOOLEAN NOT NULL,
  fallback_used   BOOLEAN DEFAULT false,
  grounding_score NUMERIC(3,2),            -- 0.00-1.00 how well grounded
  company_id      UUID REFERENCES companies(id),
  input_hash      TEXT,                    -- hash of prompt (no PII stored)
  output_hash     TEXT,                    -- hash of output
  created_at      TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_llm_traces_trace ON llm_traces(trace_id);
CREATE INDEX idx_llm_traces_company ON llm_traces(company_id);


-- ─── SYSTEM CONFIG ───────────────────────────────────────────
-- Runtime configuration that Slack commands can modify

CREATE TABLE system_config (
  key         TEXT PRIMARY KEY,
  value       JSONB NOT NULL,
  updated_by  TEXT DEFAULT 'system',
  updated_at  TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

INSERT INTO system_config (key, value) VALUES
  ('daily_quota', '{"default": 10, "today_override": null}'),
  ('schedule', '{"start_hour_utc": 4, "enabled": true}'),
  ('auto_mode', '{"enabled": true, "paused": false, "paused_by": null}'),
  ('current_territory', '{"country_code": "US", "city_index": 0, "industry_index": 0}');


-- ─── ADD SOCIAL PROFILES TO CONTACTS ─────────────────────────

ALTER TABLE contacts ADD COLUMN IF NOT EXISTS linkedin_personal_url TEXT;
ALTER TABLE contacts ADD COLUMN IF NOT EXISTS social_profiles JSONB DEFAULT '{}';
ALTER TABLE contacts ADD COLUMN IF NOT EXISTS email_verification_layers JSONB DEFAULT '{}';
ALTER TABLE contacts ADD COLUMN IF NOT EXISTS email_tier TEXT;       -- 'personal','authority','context_verified','rejected'
ALTER TABLE contacts ADD COLUMN IF NOT EXISTS authority_confirmed BOOLEAN DEFAULT false;

-- ─── ADD CITY TO COMPANIES ───────────────────────────────────

ALTER TABLE companies ADD COLUMN IF NOT EXISTS city TEXT;
ALTER TABLE companies ADD COLUMN IF NOT EXISTS service_match TEXT;   -- matched service name
ALTER TABLE companies ADD COLUMN IF NOT EXISTS service_match_score INTEGER DEFAULT 0;
ALTER TABLE companies ADD COLUMN IF NOT EXISTS pain_signals TEXT[] DEFAULT '{}';
ALTER TABLE companies ADD COLUMN IF NOT EXISTS trace_id TEXT;        -- pipeline trace