Commit Graph

391 Commits

Author SHA1 Message Date
Дмитрий 5b04d751c0 feat(телеграм-модуль): авто-режим, своё имя + помесячная оплата, админ-тарифы
Сессия 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>
2026-07-27 18:41:33 +03:00
Дмитрий 741d1697c5 feat(телеграм-бот): бот-автоматизатор кампаний МТС Маркетолог — cabinet/runner/CLI/keepalive (задачи 10–16)
Дописан 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>
2026-07-27 00:43:20 +03:00
Дмитрий 51aac64c8a docs(телеграм-бот): разметка подтверждена живым прогоном до шага «Объявление»
Прогон в черновике на реальной базе (1740 номеров): Получатели 1709, МТС 713,
Не МТС 996, стоимость показов 478,08 ₽ — цепочка «загрузка базы → счётчик → цена»
работает. Шаг «Объявление» размечен полностью (текст/заголовок/ссылка/медиа +
обязательный ОРД-блок: категория downshift, название, «Я — посредник»). Добавлены
грабли автоматизации (coach-mark overlay, downshift, force-click). Шаги
«Стоимость/Подтверждение» — доразметить на первом реальном черновике бота.
Черновик удалён, баланс 5010 ₽ цел, временные ПДн стёрты.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-26 23:34:48 +03:00
Дмитрий f747b2fc74 feat(телеграм-бот): персистентный браузер и проверка живости входа
Задачи 8–9 плана (код, проверка против кабинета — при логине владельца в
профиль бота):
- browser.js — launchPersistentContext(profileDir), humanPause по темпу
- session.js — isLoggedIn по стабильному пункту меню «Рассылки и звонки»
  (селектор подтверждён живьём при разведке)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-26 22:49:22 +03:00
Дмитрий a1492400a0 feat(телеграм-бот): тестируемое ядро — config, task, phones, budget, mailer
Задачи 3–7 плана (TDD, 14/14 зелёные):
- config.js — загрузка/валидация настроек окружения
- task.js — парсинг и валидация задания кампании (режимы draft|live)
- phones.js — нормализация и дедуп файла номеров (7XXXXXXXXXX)
- budget.js — предохранитель потолка бюджета
- mailer.js — письма алярма/отчёта через SMTP (nodemailer)

gitleaks: каталог test/ бота добавлен в allowlist (синтетические
телефоны-фикстуры 345-67-89 / 111-22-33, не реальные ПДн).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-26 22:47:15 +03:00
Дмитрий 22d1150e26 chore(телеграм-бот): скелет Node+Playwright проекта
Задача 2 плана: package.json (Node ESM, node --test), .env.example (профиль
браузера, SMTP Unisender Go, потолок бюджета, человеческий темп), .gitignore.
Зависимости playwright/nodemailer/dotenv + chromium установлены.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-26 22:44:52 +03:00
Дмитрий ea1991e926 docs(телеграм-бот): разметка визарда кампании по своей базе в кабинете МТС
Задача 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>
2026-07-26 22:33:03 +03:00
Дмитрий 9edd1f78a0 docs(телеграм-бот): дизайн бота-автоматизатора Telegram Ads через кабинет МТС
Спек по итогам brainstorming с владельцем. Бот-RPA на Playwright автоматизирует
полный цикл создания Telegram-кампании по своей базе телефонов в кабинете МТС
Маркетолог живой сессией браузера, т.к. публичного API для этого нет ни у кого
на рынке РФ. Запуск руками, алярм на почту, два режима черновик/боевой.
Модуль-витрина для клиентов — вне scope, отдельный будущий спек.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-26 22:02:58 +03:00
Дмитрий db2bf0113c docs(телефония): снимок состояния Dasha-ботов и Mango + разбор ремонта исходящей
Accessibility (Pa11y live) / a11y (push) Has been cancelled
Хендофф по голосовым «Лена»: маршруты 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>
2026-07-23 23:07:52 +03:00
Дмитрий c88e81e803 docs(смс): план реализации — маршрутизация СМС по операторам (МТС + СМС-центр)
9 задач TDD: справочник операторов, каналы МТС(Exolve)/СМС-центр(smsc.ru),
реестр каналов в конфиге + сборка роутера, нормализация оператора,
имя отправителя под канал, резерв выключателем (off). Схему БД не трогаем.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 11:36:56 +03:00
Дмитрий c333746927 docs(смс): спека — маршрутизация СМС по операторам (МТС + СМС-центр, задел под остальных)
Дизайн схемы «МТС-номер → МТС, остальные → СМС-центр» с минимумом работы
при подключении новых операторов: реестр каналов в конфиге, справочник
операторов (OperatorNormalizer), имя отправителя на каждый канал, резерв
выключателем (по умолчанию OFF). Схему БД не трогаем. Утверждено владельцем.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 07:20:10 +03:00
Дмитрий fbe4701399 feat(прогрев): firmRow отдаёт нишу, дату запуска, менеджера и состояние прогрева
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() в новом тестовом файле — по конвенции остальных тестов).
2026-07-21 15:53:10 +03:00
Дмитрий 3fd0d81e09 @
docs(александра): передача по стройке — лёгкий оператор и точило

