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/WarmupStrategy.php
<?php

namespace App\Model;

use App\Dto\WarmupStrategyData;
use App\Services\Warmup\WarmupStrategyFactory;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use App\Library\Traits\HasUid;

class WarmupStrategy extends Model
{
    use HasFactory;
    use HasUid;

    // statuses
    public const STATUS_INACTIVE = 'inactive';
    public const STATUS_ACTIVE = 'active';

    // Limit type
    public const LIMIT_TYPE_PER_DAY_CAP = 'per_day_cap';
    public const LIMIT_TYPE_TARGET_VOLUME = 'target_volume';
    public const LIMIT_TYPE_STOP_AFTER_DAYS = 'stop_after_days';

    // Strategy type
    public const GROWTH_STRATEGY_LINEAR = 'linear';
    public const GROWTH_STRATEGY_EXPONENTIAL = 'exponential';

    public const PRESET_BALANCED = 'balanced';
    public const PRESET_CAUTIOUS = 'cautious';
    public const PRESET_AGGRESSIVE = 'aggressive';

    public const QUOTA_TYPE_HOURLY = 'hourly';
    public const QUOTA_TYPE_DAILY = 'daily';
    public const QUOTA_TYPE_MONTHLY = 'monthly';

    public const DEFAULT_MAX_BOUNCE_RATE = 4.0;
    public const DEFAULT_MAX_COMPLAINT_RATE = 0.3;
    public const DEFAULT_EXPONENTIAL_FACTOR = 1.15;

    protected $fillable = [
        'name',
        'description',
        'preset',
        'growth_strategy',
        'starting_volume',
        'quota_type',
        'daily_increment',
        'exponential_factor',
        'limit_type',
        'limit_per_day_cap',
        'limit_target_volume',
        'limit_stop_after_days',
        'send_on_weekends',
        'enable_safety_checks',
        'pause_on_negative_signals',
        'max_bounce_rate',
        'max_complaint_rate',
        'status',
        'sending_limit',
        'sending_count',
        'increment_percentage',
    ];

    protected $casts = [
        'starting_volume' => 'integer',
        'daily_increment' => 'integer',
        'exponential_factor' => 'float',
        'limit_per_day_cap' => 'integer',
        'limit_target_volume' => 'integer',
        'limit_stop_after_days' => 'integer',
        'send_on_weekends' => 'boolean',
        'enable_safety_checks' => 'boolean',
        'pause_on_negative_signals' => 'boolean',
        'max_bounce_rate' => 'float',
        'max_complaint_rate' => 'float',
        'sending_limit' => 'integer',
        'sending_count' => 'integer',
        'increment_percentage' => 'integer',
    ];

    public static function scopeSearch($query, $keyword)
    {
        // Keyword
        if (!empty(trim($keyword))) {
            foreach (explode(' ', trim($keyword)) as $keyword) {
                $query = $query->where(function ($q) use ($keyword) {
                    $q->orwhere('warmup_strategies.name', 'like', '%'.$keyword.'%')
                        ->orwhere('warmup_strategies.description', 'like', '%'.$keyword.'%')
                        ->orwhere('warmup_strategies.preset', 'like', '%'.$keyword.'%');
                });
            }
        }

        return $query;
    }

    public static function newDefault()
    {
        $strategy = new self();
        $strategy->status = self::STATUS_ACTIVE;
        $strategy->preset = self::PRESET_BALANCED;
        $strategy->growth_strategy = self::GROWTH_STRATEGY_LINEAR;
        $strategy->quota_type = self::QUOTA_TYPE_DAILY;
        $strategy->limit_type = self::LIMIT_TYPE_PER_DAY_CAP;
        $strategy->starting_volume = 20;
        $strategy->daily_increment = 15;
        $strategy->exponential_factor = self::DEFAULT_EXPONENTIAL_FACTOR;
        $strategy->limit_per_day_cap = 1000;
        $strategy->limit_target_volume = 10000;
        $strategy->limit_stop_after_days = 30;
        $strategy->send_on_weekends = false;
        $strategy->enable_safety_checks = true;
        $strategy->pause_on_negative_signals = true;
        $strategy->max_bounce_rate = self::DEFAULT_MAX_BOUNCE_RATE;
        $strategy->max_complaint_rate = self::DEFAULT_MAX_COMPLAINT_RATE;
        $strategy->increment_percentage = 15;
        $strategy->sending_count = 30;
        $strategy->sending_limit = 1000;

        return $strategy;
    }

