Commit Graph

923 Commits

Author SHA1 Message Date
Дмитрий 2abd6e73b7 docs реклама за показы: снимок состояния — заход 7, карта швов с веткой телеграм-рекламы
Числа обновлены: портал 393 из 393 при 1249 проверках, было 391.

Добавлен разбор захода 7: поломка денег на шве пауза-возобновление, датчик класса
"каждая половинка честна, шов между ними голый", и карта швов с веткой телеграма
для той смены, которая будет её забирать.

Порядок выбран владельцем: сначала выкат показов, телеграм следом.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 06:53:25 +03:00
Дмитрий 08640c020e fix реклама за показы: живой прогон разведки вскрыл две поломки — робот считал вместо того, чтобы ждать
Разведку впервые прогнали живьём, с разрешения владельца: вызвали саму функцию робота
против боевого кабинета на двух отклонённых объявлениях кампании-пустышки. Живую кампанию
не трогали, робот только читал. Прогон окупился сразу — вскрылись две поломки, которых
не видел ни один из 74 зелёных тестов.

Первая. Робот не нашёл объявление, которое в кабинете было: провал за три секунды,
«объявления в списке нет». Замерили — список рисуется две секунды, а робот считал ячейки
сразу после человекоподобной паузы в 800 миллисекунд. Счёт не ждёт, он отвечает про
«прямо сейчас». На бою это значило бы, что КАЖДЫЙ отказ уезжает в «ждёт разбора»,
а клиент причину не узнаёт никогда.

Вторая. После первой починки робот стал находить объявление и приносить 29 знаков —
один заголовок «Модератор отклонил объявление», без причины. Та же ошибка: строка причины
появляется позже окна, робот считал её и получал ноль, раскрывать было нечего. Клиенту
уехало бы сообщение от Яндекса, в котором нет ни слова о том, что чинить.

Текст окна нарастает по частям: 29 знаков, потом 82, потом 785. Окно не отдаёт ошибку —
честно показывает то, что успело нарисоваться. Поэтому промах молчаливый: робот считал бы,
что справился.

Починка одна на обе: ждать, а не считать. Раскрытие строки подтверждаем появлением
подробности, а не паузой — пауза это надежда, элемент это факт. Не дождались подробности,
остаёмся с короткой причиной: она честная и клиенту полезна, промолчать было бы хуже.

Оба сторожа написаны ДО починки и падали с теми самыми живыми ошибками.

Хвост доклада: по решению владельца срезаются подписи кнопок кабинета «Написать в чат»
и «Написать письмо» — у клиента этих кнопок нет, а выглядят они приглашением написать
Яндексу. Режем только хвост и только точное совпадение строки: те же слова внутри
пояснения это слова Яндекса, их не трогаем.

Живая проверка обрезки вскрыла третью ловушку: «Написать письмо» срезалось, а «Написать
в чат» оставалось. Яндекс ставит между короткими словами неразрывный пробел. Глазу он
неотличим от обычного, а сравнению это совсем другой символ. Правило для этого кабинета:
сравнивать текст только по человеческому виду строки, а элементы ждать, а не считать.

Замеры записаны в разметку кабинета, раздел 7.8.

Итог живой проверки с продовой паузой: причины приезжают целиком, кнопок в них нет —
760 знаков по финансовым услугам и 589 по медицине, шесть-семь секунд на объявление.
Снимок берётся только с окна, логин и остаток счёта в кадр не попадают.

Робот 80 из 80. Портал не тронут. На боевой не выкатывалось, рубильник Директа выключен.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-28 20:49:34 +03:00
Дмитрий 3fdcd88ad3 feat реклама за показы: правда про документ, экран «ждёт разбора» и разбор своих ошибок
Задача 16 закрыта решением владельца. Дороги «робот везёт документ в кабинет Яндекса»
не существует — доказано двумя нарочными отказами, обычной тематикой и лицензируемой:
в окне отказа ноль полей для файла, документы Яндекс принимает только снаружи кабинета.

Приём документа оставлен, но портал больше не молчит: клиент приложил файл — в ленту сразу
ложится отметка «документ у нас, передать его Яндексу автоматически нельзя, при
необходимости отнесём сами и напишем здесь», а владельцу уходит письмо на адрес алертов.
Без письма обещание было бы пустым: файл просто лёг бы на диск. Сам файл письмом
не отправляем — это чужие бумаги. Обычный ответ без файла ни отметки, ни письма не даёт.

Экран «ждёт разбора» в админке: ручка была, экрана не было. Третья карточка на странице
«Реклама» — клиент, кампания, что робот делал человеческими словами, номер объявления
и на чём споткнулся. В подписи прямо сказано, чего там НЕ будет: обычных отказов,
их клиент разбирает сам.

Дальше — разбор собственной работы этого дня. Найдено четыре ошибки, все исправлены.

1. ТЯЖЁЛАЯ. Доклад разведки на бою уронил бы очередь заданий целиком. Робот пишет в ленту
   под служебной ролью, а у неё на этой таблице было только чтение. Отказ по правам,
   500 роботу, три повтора — и задание навсегда «в работе». Пока хоть одно задание
   в работе, выдача отвечает «работы нет» ВСЕМ клиентам. Лечение — запись схемы v9.17:
   право на запись плюс нумератор. В плане про это было написано прямым текстом,
   я прошёл мимо. Тесты поймать не могли: ходят суперпользователем.
2. Признак «набор создан» я выдумал: взял метку, которая в нашей же разметке описана
   как СКРЫТАЯ галочка. Проверка «видно ли её» не сработала бы никогда. Признак с экрана
   убран совсем: успех определяет портал слепком креативов, а «окно не закрылось» —
   это норма, так и есть живьём.
3. Сломал ленту для повторного отказа. Поменял защиту от дублей на «такой текст уже
   когда-либо был» — и клиент, починивший рекламу и получивший тот же отказ, не увидел бы
   ничего. Вернул сравнение с последним сообщением, а заглушку «причину выясняем» держит
   теперь сам джоб: показываем один раз, пока сказать нечего.
4. Мой собственный тест оказался пустышкой: оставался зелёным при вырезанной защите.
   В нём отклонялись ВСЕ объявления, а тогда кампания уходит в «отклонена» и обход её
   больше не берёт. Сценарий существует только при частичном отказе — тест переписан
   на два объявления и теперь вырезание защиты его роняет.

