Commit Graph

1492 Commits

Author SHA1 Message Date
Дмитрий cb81d9faad @
feat(sudya): спектр продажи по нашей методике + накопитель слабых мест

Судья умел говорить, чего она НЕ должна говорить (красные флажки), но не умел
судить, КАК она продаёт. Добавлены признаки по методике из базы наставника:
крючок, лесенка подачи, финал по теплоте, ведёт вопросами, цену вперёд не тащит.

🔴 Живой замер на сервере опроверг мою догадку. Я ждал, что она ВЫВАЛИВАЕТ доводы
пачкой — а она за шесть реплик назвала ОДИН блок из тринадцати. Говорит 71% времени
и почти ничего не доносит: много сочувствия, мало содержания. По доле речи это
не видно, поэтому добавлен признак 42 на пустословие. Детектор блоков проверен
на эталонном тексте базы — узнаёт 12 из 13, то есть находка настоящая, не слепота.

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

🔴 Судья ДОКЛАДЫВАЕТ, а не правит: где промахнулась, как часто, дословные примеры.
Что чинить — решает владелец. Советов «крути здесь» в накопителе нет намеренно.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
2026-07-18 19:36:31 +03:00
Дмитрий 28db52fb6e docs(разведка): проверка первоисточниками — 4 факта Perplexity оказались неверны + письмо в поддержку Яндекса
Три агента перечитали официальную документацию Яндекса, ВК и МТС.
Итог: Perplexity дал минимум 4 неверных факта, каждый пойман только
чтением первоисточника.

Исправлено:
- площадок ТРИ, не четыре: myTarget мёртв, вся дока 301 → ads.vk.ru
- ВК: два порога — 100 руками, 2000 через API (мы автоматизируем ⇒ 2000);
  создание не более 1 списка в час
- МТС: 0,48 руб/показ по прайсу от 01.06.2026, а не 0,9 (цена 2024 года);
  показывает ТОЛЬКО не-абонентам МТС ⇒ канал B3 через Telegram недостижим
- квоты Аудиторий: 5000 запросов в СУТКИ на логин, а не 100 в секунду
- медийка: от 300 руб/день, порога 35 000 не существует
- PUT /segment/{id} меняет только название; данные — modify_data
  с режимами addition/subtraction/replace
- автотаргетинг на Поиске отключить нельзя (но и не надо — сегмент режет
  через «И», автотаргетинг задаёт лишь набор запросов)
- look-alike выключается конкретной радиокнопкой «Только выбранный сегмент»

Новое:
- фича универсальная: клиент жмёт кнопку, Лидерра — такой же клиент у себя же
- агентский логин Директа без условий, но договор — 3 клиента и 3 млн/мес
- в API Аудиторий аналога Client-Login НЕТ; мультиклиентность через делегатов,
  но в интерфейсе такого раздела нет ⇒ 5 блокирующих вопросов в поддержку
- Метрика уже стоит (счётчик 110476275) — второй бесплатный источник сегментов

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-18 19:33:37 +03:00
Дмитрий fbca7e13f9 @
feat(sudya): оркестратор, карточка звонка и приёмка на подсадных ошибках

Оркестратор сам ничего не проверяет — собирает слои и выносит итог: любой СТОП →
СТОП, иначе ТРЕВОГА, иначе НОРМА. Сломанный якорь перекрывает всё. В бою полная
коллегия не зовётся — только приборы, деньги владельца берегутся.

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

Проверено сквозняком на живом журнале, а не только на выдуманных примерах:
судья честно поймал, что она говорит 57% времени при норме 35–45%.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
2026-07-18 18:59:29 +03:00
Дмитрий e2e054365c @
feat(sudya): приборы техники и характера + сторож покрытия рубрики

Техника: задержки до звука и до ответа, потеря реплики клиента (СТОП — мост однажды
глох во время думания и сказанное исчезало), выдумки ушей на тишине, доля её речи.

Характер v9: женский род, «вы», повторное приветствие, своё имя вместо обращения,
длина реплики, латиница. Женский род правится ТОЛЬКО про себя — тест стережёт
живую ловлю 18.07, когда сторож сделал пациента-мужчину «готова».

🔴 Сверка вскрыла дыру в собственном плане: признаки 8 и 9 стояли в рубрике как
измеряемые, а кода не было — судья молча делал вид, что проверяет. Признак 8
реализован (перебивания были, а обрезки нет — тот самый утренний дефект моста).
Признак 9 честно записан как неизмеряемый: журнал пишет, что мост ЖДАЛ думающего,
но не пишет случаи, когда оборвал. Добавлен тест покрытия: каждый приборный признак
либо имеет код, либо стоит в списке с причиной. Дыра больше не спрячется.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
2026-07-18 18:55:03 +03:00
Дмитрий bf30a83ed2 @
feat(sudya): прибор правды о числах и коллегия судей со слепым протоколом