Приняли архитектуру по замыслу владельца: наставник не даёт подсказку
на каждый ход, а точит промпт оператора в фоне. Замер: лёгкий промпт
2.60с против 3.14с, наставник успевает лишь в 35% ходов — итого 0.77с.

Точило ВЫБИРАЕТ блоки из 13, а не пишет текст: промпт собирается кодом,
поэтому дрейфа нет. Опасные темы выбираются кодом мгновенно, оттенки —
моделью в фоне, её не ждём никогда.

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

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
2026-07-20 19:17:01 +03:00
Дмитрий c503e93c8a docs(александра): передача сессии — ритм разговора и задержки 20.07
Два дня работы: звук (хрип от несовпадения A-law/µ-law, заворот высоких,
канал proxy.market) и механика разговора (сторож эха ел вопросы клиента,
глухота между её же предложениями, поток мозга по предложениям).

Ответ по существу 6.05с → 3.64с, пауз внутри реплики 2 → 0.

В файле: что читать следующей сессии, чем мерить, 8 ловушек
и незакрытый слой, который за владельцем.

Номер боевой линии не пишем — сторож ПДн прав, правило одно для всех
телефонов; номер берётся из журнала моста.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 10:09:38 +03:00
Дмитрий 8697f8b0a2 docs(finder): спецификация и план — фильтр по нише и групповые действия над списками
Постановка владельца 19.07.2026: на экране «Последние списки» нужен фильтр под
выбранную нишу, галочки на списках и групповые действия (удалить / собрать телефоны /
прогреть / отдать менеджеру) — все только по горячим фирмам.

Решения владельца зафиксированы: горячие = 70+, уже собранных и отданных пропускаем
и сообщаем сколько, перед платным действием и удалением спрашиваем с числом,
Телеграм и СМС — заглушки до готовности модулей.

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

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 08:00:27 +03:00
Дмитрий 4f88268678 revert: убрать docs/observer/STATUS.md из прошлого коммита
Файл случайно попал в коммит 7e7c5b6f — был уже застейджен параллельной
сессией (пере-генерируется пост-коммит хуком, коммитить нельзя, см.
границы задачи). Восстанавливает содержимое к версии на aaeab9c6.
2026-07-19 19:53:29 +03:00
Дмитрий 7e7c5b6f29 feat(sales): колонка «Где греем» и кнопки смены площадки
Task 6 плана 2026-07-19-vybor-ploshadki-progreva.md — начальник видит
площадку прогрева каждой фирмы (Яндекс/ВК/оба), отмечает строки галочкой
и жмёт одну из трёх кнопок массовой смены; api-слой зовёт уже готовый
POST /api/sales/ad-audience/channels.
2026-07-19 19:51:52 +03:00
Дмитрий ddfb50cd44 docs(sales): план выбора площадки прогрева (10 задач)
Поле channels у фирмы с бэкфиллом «яндекс» существующим 64, фильтр состава
в яндексовой заливке, приём площадки от «Поиска клиентов», массовая смена
кнопками, колонка «Где греем», джоб заливки в ВК с тремя состояниями.

