·19 мин·1 просмотров

Как настроить аналитику записей разговоров в Asterisk: полное руководство

Превращаем сырые записи разговоров в рабочие выводы: архитектура хранения, связь с CDR, SQL-аналитика, ИИ-речевая аналитика на Whisper и лучшие практики комплаенса.

A
Astervis
Команда инженеров и продактов

Записывать звонки в Asterisk просто. А вот получать из этих записей реальную пользу — задача, на которой спотыкается большинство команд.

Если вы уже настроили MixMonitor или Monitor в диалплане, у вас под рукой золотая жила данных. Проблема в том, что в большинстве инсталляций Asterisk записи разговоров воспринимают как галочку в чек-листе комплаенса: файлы лежат в /var/spool/asterisk/monitor/, и к ним никто не притрагивается, пока не возникнет спорная ситуация.

В этом руководстве разбираем, как превратить сырые записи разговоров в структурированную аналитическую систему. Вы узнаете, как организовать хранение для быстрого поиска, связать записи с метаданными CDR, построить дашборды, которые сами показывают проблемы с качеством, и даже добавить речевую аналитику на базе ИИ с помощью open-source инструментов.

Зачем нужна аналитика записей разговоров

Запись разговоров без аналитики — это как установить камеры видеонаблюдения и никогда не смотреть записи.

Что даёт аналитика записей:

СценарийБез аналитикиС аналитикой
Контроль качестваВыборочные проверки (охват 2–5%)Системная оценка 100% звонков
Обучение операторовСубъективная обратная связь по памятиОбучение на данных с конкретными примерами
Проверка комплаенсаРучной разбор, когда пришла жалобаАвтоматическая пометка нарушений
Понимание клиентовРазрозненные наблюденияАнализ трендов по тысячам обращений
Разбор спорных ситуацийРучной поиск по файловой системеМгновенный доступ по метаданным звонка

Компании, использующие аналитику записей разговоров, сообщают об улучшении показателя решения вопроса с первого звонка на 23% и сокращении среднего времени обработки на 18% уже в первом квартале после внедрения (ICMI Contact Center Research, 2024).

Шаг 1. Фундамент: настройка записи

Прежде чем строить аналитику, нужна надёжная основа для записи. В Asterisk есть два основных приложения:

MixMonitor против Monitor

ХарактеристикаMixMonitorMonitor
Сведение аудиоПишет обе стороны в один файлОтдельные файлы по направлениям (или сведённый)
Влияние на производительностьЛёгкий (рекомендуется)Тяжелее, возможны проблемы со звуком
Управление во время разговораДинамический старт/стоп/паузаОграниченное управление
Форматы записиWAV, WAV49, GSM, SLN, SLINWAV, WAV49, GSM
Требования к каналуРаботает на отвеченных каналахРаботает на отвеченных каналах
Рекомендуется дляПромышленных колл-центровLegacy-инсталляций

Используйте MixMonitor. Это современный вариант, готовый к продакшену.

Базовый диалплан с MixMonitor

; extensions.conf — Recording with structured filenames [macro-record-call] exten => s,1,NoOp(Starting call recording) same => n,Set(RECORD_DIR=/var/spool/asterisk/monitor/${STRFTIME(${EPOCH},,%Y/%m/%d)}) same => n,System(mkdir -p ${RECORD_DIR}) same => n,Set(RECORD_FILE=${RECORD_DIR}/${STRFTIME(${EPOCH},,%Y%m%d-%H%M%S)}-${UNIQUEID}-${CALLERID(num)}-${EXTEN}) same => n,Set(CDR(recordingfile)=${RECORD_FILE}.wav) same => n,MixMonitor(${RECORD_FILE}.wav,b) same => n,MacroExit()

Ключевые моменты этого диалплана:

  • Каталоги по датам (YYYY/MM/DD) не дают файловой системе тормозить из-за тысяч файлов в одной папке
  • Структурированные имена файлов содержат метку времени, уникальный ID, номер звонящего и внутренний номер — это критично для аналитики
  • Связь с CDR через CDR(recordingfile) привязывает запись к детализации звонков

