File: /home/xedaptot/ai.naniguide.com/app/Services/WarmupStrategyService.php
<?php
namespace App\Services;
use App\Dto\WarmupStrategyData;
use App\Dto\WarmupStrategyPreviewData;
use App\Model\SendingServer;
use App\Model\User;
use App\Model\WarmupStrategy;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\DB;
class WarmupStrategyService
{
public function listQuery(?string $keyword = null): Builder
{
return WarmupStrategy::query()->search($keyword);
}
public function newStrategy(?string $preset = null): WarmupStrategy
{
$strategy = WarmupStrategy::newDefault();
if ($preset !== null) {
$strategy->fill($this->presetPayload($preset));
}
return $strategy;
}
public function create(WarmupStrategyData $data): WarmupStrategy
{
return DB::transaction(function () use ($data) {
$strategy = new WarmupStrategy();
return $this->persist($strategy, $data);
});
}
public function update(WarmupStrategy $strategy, WarmupStrategyData $data): WarmupStrategy
{
return DB::transaction(function () use ($strategy, $data) {
return $this->persist($strategy, $data);
});
}
public function assignToSendingServer(SendingServer $server, ?WarmupStrategy $warmupStrategy): void
{
$server->saveWarmupStrategy($warmupStrategy);
}
public function deleteMany(iterable $strategies, User $user): array
{
$total = 0;
$deleted = 0;
foreach ($strategies as $strategy) {
$total++;
if ($user->admin->can('delete', $strategy)) {
$strategy->delete();
$deleted++;
}
}
return [$deleted, $total];
}
public function enableMany(iterable $strategies, User $user): array
{
return $this->toggleMany($strategies, $user, 'enable');
}
public function disableMany(iterable $strategies, User $user): array
{
return $this->toggleMany($strategies, $user, 'disable');
}
public function buildPreview(WarmupStrategy|WarmupStrategyData $source): WarmupStrategyPreviewData
{
$strategy = $source instanceof WarmupStrategy ? $source : $this->previewModel($source);
$planDetails = $strategy->getPlanDetails();
$estimatedFullWarmupDay = $strategy->estimatedFullWarmupDay();
$milestones = array_map(function ($step) {
return [
'label' => 'Day '.$step['step'],
'volume' => (int) $step['sendingOfStep'],
];
}, $this->samplePlanDetails($planDetails, 4));
$chartPoints = array_map(function ($step) {
return [
'day' => (int) $step['step'],
'volume' => (int) $step['sendingOfStep'],
'cumulative' => (int) $step['quota'],
];
}, $this->samplePlanDetails($planDetails, 14));
return new WarmupStrategyPreviewData(
growthStrategy: (string) ($strategy->growth_strategy ?: WarmupStrategy::GROWTH_STRATEGY_LINEAR),
limitType: (string) ($strategy->limit_type ?: WarmupStrategy::LIMIT_TYPE_PER_DAY_CAP),
startVolume: (int) $strategy->starting_volume,
dailyIncrement: (int) $strategy->daily_increment,
exponentialFactor: (float) ($strategy->exponential_factor ?: WarmupStrategy::exponentialFactorFromIncrement((int) $strategy->daily_increment)),
activeLimitValue: $strategy->activeLimitValue(),
projectedPeakVolume: $strategy->projectedPeakVolume(),
projectedTotalVolume: $strategy->projectedTotalVolume(),
estimatedFullWarmupDay: $estimatedFullWarmupDay,
riskLevel: $strategy->riskLevel(),
milestones: $milestones,
chartPoints: $chartPoints,
planDetails: $planDetails,
);
}
public function presetPayload(?string $preset): array
{
return match ($preset) {
WarmupStrategy::PRESET_CAUTIOUS => [
'preset' => WarmupStrategy::PRESET_CAUTIOUS,
'growth_strategy' => WarmupStrategy::GROWTH_STRATEGY_LINEAR,
'starting_volume' => 15,
'daily_increment' => 8,
'exponential_factor' => 1.08,
'limit_per_day_cap' => 500,
'limit_target_volume' => 4000,
'limit_stop_after_days' => 45,
'send_on_weekends' => false,
'enable_safety_checks' => true,
'pause_on_negative_signals' => true,
'max_bounce_rate' => 3.0,
'max_complaint_rate' => 0.2,
'limit_type' => WarmupStrategy::LIMIT_TYPE_PER_DAY_CAP,
'quota_type' => WarmupStrategy::QUOTA_TYPE_DAILY,
],
WarmupStrategy::PRESET_AGGRESSIVE => [
'preset' => WarmupStrategy::PRESET_AGGRESSIVE,
'growth_strategy' => WarmupStrategy::GROWTH_STRATEGY_EXPONENTIAL,
'starting_volume' => 50,
'daily_increment' => 20,
'exponential_factor' => 1.20,
'limit_per_day_cap' => 3000,
'limit_target_volume' => 15000,
'limit_stop_after_days' => 21,
'send_on_weekends' => true,
'enable_safety_checks' => true,
'pause_on_negative_signals' => true,
'max_bounce_rate' => 5.0,
'max_complaint_rate' => 0.5,
'limit_type' => WarmupStrategy::LIMIT_TYPE_STOP_AFTER_DAYS,
'quota_type' => WarmupStrategy::QUOTA_TYPE_DAILY,
],
default => [
'preset' => WarmupStrategy::PRESET_BALANCED,
'growth_strategy' => WarmupStrategy::GROWTH_STRATEGY_LINEAR,
'starting_volume' => 20,
'daily_increment' => 15,
'exponential_factor' => 1.15,
'limit_per_day_cap' => 1000,
'limit_target_volume' => 8000,
'limit_stop_after_days' => 30,
'send_on_weekends' => false,
'enable_safety_checks' => true,
'pause_on_negative_signals' => true,
'max_bounce_rate' => 4.0,
'max_complaint_rate' => 0.3,
'limit_type' => WarmupStrategy::LIMIT_TYPE_PER_DAY_CAP,
'quota_type' => WarmupStrategy::QUOTA_TYPE_DAILY,
],
};
}
private function persist(WarmupStrategy $strategy, WarmupStrategyData $data): WarmupStrategy
{
$previewStrategy = $this->previewModel($data);
$strategy->fill([
'name' => $data->name,
'description' => $data->description,
'preset' => $data->preset,
'growth_strategy' => $previewStrategy->growth_strategy,
'starting_volume' => $data->startingVolume,
'quota_type' => $data->quotaType,
'daily_increment' => $previewStrategy->daily_increment,
'exponential_factor' => $previewStrategy->exponential_factor,
'limit_type' => $data->limitType,
'limit_per_day_cap' => $data->limitPerDayCap,
'limit_target_volume' => $data->limitTargetVolume,
'limit_stop_after_days' => $data->limitStopAfterDays,
'send_on_weekends' => $data->sendOnWeekends,
'enable_safety_checks' => $data->enableSafetyChecks,
'pause_on_negative_signals' => $data->pauseOnNegativeSignals,
'max_bounce_rate' => $data->maxBounceRate,
'max_complaint_rate' => $data->maxComplaintRate,
'status' => $data->status,
'sending_count' => $previewStrategy->sending_count,
'sending_limit' => $previewStrategy->sending_limit,
'increment_percentage' => $previewStrategy->increment_percentage,
]);
$strategy->save();
return $strategy;
}
private function previewModel(WarmupStrategyData $data): WarmupStrategy
{
$strategy = WarmupStrategy::newDefault();
$strategy->fill(array_filter($data->toArray(), function ($value) {
return $value !== null;
}));
$strategy->growth_strategy = $data->growthStrategy;
$strategy->daily_increment = $data->growthStrategy === WarmupStrategy::GROWTH_STRATEGY_EXPONENTIAL
? WarmupStrategy::incrementFromExponentialFactor((float) ($data->exponentialFactor ?? WarmupStrategy::DEFAULT_EXPONENTIAL_FACTOR))
: $data->dailyIncrement;
$strategy->exponential_factor = $data->exponentialFactor ?? WarmupStrategy::DEFAULT_EXPONENTIAL_FACTOR;
$strategy->limit_type = $data->limitType;
$strategy->limit_per_day_cap = $data->limitPerDayCap;
$strategy->limit_target_volume = $data->limitTargetVolume;
$strategy->limit_stop_after_days = $data->limitStopAfterDays;
$planDetails = $strategy->getPlanDetails();
$strategy->sending_count = max(1, count($planDetails));
$strategy->sending_limit = max(1, (int) collect($planDetails)->max('sendingOfStep'));
$strategy->increment_percentage = $strategy->daily_increment;
return $strategy;
}
private function toggleMany(iterable $strategies, User $user, string $action): array
{
$total = 0;
$done = 0;
foreach ($strategies as $strategy) {
$total++;
if ($user->admin->can($action, $strategy)) {
$strategy->{$action}();
$done++;
}
}
return [$done, $total];
}
private function samplePlanDetails(array $planDetails, int $count): array
{
$total = count($planDetails);
if ($total <= $count) {
return $planDetails;
}
$indices = [];
for ($position = 0; $position < $count; $position++) {
$indices[] = (int) round(($position * ($total - 1)) / max(1, $count - 1));
}
$indices = array_values(array_unique($indices));
return array_map(function ($index) use ($planDetails) {
return $planDetails[$index];
}, $indices);
}
}