Тело обращения к API ВК вынесено за рамки плана намеренно: документация
закрыта до получения доступа, гадать нельзя. Джоб, порог 2000 и тесты
пишутся сейчас, меняться будет только тело одного метода.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 19:18:13 +03:00
Дмитрий d3ccf2db63 docs(sales): описание выбора площадки прогрева (Яндекс/ВК/оба)
Признак channels у фирмы, три кнопки и колонка «Где греем» на экране
начальника, заливка в ВК с тремя статусами (нет доступа / ждёт объёма /
работает). Порог автомата у ВК — 2000 номеров, это его же документация.

Зафиксированы находки 19.07: охват списка в ВК меньше сотни при 111
загруженных номерах, и что «111 из 111» у Яндекса — проверка формата,
а не совпадений. Открытый вопрос про перезапись списка в API ВК помечен
явно — уточняется при получении доступа.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 19:11:17 +03:00
Дмитрий 61f7da3eeb docs(sales): передача по рекламе на кандидатов — состояние на вечер 19.07
Модуль на бою, две боевые поломки найдены и исправлены (права у ночного
пересчёта, отсутствующий confirm сегмента). Сегмент 58034825 создан порталом,
111 из 111 номеров приняты. Пошаговый план на утро + риск порога в 100 номеров.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-19 17:55:33 +03:00
Дмитрий f9dea24ac4 fix(sales): сегмент в Яндексе подтверждается, а не остаётся черновиком
Создание сегмента — два шага, а не один: 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>
2026-07-19 17:31:25 +03:00
Дмитрий 2228bae51d fix(sales): ночной пересчёт рекламы читает карточки ролью с правами
На бою джоб бежит вне веб-запроса, то есть на дефолтной роли crm_app_user,
у которой нет прав на sales_prospects — пересчёт падал с permission denied
и не работал вообще. Планировщик теперь передаёт pgsql_supplier, как это
делает SalesProspectsAdvanceJob. Регресс-тест закрывает.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-19 17:22:36 +03:00
Дмитрий 0bda0a8bfe test(судья): тест к замерам задержек из боевого журнала
Код ушёл прошлым коммитом без своего теста — досылаю.
Проверяет, что судья вычитывает «до первого звука» и «до ответа
по существу» из журнала БОЯ, а не только со стенда.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 15:26:28 +03:00
Дмитрий b7f1192aeb feat(судья): замеры задержек берутся из боевого журнала, а не только со стенда
Прежде мост считал «до первого звука» и «до ответа по существу» и печатал
их только на стенде — в бою они умирали в памяти, и судья не мог сказать,
сколько человек ждал ответа. Вид строки у стенда и боя один и тот же,
поэтому разбор общий.