Настройка записи в FreePBX

Если вы работаете в FreePBX, запись настраивается отдельно для внутреннего номера, группы обзвона или очереди:

  1. Перейдите в Admin → Extensions → [Extension] → Recording
  2. Задайте политику записи: Force, Don't Care, Yes или No
  3. Выберите Inbound External, Outbound External, Inbound Internal, Outbound Internal

FreePBX хранит записи в /var/spool/asterisk/monitor/ в формате:

{year}/{month}/{day}/{type}-{date}-{time}-{source}-{destination}-{uniqueid}.wav

Шаг 2. Архитектура хранения для аналитики

Промышленной системе записи в колл-центре нужен продуманный план хранения. При битрейте 64 кбит/с (G.711 WAV) один час записанных разговоров занимает примерно 28,8 МБ. Колл-центр на 50 операторов со средним разговорным временем 6 часов на оператора в день генерирует 8,6 ГБ в сутки, или 260 ГБ в месяц.

Калькулятор объёма хранилища

ОператоровСр. часов разговора/деньХранилище в суткиВ месяцВ год
1051,4 ГБ43 ГБ516 ГБ
2553,6 ГБ108 ГБ1,3 ТБ
5068,6 ГБ260 ГБ3,1 ТБ
100617,3 ГБ518 ГБ6,2 ТБ
200634,6 ГБ1 ТБ12,4 ТБ

Рекомендуемая структура хранения

/var/spool/asterisk/ └── monitor/ ├── 2026/ │ ├── 01/ │ │ ├── 15/ │ │ │ ├── 20260115-093042-1705312242.1-2125551234-100.wav │ │ │ └── ... │ │ └── 16/ │ └── 02/ ├── compressed/ # Archived recordings (MP3/Opus) │ ├── 2025/ │ └── ... └── transcripts/ # AI-generated transcripts ├── 2026/ └── ...

Стратегия сжатия

Для архива WAV-файлы неоправданно велики. Настройте автоматическое сжатие:

#!/bin/bash # compress-recordings.sh — Run daily via cron # Compresses WAV recordings older than 7 days to Opus format # Opus achieves 10:1 compression vs WAV with excellent quality MONITOR_DIR="/var/spool/asterisk/monitor" COMPRESS_DIR="$MONITOR_DIR/compressed" DAYS_OLD=7 find "$MONITOR_DIR" -name "*.wav" -mtime +$DAYS_OLD \ -not -path "*/compressed/*" | while read wavfile; do # Preserve directory structure relative_path="${wavfile#$MONITOR_DIR/}" opus_path="$COMPRESS_DIR/${relative_path%.wav}.opus" mkdir -p "$(dirname "$opus_path")" # Convert to Opus (high quality, ~1/10 the size) ffmpeg -i "$wavfile" -c:a libopus -b:a 24k \ -application voip "$opus_path" 2>/dev/null if [ $? -eq 0 ] && [ -f "$opus_path" ]; then rm "$wavfile" echo "Compressed: $relative_path" fi done

Добавьте в crontab:

# Run compression daily at 2 AM 0 2 * * * /usr/local/bin/compress-recordings.sh >> /var/log/recording-compression.log 2>&1

Шаг 3. Связываем записи с метаданными CDR

Сырые записи становятся по-настоящему полезными, когда связаны с данными CDR (детализацией звонков). Эта связь позволяет делать запросы вроде «покажи все записи, где время ожидания превысило 60 секунд» или «найди звонки с этого номера клиента за последние 30 дней».

Схема БД для аналитики записей

