يتم تقديم تطبيقات مختلفة على الإنترنت ، ولكن ، في رأيي ، كلها بسيطة للغاية. أريد أن أقدم نسختي من الدليل الصوتي لعلامة النجمة.
ملاحظة: أنا لست مبرمجًا محترفًا ، وربما تبدو لك بعض الحلول متوحشة. قد تكون بعض الحيل قديمة. أنا على استعداد لقبول النقد وإصلاح النظام للأفضل.
وصف موجز للميزات:
يدخل المستخدم في الرد الصوتي التفاعلي ، ويقدم طلبه ، وفي معظم الحالات ، يصل إلى المكان الذي يحتاج إليه. الإحصائيات مشدودة أيضًا إلى النظام مع إدخال في جدول الخلية.
باختصار عن الشركة والشبكة التي يتم نشر هذا النظام فيها:
~ 1000 هاتف ، حوالي 50 قسم
منتجات البرمجيات التي يستخدمها النظام:
- النجمة 13.10
- YandexSpeechKit
- بيثون 2.6.6
- Mysql ، MSSQL
- سوكس 14.2.0
- حليقة 7.19.7
- عرجاء 3.99.5
وصف المخطط الهاتفي في علامة النجمة.
[officevoicerec] exten => s,1,Answer() same => n,Macro(hangercheck,${CALLERID(num)}) same => n,Set(ITERATIONS=1) same => n,Set(HANGFLAG=TRUE) same => n,Background(/var/lib/asterisk/sounds/ru/speechrec/zdravstvuite)
في هذا الجزء ، يتم تشغيل الماكرو للتحقق مما إذا كان المتصل قد أغلق الخط بعد سماع رسالة الترحيب. التالي هو تحديد قيمة المتغيرات:
التكرارات - ضرورية لتكرار عملية التعرف لعدد معين من المرات. HANGFLAG - يتم استخدام هذا المتغير بواسطة الماكرو hangercheck.
same => n(rec),Set(RECFILE=/tmp/${UNIQUEID}.wav) same => n,Playback(/var/lib/asterisk/sounds/en/beep) same => n,Record(${RECFILE},3,8) same => n,AGI(pyreq8.py,${RECFILE}) same => n,GotoIf($["${NUMTOCALL}" = "repeat"]?repeat) same => n,Set(HANGFLAG=FALSE)
قم بتعيين متغير ملف السجل ، اكتب الملف. نقوم بتشغيل برنامج agi-script مسؤول عن إرسال الملف للتعرف عليه والبحث عن الأرقام (سيتم تقديم وصف البرنامج النصي لاحقًا) ، والتحقق من متغير NUMTOCALL (يتم تعيين القيمة بواسطة البرنامج النصي) ، وتعيين علامة HANGFLAG ، مما يعني أن الشخص لم يغلق الخط في وقت مبكر.
same => n,Macro(VRstat,${CALLERID(num)},${NUMTOCALL},${RSTATUS},${CHANNEL},${RECREZ}) same => n,GotoIf($[[${EXISTS(${FNAME})}]]?foundName:havenodescr) same => n(foundName),Set(FILE_FNAME=${STRREPLACE(FNAME, ,)}) same => n,GotoIf($["${STAT(f,/var/lib/asterisk/sounds/ru/cache/${FILE_FNAME}.mp3)}"="1"]?havecache:nocache)
في هذا الجزء ، يتم تشغيل الماكرو للتحقق مما إذا كان المتصل قد أغلق الخط بعد سماعه رسالة الترحيب ، ثم تم تعيين المتغيرات ، ومن الضروري تكرار العمليات للتعرف على عدد من المرات. HANGFLAG - يتم استخدام هذا المتغير بواسطة الماكرو hangercheck.
same => n(rec),Set(RECFILE=/tmp/${UNIQUEID}.wav) same => n,Playback(/var/lib/asterisk/sounds/en/beep) same => n,Record(${RECFILE},3,8) same => n,AGI(pyreq8.py,${RECFILE}) same => n,GotoIf($["${NUMTOCALL}" = "repeat"]?repeat) same => n,Set(HANGFLAG=FALSE)
قم بتعيين متغير ملف السجل ، اكتب الملف. نبدأ البرنامج النصي المسؤول عن إرسال الملف للتعرف عليه والبحث عن الأرقام (سيكون وصف البرنامج النصي أدناه) ، والتحقق من متغير NUMTOCALL (يتم تعيين القيمة بواسطة البرنامج النصي) ، وتعيين علامة HANGFLAG التي لم يقطعها الشخص في وقت مبكر.
same => n,Macro(VRstat,${CALLERID(num)},${NUMTOCALL},${RSTATUS},${CHANNEL},${RECREZ}) same => n,GotoIf($[[${EXISTS(${FNAME})}]]?foundName:havenodescr) same => n(foundName),Set(FILE_FNAME=${STRREPLACE(FNAME, ,)}) same => n,GotoIf($["${STAT(f,/var/lib/asterisk/sounds/ru/cache/${FILE_FNAME}.mp3)}"="1"]?havecache:nocache) same => n(nocache),System(curl "https://tts.voicetech.yandex.net/generate?format=mp3&lang=ru-RU&speaker=zahar&emotion=neutral&speed=0.8&key=" -G --data-urlencode "text= ${FNAME}." > /tmp/speech-${UNIQUEID}.mp3) same => n,System(/usr/local/bin/lame -S --scale 30 /tmp/speech-${UNIQUEID}.mp3 /var/lib/asterisk/sounds/ru/cache/${FILE_FNAME}.mp3) same => n(havecache),Playback(/var/lib/asterisk/sounds/ru/cache/${FILE_FNAME}) same => n,Dial(Local/${NUMTOCALL}@common-context)
تشغيل الماكرو vrstat (المسؤول عن الإحصائيات ، لن يتم وصفه بسبب تافهه). تحقق مما إذا كان وصف FNAME (يتم تعيين المتغير بواسطة pyreq8.py) للطلب. إذا كان هناك وصف ، قم بتعيين اسم ملف ذاكرة التخزين المؤقت في المتغير وتحقق من وجوده. إذا لم يكن الملف موجودًا ، فإننا نقوم بتجميعه ، وتحويله إلى mp3 ، وزيادة مستوى الصوت ، ثم (أو في حالة وجود ذاكرة التخزين المؤقت) ، قم بتشغيله واستدعاء المشترك.
same => n(repeat),GotoIf($["${ITERATIONS}"="1"]?secretary) same => n,Background(/var/lib/asterisk/sounds/ru/speechrec/1-wav) same => n,Set(ITERATIONS=$[${ITERATIONS}+1]) same => n,Goto(rec)
كرر الاعتراف. إذا تجاوز عدد التكرارات العدد المحدد ، فإننا نترجم إلى أمناء.
same => n(secretary),Macro(VRstat,${CALLERID(num)},${NUMTOCALL},${RSTATUS},${CHANNEL},${RECREZ}) same => n,Set(HANGFLAG=FALSE) same => n,Dial(Local/1000@common-context)
نقل إلى الأمناء. ندخل في الإحصائيات ، نضع العلم الذي لم يقطع الاتصال. نسمي السكرتير.
same => n(havenodescr),Playback(/var/lib/asterisk/sounds/ru/speechrec/wait2);thanks-wait) same => n,Noop('no description') same => n,Dial(Local/${NUMTOCALL}@common-context) same => n,Hangup()
اتصل بمشترك ليس لديه وصف في الدليل. نفقد الرسالة ، أدخلها في الإحصائيات ، نسميها.
exten => h,1,Gotoif($["${HANGFLAG}"="TRUE"]?exec:noop) same => n(exec),Macro(VRstat,${CALLERID(num)},x,HANGER,${CHANNEL}) same => n(noop),Noop('exiting')
معالجة إنهاء المكالمة لكي يعمل الماكرو hangercheck.
وصف Dialplan. وحدات الماكرو.
[macro-hangercheck] ;${ARG1} -clid exten => s,1,GotoIf($["${ARG1}"="anonymous"]?end) exten => s,n,MYSQL(Connect connid SRV user password db utf8) exten => s,n,MYSQL(SET NAMES utf8) exten => s,n,MYSQL(Query resultid ${connid} SELECT IFNULL((SELECT clid from ivr_stat where rstatus="HANGER" and calldate >=ADDDATE(NOW(),INTERVAL -48 HOUR) and clid="${ARG1}" order by calldate desc limit 1),"NF")) exten => s,n,MYSQL(Fetch fetchid ${resultid} VAR) exten => s,n,MYSQL(Clear ${resultid}) exten => s,n,MYSQL(Disconnect ${connid}) exten => s,n,GotoIf($["${VAR}"="NF"]?end) exten => s,n,Macro(VRstat,${ARG1},x,H_RECALL,${CHANNEL}) exten => s,n,Dial(Local/1000@common-context) exten => s,n,Hangup() exten => s,n(end),Noop(Hanger check failed)
نتحقق في قاعدة بيانات الإحصائيات عما إذا كان الرقم المعين هو آخر 48 ساعة ، وإذا قام بتعليق الهاتف دون انتظار انتهاء التعرف عليه ، فإننا ندخله في الإحصائيات ونوصله بالسكرتير.
وصف موجز لجداول sql و mysql المستخدمة.
جدول أسطر dbo.phrases.
id(PK, int), phrase (text), number(varchar(50))
تم رفع البحث عن النص الكامل لهذا الجدول ؛ يتم استخدامه للتبديل بواسطة الكلمات الرئيسية عند تلقي عبارة طويلة. على سبيل المثال ، العبارة: "الرجاء الاتصال بي بممثل قسم الإعلان" ، إذا كان هناك إدخال في قاعدة البيانات حيث العبارة = "قسم الإعلان" ، فسيتم توصيل المتصل بالرقم المقابل.
تسجيل جدول Mysql.
id(PK, int), date (datetime), ctime(float), rtime(float),stime(float), phrase(varchar(60))
يُستخدم هذا الجدول لتخزين نتائج التعرف وجمع الإحصائيات في وقت التعرف. الوقت - الوقت المستغرق في تحويل الملف الصوتي - الوقت المستغرق في التنزيل والتعرف - الوقت المستغرق في البحث
جدول Mysql ivr_stat.
id(PK, int), calldate (datetime),clid(varchar(15)),duration(int(20)),callednum(varchar(10)),rstatus(varchar(20)),channame(varchar30)),RECREZ(varchar(200))
يتم استخدام هذا الجدول لتتبع نتائج التعرف (RECREZ ، rstatus) ، التي ذهب إليها المتصل وفقًا لنتائج البحث لعبارة معروفة (تسمىnum) ، للاحتفاظ بإحصائيات حول مقدار الوقت الذي يقضيه الشخص في قائمة التعرف (المدة) وللتصحيح (الاسم المستعار)
جدول Mysql CustomRequests.
id(PK, int), nomer(int(10)), request(varchar(100))
دليل مساعد للمجموعات والأقسام الإضافية.
جدول أرقام Mysql.
Num(text), name(text)
دليل مع وصف الرقم الذي تم إدخاله للتمثيل الصوتي.
جداول Mysql spravochnik_rus و spravochnik_rus_name_num_no_tops
nomer(varchar(11)), fio(varchar(100))
مرجع الأشخاص بأرقام. الأول كامل للاستخدام الداخلي. في الثانية ، للاستخدام الخارجي ، يتم تغليف عدد المديرين والمديرين في الأمانة.
برنامج AGI النصي الذي يحول ويرسل ويبحث عن العبارات المعروفة.
المكتبات المستعملة:
import difflib from sys import exit import uuid import time import os import subprocess import xml.etree.ElementTree import MySQLdb import pymssql import string import re from itertools import permutations from os import remove import timeit
وكذلك مكتبة asterisk.agi ، التي نستوردها اعتمادًا على التصحيح (يتم التصحيح على جهاز windows).
وصف المتغيرات.
WINDEBUG = False
علم التصحيح
_digits = re.compile('\d')
متغير التعبير العادي المتغير للأرقام.
uniqid = str(uuid.uuid1()).replace('-', '')
متغير المعرف الفريد.
dkey = '12345678-9101-1121-3141-51617181920'
مفتاح واجهة برمجة تطبيقات الكلام Yandex.
lang = 'ru-RU'
خيار اللغة لمجموعة الكلام Yandex.
topic = 'queries'
خيار موضوع التعرف على مجموعة الكلام Yandex.
callnumber = '222'
رقم الاتصال الافتراضي.
setVar('NUMTOCALL', callnumber)
تعيين رقم الاتصال الافتراضي في حالة حدوث خطأ ما.
persondic = dict()
اسم القاموس - العدد.
persondicFI=dict()
قاموس FI - العدد.
persondicF=dict()
اسم العائلة - الرقم.
otherdic = dict()
قاموس مع إدخالات أخرى للنموذج سجل - رقم.
descriptions = dict()
قاموس يحتوي على أوصاف لإنشاء التمثيل الصوتي ، اكتب Record - number.
duplicates = list()
يأخذ قائمة الخدمات للتصفية.
nums = list()
قائمة بجميع الملحقات.
outfile = '/tmp/' + uniqid + '-pcm.wav'
ملف صوتي مؤقت متغير.
mysqlhost='myhost' mysqlpass='mypass' mysqluser='myuser' mysqldb='mydb' mssqlhost='mshost' mssqlpass='mspass' mssqluser='msuser' mssqldb='msdb'
تفاصيل الاتصال بـ mysql و mssql.
if not WINDEBUG: from asterisk.agi import * agi = AGI() infile = agi.env['agi_arg_1'] caller = agi.get_variable('CALLERID(num)') else: caller = '1064' infile = ''
قم باستيراد المكتبة ، وقم بتعيين متغير لاسم ملف الصوت المدخل ورقم المتصل ، اعتمادًا على تصحيح الأخطاء.
وصف الميزة
def verb(s): if not WINDEBUG: agi.verbose(s) else: print s
بناءً على قيمة متغير التصحيح ، نعرض الرسائل في وحدة تحكم النجمة أو في stdout.
def setVar(varname, varval): if not WINDEBUG: agi.set_variable(varname, varval) else: print "setting var " + varname + " with value " + varval
اعتمادًا على قيمة متغير التصحيح ، نقوم بتعيين قيمة متغير dialplan إلى علامة النجمة أو الإخراج إلى stdout.
def contains_digits(s): verb('enter contains_digits') return bool(_digits.search(s))
تحقق مما إذا كانت s تحتوي على أرقام.
def return_digits(s): verb('return digits') pstr = s.encode("utf-8") all = string.maketrans('', '') nodigs = all.translate(all, string.digits) return unicode(pstr.translate(all, nodigs), "utf-8")
نعود فقط أرقام من s.
def check_dob(num): if int(num) in nums: verb('checkdob success') return True else: verb('checkdob fail' + num) return False
تحقق من وجود ملحق في قائمة الأرقام.
def set_dob(strnum): buf = return_digits(strnum) if contains_digits(strnum): if check_dob(buf): verb('setting var ' + buf) setVar('RSTATUS', 'SAYDIAL') return buf else: return "repeat" else: return "repeat"
وظيفة إعداد التمديد.
def checkSize(infile): if int(os.stat(infile).st_size) <= 26364: setVar('NUMTOCALL', 'repeat') setVar('RSTATUS', 'SILENCE') verb('empty file received') remove(infile) exit(9)
التحقق من ملف التسجيل الصوتي المستلم للتعرف عليه ، إذا كان الحجم صغيرًا جدًا ، فإننا نفترض أنه كان صامتًا في الهاتف.
def addSessionStat(ctime, rtime, stime, phrase): cdate = time.strftime("%Y-%m-%d %H:%M:%S") db = MySQLdb.connect(host=mysqlhost, user=mysqluser, passwd=mysqluser, db=mysqldb, charset='utf8') cur = db.cursor() cur.execute( "INSERT INTO recogStats(date,ctime,rtime,stime,phrase) VALUES ('" + cdate + "','" + str(ctime) + "','" + str( rtime) + "','" + str(stime) + "','" + phrase + "')") db.commit() db.close()
تسجيل وظيفة التعرف على الإحصائيات.
def fillDics(): db = MySQLdb.connect(host=mysqlhost, user=mysqluser, passwd=mysqlpass, db="central_cdr", charset='utf8') cur = db.cursor() if len(caller) != 4: tbname = 'spravochnik_rus_name_num_no_tops' else: tbname = 'svravochnik_rus_persons' cur.execute("""SELECT nomer,fio from """ + tbname) for row in cur.fetchall(): fullfio=row[1].lower() f=" ".join(fullfio.split(' ')[0:1]) if not f in persondicF.keys():
تملأ هذه الوظيفة في القواميس اسم العائلة (persondicF) واسم العائلة (persondicFI) والاسم (persondic) وأسماء الأقسام (otherdic) وأوصاف تركيب الكلام (الأوصاف) وجميع أرقام الشركة (nums).
اعتمادًا على طول رقم المتصل ، يتم أخذ جدول يحتوي على أرقام المديرين (المتصل الداخلي) أو لا يحتوي على (المتصل الخارجي).
يتم اختبار دليل الألقاب من حيث التفرد لاستبعاد وجود اثنين من Ivanovs بأرقام مختلفة فيه.
def convert(infile, outfile):
وظيفة تحويل الملف المسجل في العلامة النجمية ، في حالة ظهور خطأ في sox ، وتعيين الرقم الافتراضي والخروج من البرنامج النصي.
def sendRecog(file): verb("Sending file to yandex: " + outfile) proc = subprocess.Popen(['curl', '--max-time', '5', '--silent', 'asr.yandex.net/asr_xml?key=' + dkey + '&uuid=' + uniqid + '&topic=' + topic + '&lang=ru-RU', '-F', 'Content-Type=audio/x-pcm;bit=16;rate=16000', '-F', 'audio=@' + outfile], stdout=subprocess.PIPE) (out, err) = proc.communicate() verb("return code is: " + str(proc.returncode)) if proc.returncode != 0: return "" remove(file) e = xml.etree.ElementTree.fromstring(out) if e.attrib['success'] == '1': verb(e._children[0].text) return e._children[0].text else: return ""
وظيفة إرسال ملف للتعرف عليه وتلقي استجابة من Yandex. إذا نجح الاعتراف ، نأخذ الإجابة الأولى. لم أستطع القيام بذلك مع مكتبة curl ، لهذا السبب يتم استخدام هذا الخيار.
def searchfiobyf(f): if len(f.split(" "))==2: limit=2 elif len(f.split(" "))>=3: return f else: limit=1 for fio,num in persondic.iteritems(): cutfio=" ".join(fio.split(' ')[0:limit]) if f == cutfio: return fio
وظيفة البحث عن اسم كامل ، اعتمادًا على ما إذا كان فقط الاسم الأخير أو الاسم الأخير والاسم الأول جاء إلى الإدخال.
def combinationSearcher(phrase,pdic,quality): verb(u'searching in persons') result = difflib.get_close_matches(phrase, pdic.keys(), 1, quality)
دالة البحث المختلط ، على سبيل المثال: العبارة = "Alexey Petr" ، pdic تحتوي على الإدخال "Alexeyev Peter" ، ستقوم دالة get_close_matches بإرجاع "Alexeyev Peter". قد تنتج الوظيفة نتائج غير صحيحة ، ولكن ، كما أظهرت الممارسة ، هناك استجابات أكثر صحة. تسمح لك معلمة الجودة بتحديد دقة البحث عن عبارات مماثلة.
def getNumByName(recognizedString): if u'' in recognizedString: verb('enter dobavochn') return set_dob(recognizedString) elif len(recognizedString.replace(" ", "")) == 4: verb('enter num say') return set_dob(recognizedString) if len(recognizedString) <= 5 and recognizedString.lower() not in [u"",u"",u""]: setVar('RSTATUS', 'SHORT') return "repeat" verb(u'start searching')
الوظيفة الرئيسية لإيجاد الأرقام حسب العبارة.
دعونا نحلل هذه الوظيفة في أجزاء.
if u'' in recognizedString: verb('enter dobavochn') return set_dob(recognizedString)
مثال: ستعرض العبارة "من فضلك ، الرقم الداخلي 1234" رقم التمديد (إن وجد).
elif len(recognizedString.replace(" ", "")) == 4: verb('enter num say') return set_dob(recognizedString)
إذا نطق المستخدم برقم مكون من أربعة أرقام ، فأعد الامتداد المقابل (إن وجد).
if len(recognizedString) <= 5 and recognizedString.lower() not in [u"",u"",u""]: setVar('RSTATUS', 'SHORT') return "repeat"
من أجل عدم تشغيل البرنامج بشكل غير ضروري في عبارات قصيرة (يمكن أن يحدث هذا عندما سمعت Yandex وجزء من المحادثة ، عندما لم يستمع المتصل لرسالة التعرف).
if parts >= 5: verb('Phrase is long. Using FTDB') buf = mssqlwrapper(fixedstring) if buf != '': buf = mssqlwrapper(fixedstring)[0] if str(buf) in descriptions.keys(): setVar('FNAME', descriptions[str(buf)]) setVar('RSTATUS', 'DEPTSUCCESS') return buf else: setVar('RSTATUS', 'REQUESTNOTFOUND') return "repeat" result=""
بالنسبة للعبارات الطويلة (أكثر من خمس كلمات) ، ننتقل على الفور إلى قاعدة FT من mssql.
if parts == 1: mssqlcheck=mssqlwrapper(fixedstring) if mssqlcheck!='': if mssqlcheck[2]>=80: buf=str(mssqlcheck[0]) if buf in descriptions.keys(): setVar('FNAME', descriptions[str(buf)]) setVar('RSTATUS', 'DEPTSUCCESS') result=buf else: result=combinationSearcher(fixedstring,persondicF,0.7)
إذا كانت العبارة المعترف بها تتكون من كلمة واحدة ، فإننا أولاً ننظر إلى قاعدة بيانات FT ، بدقة 80٪ ، ونبحث عن وصف للرقم المعترف به ، ونعين متغير النتيجة ومتغير خطة رقم خطة الحالة ، وإلا فإننا نبحث عن كلمات مماثلة في قاموس الألقاب.
elif parts ==2: combs = list(permutations(split, parts)) for item in combs: element = " ".join(item) result=combinationSearcher(element,persondicFI,0.7) if result!="": break
إذا كانت العبارة تتكون من كلمتين ، فإننا نفترض أنها لقب واسم.
نصنع قائمة من التركيبات (Ivan Ivan ، Ivan Ivan) ونبحث عن تطابقات مماثلة.
elif parts ==3: combs = list(permutations(split, parts)) for item in combs: element = " ".join(item) result=combinationSearcher(element,persondic,0.8) if result!="": break if result!="": return result
في حالة عبارة مكونة من ثلاث كلمات ، نفترض أنه اسم كامل ، وننشئ قائمة بالمجموعات وننظر في القاموس باسم كامل. نرجع النتيجة إذا كان المتغير غير فارغ.
buf = mssqlwrapper(recognizedString) if buf != '': buf = mssqlwrapper(recognizedString)[0] if str(buf) in descriptions.keys(): verb('it is') setVar('FNAME', descriptions[str(buf)]) setVar('RSTATUS', 'DEPTSUCCESS') return buf else: verb(u'item not found ' + recognizedString)
إذا لم نعثر على أي تطابقات ، فإننا نقوم بالبحث في قاعدة بيانات FT ، وإذا وجدنا تطابقًا فإننا نرجع النتيجة والوصف ، وإلا فإننا نرجع الرقم الافتراضي ونعين الحالة التي لم يتم العثور على العبارة فيها.
الجزء الرئيسي من البرنامج.
fillDics() if not WINDEBUG: checkSize(infile) start_time = timeit.default_timer() convert(infile, outfile) convert_elapsed = timeit.default_timer() - start_time start_time = timeit.default_timer() checkstring = sendRecog(outfile).lower() recog_elapsed = timeit.default_timer() - start_time verb('convert_elapsed = ' + str(recog_elapsed)) if checkstring == "": verb('not recognized. using default.') setVar('NUMTOCALL', 'repeat') setVar('RSTATUS', 'SILENCE') exit(9) else: setVar('RECREZ', checkstring) else: checkstring = u"" start_time = timeit.default_timer() callnumber = getNumByName(checkstring) search_elapsed = timeit.default_timer() - start_time if not WINDEBUG: setVar('NUMTOCALL', str(callnumber)) addSessionStat(convert_elapsed, recog_elapsed, search_elapsed, checkstring.lower())
يتم تشغيل وظيفة ملء القواميس ، إذا لم نكن في برنامج التصحيح - نقوم بملء متغيرات المؤقت ، والتحقق من حجم الملف ، وتحويله ، وإرساله للتعرف عليه ، وإلا فإننا نملأ متغير الفحص بعبارة فحص.
بعد ذلك ، نملأ متغيرات الموقتات للتعرف ، ونبحث عن عبارات في هيكلنا. وفي حالة الأداء القتالي ، قمنا بتعيين متغير لخطة الترقيم وإدخال الإحصائيات.
بعض الإحصائيات.
يونيو 2018:
الاعتراف الناجح - 1010
كانوا صامتين في أنبوب - 78
فشل الاعتراف (لم يتم العثور على تطابق) - 79
طلب قصير - 4
قسم معترف به - 7
متوسط وقت التعرف (متوسط زمن استجابة Yandex) - 2.6 ثانية
حزيران (يونيو) 2017:
اعتراف ناجح - 1271
قسم معترف به - 18
فشل الاعتراف (لم يتم العثور على تطابق) - 127
طلب قصير - 9
كانوا صامتين في أنبوب - 71
متوسط وقت التعرف (متوسط وقت استجابة Yandex) - 1.5 ثانية
يتم استخدام هذه الخدمة بنجاح في الشركة التي أعمل فيها. آمل أن تساعد إنجازاتي الآخرين في تنفيذ أفكارهم أو وظائف مماثلة. جاهز للإجابة على جميع الأسئلة.