188 тестов зелёные.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 15:26:00 +03:00
Дмитрий 235c55fd6f fix(finder): реальный телефон директора убран из тестов службы поиска
Accessibility (Pa11y live) / a11y (push) Has been cancelled
SAST — Semgrep / Semgrep SAST scan (push) Has been cancelled
Утренняя чистка ПДн искала только в app/ и docs/ и пропустила моя/sales-finder —
папка скрыта от обычного поиска, номер оставался живым в трёх тестах (26 раз).
Заменён на фиктивный 79990000001, все 321 тест зелёные.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-19 15:01:25 +03:00
Дмитрий 31d6df0fcc feat(реклама): промпт и скрипт генерации картинок под Яндекс.Директ
Буклет 2:3 Директ не принимает. Два формата под требования Яндекса
(квадрат 1:1 и широкий 16:9), текста на картинке нет кроме логотипа,
макет телефона и карточка с номером убраны — риск модерации по 152-ФЗ.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-19 12:10:57 +03:00
Дмитрий 9a410a3358 docs(sales): поправки плана рекламы по ходу выполнения (stage_changed_at, оба трейта в тестах)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-19 11:53:54 +03:00
Дмитрий 49ee084b7e fix(sales): номер без хозяина не попадает в рекламу
Номера, заведённые до v8.77, остались без firm_id. Ночной пересчёт обходит
фирмы и такой номер не видит, а заливка видела — он крутился бы вечно.
Нашёл rls-reviewer при сквозной проверке.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-19 11:51:50 +03:00
Дмитрий c43ed7b283 feat(sales): экран прогрева — список фирм, назначение менеджера, 11 сроков
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>
2026-07-19 11:32:00 +03:00
Дмитрий 79d1567e7d feat(sales): список прогрева и назначение менеджера из рекламы
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>
2026-07-19 11:19:46 +03:00
Дмитрий 1e33da15c2 @
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>
@
2026-07-19 10:57:27 +03:00
Дмитрий f6abd1764d feat(sales): фирма на прогреве помнит увиденную стадию — честный ночной пересчёт рекламы
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>
2026-07-19 10:54:21 +03:00
Дмитрий 566f016c9c feat(sales): в прогрев уходит фирма целиком, а не отдельный номер
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-19 10:35:41 +03:00
Дмитрий 98015db231 @
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>
@
2026-07-19 10:25:10 +03:00
Дмитрий 9bc587c2de fix(sales): реальный телефон директора убран из тестов и планов; клиент Яндекс.Аудиторий возвращён в main
Коммит d2c2ec43 от 19.07 оторвался от main (dangling, ни в одной ветке):
чистка ПДн и YandexAudienceClient в основную ветку так и не попали.
Номера заменены на фиктивные 7999000000X, клиент и его тесты внесены заново.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-19 10:24:21 +03:00
Дмитрий 66f678697d feat(sales): правила рекламы по стадиям воронки 2026-07-19 10:16:39 +03:00
Дмитрий e25f940f39 feat(sales): модель фирмы на прогреве и настраиваемые сроки 2026-07-19 10:11:09 +03:00
Дмитрий 0be050395f @
feat(судья): ударение ловится ПО СЛЕДУ В ТЕКСТЕ — подтверждённый случай пойман

Владелец переслушал запись и назвал живой дефект: на 1:40 она говорит
«зАтишье» вместо «затИшье». Плюс сказал: не правь Александру, правь судью,
чтобы ловил.

🔑 КЛЮЧ, который дала эта правда. Ухо записало слово как «затЕшье» — не так,
как велено. Это и есть след: в русском безударная гласная РЕДУЦИРУЕТСЯ.
Ударение съехало → бывшая ударная «и» стала безударной → съехала в «е» →
распознавалка честно написала другую букву. Значит уехавшее ударение видно
В БУКВАХ, и городить акустику для него не нужно.

Прибор: согласные слова совпали, а гласная — нет. Это устройство языка,
а не список слов: проверено на «молоко/малоко», «сторона/старана»,
«телефон/тилефон» — работает на слове, которого мы в глаза не видели.

Разделение по смыслу, тоже из устройства языка: ПОСЛЕДНЯЯ гласная в русском —
это окончание (падеж, число, лицо), а ударение живёт в теле слова. Разошлась
последняя — доклад про окончание (признак 3); разошлась не последняя — про
ударение (признак 46). Иначе владелец не поймёт, что чинить.

Проверка на всей живой записи, 296 слов — 4 находки, ни одной лишней:
  признак 46: «затишье» → «затешье», гласная 2-го слога  ← ПОДТВЕРЖДЁН ВЛАДЕЛЬЦЕМ
  признак  3: «пациенты» → «пациента», «ступеней» → «ступень»,
              «проверяете» → «проверяйте»

🔴 Акустический разбор остаётся отвергнутым (две попытки, потолок 53% против
51% у тупого угадывания) — запись о нём в NENADYOZHNO уточнена: речь про метод,
а не про признак. На подтверждённом случае акустика ПРОМОЛЧАЛА, текст поймал.

Тестов 153 → 162.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
2026-07-19 10:06:24 +03:00
Дмитрий ba98334e8c docs(sales): план рекламы на кандидатов v2 — прогрев до менеджера, сроки по стадиям
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-19 09:53:57 +03:00
Дмитрий 2e631cf16f @
feat(судья): ударения нормальным методом — сделано, измерено, ПРОВАЛЕНО честно

