Commit d3b2bac2 authored by zhengjinlei's avatar zhengjinlei

增加客户画像

parent 4fd706a0
...@@ -4,3 +4,5 @@ from .toolapi import ToolsApi ...@@ -4,3 +4,5 @@ from .toolapi import ToolsApi
from .tasksapi import TasksApi from .tasksapi import TasksApi
from .dataapi import DataApi from .dataapi import DataApi
from .agencyapi import AgencyApi from .agencyapi import AgencyApi
from .profileapi import ProfileApi
from .openapi import OpenApi
# coding: utf-8
from rest_framework import viewsets
from rest_framework.decorators import action
from rest_framework.request import Request
from rest_framework.response import Response
from inspect_report import permissions
from inspect_report.models import CustomProfile
import json
import logging
logger = logging.getLogger(__name__)
class OpenApi(viewsets.ViewSet):
authentication_classes = ()
permission_classes = (permissions.ValidateToken, )
# permission_classes = ()
@action(['get'], detail=False)
def labels(self, req: Request):
phone_num = req.GET.get('phone_num')
if not phone_num:
logger.error('[phone_label]---invalid params')
return Response({'code': -1, 'msg': '请传入手机号'})
labels = []
profile = CustomProfile.objects.filter(phone_number=phone_num).first()
if not profile:
return Response({'code': 0, 'msg': '查询成功', 'data': json.dumps(labels)})
if profile.custom_labels:
labels = profile.custom_labels.split('、')
return Response({'code': 0, 'msg': '查询成功', 'data': labels})
# coding: utf-8
from django.core.paginator import Paginator, PageNotAnInteger, EmptyPage
from rest_framework import viewsets
from rest_framework.decorators import action
from rest_framework.request import Request
from rest_framework.response import Response
from before_request import before_request
from config.config import name_list
from inspect_report.models import CustomProfile
import logging
logger = logging.getLogger(__name__)
class ProfileApi(viewsets.ViewSet):
authentication_classes = ()
permission_classes = ()
@action(['get'], detail=False)
@before_request
def labels(self, req: Request):
page = req.GET.get('page', '1')
page_size = req.GET.get('page_size', '10')
region = req.GET.get('region', '')
username = req.data.get('username', '')
if username in name_list:
logger.info('[profile_labels]获取的username是: %s', username)
region = username
condition = {}
if region:
condition['region'] = region
profiles = CustomProfile.objects.filter(**condition).values('phone_number', 'region', 'call_recent',
'call_count', 'custom_labels')
paginator = Paginator(profiles, page_size)
total_count = paginator.count
try:
objects = paginator.page(page)
except PageNotAnInteger:
objects = paginator.page(1)
except EmptyPage:
objects = paginator.page(paginator.num_pages)
return Response({'code': 0, 'msg': '查询成功', 'total': total_count, 'data': objects.object_list})
# coding: utf-8 # coding: utf-8
from inspect_report.models import Tasks, CheckSession, RulesStat, SeatStat, ScoreStat, Team, Seat from inspect_report.models import Tasks, CheckSession, RulesStat
from config.config import TABLE_PRE from config.config import TABLE_PRE
import json import json
import logging import logging
import kronos import kronos
from datetime import datetime, timedelta from datetime import datetime, timedelta
from inspect_report.utils.report_utils import single_rules_stat, single_seat_stat, single_score_stat from inspect_report.utils.report_utils import single_rules_stat, single_seat_stat, single_score_stat, single_data_stat
"""定时任务 """定时任务
...@@ -15,7 +15,7 @@ from inspect_report.utils.report_utils import single_rules_stat, single_seat_sta ...@@ -15,7 +15,7 @@ from inspect_report.utils.report_utils import single_rules_stat, single_seat_sta
logger = logging.getLogger('app_file') logger = logging.getLogger('app_file')
@kronos.register('30 6 * * *') # @kronos.register('30 6 * * *')
def rule_stat(date_str=None): def rule_stat(date_str=None):
""" """
首页概述-违规项统计 首页概述-违规项统计
...@@ -39,7 +39,7 @@ def rule_stat(date_str=None): ...@@ -39,7 +39,7 @@ def rule_stat(date_str=None):
logger.info('[rule_stat]rule stat end.') logger.info('[rule_stat]rule stat end.')
@kronos.register('30 6 * * *') # @kronos.register('30 6 * * *')
def seat_stat(date_str=None): def seat_stat(date_str=None):
""" """
首页概述-违规坐席统计 首页概述-违规坐席统计
...@@ -58,7 +58,7 @@ def seat_stat(date_str=None): ...@@ -58,7 +58,7 @@ def seat_stat(date_str=None):
logger.info('[seat_stat]violate seat stat end.') logger.info('[seat_stat]violate seat stat end.')
@kronos.register('30 6 * * *') # @kronos.register('30 6 * * *')
def score_stat(date_str=None): def score_stat(date_str=None):
""" """
坐席得分统计 坐席得分统计
...@@ -77,6 +77,25 @@ def score_stat(date_str=None): ...@@ -77,6 +77,25 @@ def score_stat(date_str=None):
logger.info('[score_stat]seat score stat end.') logger.info('[score_stat]seat score stat end.')
@kronos.register('30 6 * * *')
def data_stat(date_str=None):
"""
坐席得分统计
:return:
"""
if not date_str:
date_str = datetime.now().strftime('%Y-%m-%d')
create_date = date_str
logger.info('[data_stat]data stat start...')
task_condition = {'hasCheck': 1, 'name__endswith': date_str}
tasks = Tasks.objects.filter(**task_condition).values('id', 'name', 'sessionCollectionId', 'extra')
stat_count = 0
for t in tasks:
single_data_stat(t, create_date, stat_count)
logger.info('[data_stat]seat count: [%s]', stat_count)
logger.info('[data_stat]seat score stat end.')
def seat_time_stat(start_date=None, end_date=None): def seat_time_stat(start_date=None, end_date=None):
""" """
首页概述-违规坐席统计 首页概述-违规坐席统计
......
...@@ -246,6 +246,45 @@ class ScoreTemplate(models.Model): ...@@ -246,6 +246,45 @@ class ScoreTemplate(models.Model):
db_table = 'score_template' db_table = 'score_template'
class CustomProfile(models.Model):
"""客户画像"""
name = models.CharField('客户姓名', max_length=190, null=True, blank=True)
phone_number = models.CharField('客户手机号', max_length=20, null=True, blank=True)
call_recent = models.DateTimeField('最近通话日期', null=True, blank=True)
call_count = models.IntegerField('通话次数', default=0)
custom_labels = models.CharField('客户标签', max_length=2000, null=True, blank=True)
region = models.CharField('所属地市', max_length=100, null=True, blank=True)
create_at = models.DateTimeField('创建时间', blank=True, null=True)
update_at = models.DateTimeField('更新时间', blank=True, null=True)
def __str__(self):
return self.phone_number
class Meta:
db_table = 'custom_profile'
class CustomLabel(models.Model):
"""客户标签"""
profile_id = models.IntegerField('客户画像主键', null=True, blank=True)
call_time = models.DateTimeField('通话日期', null=True, blank=True)
label = models.CharField('客户标签', max_length=100, null=True, blank=True)
team = models.CharField('呼叫团队', null=True, max_length=100)
agent_name = models.CharField('坐席名称', null=True, max_length=64)
task_id = models.IntegerField('质检任务标识')
session_id = models.CharField('会话唯一标识', max_length=64)
session_table_id = models.IntegerField('会话主键')
session_collection_id = models.CharField('所在表', null=True, max_length=32)
create_at = models.DateTimeField('创建时间', blank=True, null=True)
update_at = models.DateTimeField('更新时间', blank=True, null=True)
def __str__(self):
return self.label
class Meta:
db_table = 'custom_label'
class Round(Func): class Round(Func):
function = 'ROUND' function = 'ROUND'
arity = 2 arity = 2
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
import logging import logging
from rest_framework.permissions import BasePermission from rest_framework.permissions import BasePermission
from rest_framework.authentication import exceptions from rest_framework.authentication import exceptions
from config.config import CUSTOM_PROFILE_TOKEN
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
...@@ -10,17 +11,18 @@ class ValidateToken(BasePermission): ...@@ -10,17 +11,18 @@ class ValidateToken(BasePermission):
""" token 正确才可以调用 """ """ token 正确才可以调用 """
def has_permission(self, request, view): def has_permission(self, request, view):
try: try:
http_auth = request.GET.get('token', '') http_auth = request.META['HTTP_AUTHORIZATION']
if http_auth: if http_auth:
# token = http_auth.split(' ')[1] token = http_auth.split(' ')[1]
# if http_auth.split(' ')[0] != 'Token': if http_auth.split(' ')[0] != 'Token':
# log.error('token 认证异常 | HTTP_WXAUTH 中缺少 Token') log.error('token 认证异常 | HTTP_AUTHORIZATION 中缺少 Token')
# raise exceptions.AuthenticationFailed(detail={'code': -5, 'msg': 'token 认证异常'}) raise exceptions.AuthenticationFailed(detail={'code': -5,
# key = request.session.get(token, None) 'msg': 'token认证异常,HTTP_AUTHORIZATION 中缺少 Token'})
# if not key: if token == CUSTOM_PROFILE_TOKEN:
# log.error('token 认证异常 | Token不存在或者已过期') log.info('token 认证成功')
# raise exceptions.AuthenticationFailed(detail={'code': -5, 'msg': '无效的token'})
return True return True
else:
raise exceptions.AuthenticationFailed(detail={'code': -5, 'msg': 'token 认证异常'})
except KeyError as ke: except KeyError as ke:
log.error('token 认证异常 | header中缺少HTTP_WXAUTH | 异常信息 : KeyError : %s', str(ke)) log.error('token 认证异常 | header中缺少HTTP_WXAUTH | 异常信息 : KeyError : %s', str(ke))
raise exceptions.AuthenticationFailed(detail={'code': -5, 'msg': 'token 认证异常'}) raise exceptions.AuthenticationFailed(detail={'code': -5, 'msg': 'token 认证异常'})
......
...@@ -16,7 +16,7 @@ Including another URLconf ...@@ -16,7 +16,7 @@ Including another URLconf
from django.contrib import admin from django.contrib import admin
from django.urls import path from django.urls import path
from .api.tasksapi import rule, seat, group, score, check, city_score from .api.tasksapi import rule, seat, group, score, check, city_score
from .views import stat_rules_every, stat_seats_every, stat_scores_every, stat_time_every from .views import stat_rules_every, stat_seats_every, stat_scores_every, stat_time_every, stat_data_every
urlpatterns = [ urlpatterns = [
# path('admin/', admin.site.urls), # path('admin/', admin.site.urls),
...@@ -30,4 +30,5 @@ urlpatterns = [ ...@@ -30,4 +30,5 @@ urlpatterns = [
path('inspect/stat/seat/', stat_seats_every), path('inspect/stat/seat/', stat_seats_every),
path('inspect/stat/score/', stat_scores_every), path('inspect/stat/score/', stat_scores_every),
path('inspect/stat/time/', stat_time_every), path('inspect/stat/time/', stat_time_every),
path('inspect/stat/data/', stat_data_every),
] ]
...@@ -23,7 +23,9 @@ router = routers.DefaultRouter() ...@@ -23,7 +23,9 @@ router = routers.DefaultRouter()
router.register(r'tools', api.ToolsApi, base_name='tools') router.register(r'tools', api.ToolsApi, base_name='tools')
router.register(r'tasks', api.TasksApi, base_name='tasks') router.register(r'tasks', api.TasksApi, base_name='tasks')
router.register(r'data', api.DataApi, base_name='data') router.register(r'data', api.DataApi, base_name='data')
router.register(r'agency', api.AgencyApi, base_name='data') router.register(r'profile', api.ProfileApi, base_name='profile')
router.register(r'agency', api.AgencyApi, base_name='agency')
router.register(r'custom', api.OpenApi, base_name='custom')
# Wire up our API using automatic URL routing. # Wire up our API using automatic URL routing.
# Additionally, we include login URLs for the browsable API. # Additionally, we include login URLs for the browsable API.
......
...@@ -8,7 +8,8 @@ from datetime import datetime, timedelta ...@@ -8,7 +8,8 @@ from datetime import datetime, timedelta
from django.db.models import Sum from django.db.models import Sum
from inspect_report.models import RulesStat, CheckSession, Team, Seat, SeatStat, ScoreStat, ScoreTemplate, ScoreItems from inspect_report.models import RulesStat, CheckSession, Team, Seat, SeatStat, ScoreStat, ScoreTemplate, ScoreItems, \
CustomProfile, CustomLabel
from config.config import TABLE_PRE from config.config import TABLE_PRE
logger = logging.getLogger('app_file') logger = logging.getLogger('app_file')
...@@ -86,7 +87,10 @@ def single_rules_stat(t, create_date, stat_count=0): ...@@ -86,7 +87,10 @@ def single_rules_stat(t, create_date, stat_count=0):
remain = get_remain(check) remain = get_remain(check)
if 'isViolation' in d.keys() and d['isViolation'] and 'rule' in d.keys() and 'name' in d['rule'].keys(): if 'isViolation' in d.keys() and d['isViolation'] and 'rule' in d.keys() and 'name' in d['rule'].keys():
name = d['rule']['name'] name = d['rule']['name']
name = name.replace('【', '[').replace('】', ']')
if name and '[客户画像]' in name:
save_custom_profile(t, check, name)
else:
rule_obj = {'create_date': create_date, 'sessionCollectionId': t['sessionCollectionId'], rule_obj = {'create_date': create_date, 'sessionCollectionId': t['sessionCollectionId'],
'rule': name, 'rule_num': 1, 'rule': name, 'rule_num': 1,
'task': team_seat().get(check['agentName'], '未找到团队'), 'task': team_seat().get(check['agentName'], '未找到团队'),
...@@ -196,3 +200,165 @@ def delete_stat_by_task(task): ...@@ -196,3 +200,165 @@ def delete_stat_by_task(task):
RulesStat.objects.filter(taskId=task.id).delete() RulesStat.objects.filter(taskId=task.id).delete()
SeatStat.objects.filter(taskId=task.id).delete() SeatStat.objects.filter(taskId=task.id).delete()
ScoreStat.objects.filter(taskId=task.id).delete() ScoreStat.objects.filter(taskId=task.id).delete()
def save_custom_profile(task, check, name):
label = name.replace('[客户画像]', '')
custom = check['customName'].split('&') if check['customName'] else []
region = task['name'].split('_')[0]
if custom:
custom_name = custom[0]
custom_phone = custom[-1]
profile = CustomProfile.objects.filter(phone_number=custom_phone).first()
team = '未找到团队'
# team = team_seat().get(check['agentName'], '未找到团队')
call_time = datetime.strptime(check['startTime'].replace('.0', ''), "%Y-%m-%d %H:%M:%S") if check['startTime'] \
else None
if profile:
if call_time:
profile.call_recent = call_time
profile.call_count += 1
if not profile.custom_labels:
profile.custom_labels = label
if label not in profile.custom_labels.split('、'):
profile.custom_labels += f'、{label}'
profile.save()
else:
profile = CustomProfile.objects.create(name=custom_name, phone_number=custom_phone,
call_recent=call_time,
call_count=1, custom_labels=label, region=region,
create_at=datetime.now(), update_at=datetime.now())
CustomLabel.objects.create(profile_id=profile.id, call_time=call_time,
label=label, team=team, agent_name=check['agentName'], task_id=check['taskId'],
session_id=check['sessionId'], session_table_id=check['id'],
session_collection_id=task['sessionCollectionId'], create_at=datetime.now(),
update_at=datetime.now())
def single_data_stat(t, create_date, stat_count=0):
rule_stat_flag = False
seat_stat_flag = False
score_stat_flag = False
if not RulesStat.objects.filter(taskId=t['id']).exists():
rule_stat_flag = True
if not SeatStat.objects.filter(taskId=t['id']).exists():
seat_stat_flag = True
if not ScoreStat.objects.filter(taskId=t['id']).exists():
score_stat_flag = True
if rule_stat_flag or seat_stat_flag or score_stat_flag:
table_name = TABLE_PRE + t['sessionCollectionId']
tn = CheckSession.set_table(table_name)
session_condition = {'taskId': t['id']}
checks = tn.objects.filter(**session_condition).values('id', 'checkResult', 'agentName', 'customName', 'score',
'remainTime', 'startTime', 'closeTime', 'taskId',
'sessionId', 'violationRuleCount', 'scoreItemRecord')
seat_dict = {}
seat_list = []
score_dict = {}
score_list = []
service_s, business_s, vz, score_item_service, score_item_business, score_item_zero = get_inspect_rule(t)
for check in checks:
stat_count += 1
validate_rule_count = 0
remain = get_remain(check)
result = check['checkResult']
# rule_stat
if result:
rules_list = []
data = json.loads(result)
for d in data:
if 'isViolation' in d.keys() and d['isViolation'] and 'rule' in d.keys() and 'name' in d['rule'].keys():
name = d['rule']['name']
name = name.replace('【', '[').replace('】', ']')
if name and '[客户画像]' in name:
if rule_stat_flag:
save_custom_profile(t, check, name)
else:
validate_rule_count += 1
if rule_stat_flag:
rule_obj = {'create_date': create_date, 'sessionCollectionId': t['sessionCollectionId'],
'rule': name, 'rule_num': 1,
'task': '未找到团队',
# 'task': team_seat().get(check['agentName'], '未找到团队'),
'agentName': check['agentName'], 'customName': check['customName'],
'score': check['score'], 'remainTime': remain,
'taskId': check['taskId'], 'sessionId': check['sessionId'],
'session_table_id': check['id']}
rules_list.append(RulesStat(**rule_obj))
if rule_stat_flag:
RulesStat.objects.bulk_create(rules_list)
logger.info('data stat num: check编号为:[%s]违规项统计完毕', check['id'])
# seat_stat
if seat_stat_flag:
if check['agentName'] in seat_dict.keys():
seat = seat_dict.get(check['agentName'])
seat['total_session'] += 1
seat['remainTime'] += remain
seat['validate_session'] += 1 if validate_rule_count > 0 else 0
else:
seat_dict[check['agentName']] = {'create_date': create_date, 'agentName': check['agentName'],
'taskId': check['taskId'],
'task': '未找到团队',
# 'task': team_seat().get(check['agentName'], '未找到团队'),
'sessionCollectionId': t['sessionCollectionId'],
'total_session': 1}
seat_dict[check['agentName']]['validate_session'] = 1 if validate_rule_count > 0 else 0
seat_dict[check['agentName']]['remainTime'] = remain
# score_stat
if score_stat_flag:
score_item = json.loads(check['scoreItemRecord'])
service_score = 35
business_score = 65
validate_zero = 0
for m in score_item:
if m['scoreItemId'] in score_item_service.keys():
service_score -= m['score'] if m['scoreType'] == 1 else -m['score']
if m['scoreItemId'] in score_item_business.keys():
business_score -= m['score'] if m['scoreType'] == 1 else -m['score']
if m['scoreItemId'] in score_item_zero.keys():
validate_zero += 1
if validate_zero > 0:
score = 0
else:
score = service_score + business_score
if check['agentName'] in score_dict.keys():
seat = score_dict.get(check['agentName'])
seat['total_session'] += 1
seat['validate_session'] += 1 if validate_rule_count > 0 else 0
seat['validate_num'] += validate_rule_count
seat['score'] += score
seat['service_score'] += service_score
seat['business_score'] += business_score
seat['validate_zero'] += validate_zero
else:
score_dict[check['agentName']] = {'create_date': create_date, 'agentName': check['agentName'],
'taskId': check['taskId'],
'task': '未找到团队',
# 'task': team_seat().get(check['agentName'], '未找到团队'),
'sessionCollectionId': t['sessionCollectionId'], 'total_session': 1,
'score': score, 'service_score': service_score,
'business_score': business_score, 'validate_zero': validate_zero}
score_dict[check['agentName']]['validate_session'] = 1 if validate_rule_count > 0 else 0
score_dict[check['agentName']]['validate_num'] = validate_rule_count
logger.info('data stat num: [%s]', stat_count)
if seat_stat_flag:
for k, v in seat_dict.items():
seat_obj = {'agentName': k}
seat_obj.update(v)
seat_list.append(SeatStat(**seat_obj))
SeatStat.objects.bulk_create(seat_list)
logger.info('data stat num: 任务编号为:[%s]坐席统计完毕', t['id'])
if score_stat_flag:
for k, v in score_dict.items():
score_obj = {'agentName': k, 'score': round(v.pop('score') / v['total_session'], 2),
'business_score': round(v.pop('business_score') / v['total_session'], 2),
'service_score': round(v.pop('service_score') / v['total_session'], 2)}
score_obj.update(v)
score_list.append(ScoreStat(**score_obj))
ScoreStat.objects.bulk_create(score_list)
logger.info('data stat num: 任务编号为:[%s]得分统计完毕', t['id'])
from datetime import datetime, timedelta from datetime import datetime, timedelta
from .cron import rule_stat, seat_stat, score_stat, seat_time_stat from .cron import rule_stat, seat_stat, score_stat, seat_time_stat, data_stat
from django.http.response import JsonResponse from django.http.response import JsonResponse
from dateutil.rrule import rrule, DAILY from dateutil.rrule import rrule, DAILY
...@@ -41,6 +41,18 @@ def stat_scores_every(request): ...@@ -41,6 +41,18 @@ def stat_scores_every(request):
return JsonResponse({'code': 0, 'msg': 'stat success'}) return JsonResponse({'code': 0, 'msg': 'stat success'})
def stat_data_every(request):
start_date = request.GET.get('start_date')
end_date = request.GET.get('end_date')
s_date = datetime.strptime(start_date, '%Y-%m-%d')
e_date = datetime.strptime(end_date, '%Y-%m-%d')
seed_dt = list(rrule(DAILY, byweekday=[0, 1, 2, 3, 4, 5, 6], dtstart=s_date, until=e_date))
for date in seed_dt:
date_str = date.strftime('%Y-%m-%d')
data_stat(date_str)
return JsonResponse({'code': 0, 'msg': 'stat success'})
def stat_time_every(request): def stat_time_every(request):
start_date = request.GET.get('start_date') start_date = request.GET.get('start_date')
end_date = request.GET.get('end_date') end_date = request.GET.get('end_date')
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment