diff --git a/akarpov/music/services/external.py b/akarpov/music/services/external.py index 9f5047e..2da68f6 100644 --- a/akarpov/music/services/external.py +++ b/akarpov/music/services/external.py @@ -1,17 +1,20 @@ +from functools import wraps +from typing import Any, Dict, Optional + import requests -from typing import Optional, Dict, Any import structlog from django.conf import settings -from functools import wraps logger = structlog.get_logger(__name__) class ExternalServiceClient: def __init__(self): - self.base_url = getattr(settings, 'MUSIC_EXTERNAL_SERVICE_URL', None) + self.base_url = getattr(settings, "MUSIC_EXTERNAL_SERVICE_URL", None) - def _make_request(self, endpoint: str, params: Dict = None, **kwargs) -> Optional[Dict]: + def _make_request( + self, endpoint: str, params: dict = None, **kwargs + ) -> dict | None: if not self.base_url: return None @@ -25,24 +28,19 @@ def _make_request(self, endpoint: str, params: Dict = None, **kwargs) -> Optiona "External service request failed", error=str(e), endpoint=endpoint, - params=params + params=params, ) return None - def get_spotify_info(self, track_name: str) -> Optional[Dict[str, Any]]: - return self._make_request( - "/spotify/search", - params={"query": track_name} - ) + def get_spotify_info(self, track_name: str) -> dict[str, Any] | None: + return self._make_request("/spotify/search", params={"query": track_name}) - def translate_text(self, text: str, source_lang: str = "auto", target_lang: str = "en") -> Optional[str]: + def translate_text( + self, text: str, source_lang: str = "auto", target_lang: str = "en" + ) -> str | None: response = self._make_request( "/translation/translate", - json={ - "text": text, - "source_lang": source_lang, - "target_lang": target_lang - } + json={"text": text, "source_lang": source_lang, "target_lang": target_lang}, ) return response.get("translated_text") if response else None @@ -52,7 +50,10 @@ def external_service_fallback(fallback_func): @wraps(fallback_func) def wrapper(*args, **kwargs): - if not hasattr(settings, 'MUSIC_EXTERNAL_SERVICE_URL') or not settings.MUSIC_EXTERNAL_SERVICE_URL: + if ( + not hasattr(settings, "MUSIC_EXTERNAL_SERVICE_URL") + or not settings.MUSIC_EXTERNAL_SERVICE_URL + ): return fallback_func(*args, **kwargs) client = ExternalServiceClient() @@ -68,7 +69,7 @@ def wrapper(*args, **kwargs): logger.error( "External service failed, falling back to local implementation", error=str(e), - function=fallback_func.__name__ + function=fallback_func.__name__, ) return fallback_func(*args, **kwargs) diff --git a/akarpov/music/services/search.py b/akarpov/music/services/search.py index b79170a..d7066ec 100644 --- a/akarpov/music/services/search.py +++ b/akarpov/music/services/search.py @@ -8,66 +8,76 @@ def search_song(query): + # Split query into potential track and artist parts + parts = [part.strip() for part in query.split('-')] + track_query = parts[0] + artist_query = parts[1] if len(parts) > 1 else None + search = SongDocument.search() + # Base queries for track name with high boost should_queries = [ - ES_Q("match_phrase", name={"query": query, "boost": 5}), - ES_Q( - "nested", - path="authors", - query=ES_Q("match_phrase", name={"query": query, "boost": 4}), - ), - ES_Q( - "nested", - path="album", - query=ES_Q("match_phrase", name={"query": query, "boost": 4}), - ), - ES_Q("match", name={"query": query, "fuzziness": "AUTO", "boost": 3}), - ES_Q( - "nested", - path="authors", - query=ES_Q("match", name={"query": query, "fuzziness": "AUTO", "boost": 2}), - ), - ES_Q( - "nested", - path="album", - query=ES_Q("match", name={"query": query, "fuzziness": "AUTO", "boost": 2}), - ), - ES_Q("wildcard", name={"value": f"*{query.lower()}*", "boost": 1}), - ES_Q( - "nested", - path="authors", - query=ES_Q("wildcard", name={"value": f"*{query.lower()}*", "boost": 0.8}), - ), - ES_Q( - "nested", - path="album", - query=ES_Q("wildcard", name={"value": f"*{query.lower()}*", "boost": 0.8}), - ), + ES_Q("match_phrase", name={"query": track_query, "boost": 10}), + ES_Q("match", name={"query": track_query, "fuzziness": "AUTO", "boost": 8}), + ES_Q("wildcard", name={"value": f"*{track_query.lower()}*", "boost": 6}), ES_Q( "match", - name_transliterated={"query": query, "fuzziness": "AUTO", "boost": 1}, - ), - ES_Q( - "nested", - path="authors", - query=ES_Q( - "match", - name_transliterated={"query": query, "fuzziness": "AUTO", "boost": 0.8}, - ), - ), - ES_Q( - "nested", - path="album", - query=ES_Q( - "match", - name_transliterated={"query": query, "fuzziness": "AUTO", "boost": 0.8}, - ), + name_transliterated={"query": track_query, "fuzziness": "AUTO", "boost": 5}, ), ] + # Add artist-specific queries if artist part exists + if artist_query: + should_queries.extend([ + ES_Q( + "nested", + path="authors", + query=ES_Q("match_phrase", name={"query": artist_query, "boost": 4}) + ), + ES_Q( + "nested", + path="authors", + query=ES_Q("match", name={"query": artist_query, "fuzziness": "AUTO", "boost": 3}) + ), + ES_Q( + "nested", + path="authors", + query=ES_Q("wildcard", name={"value": f"*{artist_query.lower()}*", "boost": 2}) + ), + ]) + else: + # If no explicit artist, still search in authors but with lower boost + should_queries.extend([ + ES_Q( + "nested", + path="authors", + query=ES_Q("match_phrase", name={"query": track_query, "boost": 2}), + ), + ES_Q( + "nested", + path="authors", + query=ES_Q("match", name={"query": track_query, "fuzziness": "AUTO", "boost": 1}), + ), + ]) + + # Add album queries with lower boost + should_queries.extend([ + ES_Q( + "nested", + path="album", + query=ES_Q("match_phrase", name={"query": track_query, "boost": 1.5}), + ), + ES_Q( + "nested", + path="album", + query=ES_Q("match", name={"query": track_query, "fuzziness": "AUTO", "boost": 1}), + ), + ]) + + # Combine all queries with minimum_should_match=1 search_query = ES_Q("bool", should=should_queries, minimum_should_match=1) + # Execute search with size limit search = search.query(search_query).extra(size=20) response = search.execute()