🎙️ Call Analytics Playbook · Битрикс24 + AI

Методичка по сборке системы автоматической оценки качества телефонных звонков КЦ-операторов через AI на базе Битрикс24: транскрибация → QA по Калгари-Кембриджу → детектор «проёбанных» лидов → извлечение обещанных перезвонов → персональные планы обучения.

Стек: Bitrix24 REST + любая ВАТС-интеграция + STT (Whisper) + LLM (OpenRouter / Anthropic). Старт с нуля — 1 рабочий день, при наличии доступов.


🎯 TL;DR что строим

                 Bitrix24 webhook (scope: crm + telephony + voximplant + disk)
                                 │
              ┌──────────────────┴──────────────────┐
              ▼                                     ▼
     voximplant.statistic.get               crm.lead.get / crm.deal.list
       (метаданные звонков)                (outcome: won/lost/in_progress)
              │                                     │
              ▼                                     │
     ┌── crm.activity.get → FILES → disk.file.get   │
     │   (для ВАТС, у которых mp3 лежит в Activity) │
     ▼                                              │
    mp3 ──► Whisper-large-v3 (STT)                  │
              │                                     │
              ▼                                     ▼
       transcripts/*.txt ───► join по lead_id ───► bundle/per-client
                                       │
                                       ▼
                    ┌─────────────── 3 LLM-промпта ───────────────┐
                    │                                              │
              QA по Калгари-Кембридж        Lost-deals          Callbacks
              (5 этапов + 2 нити)           (горячие лиды,      (обещанные
                                            упущенные)           перезвоны)
                    │                       │                    │
                    └──────────────► dashboard.html ◄─────────────┘
                                     + персональный
                                     coaching.md

📡 1. Принципы работы с источниками данных (Битрикс24)

Что нужно из Битрикс24

Создаёшь входящий вебхук со scope:

URL вида https://<portal>.bitrix24.ru/rest/<userId>/<token>/.

Где лежат записи звонков

У Битрикса нет «единой полки записей» — каждая ВАТС-интеграция кладёт mp3 по-своему:

  1. Прямой URL — у части интеграций voximplant.statistic.get возвращает заполненный CALL_RECORD_URL. Качаешь GET без авторизации, готово.

  2. На Activity-объекте лида/контакта — большинство интеграций (виртуальные АТС, которые «подвешивают» записи к карточке лида) пишут mp3 как прикреплённый файл в Activity:

    voximplant.statistic.get → CRM_ACTIVITY_ID
    → crm.activity.get(id=...) → берёшь первый FILES[0].id
    → disk.file.get(id=file_id) → достаёшь DOWNLOAD_URL с временным токеном
    → качаешь curl-ом

    Это самый частый случай для входящих лидов от ВАТС-партнёров.

  3. Через API провайдера ВАТС напрямую — у каждого крупного оператора есть свой REST API с методами /statistics и /recording/play. Используй если запись не появилась в Битриксе или CRM не прокидывает её через Activity.

Принципиальное правило

Whitelist провайдеров. В voximplant.statistic.get поле REST_APP_NAME показывает, через какую ВАТС-интеграцию прошёл звонок. Чётко решай, какие из них анализируешь (обычно — те, через которые работают КЦ-операторы первой линии, обрабатывающие новые лиды), и фильтруй. Звонки логистов / менеджеров со старыми клиентами — другой процесс, не смешивай в одну выборку.


🛠️ 2. Pipeline (6 этапов, все идемпотентны)

# Скрипт Что делает
0 .env Bitrix webhook, STT key, OpenRouter key, мин. длительность
1 fetch_calls.py voximplant.statistic.get → фильтр whitelist → crm.activity.get + disk.file.get → mp3 + calls.csv
2 transcribe.py Whisper-large-v3 для каждого mp3 → transcripts/*.txt
3 pull_lead_outcomes.py Для каждого LEAD_ID из calls.csv — crm.lead.get + crm.deal.list?filter[LEAD_ID]=Xlead_outcomes.json
4 bundle.py (опц.) группирует все звонки одного клиента за день в markdown
5 analyze.py Применяет 3 LLM-промпта (QA, lost-deal, callback) через OpenRouter
6 build_dashboard.py HTML + per-operator markdown-фидбэк

Каждый шаг идемпотентный: если результат уже есть — пропускает. Можешь рестартовать пайплайн в любой момент после rate-limit, обрыва сети, нового аккаунта STT.


🧠 3. Три ключевых LLM-промпта

A. QA по Калгари-Кембриджу (медицинская модель консультации, адаптированная под телефон)

Структура: 5 последовательных этапов + 2 сквозные нити (отношения + структура диалога).

Этап Что проверяет
1. Установление контакта приветствие компании, представление оператора, имя клиента, причина
2. Сбор информации открытый вопрос → конкретика (что/куда/когда/как)
3. Уточнение и расчёт детали, специфика, форма оплаты, канал связи
4. Объяснение и планирование что произойдёт дальше (передача коллеге, ETA, кто перезвонит)
5. Закрытие резюме фактов, подтверждение клиентом, прощание
Нить: Отношения эмпатия, тон, не перебивает
Нить: Структура сигналит переходы, не теряет инициативу, обобщает по ходу

JSON-ответ: для каждого этапа — present + score 1-5 + tags + evidence (цитата ≤120 символов) + improvement (одна фраза в повелительном).

Корнер-кейсы (без них модель ошибается):

B. Lost-deals detector

Находит проёбанные лиды — клиент был тёплый/горячий, но не конвертировался по вине оператора (а не потому что не наш профиль или техническая проблема).

JSON:

{
  "is_lost_deal": true,
  "confidence": 0.85,
  "client_warmth": "горячий|тёплый|холодный|не_клиент",
  "lost_reason": "<категория>",
  "operator_actions_wrong": ["конкретные ошибки"],
  "what_to_say_next_time": "альтернативная реплика в прямой речи",
  "evidence_quotes": ["до 2 цитат"]
}

Главные универсальные паттерны проёба (адаптируй категории под свой вертикал):

  1. «Оператор сам назвал цену» — клиент сразу сворачивается на «дорого». Правильно: «цену просчитает специалист, пришлёт в мессенджер».
  2. «Не закрыл звонок» — клиент сказал «спасибо, я перезвоню», оператор отпустил без следующего шага. Правильно: «давайте зафиксирую заявку, пришлём просчёт без обязательств».
  3. «Не предложил альтернативу» — услуга не подходит, оператор не дал вилку.
  4. «Не отработал возражение» — оператор САМ озвучил возражение, которого клиент не делал.
  5. «Грубый/снисходительный тон» — портит впечатление о бренде, даже если формально отказ корректный.

C. Callback catcher

Извлекает из транскрипта обещанные перезвоны, парсит deadline в ISO.

JSON:

{
  "callbacks": [{
    "promised_by": "оператор|клиент|<коллега>",
    "deadline_text": "как звучало",
    "deadline_parsed_iso": "...+03:00",
    "deadline_type": "конкретное_время|относительное|неопределённое",
    "topic": "о чём",
    "client_explicit_consent": true,
    "evidence_quote": "цитата"
  }],
  "has_pending_callback": true,
  "urgency": "критично|обычно|не_срочно|нет",
  "operator_action_required": true
}

Правила парсинга времени (anchor = CALL_START_DATE):

КРИТИЧНОЕ anti-false-positive правило: Дежурная фраза «передам коллеге, он свяжется с другого номера» БЕЗ конкретного времени и БЕЗ имени — НЕ фиксировать как callback. Иначе пайплайн зальёт оператора шумом (это ~80% звонков).


📊 4. Бенчмарк моделей (как выбирать)

Что мерить

Метрика Зачем
ok_rate % звонков с валидным JSON-ответом
latency_med / p95 UX оператора и стоимость окна
cost_per_1k бюджет на масштабе
verdict_match_rate (cross-model) согласованность нескольких моделей
score_MAE_vs_baseline точность относительно ручной экспертной разметки
tags_jaccard_mean пересечение бинарных тегов

Принципы выбора production-модели


🎓 5. Lessons learned (универсальные)

Архитектура и метрики

  1. Дубли ВАТС-интеграций реальны. Один и тот же звонок может прийти через две интеграции в Битрикс (тот же phone, CALL_START_DATE ± несколько секунд, разные REST_APP_NAME). Дедуп обязателен — иначе один оператор в дашборде получит двойной счёт.
  2. Знаменатель в процентах = «релевантная подвыборка», а не «все звонки оператора». Иначе получишь «скрипт сказан в 118% случаев». Например, тег «звонок с другого номера» считай только по первичкам, а не по всем звонкам.
  3. Имя оператора берётся из транскрипта (STT слышит «оператор Иван»), но группировка строго по PORTAL_USER_ID — иначе один человек становится двумя строками когда не представился.
  4. Outcome лида — основа объективности. Без has_deal / deal_won ты оцениваешь стиль, а не результат. QA-score без outcome — это субъективщина.

Промпт-инжиниринг

  1. Калгари-Кембридж даёт структуру, а не галочки. Оператор может собрать 6 параметров, но если в случайном порядке — структура падает. Это лечится отдельной осью «structure» в промпте.
  2. Per-stage improvement лучше общего. «Подкрутить closing» полезнее чем «улучшить звонок».
  3. Корнер-кейсы в промпте — не опция. Без них модель штрафует за «не поздоровался» когда STT обрезал первую секунду.
  4. confidence 0-1 в JSON — must have. Низкий confidence — фильтр для дашборда (не показываем уверенно то, в чём модель не уверена).
  5. Цитаты ограничивай длиной (≤120 символов). Иначе модель вставит абзац и сломает UI.

Стоимость и стабильность LLM/STT

  1. Whisper-large-v3 на бесплатном tier большинства провайдеров имеет лимит секунд аудио/час. На ~200 звонков нужен либо часовой rest между батчами, либо платный tier.
  2. Reasoning-модели нужны reasoning.effort: low + max_tokens: 8000+. Иначе пустой ответ или обрыв на середине JSON.
  3. Бюджетные модели нестабильны на длинных промптах. Если промпт > 15K символов — % валидных JSON падает. Решение: либо ужать промпт, либо fallback на более крепкую модель при первом неудачном парсе.
  4. High recall / low precision — типовая болезнь дешёвых моделей на detection-задачах (callback, lost-deal). Если модель помечает 80% звонков как «требуют действия» — это шум. Используй ансамбль или строже формулируй anti-false-positive в промпте.
  5. «Vердикт средний» — статистический attractor многих моделей. Чтобы получить bimodal распределение (хороший vs плохой) — нужен либо ансамбль 2 моделей, либо ужесточение порогов в промпте, либо few-shot примеры краёв.

Безопасность и privacy

  1. mp3 — это PII. Не публиковать. Дашборд за SSO или хотя бы basic auth.
  2. Транскрипты для обучения / шеринга редактировать вручную или регекспами: имена/номера/адреса клиентов выкидывать или маскировать.

🚫 6. Red flags (что НЕ делать)

❌ Не делать ✅ Делать
Просить модель оценить «цену» или «работу других ролей» Только сам оператор и сам звонок
Считать в знаменателе все звонки Считать только «применимые» к данному тегу
Group by operator_name из транскрипта Group by PORTAL_USER_ID, name — display only
Штрафовать «не поздоровался» при STT-обрыве Поднять score этапа до 3 (floor)
Использовать temperature > 0 Всегда temperature: 0 для QA
Запускать на звонках < 15 сек Skip + помечать как мусор
Доверять одной модели на критичных решениях Бенч + ансамбль
Накапливать все теги в общий score Per-stage scores → суммировать в total
Сразу писать в Битрикс (создавать Activity по callback) Dry-run неделю → валидация супервайзером → потом продакшн

📁 7. Структура файлов

call_analytics/
├── .env
├── README.md
├── PLAYBOOK.md
├── scripts/
│   ├── common.py                  ← Bitrix REST helper + paths
│   ├── fetch_calls.py             ← тянет звонки → mp3
│   ├── transcribe.py              ← STT
│   ├── pull_lead_outcomes.py      ← lead status + deals
│   ├── bundle.py                  ← per-client markdown
│   ├── analyze.py                 ← LLM (day + bench mode, prompt-agnostic)
│   ├── bench_compare.py           ← сводка по моделям
│   ├── bench_vs_baseline.py       ← точность vs экспертная разметка
│   ├── insights.py                ← недельная аналитика
│   └── build_dashboard.py         ← HTML + per-operator feedback
├── prompts/
│   ├── qa_v3.md                   ← Калгари-Кембридж 5 этапов + 2 нити
│   ├── lost_deal_v1.md            ← детектор проёбов
│   └── callback_catcher_v1.md     ← извлечение перезвонов
└── data/
    ├── lead_outcomes.json
    ├── bench_results.json
    └── YYYY-MM-DD/
        ├── calls.csv
        ├── raw/                   ← mp3
        ├── transcripts/           ← .txt + .json
        ├── bundles/               ← per-client md
        ├── qa_v3_results.json
        ├── lost_deal_results.json
        └── callback_results.json

🤖 8. Инструкция для AI-агента «построить такой же пайплайн на Битрикс24»

Если ты агент-разработчик и тебя попросили построить аналогичную систему:

  1. Стартуй с конфига:

  2. Реверс-инжиниринг записей:

  3. Реализуй pipeline в строгом порядке: fetch_callstranscribepull_lead_outcomesbundleanalyzebuild_dashboard. Каждый шаг идемпотентен (skip уже сделанного).

  4. Адаптируй промпты:

  5. Откалибруй вручную:

  6. Выбор production-модели:

  7. Дашборд:

  8. Что НЕ делать:


📈 9. Метрики успеха (что показывать в дашборде)

Метрика Норма Алерт
Pickup rate входящих ≥ 90% < 80%
Conversion LEAD → сделка ≥ 50% < 35%
Won rate (от лидов) ≥ 5% < 2%
Средний QA score (если max=35) ≥ 25 < 20
Доля красных флагов < 15% > 25%
Покрытие ключевых скриптовых тегов в первичках ≥ 70% < 40%
Lost-deals в день < 10% от первичек > 25%
Просроченных callbacks (после прохождения dry-run) 0 ≥ 1

💡 10. Что промпт-инжиниринг показывает на проде

Полезные обобщения, наблюдаемые на реальных прогонах:


📝 Лицензия и заметки

Делитесь свободно, ссылаться не обязательно. Промпты — адаптируйте под свой вертикал (грузоперевозки / медицина / страхование / ритейл / недвижимость — структура Калгари-Кембридж работает везде).

Если строишь похожее — пиши, обмениваемся опытом.