Владелец: доводить ударения нормальным методом. Доведено. Не работает,
и я это показываю числами, а не прячу.

ЧТО СДЕЛАНО НОРМАЛЬНО (в отличие от первой попытки):
  · привязка звука к БУКВАМ (wav2vec2 + просмотр путей CTC на numpy) —
    границы гласных больше не угадываются по провалам громкости,
    а вычисляются: мы знаем буквы, которые она произносила;
  · высота тона автокорреляцией — третья примета, которой не было вовсе,
    а в русском она выдаёт ударение сильнее прочих;
  · приметность слога по трём приметам, тон считается по УХОДУ от среднего
    тона этого же говорящего (у мужчин и женщин высота разная).

ЗАМЕР НА ТЕХ ЖЕ СЛОВАХ, что и первая попытка (иначе сравнивать нельзя):
  первая попытка ............ 40%
  вторая, как есть .......... 52%
  ПОТОЛОК по всем весам ..... 53%
  тупое «всегда 2-й слог» ... 51%   ← вот с чем надо сравнивать
  только громкость .......... 52%
  только тон ................ 37%

Прибор не несёт НИЧЕГО сверх угадывания. Дело не в весах и не в ошибке кода:
сами приметы на этой записи не разделяют ударный слог и безударный.
Признак 46 остаётся выключенным, замер записан в NENADYOZHNO прямо в коде.

🪤 По дороге поймано и закреплено испытаниями:
  · октавная ошибка — на двойном периоде отклик почти такой же, прибор
    докладывал голос вдвое ниже настоящего;
  · край окна — у звука ниже человеческого голоса отклик плавно спадает,
    горба нет, а прибор докладывал частоту с границы окна;
  · пустые кусочки помечались буквой — гласная получала чужую длину.
    🔴 Починка этого сделала результат ХУЖЕ (60% → 52%): прежний перекос
    случайно помогал. Записано, чтобы никто не «вернул как было».

Прежде чем вкладываться в обученный определитель ударения — стоит убедиться,
что дефект вообще существует: слышал ли кто-нибудь, чтобы она ставила
ударение неверно. Инструмент строился под неподтверждённую беду.

Тестов 135 → 153.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
2026-07-19 09:51:24 +03:00
Дмитрий e09657bbb9 docs(sales): HANDOFF рекламной аудитории + спека v2 (порядок перевёрнут)
Владелец переформулировал: сначала ГРЕЕМ рекламой, потом отдаём менеджеру.
Прогрев — отдельный этап ДО воронки, карточка рождается при назначении менеджера.

Правила рекламы теперь следуют за стадией карточки, все сроки настраиваются:
прогрев 3 дня, новые 3, взят в работу 14, переговоры до даты созвона
(просрочена → +3 → стоп), недозвон 7, отказ 3, регистрация/тестирование 30,
пополнил баланс и пользователь — стоп.

Проверено на боевых данных: у «Взят в работу» и «Регистрация/Тестирование»
даты созвона нет и не будет — им обязателен свой срок, иначе реклама вечная.
У «Переговоров» дата обязательна в коде, логика владельца закрывает полностью.

HANDOFF содержит промпт для следующей сессии, все технические грабли,
состояние Яндекса (сегмент 58029600, письмо в поддержку отправлено)
и запись об инциденте с ПДн в истории коммитов.

Все телефоны в документах замаскированы.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-19 09:34:59 +03:00
Дмитрий c97e562d8c @
feat(судья): спектр звука — окончания, обрывы, бренд; ударения измерены и отклонены

Владелец: судья ДОЛЖЕН ловить окончания и ударения, системно, не заплаткой.
Спектр I не работал ни одним признаком из пяти — судья слышал только текст.

ПРИЁМ, общий для всего спектра: мы точно знаем текст, отправленный в голос,
значит эталон есть на КАЖДОМ слове, включая невиданное. Слушаем запись
ОТДЕЛЬНЫМ ухом судьи и сверяем «велено» с «услышано». Никаких списков слов.
Ухо судьи нарочно не то, что в мосту: тем же ухом ошибку не поймать —
прибор согласится сам с собой.