Портал 391/391, админские экраны 9/9, фронт на затронутых наборах 52/52, робот 74/74,
мест снятия заморозки денег по-прежнему четыре.
На боевой не выкатывалось, рубильник Директа выключен, живьём разведка не гонялась.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-28 20:10:18 +03:00
Дмитрий 0ba7890bf9 feat реклама за показы: робот-разведчик приносит клиенту причину отказа с экрана кабинета
Задача 15. Программный интерфейс Яндекса причину отказа не отдаёт — на отклонённое
объявление приходит «Отклонено на модерации.» и всё. Причина висит только на экране
кабинета, и добыть её может лишь тот, у кого есть глаза.

Как теперь работает:
опрос модерации видит отказ и ставит роботу задание разведки по этому объявлению;
робот открывает список объявлений, находит ячейку своего объявления, кликает по статусу,
раскрывает строку причины, читает текст и снимает одно окно; доклад уезжает порталу формой
вместе со снимком; портал кладёт его в ленту от имени Яндекса слово в слово, клиенту
письмо и колокольчик. Робот не понял, что видит — задание сбойное, владельцу письмо,
в ленту клиенту ничего не сочиняем. В админке появилась ручка «ждёт разбора».

Четыре ловушки, пойманные по дороге и проверенные вырезанием:

1. Дедуп разведки нельзя вешать на кампанию. Отказ никуда не девается, а обход бежит
   по расписанию: после закрытия первой разведки поставилась бы вторая, и робот ходил бы
   в кабинет по кругу. Ключ — номер объявления, журнал схемы v9.16.
2. Рубильник держал не выдачу задания, а построение клиента Директа. Разведке слепок
   креативов не нужен, значит при выключенном рубильнике она получила бы задание,
   и робот пошёл бы в живой кабинет.
3. Дедуп ленты сравнивал только с последним сообщением Яндекса. После доклада робота
   обход снова клал бы «причину выясняем» поверх настоящей причины.
4. Постановка разведки шла без tenant-контекста — на бою она не сработала бы ВООБЩЕ
   и молча: поиск дубля давал бы ноль, запись падала бы на политике доступа, всё это
   в предупреждение журнала при зелёных тестах. Поймал rls-reviewer. Лечение — своя
   транзакция с выставлением клиента, рецепт ChargeCampaignSpendJob. Сторож поставлен
   на сам механизм: обычным тестом это не ловится, они ходят суперпользователем.

Заодно: разведке больше не снимается слепок креативов — лишний поход в живой Яндекс
внутри открытой транзакции.

Портал 382/382, робот 75/75, мест снятия заморозки денег по-прежнему четыре.
На боевой не выкатывалось, рубильник Директа выключен.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-28 19:28:31 +03:00
Дмитрий 7a4461324e fix реклама за показы: робот грузил файлы в поле, которое Яндекс не принимает
Разметку кабинета 27.07 снимали глазами, ничего не загружая, и поле файлов выбрали
по виду — CreativeActionsMenu.FileInput. Живая загрузка 28.07 показала: это поле файлы
молча забирает, а кнопка «Создать» остаётся серой. Робот падал бы «кабинет не принял
файлы» на каждой попытке. Работает только поле внутри окна загрузки.

Вторая правка из той же поправки разметки: окно после «Создать» живьём НЕ закрывается,
а переключается на вкладку «Мои креативы» со списком наборов. Робот считал это бедой
и слал владельцу письмо-алярм на КАЖДОЙ удачной загрузке. Теперь признак успеха —
появившийся список наборов; человека зовём, только когда исход непонятен: ни списка,
ни закрытия.

Заодно снято лишнее ожидание: раньше на удачной загрузке робот стоял две минуты,
дожидаясь закрытия, которого не бывает.

Обе правки проверены вырезанием по отдельности. Робот 63/63.

Вердикт по второй пробе модерации записан в cabinet-flow.md §7.6: у лицензируемой
тематики окно отказа ровно такое же, поля для документа нет и там. Дороги «отвезти
документ роботом в кабинет» не существует.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-28 18:38:45 +03:00
Дмитрий 0b0a4705c7 docs реклама за показы: снимок состояния называет коммит починки 2026-07-28 18:17:06 +03:00
Дмитрий 865bd211b1 fix реклама за показы: клиент узнаёт про отказ понятными словами, а не отпиской Яндекса
Яндекс на отказ отдаёт машине «Отклонено на модерации.» и больше ничего. Из этого
выходило три беды сразу: в переписке клиент видел одну бесполезную фразу, в списке
кампаний под ярлыком «Отклонено» не было вообще ничего — подпись берётся как первая
строка, а первым знаком у настоящего ответа идёт перенос, — и то же самое уезжало
клиенту письмом.

Новый ModerationReason — единственное место, где ответ модерации превращается в текст
для клиента. Отписку заменяем честным «Яндекс отклонил рекламу, но причину не назвал.
Выясняем — как только узнаем, напишем здесь». Первая строка нарочно короткая: она идёт
подписью под ярлыком. Настоящую причину принесёт разведка, задача 15.

Ловушка, пойманная до написания: подмену нельзя звать на все объявления подряд —
у принятого пустое пояснение это норма, и подмена приписала бы принятой рекламе отказ.
Зовём только при отказе, на ловушку стоит отдельный тест-сторож.

Второй слой на экране: firstLine обрезает края до разбора на строки, а не после.
Слои не подпирают друг друга — сервер решает, что сказать клиенту, экран следит,
чтобы сказанное не потерялось. Письмо починилось само, оно берёт текст из переписки.

Проверено вырезанием: без подмены два теста краснеют именно на возврате отписки,
а тест про обрезку переносов остаётся зелёным.

Портал 361 из 361, экраны рекламы 37 из 37, робот 60 из 60, мест разморозки денег
по-прежнему четыре.
2026-07-28 18:16:19 +03:00
Дмитрий 898f9d575b docs реклама за показы: Яндекс не сообщает машине причину отказа — и это вскрыло дефект
Один запрос на чтение боевым ключом, с разрешения владельца: ads.get на отклонённое
пробное объявление отдаёт StatusClarification = «Отклонено на модерации.» и больше
ничего. На экране в этот же момент — «Нет предупреждения: финансовые услуги» и абзац
с указанием, что дописать в баннер. В подполях Creative и TurboPageModeration причины
тоже нет.

