""" 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)}