Files
portal/app/app/Services/Advertising/CreativeIdMatcher.php
T
Дмитрий 542b7d2d22 fix реклама за показы: очередь робота больше не встаёт колом, доклад об успехе не врёт, тихий ноль ловится
Р6 — задание, брошенное «в работе», держало очередь для всех клиентов навсегда.
Причина сбоя обрезается до 900 знаков, иначе портал отвергал отчёт о самом частом
виде поломки. Провал отчёта больше не глотается молча — едет в письмо человеку.
Приём отчёта ловит любую ошибку и закрывает задание сбойным вместо 500 роботу.
Новый сторож creative-jobs:reap каждые 10 минут разбирает пробку.

Р7 — обрыв на докладе «готово» объявлял провалом уже сделанную работу. Доклад
вынесен из-под обработчика сбоя, повторяется трижды, при неудаче только письмо.
Осечка ухода со страницы после «Создать» больше не считается провалом загрузки.

Р8 — пустой список размеров считался успехом, и робот заливал одни и те же файлы
по кругу, оставляя мусор в живом кабинете. Теперь громкий отказ до похода в Яндекс.

Портал 274/274, робот 41/41. Все защиты проверены вырезанием.
Выходов снятия заморозки денег по-прежнему четыре.
2026-07-27 20:42:23 +03:00

70 lines
3.4 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Services\Advertising;
use App\Exceptions\Advertising\CreativeMatchFailedException;
/**
* Опознание креативов, загруженных роботом в веб-кабинет Яндекса.
*
* Имя опознать не помогает: всем загруженным креативам Яндекс сам ставит имя
* «Новый креатив» (проверено живым запросом 27.07.2026). Поэтому берём разницу
* слепков `creatives.get` «до» и «после» загрузки и раскладываем новые креативы
* по размеру. Чистая функция — без БД и без сети.
*/
final class CreativeIdMatcher
{
/**
* @param array<int, array{0: int, 1: int}> $before слепок до загрузки: номер → [ш, в]
* @param array<int, array{0: int, 1: int}> $after слепок после загрузки
* @param list<array{0: int, 1: int}> $expectedSizes размеры, которые робот должен был залить
* @return array<string, int> «300x250» → номер креатива
*
* @throws CreativeMatchFailedException
*/
public function match(array $before, array $after, array $expectedSizes): array
{
// Пустой список ожидаемых размеров — не «всё сошлось», а тихий ноль: цикл ниже
// просто не выполнится и вернёт пустоту как успех. Задание пометится «готово»
// с нулём проставленных номеров, запуск снова увидит баннеры без креативов и
// поставит новое задание — робот будет заливать те же файлы по кругу, оставляя
// каждый раз пачку мусорных креативов в живом кабинете. Ловим это громко.
if ($expectedSizes === []) {
throw new CreativeMatchFailedException(
'Опознавать нечего: список ожидаемых размеров пуст. Так бывает, если баннеры кампании не видны '
.'служебной роли — проверьте, что после выката перезапущен db/03_service_bypass_policies.sql.'
);
}
$newIds = array_diff_key($after, $before);
$bySize = [];
foreach ($newIds as $id => [$w, $h]) {
$bySize[$w.'x'.$h][] = (int) $id;
}
$out = [];
foreach ($expectedSizes as [$w, $h]) {
$key = $w.'x'.$h;
$found = $bySize[$key] ?? [];
if (count($found) === 0) {
throw new CreativeMatchFailedException(
"После загрузки не появился креатив размера {$key}."
);
}
if (count($found) > 1) {
throw new CreativeMatchFailedException(
"После загрузки появилось несколько креативов размера {$key} — какой чей, не определить."
);
}
$out[$key] = $found[0];
}
return $out;
}
}