Следствие первое: задача 15 из удобства стала обязательной — без робота-разведчика
портал знает только факт отказа. Проверил до того, как писать код: вывод мог оказаться
и обратным.

Следствие второе, важнее: в куске 1, который считался готовым, живой клиент увидит
пустоту. В переписке — единственная фраза «Отклонено на модерации.», а в списке
кампаний под ярлыком «Отклонено» не будет ничего вовсе: подпись берётся как первая
строка причины, а текст Яндекса начинается с переноса строки.

Тесты этого не ловили — они подставляют выдуманную причину, и на ней всё работает.
Дефект живёт в зазоре между выдуманными данными и живыми. Записан, чинится следующим
коммитом.
2026-07-28 17:05:30 +03:00
Дмитрий 22203465ce docs реклама за показы: вторая проба отказа — лицензируемая тематика заведена
В ту же пустышку № 713110757 добавлено объявление № 17787102785 со стоматологией:
медуслуги лицензируются, значит модерация должна потребовать лицензию, то есть
документ. Первая проба дала отказ «поправьте креатив» без документов, поэтому
проверяем, отличается ли окно отказа у лицензируемых тематик. Вердикт ждём.

Попутно подтвердилось на втором случае: сохранение объявления само отправляет его
на модерацию — кнопку «Запустить кампанию» не нажимали, статус сразу стал
«Объявление на модерации». Это подтверждает вывод §7.1 о том, что кнопки повторной
модерации в кабинете нет.

Грабли: переименование набора креативов карандашиком оставляет поле в режиме правки
и перехватывает клик по «Создать» — загрузка встаёт без внятной ошибки. Имя набору
не задаём, берём первый в списке.

Живая кампания владельца № 713051718 не тронута, расход пустышки ноль.
2026-07-28 16:42:21 +03:00
Дмитрий 23db59bd22 docs реклама за показы: экран отказа модерации снят живьём — задача 13 закрыта
Вердикт по нарочно непроходному объявлению пришёл на вторые сутки:
«Модератор отклонил объявление · Нет предупреждения: финансовые услуги».

Разметка записана в bots/yandex-creatives/docs/cabinet-flow.md §7 вместе со снимком
окна отказа. Причина живёт только в списке объявлений — форма объявления знает
«Показы не идут» и молчит про модерацию. Окно открывается кликом, не наведением,
а подробное пояснение видно лишь после раскрытия строки причины.

Два отрицательных ответа важнее найденного:
кнопки повторной модерации не существует — Яндекс шлёт на перепроверку сам
по факту сохранения правки; прикладывать документ в кабинете некуда — в окне
отказа ноль полей для файла, документы уходят наружу через чат или форму
обратной связи. Задача 16 остановлена до проверки вторым отказом
на лицензируемой тематике — решение владельца.

Заодно поправлены даты: работа прошлого захода помечалась 29.07, фактически
всё делалось 28.07. Имена миграций не трогали — они уже закоммичены.
2026-07-28 16:30:54 +03:00
Дмитрий cce04aed86 feat реклама за показы: постановка доставки берёт документ только связью от кампании
Второй рубеж защиты документа. Оставлять его на задачу 16 было бы хвостом: сама проверка
от экранов кабинета не зависит. enqueueDelivery берёт сообщение связью от кампании,
сырой номер в выборку не попадает нигде. Заодно отказывается ставить задание без вложения
и не плодит второе, если клиент нажал дважды.

Поймана ловушка, заложенная прошлой задачей: постановка обычной заливки искала любое
незавершённое задание кампании и с появлением доставки вернула бы её. Запуск решил бы,
что креативы уже в очереди, и робот не повёз бы картинки вовсе, молча. Отбор по виду
добавлен, тест есть. Нашлось чтением соседнего метода, не тестом и не проверкой.

Оба рубежа доказаны вырезанием по отдельности: без проверки в коде чужой документ ловят
ключи базы, но уже ошибкой записи вместо понятного отказа.

Портал 356/356, робот 60/60, мест снятия заморозки денег по-прежнему четыре.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-28 15:55:29 +03:00
Дмитрий 03ef89e06e fix реклама за показы: чужой документ теперь отказывает сама база, а не дисциплина в коде
Проверка прав доступа по прошлой миграции вскрыла утечку: внешний ключ на сообщение
не защищал от чужого клиента, потому что проверки целостности в PostgreSQL идут в обход
RLS, а робот ходит под ролью с кросс-тенантным доступом. Он молча увёз бы документ одного
клиента в модерацию кампании другого. Обе дыры воспроизведены вживую до правок.

v9.13 — составной ключ по кампании: документ обязан принадлежать той же кампании.
v9.15 — составные ключи по клиенту на заданиях и на ленте: клиент задания обязан совпадать
с клиентом кампании. Понадобилась потому, что моя запись про v9.13 оказалась сильнее самой
защиты — поймано повторной проверкой.
v9.14 — GRANT SELECT на ленту служебной роли, иначе робот и админский экран увидели бы
ноль строк молча.

Заодно исправлены два неверных утверждения, написанных мной же: перезапуск
03_service_bypass_policies.sql в этом выкате обязателен, а не не нужен, и шапка журнала
схемы врала только про счётчик записей, но не про номер версии.

Три записи выкатываются только вместе. Проверка в коде задачи 16 остаётся вторым рубежом.

Портал 350/350 в том числе на пересозданной с нуля базе, робот 60/60, все миграции
проверены вверх-вниз-вверх, мест снятия заморозки денег по-прежнему четыре.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-28 15:07:20 +03:00
Дмитрий 81297c4837 docs реклама за показы: снимок состояния называет коммит задачи 14
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-28 14:38:37 +03:00
Дмитрий a35b28cb15 feat реклама за показы: вид задания у робота — отвезти картинки, посмотреть, отвезти документ
Робот умел ровно одно — отвезти картинки в кабинет, и очередь молчаливо означала именно
это. Теперь у задания есть вид: upload, inspect, deliver, плюс ссылка на сообщение ленты,
документ из которого везём. Умолчание upload обязательно — задания, лежащие в очереди
на момент выката, вида не имеют.

Внешний ключ на сообщение НЕ защищает от чужого клиента: проверки целостности в PostgreSQL
идут в обход RLS, а робот ходит под crm_admin_user с кросс-тенантным доступом. Дыра пока
спящая — message_id в бою никто не пишет. Требование проверять принадлежность в коде
записано в докблоке миграции, в журнале схемы v9.12 и в приёмочных строках задачи 16.

