159 lines
5.8 KiB
PHP
Executable File
159 lines
5.8 KiB
PHP
Executable File
|
|
<?php
|
|
/**
|
|
* A/B TESTING ENGINE
|
|
* Multi-variant testing with auto-winner
|
|
*/
|
|
header('Content-Type: application/json');
|
|
$pdo = new PDO("pgsql:host=localhost;dbname=adx_system", "admin", "admin123", [PDO::ATTR_ERRMODE => 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']]);
|
|
}
|
|
|