fix реклама Яндекса: сегмент Аудиторий указывается с приставкой 20 — без неё Директ отвечает «объект не найден»
Настоящая причина, по которой портал не мог завести кампанию. Прежний диагноз «сегменту нужно не меньше 1000 опознанных людей» оказался ВЫДУМКОЙ: он был построен на статьях, а не на замере, и увёл в ожидание, которое ничего не решало. Что происходит. Сегмент Аудиторий указывается в условии ретаргетинга не своим номером, а номером с приставкой 20: сегмент 58034825 уходит как ExternalId 2058034825. Без приставки Директ ищет цель Метрики с таким номером и честно отвечает 8800 «Object not found» — тем же отказом, что и на несуществующий сегмент. Поэтому поломка выглядела как «аудитория не готова». Замер на боевом кабинете 30.07 на одном и том же ГОТОВОМ сегменте: ExternalId 58034825 -> отказ 8800 «Object not found» ExternalId 2058034825 -> условие создано, № 41678818 После починки то же самое кодом портала: условие № 41678854 создано и убрано за собой. Приставку показал сам Яндекс: у уже существующего в кабинете условия на этот сегмент retargetinglists.get возвращает ровно "ExternalId": 2058034825. Документация про приставки говорит глухо и только «для интересов» — живой ответ кабинета оказался надёжнее документации. Попутно снят ложный «дефект продукта». Порога в 1000 человек НЕ существует: сегмент годится для Директа при 134 опознанных людях из 151 номера. Минимум портала менять не нужно. Ещё один замер, важный на будущее: статус is_processed у сегмента — это НЕ «готов», у готового статус processed, заполнены matched_quantity и cookies_matched_quantity, и стоит can_create_dependent. Судить о готовности только по can_create_dependent. Тестов на приставку было ДВА, и оба закрепляли поломку: проверяли, что уходит номер БЕЗ приставки. Оба были зелёные всё время. Тест, написанный по нашему представлению о правде, а не по ответу живой системы, охраняет баг и делает его невидимым. Теперь оба проверяют приставку и падают без неё; реклама портала 43 из 43. Вместе с починкой ложатся три промта смен: 29.07 сведение телеграма и прокси, 29.07 робот креативов в работу и 30.07 состояние после живой приёмки. В последнем ложный диагноз про порог в 1000 человек помечен как неверный прямо в шапке — чтобы следующая смена не пошла по нему второй раз. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -29,6 +29,14 @@ final class YandexDirectClient
|
||||
* for goal or segment of Yandex.Metrica», а следом «Object not found» — довод считается
|
||||
* негодным целиком, и запуск кампании встаёт. Поймано живым запуском 29.07.2026.
|
||||
* Берём срок из настроек кампании: сколько дней назад собирали аудиторию.
|
||||
*
|
||||
* 🔴 Номер сегмента уходит С ПРИСТАВКОЙ «20»: сегмент 58034825 → ExternalId 2058034825.
|
||||
* Приставка говорит Директу, ЧТО за объект ему дали: без неё он ищет цель Метрики
|
||||
* с таким номером и отвечает «Object not found» — тем же отказом, что на несуществующий
|
||||
* сегмент, поэтому поломка год могла бы выглядеть как «аудитория не готова».
|
||||
* Замер 30.07.2026 на боевом кабинете, один и тот же ГОТОВЫЙ сегмент: без приставки —
|
||||
* отказ 8800, с приставкой — условие создано. Приставку показал сам Яндекс: у готового
|
||||
* условия в кабинете `retargetinglists.get` возвращает ровно `ExternalId: 2058034825`.
|
||||
*/
|
||||
public function addRetargetingList(string $name, int $segmentExternalId, int $membershipDays = 30): int
|
||||
{
|
||||
@@ -39,7 +47,7 @@ final class YandexDirectClient
|
||||
'Rules' => [[
|
||||
'Operator' => 'ALL',
|
||||
'Arguments' => [[
|
||||
'ExternalId' => $segmentExternalId,
|
||||
'ExternalId' => (int) ('20'.$segmentExternalId),
|
||||
// Яндекс принимает от 1 до 540 дней.
|
||||
'MembershipLifeSpan' => max(1, min(540, $membershipDays)),
|
||||
]],
|
||||
@@ -233,7 +241,7 @@ final class YandexDirectClient
|
||||
// Тогда портал не может снять слепок, задание роботу не выдаётся, а клиент
|
||||
// видит вечное «готовим картинки». Поймано живым запуском 29.07.2026:
|
||||
// прежде ходили в песочницу, а она отвергала запрос раньше, ещё на входе.
|
||||
'SelectionCriteria' => new \stdClass(),
|
||||
'SelectionCriteria' => new \stdClass,
|
||||
'FieldNames' => ['Id', 'Type', 'Width', 'Height'],
|
||||
'Page' => ['Limit' => self::PAGE_LIMIT, 'Offset' => $offset],
|
||||
]);
|
||||
|
||||
@@ -25,7 +25,8 @@ it('adds a retargeting list referencing an audience segment', function () {
|
||||
&& $body['method'] === 'add'
|
||||
&& $body['params']['RetargetingLists'][0]['Type'] === 'AUDIENCE'
|
||||
&& $body['params']['RetargetingLists'][0]['Rules'][0]['Operator'] === 'ALL'
|
||||
&& $body['params']['RetargetingLists'][0]['Rules'][0]['Arguments'][0]['ExternalId'] === 58034825;
|
||||
// Сегмент Аудиторий указывается с приставкой «20» — см. addRetargetingList().
|
||||
&& $body['params']['RetargetingLists'][0]['Rules'][0]['Arguments'][0]['ExternalId'] === 2058034825;
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -344,9 +344,39 @@ it('в условие ретаргетинга кладёт срок хране
|
||||
Http::assertSent(function ($req) {
|
||||
$arg = $req->data()['params']['RetargetingLists'][0]['Rules'][0]['Arguments'][0];
|
||||
|
||||
expect($arg['ExternalId'])->toBe(58276009);
|
||||
expect($arg['ExternalId'])->toBe(2058276009);
|
||||
expect($arg['MembershipLifeSpan'])->toBe(30);
|
||||
|
||||
return true;
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* 🔴 Сегмент Аудиторий в условии ретаргетинга указывается НЕ своим номером, а номером
|
||||
* с приставкой «20»: сегмент 58034825 → ExternalId 2058034825. Без приставки живой Директ
|
||||
* отвечает «Object not found» — он ищет цель Метрики с таким номером и, конечно, не находит.
|
||||
*
|
||||
* Замер 30.07.2026 на боевом кабинете, один и тот же ГОТОВЫЙ сегмент:
|
||||
* ExternalId 58034825 → отказ 8800 «Object not found»
|
||||
* ExternalId 2058034825 → условие создано, № 41678818
|
||||
* Приставку подсказал сам Яндекс: у уже существующего в кабинете условия на этот сегмент
|
||||
* в `retargetinglists.get` стоит ровно `"ExternalId": 2058034825`.
|
||||
*
|
||||
* 🪤 Прежний тест закреплял поломку: он проверял, что уходит номер БЕЗ приставки. Тест,
|
||||
* написанный по нашему представлению о правде, а не по ответу живой системы, охраняет баг.
|
||||
*/
|
||||
it('сегмент Аудиторий уходит с приставкой 20 — иначе Директ не находит объект', function () {
|
||||
Http::fake(['*/json/v5/retargetinglists' => Http::response(['result' => ['AddResults' => [['Id' => 7]]]])]);
|
||||
|
||||
$client = new YandexDirectClient('https://api.direct.yandex.com', 'T');
|
||||
|
||||
expect($client->addRetargetingList('Лидерра #6', 58034825, 30))->toBe(7);
|
||||
|
||||
Http::assertSent(function ($req) {
|
||||
$arg = $req->data()['params']['RetargetingLists'][0]['Rules'][0]['Arguments'][0];
|
||||
|
||||
expect($arg['ExternalId'])->toBe(2058034825);
|
||||
|
||||
return true;
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
# Промт для следующей смены — телеграм сведён, дальше выкат и адрес для МТС
|
||||
|
||||
Дата: 29.07.2026, вечер. Свод закрыт и запушен. Открытым остался адрес для кабинета МТС.
|
||||
|
||||
---
|
||||
|
||||
Продолжаем Лидерру. Две линии: **выкат телеграм-модуля** и **адрес для кабинета МТС**.
|
||||
|
||||
## Линия 1 — телеграм сведён с main. ГОТОВО
|
||||
|
||||
**Коммит `77f61fb1`**, ветка **`feat/client-telegram-ads-on-main`**, **в gitea запушено**.
|
||||
314 файлов, +42708 / −148, два родителя: телеграм `6f469fb1` + main `8bdd58e8`. main влит целиком.
|
||||
|
||||
Рабочий каталог `.claude/worktrees/telegram-merge` **полностью снаряжён** — свои `composer install`
|
||||
и `npm ci`, свои бинари в `bin/`, своя тестовая база **`liderra_testing_tgmerge`**. Не выбрасывать:
|
||||
следующий каталог придётся снаряжать заново с нуля.
|
||||
|
||||
🔴 Исходная ветка `feat/client-telegram-ads` и 292 черновика в главном каталоге **не тронуты**.
|
||||
|
||||
### Числа, померенные на своде
|
||||
|
||||
| Что | Результат |
|
||||
|---|---|
|
||||
| Телеграм | 193/193 |
|
||||
| Реклама | 336/336 при 1029 проверках |
|
||||
| Вместе | 532/532 |
|
||||
| Экраны | 1704/1708, 3 пропущено |
|
||||
| Сборка фронта | чисто, 3.4 сек |
|
||||
| Полный Unit+Feature | **3923/3960** |
|
||||
|
||||
🔑 **16 падений полного прогона совпадают построчно с прогоном ветки БЕЗ телеграма** — свод не
|
||||
добавил ни одного. Одиннадцать из них — тесты робота креативов Яндекса, они ходят в **настоящий**
|
||||
Яндекс живым запросом: подмена ответа накрывает не все адреса. Это хвост соседней ветки.
|
||||
|
||||
### Что было починено по дороге
|
||||
|
||||
- **Помощник `rejectedCampaign` переименован в `tgRejectedCampaign`.** Одноимённый есть у рекламы,
|
||||
помощники Pest глобальные — полный прогон падал фаталом. 🔑 Каждая ветка по отдельности этого
|
||||
увидеть не могла: такие мины вылезают **только при сведении**.
|
||||
- **Два теста уведомлений считали ВСЮ таблицу** вместо строк своего пользователя. В одиночку
|
||||
зелёные, в полном прогоне красные.
|
||||
- **Убраны две проверки отменённой сущности** «своё имя отправителя» — она дропнута в v9.27.
|
||||
- **Журнал схемы:** телеграмные v8.86–v8.95 → **v9.18–v9.27**, блок наверх, пометки
|
||||
«предварительный» сняты. Версия `db/schema.sql` не тронута: там v8.85 и у main, и у телеграма.
|
||||
- 🔴 **Авто-починка разметки испортила смысл в `ПИЛОТ.md`**: «**+** нумератор» переписала в
|
||||
«**−** нумератор». Прод-документ. Откачено, переписано на «**и** нумератор».
|
||||
🪤 Авто-фиксы разметки просматривать глазами — «+» в начале русской строки линтер считает списком.
|
||||
|
||||
### Сторож денег поставлен
|
||||
|
||||
`app/tests/Feature/ClientTg/TgMoneyUnderRealRoleTest.php` — три проверки под **боевой ролью**
|
||||
`crm_app_user`, а не суперюзером. С контекстом клиента деньги двигаются, без контекста возврат
|
||||
падает **громко**.
|
||||
|
||||
🔑 **И тут же вскрылось, на чём держится эта громкость:** база без контекста молча отдаёт ноль
|
||||
строк, шумит только `firstOrFail()` в сервисе. Заменят на `first()` — возврат денег за отклонённую
|
||||
модерацию станет тихим. Записано в самом тесте.
|
||||
|
||||
🪤 Тестовая база собирается миграциями, а права ролям на бою раздаёт **отдельный скрипт**
|
||||
`db/02_grants.sql`. Без него сторож мерил бы отсутствующий доступ вместо защиты по клиентам.
|
||||
|
||||
### Что дальше по этой линии
|
||||
|
||||
1. Решить с владельцем: вливать ли `feat/client-telegram-ads-on-main` в `main`.
|
||||
2. Выкат можно делать **не дожидаясь адреса** — модуль поедет с `TG_SANDBOX=true`: только
|
||||
черновик, деньги не двигаются. Робот МТС без адреса не заработает, но это **другая задача**.
|
||||
3. 🔴 При выкате на кластер ПЕРЕзапустить `db/03_service_bypass_policies.sql` — новым RLS-таблицам
|
||||
мало `tenant_isolation`, иначе служебные роли получат тихий ноль.
|
||||
4. Хвост: статанализ исключался при коммите. Из 628 замечаний **623 в тестах** обеих сторон
|
||||
Pest-шум. Если браться — сначала пересобрать baseline под слитое состояние.
|
||||
|
||||
## Линия 2 — адрес для кабинета МТС. ЖДЁМ ПОСТАВЩИКА
|
||||
|
||||
**Куплено сегодня:** резидентный российский адрес, 1 месяц, `91.221.70.204:24009`, логин `lte4`,
|
||||
выход в интернет **95.24.162.70**, срок до **28.08.2026**, смена адреса ручная по ссылке.
|
||||
Списано **$12.28**, баланс **$131.06**.
|
||||
|
||||
🔴 **Не годится.** Замер с нашего сервера через этот прокси:
|
||||
|
||||
| Куда | Что вышло |
|
||||
|---|---|
|
||||
| ya.ru | HTTP 302 за 0.28 сек |
|
||||
| **marketolog.mts.ru** | **обрыв за 0.02 сек** |
|
||||
| mts.ru | обрыв за 0.02 сек |
|
||||
| beeline.ru | обрыв за 0.02 сек |
|
||||
|
||||
Настоящий Chrome через него получает `ERR_TUNNEL_CONNECTION_FAILED`. Это **тот же запрет на сайты
|
||||
операторов**, что на мобильном канале. Их слова «наш домен под ограничения не попадает» замером не
|
||||
подтвердились. **Обходить не пытались и не надо** — это их сознательный запрет.
|
||||
|
||||
🔴 **Отдельно: пропали $53.33.** Баланс сам съехал 196.67 → 143.34, в истории операций строки
|
||||
**нет**, мобильный канал 9493547 **не продлён** — срок как был 04.08.2026 19:50. Сумма копейка в
|
||||
копейку равна цене продления.
|
||||
|
||||
**Обращение №31027** — открыто сегодня, в нём и замер, и вопрос про 53.33. Прежнее №30979 они
|
||||
закрыли **молча, без единого ответа**.
|
||||
|
||||
Проверять появление адреса: `POST https://api.dashboard.proxy.market/dev-api/list/{ключ}` с телом
|
||||
`{"type":"ipv4"}`; ключ в `%TMP%\pm_api_key.txt`. Резидентные в этом списке **не показываются** —
|
||||
их видно только в кабинете. Браузер под управлением: Edge на порту **9222**, профиль
|
||||
`C:\liderra\pm-profile`, вход выполнен.
|
||||
|
||||
🪤 В форме нового обращения кнопка называется **`Create`**, и на странице есть ещё одна —
|
||||
**`Create the ticket`**, которая в DOM стоит РАНЬШЕ. Поиск по началу строки цепляет её и молча
|
||||
пересоздаёт пустую форму. Искать по точному совпадению.
|
||||
🪤 Поле ответа в списке обращений однострочное и режет длинный текст; в форме НОВОГО обращения
|
||||
поле нормальное, 1700 знаков проходят. Вставка не работает — печатать клавиатурой.
|
||||
🪤 Своё фоновое слежение перезагружает вкладку и стирает набранное — останавливать перед работой.
|
||||
|
||||
**Если ответа не будет:** искать другого поставщика резидентных российских адресов без запрета на
|
||||
сайты операторов. Похоже, у Proxy.Market это правило на весь дом, а не на конкретный продукт.
|
||||
|
||||
## Роботы и сервер
|
||||
|
||||
**`liderra-render`** — `51.250.1.97`, доступ `ssh -i ~/.ssh/liderra_deploy ubuntu@51.250.1.97`.
|
||||
Стоят Chrome 150, Xvfb, privoxy:8118, playwright в `/opt/liderra-render`. Соседи — Asterisk, Caddy,
|
||||
рендер 2ГИС; не мешать.
|
||||
|
||||
**Робот Яндекса** от прокси не зависит. Для доселения нужны **от владельца**: логин рекламного
|
||||
кабинета, «прихожая» — остановленная кампания и группа, разрешение прописать `CREATIVE_ROBOT_TOKEN`
|
||||
на боевом.
|
||||
|
||||
🔴 Голосовой сервер `185.182.110.115` **мёртв** — ни пинга, ни одного порта. Александра и Даша
|
||||
лежат там.
|
||||
|
||||
## 🪤🪤 Главная ловушка дня — про инструменты
|
||||
|
||||
**В свежем git-worktree проверки не работают и при этом ВРУТ человеческим текстом.** Первый заход
|
||||
коммита показал пять «замечаний»; три из пяти оказались упавшим инструментом, а не находкой:
|
||||
|
||||
| Проверка | Настоящая причина |
|
||||
|---|---|
|
||||
| «cspell нашёл слова» | пакета `cspell` нет — падение при запуске |
|
||||
| «gitleaks нашёл ПДн» | нет бинаря `bin/gitleaks.exe` |
|
||||
| «lychee нашёл битые ссылки» | нет бинаря `bin/lychee.exe`, потом — два файла, которых нет в git |
|
||||
| «larastan нашёл ошибки» | нет `app/_ide_helper_models.php`, phpstan молча выходит с ошибкой |
|
||||
|
||||
🔑 **Датчик:** проверка жалуется содержательно, но ты не увидел ни одной конкретной строки —
|
||||
значит смотришь на упавший инструмент, а не на находку. Ставить инструмент и перезапускать.
|
||||
|
||||
**Рецепт снаряжения свежего каталога:** `composer install` + `npm ci` в `app/`, `npm ci` в корне,
|
||||
скопировать `bin/gitleaks.exe` и `bin/lychee.exe`, при нужде — `app/_ide_helper_models.php`.
|
||||
🪤 На Windows `vendor` и `node_modules` **нельзя подсунуть ссылкой** — нужен свой установ.
|
||||
🪤 `npm ci --ignore-scripts` оставляет пакеты **обрезанными** — половина файлов отсутствует, а
|
||||
ошибки нет. Ставить полностью.
|
||||
🪤 Настоящие находки тоже были: разрешение gitleaks по **номеру коммита** не переживает сведение
|
||||
ветки — тот же текст приходит новым коммитом, нужен путь.
|
||||
|
||||
## Красные линии
|
||||
|
||||
Прод — живые клиенты и деньги, выкат только по явному «go», БД по умолчанию только чтение.
|
||||
**Пуш только в gitea**, GitHub мёртв. Коммит по эскейпу, сообщение без круглых скобок, в файле,
|
||||
явные пути. **В СМС-модуль не лезть.** Кампанию № 713051718 не трогать. Владелец не программист —
|
||||
говорить простым русским.
|
||||
@@ -0,0 +1,123 @@
|
||||
# Промт для следующей смены — запустить рекламу в Яндексе в работу
|
||||
|
||||
Дата: 29.07.2026, вечер. Всё ниже **проверено живой командой сегодня**, а не по памяти.
|
||||
|
||||
---
|
||||
|
||||
Задача одна: **довести рекламу в Яндексе до работающего состояния на боевом liderra.ru.**
|
||||
Портал умеет всё, кроме одного шага — картиночный креатив программно не создать (приём
|
||||
`creatives.add` берёт только видео, конструктор Яндекс закрыл 01.06.2026). Этот шаг делает
|
||||
робот-грузчик в браузере. Робота написали, живьём он ни разу не работал.
|
||||
|
||||
## Что уже живо на боевом
|
||||
|
||||
- **Загрузка аудиторий в Яндекс работает** — ключ `YANDEX_AUDIENCE_TOKEN` прописан,
|
||||
в `sales_ad_audience_platforms` 3 записи.
|
||||
- Экран «Яндекс Аудитория» открыт клиентам с 25.07.
|
||||
- Кампаний в базе **2, обе черновики** (клиент 2 от 25.07, клиент 10 от 29.07 05:42).
|
||||
|
||||
## Что мертво (и почему)
|
||||
|
||||
| Что | Состояние на 29.07 | Что это значит |
|
||||
|---|---|---|
|
||||
| `YANDEX_DIRECT_ENABLED` | **`false`** | канал Директа закрыт флагом |
|
||||
| `YANDEX_DIRECT_TOKEN` | прописан | ключ есть, ждёт включения |
|
||||
| `CREATIVE_ROBOT_TOKEN` | **отсутствует** | дверь робота в портал закрыта наглухо |
|
||||
| `ad_creative_jobs` | **0 строк** | робот не отрабатывал ни разу |
|
||||
| Каталог `bots/` на боевом | **отсутствует** | 🔴 выкат копирует только `app/`, роботы лежат рядом в `bots/` — они на прод НЕ приезжают |
|
||||
| Робот на рендер-виртуалке | **отсутствует** | там только служба снимков сайтов |
|
||||
|
||||
## Три вещи, без которых не начинать — они за владельцем
|
||||
|
||||
1. **Логин рекламного кабинета Яндекса**, под которым будет работать робот. Идёт в адресах
|
||||
Директа параметром `ulogin`.
|
||||
2. **«Прихожая»** — заранее заведённые в кабинете **остановленная** кампания и группа
|
||||
(`YC_ANTEROOM_CAMPAIGN_ID`, `YC_ANTEROOM_GROUP_ID`). Робот открывает форму нового
|
||||
объявления в них только чтобы дойти до окна «Загрузить креативы», ничего не сохраняет.
|
||||
🔑 Прихожая обязана быть отдельной остановленной кампанией — тогда робот не приближается
|
||||
к работающей рекламе вовсе.
|
||||
3. **Разрешение** прописать `CREATIVE_ROBOT_TOKEN` на боевом и включить
|
||||
`YANDEX_DIRECT_ENABLED=true`.
|
||||
|
||||
Плюс от владельца понадобятся **глаза и телефон один раз** — вход в кабинет заводится
|
||||
руками (пароль + код из СМС) и дальше живёт в папке профиля браузера.
|
||||
|
||||
## Что такое `CREATIVE_ROBOT_TOKEN` (владелец спрашивал)
|
||||
|
||||
Это **пароль на служебную дверь «робот → портал»**, к Яндексу отношения не имеет, никто
|
||||
его не выдаёт — придумываем сами длинной случайной строкой. Робот стучится в портал по
|
||||
трём адресам (`/api/creative-robot/next`, `.../jobs/{id}/banners/{id}/file`,
|
||||
`.../jobs/{id}/done` + `/inspection`) и показывает заголовок `X-Creative-Robot-Token`.
|
||||
Проверка — `app/app/Http/Middleware/CreativeRobotToken.php`. Пароль пишется в двух местах:
|
||||
боевой `.env` портала и `.env` робота. Пустой в настройках = дверь закрыта (401) — сейчас
|
||||
именно так, и это нарочно.
|
||||
|
||||
## Где робот жить будет — рекомендация
|
||||
|
||||
**Рендер-виртуалка `liderra-render` 51.250.1.97**, а не боевой сервер: браузером боевой
|
||||
нагружать не надо. Проверено сегодня, что там есть:
|
||||
|
||||
- `Xvfb` — есть; `node v20.20.2`; `google-chrome` системный; playwright-браузер
|
||||
`chromium-1228` в `~/.cache/ms-playwright`;
|
||||
- диск 15 ГБ свободно, память 7.9 ГБ (занято 0.4);
|
||||
- служба-сосед `liderra-render.service` — **не мешать**, там же Asterisk и Caddy.
|
||||
|
||||
🔴 **VNC/RDP на виртуалке НЕТ** (`x11vnc`/`xrdp` не установлены). Для разового входа глазами
|
||||
их надо поставить и пробросить портом через SSH-туннель — наружу порт не открывать.
|
||||
|
||||
Доступ: `ssh -i ~/.ssh/liderra_deploy ubuntu@51.250.1.97`.
|
||||
|
||||
## Порядок работ
|
||||
|
||||
1. **Спросить у владельца три вещи выше.** Без прихожей и логина дальше идти нельзя.
|
||||
2. Прочитать `bots/yandex-creatives/README.md` и `docs/cabinet-flow.md` (разметка экранов
|
||||
кабинета, снята живьём) — там же список писем-алярмов и что делать по каждому.
|
||||
3. Разложить робота на рендер-виртуалку: скопировать `bots/yandex-creatives`, свой
|
||||
`npm ci` (playwright + nodemailer + dotenv), заполнить `.env` из `.env.example`.
|
||||
4. Поставить `x11vnc`, поднять `Xvfb`, пробросить порт туннелем, позвать владельца —
|
||||
`npm run login`. Профиль ложится в `YC_BROWSER_PROFILE_DIR`.
|
||||
5. Прописать `CREATIVE_ROBOT_TOKEN` в боевой `.env` портала (тот же, что у робота) +
|
||||
`php artisan config:cache` **от www-data** (🪤 квирк 107: под root ломает прод).
|
||||
6. Проверить дверь: запрос к `/api/creative-robot/next` без заголовка → 401, с верным
|
||||
заголовком → осмысленный ответ.
|
||||
7. Включить `YANDEX_DIRECT_ENABLED=true`. **Это открывает деньги и показы** — только по
|
||||
явному «да» владельца.
|
||||
8. Расписание на виртуалке: `run:once` часто, `keepalive` раз в ~15 минут. Наложиться они
|
||||
не могут — общий файл-замок `robot.lock`, занят → выход с кодом 0.
|
||||
9. **Живая приёмка на одной кампании**: завести кампанию с баннерами, дождаться задания,
|
||||
посмотреть, что робот залил креативы, портал сверил список до/после и кампания поехала.
|
||||
Смотреть глазами в кабинете, а не только в журнал.
|
||||
|
||||
## Ловушки, известные заранее
|
||||
|
||||
🪤 **Выкат портала не везёт `bots/`.** Каталог лежит в корне репозитория, а rsync идёт из
|
||||
`app/`. Робота на любую машину класть отдельно, руками.
|
||||
|
||||
🪤 **11 падений полного прогона тестов — это тесты робота креативов**, они ходят в
|
||||
**настоящий** Яндекс живым запросом: подмена ответа накрывает не все адреса. Падают
|
||||
одинаково и с телеграмом, и без него. Не пугаться и не «чинить» вслепую.
|
||||
|
||||
🪤 **Робот не нажимает «Сохранить изменения»** — единственную кнопку, которая меняет
|
||||
кабинет. На это есть тест-сторож. Если появится соблазн «дожать» — сначала подумать, зачем
|
||||
сторож ставили.
|
||||
|
||||
🪤 **Снимки экрана робота показывают логин и остаток счёта** в боковой панели кабинета.
|
||||
Робот сам чистит старше двух недель; в переписку и в git их не тащить.
|
||||
|
||||
🪤 **Повторная заливка оставляет дубли креативов**, вычищать можно только руками. Если
|
||||
письмо-алярм кончается словами «Креативы в кабинет уже загружены» — сначала посмотреть
|
||||
кабинет, потом решать про перезапуск.
|
||||
|
||||
## Красные линии
|
||||
|
||||
Прод — живые клиенты и деньги, любое включение только по явному «да» владельца, БД по
|
||||
умолчанию только чтение. **Пуш только в gitea**, GitHub мёртв. Коммит по эскейпу, сообщение
|
||||
без круглых скобок, в файле, явные пути. **В СМС-модуль не лезть.** Телеграм-кампанию
|
||||
№ 713051718 не трогать. Владелец не программист — говорить простым русским.
|
||||
|
||||
## Сегодняшний фон (не мешает, но знать)
|
||||
|
||||
- Телеграм-модуль выкачен на боевой 29.07 в песочнице (`TG_SANDBOX=true`), деньги не
|
||||
двигаются. Подробности — `2026-07-29-HANDOFF-telegram-sveden-i-proksi.md`.
|
||||
- Адрес для кабинета МТС не найден, обращение к Proxy.Market №31027 открыто. **К Яндексу
|
||||
отношения не имеет** — робот Яндекса от прокси не зависит.
|
||||
@@ -0,0 +1,210 @@
|
||||
# Промт для следующей смены — реклама Яндекса после живой приёмки
|
||||
|
||||
Дата: 30.07.2026, утро. Всё ниже **проверено живой командой**, а не по памяти.
|
||||
Предыдущий промт — `2026-07-29-HANDOFF-yandex-robot-v-rabotu.md` (он уже исполнен).
|
||||
|
||||
> 🔴 **Правка от 30.07 09:00 МСК.** Первая версия этого промта содержала **неверный диагноз**:
|
||||
> будто Директ отказывает из-за порога «не меньше 1000 опознанных людей» в сегменте.
|
||||
> Это оказалось неправдой, и вся ветка «нужен список побольше» была ложной. Настоящих причин
|
||||
> было **две**, обе найдены и одна из них — наша ошибка в коде. Текст ниже исправлен.
|
||||
|
||||
---
|
||||
|
||||
Задача одна: **довести до конца последний шаг рекламы в Яндексе и убрать за приёмкой.**
|
||||
Связка «портал → робот → кабинет Яндекса → опознание креативов → условие ретаргетинга
|
||||
в Директе» **работает и проверена живьём по частям**. Не сделан последний стык: кампания
|
||||
№ 6 ещё не заведена в Директе, и упирается это теперь **только в сторону Яндекса**.
|
||||
|
||||
## Что живо и проверено настоящим прогоном 29–30.07
|
||||
|
||||
| Что | Состояние |
|
||||
|---|---|
|
||||
| Робот на рендер-виртуалке `51.250.1.97`, `/opt/liderra-creative-robot` | стоит, **84 из 84** проверок зелёные |
|
||||
| Вход в кабинет `sasha261185` | заведён глазами владельца, живёт в профиле, `keepalive` подтверждает |
|
||||
| Расписание робота | `…-run.timer` каждые 2 мин, `…-keepalive.timer` каждые 15 мин, оба **включены** |
|
||||
| Дверь «робот → портал» | без пароля 401, с верным 200 |
|
||||
| Заливка картинок роботом | **15 из 15**, задание закрыто как `done` |
|
||||
| Опознание креативов порталом | **15 из 15** номеров проставлены баннерам |
|
||||
| **Условие ретаргетинга в Директе кодом портала** | **создано живьём** (№ 41678854 на готовом сегменте, сразу убрано) |
|
||||
| Разведка причины отказа | читает обе причины целиком, 5–6 с на объявление |
|
||||
|
||||
Доступ к виртуалке робота: `ssh -i ~/.ssh/liderra_deploy ubuntu@51.250.1.97` (отдельной строки
|
||||
в `~/.ssh/config` нет). Боевой — `ssh liderra-prod` (через бастион gitea).
|
||||
|
||||
## Кампания-подопытная
|
||||
|
||||
- Кампания **№ 6**, фирма **2** (`info@lkomega.ru`), имя «Приёмка робота — займ под залог».
|
||||
- Статус **черновик**, режим ручной, свой список номеров.
|
||||
- **1693 номера** — графа Д из `C:\Users\Administrator\Downloads\1785082209_report_606058_380216.xlsx`
|
||||
(выгрузка лидов, личные мобильные). 🔴 Это ПДн: в git не тащить, в переписку не выводить.
|
||||
- Сегмент Аудиторий **58283031**, файл переварен целиком, 1693 из 1693 годных.
|
||||
- Смета 1693 показа, клиенту ≈ **203,16 ₽**. В рекламном кошельке фирмы **300 ₽**,
|
||||
заморожено 0, не списано ничего.
|
||||
- 15 баннеров нарисованы Claude: «ЗАЙМ ПОД ЗАЛОГ АВТО» + обязательное предупреждение про
|
||||
условия займа на 22 % высоты. 🪤 На 320×50 надпись 4 пункта — площадь требованию отвечает,
|
||||
но модерация может придраться к читаемости.
|
||||
- Все выгрузки лидов владельца вместе дают всего **1873** личных мобильных (замер 30.07):
|
||||
больше номеров из этого источника взять негде, и **этого больше не требуется** — см. ниже.
|
||||
|
||||
## Почему кампания не заводилась — две причины, не одна
|
||||
|
||||
Директ отвечал **8800 «Object not found»**. Долго считалось, что дело в размере аудитории.
|
||||
**Это неверно.** Разбор 30.07 дал две независимые причины:
|
||||
|
||||
**Причина 1 — наша ошибка в коде (исправлена, проверена живьём).**
|
||||
Сегмент Аудиторий указывается в условии ретаргетинга **не своим номером, а с приставкой
|
||||
«20»**: сегмент `58034825` → `ExternalId 2058034825`. Мы посылали номер без приставки, Директ
|
||||
искал цель Метрики с таким номером и честно отвечал «объект не найден» — тем же отказом, что
|
||||
и на несуществующий сегмент. Отсюда и путаница.
|
||||
|
||||
Замер на одном и том же **готовом** сегменте:
|
||||
|
||||
| Что послали | Ответ живого Директа |
|
||||
|---|---|
|
||||
| `ExternalId 58034825` (как было) | отказ 8800 «Object not found» |
|
||||
| `ExternalId 2058034825` (с приставкой) | **условие создано**, № 41678818 |
|
||||
|
||||
Приставку показал **сам Яндекс**: у уже существующего в кабинете условия `retargetinglists.get`
|
||||
возвращает ровно `"ExternalId": 2058034825`. Документация Директа про приставки говорит
|
||||
глухо (10/20/30 — «для интересов»), живой ответ кабинета оказался надёжнее.
|
||||
|
||||
**Причина 2 — Яндекс ещё не сопоставил наши номера (ждём, ускорить нельзя).**
|
||||
У свежего сегмента поля `matched_quantity` / `matched_percentage` **пустые**, статус
|
||||
`is_processed`, в кабинете «Обрабатывается». Пока сопоставления нет, Директ сегмента не видит
|
||||
даже с правильной приставкой.
|
||||
|
||||
🪤 **Ловушка на будущее:** `is_processed` — это НЕ «готов». У готового сегмента статус
|
||||
`processed` (без `is_`), заполнены `matched_quantity` и `cookies_matched_quantity`,
|
||||
и стоит `can_create_dependent: true`. Судить о готовности **только** по `can_create_dependent`.
|
||||
|
||||
**Порога в 1000 человек не существует.** Живое доказательство: сегмент 58034825 годится
|
||||
для Директа при **134 опознанных людях** из 151 номера. Прежнее утверждение про «минимум
|
||||
1000» — выдумка, построенная на статьях, а не на замере. Сюда же: **никакого дефекта
|
||||
«портал пишет 100, а надо 1000» нет** — минимум портала трогать не нужно.
|
||||
|
||||
## Что сейчас делает сторож — и что делать смене
|
||||
|
||||
Служба `liderra-priemka-storozh` (скрипт `/usr/local/bin/storozh.sh`) переписана 30.07:
|
||||
раз в 10 минут пишет в журнал **живые цифры** (статус, сколько людей опознано, охват),
|
||||
ждёт до **48 часов** и при `can_create_dependent: ДА` сам запускает кампанию через
|
||||
`/tmp/zapusk.php 6`.
|
||||
|
||||
**Первое действие смены — `sudo tail -20 /var/log/liderra-priemka-storozh.log`.** Три ветки:
|
||||
|
||||
1. Строка «КАМПАНИЯ ПОЕХАЛА» — связка доказана целиком. Дальше: посмотреть кабинет глазами,
|
||||
дождаться вердикта модерации, проверить окно передачи отказов на живом отказе.
|
||||
2. В журнале появились цифры опознанных, но `годен нет` — Яндекс считает, ждать.
|
||||
3. 48 часов прошло, цифры так и не появились — **это уже вопрос к Яндексу**, а не к коду.
|
||||
Написать в поддержку Аудиторий (сегмент 58283031 висит непосопоставленным больше двух
|
||||
суток при 100 % годных строк) и **спросить владельца**, ждать или брать другой список.
|
||||
|
||||
🪤 Сторож стоит **не в git** — только на боевом. Прежняя версия ждала 12 часов и писала
|
||||
глухое «нет»; она бы дождалась готовности и всё равно упала на приставке. Это тот же класс:
|
||||
**сторож, который «проверяет», может не проверять того, что нужно.**
|
||||
|
||||
## 🔴 Главное, что нельзя забыть
|
||||
|
||||
**Починки на боевом лежат руками, а не из хранилища.** Прямо на прод подложены:
|
||||
|
||||
- `app/Services/Advertising/YandexDirectClient.php` (в нём же приставка «20»)
|
||||
- `app/Services/Advertising/CampaignLauncher.php`
|
||||
- на виртуалке `src/cabinet.js`, `bin/run.js`, `bin/keepalive.js`
|
||||
|
||||
В хранилище: ветка **`fix/robot-yandex-zamok`** (коммиты `61ac314d`, `f2bfe3f1` отправлены
|
||||
в gitea; правка приставки — **ещё не закоммичена на момент написания**, спросить эскейп).
|
||||
В main **не влито**. 🪤 Любой обычный выкат из main вернёт все семь дыр обратно.
|
||||
Первое дело — **предложить владельцу влить ветку в main**.
|
||||
|
||||
## Что чинили — чтобы не «починить» обратно
|
||||
|
||||
1. Слепок креативов просил кабинет пустым **массивом** — живой Яндекс отказывает 8000.
|
||||
Нужен пустой **объект**.
|
||||
2. Отказы Яндекса **по позиции внутри ответа** глотались молча, портал падал «нет ключа Id».
|
||||
Теперь слова Яндекса выходят наружу — именно это позволило найти следующий пункт.
|
||||
3. В условии ретаргетинга не было `MembershipLifeSpan`. Границы Яндекса 1..540; **ноль
|
||||
нельзя** при создании, хотя в старых условиях кабинета нули стоят.
|
||||
4. Робот не нажимал «Добавить выбранные». «Набор» в кабинете и «креатив» в `creatives.get` —
|
||||
разные вещи: без добавления в черновик формы Яндекс креативы не регистрирует. Замер
|
||||
18 → 32 в ту же секунду. Объявление при этом НЕ сохраняется.
|
||||
5. Гонка: «Создать» открывается на 4-й секунде при 11 принятых файлах из 15, остальные
|
||||
доезжают к 7-й. Три прогона дали 14 из 15, каждый раз пропадала другая картинка.
|
||||
6. Замок робота не снимался — `process.exit()` не даёт выполниться `finally`.
|
||||
7. **Приставка «20» у номера сегмента** (30.07) — см. выше. Тестов на неё было два, и **оба
|
||||
закрепляли поломку**: проверяли, что уходит номер БЕЗ приставки.
|
||||
|
||||
🪤 **Урок из пункта 7:** тест, написанный по нашему представлению о правде, а не по ответу
|
||||
живой системы, **охраняет баг** и делает его невидимым. Оба теста были зелёные всё время.
|
||||
|
||||
## Незакрытый вопрос продукта — решать владельцу
|
||||
|
||||
Порога в 1000 нет, но **задержка сопоставления есть**, и клиент её увидит: он загружает
|
||||
номера, жмёт «Запустить» и получает непонятную ошибку, потому что Яндекс ещё считает.
|
||||
Правильно — не пускать в запуск, пока сегмент не стал годным, и писать по-человечески:
|
||||
«Яндекс ищет ваших клиентов в сети, обычно это занимает несколько часов». Это **меняет
|
||||
поведение продукта** (появляется ожидание вместо мгновенного запуска), поэтому Claude
|
||||
сам не менял. Спросить.
|
||||
|
||||
## Уборка, которую Claude намеренно не делал
|
||||
|
||||
- 🔴 **В кабинете Яндекса ~57 лишних креативов** — четыре набора от прогонов. Все называются
|
||||
«Новый креатив», отличить от настоящих нельзя, ошибка стоила бы боевых креативов владельца.
|
||||
Удалять только владельцу, кнопкой «Удалить набор».
|
||||
- 🔴 **Четыре лишних сегмента** в Аудиториях, все с именем «Лидерра кампания #6»:
|
||||
58275925 (503), 58275978 (503), 58276009 (503), 58282999 (2025).
|
||||
**НЕ удалять:** 58283031 (1693 — рабочий) и 58034825 («Лидерра — кандидаты», в работе).
|
||||
- **300 ₽ на рекламном кошельке фирмы 2** — положены Claude для приёмки, не потрачены.
|
||||
|
||||
## Настройки боевого, изменённые 29–30.07 — и как откатить
|
||||
|
||||
| Строка в `.env` | Стало | Откат |
|
||||
|---|---|---|
|
||||
| `YANDEX_DIRECT_ENABLED` | `true` | `false` |
|
||||
| `YANDEX_DIRECT_BASE_URL` | `https://api.direct.yandex.com` | убрать строку = вернётся песочница |
|
||||
| `CREATIVE_ROBOT_TOKEN` | выпущен | убрать строку = дверь робота закрыта |
|
||||
|
||||
Копии прежнего файла рядом: `.env.bak-2026-07-29-robot`, `.env.bak-2026-07-29-direct-off`,
|
||||
`.env.bak-2026-07-29-sandbox`. Прежняя версия кода с приставкой — рядом с файлом:
|
||||
`YandexDirectClient.php.bak-2026-07-30-bez-pristavki` (восстановлена из коммита `f2bfe3f1`,
|
||||
🪤 первый бэкап затёрся новой версией — проверять бэкап, а не верить ему).
|
||||
После любой правки `.env` — `config:clear` + `config:cache` **от www-data**
|
||||
(🪤 квирк 107: под root ломает прод) и `reload php8.3-fpm`.
|
||||
|
||||
## Ловушки, добытые кровью
|
||||
|
||||
🪤 **Выкат портала не везёт `bots/`** — робота на любую машину класть отдельно, руками.
|
||||
|
||||
🪤 **`php artisan tinker <файл>` открывает интерактив и вешает ssh.** Работает только
|
||||
`--execute`, а для длинного — отдельный php-скрипт с ручным `bootstrap()` в `/tmp`.
|
||||
|
||||
🪤 **`app(YandexDirectClient::class)` не собирается** — у него в конструкторе адрес и токен
|
||||
строками. Создавать через `new` с `config(...)`.
|
||||
|
||||
🪤 **`pkill -f имя-скрипта` через ssh убивает сам себя** — в командной строке удалённого
|
||||
bash встречается то же имя. Гасить по номеру процесса.
|
||||
|
||||
🪤 **Список кампаний/лидов через `tinker` приходит ПУСТЫМ** — это защита по фирмам, а не
|
||||
правда о базе. Читать через `DB::connection('pgsql_admin')`, писать — в транзакции
|
||||
с `SET LOCAL app.current_tenant_id`.
|
||||
|
||||
🪤 **У клиентских пользователей нет токенов** (`createToken` не существует) — кабинет на
|
||||
сессии. Ходить настоящими контроллерами с подстановкой пользователя, обернув как middleware.
|
||||
|
||||
🪤 **Браузер робота и расписание не делят профиль** — перед ручным заходом в кабинет
|
||||
останавливать оба таймера, после — возвращать. Иначе робот пишет «уже работает» и уходит.
|
||||
|
||||
🪤 **Скрипт `.ps1` с кириллицей надо сохранять с меткой UTF-8**, иначе Windows PowerShell
|
||||
читает его как абракадабру и падает на разборе.
|
||||
|
||||
🪤 **Сторож, который «проверяет», может не проверять ничего.** Дважды за две смены: сначала
|
||||
проверка гонки считала размеры в окне и не заметила постоянного фильтра из тех же размеров;
|
||||
потом сторож приёмки ждал готовности сегмента, но запустил бы кампанию в сломанный запрос.
|
||||
🔑 **Всегда искать независимый признак, что сторож правда сработал** — и спрашивать
|
||||
не «сработал ли признак», а «что именно он доказывает».
|
||||
|
||||
## Красные линии
|
||||
|
||||
Прод — живые клиенты и деньги, любое включение только по явному «да» владельца, БД по
|
||||
умолчанию только чтение. **Пуш только в gitea**, GitHub мёртв. Коммит по эскейпу, сообщение
|
||||
без круглых скобок, в файле, явные пути. **В СМС-модуль не лезть.** Телеграм-кампанию
|
||||
№ 713051718 не трогать. Владелец **не программист** — говорить простым русским, без
|
||||
технических слов; длинные разборы он не читает, нужны короткие абзацы по существу.
|
||||
Reference in New Issue
Block a user