Журнал схемы v9.12, а не v9.11 из плана: тот занят отметкой revived_at.

Портал 345/345, робот 60/60, мест снятия заморозки денег по-прежнему четыре.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-28 14:37:43 +03:00
Дмитрий aa4259e76c docs реклама за показы: живая разметка кабинета Яндекса — задача 13 в работе, отказ вызван нарочно
Поправлены три неверные записи прежней разметки, снятой глазами без загрузки файлов:
поле файлов внутри окна вместо CreativeActionsMenu.FileInput, прикрепление креатива
галочкой BatchesList вместо кнопки Выбрать, и список объявлений, который при будущем
сроке кампании отдаёт пусто. Добавлен весь путь мастера создания медийной кампании.

В боевом кабинете заведена отдельная пустышка со сроком в октябре и нулевым расходом:
кампания 713110757, объявление 17787055204 с креативом регулируемой тематики. Живая
кампания 713051718 не тронута. Ждём вердикт модерации, чтобы снять экран отказа.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-28 14:06:17 +03:00
Дмитрий 653b095a2a docs реклама за показы: заход 3 закрыт — кусок оживления готов, ход работ и снимок состояния 2026-07-28 12:36:34 +03:00
Дмитрий 6bc00ba67b docs реклама за показы: заход 2 закрыт — кусок переписки готов, ход работ и снимок состояния 2026-07-28 10:19:40 +03:00
Дмитрий ffeffe35af docs реклама за показы: заход 1 закрыт — ход работ и снимок состояния 2026-07-28 09:25:57 +03:00
Дмитрий bae6b95fda docs реклама за показы: план работ по отказам модерации — четыре захода с точками компакта 2026-07-28 08:53:05 +03:00
Дмитрий bb39703e62 docs реклама за показы: замысел — окно передачи между Яндексом и клиентом по отказам модерации 2026-07-28 08:32:38 +03:00
Дмитрий 327c347b38 docs робот креативов: список коммитов в промте v16 включает сам себя 2026-07-28 07:38:19 +03:00
Дмитрий 01ca3087aa docs робот креативов: промт перезапуска v16 и решение владельца — цену определяет клиент
Приёмочный лист v12 пройден целиком: восемь критичных дыр, хвосты портала и денег,
хвосты робота и мелочи §8. Осталось только то, чего нельзя сделать без владельца
и без живого кабинета — задачи 16 и 17.

Владелец подтвердил 28.07.2026: цену за 1000 показов назначает клиент. Пункт листа
про client_cpm_rub и budget_rub закрыт как решение, а не как дыра — поле в мастере
остаётся, ручки продолжают принимать цену. Записано и в журнале починки, и в промте,
чтобы будущий разбор не переоткрыл вопрос заново.

Портал 300/300, робот 60/60, мест снятия заморозки денег четыре.
2026-07-28 07:28:49 +03:00
Дмитрий af12b1ceb4 fix реклама за показы: предел веса картинки проверен по-настоящему, обход модерации не срывается целиком, робот не несёт токен на чужой адрес
Мелочи приёмочного листа v12 §8. Каждая правка с тестом; где защита уже стояла
в коде — тест проверен вырезанием этой защиты.

Предел веса картинки. Две прежние проверки были пустышками: сравнивали константу
саму с собой и с тем же числом в ответе сервера. Вырезание правила max: оставляло
обе зелёными. Настоящий тест грузит перевес и ждёт отказа — это четвёртая найденная
пустышка за ветку.

Обход модерации. Объявление без статуса и причина отказа длиннее колонки роняли
запись в базу ВНЕ защиты, и обход обрывался на середине: остальные клиенты не узнавали,
приняли их рекламу или отклонили, а деньги за отклонённый набор не возвращались.
Запись ответа теперь под той же защитой, что и сеть; пустой статус не пишем вовсе,
причину храним обрезанной.

Робот. Адрес файла приходил в ответе сервера, а шли по нему со своим токеном без
всякой сверки. Теперь адрес обязан вести на портал. Папка снимков экрана росла
бесконечно, а на снимках видны логин и остаток счёта — старше двух недель убираются.

Ещё: порядок посредников служебного канала — токен раньше служебного соединения;
BannerGenerator больше не отдаёт молча файл тяжелее предела и берёт предел из общей
константы; нулевой номер креатива ловится намеренно, а не случайно нестрогим сравнением.

Портал 300/300, робот 60/60. Мест снятия заморозки денег по-прежнему четыре.

Не тронуто намеренно: цена за 1000 показов и бюджет приходят от клиента — но это
видимое поле мастера и принятое продуктовое решение, а не недосмотр. Решает владелец.
2026-07-28 07:21:02 +03:00
Дмитрий 0f82a2d9ff docs робот креативов: промт перезапуска v15 — приёмочный лист пройден, дальше мелочи
Все восемь критичных дыр и все хвосты закрыты, кроме Д4 — это вопрос владельца.
В промте: где мы сейчас, что нельзя переделывать и почему, оставшиеся мелочи из
листа v12 параграф 8, известные остатки, найденные по ходу, и накопленные грабли.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-28 06:40:31 +03:00
Дмитрий 9cc3f5e950 fix реклама за показы: пауза не врёт про остановку, цена запуска остаётся на кампании, робот не дерётся сам с собой
Хвосты денег Д1-Д6 и робота Р-х1-Р-х6 из приёмочного листа v12.

Д1 цена, по которой заморожены деньги, записывается на кампанию. Пока поле было
пустым, списание читало глобальную цену — админ менял её, и клиент платил больше
обещанного при запуске.

Д2 суточное списание берёт кампанию под замком строки. Ключ идемпотентности зависит
от числа показов, поэтому два одновременных прогона получали разные ключи и списали
бы клиента дважды.

Д3 пауза, не дошедшая до Директа, больше не считается паузой: отказ 409, заморозка
остаётся. Раньше реклама крутилась дальше, портал показывал паузу, а деньги были уже
свободны. У возобновления поведение намеренно прежнее — иначе понадобилось бы пятое
место разморозки, а их ровно четыре. Там же убрана мина строгого сравнения рубильника.

Д4 не трогали — это вопрос владельца.

