Spaces:
Sleeping
Sleeping
| """ | |
| 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)} |