Отправка заказа c автоответчика в CRM (практическое применение транскрибатора)

от автора

Стандартная ситуация.
Клиенты звонят в нерабочее время, попадают на автоответчик и диктуют на автоответчик заказ.

Поставленная задача — в получающихся аудиофайлах вытаскивать смыслы и фиксировать заказ в CRM. Задача решена.

Перевод аудио в текст (транскрибация)

Применили Yandex Speech Kit.

Код Yandex Speech Kit
// $token – берется в своем аккаунте // $audioFileName – ссылка на закачанный в сеть аудиофайл в формате ogg $file = fopen($audioFileName, 'rb');  $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, "https://stt.api.cloud.yandex.net/speech/v1/stt:recognize?lang=ru-RU&format=oggopus"); curl_setopt($ch, CURLOPT_HTTPHEADER, array('Authorization: Api-Key ' . $token, 'Transfer-Encoding: chunked')); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); curl_setopt($ch, CURLOPT_BINARYTRANSFER, true);  curl_setopt($ch, CURLOPT_INFILE, $file); $res = curl_exec($ch); curl_close($ch); $decodedResponse = json_decode($res, true); if (isset($decodedResponse["result"])) { $text = urlencode($decodedResponse["result"]); } else { echo "Error code: " . $decodedResponse["error_code"] . "\r\n"; echo "Error message: " . $decodedResponse["error_message"] . "\r\n"; }  fclose($file);

На этом этапе есть текст в переменной $text.

Вытаскивание смыслов

Создаем массивы значений.
В одном массиве – слова, которые ищем, во втором – соответствующее корректное наименование.

Например, для наименований товара:

$name_dataset = array("мягк\D{2}", "природн\D{2}" ); $name_dataset_correct = array("Сестрица Мягкая", "Природная" );

И перебором ищем выражения в строке $string:

$name = ''; foreach ($name_dataset as $key => $find) {     $pattern = "/$find/ui";       if (preg_match($pattern, $string, $matches_quantity_1)) {         $name = $name_dataset_correct[$key];         }     }   if ($name == '') {$name = "(не определено)";}

С количеством и интервалами доставки аналогично:

Код для количества и интервалов
$quantity = ''; $quantity_dataset = array("бутыл\D+ воды", "бутылоч\D{1,2}", "бутыл\D{1,2}", "шту\D+ воды", "штуче\D{0,2}к", "штук\D{0,1}"); foreach ($quantity_dataset as $find) {     $pattern = "/\d+ $find/ui";       if (preg_match($pattern, $string_new, $matches_quantity_1)) {                 preg_match("/\d+/", $matches_quantity_1[0], $matches_quantity_2);         $quantity = $matches_quantity_2[0];         }      }     if ($quantity == '') {$quantity = "(не определено)";}    $interval = "";  $interval_dataset = array("к утру", "утром", "\D{0,2} утро", "к вечеру", "вечером", "\D{0,2} вечер", "до обеда", "после обеда",   "\D{1,2} 1 половин\D{1} дня", "\D{1,2} 1 половин\D{1}", "1 половин\D{1} \D{0,3}", "\D{1,2} 2 половин\D{1} дня", "\D{1,2} 2 половин\D{1}",   "2 половин\D{1} \D{0,3}", "с 15 часов", "с 15 до 18", "с 18 до 21", "с 18 до 22", "с 18 часов", "с 18", "после 18 часов", "после 18", "18 0 0");        $interval_dataset_correct = array("утро", "утро", "утро", "с 18 до 22-00", "с 18 до 22-00", "с 18 до 22-00", "до обеда", "после обеда",   "первая половина дня", "первая половина дня", "первая половина дня", "вторая половна дня", "вторая половна дня", "вторая половна дня",   "с 15 до 18-00", "с 15 до 18-00", "с 18 до 22-00", "с 18 до 22-00", "с 18 до 22-00", "с 18 до 22-00", "с 18 до 22-00", "с 18 до 22-00",   "с 18 до 22-00");  $interval = ""; foreach ($interval_dataset as $key => $find) {     $pattern = "/$find/ui";     if (preg_match($pattern, $string_new, $matches_quantity_1)) {         $interval = $interval." ".$interval_dataset_correct[$key];         }      }   if ($interval == '') {$interval = "(не определено)";}

На этом этапе уже есть наименование товара ($name), количество ($quantity) и желаемый интервал доставки ($interval).

Также можно вытащить и различные кодовые или стоп-слова:

Код для стоп-слов и кодовых команда
$stop_dataset = array("отмен\D{1,3}", "2 заказа", "двойной заказ");  $stop_dataset_real = array("отмена", "Двойной заказ", "Двойной заказ");  $stop = false; foreach ($stop_dataset as $key => $find) {     $pattern = "/$find/ui";       if (preg_match($pattern, $string, $matches_quantity_1)) {         $stop_world = $stop_dataset_real[$key];                }      else { }     }