-- Create a recordings metadata table that extends CDR CREATE TABLE recording_metadata ( id SERIAL PRIMARY KEY, uniqueid VARCHAR(64) NOT NULL, linkedid VARCHAR(64), recording_path TEXT NOT NULL, recording_format VARCHAR(10) DEFAULT 'wav', file_size_bytes BIGINT, duration_seconds INTEGER, silence_percentage DECIMAL(5,2), caller_id VARCHAR(80), destination VARCHAR(80), queue_name VARCHAR(64), agent VARCHAR(64), direction VARCHAR(10), -- 'inbound', 'outbound', 'internal' disposition VARCHAR(20), recorded_at TIMESTAMP NOT NULL, compressed BOOLEAN DEFAULT FALSE, transcribed BOOLEAN DEFAULT FALSE, transcript_path TEXT, quality_score DECIMAL(3,1), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- Indexes for common analytics queries CONSTRAINT unique_recording UNIQUE (uniqueid, recording_path) ); CREATE INDEX idx_recorded_at ON recording_metadata(recorded_at); CREATE INDEX idx_agent ON recording_metadata(agent); CREATE INDEX idx_queue ON recording_metadata(queue_name); CREATE INDEX idx_caller ON recording_metadata(caller_id); CREATE INDEX idx_disposition ON recording_metadata(disposition); CREATE INDEX idx_direction ON recording_metadata(direction);

Скрипт автоматического извлечения метаданных

#!/usr/bin/env python3 """ recording_indexer.py — Scans recordings, extracts metadata, populates database. Run every 15 minutes via cron. """ import os import re import wave import subprocess import psycopg2 from datetime import datetime from pathlib import Path DB_CONFIG = { 'host': 'localhost', 'database': 'asterisk', 'user': 'asterisk', 'password': 'your_password' } MONITOR_DIR = '/var/spool/asterisk/monitor' FILENAME_PATTERN = re.compile( r'(\d{8}-\d{6})-(\d+\.\d+)-(\d+)-(\d+)\.wav$' ) def get_wav_duration(filepath): """Get duration in seconds from WAV file.""" try: with wave.open(filepath, 'r') as wf: frames = wf.getnframes() rate = wf.getframerate() return frames / float(rate) except Exception: return None def detect_silence_percentage(filepath): """Detect percentage of silence in recording using sox.""" try: result = subprocess.run( ['sox', filepath, '-n', 'stats'], capture_output=True, text=True, timeout=30 ) # Parse sox stats for RMS level for line in result.stderr.split('\n'): if 'RMS lev dB' in line: rms = float(line.split()[-1]) # Very rough: if RMS < -40dB, significant silence if rms < -40: return 80.0 elif rms < -30: return 50.0 elif rms < -20: return 20.0 return 5.0 except Exception: return None def index_recordings(): conn = psycopg2.connect(**DB_CONFIG) cur = conn.cursor() indexed = 0 for root, dirs, files in os.walk(MONITOR_DIR): for filename in files: if not filename.endswith('.wav'): continue filepath = os.path.join(root, filename) # Skip already indexed cur.execute( "SELECT 1 FROM recording_metadata WHERE recording_path = %s", (filepath,) ) if cur.fetchone(): continue # Extract metadata from filename match = FILENAME_PATTERN.search(filename) if match: timestamp_str, uniqueid, caller, dest = match.groups() recorded_at = datetime.strptime(timestamp_str, '%Y%m%d-%H%M%S') else: # Fallback: use file modification time uniqueid = filename.replace('.wav', '') recorded_at = datetime.fromtimestamp(os.path.getmtime(filepath)) caller = dest = None duration = get_wav_duration(filepath) file_size = os.path.getsize(filepath) silence_pct = detect_silence_percentage(filepath) cur.execute(""" INSERT INTO recording_metadata (uniqueid, recording_path, file_size_bytes, duration_seconds, silence_percentage, caller_id, destination, recorded_at) VALUES (%s, %s, %s, %s, %s, %s, %s, %s) ON CONFLICT (uniqueid, recording_path) DO NOTHING """, (uniqueid, filepath, file_size, duration, silence_pct, caller, dest, recorded_at)) indexed += 1 conn.commit() cur.close() conn.close() print(f"Indexed {indexed} new recordings") if __name__ == '__main__': index_recordings()