Д5 рубильник Директа держит и служебный канал робота: выдача задания и приём отчёта
ходили в живой кабинет мимо него.

Д6 проверка рубильника приведена к общему виду: YANDEX_DIRECT_ENABLED=0 давало строку,
которую строгое сравнение читало как включено.

Р-х1 настройки читаются из .env робота, а не каталога запуска.

Р-х2 файл-замок robot.lock: проход и поддержание входа больше не дерутся за профиль
браузера. Занят — уходим молча, задание остаётся в очереди. Брошенный замок
перехватывается через полчаса.

Р-х3 письмо-алярм честно говорит, залиты ли уже креативы в кабинет. Побочно вскрылось,
что тексты писем не проверялись ни одним тестом — транспорт вынесен в src/smtp.js.

Р-х4 тест-пустышка про рабочую папку заменён настоящим: запуск из чужого каталога без
явной папки. Проверено вырезанием.

Р-х6 пустое значение в окружении читается как значение по умолчанию, мусор даёт внятную
ошибку вместо тихого NaN.

Портал 293/293, робот 57/57. Денежных выходов снятия заморозки по-прежнему четыре.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-28 06:36:11 +03:00
Дмитрий 0548fc0d8f fix реклама за показы: слепок креативов при выдаче, сверка размера перед объявлением, один баннер на слот
Хвосты портала П1-П8 из приёмочного листа v12.

П1 обрыв постановки задания больше не даёт клиенту голый 500 — 503 с человеческим
текстом и записью в журнал. Таймаут у HTTP-клиента Laravel уже был, эта половина
находки не подтвердилась.

П2 и П6 роботу отдаются только баннеры без номера креатива — тот же список
сопоставляется при отчёте. Раньше стороны расходились и опознание падало на
безупречной работе робота, плодя дубли в кабинете. Плюс постраничный обход описи
креативов: слепок обрывался на первой странице.

П3 слепок «до» снимается при выдаче задания, а не при постановке, и в той же
транзакции, что и перевод в работу. Два задания в очереди получали одинаковый
слепок, второе падало всегда. Постановка перестала зависеть от живости Яндекса.

П4 перед созданием объявлений сверяется настоящий размер каждого креатива одним
запросом. Не сошлось или креатива нет — запуск не идёт.

П5 уникальный индекс uq_ad_campaign_banner_slot, запись v9.09 в CHANGELOG схемы,
rls-reviewer GO.

П7 замок на правку расширен на сегмент Аудиторий — он создаётся раньше кампании.
Смежная находка: перезаливка картинки теперь обнуляет номер креатива.

П8 и Р-х5 файл отдаётся под настоящим расширением и типом содержимого, имя от
номера баннера; робот берёт расширение из ответа портала.

Портал 287/287, робот 43/43. Денежных выходов снятия заморозки по-прежнему четыре.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-28 05:37:56 +03:00
Дмитрий dcfd9fe297 docs робот креативов: промт перезапуска v14 — все восемь дыр закрыты, дальше хвосты
Р1-Р8 приёмочного листа починены и проверены вырезанием. Портал 274/274,
робот 41/41. Дальше по листу v12 — хвосты П1-П8, Д1-Д6, Р-х1-Р-х6, потом мелочи.
2026-07-27 20:45:39 +03:00
Дмитрий 542b7d2d22 fix реклама за показы: очередь робота больше не встаёт колом, доклад об успехе не врёт, тихий ноль ловится
Р6 — задание, брошенное «в работе», держало очередь для всех клиентов навсегда.
Причина сбоя обрезается до 900 знаков, иначе портал отвергал отчёт о самом частом
виде поломки. Провал отчёта больше не глотается молча — едет в письмо человеку.
Приём отчёта ловит любую ошибку и закрывает задание сбойным вместо 500 роботу.
Новый сторож creative-jobs:reap каждые 10 минут разбирает пробку.

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

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

Портал 274/274, робот 41/41. Все защиты проверены вырезанием.
Выходов снятия заморозки денег по-прежнему четыре.
2026-07-27 20:42:23 +03:00
Дмитрий 43be8c39fc docs робот креативов: промт перезапуска v13 — Р1-Р5 закрыты, дальше Р6
Где мы: пять дыр из восьми починены и проверены вырезанием, портал 266/266,
робот 35/35. Промт v12 остаётся приёмочным листом — v13 только про текущее
состояние, следующую работу и грабли, пойманные по дороге.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:10:18 +03:00
Дмитрий b8f75b2a0c fix реклама за показы: файл робота только по своему заданию, замок на баннерах, защита от двойного запуска
Р3 хвост. Адрес файла баннера стал /api/creative-robot/jobs/{jobId}/banners/{bannerId}/file.
Раньше портал подставлял «какое-нибудь задание в работе» — защита выдачи чужих картинок
держалась на внешнем условии, а не на самом запросе. Робота править не пришлось: он берёт
адрес из ответа портала как есть. Запись v9.08 в журнал схемы про частичный уникальный
индекс; rls-reviewer по миграции — GO.

Р4. Замок на наборе баннеров после заведения кампании в Яндексе: перезаливка, включение
и удаление отдают 409 по тому же признаку yandex_campaign_id, что и замок на параметрах.
Без него в портале была новая картинка, а в Яндексе крутилась старая, а удаление строки
с номером объявления заставляло возобновление завести второе объявление того же размера —
старое продолжало крутиться за деньги клиента. Баннер с номером объявления не удаляется
никогда.

Р5. Захват кампании под запуск: новый промежуточный статус launching, перевод под замком
строки в транзакции. Два одновременных нажатия «запустить» проходили проверку статуса оба
и заводили две CPM-кампании при одной заморозке денег. На любой ошибке прежний статус
возвращается, номера созданных в Яндексе сущностей уцелевают — возобновляемый запуск
не тронут. Брошенный захват старше пятнадцати минут перехватывается.

Тесты: портал 266/266, робот 35/35. Все новые защиты проверены вырезанием.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 20:06:57 +03:00
Дмитрий d26716ed90 fix реклама за показы: срок показа закрывает кампанию, отчёт робота только по заданию в работе, гонка выдачи заданий
Р1. Кампания, не добравшая смету показов, оставалась running навсегда, а заморозка
денег клиента - ACTIVE навсегда: единственным переходом в completed было
delivered >= paid_impressions, а задачи, закрывающей кампанию по истечении срока
показа, не существовало вовсе. Для медийки по списку телефонов недокрут сметы -
типовой исход, а не редкий случай.

