CoolFace
Apppublic

findEthics/Atlas

sourceHugging Faceupdated 1y agoView on Hugging Face
0likes
dashboard.py899 linesDownload Raw Back to analytics
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)}