Прибор ловит два дефекта, пойманных владельцем живьём: числа вне замка фактов
и цену наизнанку («от пятидесяти пяти до двадцати пяти» вместо «от двадцати пяти
до пятидесяти пяти»).

🔴 Адверсарный прогон поймал ПОВТОР истории: прибор резал правду «минимальный
заказ — одна заявка». Ровно на этой форме сторож чат-бота однажды уже съел верный
ответ и оставил клиенту обрубок. Добавлены формы один/одна/одну/штука, случай
закреплён отдельным тестом — тестов из плана было мало, они этого не видели.

Коллегия: четыре узкие роли вместо одной большой рубрики, вопрос НЕЙТРАЛЬНЫЙ
(наводящий однажды заставил судью объявить роботом живого диктора), находка без
цитаты понижается до сигнала, якорь-контроль, автостоп и кеш по деньгам.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
2026-07-18 18:47:52 +03:00
Дмитрий 7ee40fcde5 docs(разведка): реклама по своим номерам (4 площадки) + аналоги поставщика в СНГ и мире
Две находки одной сессии.

1. Реклама по своим номерам — берём все 4 площадки:
   Яндекс Аудитории (порог 100, полный API, максимум площадок), VK Реклама
   (порог 100 — поправка, 2000 относится к старому myTarget), myTarget (2000,
   ради Одноклассников), МТС Маркетолог → Telegram Ads (0,9 руб/показ, без API).
   Главный сценарий — недозвоны, которые сейчас списываются в ноль.
   Look-alike выключен намеренно: согласие давали конкретные люди.
   Грабли автовыгрузки: формат 79995551111, SHA-256 без соли, статус
   «Обновляется» на несколько часов после заливки.

2. Аналогов нашего поставщика (ГЦК/BG) нет нигде — проверено 9 стран СНГ
   и 12 мировых рынков. Операторы монетизируют данные, но контакт наружу
   не выдают: закрытый двор от Казахстана до США. Законы всех девяти стран
   СНГ требуют отдельного согласия на передачу номера третьему лицу.
   Российская модель — аномалия, моат сильнее, чем считали.
   Казахстан — единственный перспективный (Kcell Target Call, DMP.one уже там).
   Беларусь закрыта законом 99-З дословно. Узбекистан — вторым, разведать глубже.

Дыры выписаны честным списком: локализация данных в КЗ/УЗ, реальные цены,
льёт ли BG по Казахстану (проверяется тестовым проектом за час).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-18 18:45:16 +03:00
Дмитрий ec8cd1ddb2 @
feat(sudya): разбор журнала моста и надзор за судьями

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

Надзор: промах = судья сказал НОРМА, а владелец нашёл брак ушами. Сказал СТОП —
поймал, не промах. Доля выше 20% — судья негоден. Без этого слоя коллегия
со временем халтурит незаметно: она же сама себя и оценивает.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
2026-07-18 18:43:12 +03:00
Дмитрий 17cf679652 @
feat(sudya): план судьи + рубрика на 36 признаков

Рубрика — единственный источник правды по критериям: спектры I звук, II техника
разговора, III правда и обязательства, IV характер, V продажа (новое — этого не
судил ни один прежний судья), VI проверка самого судьи.

Каждый признак несёт тяжесть: СТОП двигает вердикт в брак, ТРЕВОГА зовёт смотреть
глазами, СИГНАЛ идёт только в статистику. Из 36 признаков 16 — СТОП.

Тесты закрепляют целостность: ровно 36, номера без дыр, поля из допустимых множеств,
спектр V судит только продавец.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
2026-07-18 18:39:18 +03:00
Дмитрий d2b74c8b04 docs(finder): спека и план проверки телефонов (ДаДата + HLR)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 18:22:40 +03:00
Дмитрий f386a2e27b fix(finder): мусорные номера подписаны «номер не распознан» (по спеке)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 18:21:48 +03:00
Дмитрий f8bd812807 test(finder): отрисовка размеченных номеров закреплена тестами (HTML + Excel)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 18:13:33 +03:00
Дмитрий d9e82f6e3c @
docs(sudya): проект сводного судьи диалогов Александры

Собран из трёх существующих семейств судей: голосовые (NISQA/дуэли/glitch),
чатовые (AnswerGuard + слепые судьи-агенты), нормативные (llm-judge/reviewer-agent/
judge-evaluator). Ни одно из них не судило ГЛАВНОЕ — продала ли она; для номера
менеджера по продажам на лендинге это ключевой спектр.

Устройство: приборы (приговор) → коллегия узких судей-ролей (находки) →
судья над судьями (доля промахов). 36 признаков в шести спектрах.