Связь с данными CDR

Ключевое поле для join между записями и CDR — uniqueid:

-- Join recordings with CDR for complete call context SELECT r.recording_path, r.duration_seconds, r.silence_percentage, c.src AS caller, c.dst AS destination, c.dcontext AS context, c.billsec AS billable_seconds, c.disposition, c.accountcode, c.userfield AS queue_name FROM recording_metadata r JOIN cdr c ON r.uniqueid = c.uniqueid WHERE r.recorded_at >= CURRENT_DATE - INTERVAL '7 days' ORDER BY r.recorded_at DESC;

Шаг 4. Базовые SQL-запросы для аналитики записей

Когда записи связаны с данными CDR, можно строить полноценные аналитические дашборды.

Запрос 1. Суточный объём записей и хранилища

-- Daily recording stats: count, total duration, storage used SELECT DATE(recorded_at) AS day, COUNT(*) AS total_recordings, ROUND(SUM(duration_seconds) / 3600.0, 1) AS total_hours, ROUND(SUM(file_size_bytes) / 1073741824.0, 2) AS storage_gb, ROUND(AVG(duration_seconds), 0) AS avg_duration_sec, ROUND(AVG(silence_percentage), 1) AS avg_silence_pct FROM recording_metadata WHERE recorded_at >= CURRENT_DATE - INTERVAL '30 days' GROUP BY DATE(recorded_at) ORDER BY day DESC;

Запрос 2. Анализ записей по операторам

-- Per-agent recording metrics: identify coaching opportunities SELECT c.dstchannel AS agent_channel, SUBSTRING(c.dstchannel FROM 'SIP/(.+)-') AS agent, COUNT(*) AS calls_recorded, ROUND(AVG(r.duration_seconds), 0) AS avg_duration, ROUND(AVG(r.silence_percentage), 1) AS avg_silence_pct, SUM(CASE WHEN r.duration_seconds < 30 THEN 1 ELSE 0 END) AS short_calls, SUM(CASE WHEN r.silence_percentage > 50 THEN 1 ELSE 0 END) AS high_silence_calls, ROUND(AVG(c.billsec), 0) AS avg_billsec FROM recording_metadata r JOIN cdr c ON r.uniqueid = c.uniqueid WHERE r.recorded_at >= CURRENT_DATE - INTERVAL '7 days' AND c.disposition = 'ANSWERED' GROUP BY c.dstchannel, SUBSTRING(c.dstchannel FROM 'SIP/(.+)-') ORDER BY calls_recorded DESC;

Запрос 3. Анализ покрытия записью

-- What percentage of calls are being recorded? -- Identifies gaps in recording coverage SELECT DATE(calldate) AS day, COUNT(*) AS total_calls, COUNT(r.id) AS recorded_calls, ROUND(COUNT(r.id)::DECIMAL / COUNT(*) * 100, 1) AS coverage_pct, COUNT(*) - COUNT(r.id) AS missing_recordings FROM cdr c LEFT JOIN recording_metadata r ON c.uniqueid = r.uniqueid WHERE c.calldate >= CURRENT_DATE - INTERVAL '7 days' AND c.disposition = 'ANSWERED' AND c.billsec > 5 GROUP BY DATE(calldate) ORDER BY day DESC;

Запрос 4. Детекция тишины — ищем проблемные звонки

-- Calls with excessive silence (potential quality issues) -- High silence = hold without music, dead air, or connection problems SELECT r.recorded_at, r.caller_id, r.destination, r.duration_seconds, r.silence_percentage, r.recording_path, c.disposition, c.userfield AS queue FROM recording_metadata r JOIN cdr c ON r.uniqueid = c.uniqueid WHERE r.silence_percentage > 40 AND r.duration_seconds > 60 AND r.recorded_at >= CURRENT_DATE - INTERVAL '7 days' ORDER BY r.silence_percentage DESC LIMIT 20;

Устали угадывать, что происходит в очередях?

