-
Notifications
You must be signed in to change notification settings - Fork 2.8k
feat: application stats #3225
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: application stats #3225
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| # coding=utf-8 | ||
| """ | ||
| @project: MaxKB | ||
| @Author:虎虎 | ||
| @file: application_stats.py | ||
| @date:2025/6/9 20:45 | ||
| @desc: | ||
| """ | ||
| from drf_spectacular.types import OpenApiTypes | ||
| from drf_spectacular.utils import OpenApiParameter | ||
|
|
||
| from application.serializers.application_stats import ApplicationStatsSerializer | ||
| from common.mixins.api_mixin import APIMixin | ||
| from common.result import ResultSerializer | ||
|
|
||
|
|
||
| class ApplicationStatsResult(ResultSerializer): | ||
| def get_data(self): | ||
| return ApplicationStatsSerializer(many=True) | ||
|
|
||
|
|
||
| class ApplicationStatsAPI(APIMixin): | ||
| @staticmethod | ||
| def get_parameters(): | ||
| return [OpenApiParameter( | ||
| name="workspace_id", | ||
| description="工作空间id", | ||
| type=OpenApiTypes.STR, | ||
| location='path', | ||
| required=True, | ||
| ), | ||
| OpenApiParameter( | ||
| name="application_id", | ||
| description="application ID", | ||
| type=OpenApiTypes.STR, | ||
| location='path', | ||
| required=True, | ||
| ), | ||
| OpenApiParameter( | ||
| name="start_time", | ||
| description="start Time", | ||
| type=OpenApiTypes.STR, | ||
| required=True, | ||
| ), | ||
| OpenApiParameter( | ||
| name="end_time", | ||
| description="end Time", | ||
| type=OpenApiTypes.STR, | ||
| required=True, | ||
| ), | ||
| ] | ||
|
|
||
| @staticmethod | ||
| def get_response(): | ||
| return ApplicationStatsResult | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,106 @@ | ||
| # coding=utf-8 | ||
| """ | ||
| @project: MaxKB | ||
| @Author:虎虎 | ||
| @file: application_stats.py | ||
| @date:2025/6/9 20:34 | ||
| @desc: | ||
| """ | ||
| import datetime | ||
| import os | ||
| from typing import Dict, List | ||
|
|
||
| from django.db import models | ||
| from django.db.models import QuerySet | ||
| from django.utils.translation import gettext_lazy as _ | ||
| from rest_framework import serializers | ||
|
|
||
| from application.models import ApplicationChatUserStats | ||
| from common.db.search import native_search, get_dynamics_model | ||
| from common.utils.common import get_file_content | ||
| from maxkb.conf import PROJECT_DIR | ||
|
|
||
|
|
||
| class ApplicationStatsSerializer(serializers.Serializer): | ||
| chat_record_count = serializers.IntegerField(required=True, label=_("Number of conversations")) | ||
| customer_added_count = serializers.IntegerField(required=True, label=_("Number of new users")) | ||
| customer_num = serializers.IntegerField(required=True, label=_("Total number of users")) | ||
| day = serializers.CharField(required=True, label=_("date")) | ||
| star_num = serializers.IntegerField(required=True, label=_("Number of Likes")) | ||
| tokens_num = serializers.IntegerField(required=True, label=_("Tokens consumption")) | ||
| trample_num = serializers.IntegerField(required=True, label=_("Number of thumbs-downs")) | ||
|
|
||
|
|
||
| class ApplicationStatisticsSerializer(serializers.Serializer): | ||
| application_id = serializers.UUIDField(required=True, label=_("Application ID")) | ||
| start_time = serializers.DateField(format='%Y-%m-%d', label=_("Start time")) | ||
| end_time = serializers.DateField(format='%Y-%m-%d', label=_("End time")) | ||
|
|
||
| def get_end_time(self): | ||
| 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_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(ApplicationChatUserStats).filter( | ||
| application_id=self.data.get('application_id'), | ||
| 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_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() | ||
| 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': 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 | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
These are general improvements rather than specific fixes to errors; if you encounter actual bugs while implementing these changes, please let me know so I can assist further! |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| 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"(application_chat_record."id") as chat_record_count, | ||
| "count"(DISTINCT application_chat.chat_user_id) customer_num, | ||
| application_chat_record.create_time :: DATE as "day" | ||
| FROM | ||
| application_chat_record application_chat_record | ||
| LEFT JOIN application_chat application_chat ON application_chat."id" = application_chat_record.chat_id | ||
| ${default_sql} | ||
| GROUP BY "day" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| SELECT | ||
| COUNT ( "application_chat_user_stats"."id" ) AS "customer_added_count", | ||
| create_time :: DATE as "day" | ||
| FROM | ||
| "application_chat_user_stats" | ||
| ${default_sql} | ||
| GROUP BY "day" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| # coding=utf-8 | ||
| """ | ||
| @project: MaxKB | ||
| @Author:虎虎 | ||
| @file: application_stats.py | ||
| @date:2025/6/9 20:30 | ||
| @desc: | ||
| """ | ||
| from drf_spectacular.utils import extend_schema | ||
| from rest_framework.request import Request | ||
| from rest_framework.views import APIView | ||
|
|
||
| from application.api.application_stats import ApplicationStatsAPI | ||
| from application.serializers.application_stats import ApplicationStatisticsSerializer | ||
| from common import result | ||
| from common.auth import TokenAuth | ||
| from django.utils.translation import gettext_lazy as _ | ||
|
|
||
|
|
||
| class ApplicationStats(APIView): | ||
| authentication_classes = [TokenAuth] | ||
|
|
||
| @extend_schema( | ||
| methods=['GET'], | ||
| description=_('Dialogue-related statistical trends'), | ||
| summary=_('Dialogue-related statistical trends'), | ||
| operation_id=_('Dialogue-related statistical trends'), # type: ignore | ||
| parameters=ApplicationStatsAPI.get_parameters(), | ||
| responses=ApplicationStatsAPI.get_response(), | ||
| tags=[_('Application')] # type: ignore | ||
| ) | ||
| def get(self, request: Request, workspace_id: str, application_id: str): | ||
| return result.success( | ||
| ApplicationStatisticsSerializer(data={'application_id': application_id, | ||
| 'start_time': request.query_params.get( | ||
| 'start_time'), | ||
| 'end_time': request.query_params.get( | ||
| 'end_time') | ||
| }).get_chat_record_aggregate_trend()) | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The provided code seems relatively clean and should work fine within its current context. Here are some minor suggestions:
Here is a revised version of the code with these considerations: # coding=utf-8
"""
@project: MaxKB
@Author:虎虎
@file: application_stats.py
@date:2025/6/9 20:30
@desc:
"""
import json
from rest_framework.response import Response
from rest_framework.request import Request
from rest_framework.views import APIView
from rest_framework.authentication import TokenAuthentication
from rest_framework.permissions import IsAuthenticated
from django.utils.decorators import method_decorator
from django.utils.translation import gettext_lazy as _
from application.api.application_stats import ApplicationStatsAPI
from application.serializers.application_stats import ApplicationStatisticsSerializer
class ApplicationStats(APIView):
authentication_classes = (TokenAuthentication,)
permission_classes = (IsAuthenticated,)
@method_decorator(extend_schema)
def get(self, request: Request, workspace_id: str, application_id: str) -> Response:
start_time = request.query_params.get('start_time', None)
end_time = request.query_params.get('end_time', None)
data = {
'application_id': application_id,
'start_time': start_time,
'end_time': end_time
}
agg_trend = ApplicationStatisticsSerializer(data=data).get_chat_record_aggregate_trend()
aggregated_data = {'agg_trend': agg_trend}
return Response(json.dumps(aggregated_data), status=200)
Key Changes:
This revision maintains clarity while adhering to modern best practices in RESTful APIs. |
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Your code looks generally solid for a basic Django REST Framework (DRF) viewset that handles retrieving statistical data about applications within a specific workspace and time frame. However, there are a few areas where you can make improvements or optimizations:
Improvements
Model Import: Ensure
Applicationis imported fromcommon.models.application_model, assuming that's its correct module.Documentation Comments: Use proper docstrings to describe the purpose of each method and class.
Type Annotations: Add type annotations for clarity. While not strictly necessary, they enhance readability and maintainability.
Error Handling: Consider adding error handling for invalid inputs such as non-existent
workspace_idorapplication_id.Response Formatting: Make sure the response format aligns with expected standards.
Enhanced Code Example
This enhanced version introduces asynchronous capabilities, includes detailed documentation, type hints, adds more explicit parameter checking during requests, and moves some functionality into separate methods for better organization and reusability.