'auth required']); exit; } function db_local() { static $pdo; if (!$pdo) { $pdo = new PDO("pgsql:host=127.0.0.1;dbname=adx_system", "admin", "admin123"); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); } return $pdo; } function db_paperclip() { static $pdo; if (!$pdo) { $pdo = new PDO("pgsql:host=10.1.0.3;dbname=paperclip", "admin", "admin123"); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); } return $pdo; } $action = $_GET['action'] ?? 'bridge_stats'; try { $local = db_local(); if ($action === 'bridge_stats') { $pc = db_paperclip(); $forms_total = (int)$local->query("SELECT COUNT(*) FROM contact_messages")->fetchColumn(); $forms_with_email = (int)$local->query("SELECT COUNT(DISTINCT email) FROM contact_messages WHERE email IS NOT NULL AND email != ''")->fetchColumn(); $leads_total = (int)$pc->query("SELECT COUNT(*) FROM weval_leads")->fetchColumn(); $leads_active = (int)$pc->query("SELECT COUNT(*) FROM weval_leads WHERE status='active_customer'")->fetchColumn(); $leads_warm = (int)$pc->query("SELECT COUNT(*) FROM weval_leads WHERE status='warm_prospect'")->fetchColumn(); // Overlap: emails in both tables $emails_forms = $local->query("SELECT DISTINCT LOWER(email) FROM contact_messages WHERE email IS NOT NULL AND email != ''")->fetchAll(PDO::FETCH_COLUMN); $emails_leads = $pc->query("SELECT DISTINCT LOWER(email) FROM weval_leads WHERE email IS NOT NULL AND email != ''")->fetchAll(PDO::FETCH_COLUMN); $overlap = count(array_intersect($emails_forms, $emails_leads)); echo json_encode([ 'ok' => true, 'forms_total' => $forms_total, 'forms_with_email' => $forms_with_email, 'leads_total' => $leads_total, 'leads_active' => $leads_active, 'leads_warm' => $leads_warm, 'email_overlap' => $overlap, 'unique_prospects_merged' => count(array_unique(array_merge($emails_forms, $emails_leads))), 'ts' => date('c') ]); exit; } if ($action === 'leads_unified') { $pc = db_paperclip(); $out = []; // From weval_leads (CRM canonical) $leads = $pc->query("SELECT id, email, contact_name, company, industry, country, status, mql_score, source, 'paperclip' as origin FROM weval_leads ORDER BY mql_score DESC, id DESC LIMIT 100")->fetchAll(PDO::FETCH_ASSOC); foreach ($leads as $l) { $out[] = $l; } // From contact_messages (chat form captures not yet promoted) $known = array_map('strtolower', array_column($leads, 'email')); $forms = $local->query("SELECT DISTINCT ON (email) id, email, name as contact_name, company, NULL as industry, NULL as country, 'form_captured' as status, 0 as mql_score, source, 'form' as origin FROM contact_messages WHERE email IS NOT NULL AND email != '' ORDER BY email, created_at DESC LIMIT 100")->fetchAll(PDO::FETCH_ASSOC); foreach ($forms as $f) { if (!in_array(strtolower($f['email']), $known)) { $out[] = $f; } } echo json_encode(['ok' => true, 'total' => count($out), 'leads' => $out]); exit; } if ($action === 'lead_detail') { $email = strtolower(trim($_GET['email'] ?? '')); if (!$email) { echo json_encode(['error' => 'email required']); exit; } $pc = db_paperclip(); // CRM record $st = $pc->prepare("SELECT * FROM weval_leads WHERE LOWER(email) = ?"); $st->execute([$email]); $crm = $st->fetch(PDO::FETCH_ASSOC) ?: null; // Form submissions $st = $local->prepare("SELECT id, name, email, company, subject, message, source, created_at FROM contact_messages WHERE LOWER(email) = ? ORDER BY created_at DESC"); $st->execute([$email]); $forms = $st->fetchAll(PDO::FETCH_ASSOC); // Conversations - try match via visitor table (which has no email today) + fallback company match $conversations = []; if ($crm && !empty($crm['company'])) { $st = $local->prepare("SELECT DISTINCT v.session_id, v.name, v.company, v.country, v.last_visit, COUNT(c.id) as msg_count FROM chatbot_visitors v LEFT JOIN chatbot_conversations c ON c.session_id = v.session_id WHERE LOWER(v.company) LIKE ? GROUP BY v.session_id, v.name, v.company, v.country, v.last_visit ORDER BY v.last_visit DESC LIMIT 20"); $st->execute(['%' . strtolower($crm['company']) . '%']); $conversations = $st->fetchAll(PDO::FETCH_ASSOC); } echo json_encode([ 'ok' => true, 'email' => $email, 'crm' => $crm, 'forms' => $forms, 'forms_count' => count($forms), 'conversations' => $conversations, 'conversations_count' => count($conversations), 'status_summary' => $crm ? ($crm['status'] . ' · MQL ' . $crm['mql_score']) : (count($forms) > 0 ? 'form_captured · not_promoted' : 'unknown') ]); exit; } if ($action === 'auto_promote') { $dry_run = !isset($_GET['execute']); $pc = db_paperclip(); // Find forms w/ email+company NOT already in weval_leads $emails_leads = array_map('strtolower', $pc->query("SELECT email FROM weval_leads WHERE email IS NOT NULL AND email != ''")->fetchAll(PDO::FETCH_COLUMN)); $forms = $local->query("SELECT DISTINCT ON (email) email, name, company, source, created_at FROM contact_messages WHERE email IS NOT NULL AND email != '' AND company IS NOT NULL AND company != '' ORDER BY email, created_at DESC")->fetchAll(PDO::FETCH_ASSOC); $to_promote = []; foreach ($forms as $f) { if (!in_array(strtolower($f['email']), $emails_leads)) { $to_promote[] = $f; } } $promoted = 0; if (!$dry_run && count($to_promote) > 0) { $ins = $pc->prepare("INSERT INTO weval_leads (slug, email, contact_name, company, source, status, mql_score, notes, email_status, created_at) VALUES (?, ?, ?, ?, ?, 'lead', 50, ?, 'sourced', NOW()) ON CONFLICT (slug) DO NOTHING"); foreach ($to_promote as $f) { $slug = 'form_' . substr(md5(strtolower($f['email'])), 0, 12); $notes = "Auto-promoted from contact_messages V67 · Original source: " . ($f['source'] ?? 'form'); try { $ins->execute([$slug, $f['email'], $f['name'], $f['company'], 'form_chat_autopromote_v67', $notes]); $promoted++; } catch (Exception $e) { /* skip on conflict */ } } } echo json_encode([ 'ok' => true, 'dry_run' => $dry_run, 'candidates' => count($to_promote), 'promoted' => $promoted, 'hint' => $dry_run ? 'Add ?execute=1 to perform real INSERT' : 'Real promotion complete', 'sample' => array_slice($to_promote, 0, 5) ]); exit; } if ($action === 'session_to_lead') { $sid = $_GET['sid'] ?? ''; if (!$sid) { echo json_encode(['error' => 'sid required']); exit; } // Get visitor for session $st = $local->prepare("SELECT * FROM chatbot_visitors WHERE session_id = ? LIMIT 1"); $st->execute([$sid]); $visitor = $st->fetch(PDO::FETCH_ASSOC); if (!$visitor) { echo json_encode(['ok' => true, 'visitor' => null, 'lead' => null]); exit; } $lead = null; if (!empty($visitor['email'])) { $pc = db_paperclip(); $q = $pc->prepare("SELECT * FROM weval_leads WHERE LOWER(email) = ?"); $q->execute([strtolower($visitor['email'])]); $lead = $q->fetch(PDO::FETCH_ASSOC); } elseif (!empty($visitor['company'])) { $pc = db_paperclip(); $q = $pc->prepare("SELECT * FROM weval_leads WHERE LOWER(company) LIKE ? LIMIT 1"); $q->execute(['%' . strtolower($visitor['company']) . '%']); $lead = $q->fetch(PDO::FETCH_ASSOC); } echo json_encode(['ok' => true, 'visitor' => $visitor, 'lead' => $lead, 'match_via' => $lead ? (!empty($visitor['email']) ? 'email' : 'company') : 'none']); exit; } echo json_encode(['error' => 'unknown action', 'available' => ['bridge_stats', 'leads_unified', 'lead_detail', 'auto_promote', 'session_to_lead']]); } catch (Exception $e) { http_response_code(500); echo json_encode(['error' => $e->getMessage()]); }