Протокол честности, каждое правило оплачено ошибкой: слепота, нейтральный вопрос,
живой якорь в партии (забраковал якорь → партия недействительна), роли вместо
одной большой рубрики, находка без цитаты источника не считается.

Границы названы явно: звук живых клиентов не пишем (ПДн — отдельная работа),
судья не правит Александру сам, владелец остаётся высшей инстанцией.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
2026-07-18 18:09:39 +03:00
Дмитрий f2221101c6 feat(finder): метки номеров в списке и Excel — живые мобильные первыми, мёртвые зачёркнуты
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 18:04:24 +03:00
Дмитрий ba62359337 fix(finder): нераспознанные номера в warning (не теряем), guard эндпоинта, секреты читаем раз
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 17:58:37 +03:00
Дмитрий 3db5115e6e feat(finder): проверка номеров встроена в сбор контактов (+2 поля Firm)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 17:46:52 +03:00
Дмитрий 40fa829738 fix(finder): оркестратор — сбой на одном номере не роняет остальные; junk-путь закреплён тестами
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 17:41:49 +03:00
Дмитрий c3eff704a3 feat(finder): оркестратор проверки номеров + сортировка для показа
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 17:34:49 +03:00
Дмитрий 74f3048e91 fix(finder): HLR — id=0 не терять, строковые статусы коерсить, опрос короче (8×0.5с)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 17:30:32 +03:00
Дмитрий 7255a4c1b6 feat(finder): HLR СМС-центра — живой/отключён/не существует, кэш вердиктов
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 17:23:29 +03:00
Дмитрий 7d57c8277d fix(finder): кэшировать любой оплаченный ответ ДаДаты — незнакомый тип не даёт платить дважды
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 17:19:58 +03:00
Дмитрий d358ddb6f3 feat(finder): ДаДата clean/phone — мобильный/городской, оператор с MNP, кэш
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 17:14:38 +03:00
Дмитрий 4ddcc6abae feat(finder): нормализация телефонов досье — дубли и хвосты-обрезки долой
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 17:00:45 +03:00
Дмитрий 277800149f @
fix(sales): строгое разделение двух ролей начальника — экраны «мои» больше не показывают отдел

Начальник продаёт сам и руководит отделом. Правило «начальник видит всё»
применялось к ЧЕЛОВЕКУ, а не к экрану, поэтому разделы меню врали:

- «Потенциальные клиенты» показывали все карточки отдела — точную копию
  «Воронки отдела» (это владелец и заметил);
- «Мои клиенты» и «Сводка» — всех клиентов отдела;
- «Привязать клиента» — очередь заявок отдела, причём в ЧУЖОМ формате
  ({pending, history} вместо {data}), форма получала не те данные.

Новое правило: роли разделяются по ЭКРАНУ. Любой запрос по умолчанию отдаёт
только личное — включая начальника. Весь отдел выдаётся только по явному
?scope=department, и просят его только экраны раздела НАЧАЛЬНИК. Менеджеру
параметр ничего не даёт: проверка по роли, не по параметру.

ownedTenantIds теперь ВСЕГДА личные привязки (тип сузился с ?array до array),
добавлен visibleTenantIds для области видимости запроса.

Правом начальника осталось открыть ЛЮБУЮ карточку — кандидата и клиента:
иначе из «Воронки отдела» не открылась бы карточка чужого менеджера.
Ограничены только списки, не доступ к записи.

Следствие: у начальника сейчас 0 своих кандидатов и клиентов, поэтому его
личные экраны станут пустыми — это правильно, а не поломка.

Pest 272/272, Vitest зелёный, vue-tsc чист, Larastan без своих ошибок.
Спека — §23.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
2026-07-18 15:59:56 +03:00
Дмитрий e9edb206c8 @
feat(sales): кабинет начальника — единый период (без 500) + пополнения баланса

Экраны начальника отстали от вчерашних правок кабинета менеджера (§16–§21).

1. Живой баг: «Произвольный» период до выбора дат ронял запрос в 500 на пяти
   точках из шести (сводка отдела, доход, результативность, выплаты, тарифы).
   Разбор периода вынесен в трейт ResolvesSalesPeriod: 422 вместо падения,
   период по умолчанию d30 вместо this — как показывает сам PeriodPicker.

2. Пополнения баланса (topup_rub) добавлены в dashboard/overview (kpi +
   строки менеджеров) и managers/performance. На экранах: плашка «Пополнили
   баланс» в сводке отдела и колонка «Пополнили ₽» в обеих таблицах
   результативности. Из подписей убрано «(мес)» — период больше не месяц.

