Atlas / analytics /dashboard.py
findEthics
feat: implement anonymous mode functionality with comprehensive tests
439ebb4
Raw
History Blame Contribute Delete
37.2 kB
"""
Analytics dashboard module for calculating and aggregating statistics.
"""
import logging
from datetime import datetime, timedelta
from typing import Dict, Any, List, Optional
from .database import get_sessions_collection, get_messages_collection
logger = logging.getLogger(__name__)
# Helper functions to count anonymous vs authenticated usage
async def count_anonymous_sessions() -> int:
"""Count sessions with null user_id (anonymous users)"""
try:
sessions_collection = await get_sessions_collection()
if sessions_collection is None:
return 0
return await sessions_collection.count_documents({
"$or": [
{"user_id": None},
{"user_id": {"$exists": False}}
]
})
except Exception as e:
logger.error(f"Error counting anonymous sessions: {e}")
return 0
async def count_authenticated_sessions() -> int:
"""Count sessions with non-null user_id (authenticated users)"""
try:
sessions_collection = await get_sessions_collection()
if sessions_collection is None:
return 0
return await sessions_collection.count_documents({
"user_id": {"$ne": None, "$exists": True}
})
except Exception as e:
logger.error(f"Error counting authenticated sessions: {e}")
return 0
async def count_anonymous_messages() -> int:
"""Count messages with null user_id (anonymous users)"""
try:
messages_collection = await get_messages_collection()
if messages_collection is None:
return 0
return await messages_collection.count_documents({
"$or": [
{"user_id": None},
{"user_id": {"$exists": False}}
]
})
except Exception as e:
logger.error(f"Error counting anonymous messages: {e}")
return 0
async def count_authenticated_messages() -> int:
"""Count messages with non-null user_id (authenticated users)"""
try:
messages_collection = await get_messages_collection()
if messages_collection is None:
return 0
return await messages_collection.count_documents({
"user_id": {"$ne": None, "$exists": True}
})
except Exception as e:
logger.error(f"Error counting authenticated messages: {e}")
return 0
async def count_all_sessions() -> int:
"""Count total sessions (both anonymous and authenticated)"""
try:
sessions_collection = await get_sessions_collection()
if sessions_collection is None:
return 0
return await sessions_collection.count_documents({})
except Exception as e:
logger.error(f"Error counting all sessions: {e}")
return 0
async def count_all_messages() -> int:
"""Count total messages (both anonymous and authenticated)"""
try:
messages_collection = await get_messages_collection()
if messages_collection is None:
return 0
return await messages_collection.count_documents({})
except Exception as e:
logger.error(f"Error counting all messages: {e}")
return 0
async def count_anonymous_messages_in_timeframe(start_time: datetime, end_time: Optional[datetime] = None) -> int:
"""Count anonymous messages within a specific timeframe"""
try:
messages_collection = await get_messages_collection()
if messages_collection is None:
return 0
time_filter = {"timestamp": {"$gte": start_time}}
if end_time:
time_filter["timestamp"]["$lte"] = end_time
return await messages_collection.count_documents({
"$and": [
time_filter,
{
"$or": [
{"user_id": None},
{"user_id": {"$exists": False}}
]
}
]
})
except Exception as e:
logger.error(f"Error counting anonymous messages in timeframe: {e}")
return 0
async def count_authenticated_messages_in_timeframe(start_time: datetime, end_time: Optional[datetime] = None) -> int:
"""Count authenticated messages within a specific timeframe"""
try:
messages_collection = await get_messages_collection()
if messages_collection is None:
return 0
time_filter = {"timestamp": {"$gte": start_time}}
if end_time:
time_filter["timestamp"]["$lte"] = end_time
return await messages_collection.count_documents({
"$and": [
time_filter,
{"user_id": {"$ne": None, "$exists": True}}
]
})
except Exception as e:
logger.error(f"Error counting authenticated messages in timeframe: {e}")
return 0
async def get_basic_stats(user_id: Optional[str] = None) -> Dict[str, Any]:
"""Get basic analytics statistics, optionally filtered by user_id"""
try:
sessions_collection = await get_sessions_collection()
messages_collection = await get_messages_collection()
if sessions_collection is None or messages_collection is None:
return {"error": "Database not available"}
# Build filter for user_id if provided
user_filter = {}
if user_id is not None:
user_filter = {"user_id": user_id}
# Current time for calculations
now = datetime.utcnow()
today_start = now.replace(hour=0, minute=0, second=0, microsecond=0)
week_start = today_start - timedelta(days=7)
# Basic counts - use helper functions when not filtering by user_id
if user_id is None:
# Get overall stats with anonymous vs authenticated breakdown
total_sessions = await count_all_sessions()
total_messages = await count_all_messages()
authenticated_sessions = await count_authenticated_sessions()
anonymous_sessions = await count_anonymous_sessions()
authenticated_messages = await count_authenticated_messages()
anonymous_messages = await count_anonymous_messages()
else:
# Get stats for specific user
total_sessions = await sessions_collection.count_documents(user_filter)
total_messages = await messages_collection.count_documents(user_filter)
authenticated_sessions = total_sessions if user_id else 0
anonymous_sessions = 0
authenticated_messages = total_messages if user_id else 0
anonymous_messages = 0
# Today's stats
today_filter = {**user_filter, "timestamp": {"$gte": today_start}}
messages_today = await messages_collection.count_documents(today_filter)
# This week's stats
week_filter = {**user_filter, "timestamp": {"$gte": week_start}}
messages_week = await messages_collection.count_documents(week_filter)
# Active sessions (sessions without end_time)
active_filter = {**user_filter, "status": "active"}
active_sessions = await sessions_collection.count_documents(active_filter)
# Search usage
search_filter = {**user_filter, "used_search": True}
messages_with_search = await messages_collection.count_documents(search_filter)
search_usage_percentage = (messages_with_search / total_messages * 100) if total_messages > 0 else 0
# Average response time
pipeline = [
{"$match": user_filter},
{"$group": {
"_id": None,
"avg_response_time": {"$avg": "$response_time_ms"}
}}
]
avg_result = await messages_collection.aggregate(pipeline).to_list(1)
avg_response_time = avg_result[0]["avg_response_time"] if avg_result else 0
result = {
"total_sessions": total_sessions,
"total_messages": total_messages,
"messages_today": messages_today,
"messages_week": messages_week,
"active_sessions": active_sessions,
"search_usage_percentage": round(search_usage_percentage, 1),
"average_response_time_ms": round(avg_response_time, 0) if avg_response_time else 0,
"last_updated": now.isoformat()
}
# Add anonymous vs authenticated breakdown when not filtering by user_id
if user_id is None:
result.update({
"authenticated_sessions": authenticated_sessions,
"anonymous_sessions": anonymous_sessions,
"authenticated_messages": authenticated_messages,
"anonymous_messages": anonymous_messages,
"authenticated_session_percentage": round(
(authenticated_sessions / total_sessions * 100), 1
) if total_sessions > 0 else 0,
"authenticated_message_percentage": round(
(authenticated_messages / total_messages * 100), 1
) if total_messages > 0 else 0
})
# Add user_id to result if filtering was applied
if user_id is not None:
result["filtered_by_user_id"] = user_id
return result
except Exception as e:
logger.error(f"Error getting basic stats: {e}")
return {"error": str(e)}
async def get_hourly_message_stats(hours: int = 24, user_id: Optional[str] = None) -> List[Dict[str, Any]]:
"""Get hourly message statistics for the last N hours, optionally filtered by user_id"""
try:
messages_collection = await get_messages_collection()
if messages_collection is None:
return []
# Calculate time range
now = datetime.utcnow()
start_time = now - timedelta(hours=hours)
# Build match filter
match_filter = {"timestamp": {"$gte": start_time}}
if user_id is not None:
match_filter["user_id"] = user_id
# Aggregation pipeline for hourly stats
pipeline = [
{
"$match": match_filter
},
{
"$group": {
"_id": {
"year": {"$year": "$timestamp"},
"month": {"$month": "$timestamp"},
"day": {"$dayOfMonth": "$timestamp"},
"hour": {"$hour": "$timestamp"}
},
"message_count": {"$sum": 1},
"search_count": {"$sum": {"$cond": ["$used_search", 1, 0]}},
"avg_response_time": {"$avg": "$response_time_ms"},
"success_count": {"$sum": {"$cond": ["$success", 1, 0]}}
}
},
{
"$sort": {"_id": 1}
}
]
results = await messages_collection.aggregate(pipeline).to_list(None)
# Format results
formatted_results = []
for result in results:
hour_data = {
"hour": f"{result['_id']['year']}-{result['_id']['month']:02d}-{result['_id']['day']:02d} {result['_id']['hour']:02d}:00",
"message_count": result["message_count"],
"search_count": result["search_count"],
"avg_response_time_ms": round(result["avg_response_time"], 0),
"success_rate": round(result["success_count"] / result["message_count"] * 100, 1)
}
formatted_results.append(hour_data)
return formatted_results
except Exception as e:
logger.error(f"Error getting hourly stats: {e}")
return []
async def get_session_stats(user_id: Optional[str] = None) -> Dict[str, Any]:
"""Get detailed session statistics, optionally filtered by user_id"""
try:
sessions_collection = await get_sessions_collection()
if sessions_collection is None:
return {"error": "Database not available"}
# Build base filter for user_id if provided
base_filter = {}
if user_id is not None:
base_filter = {"user_id": user_id}
# Session duration stats (for ended sessions)
duration_filter = {**base_filter, "status": "ended", "end_time": {"$exists": True}}
pipeline = [
{
"$match": duration_filter
},
{
"$addFields": {
"duration_seconds": {
"$divide": [
{"$subtract": ["$end_time", "$start_time"]},
1000
]
}
}
},
{
"$group": {
"_id": None,
"avg_duration": {"$avg": "$duration_seconds"},
"max_duration": {"$max": "$duration_seconds"},
"min_duration": {"$min": "$duration_seconds"},
"total_ended_sessions": {"$sum": 1}
}
}
]
duration_result = await sessions_collection.aggregate(pipeline).to_list(1)
# Message count per session stats
message_pipeline = [
{
"$match": base_filter
},
{
"$group": {
"_id": None,
"avg_messages_per_session": {"$avg": "$message_count"},
"max_messages_per_session": {"$max": "$message_count"},
"sessions_with_search": {"$sum": {"$cond": ["$search_used", 1, 0]}}
}
}
]
message_result = await sessions_collection.aggregate(message_pipeline).to_list(1)
# Combine results
active_filter = {**base_filter, "status": "active"}
stats = {
"total_sessions": await sessions_collection.count_documents(base_filter),
"active_sessions": await sessions_collection.count_documents(active_filter),
"ended_sessions": duration_result[0]["total_ended_sessions"] if duration_result else 0,
"avg_session_duration_seconds": round(duration_result[0]["avg_duration"], 1) if duration_result else 0,
"max_session_duration_seconds": round(duration_result[0]["max_duration"], 1) if duration_result else 0,
"avg_messages_per_session": round(message_result[0]["avg_messages_per_session"], 1) if message_result else 0,
"max_messages_per_session": message_result[0]["max_messages_per_session"] if message_result else 0,
"sessions_with_search": message_result[0]["sessions_with_search"] if message_result else 0
}
# Add user_id to result if filtering was applied
if user_id is not None:
stats["filtered_by_user_id"] = user_id
return stats
except Exception as e:
logger.error(f"Error getting session stats: {e}")
return {"error": str(e)}
async def get_performance_stats(user_id: Optional[str] = None) -> Dict[str, Any]:
"""Get performance-related statistics, optionally filtered by user_id"""
try:
messages_collection = await get_messages_collection()
if messages_collection is None:
return {"error": "Database not available"}
# Build base filter for user_id if provided
base_filter = {}
if user_id is not None:
base_filter = {"user_id": user_id}
# Response time percentiles
pipeline = [
{
"$match": base_filter
},
{
"$group": {
"_id": None,
"response_times": {"$push": "$response_time_ms"}
}
},
{
"$project": {
"p50": {"$arrayElemAt": [
{"$sortArray": {"input": "$response_times", "sortBy": 1}},
{"$floor": {"$multiply": [{"$size": "$response_times"}, 0.5]}}
]},
"p90": {"$arrayElemAt": [
{"$sortArray": {"input": "$response_times", "sortBy": 1}},
{"$floor": {"$multiply": [{"$size": "$response_times"}, 0.9]}}
]},
"p95": {"$arrayElemAt": [
{"$sortArray": {"input": "$response_times", "sortBy": 1}},
{"$floor": {"$multiply": [{"$size": "$response_times"}, 0.95]}}
]}
}
}
]
percentile_result = await messages_collection.aggregate(pipeline).to_list(1)
# Error rate
total_messages = await messages_collection.count_documents(base_filter)
failed_filter = {**base_filter, "success": False}
failed_messages = await messages_collection.count_documents(failed_filter)
error_rate = (failed_messages / total_messages * 100) if total_messages > 0 else 0
# Average response times by search usage
search_pipeline = [
{
"$match": base_filter
},
{
"$group": {
"_id": "$used_search",
"avg_response_time": {"$avg": "$response_time_ms"},
"count": {"$sum": 1}
}
}
]
search_result = await messages_collection.aggregate(search_pipeline).to_list(None)
# Format search results
search_stats = {}
for result in search_result:
key = "with_search" if result["_id"] else "without_search"
search_stats[key] = {
"avg_response_time_ms": round(result["avg_response_time"], 0),
"message_count": result["count"]
}
result = {
"total_messages": total_messages,
"failed_messages": failed_messages,
"error_rate_percentage": round(error_rate, 2),
"response_time_p50": percentile_result[0]["p50"] if percentile_result else 0,
"response_time_p90": percentile_result[0]["p90"] if percentile_result else 0,
"response_time_p95": percentile_result[0]["p95"] if percentile_result else 0,
"performance_by_search": search_stats
}
# Add user_id to result if filtering was applied
if user_id is not None:
result["filtered_by_user_id"] = user_id
return result
except Exception as e:
logger.error(f"Error getting performance stats: {e}")
return {"error": str(e)}
async def get_usage_stats() -> Dict[str, Any]:
"""Get usage statistics including anonymous vs authenticated counts using helper functions"""
try:
# Use helper functions for efficient counting
total_sessions = await count_all_sessions()
authenticated_sessions = await count_authenticated_sessions()
anonymous_sessions = await count_anonymous_sessions()
total_messages = await count_all_messages()
authenticated_messages = await count_authenticated_messages()
anonymous_messages = await count_anonymous_messages()
# Calculate percentages
auth_session_percentage = (authenticated_sessions / total_sessions * 100) if total_sessions > 0 else 0
auth_message_percentage = (authenticated_messages / total_messages * 100) if total_messages > 0 else 0
return {
"total_sessions": total_sessions,
"authenticated_sessions": authenticated_sessions,
"anonymous_sessions": anonymous_sessions,
"authenticated_session_percentage": round(auth_session_percentage, 1),
"total_messages": total_messages,
"authenticated_messages": authenticated_messages,
"anonymous_messages": anonymous_messages,
"authenticated_message_percentage": round(auth_message_percentage, 1),
"last_updated": datetime.utcnow().isoformat()
}
except Exception as e:
logger.error(f"Error getting usage stats: {e}")
return {"error": str(e)}
async def get_user_statistics() -> Dict[str, Any]:
"""Get overall user statistics including authenticated vs anonymous metrics"""
try:
sessions_collection = await get_sessions_collection()
messages_collection = await get_messages_collection()
if sessions_collection is None or messages_collection is None:
return {"error": "Database not available"}
# Use helper functions for efficient counting
authenticated_sessions = await count_authenticated_sessions()
anonymous_sessions = await count_anonymous_sessions()
total_sessions = await count_all_sessions()
authenticated_messages = await count_authenticated_messages()
anonymous_messages = await count_anonymous_messages()
total_messages = await count_all_messages()
# Count unique authenticated users
unique_users_pipeline = [
{
"$match": {
"user_id": {"$ne": None, "$exists": True}
}
},
{
"$group": {
"_id": "$user_id"
}
},
{
"$count": "unique_users"
}
]
unique_users_result = await sessions_collection.aggregate(unique_users_pipeline).to_list(1)
unique_users = unique_users_result[0]["unique_users"] if unique_users_result else 0
# Calculate percentages
auth_session_percentage = (authenticated_sessions / total_sessions * 100) if total_sessions > 0 else 0
auth_message_percentage = (authenticated_messages / total_messages * 100) if total_messages > 0 else 0
return {
"total_sessions": total_sessions,
"authenticated_sessions": authenticated_sessions,
"anonymous_sessions": anonymous_sessions,
"authenticated_session_percentage": round(auth_session_percentage, 1),
"total_messages": total_messages,
"authenticated_messages": authenticated_messages,
"anonymous_messages": anonymous_messages,
"authenticated_message_percentage": round(auth_message_percentage, 1),
"unique_authenticated_users": unique_users,
"last_updated": datetime.utcnow().isoformat()
}
except Exception as e:
logger.error(f"Error getting user statistics: {e}")
return {"error": str(e)}
async def get_user_analytics(user_id: str) -> Dict[str, Any]:
"""Get analytics for a specific user"""
try:
if not user_id or not isinstance(user_id, str):
return {"error": "Invalid user_id provided"}
sessions_collection = await get_sessions_collection()
messages_collection = await get_messages_collection()
if sessions_collection is None or messages_collection is None:
return {"error": "Database not available"}
# User session stats
user_sessions = await sessions_collection.count_documents({"user_id": user_id})
active_user_sessions = await sessions_collection.count_documents({
"user_id": user_id,
"status": "active"
})
# User message stats
user_messages = await messages_collection.count_documents({"user_id": user_id})
user_messages_with_search = await messages_collection.count_documents({
"user_id": user_id,
"used_search": True
})
# User search usage percentage
search_usage_percentage = (user_messages_with_search / user_messages * 100) if user_messages > 0 else 0
# User average response time
response_time_pipeline = [
{
"$match": {"user_id": user_id}
},
{
"$group": {
"_id": None,
"avg_response_time": {"$avg": "$response_time_ms"},
"min_response_time": {"$min": "$response_time_ms"},
"max_response_time": {"$max": "$response_time_ms"}
}
}
]
response_time_result = await messages_collection.aggregate(response_time_pipeline).to_list(1)
# User session duration stats (for ended sessions)
duration_pipeline = [
{
"$match": {
"user_id": user_id,
"status": "ended",
"end_time": {"$exists": True}
}
},
{
"$addFields": {
"duration_seconds": {
"$divide": [
{"$subtract": ["$end_time", "$start_time"]},
1000
]
}
}
},
{
"$group": {
"_id": None,
"avg_duration": {"$avg": "$duration_seconds"},
"max_duration": {"$max": "$duration_seconds"},
"total_ended_sessions": {"$sum": 1}
}
}
]
duration_result = await sessions_collection.aggregate(duration_pipeline).to_list(1)
# User messages per session
messages_per_session_pipeline = [
{
"$match": {"user_id": user_id}
},
{
"$group": {
"_id": None,
"avg_messages_per_session": {"$avg": "$message_count"},
"max_messages_per_session": {"$max": "$message_count"}
}
}
]
messages_per_session_result = await sessions_collection.aggregate(messages_per_session_pipeline).to_list(1)
# User activity over time (last 30 days)
thirty_days_ago = datetime.utcnow() - timedelta(days=30)
daily_activity_pipeline = [
{
"$match": {
"user_id": user_id,
"timestamp": {"$gte": thirty_days_ago}
}
},
{
"$group": {
"_id": {
"year": {"$year": "$timestamp"},
"month": {"$month": "$timestamp"},
"day": {"$dayOfMonth": "$timestamp"}
},
"message_count": {"$sum": 1}
}
},
{
"$sort": {"_id": 1}
}
]
daily_activity = await messages_collection.aggregate(daily_activity_pipeline).to_list(None)
# Format daily activity
formatted_activity = []
for day in daily_activity:
formatted_activity.append({
"date": f"{day['_id']['year']}-{day['_id']['month']:02d}-{day['_id']['day']:02d}",
"message_count": day["message_count"]
})
return {
"user_id": user_id,
"total_sessions": user_sessions,
"active_sessions": active_user_sessions,
"total_messages": user_messages,
"messages_with_search": user_messages_with_search,
"search_usage_percentage": round(search_usage_percentage, 1),
"avg_response_time_ms": round(response_time_result[0]["avg_response_time"], 0) if response_time_result else 0,
"min_response_time_ms": response_time_result[0]["min_response_time"] if response_time_result else 0,
"max_response_time_ms": response_time_result[0]["max_response_time"] if response_time_result else 0,
"avg_session_duration_seconds": round(duration_result[0]["avg_duration"], 1) if duration_result else 0,
"max_session_duration_seconds": round(duration_result[0]["max_duration"], 1) if duration_result else 0,
"ended_sessions": duration_result[0]["total_ended_sessions"] if duration_result else 0,
"avg_messages_per_session": round(messages_per_session_result[0]["avg_messages_per_session"], 1) if messages_per_session_result else 0,
"max_messages_per_session": messages_per_session_result[0]["max_messages_per_session"] if messages_per_session_result else 0,
"daily_activity_last_30_days": formatted_activity,
"last_updated": datetime.utcnow().isoformat()
}
except Exception as e:
logger.error(f"Error getting user analytics for {user_id}: {e}")
return {"error": str(e)}
async def get_authenticated_vs_anonymous_metrics() -> Dict[str, Any]:
"""Get detailed comparison metrics between authenticated and anonymous users"""
try:
sessions_collection = await get_sessions_collection()
messages_collection = await get_messages_collection()
if sessions_collection is None or messages_collection is None:
return {"error": "Database not available"}
# Use helper functions for basic counts
auth_sessions_count = await count_authenticated_sessions()
anon_sessions_count = await count_anonymous_sessions()
auth_messages_count = await count_authenticated_messages()
anon_messages_count = await count_anonymous_messages()
# Authenticated user metrics
auth_session_pipeline = [
{
"$match": {
"user_id": {"$ne": None, "$exists": True}
}
},
{
"$group": {
"_id": None,
"total_sessions": {"$sum": 1},
"avg_messages_per_session": {"$avg": "$message_count"},
"sessions_with_search": {"$sum": {"$cond": ["$search_used", 1, 0]}}
}
}
]
auth_session_result = await sessions_collection.aggregate(auth_session_pipeline).to_list(1)
auth_message_pipeline = [
{
"$match": {
"user_id": {"$ne": None, "$exists": True}
}
},
{
"$group": {
"_id": None,
"total_messages": {"$sum": 1},
"avg_response_time": {"$avg": "$response_time_ms"},
"messages_with_search": {"$sum": {"$cond": ["$used_search", 1, 0]}},
"successful_messages": {"$sum": {"$cond": ["$success", 1, 0]}}
}
}
]
auth_message_result = await messages_collection.aggregate(auth_message_pipeline).to_list(1)
# Anonymous user metrics
anon_session_pipeline = [
{
"$match": {
"$or": [
{"user_id": None},
{"user_id": {"$exists": False}}
]
}
},
{
"$group": {
"_id": None,
"total_sessions": {"$sum": 1},
"avg_messages_per_session": {"$avg": "$message_count"},
"sessions_with_search": {"$sum": {"$cond": ["$search_used", 1, 0]}}
}
}
]
anon_session_result = await sessions_collection.aggregate(anon_session_pipeline).to_list(1)
anon_message_pipeline = [
{
"$match": {
"$or": [
{"user_id": None},
{"user_id": {"$exists": False}}
]
}
},
{
"$group": {
"_id": None,
"total_messages": {"$sum": 1},
"avg_response_time": {"$avg": "$response_time_ms"},
"messages_with_search": {"$sum": {"$cond": ["$used_search", 1, 0]}},
"successful_messages": {"$sum": {"$cond": ["$success", 1, 0]}}
}
}
]
anon_message_result = await messages_collection.aggregate(anon_message_pipeline).to_list(1)
# Format authenticated metrics
auth_sessions = auth_session_result[0] if auth_session_result else {}
auth_messages = auth_message_result[0] if auth_message_result else {}
authenticated_metrics = {
"sessions": auth_sessions.get("total_sessions", 0),
"messages": auth_messages.get("total_messages", 0),
"avg_messages_per_session": round(auth_sessions.get("avg_messages_per_session", 0), 1),
"avg_response_time_ms": round(auth_messages.get("avg_response_time", 0), 0),
"search_usage_percentage": round(
(auth_messages.get("messages_with_search", 0) / auth_messages.get("total_messages", 1) * 100), 1
) if auth_messages.get("total_messages", 0) > 0 else 0,
"success_rate_percentage": round(
(auth_messages.get("successful_messages", 0) / auth_messages.get("total_messages", 1) * 100), 1
) if auth_messages.get("total_messages", 0) > 0 else 0,
"sessions_with_search_percentage": round(
(auth_sessions.get("sessions_with_search", 0) / auth_sessions.get("total_sessions", 1) * 100), 1
) if auth_sessions.get("total_sessions", 0) > 0 else 0
}
# Format anonymous metrics
anon_sessions = anon_session_result[0] if anon_session_result else {}
anon_messages = anon_message_result[0] if anon_message_result else {}
anonymous_metrics = {
"sessions": anon_sessions.get("total_sessions", 0),
"messages": anon_messages.get("total_messages", 0),
"avg_messages_per_session": round(anon_sessions.get("avg_messages_per_session", 0), 1),
"avg_response_time_ms": round(anon_messages.get("avg_response_time", 0), 0),
"search_usage_percentage": round(
(anon_messages.get("messages_with_search", 0) / anon_messages.get("total_messages", 1) * 100), 1
) if anon_messages.get("total_messages", 0) > 0 else 0,
"success_rate_percentage": round(
(anon_messages.get("successful_messages", 0) / anon_messages.get("total_messages", 1) * 100), 1
) if anon_messages.get("total_messages", 0) > 0 else 0,
"sessions_with_search_percentage": round(
(anon_sessions.get("sessions_with_search", 0) / anon_sessions.get("total_sessions", 1) * 100), 1
) if anon_sessions.get("total_sessions", 0) > 0 else 0
}
return {
"authenticated": authenticated_metrics,
"anonymous": anonymous_metrics,
"comparison": {
"total_sessions": auth_sessions_count + anon_sessions_count,
"total_messages": auth_messages_count + anon_messages_count,
"authenticated_session_percentage": round(
(auth_sessions_count / (auth_sessions_count + anon_sessions_count) * 100), 1
) if (auth_sessions_count + anon_sessions_count) > 0 else 0,
"authenticated_message_percentage": round(
(auth_messages_count / (auth_messages_count + anon_messages_count) * 100), 1
) if (auth_messages_count + anon_messages_count) > 0 else 0
},
"last_updated": datetime.utcnow().isoformat()
}
except Exception as e:
logger.error(f"Error getting authenticated vs anonymous metrics: {e}")
return {"error": str(e)}
async def get_dashboard_data() -> Dict[str, Any]:
"""Get all dashboard data in one call"""
try:
# Get all stats concurrently
import asyncio
basic_stats, session_stats, performance_stats, hourly_stats, usage_stats = await asyncio.gather(
get_basic_stats(),
get_session_stats(),
get_performance_stats(),
get_hourly_message_stats(24),
get_usage_stats()
)
return {
"basic": basic_stats,
"sessions": session_stats,
"performance": performance_stats,
"hourly": hourly_stats,
"usage": usage_stats,
"generated_at": datetime.utcnow().isoformat()
}
except Exception as e:
logger.error(f"Error getting dashboard data: {e}")
return {"error": str(e)}