Приёмка глазами вскрыла: кабинет МТС требует «Заголовок объявления» (до 40 знаков),
когда в объявлении ссылка на САЙТ, а не на телеграм-канал. Робот про это поле не знал,
«Продолжить» молча не срабатывало, кампания вставала на шаге «Объявление» — в бою уже
ПОСЛЕ списания денег. Проверено живьём: 2234454 (сайт — встала) против 2234462 (канал —
дошла до подтверждения) и 2234490 (сайт с заголовком — дошла).
Портал спрашивает заголовок заранее, на создании черновика: обязателен только для
не-телеграмной ссылки (App\Support\TelegramLink), колонка ad_headline varchar(40),
поле на экране появляется по той же развилке. Робот заполняет его в кабинете.
Три ловушки, добытые живыми прогонами (описаны в коде):
- поле дорисовывается в ОТВЕТ на ссылку, с задержкой — надо ждать, а не спрашивать;
- под описание подходит несколько элементов — нужен .first();
- серая надпись внутри поля НЕ placeholder, а нарисованная подпись: поиск по атрибуту
давал ноль совпадений при видимом на снимке поле. Опознаём по видимой надписи.
Тесты: робот 130/130, ClientTg 250/250, экран 23/23. Полный прогон бэкенда — те же
13 падающих классов до и после правки (ни одного в телеграм-части). В baseline
статанализа добавлен известный ложный класс Pest для нового файла тестов.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Переделка сделана и запушена, на бой не выкачено. Развилка описана: поселить
робота и выкатить, перевести на опрос чтение вердикта и пересдачу, вылечить
тестовую базу. Ловушки смены выписаны, включая молчаливую установку
зависимостей и правило замерять сторожа до починки своего текста.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Сама установка НЕ выполнена — она трогает живую машину и боевой портал,
нужно явное разрешение владельца. Куда селить робота, зависит от исхода
разговора с поставщиком прокси.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Своей петли нет: запускается таймером, зависший проход не мешает следующему.
Номера кладутся во временный файл и убираются при любом исходе.
Проверено живьём против поднятого портала: верный токен — «Работы нет.» и код 0,
чужой токен — понятная ошибка 401.
Заодно закрыта мина: токен уходит в заголовок HTTP, куда пускают только латиницу.
С русскими буквами робот падал нечитаемым сбоем Node ещё до обращения к порталу —
теперь говорит по-человечески. Наступил на неё сам, проверяя план дословно.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
При transport=poll портал кладёт задание и выходит, робот заберёт его сам.
Процессный путь оставлен рабочим и остаётся умолчанием.
Полный набор тестов телеграма: 219 из 219 зелёные.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Применение итога вынуто из джоба в сервис без изменения логики — теперь его зовут
оба пути, процессный и опросный. Отчёт принимается только по заданию в работе.
Сторож принят вырезанием: без проверки статуса повторный отчёт проходит с 200.
Отдельно закрыта мина, найденная ревью защиты и отсутствовавшая в плане: роль
crm_admin_user имела на client_tg_campaigns только чтение, а канал робота пишет
туда итог — на бою приём отчёта упал бы по правам. Миграция v9.29 даёт UPDATE.
Полный набор тестов телеграма: 217 из 217 зелёные, старые в том числе.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Номера не кладём в задание и не пишем в журнал — отдельный запрос, без кеша.
Сторож принят вырезанием: без проверки статуса тест отдаёт 200 вместо 404.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Выдача строго по одному: у робота один профиль браузера и одна сессия кабинета.
Задание, по которому робот не отчитался за срок аренды, возвращается в очередь.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Где работать, что уже стоит, красные линии, ловушки и приёмка вырезанием.
Отдельно записано, что проверка обращения к поставщику прокси умирает
вместе с сессией и её надо ставить заново.
Co-Authored-By: Claude Opus 5 (1M context) <noreply\@anthropic.com>
Нужна при ЛЮБОМ исходе с адресом: и на сервере, и на машине владельца робот стоит
не там, где очередь портала, а сегодня портал запускает его как процесс у себя.
Списано с работающего образца - робота креативов Яндекса.
12 задач по шагам с готовым кодом и тестами, переключатель process/poll оставляет
старый путь рабочим. Отдельно вынесены красные линии: перезапуск srv_bypass после
миграции, номера телефонов не в задании, приёмка сторожей вырезанием.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Живой бой 30.07.2026. Сегмент дозрел за 15 часов — Яндекс опознал 1644 человека
из 1693, охват 3379. Портал нажал «Запустить» и получил отказ Директа:
Budget for this period cannot be less than 600 rub.
Стена оказалась не одна, а две. Обе — молчаливые: портал о них не знал.
Первая — аудитория. Сегмент в Аудиториях существует и при этом НЕГОДЕН, пока
Яндекс сводит загруженные номера с людьми. Портал этого не спрашивал вообще:
ни один файл не читал can_create_dependent. Он просто шёл в Директ строить
условие ретаргетинга и получал «объект не найден» — тот же отказ, что и на
несуществующий сегмент. Судить о готовности можно ТОЛЬКО по can_create_dependent:
статус is_processed означает «обрабатывается», а не «готов».
Вторая — деньги. Директ не берёт кампанию, у которой бюджет за период ниже
шестисот рублей. Приёмочная кампания на 1693 показа давала около 146 рублей.
Проверки минимума в портале не было тоже.
В обоих случаях клиент видел сырую ошибку Яндекса на английском и не понимал,
виноват ли он и что делать дальше.
Что сделано. Обе проверки выполняются ДО первого обращения к Яндексу: в кабинете
ничего не создаётся, деньги не морозятся, кампания остаётся черновиком.
аудитория ещё готовится → 202 и «подождите, обычно несколько часов»
смета мала → 422 и «нужно 6945 показов, это 833.40 рублей»
Наружу уходят ТОЛЬКО клиентские числа. Ни минимума площадки, ни нашей наценки:
по паре «минимум площадки — цена клиенту» долю Яндекса можно вычислить делением.
Минимум вынесен в настройку YANDEX_DIRECT_MIN_SPEND_RUB, Яндекс может его менять.
Тестом вперёд, в живой Яндекс из тестов не ходили. Четыре новых теста на запуск
и два на ответы портала. Реклама целиком: 342 теста зелёные.
Заодно приведён в порядок список исключений статанализа. Он был красным ЗАДОЛГО
до этой правки и не пускал ни одну правку кода: 629 замечаний до моих изменений,
638 после. Проверено в обеих папках работы — не артефакт подпапки.
Разбор 637 замечаний по составу:
611 — статанализ не понимает устройство тестов Pest и ругается на
обращения вида this->postJson и this->tenant. Таких записей в списке
исключений уже было 671 — просто новые тестовые файлы туда не дописали.
26 — придирки к типам, из них 7 в боевом коде.
Все семь в боевом коде разобраны поимённо и оказались ложной тревогой. Доказано
не рассуждением, а зелёными тестами, которые упали бы при настоящей поломке:
shows_until, три замечания — три теста прямо проверяют закрытие кампании по
истечении срока показа и разморозку остатка. Будь там строка вместо даты,
первый тест упал бы, а деньги клиента остались бы заперты навсегда.
snapshot_from и snapshot_to, два замечания — тесты ручного режима строят
аудиторию по этим датам, на строке они бы рухнули.
SetTenantContext строка 56 и CampaignMessageService строка 168 — лишние
подстраховки, вреда нет.
Корень ложных срабатываний: Larastan ищет приведения типов в старом виде —
свойством casts, а у нас метод casts. Код правильный, инструмент отстал.
После обновления списка статанализ зелёный: 0 замечаний. Теперь сторож снова
ловит НОВОЕ, а не молчит красным.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Проверка ссылок падала на девяноста двух ошибках, поэтому любая отправка в main
шла через обход. Смысл починки не в красоте: пока ошибок девяносто две, новая
битая ссылка тонет среди них и никто её не замечает. Теперь ноль, и пуш проходит
обычным порядком.
Что было внутри девяноста двух:
68 — ссылка написана от корня хранилища, а документ лежит на две-три папки
вглубь. Приписаны шаги вверх. Файлы всё это время лежали на месте.
8 — файл существует, но переехал: Jobs/Billing, Jobs/Supplier, Services.
Адреса переписаны на нынешние места.
8 — файла нет и не будет: ProcessWebhookJob снят при уходе от старого
биллинга, SupplierCsvParser удалён 09.07 как мёртвый, каталог memory
уехал в claude-brain, а HANDOFF прогрева от 25.07 не существует ни
в одном коммите. Ссылки сняты, текст оставлен.
3 — неверное число шагов вверх: две точки вместо трёх.
3 — это вообще не ссылки, а примеры в тексте: http:// как образец мусорного
ввода, https://ваш-сайт.ru из подсказки формы. Обёрнуты в кавычки-код.
1 — адрес с приписанными номерами строк, которых проверятель не понимает.
1 — Россвязь. Сайт мёртв по-настоящему: сервер 194.226.91.2 отдаёт nginx-ову
404 на любой адрес, включая корень, а сертификат выписан не на это имя;
ведомство расформировано. Замену проверить не удалось — преемник
с этой машины не отвечает вовсе. Ссылка снята, адрес читается текстом.
Вопрос «где реестр нумерации живёт теперь» остаётся открытым.
Исключения проверятеля не тронуты ни на строку. Ноль получен починкой, а не
глушением сторожа — иначе вся работа теряет смысл.
Отдельным прибором проверено, что видимый читателю текст не изменился ни в одной
из 81 правленой строки: правились только адреса ссылок. Разметка линтером чистая.
Вместе с починкой ложится промт смены, по которому работа делалась.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Сессия 4 плана docs/superpowers/plans/2026-07-27-client-telegram-ads-module.md.
Всё в песочнице (TG_SANDBOX): деньги/живой запуск выключены; робот в тестах замокан.
4.1 Авто-режим (пачка):
- Таблица client_tg_auto_rule (правило: объявление + budget_cap на пачку, одно на тенанта, RLS).
- TelegramAutoAccumulator: копит номера новых лидов в один открытый авто-черновик
(audience_kind=list, created_by IS NULL); при наборе пачки (порог client_tg.auto_batch_threshold,
по умолчанию 367 кандидатов) доводит смету, ставит кампанию в очередь В РАМКАХ budget_cap_rub
и ставит RunTelegramCampaignJob. Ниже порога — только копим. Дедуп номеров на входе.
Отличие от СМС-близнеца: СМС шлёт по одной на лид, Telegram копит ПАЧКУ (МТС показывает базе).
4.2 Защитный observer:
- DealTelegramObserver на created: freshness-guard (сутки) → ставит AccumulateTelegramLeadJob.
🔴 try/catch(Throwable) НИКОГДА не роняет приём лида (регресс DealCreateTest 10/10 зелёный).
- Приёма-джоб уводит работу с роли приёмщика (crm_supplier_worker, без GRANT на auto_rule) на
очередь (crm_app_user) с tenant-контекстом — иначе на проде был бы «тихий ноль». ПДн: телефон
в payload не кладём, перечитываем по deal_id. Зеркало DealSmsObserver + SendAutoSmsForDealJob.
4.3 Своё имя/бренд + помесячная оплата:
- Таблица client_tg_senders (одно имя на тенанта, RLS; app_user S/I/U, supplier S, admin S/U).
- TelegramSenderService — жизненный цикл: клиент requestSender (заявка+бронь месячной платы) /
disableSender; админ approve (списание+снятие брони+paid_until +1мес) / reject / disableByAdmin.
- ChargeTgNameFeeJob (ежедневно 05:10 МСК, routes/console.php): помесячное списание, проверка
free≥cost ДО списания, идемпотентно по периоду (external_key), долг > grace (29д) → suspended,
кросс-тенантно через pgsql_supplier, деньги под SET LOCAL. В песочнице деньги не двигаются.
Лёгкое зеркало client_sms_senders + ChargeSmsNameFeeJob (без документов/операторов — согласование
в Телеграме уточняется живой разведкой §8 спеки; здесь фиксируем денежный цикл).
4.4 Админ-тарифы/настройки:
- Api\Admin\TgTariffController (GET/PUT /api/admin/telegram/tariffs|settings) под saas-admin,admin-db.
- Фронт: api/admin.ts (fetchTgTariffs/updateTgTariffs/updateTgSettings), AdminTgView.vue (правка
ступеней + плата за имя/грейс), маршрут /admin/telegram. Зеркало AdminSmsView (без секции имён).
Проверки: 89/89 Pest ClientTg зелёные (+37 новых), регресс DealCreateTest 10/10, Vitest зелёный
(+4 admin-tg-view), phpstan 0 по своему коду (level 5), pint чисто, deptrac 0 нарушений.
rls-reviewer: PASS по обеим новым таблицам (client_tg_auto_rule, client_tg_senders). CHANGELOG_schema
v8.87 + напоминание ПЕРЕзапустить db/03_service_bypass_policies.sql на кластере (Сессия 6). Робот в
тестах замокан (кабинет МТС не тронут). Синтетические номера 7999… — реальных нет.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Дописан standalone-робот (Node ESM + Playwright) полного цикла создания
Telegram-кампании по своей базе телефонов в кабинете МТС Маркетолог:
- cabinet.js: вход в визард, выбор «Своя база клиентов», загрузка базы +
подсчёт «не МТС» с порогом 367, переход «Аудитория→Объявление» (само-
верификация), заполнение объявления + блок ОРД, финал черновик-стоп/боевой
(fail-loud при неизвестном режиме — защита от боевого клика по ошибке).
- runner.js: оркестратор с алярм/отчётом на почту, гарантированное закрытие
профиля браузера, чистка временного файла номеров (152-ФЗ).
- task.js: обязательные поля заголовка и названия ОРД; config: потолок бюджета
падает при нечисловом значении (защита денег), budget: NaN-guard.
- bin/run.js (CLI), bin/keepalive.js (сторож входа с алярмом), bin/login.js
(разовый вход), task.example.json (без ПДн), README.
- Шаг «Стоимость» намеренно не размечен (заглушка бросает Error) — селекторы
снимаются на первом реальном черновике после разового логина владельца.
Ядро: 20/20 юнит-тестов зелёные. Браузерная часть проверяется живым черновиком.
Двухстадийная ревизия каждой задачи + финальный сквозной проход (поймал и
починил пропущенный переход между шагами и NaN-обход потолка бюджета).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Задачи 8–9 плана (код, проверка против кабинета — при логине владельца в
профиль бота):
- browser.js — launchPersistentContext(profileDir), humanPause по темпу
- session.js — isLoggedIn по стабильному пункту меню «Рассылки и звонки»
(селектор подтверждён живьём при разведке)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Задача 1 плана: пройден визард Telegram Ads → «Своя база клиентов» в живом
кабинете в режиме только-чтение. Зафиксированы 7 шагов степпера, селекторы,
требования к файлу номеров (TXT/CSV/XLS/XLSX, формат 79XXXXXXXXX, минимум 367
не-МТС), счётчик «МТС/Не МТС», путь удаления черновика. Черновик 2229823 убран,
баланс 5010₽ не изменился. cspell-words: добавлены CPM/CPF/MTC/ЕРИР/Таргеты/алярм.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Спек по итогам brainstorming с владельцем. Бот-RPA на Playwright автоматизирует
полный цикл создания Telegram-кампании по своей базе телефонов в кабинете МТС
Маркетолог живой сессией браузера, т.к. публичного API для этого нет ни у кого
на рынке РФ. Запуск руками, алярм на почту, два режима черновик/боевой.
Модуль-витрина для клиентов — вне scope, отдельный будущий спек.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Хендофф по голосовым «Лена»: маршруты Asterisk (вход→приёмщик, обзвон через
obzvonbot+endpoint obzvon-lena), состояние Dasha (2 агента, лимит 1 разговор),
Mango (8 номеров, этикетка «Омега», МАВ), открытые задачи. Плюс перенесён с
Рабочего стола разбор ремонта исходящей связи Mango (403→180).
Полные телефоны/секреты не кладём (ПДн) — только состояние и команды.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
9 задач TDD: справочник операторов, каналы МТС(Exolve)/СМС-центр(smsc.ru),
реестр каналов в конфиге + сборка роутера, нормализация оператора,
имя отправителя под канал, резерв выключателем (off). Схему БД не трогаем.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Дизайн схемы «МТС-номер → МТС, остальные → СМС-центр» с минимумом работы
при подключении новых операторов: реестр каналов в конфиге, справочник
операторов (OperatorNormalizer), имя отправителя на каждый канал, резерв
выключателем (по умолчанию OFF). Схему БД не трогаем. Утверждено владельцем.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Task 1 плана «кусок B». firmRow() в SalesAdAudienceController (firms()/allFirms())
теперь отдаёт rubric, warmup_started_at (ISO8601), manager_id/manager_name (через
prospect.sales_user_id) и агрегаты warming_active/warming_channels (yandex/vk/mts/sms).
Заглушка smsWarmedFirmIds() — Task 3 наполнит связью с СМС-рассылкой.
Тест AdAudienceWarmingFieldsTest — TDD (failing → green), плюс regression-прогон
72 тестов AdAudience* и composer stan (0 ошибок, phpstan-baseline.neon дополнен
записью под Pest actingAs() в новом тестовом файле — по конвенции остальных тестов).
docs(александра): передача по стройке — лёгкий оператор и точило
Приняли архитектуру по замыслу владельца: наставник не даёт подсказку
на каждый ход, а точит промпт оператора в фоне. Замер: лёгкий промпт
2.60с против 3.14с, наставник успевает лишь в 35% ходов — итого 0.77с.
Точило ВЫБИРАЕТ блоки из 13, а не пишет текст: промпт собирается кодом,
поэтому дрейфа нет. Опасные темы выбираются кодом мгновенно, оттенки —
моделью в фоне, её не ждём никогда.
В файле: где что лежит, замки лаборатории, эталон боевого с возвратом,
состав 13 блоков и утверждённый ответ про источник заявок, пять
провалившихся попыток ускорения с числами, карта задержек, ловушки
и открытые вопросы за владельцем.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
Два дня работы: звук (хрип от несовпадения A-law/µ-law, заворот высоких,
канал proxy.market) и механика разговора (сторож эха ел вопросы клиента,
глухота между её же предложениями, поток мозга по предложениям).
Ответ по существу 6.05с → 3.64с, пауз внутри реплики 2 → 0.
В файле: что читать следующей сессии, чем мерить, 8 ловушек
и незакрытый слой, который за владельцем.
Номер боевой линии не пишем — сторож ПДн прав, правило одно для всех
телефонов; номер берётся из журнала моста.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Постановка владельца 19.07.2026: на экране «Последние списки» нужен фильтр под
выбранную нишу, галочки на списках и групповые действия (удалить / собрать телефоны /
прогреть / отдать менеджеру) — все только по горячим фирмам.
Решения владельца зафиксированы: горячие = 70+, уже собранных и отданных пропускаем
и сообщаем сколько, перед платным действием и удалением спрашиваем с числом,
Телеграм и СМС — заглушки до готовности модулей.
Найдено при проектировании: прогрев берёт только фирмы с собранным мобильным, значит
порядок работы — телефоны → прогрев → менеджер; после прогрева на нашей стороне не
оставалось следа (добавляем пометку площадки); показ 1000 списков требует переноса
счётчиков в колонки, иначе главная разбирает всю базу на каждое открытие.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Файл случайно попал в коммит 7e7c5b6f — был уже застейджен параллельной
сессией (пере-генерируется пост-коммит хуком, коммитить нельзя, см.
границы задачи). Восстанавливает содержимое к версии на aaeab9c6.
Task 6 плана 2026-07-19-vybor-ploshadki-progreva.md — начальник видит
площадку прогрева каждой фирмы (Яндекс/ВК/оба), отмечает строки галочкой
и жмёт одну из трёх кнопок массовой смены; api-слой зовёт уже готовый
POST /api/sales/ad-audience/channels.
Поле channels у фирмы с бэкфиллом «яндекс» существующим 64, фильтр состава
в яндексовой заливке, приём площадки от «Поиска клиентов», массовая смена
кнопками, колонка «Где греем», джоб заливки в ВК с тремя состояниями.
Тело обращения к API ВК вынесено за рамки плана намеренно: документация
закрыта до получения доступа, гадать нельзя. Джоб, порог 2000 и тесты
пишутся сейчас, меняться будет только тело одного метода.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Признак channels у фирмы, три кнопки и колонка «Где греем» на экране
начальника, заливка в ВК с тремя статусами (нет доступа / ждёт объёма /
работает). Порог автомата у ВК — 2000 номеров, это его же документация.
Зафиксированы находки 19.07: охват списка в ВК меньше сотни при 111
загруженных номерах, и что «111 из 111» у Яндекса — проверка формата,
а не совпадений. Открытый вопрос про перезапись списка в API ВК помечен
явно — уточняется при получении доступа.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Модуль на бою, две боевые поломки найдены и исправлены (права у ночного
пересчёта, отсутствующий confirm сегмента). Сегмент 58034825 создан порталом,
111 из 111 номеров приняты. Пошаговый план на утро + риск порога в 100 номеров.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Создание сегмента — два шага, а не один: upload_csv_file даёт статус uploaded
(черновик, в списке Аудиторий его нет и Директу он не виден), и только
segment/{id}/confirm сохраняет сегмент. Второй шаг отсутствовал — 19.07 заливка
на бою отчиталась успехом, id записался, а в Аудиториях было пусто.
content_type для телефонов строго 'crm'. Регресс-тест закрывает.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
На бою джоб бежит вне веб-запроса, то есть на дефолтной роли crm_app_user,
у которой нет прав на sales_prospects — пересчёт падал с permission denied
и не работал вообще. Планировщик теперь передаёт pgsql_supplier, как это
делает SalesProspectsAdvanceJob. Регресс-тест закрывает.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Код ушёл прошлым коммитом без своего теста — досылаю.
Проверяет, что судья вычитывает «до первого звука» и «до ответа
по существу» из журнала БОЯ, а не только со стенда.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Прежде мост считал «до первого звука» и «до ответа по существу» и печатал
их только на стенде — в бою они умирали в памяти, и судья не мог сказать,
сколько человек ждал ответа. Вид строки у стенда и боя один и тот же,
поэтому разбор общий.
188 тестов зелёные.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Утренняя чистка ПДн искала только в app/ и docs/ и пропустила моя/sales-finder —
папка скрыта от обычного поиска, номер оставался живым в трёх тестах (26 раз).
Заменён на фиктивный 79990000001, все 321 тест зелёные.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Буклет 2:3 Директ не принимает. Два формата под требования Яндекса
(квадрат 1:1 и широкий 16:9), текста на картинке нет кроме логотипа,
макет телефона и карточка с номером убраны — риск модерации по 152-ФЗ.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Номера, заведённые до v8.77, остались без firm_id. Ночной пересчёт обходит
фирмы и такой номер не видит, а заливка видела — он крутился бы вечно.
Нашёл rls-reviewer при сквозной проверке.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Task 8 плана v2 (реклама на кандидатов): начальник видит таблицу фирм на
прогреве (человеческие подписи состояний: «Реклама идёт», «Прогрет — ждёт
менеджера» и т.п.), кнопкой «Назначить менеджера» заводит карточку в воронку
через POST /api/sales/ad-audience/firms/{id}/assign, и правит 11 сроков
планировщика понятными фразами («Сколько дней греем до звонка менеджера» и
т.д.) вместо технических ключей.
Список менеджеров переиспользует существующий listSalesManagers() —
отдельного маршрута не заводили. api/sales.ts дополнен типами и функциями
для двух новых бэкенд-ручек Task 7 (firms/assign), которых там ещё не было.
Тест положен в tests/Frontend/ (а не в views/sales/__tests__/, как в плане) —
это единственный путь, который реально подключён в vitest.config.ts; мокает
модуль api/sales.ts, а не global.fetch — экран ходит через axios-обёртку.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Task 7 плана v2 (реклама на кандидатов): начальник видит список фирм
на прогреве (GET /api/sales/ad-audience/firms) и назначает менеджера
(POST .../assign) — из снимка фирмы рождается карточка «Потенциальные
клиенты» стадии new, реклама дальше следует за стадией карточки.
PATCH /api/sales/ad-audience теперь принимает все 11 сроков планировщика,
GET отдаёт их в durations.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
fix(судья): судит только ЕЁ голос + второе ухо отделяет крепкие находки от слабых
Полное заключение 19.07 вскрыло две беды судьи. Владелец: сначала судья, потом голос.
1. СУДИЛ ЧУЖОЙ ГОЛОС. Доложил «велено „шарвутся", услышано „шорвутся"» — а
«шарвутся» сказал КЛИЕНТ. Судья сверял весь журнал целиком, обе стороны, и
писал в брак Александры чужое произношение. Лечение не выкидыванием слова:
каждое ожидаемое слово теперь помнит, КТО его должен был сказать, и находка
докладывается только по её речи. Выравнивание по-прежнему по ВСЕЙ записи —
иначе её слова начнут цепляться к клиентским; отсекаем на докладе, не на сверке.
Прибор частиц слушает звук и тем более не знает, чей голос — судит только те
слова, которые велели сказать ей.
Было 8 звуковых находок → стало 6, ушли обе клиентские.
2. НЕ ОТЛИЧАЛ «голос сказал плохо» от «ухо не расслышало» — выглядит одинаково.
Второе ухо другого устройства (модель разметки букв, уже стоит, качать нечего)
слушает ту же запись. Оба уха не расслышали — находка крепкая; второе
расслышало верно — слабая.
🔴 Первая версия СНИМАЛА слабые — и убила «затишье», которое владелец
подтвердил СВОИМ УХОМ: второе ухо расслышало это слово верно. Правило «оба
уха обязаны ошибиться» режет правду. Второе ухо — СВИДЕТЕЛЬ, а не судья:
говорит, насколько находка крепкая, а не есть ли она. Теперь понижение до
СИГНАЛА: видно в статистике, приговор не двигает, накопитель на пачке
разговоров сам отделит системное от случайного.
Итог по записи: 6 находок, 3 крепкие («первые»→«первое», «как-то» 81% и 114%),
3 слабые (в том числе подтверждённое владельцем «затишье»).
🪤 Испытания начали тянуть тяжёлые модели и вставать намертво. В conftest
общая заглушка: гигабайтным моделям в испытаниях не место, кому нужна
настоящая — подменяет явно у себя.
Тестов 173 → 183.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
Task 5 плана v2: RecalcAdAudienceJob (routes/console.php, дневное 03:10 МСК) ночью
сверяет стадию связанной карточки sales_prospects с последней запомненной у фирмы
(sales_ad_audience_firms.stage_seen/stage_changed_at) и зовёт AdAudienceScheduler,
чтобы проставить каждому номеру состояние active/paused/stopped.
Найдена и закрыта дыра в исходном плане: ни prospect->updated_at (двигается ЛЮБОЙ
правкой карточки — заметка менеджера ложно продлевала бы рекламу), ни firm->assigned_at
(момент назначения менеджера, не смены стадии) не годились источником даты «сколько
фирма сидит в текущей стадии». Решение: фирма сама хранит эту дату — миграция
2026_07_20_110000 (+2 nullable-колонки, db/CHANGELOG_schema.md v8.78). Новый тест
«правка карточки не перезапускает отсчёт рекламы» закрывает ровно эту дыру.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat(судья): ловит «как-то» — частица после дефиса не бывает ударной
Владелец: на 0:35 «как-то» и «когда-то» звучат криво, лови. Следа в тексте нет
— ухо записало их верно, с дефисом. Значит дефект чисто звуковой.
🔑 ПОЧЕМУ ЭТО ИЗМЕРИМО, когда абсолютное ударение — нет. Сравниваем два слога
ОДНОГО слова, сказанных одним голосом в один момент. Не надо угадывать, какой
слог в слове ударный (две попытки провалились, потолок 53% против 51% у
болвана) — достаточно, что частица не должна быть приметнее корня. Правило
языка, не список слов: -то, -нибудь, -либо, -ка безударны в ЛЮБОМ слове.
🔴 ПОРОГ ВЗЯТ ИЗ САМОЙ РЕЧИ, а не подогнан под названные случаи. Замер по 133
словам записи: обычная безударная последняя гласная звучит примерно в 30% силы
сильнейшей гласной корня, у трёх четвертей слов — ниже 50%. Отсюда порог 50%.
Испытание запрещает менять его вне этих границ.
Живой прогон, 4 слова с частицей:
0:36 «как-то» → 81% силы корня 🔴 НАЗВАН ВЛАДЕЛЬЦЕМ
0:55 «как-то» → 114% 🔴 владелец не называл, судья нашёл сам
2:37 «смотрите-ка» → 113% 🔴 то же
0:34 «когда-то» → 35%, чисто — по замеру это норма, подгонять не стал
🪤 РАЗГАДКА ПРОВАЛА АКУСТИКИ. Замер показал: все гласные выходили ровно по
0.021 с. Модель ставит по одной вспышке на букву, а между ними молчит — если
считать буквой только вспышку, длительность перестаёт что-либо значить. Мой
вчерашний «фикс» ровно это и сделал (60% → 52%). Пустые кусочки возвращены
предыдущей букве: так из привязки и получаются протяжённости звуков.
🪤 ЛОЖНАЯ ТИШИНА, пойманная на живом прогоне: ухо режет «как-то» на «как» и
«-то», прибору доставался огрызок без корня — и он докладывал «чисто» на всех
четырёх словах. Молчание, похожее на «проверено», хуже выдумки: выдумку видно,
а это нет. Куски склеиваются обратно, огрызок без корня к суду не принимается.
Тестов 162 → 173.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@