Новая колонка ad_campaigns.shows_until хранит последний день показа - ровно тот,
что уходит в Директ параметром EndDate. Пишется вместе с yandex_campaign_id, то
есть в момент, когда дату начинает держать Яндекс; возобновляемый запуск
переиспользует уже записанную дату, чтобы портал и Директ считали срок одинаково.
У выхода 1 в CampaignImpressionCharger появилось второе условие - новых мест
вызова AdWalletService::release не добавилось, их по-прежнему ровно четыре.
Запись v9.07 в журнале схемы, rls-reviewer GO.

Р2. Отчёт робота принимался по любому заданию в любом состоянии: номер брался из
адреса как есть. Готово по чужому ещё не выданному заданию разложило бы номера
креативов чужой кампании по её баннерам - картинка одного клиента уехала бы в
объявление другого; сбой по уже закрытому заданию переписал бы правильный
результат на failed. Теперь done принимает отчёт только по заданию в статусе
taken - 409 в остальных случаях, та же проверка продублирована в сервисе.

Р3, первая половина. Проверка «в работе никого» в takeNext не блокировала строку:
две одновременные выдачи обе её проходили и уносили разные задания. Слепки
creatives.get перемешивались, а размеры блоков у всех клиентов одинаковые, поэтому
итог - тихая привязка чужого номера креатива. Гарантию даёт частичный уникальный
индекс uq_creative_job_single_taken, плюс advisory-замок первой строкой транзакции,
чтобы штатный путь спокойно отвечал роботу «работы нет».

Осталось по Р3: привязать выдачу файла к номеру задания, rls-reviewer по индексу,
запись v9.08 в журнал схемы. Ход работы - в файле PROGRESS рядом с промтом v12.

Тесты, прогнаны в одиночку: портал 256/256, робот 35/35. Все девять новых тестов
были красными до правок, каждая защита проверена вырезанием.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 19:36:03 +03:00
Дмитрий 6c63389bdd docs робот креативов: промт перезапуска v12 — полный разбор ветки и приёмочный лист починки
Разбор всей ветки четырьмя проверяющими: робот, канал портала и RLS, деньги,
ядро на набор креативов. Восемь критических находок, все перепроверены по коду:
заморозка денег навсегда при недокруте сметы, отчёт робота принимается по любому
заданию, гонка на выдаче заданий, незапертые ручки баннеров, повторный запуск
кампании, очередь встающая колом, ложный отчёт о сбое, бесконечная заливка мусора.
Плюс важные хвосты и мелочи с файлами и строками.

Ничего из этого на бою нет: рубильник Директа выключен, ветка не выкачена.
2026-07-27 19:09:03 +03:00
Дмитрий b66009bd99 docs робот креативов: промт перезапуска v11 — задачи 12-15 закрыты, дальше боевой сервер
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 18:28:30 +03:00
Дмитрий 6723d14b8a docs робот креативов: промт перезапуска v10 — задача 11 закрыта, дальше задача 12
Живая разметка кабинета снята и закоммичена, поэтому в промте появился раздел
с готовыми селекторами и главным выводом — системное окно выбора файла роботу
не нужно, файлы кладутся прямо в поле формы.

Добавлено: признаки живого входа и слетевшего входа, единственная опасная кнопка
на пути робота, чего в разметке нет и что добирается в задаче 14, а также
новое открытое решение владельца про имена креативов.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 17:47:39 +03:00
Дмитрий 5b8813b033 docs робот креативов: разметка экранов загрузки в кабинете Директа
Снято живьём вместе с владельцем в кабинете sasha261185, кампания 713051718.
Ничего не сохранялось и не создавалось: до и после — те же 15 объявлений,
кнопки «Сохранить изменения» и «Создать» не нажимались.

Ключевое для задачи 14: системное окно выбора файла роботу не нужно —
в форме есть input type=file с data-testid CreativeActionsMenu.FileInput,
принимает png/jpeg/gif и сразу несколько файлов. Весь путь до окна загрузки
проверен программным прогоном, окно открылось.

Подтверждено живьём: конструктор креативов закрыт, Яндекс пишет это сам;
у всех 15 креативов имя «Новый креатив», потому что поле имени одно на всю
загрузку — опознание по разнице слепков и размеру остаётся единственным
надёжным способом.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 17:41:56 +03:00
Дмитрий 9234a9c2bc feat реклама показы: очередь заданий робота-грузчика, служебный канал и постановка при запуске
Портал ставит роботу задание, когда у баннеров ещё нет номеров креативов: вместо
ошибки клиент видит «готовим картинки», кампания остаётся черновиком, деньги не
морозятся. Робот берёт задания строго по одному — иначе слепки креативов до и
после перемешаются, и опознать их будет нельзя.

Канал робота закрыт своим сервис-токеном, внесён в исключения проверки CSRF и
отдаёт файл только того задания, которое сейчас в работе. Постановка задания
стоит внутри проверки рубильника Директа — при выключенном рубильнике портал в
Яндекс не ходит.

Права на новую таблицу выданы роли crm_admin_user: канал идёт через посредник
admin-db, подменяющий подключение. Нумератор выдан crm_app_user — он единственный
вставляет строки. Журнал схемы — запись v9.06.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 17:13:02 +03:00
Дмитрий af53dfed41 fix реклама показы: возобновляемый запуск, замки на повтор и правку, гранты админке и нумераторам
Запуск кампании стал возобновляемым: номер каждой созданной в Яндексе сущности
пишется на кампанию сразу, повторный вызов переиспользует созданное и заводит
объявления только для баннеров без номера. Номер группы записывается лишь после
успешной привязки аудитории — инвариант «есть номер группы, значит аудитория на
ней висит». Обрыв связи больше не оставляет кампанию-сироту в кабинете.

Двойной заморозки денег не было и раньше — AdWalletService::freeze идемпотентен
по активному холду; закрыто тестом. Денежный код не тронут, выходов снятия
заморозки по-прежнему четыре.

Два замка: launch отказывает из любого статуса кроме draft и queued — раньше
повтор откатывал статус и launched_at; update отказывает в правке параметров
показа, как только у кампании есть yandex_campaign_id — раньше клиент мог
поменять смету, цену и адрес сайта у работающей кампании, и портал молча
расходился с Яндексом. Название менять по-прежнему можно.