Astervis даёт 30+ реалтайм-графиков, KPI операторов и CRM-интеграцию для вашего Asterisk PBX. On-premise. Установка за 5 минут. От $119/мес flat без ограничения на операторов.

Попробовать бесплатно

Запрос 5. Почасовая тепловая карта записей

-- Recording volume by hour and day of week -- Identifies peak periods and potential capacity issues SELECT EXTRACT(DOW FROM recorded_at) AS day_of_week, EXTRACT(HOUR FROM recorded_at) AS hour, COUNT(*) AS recordings, ROUND(AVG(duration_seconds), 0) AS avg_duration, ROUND(SUM(file_size_bytes) / 1048576.0, 0) AS storage_mb FROM recording_metadata WHERE recorded_at >= CURRENT_DATE - INTERVAL '30 days' GROUP BY EXTRACT(DOW FROM recorded_at), EXTRACT(HOUR FROM recorded_at) ORDER BY day_of_week, hour;

Шаг 5. Речевая аналитика на базе ИИ

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

Обзор архитектуры

┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ Asterisk │────>│ Storage │────>│ Transcribe │────>│ Analyze │ │ MixMonitor │ │ (WAV/Opus) │ │ (Whisper) │ │ (NLP/LLM) │ └──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘ │ ┌──────┴──────┐ │ Dashboard │ │ (Metrics) │ └─────────────┘

Вариант 1. Whisper на своём сервере

Модель Whisper от OpenAI бесплатна, имеет открытый исходный код и работает на вашем железе:

#!/usr/bin/env python3 """ transcribe_recordings.py — Batch transcribe recordings using Whisper. Requires: pip install openai-whisper torch """ import whisper import json import os import psycopg2 from pathlib import Path # Load Whisper model (options: tiny, base, small, medium, large-v3) # 'small' is the best balance of speed and accuracy for telephony model = whisper.load_model("small") DB_CONFIG = { 'host': 'localhost', 'database': 'asterisk', 'user': 'asterisk', 'password': 'your_password' } TRANSCRIPT_DIR = '/var/spool/asterisk/monitor/transcripts' def transcribe_pending(): conn = psycopg2.connect(**DB_CONFIG) cur = conn.cursor() # Get un-transcribed recordings cur.execute(""" SELECT id, recording_path, recorded_at FROM recording_metadata WHERE transcribed = FALSE AND duration_seconds > 10 AND duration_seconds < 1800 ORDER BY recorded_at DESC LIMIT 50 """) for rec_id, rec_path, recorded_at in cur.fetchall(): if not os.path.exists(rec_path): continue try: # Transcribe with Whisper result = model.transcribe( rec_path, language=None, # Auto-detect language task="transcribe", fp16=False # Use fp32 for CPU ) # Save transcript transcript_path = os.path.join( TRANSCRIPT_DIR, recorded_at.strftime('%Y/%m/%d'), f"{os.path.basename(rec_path).replace('.wav', '.json')}" ) os.makedirs(os.path.dirname(transcript_path), exist_ok=True) transcript_data = { 'text': result['text'], 'language': result.get('language', 'unknown'), 'segments': [ { 'start': seg['start'], 'end': seg['end'], 'text': seg['text'] } for seg in result.get('segments', []) ] } with open(transcript_path, 'w') as f: json.dump(transcript_data, f, indent=2) # Update database cur.execute(""" UPDATE recording_metadata SET transcribed = TRUE, transcript_path = %s WHERE id = %s """, (transcript_path, rec_id)) conn.commit() print(f"Transcribed: {os.path.basename(rec_path)} " f"({result.get('language', '?')})") except Exception as e: print(f"Error transcribing {rec_path}: {e}") continue cur.close() conn.close() if __name__ == '__main__': transcribe_pending()

Требования к железу для Whisper

