{"id":472203,"date":"2025-08-26T15:01:05","date_gmt":"2025-08-26T15:01:05","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=472203"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=472203","title":{"rendered":"<span>\u0421\u043e\u0431\u0438\u0440\u0430\u0435\u043c \u0441\u0432\u043e\u044e \u0441\u0438\u0441\u0442\u0435\u043c\u0443 \u043e\u0446\u0435\u043d\u043a\u0438 \u043e\u0431\u0449\u0435\u043d\u0438\u044f \u043e\u043f\u0435\u0440\u0430\u0442\u043e\u0440\u043e\u0432 \u041a\u0426 \u0438 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u043e\u0442\u0447\u0451\u0442\u044b \u0432 Telegram<\/span>"},"content":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>\u041f\u0440\u0438\u0432\u0435\u0442, \u0425\u0430\u0431\u0440! \u0421\u0435\u0433\u043e\u0434\u043d\u044f \u043f\u043e\u043a\u0430\u0436\u0435\u043c, \u043a\u0430\u043a \u0431\u0443\u043a\u0432\u0430\u043b\u044c\u043d\u043e \u0437\u0430 \u043f\u0430\u0440\u0443 \u0432\u0435\u0447\u0435\u0440\u043e\u0432 \u0441\u043e\u0431\u0440\u0430\u0442\u044c \u0441\u0438\u0441\u0442\u0435\u043c\u0443, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0440\u0430\u0441\u0448\u0438\u0444\u0440\u043e\u0432\u044b\u0432\u0430\u0435\u0442 \u0437\u0432\u043e\u043d\u043a\u0438, \u0430\u043d\u0430\u043b\u0438\u0437\u0438\u0440\u0443\u0435\u0442 \u0440\u0435\u0447\u044c \u043e\u043f\u0435\u0440\u0430\u0442\u043e\u0440\u043e\u0432 \u0438 \u043f\u0440\u0438\u0441\u044b\u043b\u0430\u0435\u0442 \u0440\u0443\u043a\u043e\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044e \u043e\u0442\u0447\u0451\u0442 \u0432 Telegram.<\/p>\n<p>\u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0432 \u043a\u043e\u043b-\u0446\u0435\u043d\u0442\u0440\u0435 \u0441 15 \u043e\u043f\u0435\u0440\u0430\u0442\u043e\u0440\u0430\u043c\u0438 \u0442\u0430\u043a\u0430\u044f \u0441\u0432\u043e\u0434\u043a\u0430 \u043f\u043e\u043c\u043e\u0436\u0435\u0442 \u0440\u0443\u043a\u043e\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044e \u0431\u044b\u0441\u0442\u0440\u043e \u043f\u043e\u043d\u044f\u0442\u044c, \u043a\u0442\u043e \u043f\u0435\u0440\u0435\u0433\u0440\u0443\u0436\u0435\u043d, \u0433\u0434\u0435 \u0447\u0430\u0449\u0435 \u0437\u0432\u0443\u0447\u0438\u0442 \u043d\u0435\u0433\u0430\u0442\u0438\u0432, \u0430 \u043a\u0442\u043e \u043f\u0440\u043e\u0441\u0442\u043e \u0441\u043b\u0438\u0448\u043a\u043e\u043c \u043c\u043d\u043e\u0433\u043e \u0433\u043e\u0432\u043e\u0440\u0438\u0442. \u041d\u0435 \u043d\u0430\u0434\u043e \u0441\u043b\u0443\u0448\u0430\u0442\u044c \u0437\u0430\u043f\u0438\u0441\u0438 \u2014 \u043e\u0442\u0447\u0451\u0442 \u0441\u0430\u043c \u0432\u0441\u0451 \u0440\u0430\u0441\u0441\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442.<\/p>\n<p>\ud83d\udcca \u041e\u0442\u0447\u0451\u0442 \u0437\u0430 19 \u0438\u044e\u043b\u044f<br \/>\ud83c\udfa7 \u041e\u043f\u0435\u0440\u0430\u0442\u043e\u0440 \u0434\u043d\u044f: \u0418\u0432\u0430\u043d \u0418\u0432\u0430\u043d\u043e\u0432 (emotionScore: 0.42)<br \/>\ud83e\udd75 \u0411\u043e\u043b\u044c\u0448\u0435 \u0432\u0441\u0435\u0433\u043e \u043d\u0435\u0433\u0430\u0442\u0438\u0432\u0430: \u042e\u043b\u0438\u044f \u0422\u0435\u0441\u0442\u043e\u0432\u0430 (33%)<br \/>\ud83d\udde3\ufe0f \u0421\u0440\u0435\u0434\u043d\u044f\u044f \u0441\u043a\u043e\u0440\u043e\u0441\u0442\u044c \u0440\u0435\u0447\u0438: 132 \u0441\u043b\u043e\u0432\/\u043c\u0438\u043d<br \/>\ud83e\udd2f \u0421\u0430\u043c\u044b\u0439 \u00ab\u0433\u043e\u0432\u043e\u0440\u044f\u0449\u0438\u0439\u00bb: \u0410\u043d\u0434\u0440\u0435\u0439 \u041c\u0430\u043a\u0441\u0438\u043c\u043e\u0432 (74% \u0432\u0440\u0435\u043c\u0435\u043d\u0438)<br \/>\ud83d\udea8 \u041f\u0435\u0440\u0435\u0431\u0438\u0432\u0430\u043d\u0438\u0439 \u0432 \u0441\u0440\u0435\u0434\u043d\u0435\u043c: 2,7 \u043d\u0430 \u0437\u0432\u043e\u043d\u043e\u043a<\/p>\n<h3>\u041a\u0430\u043a \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0441\u0438\u0441\u0442\u0435\u043c\u0430<\/h3>\n<p>\u0420\u0435\u0448\u0435\u043d\u0438\u0435 \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u043e \u043d\u0430 PHP \u0438 MySQL, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 cron \u0438 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f \u043a API \u041c\u0422\u0421 Exolve. \u0414\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0437\u0430\u0432\u0435\u0440\u0448\u0451\u043d\u043d\u043e\u0433\u043e \u0437\u0432\u043e\u043d\u043a\u0430 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u0441\u043e\u0431\u0438\u0440\u0430\u044e\u0442\u0441\u044f:<\/p>\n<ul>\n<li>\n<p>\u0420\u0430\u0441\u0448\u0438\u0444\u0440\u043e\u0432\u043a\u0430 \u0434\u0438\u0430\u043b\u043e\u0433\u0430 \u2014 \u043a\u0442\u043e \u0438 \u0447\u0442\u043e \u0441\u043a\u0430\u0437\u0430\u043b<\/p>\n<\/li>\n<li>\n<p>\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0430\u043d\u0430\u043b\u0438\u0437\u0430 \u0440\u0435\u0447\u0438 \u2014 \u044d\u043c\u043e\u0446\u0438\u0438, \u0441\u043e\u043e\u0442\u043d\u043e\u0448\u0435\u043d\u0438\u0435 \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0438 \u0440\u0435\u0447\u0438 \u0438 \u0442\u0438\u0448\u0438\u043d\u044b, \u0441\u043a\u043e\u0440\u043e\u0441\u0442\u044c, \u043f\u0435\u0440\u0435\u0431\u0438\u0432\u0430\u043d\u0438\u044f \u0438 \u0434\u0440\u0443\u0433\u0438\u0435 \u043f\u043e\u043a\u0430\u0437\u0430\u0442\u0435\u043b\u0438<\/p>\n<\/li>\n<\/ul>\n<p>\u041d\u0430 \u043e\u0441\u043d\u043e\u0432\u0435 \u044d\u0442\u0438\u0445 \u0434\u0430\u043d\u043d\u044b\u0445 \u0441\u0438\u0441\u0442\u0435\u043c\u0430:<\/p>\n<ul>\n<li>\n<p>\u0421\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u0442 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u0432 \u0431\u0430\u0437\u0443 \u0434\u0430\u043d\u043d\u044b\u0445<\/p>\n<\/li>\n<li>\n<p>\u0420\u0430\u0437 \u0432 \u0441\u0443\u0442\u043a\u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442 \u0441\u043a\u0440\u0438\u043f\u0442<\/p>\n<\/li>\n<li>\n<p>\u0420\u0430\u0441\u0441\u0447\u0438\u0442\u044b\u0432\u0430\u0435\u0442 \u043c\u0435\u0442\u0440\u0438\u043a\u0438 \u043f\u043e \u043a\u0430\u0436\u0434\u043e\u043c\u0443 \u043e\u043f\u0435\u0440\u0430\u0442\u043e\u0440\u0443:<\/p>\n<ul>\n<li>\n<p>\u0443\u0440\u043e\u0432\u0435\u043d\u044c \u044d\u043c\u043e\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u043e\u0441\u0442\u0438 \u2014 emotionScore<\/p>\n<\/li>\n<li>\n<p>\u0441\u0440\u0435\u0434\u043d\u044e\u044e \u0441\u043a\u043e\u0440\u043e\u0441\u0442\u044c \u0440\u0435\u0447\u0438<\/p>\n<\/li>\n<li>\n<p>\u0440\u0435\u0447\u0435\u0432\u043e\u0439 \u0431\u0430\u043b\u0430\u043d\u0441 \u2014 \u043a\u0442\u043e \u0433\u043e\u0432\u043e\u0440\u0438\u043b \u0431\u043e\u043b\u044c\u0448\u0435<\/p>\n<\/li>\n<li>\n<p>\u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u043f\u0435\u0440\u0435\u0431\u0438\u0432\u0430\u043d\u0438\u0439<\/p>\n<\/li>\n<\/ul>\n<\/li>\n<li>\n<p>\u0410\u043d\u0430\u043b\u0438\u0437\u0438\u0440\u0443\u0435\u0442 \u043e\u0431\u0449\u0438\u0439 \u0443\u0440\u043e\u0432\u0435\u043d\u044c \u043d\u0435\u0433\u0430\u0442\u0438\u0432\u0430<\/p>\n<\/li>\n<li>\n<p>\u041e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442 \u0438\u0442\u043e\u0433\u043e\u0432\u044b\u0439 \u043e\u0442\u0447\u0451\u0442 \u0432 Telegram<\/p>\n<\/li>\n<\/ul>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u0440\u0430\u0437\u0431\u0435\u0440\u0451\u043c \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u0435\u0435.<\/p>\n<h3>\u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043f\u0440\u043e\u0435\u043a\u0442\u0430<\/h3>\n<p>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u043c \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0443 \u043f\u0440\u043e\u0435\u043a\u0442\u0430, \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u043c \u0430\u0432\u0442\u043e\u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0443 \u0447\u0435\u0440\u0435\u0437 Composer, \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u043c \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438 \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 .env \u0438 HTTP-\u0437\u0430\u043f\u0440\u043e\u0441\u0430\u043c\u0438, \u043f\u0440\u043e\u043f\u0438\u0448\u0435\u043c \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f, \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0431\u0430\u0437\u0443 \u0434\u0430\u043d\u043d\u044b\u0445 \u0438 \u0442\u0430\u0431\u043b\u0438\u0446\u044b \u0434\u043b\u044f \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u043e\u043f\u0435\u0440\u0430\u0442\u043e\u0440\u043e\u0432 \u0438 \u0437\u0432\u043e\u043d\u043a\u043e\u0432.<\/p>\n<p>\u0421\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430, \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438, .env \u0438 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u0411\u0414 \u25bc<\/p>\n<details class=\"spoiler\">\n<summary>\u0421\u043a\u0440\u044b\u0442\u044b\u0439 \u0442\u0435\u043a\u0441\u0442<\/summary>\n<div class=\"spoiler__content\">\n<h3>\u0421\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u043f\u0440\u043e\u0435\u043a\u0442\u0430<\/h3>\n<pre><code class=\"php\">voice_analytics\/ \u251c\u2500\u2500 app\/ \u2502   \u251c\u2500\u2500 Infrastructure\/ \u2502   \u2502   \u2514\u2500\u2500 Database.php            # \u041e\u0431\u0451\u0440\u0442\u043a\u0430 \u043d\u0430\u0434 PDO. \u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a MySQL, Singleton \u2502   \u251c\u2500\u2500 Repository\/ \u2502   \u2502   \u251c\u2500\u2500 CallRepository.php      # \u0420\u0430\u0431\u043e\u0442\u0430 \u0441 \u0442\u0430\u0431\u043b\u0438\u0446\u0435\u0439 \u0437\u0432\u043e\u043d\u043a\u043e\u0432 (\u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435, \u043c\u0435\u0442\u0440\u0438\u043a\u0438) \u2502   \u2502   \u2514\u2500\u2500 OperatorRepository.php  # \u041f\u043e\u0438\u0441\u043a \u043e\u043f\u0435\u0440\u0430\u0442\u043e\u0440\u0430 \u043f\u043e \u043d\u043e\u043c\u0435\u0440\u0443 \u2502   \u251c\u2500\u2500 Service\/ \u2502   \u2502   \u251c\u2500\u2500 CallService.php         # \u041b\u043e\u0433\u0438\u043a\u0430 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0437\u0432\u043e\u043d\u043a\u043e\u0432 \u2502   \u2502   \u251c\u2500\u2500 ExolveApiService.php    # \u0418\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f \u0441 \u0432\u043d\u0435\u0448\u043d\u0438\u043c API Exolve \u2502   \u2502   \u251c\u2500\u2500 ReportService.php       # \u0413\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u044f \u043c\u0435\u0442\u0440\u0438\u043a \u043f\u043e \u043e\u043f\u0435\u0440\u0430\u0442\u043e\u0440\u0430\u043c \u2502   \u2502   \u2514\u2500\u2500 ReportSenderService.php # \u041e\u0442\u043f\u0440\u0430\u0432\u043a\u0430 \u043e\u0442\u0447\u0451\u0442\u0430 \u0432 Telegram \u251c\u2500\u2500 cron\/ \u2502   \u2514\u2500\u2500 send_report.sh              # Shell-\u0441\u043a\u0440\u0438\u043f\u0442 \u0434\u043b\u044f \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u043e\u0442\u0447\u0451\u0442\u0430 \u251c\u2500\u2500 artisan.php                     # \u0422\u043e\u0447\u043a\u0430 \u0432\u0445\u043e\u0434\u0430 CLI-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u251c\u2500\u2500 dump.sql                        # SQL-\u0434\u0430\u043c\u043f: \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u0431\u0430\u0437\u044b \u0434\u0430\u043d\u043d\u044b\u0445 \u251c\u2500\u2500 composer.json                   # Composer-\u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u0438 \u0430\u0432\u0442\u043e\u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0430 \u2514\u2500\u2500 .env                            # \u041a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f (\u0411\u0414, \u043a\u043b\u044e\u0447\u0438 API, Telegram) <\/code><\/pre>\n<h4>\u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0438 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0435\u0439<\/h4>\n<p>\u0421\u043e\u0437\u0434\u0430\u0451\u043c \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0443 \u043f\u0440\u043e\u0435\u043a\u0442\u0430, \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u0443\u0435\u043c Composer \u0438 \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438 \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u043c\u0438 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f \u0438 API-\u0437\u0430\u043f\u0440\u043e\u0441\u0430\u043c\u0438.<\/p>\n<p>\u0421\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u0438 \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f Composer:<\/p>\n<pre><code class=\"php\">mkdir voice_analytics cd voice_analytics composer init composer require vlucas\/phpdotenv guzzlehttp\/guzzle<\/code><\/pre>\n<p>Composer.json:<\/p>\n<pre><code class=\"php\">{    \"autoload\": {        \"psr-4\": {            \"App\\\\\": \"app\/\"        }    },    \"require\": {        \"vlucas\/phpdotenv\": \"^5.6\",        \"guzzlehttp\/guzzle\": \"^7.9\"    } }<\/code><\/pre>\n<p>\u0413\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u044f \u0430\u0432\u0442\u043e\u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438:<\/p>\n<pre><code class=\"php\">composer dump-autoload<\/code><\/pre>\n<h4>\u041a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0445 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f<\/h4>\n<p>\u0421\u043e\u0437\u0434\u0430\u0451\u043c \u0444\u0430\u0439\u043b .env \u0432 \u043a\u043e\u0440\u043d\u0435 \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0438 \u0437\u0430\u043f\u043e\u043b\u043d\u044f\u0435\u043c \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435 \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u043a MySQL, API-\u043a\u043b\u044e\u0447 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u041c\u0422\u0421 Exolve, \u0442\u043e\u043a\u0435\u043d \u0434\u043b\u044f \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u043a \u0442\u0435\u043b\u0435\u0433\u0440\u0430\u043c-\u0431\u043e\u0442\u0443 \u0438 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440 \u0447\u0430\u0442\u0430, \u043a\u0443\u0434\u0430 \u0431\u0443\u0434\u0435\u0442 \u043f\u0440\u0438\u0445\u043e\u0434\u0438\u0442\u044c \u043e\u0442\u0447\u0451\u0442.<\/p>\n<pre><code class=\"php\">DB_HOST=\u0432\u0430\u0448 \u0445\u043e\u0441\u0442 DB_PORT=\u0432\u0430\u0448 \u043f\u043e\u0440\u0442 DB_NAME=voice_analytics DB_USER=root DB_PASS=root   EXOLVE_API_KEY=\u0432\u0430\u0448_\u043a\u043b\u044e\u0447 TELEGRAM_BOT_TOKEN=\u0442\u043e\u043a\u0435\u043d_\u0432\u0430\u0448\u0435\u0433\u043e_\u0431\u043e\u0442\u0430 TELEGRAM_CHAT_ID=id_\u0432\u0430\u0448\u0435\u0433\u043e_\u0447\u0430\u0442\u0430<\/code><\/pre>\n<h4>\u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u0431\u0430\u0437\u044b \u0434\u0430\u043d\u043d\u044b\u0445 \u0438 \u0442\u0430\u0431\u043b\u0438\u0446\u044b<\/h4>\n<p>\u0414\u043b\u044f \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0440\u0430\u0441\u0448\u0438\u0444\u0440\u043e\u0432\u043e\u043a \u0437\u0432\u043e\u043d\u043a\u043e\u0432 \u0438 \u0440\u0430\u0441\u0447\u0451\u0442\u0430 \u043c\u0435\u0442\u0440\u0438\u043a \u043d\u0430\u043c \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u044f\u0442\u0441\u044f \u0434\u0432\u0435 \u0442\u0430\u0431\u043b\u0438\u0446\u044b: \u043e\u0434\u043d\u0430 \u2014 \u0441 \u043e\u043f\u0435\u0440\u0430\u0442\u043e\u0440\u0430\u043c\u0438, \u0434\u0440\u0443\u0433\u0430\u044f \u2014 \u0441 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430\u043c\u0438 \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0437\u0432\u043e\u043d\u043a\u0430. \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u043c\u0438\u043d\u0438\u043c\u0430\u043b\u044c\u043d\u0443\u044e, \u043d\u043e \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u0443\u044e \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0443. \u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0435\u043c\u0441\u044f \u043a \u0441\u0432\u043e\u0435\u0439 MySQL \u0438 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u043c \u0447\u0435\u0442\u044b\u0440\u0435 \u043a\u043e\u043c\u0430\u043d\u0434\u044b:<\/p>\n<pre><code class=\"php\">CREATE DATABASE voice_analytics CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE voice_analytics;   -- \u0422\u0430\u0431\u043b\u0438\u0446\u0430 \u043e\u043f\u0435\u0440\u0430\u0442\u043e\u0440\u043e\u0432 CREATE TABLE operators (    id    INT AUTO_INCREMENT PRIMARY KEY,    name  VARCHAR(100) NOT NULL,    phone VARCHAR(20)  NOT NULL UNIQUE );   -- \u0422\u0430\u0431\u043b\u0438\u0446\u0430 \u0437\u0432\u043e\u043d\u043a\u043e\u0432 CREATE TABLE calls (    id                       INT AUTO_INCREMENT PRIMARY KEY,    call_id                  VARCHAR(100) NOT NULL UNIQUE,    operator_id              INT,    call_date                DATETIME     NOT NULL,    emotion_score            DECIMAL(4, 2),    speech_rate              DECIMAL(5, 2),    speech_duration_operator INT DEFAULT 0,    speech_duration_client   INT DEFAULT 0,    interruptions            INT DEFAULT 0,    sentiment                VARCHAR(20),    transcript_text          TEXT,    FOREIGN KEY (operator_id) REFERENCES operators (id) ON DELETE SET NULL ); <\/code><\/pre>\n<\/div>\n<\/details>\n<h3>\u041a\u0430\u043a \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435<\/h3>\n<p>\u0412 \u044d\u0442\u043e\u0439 \u0447\u0430\u0441\u0442\u0438 \u0440\u0430\u0437\u0431\u0435\u0440\u0451\u043c \u043a\u043b\u044e\u0447\u0435\u0432\u044b\u0435 \u043c\u043e\u0434\u0443\u043b\u0438, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0441\u043e\u0431\u0438\u0440\u0430\u044e\u0442 \u0430\u043d\u0430\u043b\u0438\u0442\u0438\u043a\u0443 \u043f\u043e \u0437\u0432\u043e\u043d\u043a\u0430\u043c \u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u044e\u0442 \u043e\u0442\u0447\u0451\u0442. \u041a\u043e\u0434 \u0440\u0430\u0437\u0434\u0435\u043b\u0451\u043d \u043f\u043e \u0441\u043b\u043e\u044f\u043c:<\/p>\n<ul>\n<li>\n<p>Infrastructure \u2014 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a \u0431\u0430\u0437\u0435 \u0434\u0430\u043d\u043d\u044b\u0445<\/p>\n<\/li>\n<li>\n<p>Repository \u2014 \u0434\u043e\u0441\u0442\u0443\u043f \u043a \u0442\u0430\u0431\u043b\u0438\u0446\u0430\u043c \u0437\u0432\u043e\u043d\u043a\u043e\u0432 \u0438 \u043e\u043f\u0435\u0440\u0430\u0442\u043e\u0440\u043e\u0432<\/p>\n<\/li>\n<li>\n<p>Service \u2014 \u043b\u043e\u0433\u0438\u043a\u0430 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0437\u0432\u043e\u043d\u043a\u043e\u0432, \u0432\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435 \u0441 API \u0438 \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u044f \u043e\u0442\u0447\u0451\u0442\u043e\u0432<\/p>\n<\/li>\n<li>\n<p>artisan.php \u2014 \u0442\u043e\u0447\u043a\u0430 \u0432\u0445\u043e\u0434\u0430 \u0434\u043b\u044f \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u0432\u0440\u0443\u0447\u043d\u0443\u044e \u0438\u043b\u0438 \u0447\u0435\u0440\u0435\u0437 cron<\/p>\n<\/li>\n<li>\n<p>cron\/ \u2014 \u043e\u0431\u0451\u0440\u0442\u043a\u0430 \u0434\u043b\u044f \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0433\u043e \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u043e\u0442\u0447\u0451\u0442\u0430 \u043f\u043e \u0440\u0430\u0441\u043f\u0438\u0441\u0430\u043d\u0438\u044e<\/p>\n<\/li>\n<\/ul>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043f\u0440\u043e\u0439\u0434\u0451\u043c\u0441\u044f \u043f\u043e \u043a\u0430\u0436\u0434\u043e\u043c\u0443 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0443.<\/p>\n<h4>\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a \u0431\u0430\u0437\u0435 \u0434\u0430\u043d\u043d\u044b\u0445<\/h4>\n<p>\u0424\u0430\u0439\u043b: app\/Infrastructure\/Database.php<\/p>\n<p>\u041a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 Database \u0441\u043e\u0437\u0434\u0430\u0451\u0442 \u0438 \u043f\u0435\u0440\u0435\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a MySQL \u0447\u0435\u0440\u0435\u0437 PDO. \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0448\u0430\u0431\u043b\u043e\u043d Singleton, \u0432\u0441\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0431\u0435\u0440\u0443\u0442\u0441\u044f \u0438\u0437 .env.<\/p>\n<pre><code class=\"php\">&lt;?php   namespace app\\Infrastructure;   use PDO; use PDOException; use RuntimeException;   final class Database {    private static ?PDO $pdo = null;      private function __construct()    {    }      public static function getConnection(): PDO    {        if (self::$pdo === null) {            $host = getenv('DB_HOST') ?? null;            $port = getenv('DB_PORT') ?? '3306';            $db = getenv('DB_NAME') ?? null;            $user = getenv('DB_USER') ?? null;            $pass = getenv('DB_PASS') ?? null;              if (!$host || !$db || !$user) {                throw new RuntimeException('\u041a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f \u0431\u0430\u0437\u044b \u0434\u0430\u043d\u043d\u044b\u0445 \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u0432 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0445 \u0441\u0440\u0435\u0434\u044b.');            }              $dsn = sprintf(                'mysql:host=%s;port=%s;dbname=%s;charset=utf8mb4',                $host,                $port,                $db            );              try {                self::$pdo = new PDO(                    $dsn,                    $user,                    $pass,                    [                        PDO::ATTR_ERRMODE =&gt; PDO::ERRMODE_EXCEPTION,                        PDO::ATTR_DEFAULT_FETCH_MODE =&gt; PDO::FETCH_ASSOC,                        PDO::ATTR_EMULATE_PREPARES =&gt; false,                    ]                );            } catch (PDOException $e) {                throw new RuntimeException('\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a \u0431\u0430\u0437\u0435 \u0434\u0430\u043d\u043d\u044b\u0445 \u043d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c: ' . $e-&gt;getMessage());            }        }          return self::$pdo;    } } <\/code><\/pre>\n<h4>\u0420\u0430\u0431\u043e\u0442\u0430 \u0441 \u0434\u0430\u043d\u043d\u044b\u043c\u0438<\/h4>\n<p>\u0421\u043e\u0441\u0442\u043e\u0438\u0442 \u0438\u0437 \u0434\u0432\u0443\u0445 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u043e\u0432: \u043e\u0434\u0438\u043d \u043e\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u0437\u0430 \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435 \u0430\u043d\u0430\u043b\u0438\u0442\u0438\u043a\u0438 \u0437\u0432\u043e\u043d\u043a\u043e\u0432 \u0438 \u0440\u0430\u0441\u0447\u0451\u0442 \u043c\u0435\u0442\u0440\u0438\u043a \u0434\u043b\u044f \u043e\u0442\u0447\u0451\u0442\u043e\u0432, \u0434\u0440\u0443\u0433\u043e\u0439 \u2014 \u0437\u0430 \u043f\u043e\u0438\u0441\u043a \u043e\u043f\u0435\u0440\u0430\u0442\u043e\u0440\u0430 \u043f\u043e \u043d\u043e\u043c\u0435\u0440\u0443 \u0442\u0435\u043b\u0435\u0444\u043e\u043d\u0430. \u041f\u0440\u043e\u0441\u0442\u0430\u044f \u043e\u0431\u0432\u044f\u0437\u043a\u0430 \u043f\u043e\u0432\u0435\u0440\u0445 \u0431\u0430\u0437\u044b, \u0431\u0435\u0437 \u043b\u0438\u0448\u043d\u0435\u0439 \u043b\u043e\u0433\u0438\u043a\u0438.<\/p>\n<h3>\u0421\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435 \u0437\u0432\u043e\u043d\u043a\u043e\u0432 \u0438 \u0440\u0430\u0441\u0447\u0451\u0442 \u043c\u0435\u0442\u0440\u0438\u043a<\/h3>\n<p>\u0424\u0430\u0439\u043b: app\/Repository\/CallRepository.php<\/p>\n<p>\u042d\u0442\u043e \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0439 \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u0442\u0430\u0431\u043b\u0438\u0446\u0435\u0439 calls \u0432 \u0431\u0430\u0437\u0435 \u0434\u0430\u043d\u043d\u044b\u0445. \u0412 \u043d\u0451\u043c \u0445\u0440\u0430\u043d\u044f\u0442\u0441\u044f \u0434\u0430\u043d\u043d\u044b\u0435 \u043f\u043e \u043a\u0430\u0436\u0434\u043e\u043c\u0443 \u0437\u0430\u0432\u0435\u0440\u0448\u0451\u043d\u043d\u043e\u043c\u0443 \u0437\u0432\u043e\u043d\u043a\u0443 \u0438 \u0441\u043e\u0431\u0438\u0440\u0430\u044e\u0442\u0441\u044f \u043c\u0435\u0442\u0440\u0438\u043a\u0438 \u043f\u043e \u043e\u043f\u0435\u0440\u0430\u0442\u043e\u0440\u0430\u043c \u0437\u0430 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0435 24 \u0447\u0430\u0441\u0430: \u044d\u043c\u043e\u0446\u0438\u0438, \u043f\u0435\u0440\u0435\u0431\u0438\u0432\u0430\u043d\u0438\u044f, \u0440\u0435\u0447\u0435\u0432\u043e\u0439 \u0431\u0430\u043b\u0430\u043d\u0441 \u0438 \u0434\u043e\u043b\u044f \u043d\u0435\u0433\u0430\u0442\u0438\u0432\u0430.<\/p>\n<pre><code class=\"php\">&lt;?php   namespace app\\Repository;   use app\\Infrastructure\\Database; use DateTimeImmutable; use DateTimeZone; use Exception; use PDO;   class CallRepository {    private PDO $pdo;      public function __construct()    {        $this-&gt;pdo = Database::getConnection();    }      public function saveCall(array $data): bool    {        $stmt = $this-&gt;pdo-&gt;prepare(\"            INSERT INTO calls (                call_id,                operator_id,                call_date,                emotion_score,                speech_rate,                speech_duration_operator,                speech_duration_client,                interruptions,                sentiment,                transcript_text            ) VALUES (                :call_id,                :operator_id,                :call_date,                :emotion_score,                :speech_rate,                :speech_duration_operator,                :speech_duration_client,                :interruptions,                :sentiment,                :transcript_text            )        \");          return $stmt-&gt;execute([            ':call_id' =&gt; $data['call_id'],            ':operator_id' =&gt; $data['operator_id'],            ':call_date' =&gt; $data['call_date'],            ':emotion_score' =&gt; $data['emotion_score'],            ':speech_rate' =&gt; $data['speech_rate'],            ':speech_duration_operator' =&gt; $data['speech_duration_operator'],            ':speech_duration_client' =&gt; $data['speech_duration_client'],            ':interruptions' =&gt; $data['interruptions'],            ':sentiment' =&gt; $data['sentiment'],            ':transcript_text' =&gt; is_array($data['transcript_text']) ? json_encode($data['transcript_text']) : $data['transcript_text'],        ]);    }      \/**     * \u041f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u043c\u0435\u0442\u0440\u0438\u043a \u043f\u043e \u043e\u043f\u0435\u0440\u0430\u0442\u043e\u0440\u0430\u043c \u0437\u0430 \u0437\u0430\u0434\u0430\u043d\u043d\u0443\u044e \u0434\u0430\u0442\u0443     * @throws Exception     *\/    public function getOperatorMetrics(): array    {        $now = new DateTimeImmutable('now', new DateTimeZone('Europe\/Moscow'));        $yesterday = $now-&gt;modify('-1 day')-&gt;format('Y-m-d H:i:s');          $sql = \"        SELECT            o.id,            o.name,            COUNT(c.id) AS calls_count,            IFNULL(AVG(c.emotion_score), 0) AS avg_emotion_score,            IFNULL(AVG(c.interruptions), 0) AS avg_interruptions,            IFNULL(AVG(                CASE                    WHEN (c.speech_duration_operator + c.speech_duration_client) &gt; 0                    THEN c.speech_duration_operator \/ (c.speech_duration_operator + c.speech_duration_client) * 100                    ELSE 0                END            ), 0) AS avg_speech_balance,            IFNULL(AVG(c.speech_rate), 0) AS avg_speech_rate,            IFNULL(                SUM(                    CASE                        WHEN c.emotion_score &lt; :negativeScore OR c.sentiment = :negativeSentiment THEN 1                        ELSE 0                    END                ) \/ COUNT(c.id) * 100, 0            ) AS negative_calls_percent        FROM operators o        LEFT JOIN calls c ON o.id = c.operator_id AND c.call_date &gt;= :yesterday        GROUP BY o.id    \";          $stmt = $this-&gt;pdo-&gt;prepare($sql);        $stmt-&gt;execute([            ':negativeScore' =&gt; -0.4,            ':negativeSentiment' =&gt; 'negative',            ':yesterday' =&gt; $yesterday,        ]);          return $stmt-&gt;fetchAll(PDO::FETCH_ASSOC);    }      public function exists(string $callId): bool    {        $stmt = $this-&gt;pdo-&gt;prepare(\"SELECT 1 FROM calls WHERE call_id = :call_id LIMIT 1\");        $stmt-&gt;execute([':call_id' =&gt; $callId]);        return (bool) $stmt-&gt;fetchColumn();    } } <\/code><\/pre>\n<h3>\u041f\u043e\u0438\u0441\u043a \u043e\u043f\u0435\u0440\u0430\u0442\u043e\u0440\u0430 \u043f\u043e \u043d\u043e\u043c\u0435\u0440\u0443<\/h3>\n<p>\u0424\u0430\u0439\u043b: app\/Repository\/OperatorRepository.php<\/p>\n<p>\u041f\u0440\u043e\u0441\u0442\u0430\u044f \u043e\u0431\u0432\u044f\u0437\u043a\u0430 \u043d\u0430\u0434 \u0442\u0430\u0431\u043b\u0438\u0446\u0435\u0439 operators. \u041d\u0430\u0445\u043e\u0434\u0438\u0442 ID \u043e\u043f\u0435\u0440\u0430\u0442\u043e\u0440\u0430 \u043f\u043e \u043d\u043e\u043c\u0435\u0440\u0443 \u0442\u0435\u043b\u0435\u0444\u043e\u043d\u0430. \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u043f\u0440\u0438 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0435 \u0437\u0432\u043e\u043d\u043a\u043e\u0432.<\/p>\n<pre><code class=\"php\">&lt;?php   namespace app\\Repository;   use app\\Infrastructure\\Database;   class OperatorRepository {    protected Database $pdo;      public function __construct()    {        $this-&gt;pdo = Database::getConnection();    }      public function getOperatorIdByPhone(string $phone): ?int    {        if (!$phone) {            return null;        }          $stmt = $this-&gt;pdo-&gt;prepare(\"SELECT id FROM operators WHERE phone = ?\");        $stmt-&gt;execute([$phone]);        $row = $stmt-&gt;fetch();          return $row['id'] ?? null;    } } <\/code><\/pre>\n<h4>\u041e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u0437\u0432\u043e\u043d\u043a\u043e\u0432<\/h4>\n<p>\u0424\u0430\u0439\u043b: app\/Service\/CallService.php<\/p>\n<p>\u041a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u043f\u0440\u0438 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u0438 \u0440\u0430\u0437\u0433\u043e\u0432\u043e\u0440\u0430: \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 \u0434\u0430\u043d\u043d\u044b\u0435 \u043f\u043e \u0437\u0432\u043e\u043d\u043a\u0430\u043c \u0438\u0437 \u0442\u0435\u043b\u0435\u043a\u043e\u043c API, \u0438\u0437\u0432\u043b\u0435\u043a\u0430\u0435\u0442 \u043d\u0443\u0436\u043d\u044b\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0438 \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u0442 \u0432\u0441\u0451 \u0432 \u0431\u0430\u0437\u0443. \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0432 \u0432\u0435\u0431\u0445\u0443\u043a-\u0442\u0435\u043b\u0435\u0444\u043e\u043d\u0438\u0438 \u0438\u043b\u0438 \u0432 \u0440\u0443\u0447\u043d\u043e\u0439 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0435.<\/p>\n<pre><code class=\"php\">&lt;?php   namespace App\\Service;   use app\\Repository\\CallRepository; use app\\Repository\\OperatorRepository;   \/** * \u041e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u0442 \u0437\u0430\u0432\u0435\u0440\u0448\u0451\u043d\u043d\u044b\u0435 \u0437\u0432\u043e\u043d\u043a\u0438: \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 \u0430\u043d\u0430\u043b\u0438\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u0438\u0437 Exolve API \u0438 \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u0442 \u0432 \u0431\u0430\u0437\u0443. * * \u041c\u0435\u0442\u043e\u0434 handleCall \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u043f\u0440\u0438 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u0438 \u0437\u0432\u043e\u043d\u043a\u0430 (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0447\u0435\u0440\u0435\u0437 webhook \u043e\u0442 \u0441\u0438\u0441\u0442\u0435\u043c\u044b \u0442\u0435\u043b\u0435\u0444\u043e\u043d\u0438\u0438). *\/ class CallService {    public function __construct(        private ExolveApiService   $api,        private CallRepository     $callRepository,        private OperatorRepository $operatorRepository    ) {}      \/**     * \u041e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u0442 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u0435 \u0437\u0432\u043e\u043d\u043a\u0430 \u0438 \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u0442 \u0430\u043d\u0430\u043b\u0438\u0442\u0438\u043a\u0443.     *\/    public function handleCall(string $callId): bool    {        $data = $this-&gt;api-&gt;getSpeechAnalytics($callId);          if (empty($data['speech_analytic'])) {            error_log(\"CallService: \u041f\u0443\u0441\u0442\u043e\u0439 \u043e\u0442\u0432\u0435\u0442 \u043f\u043e $callId\");            return false;        }          $analytic = $data['speech_analytic'];          $operatorPhone = $analytic['from'] ?? '';        $operatorId = $this-&gt;operatorRepository-&gt;getOperatorIdByPhone($operatorPhone);          $speakerStats = $analytic['conversation_statistics']['speaker_statistics'] ?? [];          $operatorSpeech = 0.0;        $clientSpeech = 0.0;        $speechRate = null;          foreach ($speakerStats as $speaker) {            $duration = $this-&gt;parseDuration($speaker['total_speech_duration'] ?? '0s');              if ($speaker['channel_tag'] === '0') {                $clientSpeech = $duration;            } elseif ($speaker['channel_tag'] === '1') {                $operatorSpeech = $duration;                $speechRate = $speaker['speech_speed']['avg'] ?? null;            }        }          $transcript = $analytic['transcription']['phrases'] ?? [];          $transcriptJson = json_encode($transcript, JSON_UNESCAPED_UNICODE);        if (json_last_error() !== JSON_ERROR_NONE) {            error_log(\"CallService: \u041e\u0448\u0438\u0431\u043a\u0430 \u0441\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0442\u0440\u0430\u043d\u0441\u043a\u0440\u0438\u043f\u0442\u0430 \u0434\u043b\u044f $callId: \" . json_last_error_msg());            $transcriptJson = null;        }          $emotionScore = $this-&gt;calculateEmotionScore($analytic['conversation_summary']['quiz'] ?? []);          $interruptions = array_sum(array_map(            fn($speaker) =&gt; (int)($speaker['total_interrupts_count'] ?? 0),            $analytic['interrupts_statistics']['speaker_interrupts'] ?? []        ));          if ($this-&gt;callRepository-&gt;exists($callId)) {            error_log(\"CallService: \u0437\u0430\u043f\u0438\u0441\u044c \u0441 call_id=$callId \u0443\u0436\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442, \u043f\u0440\u043e\u043f\u0443\u0441\u043a\");            return false;        }          return $this-&gt;callRepository-&gt;saveCall([            'call_id' =&gt; $callId,            'operator_id' =&gt; $operatorId,            'call_date' =&gt; $analytic['start_time'] ?? date('Y-m-d H:i:s'),            'emotion_score' =&gt; $emotionScore,            'speech_rate' =&gt; $speechRate,            'speech_duration_operator' =&gt; $operatorSpeech,            'speech_duration_client' =&gt; $clientSpeech,            'interruptions' =&gt; $interruptions,            'sentiment' =&gt; $this-&gt;extractFirstStatement($analytic['summarization']['statements'] ?? []),            'transcript_text' =&gt; $transcriptJson,        ]);    }      \/**     * \u0412\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u043f\u0435\u0440\u0432\u043e\u0435 \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u0435 (summary) \u0438\u0437 \u0431\u043b\u043e\u043a\u0430 statements.     *\/    private function extractFirstStatement(array $statements): ?string    {        return $statements[0]['response'] ?? null;    }      \/**     * \u041f\u0435\u0440\u0435\u0432\u043e\u0434\u0438\u0442 \u0441\u0442\u0440\u043e\u043a\u043e\u0432\u0443\u044e \u0434\u043b\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \"12s\") \u0432 float-\u0441\u0435\u043a\u0443\u043d\u0434\u044b.     *\/    private function parseDuration(string $duration): float    {        if (preg_match('\/([\\d.]+)\/', $duration, $matches)) {            return (float)$matches[1];        }          return 0.0;    }      \/**     * \u0412\u044b\u0447\u0438\u0441\u043b\u044f\u0435\u0442 \u0443\u0441\u043b\u043e\u0432\u043d\u044b\u0439 \u044d\u043c\u043e\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u044b\u0439 \u0438\u043d\u0434\u0435\u043a\u0441 \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0435 \u043e\u0442\u0432\u0435\u0442\u043e\u0432 \u0432 quiz.     *\/    private function calculateEmotionScore(array $quiz): ?float    {        $positive = [            '\u041e\u043f\u0435\u0440\u0430\u0442\u043e\u0440 \u0431\u044b\u043b \u0432\u0435\u0436\u043b\u0438\u0432\u044b\u043c?',            '\u041e\u043f\u0435\u0440\u0430\u0442\u043e\u0440 \u0431\u044b\u043b \u044d\u043c\u043f\u0430\u0442\u0438\u0447\u043d\u044b\u043c?',            '\u041e\u043f\u0435\u0440\u0430\u0442\u043e\u0440 \u0431\u044b\u043b \u0443\u0432\u0435\u0440\u0435\u043d\u043d\u044b\u043c?',            '\u041a\u043b\u0438\u0435\u043d\u0442 \u043e\u0441\u0442\u0430\u043b\u0441\u044f \u0434\u043e\u0432\u043e\u043b\u0435\u043d?',        ];          $negative = [            '\u041e\u043f\u0435\u0440\u0430\u0442\u043e\u0440 \u0431\u044b\u043b \u0440\u0430\u0437\u0434\u0440\u0430\u0436\u0435\u043d?',            '\u041e\u043f\u0435\u0440\u0430\u0442\u043e\u0440 \u0445\u0430\u043c\u0438\u043b?',            '\u041a\u043b\u0438\u0435\u043d\u0442 \u0443\u0448\u0435\u043b \u0440\u0430\u0437\u0434\u0440\u0430\u0436\u0435\u043d\u043d\u044b\u043c?',            '\u041a\u043b\u0438\u0435\u043d\u0442 \u0445\u0430\u043c\u0438\u043b?',        ];          $score = 0;        $total = 0;          foreach ($quiz as $item) {            $question = preg_replace('\/^\\d+\\.\\s*\/', '', $item['request'] ?? '');            $answer = mb_strtolower($item['response'] ?? '');              if (in_array($question, $positive, true)) {                $score += str_contains($answer, '\u0434\u0430') ? 1 : 0;                $total++;            } elseif (in_array($question, $negative, true)) {                $score += str_contains($answer, '\u0434\u0430') ? 0 : 1;                $total++;            }        }          return $total &gt; 0 ? round($score \/ $total, 2) : null;    } } <\/code><\/pre>\n<h4>\u041f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u0440\u0435\u0447\u0435\u0432\u043e\u0439 \u0430\u043d\u0430\u043b\u0438\u0442\u0438\u043a\u0438<\/h4>\n<p>\u0424\u0430\u0439\u043b: app\/Service\/ExolveApiService.php<\/p>\n<p>\u0421\u0435\u0440\u0432\u0438\u0441 \u0438\u043d\u043a\u0430\u043f\u0441\u0443\u043b\u0438\u0440\u0443\u0435\u0442 \u0432\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435 \u0441 <a href=\"https:\/\/docs.exolve.ru\/docs\/ru\/api-reference\/speech-analytics-api\/\" rel=\"noopener noreferrer nofollow\">API \u041c\u0422\u0421 Exolve<\/a>: \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442 HTTP-\u0437\u0430\u043f\u0440\u043e\u0441 \u0441 call_id, \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 JSON-\u043e\u0442\u0432\u0435\u0442 \u0438 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u0435\u0433\u043e \u0432 \u0432\u0438\u0434\u0435 \u043c\u0430\u0441\u0441\u0438\u0432\u0430. \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 Guzzle, \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u0442 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0438 \u0438 \u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u0442 \u043e\u0448\u0438\u0431\u043a\u0438.<\/p>\n<pre><code class=\"php\">&lt;?php   namespace App\\Service;   use GuzzleHttp\\Client; use RuntimeException;   class ExolveApiService {    private Client $client;    private string $apiKey;      public function __construct()    {        $this-&gt;apiKey = getenv('EXOLVE_API_KEY') ?? '';        if (empty($this-&gt;apiKey)) {            throw new RuntimeException('EXOLVE_API_KEY \u043d\u0435 \u0437\u0430\u0434\u0430\u043d.');        }          $this-&gt;client = new Client([            'base_uri' =&gt; 'https:\/\/api.exolve.ru',            'headers' =&gt; [                'Authorization' =&gt; 'Bearer ' . $this-&gt;apiKey,                'Content-Type' =&gt; 'application\/json',                'Accept' =&gt; 'application\/json',            ],            'timeout' =&gt; 10.0,        ]);    }      public function getSpeechAnalytics(string $callId): ?array    {        $response = $this-&gt;client-&gt;post('\/statistics\/call-record\/v1\/GetSpeechAnalytic', [            'json' =&gt; ['call_id' =&gt; $callId]        ]);            if ($response-&gt;getStatusCode() !== 200) {            throw new \\RuntimeException(\"Exolve API \u0432\u0435\u0440\u043d\u0443\u043b \u043a\u043e\u0434: \" . $response-&gt;getStatusCode());        }          $data = json_decode($response-&gt;getBody()-&gt;getContents(), true);          if (!is_array($data)) {            throw new \\RuntimeException(\"\u041d\u0435\u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u044b\u0439 JSON \u043e\u0442 Exolve API\");        }          return $data;    } } <\/code><\/pre>\n<h4>\u0424\u043e\u0440\u043c\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u043e\u0442\u0447\u0451\u0442\u0430 \u043f\u043e \u043c\u0435\u0442\u0440\u0438\u043a\u0430\u043c<\/h4>\n<p>\u0424\u0430\u0439\u043b: app\/Service\/ReportService.php<\/p>\n<p>\u041a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 \u0430\u0433\u0440\u0435\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0435 \u043c\u0435\u0442\u0440\u0438\u043a\u0438 \u0438\u0437 \u0431\u0430\u0437\u044b \u0438 \u0444\u043e\u0440\u043c\u0438\u0440\u0443\u0435\u0442 \u0438\u0442\u043e\u0433\u043e\u0432\u044b\u0439 \u043e\u0442\u0447\u0451\u0442: \u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0431\u044b\u043b\u043e \u0437\u0432\u043e\u043d\u043a\u043e\u0432, \u043a\u0430\u043a\u043e\u0432 \u0443\u0440\u043e\u0432\u0435\u043d\u044c \u044d\u043c\u043e\u0446\u0438\u0439, \u043f\u0435\u0440\u0435\u0431\u0438\u0432\u0430\u043d\u0438\u0439, \u0440\u0435\u0447\u0435\u0432\u043e\u0439 \u0431\u0430\u043b\u0430\u043d\u0441 \u0438 \u0434\u043e\u043b\u044f \u043d\u0435\u0433\u0430\u0442\u0438\u0432\u0430 \u043f\u043e \u043a\u0430\u0436\u0434\u043e\u043c\u0443 \u043e\u043f\u0435\u0440\u0430\u0442\u043e\u0440\u0443. \u042d\u0442\u043e\u0442 \u0442\u0435\u043a\u0441\u0442 \u0431\u0443\u0434\u0435\u0442 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u0435\u043d \u0432 Telegram.<\/p>\n<pre><code class=\"php\">&lt;?php   namespace App\\Service;   use App\\Repository\\CallRepository;   class ReportService {    public function __construct(        private CallRepository $callRepository,    )    {}      public function getMetrics(): array    {        return $this-&gt;callRepository-&gt;getOperatorMetrics();    }      public function formatReport(array $metrics): string    {        $lines = [];        foreach ($metrics as $m) {            $lines[] = \"\u041e\u043f\u0435\u0440\u0430\u0442\u043e\u0440: {$m['name']}\";            $lines[] = \"\u0417\u0432\u043e\u043d\u043a\u043e\u0432: {$m['calls_count']}\";            $lines[] = \"\u0421\u0440\u0435\u0434\u043d\u0438\u0439 emotionScore \u043a\u043b\u0438\u0435\u043d\u0442\u0430: \" . number_format($m['avg_emotion_score'], 2);            $lines[] = \"\u0421\u0440\u0435\u0434\u043d\u0435\u0435 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u043f\u0435\u0440\u0435\u0431\u0438\u0432\u0430\u043d\u0438\u0439: \" . number_format($m['avg_interruptions'], 2);            $lines[] = \"\u0420\u0435\u0447\u0435\u0432\u043e\u0439 \u0431\u0430\u043b\u0430\u043d\u0441 (% \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u0433\u043e\u0432\u043e\u0440\u0438\u0442 \u043e\u043f\u0435\u0440\u0430\u0442\u043e\u0440): \" . number_format($m['avg_speech_balance'], 2) . \"%\";            $lines[] = \"\u0421\u0440\u0435\u0434\u043d\u044f\u044f \u0441\u043a\u043e\u0440\u043e\u0441\u0442\u044c \u0440\u0435\u0447\u0438: \" . number_format($m['avg_speech_rate'], 2);            $lines[] = \"\u0414\u043e\u043b\u044f \u043d\u0435\u0433\u0430\u0442\u0438\u0432\u043d\u044b\u0445 \u0437\u0432\u043e\u043d\u043a\u043e\u0432: \" . number_format($m['negative_calls_percent'], 2) . \"%\";            $lines[] = \"-------------------------------\";        }        return implode(\"\\n\", $lines);    } } <\/code><\/pre>\n<h4>\u041e\u0442\u043f\u0440\u0430\u0432\u043a\u0430 \u043e\u0442\u0447\u0451\u0442\u0430<\/h4>\n<p>\u0424\u0430\u0439\u043b: app\/Service\/ReportSenderService.php<\/p>\n<p>\u0421\u0435\u0440\u0432\u0438\u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 Telegram Bot API \u0434\u043b\u044f \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u043e\u0442\u0447\u0451\u0442\u0430 \u0432 \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0439 \u0447\u0430\u0442. \u0411\u0435\u0440\u0451\u0442 \u0442\u043e\u043a\u0435\u043d \u0438 chat_id \u0438\u0437 .env, \u0441\u043e\u0431\u0438\u0440\u0430\u0435\u0442 HTTP-\u0437\u0430\u043f\u0440\u043e\u0441 \u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u0447\u0435\u0440\u0435\u0437 file_get_contents.<\/p>\n<pre><code class=\"php\">&lt;?php   namespace App\\Service;   class ReportSenderService {    private string $botToken;    private string $chatId;      public function __construct()    {        $this-&gt;botToken = getenv('TELEGRAM_BOT_TOKEN');        $this-&gt;chatId = getenv('TELEGRAM_CHAT_ID');    }      public function sendMessage(string $text): bool    {        $text = htmlspecialchars($text, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');          $url = \"https:\/\/api.telegram.org\/bot{$this-&gt;botToken}\/sendMessage\";          $data = [            'chat_id' =&gt; $this-&gt;chatId,            'text' =&gt; $text,            'parse_mode' =&gt; 'HTML',        ];          $options = [            'http' =&gt; [                'header'  =&gt; \"Content-type: application\/x-www-form-urlencoded\\r\\n\",                'method'  =&gt; 'POST',                'content' =&gt; http_build_query($data),                'timeout' =&gt; 5,            ],        ];          $context  = stream_context_create($options);        $result = file_get_contents($url, false, $context);          return $result !== false;    } } <\/code><\/pre>\n<h4>\u0422\u043e\u0447\u043a\u0430 \u0432\u0445\u043e\u0434\u0430 \u0438 \u0437\u0430\u043f\u0443\u0441\u043a \u043f\u043e \u0440\u0430\u0441\u043f\u0438\u0441\u0430\u043d\u0438\u044e<\/h4>\n<p>\u0421\u043a\u0440\u0438\u043f\u0442 artisan.php \u0441\u043e\u0431\u0438\u0440\u0430\u0435\u0442 \u0432\u0441\u0435 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u044b: \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u0442 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f, \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 \u043c\u0435\u0442\u0440\u0438\u043a\u0438, \u0444\u043e\u0440\u043c\u0438\u0440\u0443\u0435\u0442 \u043e\u0442\u0447\u0451\u0442 \u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442 \u0435\u0433\u043e \u0432 Telegram. \u0414\u043b\u044f \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0430\u0446\u0438\u0438 \u0441\u043e\u0437\u0434\u0430\u0451\u043c shell-\u043e\u0431\u0451\u0440\u0442\u043a\u0443 \u0438 \u043d\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0435\u043c \u0437\u0430\u043f\u0443\u0441\u043a \u043f\u043e cron \u2014 \u043e\u0442\u0447\u0451\u0442 \u0431\u0443\u0434\u0435\u0442 \u043f\u0440\u0438\u0445\u043e\u0434\u0438\u0442\u044c \u043a\u0430\u0436\u0434\u044b\u0439 \u0434\u0435\u043d\u044c \u0432 20:00.<\/p>\n<p>\u0421\u043a\u0440\u0438\u043f\u0442 artisan.php \u2014 \u044d\u0442\u043e \u0441\u0430\u043c\u043e\u0441\u0442\u043e\u044f\u0442\u0435\u043b\u044c\u043d\u0430\u044f \u0442\u043e\u0447\u043a\u0430 \u0432\u0445\u043e\u0434\u0430, \u043a\u043e\u0442\u043e\u0440\u0430\u044f:<\/p>\n<ul>\n<li>\n<p>\u0417\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u0442 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0435 \u043a\u043b\u0430\u0441\u0441\u044b \u0447\u0435\u0440\u0435\u0437 autoload.php<\/p>\n<\/li>\n<li>\n<p>\u0421\u043e\u0437\u0434\u0430\u0451\u0442 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0439 \u0438 \u0441\u0435\u0440\u0432\u0438\u0441\u044b<\/p>\n<\/li>\n<li>\n<p>\u041f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 \u043c\u0435\u0442\u0440\u0438\u043a\u0438 \u0437\u0430 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0435 24 \u0447\u0430\u0441\u0430<\/p>\n<\/li>\n<li>\n<p>\u0424\u043e\u0440\u043c\u0438\u0440\u0443\u0435\u0442 \u043e\u0442\u0447\u0451\u0442<\/p>\n<\/li>\n<li>\n<p>\u041e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442 \u0435\u0433\u043e \u0432 Telegram \u0447\u0435\u0440\u0435\u0437 \u0431\u043e\u0442<\/p>\n<\/li>\n<li>\n<p>\u0412\u044b\u0432\u043e\u0434\u0438\u0442 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 \u0432 \u043a\u043e\u043d\u0441\u043e\u043b\u044c<\/p>\n<\/li>\n<\/ul>\n<pre><code class=\"php\">&lt;?php   require_once __DIR__ . '\/vendor\/autoload.php';   use Dotenv\\Dotenv; use App\\Repository\\CallRepository; use App\\Service\\ReportService; use App\\Service\\ReportSenderService;   $dotenv = Dotenv\\Dotenv::createImmutable(__DIR__); $dotenv-&gt;load();   \/\/ \u0421\u043e\u0437\u0434\u0430\u0451\u043c \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0439 \u0438 \u0441\u0435\u0440\u0432\u0438\u0441\u044b $callRepository = new CallRepository(); $reportService = new ReportService($callRepository); $reportSender = new ReportSenderService();   try {    \/\/ \u041f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u043c\u0435\u0442\u0440\u0438\u043a\u0438 \u0437\u0430 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0435 24 \u0447\u0430\u0441\u0430    $metrics = $callRepository-&gt;getOperatorMetrics();      \/\/ \u0424\u043e\u0440\u043c\u0438\u0440\u0443\u0435\u043c \u0442\u0435\u043a\u0441\u0442 \u043e\u0442\u0447\u0451\u0442\u0430    $reportText = $reportService-&gt;formatReport($metrics);      \/\/ \u041e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u043c \u0432 Telegram    $sent = $reportSender-&gt;sendMessage($reportText);      if ($sent) {        echo \"\u041e\u0442\u0447\u0451\u0442 \u0443\u0441\u043f\u0435\u0448\u043d\u043e \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u0435\u043d.\\n\";    } else {        echo \"\u041e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0435 \u043e\u0442\u0447\u0451\u0442\u0430.\\n\";    } } catch (Throwable $e) {    echo \"\u041e\u0448\u0438\u0431\u043a\u0430: \" . $e-&gt;getMessage() . \"\\n\"; } <\/code><\/pre>\n<p>\u0421\u043e\u0437\u0434\u0430\u0451\u043c \u0444\u0430\u0439\u043b cron\/report.sh \u0441\u043e \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u044b\u043c:<\/p>\n<pre><code class=\"php\">#!\/bin\/bash php \/var\/www\/html\/artisan.php<\/code><\/pre>\n<p>\u0427\u0442\u043e\u0431\u044b \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0442\u044c \u0441\u043a\u0440\u0438\u043f\u0442 \u0435\u0436\u0435\u0434\u043d\u0435\u0432\u043d\u043e \u0432 20:00, \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0443\u044e \u0441\u0442\u0440\u043e\u043a\u0443 \u0432 crontab \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f www-data. \u041e\u0442\u043a\u0440\u044b\u0432\u0430\u0435\u043c \u0440\u0435\u0434\u0430\u043a\u0442\u043e\u0440 crontab \u0438 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u0441\u0442\u0440\u043e\u043a\u0443:<\/p>\n<pre><code class=\"php\">0 20 * * * \/var\/www\/html\/cron\/report.sh<\/code><\/pre>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043e\u0442\u0447\u0451\u0442 \u0444\u043e\u0440\u043c\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0432 Telegram \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u043a\u0430\u0436\u0434\u044b\u0439 \u0434\u0435\u043d\u044c \u0432 20:00.<\/p>\n<h4>\u0421\u043a\u0440\u0438\u043d\u0448\u043e\u0442 \u043e\u0442\u0447\u0451\u0442\u0430 \u0432 Telegram<\/h4>\n<figure class=\"\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/095\/7af\/d2d\/0957afd2d68eb85f660ae21e846058fb.png\" width=\"493\" height=\"210\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/095\/7af\/d2d\/0957afd2d68eb85f660ae21e846058fb.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/095\/7af\/d2d\/0957afd2d68eb85f660ae21e846058fb.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/figure>\n<h3>\u0417\u0430\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435<\/h3>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043a\u0430\u0436\u0434\u044b\u0439 \u0437\u0430\u0432\u0435\u0440\u0448\u0451\u043d\u043d\u044b\u0439 \u0437\u0432\u043e\u043d\u043e\u043a \u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438, \u0430\u043d\u0430\u043b\u0438\u0442\u0438\u043a\u0430 \u043f\u043e \u0440\u0435\u0447\u0438 \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u0442\u0441\u044f \u0432 \u0431\u0430\u0437\u0443, \u0430 \u0432 20:00 \u0444\u043e\u0440\u043c\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u043e\u0442\u0447\u0451\u0442 \u0441 \u043a\u043b\u044e\u0447\u0435\u0432\u044b\u043c\u0438 \u043c\u0435\u0442\u0440\u0438\u043a\u0430\u043c\u0438 \u043f\u043e \u043a\u0430\u0436\u0434\u043e\u043c\u0443 \u043e\u043f\u0435\u0440\u0430\u0442\u043e\u0440\u0443. \u0413\u043e\u0442\u043e\u0432\u044b\u0439 \u043e\u0442\u0447\u0451\u0442 \u0443\u0445\u043e\u0434\u0438\u0442 \u0432 Telegram. \u0412\u0441\u0451 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0444\u043e\u043d\u043e\u043c \u0438 \u0442\u0440\u0435\u0431\u0443\u0435\u0442 \u043c\u0438\u043d\u0438\u043c\u0443\u043c \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0438.<\/p>\n<p>\u0420\u0435\u0448\u0435\u043d\u0438\u0435 \u043f\u043e\u043b\u0435\u0437\u043d\u043e\u0435 \u0438 \u043f\u0440\u043e\u0441\u0442\u043e\u0435 \u0432 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0435: \u043d\u0438\u043a\u0430\u043a\u043e\u0439 \u0442\u044f\u0436\u0451\u043b\u043e\u0439 \u0438\u043d\u0444\u0440\u0430\u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u044b, \u0442\u043e\u043b\u044c\u043a\u043e PHP, MySQL \u0438 cron. \u042d\u0442\u043e \u044d\u043a\u043e\u043d\u043e\u043c\u0438\u0442 \u0432\u0440\u0435\u043c\u044f \u0440\u0443\u043a\u043e\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044f \u0438 \u0434\u0430\u0451\u0442 \u043e\u0431\u044a\u0435\u043a\u0442\u0438\u0432\u043d\u0443\u044e \u043a\u0430\u0440\u0442\u0438\u043d\u0443 \u043f\u043e \u043a\u0430\u0436\u0434\u043e\u043c\u0443 \u043e\u043f\u0435\u0440\u0430\u0442\u043e\u0440\u0443. \u041a\u043e\u0434 \u2014 <a href=\"https:\/\/github.com\/duckdevdotdev\/postprod-article-jul2025-common-speech-analytics\" rel=\"noopener noreferrer nofollow\">\u0432 \u0433\u0438\u0442\u0445\u0430\u0431\u0435<\/a>.<\/p>\n<h4>\u0418\u0434\u0435\u0438 \u0434\u043b\u044f \u0440\u0430\u0437\u0432\u0438\u0442\u0438\u044f<\/h4>\n<ol>\n<li>\n<p>\u0421\u043e\u0437\u0434\u0430\u0442\u044c \u0430\u043d\u0430\u043b\u0438\u0442\u0438\u043a\u0443 \u043f\u043e \u043a\u0430\u0436\u0434\u043e\u043c\u0443 \u043e\u043f\u0435\u0440\u0430\u0442\u043e\u0440\u0443, \u043f\u043e\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u043b\u0438\u0434\u0435\u0440\u0431\u043e\u0440\u0434 \u0438 \u0432\u044b\u0432\u0435\u0441\u0442\u0438 \u043d\u0430 \u044d\u043a\u0440\u0430\u043d \u0438\u043b\u0438 \u0440\u0430\u0441\u0441\u044b\u043b\u0430\u0442\u044c \u0432\u0441\u0435\u043c \u043e\u043f\u0435\u0440\u0430\u0442\u043e\u0440\u0430\u043c.<\/p>\n<\/li>\n<li>\n<p>\u0421\u0432\u044f\u0437\u0430\u0442\u044c \u0441 \u043c\u0435\u0442\u0440\u0438\u043a\u0430\u043c\u0438 \u0443\u0441\u043f\u0435\u0445\u0430 \u043f\u0440\u043e\u0434\u0430\u0436, \u043e\u0446\u0435\u043d\u043a\u0438 \u043a\u043b\u0438\u0435\u043d\u0442\u043e\u0432 \u0438\u043b\u0438 \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u044b\u0445 \u043e\u0431\u0440\u0430\u0449\u0435\u043d\u0438\u0439 \u043d\u0430 \u0442\u0443 \u0436\u0435 \u0442\u0435\u043c\u0443. \u0422\u0430\u043a \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u0441\u044f \u0432\u044b\u044f\u0432\u0438\u0442\u044c \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0438\u0432\u043d\u044b\u0435 \u043f\u0430\u0442\u0442\u0435\u0440\u043d\u044b \u043f\u043e\u0432\u0435\u0434\u0435\u043d\u0438\u044f.<\/p>\n<\/li>\n<li>\n<p>\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0430\u043d\u0430\u043b\u0438\u0437 \u0440\u0430\u0441\u0448\u0438\u0444\u0440\u043e\u0432\u043e\u043a \u0447\u0435\u0440\u0435\u0437 \u0432\u043d\u0435\u0448\u043d\u044e\u044e LLM \u0434\u043b\u044f \u0431\u043e\u043b\u0435\u0435 \u0433\u043b\u0443\u0431\u043e\u043a\u043e\u0433\u043e \u043f\u043e\u043d\u0438\u043c\u0430\u043d\u0438\u044f. \u0422\u0430\u043a\u043e\u0439 \u043f\u0440\u0438\u043c\u0435\u0440 \u043c\u044b \u0440\u0430\u0437\u0431\u0438\u0440\u0430\u043b\u0438 \u0432 \u0434\u0440\u0443\u0433\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435.<\/p>\n<\/li>\n<\/ol>\n<\/div>\n<\/div>\n<\/div>\n<p><!----><!----><\/div>\n<p><!----><!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/940790\/\"> https:\/\/habr.com\/ru\/articles\/940790\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>\u041f\u0440\u0438\u0432\u0435\u0442, \u0425\u0430\u0431\u0440! \u0421\u0435\u0433\u043e\u0434\u043d\u044f \u043f\u043e\u043a\u0430\u0436\u0435\u043c, \u043a\u0430\u043a \u0431\u0443\u043a\u0432\u0430\u043b\u044c\u043d\u043e \u0437\u0430 \u043f\u0430\u0440\u0443 \u0432\u0435\u0447\u0435\u0440\u043e\u0432 \u0441\u043e\u0431\u0440\u0430\u0442\u044c \u0441\u0438\u0441\u0442\u0435\u043c\u0443, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0440\u0430\u0441\u0448\u0438\u0444\u0440\u043e\u0432\u044b\u0432\u0430\u0435\u0442 \u0437\u0432\u043e\u043d\u043a\u0438, \u0430\u043d\u0430\u043b\u0438\u0437\u0438\u0440\u0443\u0435\u0442 \u0440\u0435\u0447\u044c \u043e\u043f\u0435\u0440\u0430\u0442\u043e\u0440\u043e\u0432 \u0438 \u043f\u0440\u0438\u0441\u044b\u043b\u0430\u0435\u0442 \u0440\u0443\u043a\u043e\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044e \u043e\u0442\u0447\u0451\u0442 \u0432 Telegram.<\/p>\n<p>\u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0432 \u043a\u043e\u043b-\u0446\u0435\u043d\u0442\u0440\u0435 \u0441 15 \u043e\u043f\u0435\u0440\u0430\u0442\u043e\u0440\u0430\u043c\u0438 \u0442\u0430\u043a\u0430\u044f \u0441\u0432\u043e\u0434\u043a\u0430 \u043f\u043e\u043c\u043e\u0436\u0435\u0442 \u0440\u0443\u043a\u043e\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044e \u0431\u044b\u0441\u0442\u0440\u043e \u043f\u043e\u043d\u044f\u0442\u044c, \u043a\u0442\u043e \u043f\u0435\u0440\u0435\u0433\u0440\u0443\u0436\u0435\u043d, \u0433\u0434\u0435 \u0447\u0430\u0449\u0435 \u0437\u0432\u0443\u0447\u0438\u0442 \u043d\u0435\u0433\u0430\u0442\u0438\u0432, \u0430 \u043a\u0442\u043e \u043f\u0440\u043e\u0441\u0442\u043e \u0441\u043b\u0438\u0448\u043a\u043e\u043c \u043c\u043d\u043e\u0433\u043e \u0433\u043e\u0432\u043e\u0440\u0438\u0442. \u041d\u0435 \u043d\u0430\u0434\u043e \u0441\u043b\u0443\u0448\u0430\u0442\u044c \u0437\u0430\u043f\u0438\u0441\u0438 \u2014 \u043e\u0442\u0447\u0451\u0442 \u0441\u0430\u043c \u0432\u0441\u0451 \u0440\u0430\u0441\u0441\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442.<\/p>\n<p>\ud83d\udcca \u041e\u0442\u0447\u0451\u0442 \u0437\u0430 19 \u0438\u044e\u043b\u044f<br \/>\ud83c\udfa7 \u041e\u043f\u0435\u0440\u0430\u0442\u043e\u0440 \u0434\u043d\u044f: \u0418\u0432\u0430\u043d \u0418\u0432\u0430\u043d\u043e\u0432 (emotionScore: 0.42)<br \/>\ud83e\udd75 \u0411\u043e\u043b\u044c\u0448\u0435 \u0432\u0441\u0435\u0433\u043e \u043d\u0435\u0433\u0430\u0442\u0438\u0432\u0430: \u042e\u043b\u0438\u044f \u0422\u0435\u0441\u0442\u043e\u0432\u0430 (33%)<br \/>\ud83d\udde3\ufe0f \u0421\u0440\u0435\u0434\u043d\u044f\u044f \u0441\u043a\u043e\u0440\u043e\u0441\u0442\u044c \u0440\u0435\u0447\u0438: 132 \u0441\u043b\u043e\u0432\/\u043c\u0438\u043d<br \/>\ud83e\udd2f \u0421\u0430\u043c\u044b\u0439 \u00ab\u0433\u043e\u0432\u043e\u0440\u044f\u0449\u0438\u0439\u00bb: \u0410\u043d\u0434\u0440\u0435\u0439 \u041c\u0430\u043a\u0441\u0438\u043c\u043e\u0432 (74% \u0432\u0440\u0435\u043c\u0435\u043d\u0438)<br \/>\ud83d\udea8 \u041f\u0435\u0440\u0435\u0431\u0438\u0432\u0430\u043d\u0438\u0439 \u0432 \u0441\u0440\u0435\u0434\u043d\u0435\u043c: 2,7 \u043d\u0430 \u0437\u0432\u043e\u043d\u043e\u043a<\/p>\n<h3>\u041a\u0430\u043a \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0441\u0438\u0441\u0442\u0435\u043c\u0430<\/h3>\n<p>\u0420\u0435\u0448\u0435\u043d\u0438\u0435 \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u043e \u043d\u0430 PHP \u0438 MySQL, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 cron \u0438 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f \u043a API \u041c\u0422\u0421 Exolve. \u0414\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0437\u0430\u0432\u0435\u0440\u0448\u0451\u043d\u043d\u043e\u0433\u043e \u0437\u0432\u043e\u043d\u043a\u0430 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u0441\u043e\u0431\u0438\u0440\u0430\u044e\u0442\u0441\u044f:<\/p>\n<ul>\n<li>\n<p>\u0420\u0430\u0441\u0448\u0438\u0444\u0440\u043e\u0432\u043a\u0430 \u0434\u0438\u0430\u043b\u043e\u0433\u0430 \u2014 \u043a\u0442\u043e \u0438 \u0447\u0442\u043e \u0441\u043a\u0430\u0437\u0430\u043b<\/p>\n<\/li>\n<li>\n<p>\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0430\u043d\u0430\u043b\u0438\u0437\u0430 \u0440\u0435\u0447\u0438 \u2014 \u044d\u043c\u043e\u0446\u0438\u0438, \u0441\u043e\u043e\u0442\u043d\u043e\u0448\u0435\u043d\u0438\u0435 \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0438 \u0440\u0435\u0447\u0438 \u0438 \u0442\u0438\u0448\u0438\u043d\u044b, \u0441\u043a\u043e\u0440\u043e\u0441\u0442\u044c, \u043f\u0435\u0440\u0435\u0431\u0438\u0432\u0430\u043d\u0438\u044f \u0438 \u0434\u0440\u0443\u0433\u0438\u0435 \u043f\u043e\u043a\u0430\u0437\u0430\u0442\u0435\u043b\u0438<\/p>\n<\/li>\n<\/ul>\n<p>\u041d\u0430 \u043e\u0441\u043d\u043e\u0432\u0435 \u044d\u0442\u0438\u0445 \u0434\u0430\u043d\u043d\u044b\u0445 \u0441\u0438\u0441\u0442\u0435\u043c\u0430:<\/p>\n<ul>\n<li>\n<p>\u0421\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u0442 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u0432 \u0431\u0430\u0437\u0443 \u0434\u0430\u043d\u043d\u044b\u0445<\/p>\n<\/li>\n<li>\n<p>\u0420\u0430\u0437 \u0432 \u0441\u0443\u0442\u043a\u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442 \u0441\u043a\u0440\u0438\u043f\u0442<\/p>\n<\/li>\n<li>\n<p>\u0420\u0430\u0441\u0441\u0447\u0438\u0442\u044b\u0432\u0430\u0435\u0442 \u043c\u0435\u0442\u0440\u0438\u043a\u0438 \u043f\u043e \u043a\u0430\u0436\u0434\u043e\u043c\u0443 \u043e\u043f\u0435\u0440\u0430\u0442\u043e\u0440\u0443:<\/p>\n<ul>\n<li>\n<p>\u0443\u0440\u043e\u0432\u0435\u043d\u044c \u044d\u043c\u043e\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u043e\u0441\u0442\u0438 \u2014 emotionScore<\/p>\n<\/li>\n<li>\n<p>\u0441\u0440\u0435\u0434\u043d\u044e\u044e \u0441\u043a\u043e\u0440\u043e\u0441\u0442\u044c \u0440\u0435\u0447\u0438<\/p>\n<\/li>\n<li>\n<p>\u0440\u0435\u0447\u0435\u0432\u043e\u0439 \u0431\u0430\u043b\u0430\u043d\u0441 \u2014 \u043a\u0442\u043e \u0433\u043e\u0432\u043e\u0440\u0438\u043b \u0431\u043e\u043b\u044c\u0448\u0435<\/p>\n<\/li>\n<li>\n<p>\u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u043f\u0435\u0440\u0435\u0431\u0438\u0432\u0430\u043d\u0438\u0439<\/p>\n<\/li>\n<\/ul>\n<\/li>\n<li>\n<p>\u0410\u043d\u0430\u043b\u0438\u0437\u0438\u0440\u0443\u0435\u0442 \u043e\u0431\u0449\u0438\u0439 \u0443\u0440\u043e\u0432\u0435\u043d\u044c \u043d\u0435\u0433\u0430\u0442\u0438\u0432\u0430<\/p>\n<\/li>\n<li>\n<p>\u041e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442 \u0438\u0442\u043e\u0433\u043e\u0432\u044b\u0439 \u043e\u0442\u0447\u0451\u0442 \u0432 Telegram<\/p>\n<\/li>\n<\/ul>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u0440\u0430\u0437\u0431\u0435\u0440\u0451\u043c \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u0435\u0435.<\/p>\n<h3>\u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043f\u0440\u043e\u0435\u043a\u0442\u0430<\/h3>\n<p>\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u043c \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0443 \u043f\u0440\u043e\u0435\u043a\u0442\u0430, \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u043c \u0430\u0432\u0442\u043e\u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0443 \u0447\u0435\u0440\u0435\u0437 Composer, \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u043c \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438 \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 .env \u0438 HTTP-\u0437\u0430\u043f\u0440\u043e\u0441\u0430\u043c\u0438, \u043f\u0440\u043e\u043f\u0438\u0448\u0435\u043c \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f, \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0431\u0430\u0437\u0443 \u0434\u0430\u043d\u043d\u044b\u0445 \u0438 \u0442\u0430\u0431\u043b\u0438\u0446\u044b \u0434\u043b\u044f \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u043e\u043f\u0435\u0440\u0430\u0442\u043e\u0440\u043e\u0432 \u0438 \u0437\u0432\u043e\u043d\u043a\u043e\u0432.<\/p>\n<p>\u0421\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430, \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438, .env \u0438 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u0411\u0414 \u25bc<\/p>\n<details class=\"spoiler\">\n<summary>\u0421\u043a\u0440\u044b\u0442\u044b\u0439 \u0442\u0435\u043a\u0441\u0442<\/summary>\n<div class=\"spoiler__content\">\n<h3>\u0421\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u043f\u0440\u043e\u0435\u043a\u0442\u0430<\/h3>\n<pre><code class=\"php\">voice_analytics\/ \u251c\u2500\u2500 app\/ \u2502   \u251c\u2500\u2500 Infrastructure\/ \u2502   \u2502   \u2514\u2500\u2500 Database.php            # \u041e\u0431\u0451\u0440\u0442\u043a\u0430 \u043d\u0430\u0434 PDO. \u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a MySQL, Singleton \u2502   \u251c\u2500\u2500 Repository\/ \u2502   \u2502   \u251c\u2500\u2500 CallRepository.php      # \u0420\u0430\u0431\u043e\u0442\u0430 \u0441 \u0442\u0430\u0431\u043b\u0438\u0446\u0435\u0439 \u0437\u0432\u043e\u043d\u043a\u043e\u0432 (\u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435, \u043c\u0435\u0442\u0440\u0438\u043a\u0438) \u2502   \u2502   \u2514\u2500\u2500 OperatorRepository.php  # \u041f\u043e\u0438\u0441\u043a \u043e\u043f\u0435\u0440\u0430\u0442\u043e\u0440\u0430 \u043f\u043e \u043d\u043e\u043c\u0435\u0440\u0443 \u2502   \u251c\u2500\u2500 Service\/ \u2502   \u2502   \u251c\u2500\u2500 CallService.php         # \u041b\u043e\u0433\u0438\u043a\u0430 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0437\u0432\u043e\u043d\u043a\u043e\u0432 \u2502   \u2502   \u251c\u2500\u2500 ExolveApiService.php    # \u0418\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f \u0441 \u0432\u043d\u0435\u0448\u043d\u0438\u043c API Exolve \u2502   \u2502   \u251c\u2500\u2500 ReportService.php       # \u0413\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u044f \u043c\u0435\u0442\u0440\u0438\u043a \u043f\u043e \u043e\u043f\u0435\u0440\u0430\u0442\u043e\u0440\u0430\u043c \u2502   \u2502   \u2514\u2500\u2500 ReportSenderService.php # \u041e\u0442\u043f\u0440\u0430\u0432\u043a\u0430 \u043e\u0442\u0447\u0451\u0442\u0430 \u0432 Telegram \u251c\u2500\u2500 cron\/ \u2502   \u2514\u2500\u2500 send_report.sh              # Shell-\u0441\u043a\u0440\u0438\u043f\u0442 \u0434\u043b\u044f \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u043e\u0442\u0447\u0451\u0442\u0430 \u251c\u2500\u2500 artisan.php                     # \u0422\u043e\u0447\u043a\u0430 \u0432\u0445\u043e\u0434\u0430 CLI-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u251c\u2500\u2500 dump.sql                        # SQL-\u0434\u0430\u043c\u043f: \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u0431\u0430\u0437\u044b \u0434\u0430\u043d\u043d\u044b\u0445 \u251c\u2500\u2500 composer.json                   # Composer-\u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u0438 \u0430\u0432\u0442\u043e\u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0430 \u2514\u2500\u2500 .env                            # \u041a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f (\u0411\u0414, \u043a\u043b\u044e\u0447\u0438 API, Telegram) <\/code><\/pre>\n<h4>\u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0438 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0435\u0439<\/h4>\n<p>\u0421\u043e\u0437\u0434\u0430\u0451\u043c \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0443 \u043f\u0440\u043e\u0435\u043a\u0442\u0430, \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u0443\u0435\u043c Composer \u0438 \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438 \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u043c\u0438 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f \u0438 API-\u0437\u0430\u043f\u0440\u043e\u0441\u0430\u043c\u0438.<\/p>\n<p>\u0421\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u0438 \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f Composer:<\/p>\n<pre><code class=\"php\">mkdir voice_analytics cd voice_analytics composer init composer require vlucas\/phpdotenv guzzlehttp\/guzzle<\/code><\/pre>\n<p>Composer.json:<\/p>\n<pre><code class=\"php\">{    \"autoload\": {        \"psr-4\": {            \"App\\\\\": \"app\/\"        }    },    \"require\": {        \"vlucas\/phpdotenv\": \"^5.6\",        \"guzzlehttp\/guzzle\": \"^7.9\"    } }<\/code><\/pre>\n<p>\u0413\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u044f \u0430\u0432\u0442\u043e\u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438:<\/p>\n<pre><code class=\"php\">composer dump-autoload<\/code><\/pre>\n<h4>\u041a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0445 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f<\/h4>\n<p>\u0421\u043e\u0437\u0434\u0430\u0451\u043c \u0444\u0430\u0439\u043b .env \u0432 \u043a\u043e\u0440\u043d\u0435 \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0438 \u0437\u0430\u043f\u043e\u043b\u043d\u044f\u0435\u043c \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435 \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u043a MySQL, API-\u043a\u043b\u044e\u0447 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u041c\u0422\u0421 Exolve, \u0442\u043e\u043a\u0435\u043d \u0434\u043b\u044f \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u043a \u0442\u0435\u043b\u0435\u0433\u0440\u0430\u043c-\u0431\u043e\u0442\u0443 \u0438 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440 \u0447\u0430\u0442\u0430, \u043a\u0443\u0434\u0430 \u0431\u0443\u0434\u0435\u0442 \u043f\u0440\u0438\u0445\u043e\u0434\u0438\u0442\u044c \u043e\u0442\u0447\u0451\u0442.<\/p>\n<pre><code class=\"php\">DB_HOST=\u0432\u0430\u0448 \u0445\u043e\u0441\u0442 DB_PORT=\u0432\u0430\u0448 \u043f\u043e\u0440\u0442 DB_NAME=voice_analytics DB_USER=root DB_PASS=root   EXOLVE_API_KEY=\u0432\u0430\u0448_\u043a\u043b\u044e\u0447 TELEGRAM_BOT_TOKEN=\u0442\u043e\u043a\u0435\u043d_\u0432\u0430\u0448\u0435\u0433\u043e_\u0431\u043e\u0442\u0430 TELEGRAM_CHAT_ID=id_\u0432\u0430\u0448\u0435\u0433\u043e_\u0447\u0430\u0442\u0430<\/code><\/pre>\n<h4>\u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u0431\u0430\u0437\u044b \u0434\u0430\u043d\u043d\u044b\u0445 \u0438 \u0442\u0430\u0431\u043b\u0438\u0446\u044b<\/h4>\n<p>\u0414\u043b\u044f \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0440\u0430\u0441\u0448\u0438\u0444\u0440\u043e\u0432\u043e\u043a \u0437\u0432\u043e\u043d\u043a\u043e\u0432 \u0438 \u0440\u0430\u0441\u0447\u0451\u0442\u0430 \u043c\u0435\u0442\u0440\u0438\u043a \u043d\u0430\u043c \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u044f\u0442\u0441\u044f \u0434\u0432\u0435 \u0442\u0430\u0431\u043b\u0438\u0446\u044b: \u043e\u0434\u043d\u0430 \u2014 \u0441 \u043e\u043f\u0435\u0440\u0430\u0442\u043e\u0440\u0430\u043c\u0438, \u0434\u0440\u0443\u0433\u0430\u044f \u2014 \u0441 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430\u043c\u0438 \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0437\u0432\u043e\u043d\u043a\u0430. \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u043c\u0438\u043d\u0438\u043c\u0430\u043b\u044c\u043d\u0443\u044e, \u043d\u043e \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u0443\u044e \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0443. \u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0435\u043c\u0441\u044f \u043a \u0441\u0432\u043e\u0435\u0439 MySQL \u0438 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u043c \u0447\u0435\u0442\u044b\u0440\u0435 \u043a\u043e\u043c\u0430\u043d\u0434\u044b:<\/p>\n<pre><code class=\"php\">CREATE DATABASE voice_analytics CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE voice_analytics;   -- \u0422\u0430\u0431\u043b\u0438\u0446\u0430 \u043e\u043f\u0435\u0440\u0430\u0442\u043e\u0440\u043e\u0432 CREATE TABLE operators (    id    INT AUTO_INCREMENT PRIMARY KEY,    name  VARCHAR(100) NOT NULL,    phone VARCHAR(20)  NOT NULL UNIQUE );   -- \u0422\u0430\u0431\u043b\u0438\u0446\u0430 \u0437\u0432\u043e\u043d\u043a\u043e\u0432 CREATE TABLE calls (    id                       INT AUTO_INCREMENT PRIMARY KEY,    call_id                  VARCHAR(100) NOT NULL UNIQUE,    operator_id              INT,    call_date                DATETIME     NOT NULL,    emotion_score            DECIMAL(4, 2),    speech_rate              DECIMAL(5, 2),    speech_duration_operator INT DEFAULT 0,    speech_duration_client   INT DEFAULT 0,    interruptions            INT DEFAULT 0,    sentiment                VARCHAR(20),    transcript_text          TEXT,    FOREIGN KEY (operator_id) REFERENCES operators (id) ON DELETE SET NULL ); <\/code><\/pre>\n<\/div>\n<\/details>\n<h3>\u041a\u0430\u043a \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435<\/h3>\n<p>\u0412 \u044d\u0442\u043e\u0439 \u0447\u0430\u0441\u0442\u0438 \u0440\u0430\u0437\u0431\u0435\u0440\u0451\u043c \u043a\u043b\u044e\u0447\u0435\u0432\u044b\u0435 \u043c\u043e\u0434\u0443\u043b\u0438, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0441\u043e\u0431\u0438\u0440\u0430\u044e\u0442 \u0430\u043d\u0430\u043b\u0438\u0442\u0438\u043a\u0443 \u043f\u043e \u0437\u0432\u043e\u043d\u043a\u0430\u043c \u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u044e\u0442 \u043e\u0442\u0447\u0451\u0442. \u041a\u043e\u0434 \u0440\u0430\u0437\u0434\u0435\u043b\u0451\u043d \u043f\u043e \u0441\u043b\u043e\u044f\u043c:<\/p>\n<ul>\n<li>\n<p>Infrastructure \u2014 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a \u0431\u0430\u0437\u0435 \u0434\u0430\u043d\u043d\u044b\u0445<\/p>\n<\/li>\n<li>\n<p>Repository \u2014 \u0434\u043e\u0441\u0442\u0443\u043f \u043a \u0442\u0430\u0431\u043b\u0438\u0446\u0430\u043c \u0437\u0432\u043e\u043d\u043a\u043e\u0432 \u0438 \u043e\u043f\u0435\u0440\u0430\u0442\u043e\u0440\u043e\u0432<\/p>\n<\/li>\n<li>\n<p>Service \u2014 \u043b\u043e\u0433\u0438\u043a\u0430 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0437\u0432\u043e\u043d\u043a\u043e\u0432, \u0432\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435 \u0441 API \u0438 \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u044f \u043e\u0442\u0447\u0451\u0442\u043e\u0432<\/p>\n<\/li>\n<li>\n<p>artisan.php \u2014 \u0442\u043e\u0447\u043a\u0430 \u0432\u0445\u043e\u0434\u0430 \u0434\u043b\u044f \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u0432\u0440\u0443\u0447\u043d\u0443\u044e \u0438\u043b\u0438 \u0447\u0435\u0440\u0435\u0437 cron<\/p>\n<\/li>\n<li>\n<p>cron\/ \u2014 \u043e\u0431\u0451\u0440\u0442\u043a\u0430 \u0434\u043b\u044f \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0433\u043e \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u043e\u0442\u0447\u0451\u0442\u0430 \u043f\u043e \u0440\u0430\u0441\u043f\u0438\u0441\u0430\u043d\u0438\u044e<\/p>\n<\/li>\n<\/ul>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043f\u0440\u043e\u0439\u0434\u0451\u043c\u0441\u044f \u043f\u043e \u043a\u0430\u0436\u0434\u043e\u043c\u0443 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0443.<\/p>\n<h4>\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a \u0431\u0430\u0437\u0435 \u0434\u0430\u043d\u043d\u044b\u0445<\/h4>\n<p>\u0424\u0430\u0439\u043b: app\/Infrastructure\/Database.php<\/p>\n<p>\u041a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 Database \u0441\u043e\u0437\u0434\u0430\u0451\u0442 \u0438 \u043f\u0435\u0440\u0435\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a MySQL \u0447\u0435\u0440\u0435\u0437 PDO. \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0448\u0430\u0431\u043b\u043e\u043d Singleton, \u0432\u0441\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0431\u0435\u0440\u0443\u0442\u0441\u044f \u0438\u0437 .env.<\/p>\n<pre><code class=\"php\">&lt;?php   namespace app\\Infrastructure;   use PDO; use PDOException; use RuntimeException;   final class Database {    private static ?PDO $pdo = null;      private function __construct()    {    }      public static function getConnection(): PDO    {        if (self::$pdo === null) {            $host = getenv('DB_HOST') ?? null;            $port = getenv('DB_PORT') ?? '3306';            $db = getenv('DB_NAME') ?? null;            $user = getenv('DB_USER') ?? null;            $pass = getenv('DB_PASS') ?? null;              if (!$host || !$db || !$user) {                throw new RuntimeException('\u041a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f \u0431\u0430\u0437\u044b \u0434\u0430\u043d\u043d\u044b\u0445 \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u0432 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0445 \u0441\u0440\u0435\u0434\u044b.');            }              $dsn = sprintf(                'mysql:host=%s;port=%s;dbname=%s;charset=utf8mb4',                $host,                $port,                $db            );              try {                self::$pdo = new PDO(                    $dsn,                    $user,                    $pass,                    [                        PDO::ATTR_ERRMODE =&gt; PDO::ERRMODE_EXCEPTION,                        PDO::ATTR_DEFAULT_FETCH_MODE =&gt; PDO::FETCH_ASSOC,                        PDO::ATTR_EMULATE_PREPARES =&gt; false,                    ]                );            } catch (PDOException $e) {                throw new RuntimeException('\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a \u0431\u0430\u0437\u0435 \u0434\u0430\u043d\u043d\u044b\u0445 \u043d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c: ' . $e-&gt;getMessage());            }        }          return self::$pdo;    } } <\/code><\/pre>\n<h4>\u0420\u0430\u0431\u043e\u0442\u0430 \u0441 \u0434\u0430\u043d\u043d\u044b\u043c\u0438<\/h4>\n<p>\u0421\u043e\u0441\u0442\u043e\u0438\u0442 \u0438\u0437 \u0434\u0432\u0443\u0445 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u043e\u0432: \u043e\u0434\u0438\u043d \u043e\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u0437\u0430 \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435 \u0430\u043d\u0430\u043b\u0438\u0442\u0438\u043a\u0438 \u0437\u0432\u043e\u043d\u043a\u043e\u0432 \u0438 \u0440\u0430\u0441\u0447\u0451\u0442 \u043c\u0435\u0442\u0440\u0438\u043a \u0434\u043b\u044f \u043e\u0442\u0447\u0451\u0442\u043e\u0432, \u0434\u0440\u0443\u0433\u043e\u0439 \u2014 \u0437\u0430 \u043f\u043e\u0438\u0441\u043a \u043e\u043f\u0435\u0440\u0430\u0442\u043e\u0440\u0430 \u043f\u043e \u043d\u043e\u043c\u0435\u0440\u0443 \u0442\u0435\u043b\u0435\u0444\u043e\u043d\u0430. \u041f\u0440\u043e\u0441\u0442\u0430\u044f \u043e\u0431\u0432\u044f\u0437\u043a\u0430 \u043f\u043e\u0432\u0435\u0440\u0445 \u0431\u0430\u0437\u044b, \u0431\u0435\u0437 \u043b\u0438\u0448\u043d\u0435\u0439 \u043b\u043e\u0433\u0438\u043a\u0438.<\/p>\n<h3>\u0421\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435 \u0437\u0432\u043e\u043d\u043a\u043e\u0432 \u0438 \u0440\u0430\u0441\u0447\u0451\u0442 \u043c\u0435\u0442\u0440\u0438\u043a<\/h3>\n<p>\u0424\u0430\u0439\u043b: app\/Repository\/CallRepository.php<\/p>\n<p>\u042d\u0442\u043e \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0439 \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u0442\u0430\u0431\u043b\u0438\u0446\u0435\u0439 calls \u0432 \u0431\u0430\u0437\u0435 \u0434\u0430\u043d\u043d\u044b\u0445. \u0412 \u043d\u0451\u043c \u0445\u0440\u0430\u043d\u044f\u0442\u0441\u044f \u0434\u0430\u043d\u043d\u044b\u0435 \u043f\u043e \u043a\u0430\u0436\u0434\u043e\u043c\u0443 \u0437\u0430\u0432\u0435\u0440\u0448\u0451\u043d\u043d\u043e\u043c\u0443 \u0437\u0432\u043e\u043d\u043a\u0443 \u0438 \u0441\u043e\u0431\u0438\u0440\u0430\u044e\u0442\u0441\u044f \u043c\u0435\u0442\u0440\u0438\u043a\u0438 \u043f\u043e \u043e\u043f\u0435\u0440\u0430\u0442\u043e\u0440\u0430\u043c \u0437\u0430 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0435 24 \u0447\u0430\u0441\u0430: \u044d\u043c\u043e\u0446\u0438\u0438, \u043f\u0435\u0440\u0435\u0431\u0438\u0432\u0430\u043d\u0438\u044f, \u0440\u0435\u0447\u0435\u0432\u043e\u0439 \u0431\u0430\u043b\u0430\u043d\u0441 \u0438 \u0434\u043e\u043b\u044f \u043d\u0435\u0433\u0430\u0442\u0438\u0432\u0430.<\/p>\n<pre><code class=\"php\">&lt;?php   namespace app\\Repository;   use app\\Infrastructure\\Database; use DateTimeImmutable; use DateTimeZone; use Exception; use PDO;   class CallRepository {    private PDO $pdo;      public function __construct()    {        $this-&gt;pdo = Database::getConnection();    }      public function saveCall(array $data): bool    {        $stmt = $this-&gt;pdo-&gt;prepare(\"            INSERT INTO calls (                call_id,                operator_id,                call_date,                emotion_score,                speech_rate,                speech_duration_operator,                speech_duration_client,                interruptions,                sentiment,                transcript_text            ) VALUES (                :call_id,                :operator_id,                :call_date,                :emotion_score,                :speech_rate,                :speech_duration_operator,                :speech_duration_client,                :interruptions,                :sentiment,                :transcript_text            )        \");          return $stmt-&gt;execute([            ':call_id' =&gt; $data['call_id'],            ':operator_id' =&gt; $data['operator_id'],            ':call_date' =&gt; $data['call_date'],            ':emotion_score' =&gt; $data['emotion_score'],            ':speech_rate' =&gt; $data['speech_rate'],            ':speech_duration_operator' =&gt; $data['speech_duration_operator'],            ':speech_duration_client' =&gt; $data['speech_duration_client'],            ':interruptions' =&gt; $data['interruptions'],            ':sentiment' =&gt; $data['sentiment'],            ':transcript_text' =&gt; is_array($data['transcript_text']) ? json_encode($data['transcript_text']) : $data['transcript_text'],        ]);    }      \/**     * \u041f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u043c\u0435\u0442\u0440\u0438\u043a \u043f\u043e \u043e\u043f\u0435\u0440\u0430\u0442\u043e\u0440\u0430\u043c \u0437\u0430 \u0437\u0430\u0434\u0430\u043d\u043d\u0443\u044e \u0434\u0430\u0442\u0443     * @throws Exception     *\/    public function getOperatorMetrics(): array    {        $now = new DateTimeImmutable('now', new DateTimeZone('Europe\/Moscow'));        $yesterday = $now-&gt;modify('-1 day')-&gt;format('Y-m-d H:i:s');          $sql = \"        SELECT            o.id,            o.name,            COUNT(c.id) AS calls_count,            IFNULL(AVG(c.emotion_score), 0) AS avg_emotion_score,            IFNULL(AVG(c.interruptions), 0) AS avg_interruptions,            IFNULL(AVG(                CASE                    WHEN (c.speech_duration_operator + c.speech_duration_client) &gt; 0                    THEN c.speech_duration_operator \/ (c.speech_duration_operator + c.speech_duration_client) * 100                    ELSE 0                END            ), 0) AS avg_speech_balance,            IFNULL(AVG(c.speech_rate), 0) AS avg_speech_rate,           <\/code><\/pre>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-472203","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/472203","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=472203"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/472203\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=472203"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=472203"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=472203"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}