HEX
Server: LiteSpeed
System: Linux s1049.use1.mysecurecloudhost.com 4.18.0-477.27.2.lve.el8.x86_64 #1 SMP Wed Oct 11 12:32:56 UTC 2023 x86_64
User: xedaptot (3356)
PHP: 8.3.31
Disabled: NONE
Upload Files
File: /home/xedaptot/ai.naniguide.com/app/Model/AdAbTestEvaluationLog.php
<?php

namespace App\Model;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

/**
 * @property int $id
 * @property int $ad_ab_test_id
 * @property array<int,array{id:int,name:string,weight:int,impressions:int,clicks:int,spend:float,conversions:int,ctr:float}> $variants_snapshot
 * @property string $method
 * @property float|null $test_statistic
 * @property float|null $p_value
 * @property float|null $confidence
 * @property bool $winner_declared
 * @property int|null $winner_variant_id
 * @property string $gate_result
 * @property string|null $notes
 * @property \Carbon\Carbon|null $created_at
 * @property \Carbon\Carbon|null $updated_at
 *
 * R14 — single audit row per evaluator run.
 *
 * Every time the AdAbTestEvaluator scores a running test, it writes one of
 * these rows.  Customers reading their A/B test history can see the full
 * timeline of "test was evaluated at T1 with confidence 85% (no winner),
 * T2 with confidence 92% (no winner), T3 with confidence 96% (winner = B)"
 * — answers the "why isn't this finishing?" support question without
 * digging into application logs.
 *
 * Snapshot semantics: variants_snapshot is a frozen-at-evaluation copy of
 * each variant's metrics.  Storing it means later metric pulls (R7) don't
 * retroactively change the audit row's numbers.
 *
 * Statistical-method values:
 *   two_proportion_z   — z-test on CTR (clicks / impressions), the default
 *                        for almost every A/B test
 *   welch_t            — Welch's t-test on CPA (spend / conversions); used
 *                        when both variants have ≥30 conversions and the
 *                        customer flagged the test for cost optimization
 *   min_sample_not_met — evaluator skipped the test (insufficient data);
 *                        test_statistic + p_value + confidence are null
 *
 * Gate-result values:
 *   passed                      — min-sample requirements met; statistical
 *                                 test ran
 *   insufficient_conversions    — fewer than 100 conversions per variant
 *                                 AND test ran for less than 7 days
 *   insufficient_duration       — has 100 conversions per variant but the
 *                                 7-day floor hasn't elapsed (rare; only
 *                                 hits ultra-high-traffic experiments)
 *   inconclusive                — test ran 7+ days but confidence below
 *                                 threshold; evaluator suggests stopping
 */
class AdAbTestEvaluationLog extends Model
{
    public const METHOD_TWO_PROPORTION_Z = 'two_proportion_z';
    public const METHOD_WELCH_T = 'welch_t';
    public const METHOD_MIN_SAMPLE_NOT_MET = 'min_sample_not_met';

    public const METHODS = [
        self::METHOD_TWO_PROPORTION_Z,
        self::METHOD_WELCH_T,
        self::METHOD_MIN_SAMPLE_NOT_MET,
    ];

    public const GATE_PASSED = 'passed';
    public const GATE_INSUFFICIENT_CONVERSIONS = 'insufficient_conversions';
    public const GATE_INSUFFICIENT_DURATION = 'insufficient_duration';
    public const GATE_INCONCLUSIVE = 'inconclusive';

    public const GATES = [
        self::GATE_PASSED,
        self::GATE_INSUFFICIENT_CONVERSIONS,
        self::GATE_INSUFFICIENT_DURATION,
        self::GATE_INCONCLUSIVE,
    ];

    protected $table = 'ad_ab_test_evaluation_logs';

    protected $fillable = [
        'ad_ab_test_id',
        'variants_snapshot',
        'method',
        'test_statistic',
        'p_value',
        'confidence',
        'winner_declared',
        'winner_variant_id',
        'gate_result',
        'notes',
    ];

    protected $casts = [
        'variants_snapshot' => 'json',
        'test_statistic' => 'decimal:4',
        'p_value' => 'decimal:8',
        'confidence' => 'decimal:2',
        'winner_declared' => 'boolean',
    ];

    public function abTest(): BelongsTo
    {
        return $this->belongsTo(AdAbTest::class, 'ad_ab_test_id');
    }

    public function winnerVariant(): BelongsTo
    {
        return $this->belongsTo(AdAbVariant::class, 'winner_variant_id');
    }
}