Skip to content

Commit 6c7677c

Browse files
committed
feat: application stats
1 parent e8886d5 commit 6c7677c

File tree

7 files changed

+222
-0
lines changed

7 files changed

+222
-0
lines changed
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# coding=utf-8
2+
"""
3+
@project: MaxKB
4+
@Author:虎虎
5+
@file: application_stats.py
6+
@date:2025/6/9 20:45
7+
@desc:
8+
"""
9+
from drf_spectacular.types import OpenApiTypes
10+
from drf_spectacular.utils import OpenApiParameter
11+
12+
from application.serializers.application_stats import ApplicationStatsSerializer
13+
from common.mixins.api_mixin import APIMixin
14+
from common.result import ResultSerializer
15+
16+
17+
class ApplicationStatsResult(ResultSerializer):
18+
def get_data(self):
19+
return ApplicationStatsSerializer(many=True)
20+
21+
22+
class ApplicationStatsAPI(APIMixin):
23+
@staticmethod
24+
def get_parameters():
25+
return [OpenApiParameter(
26+
name="workspace_id",
27+
description="工作空间id",
28+
type=OpenApiTypes.STR,
29+
location='path',
30+
required=True,
31+
),
32+
OpenApiParameter(
33+
name="application_id",
34+
description="application ID",
35+
type=OpenApiTypes.STR,
36+
location='path',
37+
required=True,
38+
),
39+
OpenApiParameter(
40+
name="start_time",
41+
description="start Time",
42+
type=OpenApiTypes.STR,
43+
required=True,
44+
),
45+
OpenApiParameter(
46+
name="end_time",
47+
description="end Time",
48+
type=OpenApiTypes.STR,
49+
required=True,
50+
),
51+
]
52+
53+
@staticmethod
54+
def get_response():
55+
return ApplicationStatsResult
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
# coding=utf-8
2+
"""
3+
@project: MaxKB
4+
@Author:虎虎
5+
@file: application_stats.py
6+
@date:2025/6/9 20:34
7+
@desc:
8+
"""
9+
import datetime
10+
import os
11+
from typing import Dict, List
12+
13+
from django.db import models
14+
from django.db.models import QuerySet
15+
from django.utils.translation import gettext_lazy as _
16+
from rest_framework import serializers
17+
18+
from application.models import ApplicationChatUserStats
19+
from common.db.search import native_search, get_dynamics_model
20+
from common.utils.common import get_file_content
21+
from maxkb.conf import PROJECT_DIR
22+
23+
24+
class ApplicationStatsSerializer(serializers.Serializer):
25+
chat_record_count = serializers.IntegerField(required=True, label=_("Number of conversations"))
26+
customer_added_count = serializers.IntegerField(required=True, label=_("Number of new users"))
27+
customer_num = serializers.IntegerField(required=True, label=_("Total number of users"))
28+
day = serializers.CharField(required=True, label=_("date"))
29+
star_num = serializers.IntegerField(required=True, label=_("Number of Likes"))
30+
tokens_num = serializers.IntegerField(required=True, label=_("Tokens consumption"))
31+
trample_num = serializers.IntegerField(required=True, label=_("Number of thumbs-downs"))
32+
33+
34+
class ApplicationStatisticsSerializer(serializers.Serializer):
35+
application_id = serializers.UUIDField(required=True, label=_("Application ID"))
36+
start_time = serializers.DateField(format='%Y-%m-%d', label=_("Start time"))
37+
end_time = serializers.DateField(format='%Y-%m-%d', label=_("End time"))
38+
39+
def get_end_time(self):
40+
return datetime.datetime.combine(
41+
datetime.datetime.strptime(self.data.get('end_time'), '%Y-%m-%d'),
42+
datetime.datetime.max.time())
43+
44+
def get_start_time(self):
45+
return self.data.get('start_time')
46+
47+
def get_customer_count_trend(self, with_valid=True):
48+
if with_valid:
49+
self.is_valid(raise_exception=True)
50+
start_time = self.get_start_time()
51+
end_time = self.get_end_time()
52+
return native_search(
53+
{'default_sql': QuerySet(ApplicationChatUserStats).filter(
54+
application_id=self.data.get('application_id'),
55+
create_time__gte=start_time,
56+
create_time__lte=end_time)},
57+
select_string=get_file_content(
58+
os.path.join(PROJECT_DIR, "apps", "application", 'sql', 'customer_count_trend.sql')))
59+
60+
def get_chat_record_aggregate_trend(self, with_valid=True):
61+
if with_valid:
62+
self.is_valid(raise_exception=True)
63+
start_time = self.get_start_time()
64+
end_time = self.get_end_time()
65+
chat_record_aggregate_trend = native_search(
66+
{'default_sql': QuerySet(model=get_dynamics_model(
67+
{'application_chat.application_id': models.UUIDField(),
68+
'application_chat_record.create_time': models.DateTimeField()})).filter(
69+
**{'application_chat.application_id': self.data.get('application_id'),
70+
'application_chat_record.create_time__gte': start_time,
71+
'application_chat_record.create_time__lte': end_time}
72+
)},
73+
select_string=get_file_content(
74+
os.path.join(PROJECT_DIR, "apps", "application", 'sql', 'chat_record_count_trend.sql')))
75+
customer_count_trend = self.get_customer_count_trend(with_valid=False)
76+
return self.merge_customer_chat_record(chat_record_aggregate_trend, customer_count_trend)
77+
78+
def merge_customer_chat_record(self, chat_record_aggregate_trend: List[Dict], customer_count_trend: List[Dict]):
79+
80+
return [{**self.find(chat_record_aggregate_trend, lambda c: c.get('day').strftime('%Y-%m-%d') == day,
81+
{'star_num': 0, 'trample_num': 0, 'tokens_num': 0, 'chat_record_count': 0,
82+
'customer_num': 0,
83+
'day': day}),
84+
**self.find(customer_count_trend, lambda c: c.get('day').strftime('%Y-%m-%d') == day,
85+
{'customer_added_count': 0})}
86+
for
87+
day in
88+
self.get_days_between_dates(self.data.get('start_time'), self.data.get('end_time'))]
89+
90+
@staticmethod
91+
def find(source_list, condition, default):
92+
value_list = [row for row in source_list if condition(row)]
93+
if len(value_list) > 0:
94+
return value_list[0]
95+
return default
96+
97+
@staticmethod
98+
def get_days_between_dates(start_date, end_date):
99+
start_date = datetime.datetime.strptime(start_date, '%Y-%m-%d')
100+
end_date = datetime.datetime.strptime(end_date, '%Y-%m-%d')
101+
days = []
102+
current_date = start_date
103+
while current_date <= end_date:
104+
days.append(current_date.strftime('%Y-%m-%d'))
105+
current_date += datetime.timedelta(days=1)
106+
return days
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
SELECT SUM
2+
( CASE WHEN application_chat_record.vote_status = '0' THEN 1 ELSE 0 END ) AS "star_num",
3+
SUM ( CASE WHEN application_chat_record.vote_status = '1' THEN 1 ELSE 0 END ) AS "trample_num",
4+
SUM ( application_chat_record.message_tokens + application_chat_record.answer_tokens ) as "tokens_num",
5+
"count"(application_chat_record."id") as chat_record_count,
6+
"count"(DISTINCT application_chat.chat_user_id) customer_num,
7+
application_chat_record.create_time :: DATE as "day"
8+
FROM
9+
application_chat_record application_chat_record
10+
LEFT JOIN application_chat application_chat ON application_chat."id" = application_chat_record.chat_id
11+
${default_sql}
12+
GROUP BY "day"
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
SELECT
2+
COUNT ( "application_chat_user_stats"."id" ) AS "customer_added_count",
3+
create_time :: DATE as "day"
4+
FROM
5+
"application_chat_user_stats"
6+
${default_sql}
7+
GROUP BY "day"