3. «Воронка отдела» правок не потребовала: она рендерит те же доску и карточку,
   что экран менеджера, а карточка сама грузит журнал и сохраняет контакты.

Тесты: SalesPeriodRequestTest проходит по всем шести точкам сразу.
Pest 263/263, Vitest 1420/1420, vue-tsc чист.
Спека — §22.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
2026-07-18 15:20:44 +03:00
Дмитрий 1514991ceb @
style(sales): Pint — порядок импортов в SalesClientsController

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
2026-07-18 14:40:02 +03:00
Дмитрий 6416ec3789 @
feat(sales): периоды сегодня/вчера/7/30 дней календарём, пополнения за период, скролл доски наверх

Замечания владельца по сводке и доске.

ПЕРИОДЫ:
- Резолвер понимает today/yesterday/d7/d30; «7 дней» = сегодня и 6 предыдущих.
  Месячные this/prev/prev2 сервер принимает по-прежнему.
- По умолчанию 30 дней.
- ПОЧИНЕНО: выбор «Произвольный» без дат ронял запрос (500). Теперь понятный 422,
  а на фронте период применяется только когда отмечены ОБЕ даты.
- Произвольный выбирается календарём-диапазоном, не руками; порядок дат неважен.

ПОПОЛНЕНИЯ ЗА ПЕРИОД (переиспользован готовый topupsRub):
- Сводка: плашка «Пополнили баланс» рядом с «Σ баланс».
- Мои клиенты: колонка «Пополнил».
  Заодно даёт число, которое видимо меняется при смене периода.

ДОСКА: горизонтальная полоса прокрутки поднята НАД колонками (колонки высокие,
системная полоса уезжала за экран); синхронизация в обе стороны, ResizeObserver
на приезжающие карточки.

Спека §21 (включая §21.4 — как по коду заполняется «Требуют внимания»).
Гейты: Pest 257/257 sales+unit, Vitest 1417, vue-tsc чисто, Larastan 0 в своих. TDD.

LEFTHOOK_EXCLUDE: larastan/cspell падают на файлах параллельной сессии.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
2026-07-18 14:29:18 +03:00
Дмитрий 1e0442facf @
feat(sales): результат разговора и его содержание — одной записью в журнале

Владелец: «объедини в одну запись: договорились… сверху, внизу текст».
Было две записи подряд — в ленте читалось как два события, хотя разговор один.

- БД v8.75: sales_prospect_notes.title VARCHAR(500) NULL — что решили.
- Одна запись kind=note: title = «Договорились на созвон 20.07.2026 17:35»,
  body = краткое содержание. Есть результат без содержания — как раньше,
  одна запись kind=stage без заголовка. Ручная заметка — без заголовка.
- В ленте заголовок строкой сверху, под ним текст.
- Старые парные записи задним числом не сливаем — историю не переписываем.

Спека §20.2. Гейты: Pest 240/240 sales, Vitest 64/64 воронка, vue-tsc чисто,
Larastan 0 в своих файлах. TDD RED→GREEN.
🪤 Тесты DOM для v-dialog: контент уезжает телепортом в body — искать через
document.body.querySelector, а не wrapper.find.

LEFTHOOK_EXCLUDE: larastan/cspell падают на файлах параллельной сессии.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
2026-07-18 14:00:55 +03:00
Дмитрий 2cd99d6ddc @
fix(sales): одна кнопка «Сохранить» в карточке — контакты больше не отдельно

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

- Кнопка «Сохранить контакты» и подпись «сохранено» убраны.
- «Сохранить» теперь: сохраняет контакты → если выбран результат, проверяет
  и отправляет его → если результат не выбран, просто закрывает окно.
- Контакты не сохранились (нет сети) — результат НЕ отправляем и показываем
  ошибку, иначе стадия уехала бы, а телефоны потерялись.
- submit() стал асинхронным, кнопка показывает загрузку.

Спека §20.1. Гейты: Vitest 62/62 воронка (4 новых теста), vue-tsc чисто. TDD.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
2026-07-18 13:49:14 +03:00
Дмитрий 09f67a92a9 docs(golos): стенд-двойник боевого звонка + первый честный замер задержек
Прочитан боевой /root/bridge.py и aleksandra_engine.py (только чтение).
Собран офлайн-стенд, повторяющий звонок узел в узел; задержки измерены,
а не смоделированы арифметикой.

Замер (до звука / до ответа): как на сервере 8.17/18.68с → правка ушей
2.18/12.36с → + голос по фразам 2.19/10.33с. Тот же диалог короче на 79с.

Два дефекта в боевом мосту (не правил, только зафиксировал):
- sleep(0.1) кормит уши уже записанной фразой в реальном времени (−4.4с);
- результат play() «перебили» выбрасывается, в историю идёт полный текст.

