Skip to content

Commit

Permalink
Merge pull request #669 from digitalgreenorg/loop_ivr
Browse files Browse the repository at this point in the history
Loop ivr - Release V1
  • Loading branch information
lokeshgarg92 committed Feb 16, 2017
2 parents e466e2b + 698225f commit b11aa0e
Show file tree
Hide file tree
Showing 11 changed files with 659 additions and 2 deletions.
Empty file.
28 changes: 28 additions & 0 deletions loop/admin.py
Expand Up @@ -151,6 +151,29 @@ class AggregatorShareOutlierAdmin(admin.ModelAdmin):
class IncentiveParameterAdmin(admin.ModelAdmin):
list_display = ('notation','parameter_name', 'notation_equivalent')

class HelplineExpertAdmin(admin.ModelAdmin):
list_display = ('id', 'name', 'phone_number', 'email_id', 'expert_status')
list_filter = ('expert_status',)
search_fields = ['name', 'phone_number', 'email_id', 'expert_status']

class HelplineIncomingAdmin(admin.ModelAdmin):
list_display = ('id', 'from_number', 'to_number', 'call_status', 'incoming_time', 'last_incoming_time', 'resolved_time', 'recording_url', 'resolved_by', 'acknowledge_user')
list_filter = ('call_status', 'resolved_by')
search_fields = ['call_id', 'from_number', 'to_number', 'call_status', 'resolved_by']

class HelplineOutgoingAdmin(admin.ModelAdmin):
list_display = ('id', 'call_id', 'from_number', 'to_number', 'outgoing_time', 'incoming_call')
search_fields = ['call_id', 'from_number', 'to_number', 'outgoing_time']

class HelplineCallLogAdmin(admin.ModelAdmin):
list_display = ('id', 'call_id', 'from_number', 'to_number', 'start_time', 'call_type')
list_filter = ('call_type',)
search_fields = ['call_id', 'from_number', 'to_number', 'start_time', 'call_type']

class HelplineSmsLogAdmin(admin.ModelAdmin):
list_display = ('id', 'sms_id', 'from_number', 'to_number', 'sent_time')
search_fields = ['from_number', 'to_number', 'sent_time']

loop_admin = LoopAdmin(name='loop_admin')
loop_admin.register(Village, VillageAdmin)
loop_admin.register(Block)
Expand All @@ -177,3 +200,8 @@ class IncentiveParameterAdmin(admin.ModelAdmin):
loop_admin.register(IncentiveModel,IncentiveModelAdmin)
loop_admin.register(IncentiveParameter,IncentiveParameterAdmin)
loop_admin.register(AggregatorShareOutliers,AggregatorShareOutlierAdmin)
loop_admin.register(HelplineExpert,HelplineExpertAdmin)
loop_admin.register(HelplineIncoming,HelplineIncomingAdmin)
loop_admin.register(HelplineOutgoing,HelplineOutgoingAdmin)
loop_admin.register(HelplineCallLog,HelplineCallLogAdmin)
loop_admin.register(HelplineSmsLog,HelplineSmsLogAdmin)
168 changes: 168 additions & 0 deletions loop/helpline_view.py
@@ -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)
64 changes: 64 additions & 0 deletions loop/management/commands/helpline_queue.py
@@ -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)
112 changes: 112 additions & 0 deletions loop/migrations/0007_auto_20170214_1040.py
@@ -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')]),
),
]

0 comments on commit b11aa0e

Please sign in to comment.