feat(прогрев ф2): decider — решение по строке канала (flat-режим + funnel из строки)
Фаза 2 Этап A, Task 3. Новый метод PlatformWarmingDecider::decideForChannelRow — решает по строке firm_channels: mode=flat считает простой срок от warming_started_at строки + flat_days (active/stopped(flat_done)); mode=funnel — та же логика decide(), но точка отсчёта warmup берётся из строки канала, а не из firm.warmup_started_at (у каждого канала свой заезд). Существующий decide() не тронут — Фаза 1 цела. AdAudienceScheduler без изменений. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3,9 +3,11 @@
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Models\SalesAdAudienceFirm;
|
||||
use App\Models\SalesAdAudienceFirmChannel;
|
||||
use App\Models\SalesAdAudiencePlatform;
|
||||
use App\Services\Sales\PlatformWarmingDecider;
|
||||
use Carbon\CarbonImmutable;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Tests\TestCase;
|
||||
|
||||
// Модели вне контейнера Laravel не могут выставить datetime-cast атрибут
|
||||
@@ -18,9 +20,9 @@ it('одна фирма: на площадке с большим сроком ac
|
||||
$now = CarbonImmutable::parse('2026-07-22 12:00:00');
|
||||
|
||||
$firm = new SalesAdAudienceFirm(['firm_name' => 'X']);
|
||||
$firm->warmup_started_at = \Illuminate\Support\Carbon::parse('2026-07-18 12:00:00'); // 4 дня назад
|
||||
$firm->warmup_started_at = Carbon::parse('2026-07-18 12:00:00'); // 4 дня назад
|
||||
|
||||
$decider = new PlatformWarmingDecider();
|
||||
$decider = new PlatformWarmingDecider;
|
||||
|
||||
$long = new SalesAdAudiencePlatform(['platform' => 'yandex']);
|
||||
$long->warmup_days = 5;
|
||||
@@ -34,3 +36,82 @@ it('одна фирма: на площадке с большим сроком ac
|
||||
expect($shortDecision['state'])->toBe('stopped');
|
||||
expect($shortDecision['reason'])->toBe('ready_for_manager');
|
||||
});
|
||||
|
||||
it('flat active: строка канала mode=flat, flat_days больше прошедшего срока — active', function () {
|
||||
$now = CarbonImmutable::parse('2026-07-22 12:00:00');
|
||||
|
||||
$firm = new SalesAdAudienceFirm(['firm_name' => 'X']);
|
||||
$platform = new SalesAdAudiencePlatform(['platform' => 'yandex']);
|
||||
$platform->warmup_days = 3;
|
||||
|
||||
$row = new SalesAdAudienceFirmChannel([
|
||||
'channel' => 'yandex',
|
||||
'status' => 'warming',
|
||||
'mode' => 'flat',
|
||||
'flat_days' => 10,
|
||||
]);
|
||||
$row->warming_started_at = Carbon::parse('2026-07-18 12:00:00'); // 4 дня назад
|
||||
|
||||
$decider = new PlatformWarmingDecider;
|
||||
|
||||
$decision = $decider->decideForChannelRow($firm, $platform, null, $row, $now);
|
||||
|
||||
expect($decision['state'])->toBe('active');
|
||||
});
|
||||
|
||||
it('flat done: строка канала mode=flat, flat_days меньше прошедшего срока — stopped(flat_done)', function () {
|
||||
$now = CarbonImmutable::parse('2026-07-22 12:00:00');
|
||||
|
||||
$firm = new SalesAdAudienceFirm(['firm_name' => 'X']);
|
||||
$platform = new SalesAdAudiencePlatform(['platform' => 'yandex']);
|
||||
$platform->warmup_days = 3;
|
||||
|
||||
$row = new SalesAdAudienceFirmChannel([
|
||||
'channel' => 'yandex',
|
||||
'status' => 'warming',
|
||||
'mode' => 'flat',
|
||||
'flat_days' => 3,
|
||||
]);
|
||||
$row->warming_started_at = Carbon::parse('2026-07-17 12:00:00'); // 5 дней назад
|
||||
|
||||
$decider = new PlatformWarmingDecider;
|
||||
|
||||
$decision = $decider->decideForChannelRow($firm, $platform, null, $row, $now);
|
||||
|
||||
expect($decision['state'])->toBe('stopped');
|
||||
expect($decision['reason'])->toBe('flat_done');
|
||||
});
|
||||
|
||||
it('funnel из строки: срок берётся из warming_started_at строки канала, не из firm.warmup_started_at', function () {
|
||||
$now = CarbonImmutable::parse('2026-07-22 12:00:00');
|
||||
|
||||
$firm = new SalesAdAudienceFirm(['firm_name' => 'X']);
|
||||
// Умышленно НЕ ставим firm->warmup_started_at — decideForChannelRow должен
|
||||
// взять срок из строки канала, а не из фирмы.
|
||||
|
||||
$platform = new SalesAdAudiencePlatform(['platform' => 'yandex']);
|
||||
$platform->warmup_days = 3;
|
||||
|
||||
$decider = new PlatformWarmingDecider;
|
||||
|
||||
$rowActive = new SalesAdAudienceFirmChannel([
|
||||
'channel' => 'yandex',
|
||||
'status' => 'warming',
|
||||
'mode' => 'funnel',
|
||||
]);
|
||||
$rowActive->warming_started_at = Carbon::parse('2026-07-20 12:00:00'); // 2 дня назад
|
||||
|
||||
$activeDecision = $decider->decideForChannelRow($firm, $platform, null, $rowActive, $now);
|
||||
expect($activeDecision['state'])->toBe('active');
|
||||
|
||||
$rowStopped = new SalesAdAudienceFirmChannel([
|
||||
'channel' => 'yandex',
|
||||
'status' => 'warming',
|
||||
'mode' => 'funnel',
|
||||
]);
|
||||
$rowStopped->warming_started_at = Carbon::parse('2026-07-17 12:00:00'); // 5 дней назад
|
||||
|
||||
$stoppedDecision = $decider->decideForChannelRow($firm, $platform, null, $rowStopped, $now);
|
||||
expect($stoppedDecision['state'])->toBe('stopped');
|
||||
expect($stoppedDecision['reason'])->toBe('ready_for_manager');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user