Плюс правила перебивания и поддакивания, выведенные на слух, и разбор
готовых решений вендоров (Ink-2 только английский, ElevenLabs юр. блокер).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 13:37:21 +03:00
Дмитрий 138550207a @
feat(sales): содержание разговора — вместе с результатом; недозвону дата перезвона

Владелец после первого показа карточки: «куда звонить убери; договорились на
созвон — ниже краткое содержание, и оно копится в Историю разговоров, свежие
в начало; недозвон — поставь дату следующего перезвона».

- Блок «Куда звонить» убран: дублировал строку «Телефон общий» из данных фирмы.
- «Краткое содержание разговора» переехало последним полем в блок «Результат
  разговора» (было отдельное поле с кнопкой слева — два места для одного
  действия). Уходит параметром summary вместе с результатом, ложится в журнал
  отдельной записью kind=note. Пустое — не пишем.
- Порядок внутри одного сохранения: сначала автозапись про этап, затем
  содержание → у него больший id и в ленте оно оказывается НАД этапом.
- Недозвон получил необязательное поле «Когда перезвонить»: пишется в
  next_call_at (видно на плитке) и дописывается в автозапись журнала.

Спека §20. Гейты: Pest 236/236 sales, Vitest 59/59 воронка, vue-tsc чисто,
Larastan 0 в своих файлах. TDD RED→GREEN.

