PDO::ERRMODE_EXCEPTION]); $pdo->exec(" CREATE TABLE IF NOT EXISTS admin.ab_tests ( id SERIAL PRIMARY KEY, name VARCHAR(255), campaign_id INTEGER, test_type VARCHAR(50) DEFAULT 'subject', variants TEXT, test_size_percent INTEGER DEFAULT 20, winning_metric VARCHAR(50) DEFAULT 'open_rate', min_sample_size INTEGER DEFAULT 1000, confidence_level FLOAT DEFAULT 0.95, status VARCHAR(50) DEFAULT 'draft', winner_variant VARCHAR(10), results TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, started_at TIMESTAMP, completed_at TIMESTAMP ); CREATE TABLE IF NOT EXISTS admin.ab_variant_stats ( id SERIAL PRIMARY KEY, test_id INTEGER, variant VARCHAR(10), sent INTEGER DEFAULT 0, delivered INTEGER DEFAULT 0, opened INTEGER DEFAULT 0, clicked INTEGER DEFAULT 0, converted INTEGER DEFAULT 0, revenue FLOAT DEFAULT 0, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); "); class ABTestEngine { private $pdo; public function __construct($pdo) { $this->pdo = $pdo; } public function createTest($data) { $variants = []; foreach (['A', 'B', 'C', 'D', 'E'] as $v) { if (!empty($data["variant_$v"])) { $variants[$v] = $data["variant_$v"]; } } $this->pdo->prepare("INSERT INTO admin.ab_tests (name, campaign_id, test_type, variants, test_size_percent, winning_metric, min_sample_size) VALUES (?, ?, ?, ?, ?, ?, ?)") ->execute([ $data['name'], $data['campaign_id'] ?? null, $data['test_type'] ?? 'subject', json_encode($variants), $data['test_size_percent'] ?? 20, $data['winning_metric'] ?? 'open_rate', $data['min_sample_size'] ?? 1000 ]); $testId = $this->pdo->lastInsertId(); // Create variant stats foreach (array_keys($variants) as $v) { $this->pdo->exec("INSERT INTO admin.ab_variant_stats (test_id, variant) VALUES ($testId, '$v')"); } return ['success' => true, 'test_id' => $testId, 'variants' => count($variants)]; } public function recordEvent($testId, $variant, $eventType) { $column = ['send' => 'sent', 'deliver' => 'delivered', 'open' => 'opened', 'click' => 'clicked', 'convert' => 'converted'][$eventType] ?? null; if ($column) { $this->pdo->exec("UPDATE admin.ab_variant_stats SET $column = $column + 1, updated_at = NOW() WHERE test_id = $testId AND variant = '$variant'"); } return ['success' => true]; } public function getResults($testId) { $test = $this->pdo->query("SELECT * FROM admin.ab_tests WHERE id = $testId")->fetch(PDO::FETCH_ASSOC); $stats = $this->pdo->query("SELECT * FROM admin.ab_variant_stats WHERE test_id = $testId")->fetchAll(PDO::FETCH_ASSOC); $results = []; foreach ($stats as $s) { $sent = max(1, $s['sent']); $results[$s['variant']] = [ 'sent' => $s['sent'], 'open_rate' => round($s['opened'] / $sent * 100, 2), 'click_rate' => round($s['clicked'] / $sent * 100, 2), 'conversion_rate' => round($s['converted'] / $sent * 100, 2) ]; } // Determine winner $metric = $test['winning_metric']; $winner = null; $bestValue = 0; foreach ($results as $v => $r) { if ($r[$metric] > $bestValue) { $bestValue = $r[$metric]; $winner = $v; } } // Statistical significance $isSignificant = $this->calculateSignificance($stats, $metric, $test['confidence_level']); return [ 'test' => $test, 'results' => $results, 'winner' => $winner, 'is_significant' => $isSignificant, 'recommendation' => $isSignificant ? "Use variant $winner" : "Need more data" ]; } private function calculateSignificance($stats, $metric, $confidence) { // Simplified significance check $totalSent = array_sum(array_column($stats, 'sent')); return $totalSent >= 2000; } public function autoSelectWinner($testId) { $results = $this->getResults($testId); if ($results['is_significant'] && $results['winner']) { $this->pdo->prepare("UPDATE admin.ab_tests SET status = 'completed', winner_variant = ?, completed_at = NOW(), results = ? WHERE id = ?") ->execute([$results['winner'], json_encode($results['results']), $testId]); return ['success' => true, 'winner' => $results['winner']]; } return ['success' => false, 'message' => 'Not enough data yet']; } } $engine = new ABTestEngine($pdo); $action = $_POST['action'] ?? $_GET['action'] ?? ''; switch ($action) { case 'create': echo json_encode($engine->createTest($_POST)); break; case 'event': echo json_encode($engine->recordEvent($_POST['test_id'], $_POST['variant'], $_POST['event_type'])); break; case 'results': echo json_encode($engine->getResults($_GET['test_id'])); break; case 'auto_winner': echo json_encode($engine->autoSelectWinner($_POST['test_id'])); break; case 'list': echo json_encode(['tests' => $pdo->query("SELECT * FROM admin.ab_tests ORDER BY created_at DESC")->fetchAll(PDO::FETCH_ASSOC)]); break; default: echo json_encode(['name' => 'A/B Testing Engine', 'actions' => ['create','event','results','auto_winner','list']]); }