Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #669 from digitalgreenorg/loop_ivr
Loop ivr - Release V1
- Loading branch information
Showing
11 changed files
with
659 additions
and
2 deletions.
There are no files selected for viewing
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,168 @@ | ||
import time | ||
import datetime | ||
import requests | ||
import unicodecsv as csv | ||
import xml.etree.ElementTree as xml_parse | ||
from pytz import timezone | ||
|
||
from loop.models import HelplineExpert, HelplineIncoming, HelplineOutgoing, \ | ||
HelplineCallLog, HelplineSmsLog | ||
|
||
from dg.settings import EXOTEL_ID, EXOTEL_TOKEN, EXOTEL_HELPLINE_NUMBER, MEDIA_ROOT | ||
|
||
from loop.utils.ivr_helpline.helpline_data import CALL_STATUS_URL, CALL_REQUEST_URL, \ | ||
CALL_RESPONSE_URL, SMS_REQUEST_URL, APP_REQUEST_URL, APP_URL | ||
|
||
HELPLINE_LOG_FILE = '%s/loop/helpline_log.log'%(MEDIA_ROOT,) | ||
|
||
def write_log(log_file,module,log): | ||
curr_india_time = datetime.datetime.now(timezone('Asia/Kolkata')) | ||
with open(log_file, 'ab') as csvfile: | ||
file_write = csv.writer(csvfile, quoting=csv.QUOTE_ALL) | ||
file_write.writerow([curr_india_time,module,log]) | ||
|
||
# Call Type is 0 for Incoming Call and 1 for Outgoing Call | ||
def save_call_log(call_id,from_number,to_number,call_type,start_time): | ||
call_obj = HelplineCallLog(call_id=call_id,from_number=from_number,to_number=to_number,call_type=call_type,start_time=start_time) | ||
try: | ||
call_obj.save() | ||
except Exception as e: | ||
# if error then log | ||
module = 'save_call_log' | ||
write_log(HELPLINE_LOG_FILE,module,str(e)) | ||
|
||
def save_sms_log(sms_id,from_number,to_number,sms_body,sent_time): | ||
sms_obj = HelplineSmsLog(sms_id=sms_id,from_number=from_number,to_number=to_number,sms_body=sms_body,sent_time=sent_time) | ||
try: | ||
sms_obj.save() | ||
except Exception as e: | ||
# if error then log | ||
module = 'save_sms_log' | ||
write_log(HELPLINE_LOG_FILE,module,str(e)) | ||
|
||
def get_status(call_id): | ||
call_status_url = CALL_STATUS_URL%(EXOTEL_ID,EXOTEL_TOKEN,EXOTEL_ID,call_id) | ||
response = requests.get(call_status_url) | ||
call_status = dict() | ||
# Status code 200 if API call is successful, 429 if Too many request | ||
if response.status_code == 200: | ||
response_tree = xml_parse.fromstring((response.text).encode('utf-8')) | ||
call_detail = response_tree.findall('Call')[0] | ||
call_status['response_code'] = 200 | ||
call_status['status'] = str(call_detail.find('Status').text) | ||
call_status['to'] = str(call_detail.find('To').text) | ||
call_status['from'] = str(call_detail.find('From').text) | ||
call_status['start_time'] = str(call_detail.find('StartTime').text) | ||
call_status['end_time'] = str(call_detail.find('EndTime').text) | ||
extra_detail = call_detail.findall('Details')[0] | ||
call_status['from_status'] = str(extra_detail.find('Leg1Status').text) | ||
call_status['to_status'] = str(extra_detail.find('Leg2Status').text) | ||
else: | ||
call_status['response_code'] = response.status_code | ||
return call_status | ||
|
||
def get_info_through_api(outgoing_call_id): | ||
call_status = get_status(outgoing_call_id) | ||
if call_status['response_code'] == 200: | ||
# Search latest pending Incoming object | ||
incoming_obj = HelplineIncoming.objects.filter(from_number=call_status['to'],call_status=0).order_by('-id') | ||
expert_obj = HelplineExpert.objects.filter(phone_number=call_status['from']) | ||
if len(incoming_obj) > 0 and len(expert_obj) > 0: | ||
incoming_obj = incoming_obj[0] | ||
expert_obj = expert_obj[0] | ||
to_number = call_status['to'] | ||
return (incoming_obj,expert_obj,to_number) | ||
return '' | ||
|
||
def update_incoming_acknowledge_user(incoming_call_obj,acknowledge_user): | ||
if acknowledge_user == 0: | ||
incoming_call_obj.acknowledge_user = 0 | ||
else: | ||
incoming_call_obj.acknowledge_user += 1 | ||
try: | ||
incoming_call_obj.save() | ||
except Exception as e: | ||
module = 'update_incoming_acknowledge_user' | ||
write_log(HELPLINE_LOG_FILE,module,str(e)) | ||
|
||
# When we do not want to acknowledge User in case of call is not successfull | ||
# then acknowledge_user parameter is more than 1 | ||
# (For cases like call generated from queue module) | ||
def make_helpline_call(incoming_call_obj,from_number_obj,to_number,acknowledge_user=0): | ||
call_request_url = CALL_REQUEST_URL%(EXOTEL_ID,EXOTEL_TOKEN,EXOTEL_ID) | ||
call_response_url = CALL_RESPONSE_URL | ||
from_number = from_number_obj.phone_number | ||
# CallType is either Transactional or Promotional | ||
parameters = {'From':from_number,'To':to_number,'CallerId':EXOTEL_HELPLINE_NUMBER,'CallType':'trans','StatusCallback':call_response_url} | ||
response = requests.post(call_request_url,data=parameters) | ||
module = 'make_helpline_call' | ||
if response.status_code == 200: | ||
update_incoming_acknowledge_user(incoming_call_obj,acknowledge_user) | ||
response_tree = xml_parse.fromstring((response.text).encode('utf-8')) | ||
call_detail = response_tree.findall('Call')[0] | ||
outgoing_call_id = str(call_detail.find('Sid').text) | ||
outgoing_call_time = str(call_detail.find('StartTime').text) | ||
save_call_log(outgoing_call_id,from_number,to_number,1,outgoing_call_time) | ||
outgoing_obj = HelplineOutgoing(call_id=outgoing_call_id,incoming_call=incoming_call_obj,outgoing_time=outgoing_call_time,from_number=from_number_obj,to_number=to_number) | ||
try: | ||
outgoing_obj.save() | ||
except Exception as e: | ||
# Save Errors in Logs | ||
write_log(HELPLINE_LOG_FILE,module,str(e)) | ||
else: | ||
# Enter in Log | ||
log = 'Status Code: %s (Parameters: %s)'%(str(response.status_code),parameters) | ||
write_log(HELPLINE_LOG_FILE,module,log) | ||
|
||
def send_helpline_sms(from_number,to_number,sms_body): | ||
sms_request_url = SMS_REQUEST_URL%(EXOTEL_ID,EXOTEL_TOKEN,EXOTEL_ID) | ||
parameters = {'From':from_number,'To':to_number,'Body':sms_body,'Priority':'high'} | ||
response = requests.post(sms_request_url,data=parameters) | ||
if response.status_code == 200: | ||
response_tree = xml_parse.fromstring((response.text).encode('utf-8')) | ||
sms_detail = response_tree.findall('SMSMessage')[0] | ||
sms_id = str(sms_detail.find('Sid').text) | ||
sent_time = str(sms_detail.find('DateCreated').text) | ||
save_sms_log(sms_id,from_number,to_number,sms_body,sent_time) | ||
else: | ||
module = 'send_helpline_sms' | ||
log = "Status Code: %s (Parameters: %s)"%(str(response.status_code),parameters) | ||
write_log(HELPLINE_LOG_FILE,module,log) | ||
|
||
def connect_to_app(to_number,app_id): | ||
app_request_url = APP_REQUEST_URL%(EXOTEL_ID,EXOTEL_TOKEN,EXOTEL_ID) | ||
app_url = APP_URL%(app_id,) | ||
parameters = {'From':to_number,'CallerId':EXOTEL_HELPLINE_NUMBER,'CallType':'trans','Url':app_url} | ||
response = requests.post(app_request_url,data=parameters) | ||
module = 'connect_to_app' | ||
log = "App Id: %s Status Code: %s (Response text: %s)"%(app_id,str(response.status_code),str(response.text)) | ||
write_log(HELPLINE_LOG_FILE,module,log) | ||
|
||
def fetch_info_of_incoming_call(request): | ||
call_id = str(request.GET.getlist('CallSid')[0]) | ||
farmer_number = str(request.GET.getlist('From')[0]) | ||
dg_number = str(request.GET.getlist('To')[0]) | ||
incoming_time = str(request.GET.getlist('StartTime')[0]) | ||
return (call_id,farmer_number,dg_number,incoming_time) | ||
|
||
def update_incoming_obj(incoming_obj,call_status,recording_url,expert_obj,resolved_time): | ||
incoming_obj.call_status = call_status | ||
incoming_obj.recording_url = recording_url | ||
incoming_obj.resolved_by = expert_obj | ||
incoming_obj.resolved_time = resolved_time | ||
try: | ||
incoming_obj.save() | ||
except Exception as e: | ||
# if error in updating incoming object then Log | ||
module = 'update_incoming_obj' | ||
write_log(HELPLINE_LOG_FILE,module,str(e)) | ||
|
||
def send_acknowledge(incoming_call_obj): | ||
if incoming_call_obj.acknowledge_user == 0: | ||
return 0 | ||
else: | ||
return 1 | ||
|
||
def send_voicemail(farmer_number,OFF_HOURS_VOICEMAIL_APP_ID): | ||
time.sleep(2) | ||
connect_to_app(farmer_number,OFF_HOURS_VOICEMAIL_APP_ID) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
import time | ||
import datetime | ||
from datetime import timedelta | ||
from pytz import timezone | ||
|
||
from django.core.management.base import BaseCommand | ||
from django.utils.timezone import now | ||
|
||
from dg.settings import EXOTEL_ID, EXOTEL_TOKEN, EXOTEL_HELPLINE_NUMBER, MEDIA_ROOT | ||
|
||
from loop.models import HelplineExpert, HelplineIncoming, HelplineOutgoing | ||
from loop.helpline_view import get_status, make_helpline_call, write_log | ||
|
||
HELPLINE_LOG_FILE = '%s/loop/helpline_log.log'%(MEDIA_ROOT,) | ||
|
||
class Command(BaseCommand): | ||
|
||
def check_pending_or_not(self,incoming_call_id): | ||
incoming_call_obj = HelplineIncoming.objects.filter(id=incoming_call_id) | ||
if incoming_call_obj: | ||
incoming_call_obj = incoming_call_obj[0] | ||
# Check If Status of call is pending | ||
if incoming_call_obj.call_status == 0: | ||
# Fetch latest outgoing of respective Incoming | ||
latest_outgoing_of_incoming = HelplineOutgoing.objects.filter(incoming_call=incoming_call_obj).order_by('-id').values_list('call_id', flat=True)[:1] | ||
if len(latest_outgoing_of_incoming) != 0: | ||
call_status = get_status(latest_outgoing_of_incoming[0]) | ||
else: | ||
call_status = '' | ||
# Check If Pending call is already in-progress | ||
if call_status != '' and call_status['response_code'] == 200 and (call_status['status'] in ('ringing', 'in-progress')): | ||
return '' | ||
return incoming_call_obj | ||
# If Status of call is not pending | ||
else: | ||
return '' | ||
# If Incoming Call object not found | ||
else: | ||
return '' | ||
|
||
def handle(self, *args, **options): | ||
expert_obj = HelplineExpert.objects.filter(expert_status=1)[:1] | ||
if expert_obj: | ||
expert_obj = expert_obj[0] | ||
else: | ||
#Log in file that no expert available | ||
module = "helpline_queue" | ||
write_log(HELPLINE_LOG_FILE,module,"No Expert Available") | ||
return | ||
old_time = datetime.datetime.now(timezone('Asia/Kolkata'))-timedelta(days=2) | ||
old_time = old_time.replace(tzinfo=None) | ||
try: | ||
HelplineIncoming.objects.filter(call_status=0,last_incoming_time__lte=old_time).update(call_status=2) | ||
except Exception as e: | ||
module = "helpline_queue" | ||
write_log(HELPLINE_LOG_FILE,module,str(e)) | ||
pending_incoming_call_id = HelplineIncoming.objects.filter(call_status=0).order_by('id').values_list('id', flat=True) | ||
for ids in pending_incoming_call_id: | ||
incoming_call_obj = self.check_pending_or_not(ids) | ||
if incoming_call_obj: | ||
farmer_number = incoming_call_obj.from_number | ||
# Last parameter more than 0 only when we do not want to acknowledge User if call is not successfull | ||
make_helpline_call(incoming_call_obj,expert_obj,farmer_number,1) | ||
time.sleep(120) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
# -*- coding: utf-8 -*- | ||
from __future__ import unicode_literals | ||
|
||
from django.db import migrations, models | ||
from django.conf import settings | ||
|
||
|
||
class Migration(migrations.Migration): | ||
|
||
dependencies = [ | ||
migrations.swappable_dependency(settings.AUTH_USER_MODEL), | ||
('loop', '0006_auto_20161230_0814'), | ||
] | ||
|
||
operations = [ | ||
migrations.CreateModel( | ||
name='HelplineCallLog', | ||
fields=[ | ||
('time_created', models.DateTimeField(auto_now_add=True, null=True)), | ||
('time_modified', models.DateTimeField(auto_now=True, null=True)), | ||
('id', models.AutoField(serialize=False, primary_key=True)), | ||
('call_id', models.CharField(max_length=100)), | ||
('from_number', models.CharField(max_length=20)), | ||
('to_number', models.CharField(max_length=20)), | ||
('call_type', models.IntegerField(choices=[(0, b'Incoming'), (1, b'Outgoing')])), | ||
('start_time', models.DateTimeField()), | ||
('user_created', models.ForeignKey(related_name='loop_helplinecalllog_created', blank=True, editable=False, to=settings.AUTH_USER_MODEL, null=True)), | ||
('user_modified', models.ForeignKey(related_name='loop_helplinecalllog_related_modified', blank=True, editable=False, to=settings.AUTH_USER_MODEL, null=True)), | ||
], | ||
options={ | ||
'abstract': False, | ||
}, | ||
), | ||
migrations.CreateModel( | ||
name='HelplineExpert', | ||
fields=[ | ||
('time_created', models.DateTimeField(auto_now_add=True, null=True)), | ||
('time_modified', models.DateTimeField(auto_now=True, null=True)), | ||
('id', models.AutoField(serialize=False, primary_key=True)), | ||
('name', models.CharField(max_length=100)), | ||
('phone_number', models.CharField(unique=True, max_length=20)), | ||
('email_id', models.CharField(max_length=50)), | ||
('expert_status', models.IntegerField(default=1, choices=[(0, b'Inactive'), (1, b'Active')])), | ||
('user_created', models.ForeignKey(related_name='loop_helplineexpert_created', blank=True, editable=False, to=settings.AUTH_USER_MODEL, null=True)), | ||
('user_modified', models.ForeignKey(related_name='loop_helplineexpert_related_modified', blank=True, editable=False, to=settings.AUTH_USER_MODEL, null=True)), | ||
], | ||
options={ | ||
'abstract': False, | ||
}, | ||
), | ||
migrations.CreateModel( | ||
name='HelplineIncoming', | ||
fields=[ | ||
('time_created', models.DateTimeField(auto_now_add=True, null=True)), | ||
('time_modified', models.DateTimeField(auto_now=True, null=True)), | ||
('id', models.AutoField(serialize=False, primary_key=True)), | ||
('call_id', models.CharField(max_length=100)), | ||
('from_number', models.CharField(max_length=20, db_index=True)), | ||
('to_number', models.CharField(max_length=20)), | ||
('incoming_time', models.DateTimeField()), | ||
('last_incoming_time', models.DateTimeField()), | ||
('resolved_time', models.DateTimeField(null=True, blank=True)), | ||
('call_status', models.IntegerField(default=0, db_index=True, choices=[(0, b'Pending'), (1, b'Resolved'), (2, b'Declined')])), | ||
('recording_url', models.CharField(max_length=200, null=True, blank=True)), | ||
('acknowledge_user', models.IntegerField(default=0)), | ||
('resolved_by', models.ForeignKey(blank=True, to='loop.HelplineExpert', null=True)), | ||
('user_created', models.ForeignKey(related_name='loop_helplineincoming_created', blank=True, editable=False, to=settings.AUTH_USER_MODEL, null=True)), | ||
('user_modified', models.ForeignKey(related_name='loop_helplineincoming_related_modified', blank=True, editable=False, to=settings.AUTH_USER_MODEL, null=True)), | ||
], | ||
), | ||
migrations.CreateModel( | ||
name='HelplineOutgoing', | ||
fields=[ | ||
('time_created', models.DateTimeField(auto_now_add=True, null=True)), | ||
('time_modified', models.DateTimeField(auto_now=True, null=True)), | ||
('id', models.AutoField(serialize=False, primary_key=True)), | ||
('call_id', models.CharField(max_length=100)), | ||
('to_number', models.CharField(max_length=20)), | ||
('outgoing_time', models.DateTimeField()), | ||
('from_number', models.ForeignKey(to='loop.HelplineExpert')), | ||
('incoming_call', models.ForeignKey(to='loop.HelplineIncoming')), | ||
('user_created', models.ForeignKey(related_name='loop_helplineoutgoing_created', blank=True, editable=False, to=settings.AUTH_USER_MODEL, null=True)), | ||
('user_modified', models.ForeignKey(related_name='loop_helplineoutgoing_related_modified', blank=True, editable=False, to=settings.AUTH_USER_MODEL, null=True)), | ||
], | ||
), | ||
migrations.CreateModel( | ||
name='HelplineSmsLog', | ||
fields=[ | ||
('time_created', models.DateTimeField(auto_now_add=True, null=True)), | ||
('time_modified', models.DateTimeField(auto_now=True, null=True)), | ||
('id', models.AutoField(serialize=False, primary_key=True)), | ||
('sms_id', models.CharField(max_length=100)), | ||
('from_number', models.CharField(max_length=20)), | ||
('to_number', models.CharField(max_length=20)), | ||
('sms_body', models.CharField(max_length=2000, null=True, blank=True)), | ||
('sent_time', models.DateTimeField()), | ||
('user_created', models.ForeignKey(related_name='loop_helplinesmslog_created', blank=True, editable=False, to=settings.AUTH_USER_MODEL, null=True)), | ||
('user_modified', models.ForeignKey(related_name='loop_helplinesmslog_related_modified', blank=True, editable=False, to=settings.AUTH_USER_MODEL, null=True)), | ||
], | ||
options={ | ||
'abstract': False, | ||
}, | ||
), | ||
migrations.AlterUniqueTogether( | ||
name='helplineoutgoing', | ||
unique_together=set([('call_id', 'incoming_call', 'from_number', 'outgoing_time')]), | ||
), | ||
migrations.AlterUniqueTogether( | ||
name='helplineincoming', | ||
unique_together=set([('call_id', 'from_number', 'incoming_time')]), | ||
), | ||
] |
Oops, something went wrong.