Если встречены такие слова, то можно отправить команду на отмену заказа или, например, «тревожное сообщение» в телеграм.

Стандартизация адреса

Для улучшения содержания CRM адрес стандартизируется и в CRM отправляется уже стандартизированный адрес. Применили dadata.

    $token = "ВАШ_API_КЛЮЧ";     $secret = "ВАШ_СЕКРЕТНЫЙ_КЛЮЧ";     $city="ЗАДАЕМ ГОРОД";      $dadata = new Dadata($token, $secret);     $dadata->init();     $result = $dadata->clean("address", $city." ".$string_new);     $address = $result[0]["result"];     $quality_cod = result[0]["qc"];     $dadata->close();

Вот такой простой код из документации стандартизирует адрес.
Отправляется строка $string_new, возвращается строка с адресом.
Адрес сохраняется в переменной $address.
При необходимости можно использовать и код качества стандартизации ($quality_cod).

Код применяемого класса копируется полностью из документации:

Код class Dadata
class TooManyRequests extends Exception { }  class Dadata {     private $clean_url = "https://cleaner.dadata.ru/api/v1/clean";     private $suggest_url = "https://suggestions.dadata.ru/suggestions/api/4_1/rs";     private $token;     private $secret;     private $handle;      public function __construct($token, $secret)     {         $this->token = $token;         $this->secret = $secret;     }      /*      * Initialize connection.      */           public function init()     {         $this->handle = curl_init();         curl_setopt($this->handle, CURLOPT_RETURNTRANSFER, 1);         curl_setopt($this->handle, CURLOPT_HTTPHEADER, array(             "Content-Type: application/json",             "Accept: application/json",             "Authorization: Token " . $this->token,             "X-Secret: " . $this->secret,         ));         curl_setopt($this->handle, CURLOPT_POST, 1);     }      /*      * Clean service.      * See for details:      *   - https://dadata.ru/api/clean/address      *   - https://dadata.ru/api/clean/phone      *   - https://dadata.ru/api/clean/passport      *   - https://dadata.ru/api/clean/name      *       * (!) This is a PAID service. Not included in free or other plans.      */           public function clean($type, $value)     {         $url = $this->clean_url . "/$type";         $fields = array($value);         return $this->executeRequest($url, $fields);     }       /*      * Close connection.      */           public function close()     {         curl_close($this->handle);     }      private function executeRequest($url, $fields)     {         curl_setopt($this->handle, CURLOPT_URL, $url);         if ($fields != null) {             curl_setopt($this->handle, CURLOPT_POST, 1);             curl_setopt($this->handle, CURLOPT_POSTFIELDS, json_encode($fields));         } else {             curl_setopt($this->handle, CURLOPT_POST, 0);         }         $result = $this->exec();         $result = json_decode($result, true);         return $result;     }      private function exec()     {         $result = curl_exec($this->handle);         $info = curl_getinfo($this->handle);         if ($info['http_code'] == 429) {             throw new TooManyRequests();         } elseif ($info['http_code'] != 200) {             throw new Exception('Request failed with http code ' . $info['http_code'] . ': ' .              $result);         }         return $result;     }
public function __construct($token, $secret) {     $this->token = $token;     $this->secret = $secret; }  /*  * Initialize connection.  */   public function init() {     $this->handle = curl_init();     curl_setopt($this->handle, CURLOPT_RETURNTRANSFER, 1);     curl_setopt($this->handle, CURLOPT_HTTPHEADER, array(         "Content-Type: application/json",         "Accept: application/json",         "Authorization: Token " . $this->token,         "X-Secret: " . $this->secret,     ));     curl_setopt($this->handle, CURLOPT_POST, 1); }  /*  * Clean service.  * See for details:  *   - https://dadata.ru/api/clean/address  *   - https://dadata.ru/api/clean/phone  *   - https://dadata.ru/api/clean/passport  *   - https://dadata.ru/api/clean/name  *   * (!) This is a PAID service. Not included in free or other plans.  */   public function clean($type, $value) {     $url = $this->clean_url . "/$type";     $fields = array($value);     return $this->executeRequest($url, $fields); }   /*  * Close connection.  */   public function close() {     curl_close($this->handle); }  private function executeRequest($url, $fields) {     curl_setopt($this->handle, CURLOPT_URL, $url);     if ($fields != null) {         curl_setopt($this->handle, CURLOPT_POST, 1);         curl_setopt($this->handle, CURLOPT_POSTFIELDS, json_encode($fields));     } else {         curl_setopt($this->handle, CURLOPT_POST, 0);     }     $result = $this->exec();     $result = json_decode($result, true);     return $result; }  private function exec() {     $result = curl_exec($this->handle);     $info = curl_getinfo($this->handle);     if ($info['http_code'] == 429) {         throw new TooManyRequests();     } elseif ($info['http_code'] != 200) {         throw new Exception('Request failed with http code ' . $info['http_code'] . ': ' .          $result);     }     return $result; } 

Итак, на этом этапе есть адрес, который удобно фиксировать в CRM.

Улучшаем стандартизацию адреса

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

Например, вырезаем обычные слова приветствия и прощания:

$hallo_resize = array("здравствуй\D{0,2}", "приветству\D{1,2}", "привет", "добрый день", "добро\D{1,2} утр\D{1}", "доброго дня",  "добр\D{2,3} вечер\D{0,1}", "добр\D{2,3} вечер\D{2}", "до свидания", "всего доброго", "всего самого доброго", "всего наилучшего", "всего лучшего",  "всего самого лучшего", "всего хорошего", всего самого хорошего", "спасибо.");  foreach ($hallo_resize as $value) {     $pattern = "/$value/ui";     $string_new = preg_replace($pattern, '', $string_new);     }

Аналогично, прочие различные слова:

Код для вырезания слов
$another_resize = array("\bнужно\b", "мне бы", "\bмне\b", "спасибо", "\bадрес\D{0,1}\b", "\b\Dоставк\D{1,2}\b", "сделать",      "\bзаказ\D{0,2}ать\b", "по заказ\D{0,2}у", "про заказ\D{0,2}", "\bзаказикD{0,1}\b", "\bзаказD{0,1}\b",     "желательно", "прошу", "примите", "пожалуйста", "хочу", "\bя\b", "\bвы\b",  "\bмы\b",  "\bвсе\b", "алло", "буду дома", "время",      "\bвот\b", "\bуже\b", "\bвам\b", "\bзвон\D{1,2}\b", "\bживой\b",      "\bголос\b", "узна\D{1,2}", "\bприня\D{1,2}\b", "\bмой\b", "\bможно\b", "\bуслышать\b", "\bавтоответчик\D{1,2}\b");      foreach ($another_resize as $value) {     $pattern = "/$value/ui";     $string_new = preg_replace($pattern, '', $string_new);     }

Именно из-за такой обработки, как видно на представленных фрагментах кода, текст из аудио хранится в переменной $text, а на стандартизацию отправляется переработанная строка $string_new,

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

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

Код для вырезания слов
   $finresize_dataset = array(     "\d{1,2} числ\D{1,2}",      "с \d{1,2} часов", "с \d{1,2}\b", "до \d{1,2} часов", "до \d{1,2}\b", "к \d{1,2} часам", "к \d{1,2}\b", "после \d{1,2} часов", "после \d{1,2}\b",     "\D{0,2} сегодня", "\D{0,2} завтра",     "\D{1,2} понедельник\D{0,1}", "\D{1,2} вторник\D{0,1}", "\D{1,2} сред\D{1}", "\D{1,2} четверг\D{0,1}", "\D{1,2} пятниц\D{1}", "\D{1,2} суббот\D{1}", "\D{1,2} воскресень\D{1}",      "\D{1,2} воскресени\D{1}",      "понедельник", "вторник", "среда", "четверг", "пятница", "суббота", "воскресенье", "воскресение",     "\d{1,2} января", "\d{1,2} февраля", "\d{1,2} марта", "\d{1,2} апреля", "\d{1,2} мая", "\d{1,2} июня", "\d{1,2} июля", "\d{1,2} августа", "\d{1,2} сентября",      "\d{1,2} октября", "\d{1,2} ноября", "\d{1,2} декабря",     "\bна\b", "\bв\b", "\bс\b", "\bк\b", "\bиз-за\b", "\bиз\b", "\bза\b",  "\bот\b", "\bперед\b", "\bи\b",  "\bили\b",  "\bда\b",  "\bнет\b", "\bкак\b", "\bтак\b", "\bвот\b",      "\bтолько\b", "\bнисколько\b", "\bсколько\b", "\bстолько\b", "\bто\b", "\bводу\b"     );         foreach ($finresize_dataset as $value) {     $string_new = preg_replace("/$value/ui", '', $string_new);     }

Итоги

Было. Операторы приходили на работу, и в перерывах между входящими звонками слушали аудиозаписи с автоответчика и вручную набивали заказы в CRM.

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

Итог. Снижение расходов на обработку заказов, полученных в нерабочее время.


ссылка на оригинал статьи https://habr.com/ru/post/684914/