diff --git a/apps/application/chat_pipeline/step/chat_step/i_chat_step.py b/apps/application/chat_pipeline/step/chat_step/i_chat_step.py index f18fbc211..fe4bc1dfd 100644 --- a/apps/application/chat_pipeline/step/chat_step/i_chat_step.py +++ b/apps/application/chat_pipeline/step/chat_step/i_chat_step.py @@ -44,7 +44,7 @@ class PostResponseHandler: @abstractmethod def handler(self, chat_id, chat_record_id, paragraph_list: List[ParagraphPipelineModel], problem_text: str, answer_text, - manage, step, padding_problem_text: str = None, **kwargs): + manage, step, padding_problem_text: str = None, client_id=None, **kwargs): pass diff --git a/apps/application/chat_pipeline/step/chat_step/impl/base_chat_step.py b/apps/application/chat_pipeline/step/chat_step/impl/base_chat_step.py index 442f359d0..808b43de3 100644 --- a/apps/application/chat_pipeline/step/chat_step/impl/base_chat_step.py +++ b/apps/application/chat_pipeline/step/chat_step/impl/base_chat_step.py @@ -67,7 +67,7 @@ def event_content(response, manage.context['message_tokens'] = manage.context['message_tokens'] + request_token manage.context['answer_tokens'] = manage.context['answer_tokens'] + response_token post_response_handler.handler(chat_id, chat_record_id, paragraph_list, problem_text, - all_text, manage, step, padding_problem_text) + all_text, manage, step, padding_problem_text, client_id) yield 'data: ' + json.dumps({'chat_id': str(chat_id), 'id': str(chat_record_id), 'operate': True, 'content': '', 'is_end': True}) + "\n\n" add_access_num(client_id, client_type) @@ -172,7 +172,7 @@ class BaseChatStep(IChatStep): manage.context['message_tokens'] = manage.context['message_tokens'] + request_token manage.context['answer_tokens'] = manage.context['answer_tokens'] + response_token post_response_handler.handler(chat_id, chat_record_id, paragraph_list, problem_text, - chat_result.content, manage, self, padding_problem_text) + chat_result.content, manage, self, padding_problem_text, client_id) add_access_num(client_id, client_type) return result.success({'chat_id': str(chat_id), 'id': str(chat_record_id), 'operate': True, 'content': chat_result.content, 'is_end': True}) diff --git a/apps/application/migrations/0002_chat_client_id.py b/apps/application/migrations/0002_chat_client_id.py new file mode 100644 index 000000000..37d900ef4 --- /dev/null +++ b/apps/application/migrations/0002_chat_client_id.py @@ -0,0 +1,18 @@ +# Generated by Django 4.1.13 on 2024-03-28 13:59 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('application', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='chat', + name='client_id', + field=models.UUIDField(default=None, null=True, verbose_name='客户端id'), + ), + ] diff --git a/apps/application/models/application.py b/apps/application/models/application.py index ecd884c2b..e20e92385 100644 --- a/apps/application/models/application.py +++ b/apps/application/models/application.py @@ -69,6 +69,7 @@ class Chat(AppModelMixin): id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid1, editable=False, verbose_name="主键id") application = models.ForeignKey(Application, on_delete=models.CASCADE) abstract = models.CharField(max_length=256, verbose_name="摘要") + client_id = models.UUIDField(verbose_name="客户端id", default=None, null=True) class Meta: db_table = "application_chat" diff --git a/apps/application/serializers/application_statistics_serializers.py b/apps/application/serializers/application_statistics_serializers.py index 3378210a8..e958cb34d 100644 --- a/apps/application/serializers/application_statistics_serializers.py +++ b/apps/application/serializers/application_statistics_serializers.py @@ -8,6 +8,7 @@ """ import datetime import os +from typing import List, Dict from django.db import models from django.db.models.query import QuerySet @@ -22,18 +23,26 @@ from smartdoc.conf import PROJECT_DIR class ApplicationStatisticsSerializer(serializers.Serializer): application_id = serializers.UUIDField(required=True, error_messages=ErrMessage.char("应用id")) - history_day = serializers.IntegerField(required=True, error_messages=ErrMessage.integer("历史天数")) + start_time = serializers.DateField(format='%Y-%m-%d', error_messages=ErrMessage.date("开始时间")) + end_time = serializers.DateField(format='%Y-%m-%d', error_messages=ErrMessage.date("结束时间")) def get_end_time(self): - history_day = self.data.get('history_day') - return datetime.datetime.now() - datetime.timedelta(days=history_day) + return datetime.datetime.combine( + datetime.datetime.strptime(self.data.get('end_time'), '%Y-%m-%d'), + datetime.datetime.max.time()) + + def get_start_time(self): + return self.data.get('start_time') def get_customer_count(self, with_valid=True): if with_valid: self.is_valid(raise_exception=True) + start_time = self.get_start_time() + end_time = self.get_end_time() return native_search( QuerySet(ApplicationPublicAccessClient).filter(application_id=self.data.get('application_id'), - create_time__gte=self.get_end_time()), + create_time__gte=start_time, + create_time__lte=end_time), select_string=get_file_content( os.path.join(PROJECT_DIR, "apps", "application", 'sql', 'customer_count.sql')), with_search_one=True) @@ -41,36 +50,79 @@ class ApplicationStatisticsSerializer(serializers.Serializer): def get_customer_count_trend(self, with_valid=True): if with_valid: self.is_valid(raise_exception=True) + start_time = self.get_start_time() + end_time = self.get_end_time() return native_search( {'default_sql': QuerySet(ApplicationPublicAccessClient).filter( application_id=self.data.get('application_id'), - create_time__gte=self.get_end_time())}, + create_time__gte=start_time, + create_time__lte=end_time)}, select_string=get_file_content( os.path.join(PROJECT_DIR, "apps", "application", 'sql', 'customer_count_trend.sql'))) def get_chat_record_aggregate(self, with_valid=True): if with_valid: self.is_valid(raise_exception=True) - return native_search( + start_time = self.get_start_time() + end_time = self.get_end_time() + chat_record_aggregate = native_search( QuerySet(model=get_dynamics_model( {'application_chat.application_id': models.UUIDField(), 'application_chat_record.create_time': models.DateTimeField()})).filter( **{'application_chat.application_id': self.data.get('application_id'), - 'application_chat_record.create_time__gte': self.get_end_time()} + 'application_chat_record.create_time__gte': start_time, + 'application_chat_record.create_time__lte': end_time} ), select_string=get_file_content( os.path.join(PROJECT_DIR, "apps", "application", 'sql', 'chat_record_count.sql')), with_search_one=True) + customer = self.get_customer_count(with_valid=False) + return {**chat_record_aggregate, **customer} def get_chat_record_aggregate_trend(self, with_valid=True): if with_valid: self.is_valid(raise_exception=True) - return native_search( + start_time = self.get_start_time() + end_time = self.get_end_time() + chat_record_aggregate_trend = native_search( {'default_sql': QuerySet(model=get_dynamics_model( {'application_chat.application_id': models.UUIDField(), 'application_chat_record.create_time': models.DateTimeField()})).filter( **{'application_chat.application_id': self.data.get('application_id'), - 'application_chat_record.create_time__gte': self.get_end_time()} + 'application_chat_record.create_time__gte': start_time, + 'application_chat_record.create_time__lte': end_time} )}, select_string=get_file_content( os.path.join(PROJECT_DIR, "apps", "application", 'sql', 'chat_record_count_trend.sql'))) + customer_count_trend = self.get_customer_count_trend(with_valid=False) + return self.merge_customer_chat_record(chat_record_aggregate_trend, customer_count_trend) + + def merge_customer_chat_record(self, chat_record_aggregate_trend: List[Dict], customer_count_trend: List[Dict]): + + return [{**self.find(chat_record_aggregate_trend, lambda c: c.get('day').strftime('%Y-%m-%d') == day, + {'star_num': 0, 'trample_num': 0, 'tokens_num': 0, 'chat_record_count': 0, + 'customer_num': 0, + 'day': day}), + **self.find(customer_count_trend, lambda c: c.get('day').strftime('%Y-%m-%d') == day, + {'customer_added_count': 0})} + for + day in + self.get_days_between_dates(self.data.get('start_time'), self.data.get('end_time'))] + + @staticmethod + def find(source_list, condition, default): + value_list = [row for row in source_list if condition(row)] + if len(value_list) > 0: + return value_list[0] + return default + + @staticmethod + def get_days_between_dates(start_date, end_date): + start_date = datetime.datetime.strptime(start_date, '%Y-%m-%d') + end_date = datetime.datetime.strptime(end_date, '%Y-%m-%d') + days = [] + current_date = start_date + while current_date <= end_date: + days.append(current_date.strftime('%Y-%m-%d')) + current_date += datetime.timedelta(days=1) + return days diff --git a/apps/application/serializers/chat_message_serializers.py b/apps/application/serializers/chat_message_serializers.py index d94f633f4..cb40dad86 100644 --- a/apps/application/serializers/chat_message_serializers.py +++ b/apps/application/serializers/chat_message_serializers.py @@ -87,13 +87,14 @@ class ChatInfo: 'exclude_paragraph_id_list': exclude_paragraph_id_list, 'stream': stream, 'client_id': client_id, 'client_type': client_type} - def append_chat_record(self, chat_record: ChatRecord): + def append_chat_record(self, chat_record: ChatRecord, client_id=None): # 存入缓存中 self.chat_record_list.append(chat_record) if self.application.id is not None: # 插入数据库 if not QuerySet(Chat).filter(id=self.chat_id).exists(): - Chat(id=self.chat_id, application_id=self.application.id, abstract=chat_record.problem_text).save() + Chat(id=self.chat_id, application_id=self.application.id, abstract=chat_record.problem_text, + client_id=client_id).save() # 插入会话记录 chat_record.save() @@ -110,6 +111,7 @@ def get_post_handler(chat_info: ChatInfo): manage: PiplineManage, step: BaseChatStep, padding_problem_text: str = None, + client_id=None, **kwargs): chat_record = ChatRecord(id=chat_record_id, chat_id=chat_id, @@ -120,7 +122,7 @@ def get_post_handler(chat_info: ChatInfo): answer_tokens=manage.context['answer_tokens'], run_time=manage.context['run_time'], index=len(chat_info.chat_record_list) + 1) - chat_info.append_chat_record(chat_record) + chat_info.append_chat_record(chat_record, client_id) # 重新设置缓存 chat_cache.set(chat_id, chat_info, timeout=60 * 30) diff --git a/apps/application/sql/chat_record_count.sql b/apps/application/sql/chat_record_count.sql index 20b1647b3..0cdbfb9d2 100644 --- a/apps/application/sql/chat_record_count.sql +++ b/apps/application/sql/chat_record_count.sql @@ -2,6 +2,7 @@ SELECT SUM ( CASE WHEN application_chat_record.vote_status = '0' THEN 1 ELSE 0 END ) AS "star_num", SUM ( CASE WHEN application_chat_record.vote_status = '1' THEN 1 ELSE 0 END ) AS "trample_num", SUM ( application_chat_record.message_tokens + application_chat_record.answer_tokens ) as "tokens_num", + "count"(DISTINCT application_chat.client_id) customer_num, "count"(application_chat_record."id") as chat_record_count FROM application_chat_record application_chat_record diff --git a/apps/application/sql/chat_record_count_trend.sql b/apps/application/sql/chat_record_count_trend.sql index 8127de95b..b4499f0e2 100644 --- a/apps/application/sql/chat_record_count_trend.sql +++ b/apps/application/sql/chat_record_count_trend.sql @@ -3,6 +3,7 @@ SELECT SUM SUM ( CASE WHEN application_chat_record.vote_status = '1' THEN 1 ELSE 0 END ) AS "trample_num", SUM ( application_chat_record.message_tokens + application_chat_record.answer_tokens ) as "tokens_num", "count"(application_chat_record."id") as chat_record_count, + "count"(DISTINCT application_chat.client_id) customer_num, application_chat_record.create_time :: DATE as "day" FROM application_chat_record application_chat_record diff --git a/apps/application/sql/customer_count.sql b/apps/application/sql/customer_count.sql index b20b5274f..0e4090890 100644 --- a/apps/application/sql/customer_count.sql +++ b/apps/application/sql/customer_count.sql @@ -1,5 +1,5 @@ SELECT - ( SUM ( CASE WHEN create_time :: DATE = CURRENT_DATE THEN 1 ELSE 0 END ) ) AS "today_added_count", - COUNT ( "application_public_access_client"."id" ) AS "added_count" + ( SUM ( CASE WHEN create_time :: DATE = CURRENT_DATE THEN 1 ELSE 0 END ) ) AS "customer_today_added_count", + COUNT ( "application_public_access_client"."id" ) AS "customer_added_count" FROM "application_public_access_client" \ No newline at end of file diff --git a/apps/application/sql/customer_count_trend.sql b/apps/application/sql/customer_count_trend.sql index e0a006235..159cd031b 100644 --- a/apps/application/sql/customer_count_trend.sql +++ b/apps/application/sql/customer_count_trend.sql @@ -1,5 +1,5 @@ SELECT - COUNT ( "application_public_access_client"."id" ) AS "added_count", + COUNT ( "application_public_access_client"."id" ) AS "customer_added_count", create_time :: DATE as "day" FROM "application_public_access_client" diff --git a/apps/application/swagger_api/application_statistics_api.py b/apps/application/swagger_api/application_statistics_api.py index b30f19f0a..87fde1098 100644 --- a/apps/application/swagger_api/application_statistics_api.py +++ b/apps/application/swagger_api/application_statistics_api.py @@ -19,12 +19,16 @@ class ApplicationStatisticsApi(ApiMixin): type=openapi.TYPE_STRING, required=True, description='应用id'), - openapi.Parameter(name='history_day', + openapi.Parameter(name='start_time', in_=openapi.IN_QUERY, - type=openapi.TYPE_NUMBER, + type=openapi.TYPE_STRING, required=True, - description='历史天数'), - + description='开始时间'), + openapi.Parameter(name='end_time', + in_=openapi.IN_QUERY, + type=openapi.TYPE_STRING, + required=True, + description='结束时间'), ] class ChatRecordAggregate(ApiMixin): @@ -42,6 +46,10 @@ class ApplicationStatisticsApi(ApiMixin): description="token使用数量"), 'chat_record_count': openapi.Schema(type=openapi.TYPE_NUMBER, title="对话次数", description="对话次数"), + 'customer_num': openapi.Schema(type=openapi.TYPE_NUMBER, title="客户数量", + description="客户数量"), + 'customer_added_count': openapi.Schema(type=openapi.TYPE_NUMBER, title="客户新增数量", + description="客户新增数量"), 'day': openapi.Schema(type=openapi.TYPE_STRING, title="日期", description="日期,只有查询趋势的时候才有该字段"), diff --git a/apps/application/views/application_views.py b/apps/application/views/application_views.py index 0f4d9bf8a..12e47f854 100644 --- a/apps/application/views/application_views.py +++ b/apps/application/views/application_views.py @@ -47,8 +47,11 @@ class ApplicationStatistics(APIView): def get(self, request: Request, application_id: str): return result.success( ApplicationStatisticsSerializer(data={'application_id': application_id, - 'history_day': request.query_params.get( - 'history_day')}).get_customer_count()) + 'start_time': request.query_params.get( + 'start_time'), + 'end_time': request.query_params.get( + 'end_time') + }).get_customer_count()) class CustomerCountTrend(APIView): authentication_classes = [TokenAuth] @@ -68,8 +71,11 @@ class ApplicationStatistics(APIView): def get(self, request: Request, application_id: str): return result.success( ApplicationStatisticsSerializer(data={'application_id': application_id, - 'history_day': request.query_params.get( - 'history_day')}).get_customer_count_trend()) + 'start_time': request.query_params.get( + 'start_time'), + 'end_time': request.query_params.get( + 'end_time') + }).get_customer_count_trend()) class ChatRecordAggregate(APIView): authentication_classes = [TokenAuth] @@ -90,8 +96,11 @@ class ApplicationStatistics(APIView): def get(self, request: Request, application_id: str): return result.success( ApplicationStatisticsSerializer(data={'application_id': application_id, - 'history_day': request.query_params.get( - 'history_day')}).get_chat_record_aggregate()) + 'start_time': request.query_params.get( + 'start_time'), + 'end_time': request.query_params.get( + 'end_time') + }).get_chat_record_aggregate()) class ChatRecordAggregateTrend(APIView): authentication_classes = [TokenAuth] @@ -112,8 +121,11 @@ class ApplicationStatistics(APIView): def get(self, request: Request, application_id: str): return result.success( ApplicationStatisticsSerializer(data={'application_id': application_id, - 'history_day': request.query_params.get( - 'history_day')}).get_chat_record_aggregate_trend()) + 'start_time': request.query_params.get( + 'start_time'), + 'end_time': request.query_params.get( + 'end_time') + }).get_chat_record_aggregate_trend()) class Application(APIView): diff --git a/apps/common/util/field_message.py b/apps/common/util/field_message.py index fd961dc8a..3bcf067e1 100644 --- a/apps/common/util/field_message.py +++ b/apps/common/util/field_message.py @@ -86,3 +86,12 @@ class ErrMessage: 'required': gettext_lazy('【%s】此字段必填。' % field), 'null': gettext_lazy('【%s】此字段不能为null。' % field), } + + @staticmethod + def date(field: str): + return { + 'required': gettext_lazy('【%s】此字段必填。' % field), + 'null': gettext_lazy('【%s】此字段不能为null。' % field), + 'invalid': gettext_lazy('【%s】日期格式错误。请改用以下格式之一: {format}。'), + 'datetime': gettext_lazy('【%s】应为日期,但得到的是日期时间。') + }