feat(прогрев ф2): Яндекс-заливка берёт только строки firm_channels warming+active
Фаза 2 Этап A, Task 5. SyncAdAudienceJob выбирает фирмы не по ch_yandex, а по наличию строки firm_channels(channel=yandex, status=warming), и решает по этой строке через decideForChannelRow (funnel/flat). Фирма с yandex-строкой loaded (не греется) в сегмент не попадает, даже если ch_yandex=true. Kill-switch enabled и token-guard — не тронуты (защита денег владельца). prospectConnection, client/replace/last_error — без изменений. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -63,15 +63,21 @@ class SyncAdAudienceJob implements ShouldQueue
|
||||
return;
|
||||
}
|
||||
|
||||
// Состав считаем пофирменно по срокам Яндекса. Берём только фирмы, кого
|
||||
// начальник пометил Яндексом (v8.79); номер без хозяина (сирота до v8.77)
|
||||
// в фирмы не входит и в сегмент не попадёт. Решение по срокам ЯНДЕКСА, а не
|
||||
// по сводному phone.state: погасшая по Яндексу фирма уходит из сегмента,
|
||||
// даже если сводка active из-за другой площадки.
|
||||
// Состав считаем пофирменно по срокам Яндекса. Берём только фирмы, у кого
|
||||
// ЕСТЬ строка firm_channels(channel=yandex, status=warming) — Фаза 2:
|
||||
// раздельные каналы, ch_yandex больше не источник (та фирма могла уже
|
||||
// выгореть на Яндексе и стоять только на другой площадке). Номер без
|
||||
// хозяина (сирота до v8.77) в фирмы не входит и в сегмент не попадёт.
|
||||
// Решение по срокам ЯНДЕКСА конкретной СТРОКИ канала, а не по сводному
|
||||
// phone.state: погасшая по Яндексу фирма уходит из сегмента, даже если
|
||||
// сводка active из-за другой площадки (funnel/flat решает decideForChannelRow).
|
||||
$now = CarbonImmutable::now();
|
||||
$decider = new PlatformWarmingDecider;
|
||||
|
||||
$firms = SalesAdAudienceFirm::forYandex()->with('phones')->get();
|
||||
$firms = SalesAdAudienceFirm::whereHas(
|
||||
'firmChannels',
|
||||
fn ($q) => $q->where('channel', 'yandex')->where('status', 'warming')
|
||||
)->with(['phones', 'firmChannels'])->get();
|
||||
|
||||
/** @var Collection<int, SalesProspect> $prospects */
|
||||
$prospects = SalesProspect::on($this->prospectConnection)
|
||||
@@ -82,8 +88,12 @@ class SyncAdAudienceJob implements ShouldQueue
|
||||
/** @var array<int, string> $phones */
|
||||
$phones = [];
|
||||
foreach ($firms as $firm) {
|
||||
$row = $firm->channel('yandex');
|
||||
if ($row === null) {
|
||||
continue;
|
||||
}
|
||||
$prospect = $firm->prospect_id ? $prospects->get($firm->prospect_id) : null;
|
||||
if ($decider->decide($firm, $platform, $prospect, $now)['state'] !== 'active') {
|
||||
if ($decider->decideForChannelRow($firm, $platform, $prospect, $row, $now)['state'] !== 'active') {
|
||||
continue;
|
||||
}
|
||||
foreach ($firm->phones as $phone) {
|
||||
|
||||
@@ -39,6 +39,10 @@ it('номер без хозяина в рекламу не попадает', f
|
||||
]);
|
||||
|
||||
$firm = SalesAdAudienceFirm::create(['firm_name' => 'Тестовая стоматология', 'ch_yandex' => true]);
|
||||
$firm->firmChannels()->create([
|
||||
'channel' => 'yandex', 'status' => 'warming', 'mode' => 'funnel',
|
||||
'warming_started_at' => $firm->warmup_started_at ?? now(), 'warmed_times' => 1,
|
||||
]);
|
||||
|
||||
SalesAdAudiencePhone::create(['phone' => '79990000001', 'firm_id' => $firm->id, 'expires_at' => now()->addDays(3), 'state' => 'active']);
|
||||
SalesAdAudiencePhone::create(['phone' => '79990000009', 'firm_id' => null, 'expires_at' => now()->addDays(3), 'state' => 'active']);
|
||||
@@ -74,6 +78,10 @@ it('заливает номера фирмы, активной по срокам
|
||||
$fresh = SalesAdAudienceFirm::create([
|
||||
'firm_name' => 'Свежая', 'warmup_started_at' => now(), 'ch_yandex' => true,
|
||||
]);
|
||||
$fresh->firmChannels()->create([
|
||||
'channel' => 'yandex', 'status' => 'warming', 'mode' => 'funnel',
|
||||
'warming_started_at' => now(), 'warmed_times' => 1,
|
||||
]);
|
||||
SalesAdAudiencePhone::create([
|
||||
'phone' => '79990000001', 'firm_id' => $fresh->id,
|
||||
'expires_at' => now()->addDays(30), 'state' => 'active',
|
||||
@@ -83,6 +91,10 @@ it('заливает номера фирмы, активной по срокам
|
||||
$gone = SalesAdAudienceFirm::create([
|
||||
'firm_name' => 'Погасшая', 'warmup_started_at' => now()->subDays(10), 'ch_yandex' => true,
|
||||
]);
|
||||
$gone->firmChannels()->create([
|
||||
'channel' => 'yandex', 'status' => 'warming', 'mode' => 'funnel',
|
||||
'warming_started_at' => now()->subDays(10), 'warmed_times' => 1,
|
||||
]);
|
||||
SalesAdAudiencePhone::create([
|
||||
'phone' => '79990000002', 'firm_id' => $gone->id,
|
||||
'expires_at' => now()->addDays(30), 'state' => 'active',
|
||||
@@ -127,6 +139,10 @@ it('фирму, помеченную только для ВК, в Яндекс
|
||||
$yandexFirm = SalesAdAudienceFirm::create([
|
||||
'firm_name' => 'Греем в Яндексе', 'warmup_started_at' => now(), 'ch_yandex' => true,
|
||||
]);
|
||||
$yandexFirm->firmChannels()->create([
|
||||
'channel' => 'yandex', 'status' => 'warming', 'mode' => 'funnel',
|
||||
'warming_started_at' => now(), 'warmed_times' => 1,
|
||||
]);
|
||||
SalesAdAudiencePhone::create([
|
||||
'phone' => '79990000001', 'firm_id' => $yandexFirm->id,
|
||||
'expires_at' => now()->addDays(30), 'state' => 'active',
|
||||
@@ -135,6 +151,10 @@ it('фирму, помеченную только для ВК, в Яндекс
|
||||
$vkFirm = SalesAdAudienceFirm::create([
|
||||
'firm_name' => 'Греем в ВК', 'warmup_started_at' => now(), 'ch_vk' => true,
|
||||
]);
|
||||
$vkFirm->firmChannels()->create([
|
||||
'channel' => 'vk', 'status' => 'warming', 'mode' => 'funnel',
|
||||
'warming_started_at' => now(), 'warmed_times' => 1,
|
||||
]);
|
||||
SalesAdAudiencePhone::create([
|
||||
'phone' => '79990000002', 'firm_id' => $vkFirm->id,
|
||||
'expires_at' => now()->addDays(30), 'state' => 'active',
|
||||
@@ -168,6 +188,14 @@ it('фирма, погасшая по срокам Яндекса, в сегме
|
||||
'firm_name' => 'На обеих', 'warmup_started_at' => now()->subDays(5),
|
||||
'ch_yandex' => true, 'ch_vk' => true,
|
||||
]);
|
||||
$firm->firmChannels()->create([
|
||||
'channel' => 'yandex', 'status' => 'warming', 'mode' => 'funnel',
|
||||
'warming_started_at' => now()->subDays(5), 'warmed_times' => 1,
|
||||
]);
|
||||
$firm->firmChannels()->create([
|
||||
'channel' => 'vk', 'status' => 'warming', 'mode' => 'funnel',
|
||||
'warming_started_at' => now()->subDays(5), 'warmed_times' => 1,
|
||||
]);
|
||||
SalesAdAudiencePhone::create([
|
||||
'phone' => '79990000077', 'firm_id' => $firm->id,
|
||||
'expires_at' => now()->addDays(30), 'state' => 'active', // сводка active
|
||||
@@ -183,3 +211,51 @@ it('фирма, погасшая по срокам Яндекса, в сегме
|
||||
return true;
|
||||
});
|
||||
});
|
||||
|
||||
it('фирма со строкой yandex warming+active — номер попадает в сегмент (Task 5)', function () {
|
||||
// Источник Task 5: не forYandex()/ch_yandex, а строка firm_channels
|
||||
// (channel=yandex, status=warming). Решение по ней же — decideForChannelRow.
|
||||
Http::fake(['api-audience.yandex.ru/*' => Http::response(['result' => true], 200)]);
|
||||
|
||||
$firm = SalesAdAudienceFirm::create(['firm_name' => 'Тёплая по Яндексу', 'ch_yandex' => true]);
|
||||
$firm->firmChannels()->create([
|
||||
'channel' => 'yandex', 'status' => 'warming', 'mode' => 'funnel',
|
||||
'warming_started_at' => now(), 'warmed_times' => 1,
|
||||
]);
|
||||
SalesAdAudiencePhone::create([
|
||||
'phone' => '79990000031', 'firm_id' => $firm->id,
|
||||
'expires_at' => now()->addDays(30), 'state' => 'active',
|
||||
]);
|
||||
|
||||
SyncAdAudienceJob::dispatchSync();
|
||||
|
||||
Http::assertSent(function (Request $r) {
|
||||
$file = collect($r->data())->firstWhere('name', 'file');
|
||||
$csv = is_array($file) && is_string($file['contents'] ?? null) ? $file['contents'] : (string) $r->body();
|
||||
|
||||
return str_contains($r->url(), 'modify_data') && str_contains($csv, '79990000031');
|
||||
});
|
||||
});
|
||||
|
||||
it('фирма со строкой yandex loaded (не греется) — номер НЕ в сегменте, даже с ch_yandex=true (Task 5)', function () {
|
||||
Http::fake(['api-audience.yandex.ru/*' => Http::response(['result' => true], 200)]);
|
||||
|
||||
$firm = SalesAdAudienceFirm::create(['firm_name' => 'Уже загруженная', 'ch_yandex' => true]);
|
||||
$firm->firmChannels()->create([
|
||||
'channel' => 'yandex', 'status' => 'loaded', 'mode' => 'funnel',
|
||||
'warming_started_at' => now()->subDays(20), 'warmed_times' => 1,
|
||||
]);
|
||||
SalesAdAudiencePhone::create([
|
||||
'phone' => '79990000032', 'firm_id' => $firm->id,
|
||||
'expires_at' => now()->addDays(30), 'state' => 'active',
|
||||
]);
|
||||
|
||||
SyncAdAudienceJob::dispatchSync();
|
||||
|
||||
Http::assertSent(function (Request $r) {
|
||||
$file = collect($r->data())->firstWhere('name', 'file');
|
||||
$csv = is_array($file) && is_string($file['contents'] ?? null) ? $file['contents'] : (string) $r->body();
|
||||
|
||||
return str_contains($r->url(), 'modify_data') && ! str_contains($csv, '79990000032');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user