    public function disable()
    {
        $this->status = self::STATUS_INACTIVE;
        $this->save();
    }

    public function enable()
    {
        $this->status = self::STATUS_ACTIVE;
        $this->save();
    }

    public static function scopeActive($query)
    {
        return $query->where('warmup_strategies.status', '=', self::STATUS_ACTIVE);
    }

    public static function limitTypeOptions()
    {
        return [
            ['text' => trans('warmup.limit_types.per_day_cap'), 'value' => self::LIMIT_TYPE_PER_DAY_CAP],
            ['text' => trans('warmup.limit_types.target_volume'), 'value' => self::LIMIT_TYPE_TARGET_VOLUME],
            ['text' => trans('warmup.limit_types.stop_after_days'), 'value' => self::LIMIT_TYPE_STOP_AFTER_DAYS],
        ];
    }

    public static function strategyOptions()
    {
        return [
            ['text' => trans('warmup.growth_strategies.linear'), 'value' => self::GROWTH_STRATEGY_LINEAR],
            ['text' => trans('warmup.growth_strategies.exponential'), 'value' => self::GROWTH_STRATEGY_EXPONENTIAL],
        ];
    }

    public static function quotaTypeOptions()
    {
        return [
            ['text' => trans('warmup.quota_types.hourly'), 'value' => self::QUOTA_TYPE_HOURLY],
            ['text' => trans('warmup.quota_types.daily'), 'value' => self::QUOTA_TYPE_DAILY],
            ['text' => trans('warmup.quota_types.monthly'), 'value' => self::QUOTA_TYPE_MONTHLY],
        ];
    }

    public static function presetOptions()
    {
        return [
            ['text' => trans('warmup.presets.balanced'), 'value' => self::PRESET_BALANCED],
            ['text' => trans('warmup.presets.cautious'), 'value' => self::PRESET_CAUTIOUS],
            ['text' => trans('warmup.presets.aggressive'), 'value' => self::PRESET_AGGRESSIVE],
        ];
    }

    public static function presetValues(): array
    {
        return array_column(self::presetOptions(), 'value');
    }

    public static function growthStrategyValues(): array
    {
        return array_column(self::strategyOptions(), 'value');
    }

    public static function quotaTypeValues(): array
    {
        return array_column(self::quotaTypeOptions(), 'value');
    }

    public static function limitTypeValues(): array
    {
        return array_column(self::limitTypeOptions(), 'value');
    }

    public static function incrementFromExponentialFactor(float $factor): int
    {
        $normalizedFactor = max(1.01, $factor);

        return max(1, (int) round(($normalizedFactor - 1) * 100));
    }

    public static function exponentialFactorFromIncrement(int|float $increment): float
    {
        return round(1 + (max(0, (float) $increment) / 100), 2);
    }

    public function getPlanDetails(): array
    {
        $calculator = app(WarmupStrategyFactory::class)->make($this);
        $limitType = $this->limit_type ?: self::LIMIT_TYPE_PER_DAY_CAP;
        $limitValue = $this->activeLimitValue();

        $result = [];
        $currentQuota = 0;

        for ($step = 1; $step <= 365; $step++) {
            $sendingOfStep = $calculator->calculateDailyQuota($step);

            if ($limitType === self::LIMIT_TYPE_PER_DAY_CAP) {
                $sendingOfStep = min($limitValue, $sendingOfStep);
            }

            if ($limitType === self::LIMIT_TYPE_TARGET_VOLUME) {
                $remaining = max(0, $limitValue - $currentQuota);

                if ($remaining < 1) {
                    break;
                }

                $sendingOfStep = min($sendingOfStep, $remaining);
            }

            $currentQuota += $sendingOfStep;

            $result[] = [
                'step' => $step,
                'sendingOfStep' => (int) $sendingOfStep,
                'quota' => (int) $currentQuota,
            ];

            if ($limitType === self::LIMIT_TYPE_PER_DAY_CAP && $sendingOfStep >= $limitValue) {
                break;
            }

            if ($limitType === self::LIMIT_TYPE_TARGET_VOLUME && $currentQuota >= $limitValue) {
                break;
            }

            if ($limitType === self::LIMIT_TYPE_STOP_AFTER_DAYS && $step >= $limitValue) {
                break;
            }
        }

        return $result;
    }

