Files
wevads-platform/scripts/api_ab-testing.php
2026-02-26 04:53:11 +01:00

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']]);
}