Заработали три признака: 3 окончания, 4 обрыв реплики, 5 бренд «Лидэрра».
На живой записи 19.07 нашли настоящее: «меня зовут алексан(др)»
и «компания лидера» — ровно то, ради чего затевалось.

🔴 УДАРЕНИЯ — ЧЕСТНЫЙ ОТКАЗ, а не тихий пропуск. Признак 46 заведён, прибор
написан (физика: ударный гласный длиннее и громче; эталон — словарь ударений
русского, знает любое слово; правило «ё всегда ударная» работает без словаря).
На синтетических гудках прибор безошибочен. На ЖИВОЙ речи: 187 судимых слов,
совпало с эталоном 40%. Перебор настроек — 32–47% при случайном угадывании
около 40%. Прибор не знает ничего; голос столько ошибок не делает, врёт
измеритель. Причина: громкость и длительность не разделяют слоги живой речи.
Нужен разбор по звукам (гласные + высота тона), а не огибающая громкости.
Признак внесён в новый список NENADYOZHNO, судья по нему МОЛЧИТ. Сторож
покрытия расширен: негодный прибор обязан лежать в списке с ЧИСЛОМ замера,
иначе через месяц его тихо включат обратно.

🪤 Наивное сопоставление «по порядку подряд» опознало на живой записи 27% слов
вместо 86% и выдало ложную тревогу об обрыве: журнал стенда не пишет ни
приветствия, ни филлеров, а в звуке они есть. Заменено на difflib — лишнее
терпится с ОБЕИХ сторон.

Признаков 45 → 46, тестов 125 → 135.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
2026-07-19 09:13:10 +03:00
Дмитрий 3ab8b2cbff feat(sales): руками введённые телефоны контактов чистятся при сохранении (формат 79…, дубли долой)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 08:57:02 +03:00
Дмитрий 44b892c771 feat(sales): экран «Реклама на кандидатов» в кабинете начальника
GET/PATCH /api/sales/ad-audience — три числа (в рекламе сейчас, выходят на
этой неделе, ждут отправки), рубильник и срок хранения номера. Доступ только
роли head — гейт denyIfNotHead зеркалит SalesManagersController.

Экран объясняет обычными словами, что список пополняется только кнопкой
«Отправить в рекламу» в Поиске клиентов, и предупреждает, когда номеров
меньше 100 (Яндекс не запустит рекламу).

Тесты: 9 в tests/Feature/Sales/AdAudienceScreenTest.php (доступ, три числа,
смена настроек, валидация срока). SharesSupplierPdo — модели на pgsql_supplier.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-19 08:46:36 +03:00
Дмитрий 536716a65e @
feat(судья): опровергатели против ложных тревог + чтение всех трёх форм журнала

Владелец: «нам надо пару сотен разговоров прогнать, мы умрём слушать сами».
Значит на объёме ложная тревога дороже пропуска: судья с выдумками бесполезен,
ему перестают верить. Первый живой запуск коллегии 19.07 дал ЧЕТЫРЕ ложных
СТОПа на два разговора — два разных довода принял за противоречие, оговорку
«приходят люди или нет» за гарантию, настоящий подарок за возврат денег.

Два системных замка, оба универсальные:

  1. Поле «narushen» вместо разбора прозы. Судья писал в обосновании
     «признак соблюдён, нарушения нет» — и всё равно подавал это находкой.
     Думать вслух теперь некуда: есть отдельное поле. Поля нет (старая форма
     ответа) — считаем нарушением, чтобы не терять молча.

  2. Опровергатели. Каждый СТОП коллегии уходит к трём независимым судьям,
     которым велено ОПРОВЕРГНУТЬ, и при сомнении опровергать. Опровергнут
     двумя из трёх — понижается до СИГНАЛА (не выбрасывается: судью самого
     надо мерить). Приборные СТОПы через опровержение НЕ гоняем — приборы
     нельзя уговорить, а деньги владельца тратить незачем.

