findEthics/Atlas
0
1"""2Analytics dashboard module for calculating and aggregating statistics.3"""4 5import logging6from datetime import datetime, timedelta7from typing import Dict, Any, List, Optional8from .database import get_sessions_collection, get_messages_collection9 10logger = logging.getLogger(__name__)11 12# Helper functions to count anonymous vs authenticated usage13async def count_anonymous_sessions() -> int:14 """Count sessions with null user_id (anonymous users)"""15 try:16 sessions_collection = await get_sessions_collection()17 if sessions_collection is None:18 return 019 20 return await sessions_collection.count_documents({21 "$or": [22 {"user_id": None},23 {"user_id": {"$exists": False}}24 ]25 })26 except Exception as e:27 logger.error(f"Error counting anonymous sessions: {e}")28 return 029 30async def count_authenticated_sessions() -> int:31 """Count sessions with non-null user_id (authenticated users)"""32 try:33 sessions_collection = await get_sessions_collection()34 if sessions_collection is None:35 return 036 37 return await sessions_collection.count_documents({38 "user_id": {"$ne": None, "$exists": True}39 })40 except Exception as e:41 logger.error(f"Error counting authenticated sessions: {e}")42 return 043 44async def count_anonymous_messages() -> int:45 """Count messages with null user_id (anonymous users)"""46 try:47 messages_collection = await get_messages_collection()48 if messages_collection is None:49 return 050 51 return await messages_collection.count_documents({52 "$or": [53 {"user_id": None},54 {"user_id": {"$exists": False}}55 ]56 })57 except Exception as e:58 logger.error(f"Error counting anonymous messages: {e}")59 return 060 61async def count_authenticated_messages() -> int:62 """Count messages with non-null user_id (authenticated users)"""63 try:64 messages_collection = await get_messages_collection()65 if messages_collection is None:66 return 067 68 return await messages_collection.count_documents({69 "user_id": {"$ne": None, "$exists": True}70 })71 except Exception as e:72 logger.error(f"Error counting authenticated messages: {e}")73 return 074 75async def count_all_sessions() -> int:76 """Count total sessions (both anonymous and authenticated)"""77 try:78 sessions_collection = await get_sessions_collection()79 if sessions_collection is None:80 return 081 82 return await sessions_collection.count_documents({})83 except Exception as e:84 logger.error(f"Error counting all sessions: {e}")85 return 086 87async def count_all_messages() -> int:88 """Count total messages (both anonymous and authenticated)"""89 try:90 messages_collection = await get_messages_collection()91 if messages_collection is None:92 return 093 94 return await messages_collection.count_documents({})95 except Exception as e:96 logger.error(f"Error counting all messages: {e}")97 return 098 99async def count_anonymous_messages_in_timeframe(start_time: datetime, end_time: Optional[datetime] = None) -> int:100 """Count anonymous messages within a specific timeframe"""101 try:102 messages_collection = await get_messages_collection()103 if messages_collection is None:104 return 0105 106 time_filter = {"timestamp": {"$gte": start_time}}107 if end_time:108 time_filter["timestamp"]["$lte"] = end_time109 110 return await messages_collection.count_documents({111 "$and": [112 time_filter,113 {114 "$or": [115 {"user_id": None},116 {"user_id": {"$exists": False}}117 ]118 }119 ]120 })121 except Exception as e:122 logger.error(f"Error counting anonymous messages in timeframe: {e}")123 return 0124 125async def count_authenticated_messages_in_timeframe(start_time: datetime, end_time: Optional[datetime] = None) -> int:126 """Count authenticated messages within a specific timeframe"""127 try:128 messages_collection = await get_messages_collection()129 if messages_collection is None:130 return 0131 132 time_filter = {"timestamp": {"$gte": start_time}}133 if end_time:134 time_filter["timestamp"]["$lte"] = end_time135 136 return await messages_collection.count_documents({137 "$and": [138 time_filter,139 {"user_id": {"$ne": None, "$exists": True}}140 ]141 })142 except Exception as e:143 logger.error(f"Error counting authenticated messages in timeframe: {e}")144 return 0145 146async def get_basic_stats(user_id: Optional[str] = None) -> Dict[str, Any]:147 """Get basic analytics statistics, optionally filtered by user_id"""148 try:149 sessions_collection = await get_sessions_collection()150 messages_collection = await get_messages_collection()151 152 if sessions_collection is None or messages_collection is None:153 return {"error": "Database not available"}154 155 # Build filter for user_id if provided156 user_filter = {}157 if user_id is not None:158 user_filter = {"user_id": user_id}159 160 # Current time for calculations161 now = datetime.utcnow()162 today_start = now.replace(hour=0, minute=0, second=0, microsecond=0)163 week_start = today_start - timedelta(days=7)164 165 # Basic counts - use helper functions when not filtering by user_id166 if user_id is None:167 # Get overall stats with anonymous vs authenticated breakdown168 total_sessions = await count_all_sessions()169 total_messages = await count_all_messages()170 authenticated_sessions = await count_authenticated_sessions()171 anonymous_sessions = await count_anonymous_sessions()172 authenticated_messages = await count_authenticated_messages()173 anonymous_messages = await count_anonymous_messages()174 else:175 # Get stats for specific user176 total_sessions = await sessions_collection.count_documents(user_filter)177 total_messages = await messages_collection.count_documents(user_filter)178 authenticated_sessions = total_sessions if user_id else 0179 anonymous_sessions = 0180 authenticated_messages = total_messages if user_id else 0181 anonymous_messages = 0182 183 # Today's stats184 today_filter = {**user_filter, "timestamp": {"$gte": today_start}}185 messages_today = await messages_collection.count_documents(today_filter)186 187 # This week's stats188 week_filter = {**user_filter, "timestamp": {"$gte": week_start}}189 messages_week = await messages_collection.count_documents(week_filter)190 191 # Active sessions (sessions without end_time)192 active_filter = {**user_filter, "status": "active"}193 active_sessions = await sessions_collection.count_documents(active_filter)194 195 # Search usage196 search_filter = {**user_filter, "used_search": True}197 messages_with_search = await messages_collection.count_documents(search_filter)198 search_usage_percentage = (messages_with_search / total_messages * 100) if total_messages > 0 else 0199 200 # Average response time201 pipeline = [202 {"$match": user_filter},203 {"$group": {204 "_id": None,205 "avg_response_time": {"$avg": "$response_time_ms"}206 }}207 ]208 avg_result = await messages_collection.aggregate(pipeline).to_list(1)209 avg_response_time = avg_result[0]["avg_response_time"] if avg_result else 0210 211 result = {212 "total_sessions": total_sessions,213 "total_messages": total_messages,214 "messages_today": messages_today,215 "messages_week": messages_week,216 "active_sessions": active_sessions,217 "search_usage_percentage": round(search_usage_percentage, 1),218 "average_response_time_ms": round(avg_response_time, 0) if avg_response_time else 0,219 "last_updated": now.isoformat()220 }221 222 # Add anonymous vs authenticated breakdown when not filtering by user_id223 if user_id is None:224 result.update({225 "authenticated_sessions": authenticated_sessions,226 "anonymous_sessions": anonymous_sessions,227 "authenticated_messages": authenticated_messages,228 "anonymous_messages": anonymous_messages,229 "authenticated_session_percentage": round(230 (authenticated_sessions / total_sessions * 100), 1231 ) if total_sessions > 0 else 0,232 "authenticated_message_percentage": round(233 (authenticated_messages / total_messages * 100), 1234 ) if total_messages > 0 else 0235 })236 237 # Add user_id to result if filtering was applied238 if user_id is not None:239 result["filtered_by_user_id"] = user_id240 241 return result242 243 except Exception as e:244 logger.error(f"Error getting basic stats: {e}")245 return {"error": str(e)}246 247async def get_hourly_message_stats(hours: int = 24, user_id: Optional[str] = None) -> List[Dict[str, Any]]:248 """Get hourly message statistics for the last N hours, optionally filtered by user_id"""249 try:250 messages_collection = await get_messages_collection()251 252 if messages_collection is None:253 return []254 255 # Calculate time range256 now = datetime.utcnow()257 start_time = now - timedelta(hours=hours)258 259 # Build match filter260 match_filter = {"timestamp": {"$gte": start_time}}261 if user_id is not None:262 match_filter["user_id"] = user_id263 264 # Aggregation pipeline for hourly stats265 pipeline = [266 {267 "$match": match_filter268 },269 {270 "$group": {271 "_id": {272 "year": {"$year": "$timestamp"},273 "month": {"$month": "$timestamp"},274 "day": {"$dayOfMonth": "$timestamp"},275 "hour": {"$hour": "$timestamp"}276 },277 "message_count": {"$sum": 1},278 "search_count": {"$sum": {"$cond": ["$used_search", 1, 0]}},279 "avg_response_time": {"$avg": "$response_time_ms"},280 "success_count": {"$sum": {"$cond": ["$success", 1, 0]}}281 }282 },283 {284 "$sort": {"_id": 1}285 }286 ]287 288 results = await messages_collection.aggregate(pipeline).to_list(None)289 290 # Format results291 formatted_results = []292 for result in results:293 hour_data = {294 "hour": f"{result['_id']['year']}-{result['_id']['month']:02d}-{result['_id']['day']:02d} {result['_id']['hour']:02d}:00",295 "message_count": result["message_count"],296 "search_count": result["search_count"],297 "avg_response_time_ms": round(result["avg_response_time"], 0),298 "success_rate": round(result["success_count"] / result["message_count"] * 100, 1)299 }300 formatted_results.append(hour_data)301 302 return formatted_results303 304 except Exception as e:305 logger.error(f"Error getting hourly stats: {e}")306 return []307 308async def get_session_stats(user_id: Optional[str] = None) -> Dict[str, Any]:309 """Get detailed session statistics, optionally filtered by user_id"""310 try:311 sessions_collection = await get_sessions_collection()312 313 if sessions_collection is None:314 return {"error": "Database not available"}315 316 # Build base filter for user_id if provided317 base_filter = {}318 if user_id is not None:319 base_filter = {"user_id": user_id}320 321 # Session duration stats (for ended sessions)322 duration_filter = {**base_filter, "status": "ended", "end_time": {"$exists": True}}323 pipeline = [324 {325 "$match": duration_filter326 },327 {328 "$addFields": {329 "duration_seconds": {330 "$divide": [331 {"$subtract": ["$end_time", "$start_time"]},332 1000333 ]334 }335 }336 },337 {338 "$group": {339 "_id": None,340 "avg_duration": {"$avg": "$duration_seconds"},341 "max_duration": {"$max": "$duration_seconds"},342 "min_duration": {"$min": "$duration_seconds"},343 "total_ended_sessions": {"$sum": 1}344 }345 }346 ]347 348 duration_result = await sessions_collection.aggregate(pipeline).to_list(1)349 350 # Message count per session stats351 message_pipeline = [352 {353 "$match": base_filter354 },355 {356 "$group": {357 "_id": None,358 "avg_messages_per_session": {"$avg": "$message_count"},359 "max_messages_per_session": {"$max": "$message_count"},360 "sessions_with_search": {"$sum": {"$cond": ["$search_used", 1, 0]}}361 }362 }363 ]364 365 message_result = await sessions_collection.aggregate(message_pipeline).to_list(1)366 367 # Combine results368 active_filter = {**base_filter, "status": "active"}369 stats = {370 "total_sessions": await sessions_collection.count_documents(base_filter),371 "active_sessions": await sessions_collection.count_documents(active_filter),372 "ended_sessions": duration_result[0]["total_ended_sessions"] if duration_result else 0,373 "avg_session_duration_seconds": round(duration_result[0]["avg_duration"], 1) if duration_result else 0,374 "max_session_duration_seconds": round(duration_result[0]["max_duration"], 1) if duration_result else 0,375 "avg_messages_per_session": round(message_result[0]["avg_messages_per_session"], 1) if message_result else 0,376 "max_messages_per_session": message_result[0]["max_messages_per_session"] if message_result else 0,377 "sessions_with_search": message_result[0]["sessions_with_search"] if message_result else 0378 }379 380 # Add user_id to result if filtering was applied381 if user_id is not None:382 stats["filtered_by_user_id"] = user_id383 384 return stats385 386 except Exception as e:387 logger.error(f"Error getting session stats: {e}")388 return {"error": str(e)}389 390async def get_performance_stats(user_id: Optional[str] = None) -> Dict[str, Any]:391 """Get performance-related statistics, optionally filtered by user_id"""392 try:393 messages_collection = await get_messages_collection()394 395 if messages_collection is None:396 return {"error": "Database not available"}397 398 # Build base filter for user_id if provided399 base_filter = {}400 if user_id is not None:401 base_filter = {"user_id": user_id}402 403 # Response time percentiles404 pipeline = [405 {406 "$match": base_filter407 },408 {409 "$group": {410 "_id": None,411 "response_times": {"$push": "$response_time_ms"}412 }413 },414 {415 "$project": {416 "p50": {"$arrayElemAt": [417 {"$sortArray": {"input": "$response_times", "sortBy": 1}},418 {"$floor": {"$multiply": [{"$size": "$response_times"}, 0.5]}}419 ]},420 "p90": {"$arrayElemAt": [421 {"$sortArray": {"input": "$response_times", "sortBy": 1}},422 {"$floor": {"$multiply": [{"$size": "$response_times"}, 0.9]}}423 ]},424 "p95": {"$arrayElemAt": [425 {"$sortArray": {"input": "$response_times", "sortBy": 1}},426 {"$floor": {"$multiply": [{"$size": "$response_times"}, 0.95]}}427 ]}428 }429 }430 ]431 432 percentile_result = await messages_collection.aggregate(pipeline).to_list(1)433 434 # Error rate435 total_messages = await messages_collection.count_documents(base_filter)436 failed_filter = {**base_filter, "success": False}437 failed_messages = await messages_collection.count_documents(failed_filter)438 error_rate = (failed_messages / total_messages * 100) if total_messages > 0 else 0439 440 # Average response times by search usage441 search_pipeline = [442 {443 "$match": base_filter444 },445 {446 "$group": {447 "_id": "$used_search",448 "avg_response_time": {"$avg": "$response_time_ms"},449 "count": {"$sum": 1}450 }451 }452 ]453 454 search_result = await messages_collection.aggregate(search_pipeline).to_list(None)455 456 # Format search results457 search_stats = {}458 for result in search_result:459 key = "with_search" if result["_id"] else "without_search"460 search_stats[key] = {461 "avg_response_time_ms": round(result["avg_response_time"], 0),462 "message_count": result["count"]463 }464 465 result = {466 "total_messages": total_messages,467 "failed_messages": failed_messages,468 "error_rate_percentage": round(error_rate, 2),469 "response_time_p50": percentile_result[0]["p50"] if percentile_result else 0,470 "response_time_p90": percentile_result[0]["p90"] if percentile_result else 0,471 "response_time_p95": percentile_result[0]["p95"] if percentile_result else 0,472 "performance_by_search": search_stats473 }474 475 # Add user_id to result if filtering was applied476 if user_id is not None:477 result["filtered_by_user_id"] = user_id478 479 return result480 481 except Exception as e:482 logger.error(f"Error getting performance stats: {e}")483 return {"error": str(e)}484 485async def get_usage_stats() -> Dict[str, Any]:486 """Get usage statistics including anonymous vs authenticated counts using helper functions"""487 try:488 # Use helper functions for efficient counting489 total_sessions = await count_all_sessions()490 authenticated_sessions = await count_authenticated_sessions()491 anonymous_sessions = await count_anonymous_sessions()492 493 total_messages = await count_all_messages()494 authenticated_messages = await count_authenticated_messages()495 anonymous_messages = await count_anonymous_messages()496 497 # Calculate percentages498 auth_session_percentage = (authenticated_sessions / total_sessions * 100) if total_sessions > 0 else 0499 auth_message_percentage = (authenticated_messages / total_messages * 100) if total_messages > 0 else 0500 501 return {502 "total_sessions": total_sessions,503 "authenticated_sessions": authenticated_sessions,504 "anonymous_sessions": anonymous_sessions,505 "authenticated_session_percentage": round(auth_session_percentage, 1),506 "total_messages": total_messages,507 "authenticated_messages": authenticated_messages,508 "anonymous_messages": anonymous_messages,509 "authenticated_message_percentage": round(auth_message_percentage, 1),510 "last_updated": datetime.utcnow().isoformat()511 }512 513 except Exception as e:514 logger.error(f"Error getting usage stats: {e}")515 return {"error": str(e)}516 517async def get_user_statistics() -> Dict[str, Any]:518 """Get overall user statistics including authenticated vs anonymous metrics"""519 try:520 sessions_collection = await get_sessions_collection()521 messages_collection = await get_messages_collection()522 523 if sessions_collection is None or messages_collection is None:524 return {"error": "Database not available"}525 526 # Use helper functions for efficient counting527 authenticated_sessions = await count_authenticated_sessions()528 anonymous_sessions = await count_anonymous_sessions()529 total_sessions = await count_all_sessions()530 531 authenticated_messages = await count_authenticated_messages()532 anonymous_messages = await count_anonymous_messages()533 total_messages = await count_all_messages()534 535 # Count unique authenticated users536 unique_users_pipeline = [537 {538 "$match": {539 "user_id": {"$ne": None, "$exists": True}540 }541 },542 {543 "$group": {544 "_id": "$user_id"545 }546 },547 {548 "$count": "unique_users"549 }550 ]551 unique_users_result = await sessions_collection.aggregate(unique_users_pipeline).to_list(1)552 unique_users = unique_users_result[0]["unique_users"] if unique_users_result else 0553 554 # Calculate percentages555 auth_session_percentage = (authenticated_sessions / total_sessions * 100) if total_sessions > 0 else 0556 auth_message_percentage = (authenticated_messages / total_messages * 100) if total_messages > 0 else 0557 558 return {559 "total_sessions": total_sessions,560 "authenticated_sessions": authenticated_sessions,561 "anonymous_sessions": anonymous_sessions,562 "authenticated_session_percentage": round(auth_session_percentage, 1),563 "total_messages": total_messages,564 "authenticated_messages": authenticated_messages,565 "anonymous_messages": anonymous_messages,566 "authenticated_message_percentage": round(auth_message_percentage, 1),567 "unique_authenticated_users": unique_users,568 "last_updated": datetime.utcnow().isoformat()569 }570 571 except Exception as e:572 logger.error(f"Error getting user statistics: {e}")573 return {"error": str(e)}574 575async def get_user_analytics(user_id: str) -> Dict[str, Any]:576 """Get analytics for a specific user"""577 try:578 if not user_id or not isinstance(user_id, str):579 return {"error": "Invalid user_id provided"}580 581 sessions_collection = await get_sessions_collection()582 messages_collection = await get_messages_collection()583 584 if sessions_collection is None or messages_collection is None:585 return {"error": "Database not available"}586 587 # User session stats588 user_sessions = await sessions_collection.count_documents({"user_id": user_id})589 active_user_sessions = await sessions_collection.count_documents({590 "user_id": user_id,591 "status": "active"592 })593 594 # User message stats595 user_messages = await messages_collection.count_documents({"user_id": user_id})596 user_messages_with_search = await messages_collection.count_documents({597 "user_id": user_id,598 "used_search": True599 })600 601 # User search usage percentage602 search_usage_percentage = (user_messages_with_search / user_messages * 100) if user_messages > 0 else 0603 604 # User average response time605 response_time_pipeline = [606 {607 "$match": {"user_id": user_id}608 },609 {610 "$group": {611 "_id": None,612 "avg_response_time": {"$avg": "$response_time_ms"},613 "min_response_time": {"$min": "$response_time_ms"},614 "max_response_time": {"$max": "$response_time_ms"}615 }616 }617 ]618 response_time_result = await messages_collection.aggregate(response_time_pipeline).to_list(1)619 620 # User session duration stats (for ended sessions)621 duration_pipeline = [622 {623 "$match": {624 "user_id": user_id,625 "status": "ended",626 "end_time": {"$exists": True}627 }628 },629 {630 "$addFields": {631 "duration_seconds": {632 "$divide": [633 {"$subtract": ["$end_time", "$start_time"]},634 1000635 ]636 }637 }638 },639 {640 "$group": {641 "_id": None,642 "avg_duration": {"$avg": "$duration_seconds"},643 "max_duration": {"$max": "$duration_seconds"},644 "total_ended_sessions": {"$sum": 1}645 }646 }647 ]648 duration_result = await sessions_collection.aggregate(duration_pipeline).to_list(1)649 650 # User messages per session651 messages_per_session_pipeline = [652 {653 "$match": {"user_id": user_id}654 },655 {656 "$group": {657 "_id": None,658 "avg_messages_per_session": {"$avg": "$message_count"},659 "max_messages_per_session": {"$max": "$message_count"}660 }661 }662 ]663 messages_per_session_result = await sessions_collection.aggregate(messages_per_session_pipeline).to_list(1)664 665 # User activity over time (last 30 days)666 thirty_days_ago = datetime.utcnow() - timedelta(days=30)667 daily_activity_pipeline = [668 {669 "$match": {670 "user_id": user_id,671 "timestamp": {"$gte": thirty_days_ago}672 }673 },674 {675 "$group": {676 "_id": {677 "year": {"$year": "$timestamp"},678 "month": {"$month": "$timestamp"},679 "day": {"$dayOfMonth": "$timestamp"}680 },681 "message_count": {"$sum": 1}682 }683 },684 {685 "$sort": {"_id": 1}686 }687 ]688 daily_activity = await messages_collection.aggregate(daily_activity_pipeline).to_list(None)689 690 # Format daily activity691 formatted_activity = []692 for day in daily_activity:693 formatted_activity.append({694 "date": f"{day['_id']['year']}-{day['_id']['month']:02d}-{day['_id']['day']:02d}",695 "message_count": day["message_count"]696 })697 698 return {699 "user_id": user_id,700 "total_sessions": user_sessions,701 "active_sessions": active_user_sessions,702 "total_messages": user_messages,703 "messages_with_search": user_messages_with_search,704 "search_usage_percentage": round(search_usage_percentage, 1),705 "avg_response_time_ms": round(response_time_result[0]["avg_response_time"], 0) if response_time_result else 0,706 "min_response_time_ms": response_time_result[0]["min_response_time"] if response_time_result else 0,707 "max_response_time_ms": response_time_result[0]["max_response_time"] if response_time_result else 0,708 "avg_session_duration_seconds": round(duration_result[0]["avg_duration"], 1) if duration_result else 0,709 "max_session_duration_seconds": round(duration_result[0]["max_duration"], 1) if duration_result else 0,710 "ended_sessions": duration_result[0]["total_ended_sessions"] if duration_result else 0,711 "avg_messages_per_session": round(messages_per_session_result[0]["avg_messages_per_session"], 1) if messages_per_session_result else 0,712 "max_messages_per_session": messages_per_session_result[0]["max_messages_per_session"] if messages_per_session_result else 0,713 "daily_activity_last_30_days": formatted_activity,714 "last_updated": datetime.utcnow().isoformat()715 }716 717 except Exception as e:718 logger.error(f"Error getting user analytics for {user_id}: {e}")719 return {"error": str(e)}720 721async def get_authenticated_vs_anonymous_metrics() -> Dict[str, Any]:722 """Get detailed comparison metrics between authenticated and anonymous users"""723 try:724 sessions_collection = await get_sessions_collection()725 messages_collection = await get_messages_collection()726 727 if sessions_collection is None or messages_collection is None:728 return {"error": "Database not available"}729 730 # Use helper functions for basic counts731 auth_sessions_count = await count_authenticated_sessions()732 anon_sessions_count = await count_anonymous_sessions()733 auth_messages_count = await count_authenticated_messages()734 anon_messages_count = await count_anonymous_messages()735 736 # Authenticated user metrics737 auth_session_pipeline = [738 {739 "$match": {740 "user_id": {"$ne": None, "$exists": True}741 }742 },743 {744 "$group": {745 "_id": None,746 "total_sessions": {"$sum": 1},747 "avg_messages_per_session": {"$avg": "$message_count"},748 "sessions_with_search": {"$sum": {"$cond": ["$search_used", 1, 0]}}749 }750 }751 ]752 auth_session_result = await sessions_collection.aggregate(auth_session_pipeline).to_list(1)753 754 auth_message_pipeline = [755 {756 "$match": {757 "user_id": {"$ne": None, "$exists": True}758 }759 },760 {761 "$group": {762 "_id": None,763 "total_messages": {"$sum": 1},764 "avg_response_time": {"$avg": "$response_time_ms"},765 "messages_with_search": {"$sum": {"$cond": ["$used_search", 1, 0]}},766 "successful_messages": {"$sum": {"$cond": ["$success", 1, 0]}}767 }768 }769 ]770 auth_message_result = await messages_collection.aggregate(auth_message_pipeline).to_list(1)771 772 # Anonymous user metrics773 anon_session_pipeline = [774 {775 "$match": {776 "$or": [777 {"user_id": None},778 {"user_id": {"$exists": False}}779 ]780 }781 },782 {783 "$group": {784 "_id": None,785 "total_sessions": {"$sum": 1},786 "avg_messages_per_session": {"$avg": "$message_count"},787 "sessions_with_search": {"$sum": {"$cond": ["$search_used", 1, 0]}}788 }789 }790 ]791 anon_session_result = await sessions_collection.aggregate(anon_session_pipeline).to_list(1)792 793 anon_message_pipeline = [794 {795 "$match": {796 "$or": [797 {"user_id": None},798 {"user_id": {"$exists": False}}799 ]800 }801 },802 {803 "$group": {804 "_id": None,805 "total_messages": {"$sum": 1},806 "avg_response_time": {"$avg": "$response_time_ms"},807 "messages_with_search": {"$sum": {"$cond": ["$used_search", 1, 0]}},808 "successful_messages": {"$sum": {"$cond": ["$success", 1, 0]}}809 }810 }811 ]812 anon_message_result = await messages_collection.aggregate(anon_message_pipeline).to_list(1)813 814 # Format authenticated metrics815 auth_sessions = auth_session_result[0] if auth_session_result else {}816 auth_messages = auth_message_result[0] if auth_message_result else {}817 818 authenticated_metrics = {819 "sessions": auth_sessions.get("total_sessions", 0),820 "messages": auth_messages.get("total_messages", 0),821 "avg_messages_per_session": round(auth_sessions.get("avg_messages_per_session", 0), 1),822 "avg_response_time_ms": round(auth_messages.get("avg_response_time", 0), 0),823 "search_usage_percentage": round(824 (auth_messages.get("messages_with_search", 0) / auth_messages.get("total_messages", 1) * 100), 1825 ) if auth_messages.get("total_messages", 0) > 0 else 0,826 "success_rate_percentage": round(827 (auth_messages.get("successful_messages", 0) / auth_messages.get("total_messages", 1) * 100), 1828 ) if auth_messages.get("total_messages", 0) > 0 else 0,829 "sessions_with_search_percentage": round(830 (auth_sessions.get("sessions_with_search", 0) / auth_sessions.get("total_sessions", 1) * 100), 1831 ) if auth_sessions.get("total_sessions", 0) > 0 else 0832 }833 834 # Format anonymous metrics835 anon_sessions = anon_session_result[0] if anon_session_result else {}836 anon_messages = anon_message_result[0] if anon_message_result else {}837 838 anonymous_metrics = {839 "sessions": anon_sessions.get("total_sessions", 0),840 "messages": anon_messages.get("total_messages", 0),841 "avg_messages_per_session": round(anon_sessions.get("avg_messages_per_session", 0), 1),842 "avg_response_time_ms": round(anon_messages.get("avg_response_time", 0), 0),843 "search_usage_percentage": round(844 (anon_messages.get("messages_with_search", 0) / anon_messages.get("total_messages", 1) * 100), 1845 ) if anon_messages.get("total_messages", 0) > 0 else 0,846 "success_rate_percentage": round(847 (anon_messages.get("successful_messages", 0) / anon_messages.get("total_messages", 1) * 100), 1848 ) if anon_messages.get("total_messages", 0) > 0 else 0,849 "sessions_with_search_percentage": round(850 (anon_sessions.get("sessions_with_search", 0) / anon_sessions.get("total_sessions", 1) * 100), 1851 ) if anon_sessions.get("total_sessions", 0) > 0 else 0852 }853 854 return {855 "authenticated": authenticated_metrics,856 "anonymous": anonymous_metrics,857 "comparison": {858 "total_sessions": auth_sessions_count + anon_sessions_count,859 "total_messages": auth_messages_count + anon_messages_count,860 "authenticated_session_percentage": round(861 (auth_sessions_count / (auth_sessions_count + anon_sessions_count) * 100), 1862 ) if (auth_sessions_count + anon_sessions_count) > 0 else 0,863 "authenticated_message_percentage": round(864 (auth_messages_count / (auth_messages_count + anon_messages_count) * 100), 1865 ) if (auth_messages_count + anon_messages_count) > 0 else 0866 },867 "last_updated": datetime.utcnow().isoformat()868 }869 870 except Exception as e:871 logger.error(f"Error getting authenticated vs anonymous metrics: {e}")872 return {"error": str(e)}873 874async def get_dashboard_data() -> Dict[str, Any]:875 """Get all dashboard data in one call"""876 try:877 # Get all stats concurrently878 import asyncio879 880 basic_stats, session_stats, performance_stats, hourly_stats, usage_stats = await asyncio.gather(881 get_basic_stats(),882 get_session_stats(),883 get_performance_stats(),884 get_hourly_message_stats(24),885 get_usage_stats()886 )887 888 return {889 "basic": basic_stats,890 "sessions": session_stats,891 "performance": performance_stats,892 "hourly": hourly_stats,893 "usage": usage_stats,894 "generated_at": datetime.utcnow().isoformat()895 }896 897 except Exception as e:898 logger.error(f"Error getting dashboard data: {e}")899 return {"error": str(e)}