    public function estimateWarmupStepsFromData(WarmupStrategyData $data): int
    {
        $previewStrategy = self::newDefault();

        $previewStrategy->fill($data->toArray());
        $previewStrategy->growth_strategy = $data->growthStrategy;
        $previewStrategy->daily_increment = $data->growthStrategy === self::GROWTH_STRATEGY_EXPONENTIAL
            ? self::incrementFromExponentialFactor((float) ($data->exponentialFactor ?? self::DEFAULT_EXPONENTIAL_FACTOR))
            : $data->dailyIncrement;
        $previewStrategy->exponential_factor = $data->exponentialFactor ?? self::DEFAULT_EXPONENTIAL_FACTOR;

        return count($previewStrategy->getPlanDetails());
    }

    public function estimatedFullWarmupDay(): int
    {
        $steps = max(1, count($this->getPlanDetails()));

        if ($this->send_on_weekends) {
            return $steps;
        }

        $calendarDays = 0;
        $activeDays = 0;

        while ($activeDays < $steps) {
            $calendarDays++;
            $dayOfWeek = (($calendarDays - 1) % 7) + 1;
            if ($dayOfWeek <= 5) {
                $activeDays++;
            }
        }

        return $calendarDays;
    }

    public function riskLevel(): string
    {
        $score = 0;
        $peakVolume = $this->projectedPeakVolume();
        $activeLimitValue = $this->activeLimitValue();

        if ($this->preset === self::PRESET_AGGRESSIVE) {
            $score += 2;
        }

        if ($this->growth_strategy === self::GROWTH_STRATEGY_EXPONENTIAL) {
            $score += 1;
        }

        if ((int) $this->daily_increment >= 20) {
            $score += 1;
        }

        if ($peakVolume >= 2000) {
            $score += 1;
        }

        if (($this->limit_type ?: self::LIMIT_TYPE_PER_DAY_CAP) === self::LIMIT_TYPE_TARGET_VOLUME && $activeLimitValue >= 10000) {
            $score += 1;
        }

        if (($this->limit_type ?: self::LIMIT_TYPE_PER_DAY_CAP) === self::LIMIT_TYPE_STOP_AFTER_DAYS && $activeLimitValue >= 45) {
            $score += 1;
        }

        if ($this->send_on_weekends) {
            $score += 1;
        }

        return match (true) {
            $score >= 4 => 'high',
            $score >= 2 => 'medium',
            default => 'low',
        };
    }

    public function volumeForDisplayDay(int $day): int
    {
        $details = $this->getPlanDetails();
        $index = max(0, min(count($details) - 1, $day - 1));

        return (int) ($details[$index]['sendingOfStep'] ?? 0);
    }

    public function activeLimitValue(): int
    {
        $startingVolume = max(1, (int) ($this->starting_volume ?: 1));
        $limitType = $this->limit_type ?: self::LIMIT_TYPE_PER_DAY_CAP;

        return match ($limitType) {
            self::LIMIT_TYPE_TARGET_VOLUME => max($startingVolume, (int) ($this->limit_target_volume ?: $this->sending_limit ?: $startingVolume)),
            self::LIMIT_TYPE_STOP_AFTER_DAYS => max(1, (int) ($this->limit_stop_after_days ?: $this->sending_count ?: 1)),
            default => max($startingVolume, (int) ($this->limit_per_day_cap ?: $this->sending_limit ?: $startingVolume)),
        };
    }

    public function projectedPeakVolume(): int
    {
        return max(1, (int) collect($this->getPlanDetails())->max('sendingOfStep'));
    }

    public function projectedTotalVolume(): int
    {
        return max(1, (int) collect($this->getPlanDetails())->max('quota'));
    }
}