МодельVRAMCPU: время/мин аудиоGPU: время/мин аудиоТочность
tiny~1 ГБ~10 сек~1 секГодится для поиска ключевых слов
base~1 ГБ~15 сек~2 секПриемлемо для аналитики
small~2 ГБ~30 сек~3 секРекомендуется для телефонии
medium~5 ГБ~60 сек~5 секВысокая точность
large-v3~10 ГБ~120 сек~8 секМаксимальная точность

Для колл-центра на 50 операторов, который обрабатывает 300 записей в день средней длительностью 5 минут, модель small на GPU справится со всеми записями за день примерно за 75 минут.

Вариант 2. Облачный API транскрибации

Для команд, которым удобнее управляемый сервис:

# Using OpenAI Whisper API (cloud) import openai client = openai.OpenAI(api_key="your-api-key") def transcribe_cloud(audio_path): with open(audio_path, "rb") as audio_file: transcript = client.audio.transcriptions.create( model="whisper-1", file=audio_file, response_format="verbose_json", timestamp_granularities=["segment"] ) return transcript

Облачный API стоит примерно $0.006 за минуту аудио. При 300 звонках × 5 минут в день это $9 в день, то есть ~$270 в месяц.

Поиск ключевых слов и определение тем

Когда расшифровки готовы, ключевые слова и темы можно находить даже без ИИ:

#!/usr/bin/env python3 """ keyword_analyzer.py — Detect keywords and topics in call transcripts. """ import json import re from collections import Counter # Define keyword categories relevant to your business KEYWORD_CATEGORIES = { 'complaint': [ 'complaint', 'unhappy', 'dissatisfied', 'terrible', 'worst', 'cancel', 'refund', 'manager', 'supervisor', 'unacceptable' ], 'upsell_opportunity': [ 'upgrade', 'additional', 'more features', 'enterprise', 'premium', 'advanced', 'expand', 'grow', 'scale' ], 'technical_issue': [ 'not working', 'broken', 'error', 'bug', 'crash', 'down', 'outage', 'slow', 'timeout', 'failed' ], 'positive': [ 'thank you', 'excellent', 'great', 'wonderful', 'perfect', 'appreciate', 'helpful', 'resolved', 'solved', 'happy' ], 'compliance_risk': [ 'guarantee', 'promise', 'definitely', 'always', 'never', 'lawsuit', 'legal', 'attorney', 'sue' ] } def analyze_transcript(transcript_path): with open(transcript_path) as f: data = json.load(f) text = data['text'].lower() results = {} for category, keywords in KEYWORD_CATEGORIES.items(): found = [] for keyword in keywords: count = len(re.findall(r'\b' + re.escape(keyword) + r'\b', text)) if count > 0: found.append({'keyword': keyword, 'count': count}) results[category] = { 'matches': found, 'total_hits': sum(m['count'] for m in found), 'flagged': len(found) > 0 } return results

Динамика тональности по сегментам

# Simple sentiment scoring per transcript segment # For production: use a fine-tuned model or LLM API from textblob import TextBlob def segment_sentiment(transcript_path): """Analyze sentiment across call segments to detect escalation patterns.""" with open(transcript_path) as f: data = json.load(f) timeline = [] for segment in data.get('segments', []): blob = TextBlob(segment['text']) timeline.append({ 'start': segment['start'], 'end': segment['end'], 'text': segment['text'], 'polarity': round(blob.sentiment.polarity, 2), # -1 to 1 'subjectivity': round(blob.sentiment.subjectivity, 2) # 0 to 1 }) # Detect escalation: sentiment dropping over time if len(timeline) > 4: first_quarter = sum(s['polarity'] for s in timeline[:len(timeline)//4]) last_quarter = sum(s['polarity'] for s in timeline[-len(timeline)//4:]) escalation = first_quarter - last_quarter > 0.5 else: escalation = False return { 'segments': timeline, 'avg_polarity': round(sum(s['polarity'] for s in timeline) / max(len(timeline), 1), 2), 'escalation_detected': escalation }

Шаг 6. Комплаенс и сроки хранения

Требования к записи разговоров сильно различаются в разных юрисдикциях. Нарушение этих требований грозит штрафами и юридической ответственностью.