LEFTHOOK_EXCLUDE: larastan/cspell падают на файлах параллельной сессии
tests/Feature/Admin/*Balances*, свои файлы проверены отдельно и чисты.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
2026-07-18 13:20:27 +03:00
Дмитрий 41fbc4491d @
feat(sales): журнал разговоров по кандидату + переделка обоих окон воронки

Владелец: «краткое содержание разговора писать, чтобы не потерять историю»
и «переделай обоих дизайн, очень не практично, спроси у перплексити».

ЖУРНАЛ (БД v8.74, таблица sales_prospect_notes, append-only):
- kind=note — менеджер написал руками; kind=stage — автозапись о переезде
  по стадии (взял в работу, созвон, недозвон, отказ, регистрация).
  Повторное открытие карточки журнал не засоряет.
- GET/POST /api/sales/prospects/{id}/notes; лента свежими сверху, грузится
  при открытии карточки, а не вместе с доской.
- PATCH /prospects/{id} БОЛЬШЕ НЕ принимает notes: он молча затирал прошлую
  запись — ровно та потеря истории, ради которой журнал и появился.
  Нашёл rls-reviewer, закрыто тестом. Старые notes перенесены в журнал.

ДИЗАЙН (по разбору Pipedrive/HubSpot/Salesforce через Perplexity):
- Карточка 1100px, две колонки. Слева «что за фирма» + история разговоров
  с полем «о чём поговорили». Справа зона действия: «Куда звонить» (номер
  крупно, ссылкой tel:), «Результат разговора» своим фоном, контактные лица.
  Кнопки внизу окна. Пустая история объясняет, что делать.
- Форма создания разбита на разделы: Компания / Контактные лица / Заметка;
  Юрлицо и Город в одну строку; обязательных полей по-прежнему два.

Спека §19. Гейты: Pest 231/231 sales, Vitest 60/60 воронка, vue-tsc чисто,
Larastan 0 в своих файлах, rls-reviewer PASS. TDD RED→GREEN.

LEFTHOOK_EXCLUDE: larastan/cspell падают на файлах параллельной сессии
tests/Feature/Admin/*Balances*, свои файлы проверены отдельно и чисты.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
2026-07-18 13:04:02 +03:00
Дмитрий b0750dce53 @
feat(sales): сайт кандидата ссылкой прямо на плитке доски

Менеджеру удобно глянуть на клиента перед звонком, не открывая карточку.

- На плитке колонки сайт выводится ссылкой (target=_blank, rel=noopener),
  click.stop — иначе клик по ссылке заодно открывал бы диалог карточки.
- Расчёт ссылки вынесен в utils/prospectSite.ts и переиспользован в карточке:
  домен получает https, готовый адрес не трогаем, пусто → строки нет.

Гейты: Vitest 55/55 по воронке (4 новых теста доски), vue-tsc чисто.
TDD RED→GREEN.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
2026-07-18 12:36:14 +03:00
Дмитрий 68428f16c6 @
feat(sales): карточка кандидата — девять полей владельца + правка контактных лиц

Владелец задал список полей карточки и попросил дать менеджеру записывать
контактных лиц прямо в карточке: поиск отдаёт 5-7 ПРЕДПОЛАГАЕМЫХ номеров
директора, живой из них выясняется обзвоном; а если директор перенаправил
к маркетологу — менеджер фиксирует и его, людей может быть 2-3.

- Карточка показывает РОВНО девять полей в его порядке: Юрлицо, Сайт, ИНН фирмы,
  Адрес, Телефон общий, Каналы, Бюджет, Директор, Тел. директора (предполагаемый).
  Убраны Оценка, Достоверность, Реклама, Запросы в Директе, ОГРН, Статус юрлица,
  Личный ИНН директора, Почта директора — данные остаются в payload.
- PATCH /api/sales/prospects/{id}/contacts — список заменяется целиком, пустой
  стирает всех; права как у update() (менеджер свои, начальник любые); главный
  телефон карточки из поиска не трогается.
- ProspectContactsEditor.vue + utils/prospectContacts.ts — один редактор контактов
  на диалог создания и карточку; диалог создания переведён на него.
- Закрывает хвост §16: у 24 живых карточек из поиска контактных лиц не было,
  теперь дозаполняются руками.

Спека §18. Гейты: Pest 223/223 sales, Vitest 196 файлов / 1397 тестов,
vue-tsc чисто, Larastan 0 в своих файлах. TDD RED→GREEN на каждом шаге.

LEFTHOOK_EXCLUDE: larastan/cspell падают на файлах параллельной сессии
tests/Feature/Admin/*Balances*, свои файлы проверены отдельно и чисты.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
2026-07-18 12:29:04 +03:00
Дмитрий 48dd6c44af @
fix(sales): «Зарегистрировался» подаёт ЗАЯВКУ начальнику, а не привязывает клиента сразу

Владелец заметил: в отделе есть штатный порядок «через начальника» (экран «Заявки
на привязку»), а кнопка в карточке кандидата создавала SalesClientAssignment напрямую
и в истории заявок ничего не появлялось.

- registerProspect() теперь зовёт SalesAttachmentService::submit() от имени ВЛАДЕЛЬЦА
  карточки. Свободен → заявка pending + письма менеджеру и начальникам; занят другим →
  заявка с пометкой конфликта, чужая привязка не трогается; уже свой → заявки нет.
  Привязка со снимком тарифа создаётся только при одобрении (handleApprove).
- Стадия карточки едет в «Зарегистрировался» СРАЗУ (решение владельца): регистрация —
  факт, воронка показывает правду; «чей клиент и кому деньги» решает начальник.
  Автожизнь не зависит от одобрения — джоб смотрит linked_tenant_id.
- Отменён прежний 422 «клиент уже закреплён за другим менеджером» — теперь это заявка.
- Подсказка под полем e-mail говорит про одобрение начальником.

Спека §17 (отменяет правило §6 «привязываем сразу»). Гейты: Pest 218/218 sales,
Vitest 38/38, Larastan 0 в своих файлах. TDD RED→GREEN.

LEFTHOOK_EXCLUDE: larastan/cspell падают на файлах параллельной сессии
tests/Feature/Admin/*Balances*, свои файлы проверены отдельно и чисты.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
2026-07-18 12:09:24 +03:00
Дмитрий b3d758c81b @
style(sales): Pint — импорты ДаData в тесте кандидатов

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
2026-07-18 11:40:27 +03:00
Дмитрий 1011a37e77 @
feat(sales): кандидат — ИНН обязателен, юрлицо отдельно от бренда, контактные лица

Замечание владельца по форме «Добавить кандидата»: телефонов бывает много, нужны
ФИО и должность человека, ИНН обязателен, бренд и юрлицо — разные вещи, юрлицо
подтягивать по ИНН через ДаData.

- БД v8.73: sales_prospects.legal_name VARCHAR(500) + contacts JSONB DEFAULT [].
  phone остаётся «главным телефоном»; inn в БД по-прежнему nullable — карточки
  из поиска и 24 живые строки могут быть без него.
- store(): ИНН обязателен + контрольная сумма ФНС; повтор ИНН у того же
  менеджера — понятный 422 вместо 500 от уникального индекса; пустые контакты
  и телефоны отсекаются; phone = первый телефон первого контакта.
- POST /api/sales/prospects/lookup-inn — юрлицо/город по ИНН через готовый шов
  PartyLookup (тот же, что в «Реквизитах»). Ничего не сохраняет; ДаData молчит
  или упала — заводим руками.
- Диалог создания: ИНН* с кнопкой «Найти», бренд*, юрлицо, блок контактных лиц
  (+человек / +телефон). Карточка показывает юрлицо и контакты со ссылками tel:.

Спека §16, CHANGELOG_schema v8.73. Гейты: Pest 28/28, Vitest 38/38,
Larastan 0 в своих файлах, rls-reviewer PASS 4/4.

LEFTHOOK_EXCLUDE: larastan/cspell падают на файлах параллельной сессии
tests/Feature/Admin/*Balances*, свои файлы проверены отдельно и чисты.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
2026-07-18 11:39:48 +03:00
Дмитрий 5b51c738b0 feat(sales): миграция — стадия «Взят в работу» + источник карточки (search|manager)
stage CHECK += in_work (между new и negotiation); новая колонка source
VARCHAR(16) NOT NULL DEFAULT 'search' CHECK (search|manager) + индекс.
CHANGELOG_schema v8.72. RLS-review PASS 7/7 (GRANT наследуется колонкой,
идемпотентность и down() прогнаны, squawk 0).

NB: LEFTHOOK_EXCLUDE=larastan,cspell — гейты падали на ЧУЖИХ файлах параллельной
сессии (Admin/Billing тесты балансов), мои файлы их проходят.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 10:43:18 +03:00
Дмитрий 793f8d3d4e docs(golos): главный открытый хвост Александры — ПЕРЕБИВАНИЕ друг друга (barge-in)
Владелец после приёмки v9: «круто получилось, но остался один момент стерильности — это перебивание друг друга».
Разговор идёт строго по очереди; поддакивания «угу/ага» поверх речи клиента — полшага, не перебивание.

Нужно: (1) клиент заговорил → бот мгновенно замолкает на полуслове (VAD + стоп TTS, «угу/ага» не считать перебиванием);
(2) Александра подхватывает на паузе-вдохе, не дожидаясь тишины; (3) обрыв реплики = норма (конфликт с trim_reply).
Живёт в живом мосту /root/bridge.py (RTP в обе стороны) → пересекается с сессией Манго, делается на сервере.
Ломает текущую честную модель пауз (real_gap + филлер).

FLOOR-ESCAPE: владелец разрешил («фиксируй все»).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-18 10:22:53 +03:00
Дмитрий 34bc92ca73 Merge feat/aleksandra-knowledge-base → main: мозг знаний + Александра v9 «друг-профи»
Приносит в main всю работу над голосовым роботом-продажником Александрой:
- База знаний (мозг): дерево карточек ниш, get_brief, лесенка, реальный AITunnel-индекс.
- Эксперимент оператор+наставник v1-v9: от гибрид-маршрута и замка фактов до системного разворота характера в «грамотного друга-коллегу в мире клиента» (не впаривает, делится).
- Голосовой движок engine_v9.py: эталонный звук + мозг v9 + реальный замер + человеческие филлеры + код-стопор длины. Владелец: «идеал».

Слияние чистое, только добавляет (knowledge/aleksandra + docs/superpowers/experiments+findings). Прод-код / render-service / телефония Манго НЕ тронуты. Записи/ключи — вне git.

FLOOR-ESCAPE: владелец разрешил ("гит и мэйн не забудь").

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 21:14:52 +03:00
Дмитрий 7d0c06ca51 docs(golos): v9 «друг-профи» + голосовой движок engine_v9 — идеал по слуху владельца
Системный разворот характера (по разбору владельца, что v8 впаривала/суше):
- Оператор = грамотный ДРУГ-КОЛЛЕГА в мире клиента (не продавец): понимает его боль (дорогой директ, клики золотые, клиентов то густо-то пусто), делится как о находке, на «вы», не эхает жаргон («директолог»→«та реклама, что крутится»). Знаний внутри нет — их несёт наставник, скорость сохранена.
- Наставник даёт ПОНИМАНИЕ {za_klientom,opora,fakt,flazhok}, не команду-приём.
- engine_v9.py: эталонный звук 1-в-1 + мозг v9 + реальный замер пауз + филлеры без «лезу в базу».

Шлифовка на слух (7 итераций): краткость→«вы»→друг-персона→филлеры человеческие→общие без хвостовых многоточий→ход-1 без «представилась»→код-стопор trim_reply (2-4 предложения до вывода, не рубит мысль)→«только по-русски» (no-show коверкался голосом)→код-подстраховка «вы».

Итог: реплики договорены, эхо 0, впаривания нет, паузы ~1.8с. Владелец: «нет ни одного замечания, создали идеал». Запись/ключи — ВНЕ git.

FLOOR-ESCAPE: владелец разрешил коммит («так фиксируй, это фиксируй тоже»).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 20:56:42 +03:00
Дмитрий 0bcfb8c09c docs(golos): эксперимент оператор+наставник v6/v7/v8 — закрыт баг «наставник пишет реплику» (разбор Перплексити)
- v6 (stand6.py): строгая 4-строчная шпаргалка МОТИВ/ХОД/ФАКТ/ФЛАЖОК — помогло частично (утечка реплики 5/8).
- Перплексити разобрала промты, дала 4 приёма (JSON, инфинитивы, few-shot, наставник-классификатор).
- v7 (stand7.py): наставник→JSON {motiv,hod,fakt,flazhok} + оператор-антипаррот + защита в коде (при срыве JSON текст наставника выбрасывается) → паррот 0/8, баг закрыт на уровне системы.
- v8 (stand8.py): наставник-КЛАССИФИКАТОР (ход = код из enum) → валидный ход 8/8, ретраи 0, паррот 0. Пойман мой баг: chat резал подчёркивания и портил коды/JSON-ключи. Зазубрина хода 7 (флэш представлялся заново на закрытии) залечена: запрет пере-представления + эскалация закрывающих ходов на хайку.
- дописан раздел 4б + обновлены шапка/стартовый блок находок.

FLOOR-ESCAPE: владелец разрешил коммит.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 11:34:44 +03:00
Дмитрий 6ed30ff042 docs(golos): эксперимент оператор+наставник v4/v5 — гибрид-маршрут, замок фактов, фикс долг/долгий
- v4 (stand4.py): наставник=всегда хайку, оператор=лёгкий промпт с маршрутом эталона (флэш на простое/хайку на сложное). На диалоге продаж флэш не включается — всё сложное; выигрыш скорости даёт лёгкий промпт (10x), а не смена модели.
- v5 (stand5.py): ПРИГВОЖДЁННЫЕ ФАКТЫ (числа дословно, запрет арифметики/производных) — баг с домысленными числами закрыт для haiku-оператора; +3 простых хода → флэш включился (ход-подтверждение ~0.8с), качество вровень/лучше эталонного флэша.
- фикс ловушки STOP_RE: долг(?![оаие]) — долгая/надолго/недолго больше не считаются горем (только в стенде; эталон на сервере не тронут).
- дописан раздел 4а + обновлены шапка/стартовый блок в находках.

FLOOR-ESCAPE: владелец разрешил коммит.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 10:54:01 +03:00
Дмитрий feecf22d1f docs(golos): эксперимент оператор+наставник — фиксация (3 прогона, баги, старт-промпт) + код стендов 2026-07-17 10:33:27 +03:00
Дмитрий 1393319d65 docs(golos): телефония Mango ПРОБИТА (бот отвечает через NAT) + звук + мозг знаний
- NAT-фикс: Contact=rport (порт-дырка) + keepalive → INVITE доходит, бот берёт трубку
- звук: реплика целиком (без underrun-хрипа) + нормализация громкости перед 8-бит
- канал Cartesia через EXA-тоннель (прямой поток РФ→США режется)
- мозг знаний подключён (заплата feat/aleksandra-knowledge-base), offline-смоук OK
- рестарт-промт под компакт обновлён; тест-номер замаскирован

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 09:51:58 +03:00
Дмитрий 2bc85f6979 docs(brain): turnkey-заплата подключения мозга к движку aleksandra_engine.py 2026-07-17 09:29:02 +03:00
Дмитрий 6b26bf28a7 feat(brain): построение индекса ниш из карточек + CLI-пересборка
index_store.py сканирует папку карточек, считает отпечаток каждой (embed_fn
инъекцией) и складывает индекс nisha/roditel/sinonimy/path/vector;
save_index/load_index — JSON roundtrip. build_index.py — CLI ручной
пересборки индекса (провайдер эмбеддингов через ALEX_EMBED). TDD: тест
падал на ModuleNotFoundError до реализации, затем 2 passed (плюс полный
пакет brain — 12 passed, регрессий нет).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 08:20:19 +03:00
Дмитрий 0df125c855 feat(brain): парсер карточки + сборка справки по дереву домен→ниша 2026-07-17 08:06:32 +03:00
Дмитрий f3b445b049 docs(brain): спека + план — хранилище знаний Александры (Obsidian) + умный поиск ниши 2026-07-17 07:56:06 +03:00
Дмитрий 5ec26c01d7 @
docs(plan): пошаговый план — прокси автоподбора (Proxy.Market) на плашке «Внешние сервисы»

7 задач TDD: /proxy-check на рендере, LivenessReading::warn, ProxyMarketProbe,
регистрация в реестре, контроллер (whitelist+topup+срок в payload), фронт-строка,
регрессия. Схему БД не трогаем. Выкат — отдельно, по команде владельца.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@
2026-07-17 07:27:19 +03:00
Дмитрий e8584c6214 @
docs(spec): дизайн — прокси автоподбора (Proxy.Market) на плашке «Внешние сервисы» Командного центра

Вариант А: живость проверяет виртуалка-рендер (у неё логин/пароль прокси),
портал показывает срок «до 04.08.2026» из конфига (владелец обновляет руками
при продлении). Схему БД не трогаем. Новая ручка /proxy-check на рендере +
ProxyMarketProbe + строка proxy_market в реестре/плашке.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@
2026-07-17 07:14:55 +03:00