Права: GRANT SELECT, UPDATE на ad_campaign_banners роли crm_admin_user — без
него админ-экран увидел бы тихий ноль. GRANT USAGE, SELECT на нумераторы семи
рекламных таблиц роли crm_app_user — bigserial без USAGE даёт отказ на бою, а на
dev невидим из-за суперпользователя. Корневая причина в db/02_grants.sql —
ALTER DEFAULT PRIVILEGES без FOR ROLE crm_migrator — вынесена отдельным вопросом
к владельцу.

Миграция 2026_07_27_100000 получила гард на существование колонок под
прод-порядок выката migrate --pretend --force плюс ручной psql.

Журнал схемы v9.04 и v9.05, формулировка проверки в v9.04 уточнена честно.
229 из 229 тестов рекламы зелёные, squawk чист.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 15:55:16 +03:00
Дмитрий 1a00e215f5 docs робот-грузчик креативов: план работ на 17 задач и промт перезапуска
План — три фазы: ядро переходит на креатив у каждого баннера, канал заданий
между порталом и роботом, сам робот-грузчик на Playwright.
Попутно чинятся кап веса баннера 150 КБ до 512 КБ и правило модерации,
по которому один забракованный баннер валил всю оплаченную кампанию.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 13:04:03 +03:00
Дмитрий d3bfa2b05c docs подавать креативы роботом: дизайн-спека и находка про закрытый конструктор Яндекса
Живая проверка боевого кабинета опровергла допущение, на котором стояла Часть C и
часть уже написанного ядра. Конструктор креативов Яндекса закрыт 01.06.2026 —
Яндекс пишет об этом сам плашкой в редакторе объявления, показы его креативов
остановлены с 30.06. Адаптивного креатива из одной картинки не существует: медийная
кампания устроена как объявление на каждый размер блока со своим креативом, что
подтверждает наша живая кампания прогрева из 15 объявлений.

Отдельно проверен read-only запрос creatives.get с боевым токеном: метод отдаёт наши
баннеры, тип у них HTML5_CREATIVE, имя у всех одинаковое «Новый креатив» — опознавать
по имени нельзя, — и приходит PreviewUrl, годный для показа клиенту.

Отсюда дизайн: робот становится грузчиком готовых файлов, а не оформителем креатива.
Номера портал берёт сам через creatives.get слепком «до и после» с сопоставлением по
ширине и высоте. Ядро переходит с одного креатива на кампанию на креатив у каждого
баннера. Решения владельца: файлы готовит клиент, робот сам опрашивает портал, живёт
на боевом сервере, вход заводится через экран и удалённый рабочий стол на localhost.

Дописаны три вещи, не проговорённые в обсуждении: правило частичной модерации —
кампания работает, если принято хотя бы одно объявление, разморозка только когда
отклонены все; минимум один файл и не более одного на размер; раздел честно
неизвестного вместо выдуманных ответов.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 12:32:11 +03:00
Дмитрий 5fde295bdd fix(портал): читаемые чипы статусов — синий и красный
Проверка живьём на восьми статусах рекламных кампаний: зелёный и жёлтый уже
были закрыты слоем правок от 14.05.2026 и дают 6.06 и 6.37 — прежнее
предположение, что они не проходят, оказалось неверным. Тот слой закрыл только
два цвета из четырёх: info давал 3.63 и error 4.31 при норме WCAG 2.1 AA 4.5.

Дописаны в тот же слой, той же глубины: info #2e5a6d даёт 5.87, error #952c2c
даёт 5.98. Подложка чипа берёт цвет из color-пропа и не меняется — темнеет
только текст, вид почти прежний. Задеты статусы «Готова к запуску»,
«Отклонено», «Остановлено (нет денег)» и все тональные чипы портала.

Заодно вторая копия карты статусов в CampaignReportDialog оставалась на обычном
grey (2.42) — приведена к grey-darken-2, как в списке кампаний.

Мерил на живой странице по .v-chip__content с учётом подложки, а не по токену
темы — прежняя ошибка была именно в этом.

Проверено: рекламные фронт-тесты 17/17; Pa11y по всем 24 адресам — 20 из 24 как
и до правки, регрессий нет, рекламные и все админские экраны дают 0 ошибок.

Боевого не касается, в main не влито.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 11:26:32 +03:00
Дмитрий 7c97980b3a fix(портал): читаемый текст зелёным и жёлтым — текстовые двойники брендовых цветов
Брендовые success и warning нечитаемы текстом: success даёт 4.25:1 на белой
карточке и 3.83:1 на ивори, warning — 2.25 и 2.03 при норме WCAG 2.1 AA 4.5:1.
Сами цвета не тронуты — остаются для заливок, иконок и рамок. Рядом добавлены
затемнённые текстовые двойники success-strong #256F46 и warning-strong #8A6410,
тон сохранён.

Все 15 мест text-success и 8 мест text-warning переведены на новые классы —
13 файлов: командный центр, биллинг и инциденты админки, система, тенанты,
интеграция с поставщиком, карточка тенанта, диалог баланса, рекламный
админ-экран, мастер рекламной кампании, массовые диалоги дней и регионов,
реквизиты в настройках.

Проверено: фронт 226 файлов / 1663 теста зелёные; Pa11y по всем 24 адресам —
рекламные и ВСЕ админские экраны дают 0 ошибок. Побочно вскрылось 12 старых
недочётов на клиентских экранах — записаны в находку, к этой правке отношения
не имеют.

Боевого не касается, в main не влито.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 11:02:26 +03:00
Дмитрий 3c69f43864 fix(реклама): доступность рекламных экранов и чистота тестов
- Pa11y: в конфиг добавлены три рекламных экрана — клиентский Яндекс,
  рекламный кошелёк и админский расход/маржа.
- Чипы статусов «Черновик» и «На паузе» были на обычном grey — контраст
  2.42:1 при норме WCAG 2.1 AA 4.5:1. Заменены на grey-darken-2, после
  чего оба клиентских рекламных экрана дают 0 ошибок.
- CampaignLauncherTest коммитил кампании в статусе pending_moderation
  с сегментом и сотней телефонов и не откатывал их. При полном прогоне
  SyncCampaignAudienceJob перечисляет кампании всех тенантов через
  pgsql_supplier и подхватывал эти хвосты — SyncCampaignAudienceJobTest
  падал на «ни одного обращения к Яндексу». Добавлен DatabaseTransactions.
  Замер: раньше после прогона папки оставалось 2 живых кампании, теперь 0.
  Полный прогон: было 5 падений, стало 3 — оба рекламных ушли.