Модели согласия на запись

МодельТребованиеЮрисдикции
Согласие одной стороныО записи знает один участник разговораСША (федеральный уровень), Великобритания, большая часть ЕС
Согласие обеих/всех сторонО записи знают все участники разговораКалифорния, Флорида, Германия, ряд стран ЕС
Согласие не требуетсяДеловые звонки выведены из-под требованияОтдельные B2B-сценарии

Как реализовать уведомление о записи

; extensions.conf — Play recording notification before connecting [inbound-queue] exten => s,1,Answer() same => n,Playback(this-call-may-be-recorded) same => n,Wait(0.5) same => n,Macro(record-call) same => n,Queue(support,t,,,180) same => n,Hangup()

Автоматическая политика хранения

#!/bin/bash # recording-retention.sh — Enforce data retention policies # Run weekly via cron MONITOR_DIR="/var/spool/asterisk/monitor" TRANSCRIPT_DIR="$MONITOR_DIR/transcripts" # Retention periods (adjust per your compliance requirements) RECORDING_RETENTION_DAYS=365 # Keep recordings for 1 year TRANSCRIPT_RETENTION_DAYS=730 # Keep transcripts for 2 years COMPRESSED_RETENTION_DAYS=730 # Keep compressed archives for 2 years echo "$(date): Starting retention cleanup" # Delete original WAV files older than retention period find "$MONITOR_DIR" -name "*.wav" -mtime +$RECORDING_RETENTION_DAYS \ -not -path "*/compressed/*" -delete -print | wc -l | \ xargs -I {} echo "Deleted {} expired WAV files" # Delete compressed files older than retention period find "$MONITOR_DIR/compressed" -name "*.opus" \ -mtime +$COMPRESSED_RETENTION_DAYS -delete -print | wc -l | \ xargs -I {} echo "Deleted {} expired compressed files" # Delete transcripts older than retention period find "$TRANSCRIPT_DIR" -name "*.json" \ -mtime +$TRANSCRIPT_RETENTION_DAYS -delete -print | wc -l | \ xargs -I {} echo "Deleted {} expired transcripts" # Clean empty directories find "$MONITOR_DIR" -type d -empty -delete echo "$(date): Retention cleanup complete"

GDPR и запросы субъектов данных

Для соответствия GDPR вам нужна возможность найти и удалить все записи по конкретному звонящему:

-- Find all recordings for a specific caller (data subject request) SELECT recording_path, transcript_path, recorded_at, duration_seconds, destination FROM recording_metadata WHERE caller_id = '2125551234' ORDER BY recorded_at DESC; -- Delete all data for a specific caller (right to erasure) -- Step 1: Get file paths for physical deletion SELECT recording_path, transcript_path FROM recording_metadata WHERE caller_id = '2125551234'; -- Step 2: Delete database records DELETE FROM recording_metadata WHERE caller_id = '2125551234';

Шаг 7. Собираем дашборд

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

Каркас дашборда: три уровня

Уровень 1 — воллборд в реальном времени (взгляд на секунду):

  • Записи, которые идут прямо сейчас
  • Количество записей за сегодня против плана
  • Процент заполнения хранилища
  • Оповещения о сбоях записи

Уровень 2 — ежедневная операционка (для супервизоров):

  • Процент покрытия записью (все ли звонки записываются?)
  • Динамика средней длительности разговора
  • Распределение доли тишины
  • Пометки звонков с высокой долей тишины для разбора
  • Звонки, отмеченные детектором ключевых слов

Уровень 3 — стратегическая аналитика (неделя/месяц):

  • Тренды объёма записей
  • Прогноз роста хранилища
  • Инсайты из расшифровок: топ тем, динамика тональности
  • Точки для обучения операторов (много тишины, короткие звонки)
  • Аудит комплаенса: пробелы в покрытии, проверка уведомлений о записи

Ключевые метрики

