# ============================================ # apps/analytics/services.py - Search Tracking Service # ============================================ from apps.analytics.models import SearchLog, PopularSearch from django.db.models import F class SearchTrackingService: """Service centralisé pour tracker les recherches""" @staticmethod def log_search( category: str, search_query: str, user=None, filters_applied: dict = None, results_count: int = 0, request=None ): """ Enregistre une recherche dans les logs Args: category: Catégorie de recherche (QUESTIONS, MENTORS, etc.) search_query: Terme recherché user: Utilisateur qui effectue la recherche (None si anonyme) filters_applied: Dict des filtres appliqués results_count: Nombre de résultats retournés request: Objet request Django pour extraire métadonnées Returns: SearchLog instance """ # Extraire métadonnées de la requête si disponible session_id = '' ip_address = None user_agent = '' page_url = '' if request: session_id = request.session.session_key or '' ip_address = request.META.get('REMOTE_ADDR') user_agent = request.META.get('HTTP_USER_AGENT', '')[:500] page_url = request.META.get('HTTP_REFERER', '')[:500] # Créer le log search_log = SearchLog.objects.create( user=user, category=category, search_query=search_query.strip()[:500], filters_applied=filters_applied or {}, results_count=results_count, session_id=session_id, ip_address=ip_address, user_agent=user_agent, page_url=page_url ) # Mettre à jour les recherches populaires SearchTrackingService._update_popular_search(category, search_query.strip()) return search_log @staticmethod def _update_popular_search(category: str, search_query: str): """Mise à jour incrémentale des recherches populaires""" if not search_query or len(search_query) < 2: return popular, created = PopularSearch.objects.get_or_create( category=category, search_query=search_query[:500], defaults={'search_count': 1} ) if not created: popular.search_count = F('search_count') + 1 popular.save(update_fields=['search_count', 'last_searched']) @staticmethod def log_result_click(search_log_id: int, result_id: str, position: int): """ Enregistre le clic sur un résultat de recherche Args: search_log_id: ID du SearchLog result_id: ID du résultat cliqué position: Position dans la liste (0-indexed) """ try: search_log = SearchLog.objects.get(id=search_log_id) search_log.clicked_result_id = str(result_id) search_log.clicked_result_position = position search_log.save(update_fields=['clicked_result_id', 'clicked_result_position']) except SearchLog.DoesNotExist: pass @staticmethod def get_popular_searches(category: str = None, limit: int = 10): """ Récupère les recherches les plus populaires Args: category: Filtrer par catégorie (None = toutes) limit: Nombre max de résultats Returns: QuerySet de PopularSearch """ qs = PopularSearch.objects.all() if category: qs = qs.filter(category=category) return qs[:limit] @staticmethod def get_trending_searches(category: str = None, days: int = 7, limit: int = 10): """ Récupère les recherches tendances (populaires récemment) Args: category: Filtrer par catégorie days: Nombre de jours à considérer limit: Nombre max de résultats Returns: Liste de dicts avec query et count """ from django.utils import timezone from datetime import timedelta from django.db.models import Count since = timezone.now() - timedelta(days=days) qs = SearchLog.objects.filter(created_at__gte=since) if category: qs = qs.filter(category=category) trending = qs.values('search_query').annotate( count=Count('id') ).order_by('-count')[:limit] return list(trending)