Проверка на живых деньгах, эталон v9 от 17.07:
  было  — СТОП по трём обвинениям, два из них выдуманы;
  стало — оба выдуманных опровергнуты и понижены, СТОП остался ОДИН
          и настоящий: прибор 15 поймал перевёрнутую цену
          «от пятидесяти пяти до двадцати пяти». 10 обращений, 4.11 ₽.

Судья теперь читает все три формы журнала (боевой мост / стенд моста /
движок v9) + автоопределение формы: владелец подсовывает файл, не думая.
Стенд и движок не пишут решение маршрута — признак 44 там честно
не измеряется, а не делает вид, что проверен.

🪤 Четвёртый вид тех же граблей: прибор 41 «цену вперёд не тащи» ловил слово
«почему» — внутри него сидит «почем». Обвинял её за вопрос «почему не начали».
Найдено судом над эталоном, починено, регрессия закреплена.

Тестов 77 → 100.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
2026-07-19 08:39:16 +03:00
Дмитрий b4c51bf0bc feat(admin): СМС-центр (smsc.ru) на плашке внешних сервисов — баланс, пороги, кнопка пополнения
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 08:25:28 +03:00
Дмитрий bb20a8859d @
feat(sales): приём номеров в рекламную аудиторию

Сервис-канал POST /api/sales/integration/ad-audience (тот же X-Sales-Token,
что и «Отдать менеджеру»). Новый номер добавляем, известный — продлеваем срок
из настройки и просим ночной джоб дослать (synced_at=NULL, removed_at=NULL).
Формат Яндекса строгий: 11 цифр с 7, без плюса.

Task 3 плана 2026-07-19-reklamnaya-auditoriya-kabinet-nachalnika.

Моделям дописаны @property-докблоки (конвенция проекта, как у SalesProspect) —
без них Larastan не видит полей.

Тесты: 6 в tests/Feature/Sales/AdAudienceIntakeTest.php, вся Sales-пачка 264 зелёные.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@
2026-07-19 08:16:14 +03:00
Дмитрий d7e6766760 feat(sales): модели рекламной аудитории 2026-07-19 08:01:58 +03:00
Дмитрий 237be1f7fd @
feat(судья): закрыты три слепых пятна — сор из избы, маршрут обязательств, разворот сферы

Четыре дефекта живого прогона 18.07 судья не видел. Три из них закрыты
приборами и рубрикой, четвёртый (противоречие себе) уже покрыт признаком 13
и ждёт первого живого запуска коллегии.

Лечим КЛАСС, а не случай — ни одного слова из живого разговора в коде нет:

  43 «сор из избы» (правдоруб, СТОП) — признание клиенту наших сбоев и
     внутренней кухни. Смысловой, судит роль. Тест запрещает привязку к фразе.
  44 «зона обязательств ведётся умным мозгом» (прибор, ТРЕВОГА) — меряем
     РЕШЕНИЕ маршрута, а не имя модели. Зона = вопрос И тема обязательств.
     🔴 Вторая сторона правила важнее первой: когда ухо съедает ключевое слово,
     реплика клиента в журнале уже испорчена — зато её ответ цел. Заговорила
     про обязательства с быстрого мозга — тот же дефект с уцелевшей стороны.
  45 «разворот сферы доведён до конца» (прибор, ТРЕВОГА) — судья не знает
     ни одной ниши, читает след моста: заметил чужую сферу и не сменил справку.

Мост НЕ тронут: весь след уже был в журнале, надо было научиться читать.

🪤 Третий раз одни грабли: первая версия прибора резала правду
(«Договорились!» → договор, «Отвечаю:» → ответственность). Поймано только
адверсарным прогоном НА ПРАВДЕ. Формы сужены, регрессия закреплена тестом.
Итог прогона: 12/12 верных реплик чисто, 4/4 бракованных пойманы.

Тестов 61 → 77.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
2026-07-19 07:49:02 +03:00
Дмитрий d7eaee6021 feat(finder): убраны значки 📱☎️ из подписей номеров (просьба владельца)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 07:46:00 +03:00