МетрикаФормулаЦелевое значениеПочему важно
Покрытие записьюЗаписанные звонки / Все отвеченные звонки × 100>98%Если звонки не пишутся — это риск по комплаенсу
Средняя длительность записиВсего секунд записи / Число записейЗависит от бизнесаИзменение тренда сигнализирует о сбоях в процессах
Доля тишиныСекунды тишины / Общая длительность × 100<20%Много тишины = время ожидания, «мёртвый эфир»
Скорость роста хранилищаГБ в суткиПланируйте с запасом ×2Защищает от аварийного заполнения диска
Очередь на транскрибациюНерасшифрованные / Все записи<5%Гарантирует актуальность аналитики
Доля негативной тональностиНегативные звонки / Все расшифрованные × 100<15%Индикатор удовлетворённости клиентов
Доля комплаенс-флаговПомеченные звонки / Все расшифрованные × 100<2%Управление рисками

Сравнение: подходы к аналитике записей

ВозможностьСвои скриптыQueueMetricsCallCabinetAstervis
Трудозатраты на внедрениеВысокие (недели)Средние (дни)Низкие (часы)Низкие (минуты)
Прослушивание записейРучной доступ к файламВстроенный плеерОблачный плеерВстроенный плеер
Связь с CDRСвой SQLАвтоматическиАвтоматическиАвтоматически
Поиск по номеру/датеSQL-запросыПоиск в GUIПоиск в GUIМгновенный поиск
Речевая аналитикаПишите самиНетНа базе ИИ ($$$)Скоро
Детекция тишиныСвои скриптыНетЕстьЕсть
Инструменты комплаенсаВручнуюБазовыеПолный наборВстроенные политики хранения
Оценка операторовНетРучные QA-формыИИ-оценкаАвтоматические KPI
СтоимостьБесплатно + ваше времяCHF 8/агент/месЦена по запросуОт $119/мес
Self-hostedДаДаТолько облакоДа
Дашборд в реальном времениПишите самиБазовыйДа30+ графиков

Чек-лист быстрого старта

Эти шаги проведут вас от нуля до работающей аналитики записей разговоров:

  • Шаг 1: включите запись — добавьте MixMonitor в диалплан со структурированными именами файлов
  • Шаг 2: организуйте хранение — заведите каталоги по датам, спланируйте объём
  • Шаг 3: свяжите с CDR — убедитесь, что CDR(recordingfile) заполняется, создайте таблицу метаданных
  • Шаг 4: индексируйте записи — запускайте скрипт-индексатор по cron каждые 15 минут
  • Шаг 5: сжимайте старые файлы — настройте ежедневную cron-задачу на сжатие
  • Шаг 6: соберите базовый дашборд — начните с запросов по покрытию и длительности
  • Шаг 7: добавьте транскрибацию — установите Whisper, запустите пакетную расшифровку
  • Шаг 8: настройте детекцию ключевых слов — задайте категории, сканируйте расшифровки автоматически
  • Шаг 9: задайте политику хранения — настройте автоочистку под требования вашего комплаенса
  • Шаг 10: пересматривайте и улучшайте — еженедельно смотрите метрики дашборда и подкручивайте пороги

Что дальше

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

Astervis берёт эту рутину на себя. Установка одной командой — и вы сразу получаете 30+ аналитических графиков, прослушивание записей со связью с CDR, отслеживание эффективности операторов и мониторинг очередей в реальном времени. Без единого SQL-запроса.

Начните с 14-дневного бесплатного триала: astervis.io


Нужны ещё руководства по аналитике Asterisk? Читайте наши статьи о мониторинге очередей Asterisk в реальном времени, лучших практиках CDR-отчётности и отслеживании эффективности операторов.

Хватит гадать. Начни видеть.

Реальная картина вашего Asterisk колл-центра: время ожидания, активность операторов, нагрузка на транки и 30+ графиков. On-premise на вашем сервере. Установка за 5 минут. Без карты.

От $119/мес flat. Без ограничения на операторов. Триал 14 дней.

Поделиться