apps/application/urls.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
path('workspace/<str:workspace_id>/application/<str:application_id>', views.Application.Operate.as_view()),
1414
path('workspace/<str:workspace_id>/application/<str:application_id>/application_key',
1515
views.ApplicationKey.as_view()),
16+
path('workspace/<str:workspace_id>/application/<str:application_id>/application_stats',
17+
views.ApplicationStats.as_view()),
1618
path('workspace/<str:workspace_id>/application/<str:application_id>/application_key/<str:api_key_id>',
1719
views.ApplicationKey.Operate.as_view()),
1820
path('workspace/<str:workspace_id>/application/<str:application_id>/export', views.Application.Export.as_view()),

apps/application/views/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@
1010
from .application import *
1111
from .application_version import *
1212
from .application_access_token import *
13+
from .application_stats import *
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# coding=utf-8
2+
"""
3+
@project: MaxKB
4+
@Author:虎虎
5+
@file: application_stats.py
6+
@date:2025/6/9 20:30
7+
@desc:
8+
"""
9+
from drf_spectacular.utils import extend_schema
10+
from rest_framework.request import Request
11+
from rest_framework.views import APIView
12+
13+
from application.api.application_stats import ApplicationStatsAPI
14+
from application.serializers.application_stats import ApplicationStatisticsSerializer
15+
from common import result
16+
from common.auth import TokenAuth
17+
from django.utils.translation import gettext_lazy as _
18+
19+
20+
class ApplicationStats(APIView):
21+
authentication_classes = [TokenAuth]
22+
23+
@extend_schema(
24+
methods=['GET'],
25+
description=_('Dialogue-related statistical trends'),
26+
summary=_('Dialogue-related statistical trends'),
27+
operation_id=_('Dialogue-related statistical trends'), # type: ignore
28+
parameters=ApplicationStatsAPI.get_parameters(),
29+
responses=ApplicationStatsAPI.get_response(),
30+
tags=[_('Application')] # type: ignore
31+
)
32+
def get(self, request: Request, workspace_id: str, application_id: str):
33+
return result.success(
34+
ApplicationStatisticsSerializer(data={'application_id': application_id,
35+
'start_time': request.query_params.get(
36+
'start_time'),
37+
'end_time': request.query_params.get(
38+
'end_time')
39+
}).get_chat_record_aggregate_trend())

0 commit comments

Comments
 (0)