- Находка про контраст палитры портала вынесена отдельным документом:
  зелёный 4.25 и жёлтый 2.25 на белом не проходят AA, но это брендбук
  и уже на бою — решение за владельцем.

Реклама 156/156. Боевого не касается, в main не влито.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 10:41:52 +03:00
Дмитрий a384fc43d9 docs(реклама показы): промт перезапуска v5 и снимок состояния после закрытия заморозки
Риск «заморозка не снимается» в снимке помечен закрытым — коммит dd894a52.
Промт v5 самодостаточен: где что лежит, что сделано, что осталось по приоритету,
грабли окружения.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 09:48:24 +03:00
Дмитрий be9eaddea0 docs(реклама показы): хэндофф Части 4 ЯДРО + чек-лист go-live
Состояние Части 4 автозапуск через API Директа: собрано и закоммичено в ветку,
не влито, не на проде, рубильник ВЫКЛ. Список коммитов, единая денежная модель
показы плюс 40%, чек-лист go-live Задача 9, открытые риски заморозка не снимается
при завершении release не зовётся, формат отчёта Директа, грабли окружения
изоляция теста и larastan в worktree. Дальше РОБОТ Часть C и ВЫБОР Часть A.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-27 10:18:37 +03:00
Дмитрий fbbe49dac6 feat(реклама): показы — фундамент Части 4: предохранитель бюджета + номер креатива Яндекса на кампании
- config yandex_direct.spend_limit_guard_multiplier — бэкстоп от перерасхода, не клиентская цена
- миграция ad_campaigns.yandex_creative_id nullable + точечный GRANT UPDATE админ-роли с гардом
- модель AdCampaign: yandex_creative_id в fillable и casts integer
- тесты: config-набор и миграция зелёные, реклама-набор не сломан
- CHANGELOG схемы v9.01; план Части 4 и findings контракта медийного API Директа

Часть B «мотор» ещё впереди: медийные методы клиента, переписанный запускатор, контроллер.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-26 23:41:48 +03:00
Дмитрий 5a4c0e0235 feat(реклама): показы — баннеры клиента по размерам, два режима аудитории, клиентская цена и наценка
Часть 5c — баннеры: клиент грузит свой готовый файл на каждый из 15 размеров вместо автогенерации из одной картинки. Частичное утверждение флагом included, замена и удаление отдельного баннера, валидация точного размера и веса. Админ-поле цены за 1000 показов. Пример CSV для скачивания и подъём лимита загрузки.

Часть 5d — два режима сбора аудитории. Авто: скользящее окно, обновляется ежедневно, только контакты системы. Ручной: снимок сделок за период плюс свой список номеров и срок показа. Клиент сам задаёт цену за 1000 показов с дефолтом из админки. Наценка настраивается в админке, по умолчанию 40 процентов, в Директ уходит меньше, клиенту не видна нигде.

Миграции: ad_campaign_banners += included; ad_campaigns += mode/snapshot_from/snapshot_to/run_days/client_cpm_rub; ad_settings += ad_margin_percent. RLS-ревью PASS на всех миграциях. Backend 166 тестов, фронт 123 теста, сборка чистая. Маржа и yandex_cost_rub клиенту не сериализуются.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-26 21:13:20 +03:00
Дмитрий 994bb42f26 docs(реклама): фидбек владельца по мастеру показов + промт перезапуска (Часть 5c) + текст шапки
Живой тест мастера владельцем: правки — упростить тексты, цену 120₽/1000 сделать
настраиваемой (из ad_settings.client_cpm_rub + админ-контрол), «Утвердить баннеры»
только после галереи, замена отдельного баннера + частичное утверждение, убрать
«Далее» на последнем шаге, поднять лимиты загрузки. Открытый вопрос — авто-кроп/ИИ
(владелец предложил ИИ-генерацию; 3 варианта, не выбрал — спросить в начале сессии).
Заодно текст шапки раздела: «платите только за переходы» → «оплата за показы».

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-26 16:40:13 +03:00
Дмитрий 7d3ed2f6eb feat(реклама): Часть 6 — списание по факту показов + клиентский отчёт по показам
CampaignImpressionCharger: списание с рекламного кошелька за фактически
показанные показы (показано×120/1000, дельта от уже списанного, идемпотентно
по external_key yandex-imp:{id}:{billable}), режет по оплаченному, статус
completed при достижении оплаченного; bcmath, 6 boundary-тестов. Клиентский
отчёт CampaignReportDialog переведён с недельного бюджета на показы
(оплачено/показано/частота/потрачено); статусы queued/completed в списке и
отчёте. Маржа/yandex_cost клиенту не видны. Бэкенд 105/105, фронт 104/104.

Отложено до Части 4 (нужен Директ): джоб, тянущий фактические показы из отчёта
Директа и зовущий CampaignImpressionCharger; campaigns.suspend при стопе;
переделка админ-маржи с наценки-% на реальный yandex_cost_rub.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-26 16:07:19 +03:00
Дмитрий de9f0d7d8e feat(реклама): Часть 5b — мастер «за показы», отправка-заявка, скрытие маржи
Мастер CampaignWizard переделан с клик-модели на показы: окно дней → частота
+ живая смета (120 ₽/1000 показов, без маржи) → одна картинка + галерея превью
+ утверждение баннеров → проверка и «Отправить заявку». Бэкенд store/update
принимают частоту/бюджет показов (клик-поля убраны), новый submit → статус
queued («готова к запуску», реальный запуск в Директ — Часть 4). Маржа
yandex_cost_rub скрыта от клиента ($hidden, тест). CampaignList: метка queued
и показы вместо недельного бюджета. Тесты: бэкенд 99/99, фронт 102/102.

Директ: заявка на ПОЛНЫЙ доступ к API подана 26.07 (upgrade, статус «новая»);
картинка-креатив через API невозможна (creatives.add только видео) — гибрид,
баннер заливается в кабинет вручную, CreativeId вставляется в портал.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-26 15:45:53 +03:00
Дмитрий 449c6c5e27 docs(реклама): промт для перезапуска сессии — восстановление контекста + продолжение с Ч.5b
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-26 12:30:07 +03:00