feat: add recommendation domain foundations
This commit is contained in:
parent
fad7884c42
commit
5c3ba8d6ec
10 changed files with 446 additions and 0 deletions
54
backend/tests/test_matching.py
Normal file
54
backend/tests/test_matching.py
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
"""Tests for conservative Spotify candidate matching."""
|
||||
|
||||
from app.domain.matching import judge_candidate_match, normalize_text, title_similarity
|
||||
from app.domain.models import Track, TrackCandidate
|
||||
|
||||
|
||||
def test_normalization_removes_case_punctuation_marks_and_suffixes() -> None:
|
||||
value = " H\u00e9llo, WORLD! (2011 Remaster) [Deluxe] "
|
||||
|
||||
assert normalize_text(value) == "hello world"
|
||||
|
||||
|
||||
def test_title_similarity_uses_normalized_values() -> None:
|
||||
assert title_similarity("Signal Fire", "Signal Fire (Remastered)") == 1.0
|
||||
|
||||
|
||||
def test_matching_accepts_a_similar_title_with_the_requested_artist() -> None:
|
||||
candidate = TrackCandidate(title="The Night Drive", artist="Example Artist")
|
||||
track = _track(title="The Night Drive (Radio Edit)", artist="Example Artist")
|
||||
|
||||
verdict = judge_candidate_match(candidate, track, title_threshold=0.82)
|
||||
|
||||
assert verdict.is_match
|
||||
assert verdict.is_artist_match
|
||||
|
||||
|
||||
def test_matching_drops_a_title_substitution_from_another_artist() -> None:
|
||||
candidate = TrackCandidate(title="The Night Drive", artist="Requested Artist")
|
||||
substitution = _track(title="The Night Drive", artist="Different Artist")
|
||||
|
||||
verdict = judge_candidate_match(candidate, substitution, title_threshold=0.82)
|
||||
|
||||
assert not verdict.is_match
|
||||
assert verdict.title_similarity == 1.0
|
||||
assert not verdict.is_artist_match
|
||||
|
||||
|
||||
def test_matching_drops_a_weak_title_even_for_the_right_artist() -> None:
|
||||
candidate = TrackCandidate(title="The Night Drive", artist="Requested Artist")
|
||||
substitution = _track(title="Morning Train", artist="Requested Artist")
|
||||
|
||||
assert not judge_candidate_match(candidate, substitution, title_threshold=0.82).is_match
|
||||
|
||||
|
||||
def _track(*, title: str, artist: str) -> Track:
|
||||
return Track(
|
||||
id="track-id",
|
||||
uri="spotify:track:track-id",
|
||||
title=title,
|
||||
artists=(artist,),
|
||||
album_name="Album",
|
||||
album_art_url=None,
|
||||
external_url=None,
|
||||
)
|
||||
46
backend/tests/test_profile.py
Normal file
46
backend/tests/test_profile.py
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
"""Tests for compact taste profile construction."""
|
||||
|
||||
from app.domain.models import TasteProfile, Track
|
||||
from app.domain.profile import compress_taste_profile
|
||||
|
||||
|
||||
def test_profile_compression_includes_bounded_signals_and_known_ids() -> None:
|
||||
short_track = _track("short", "Quick Song", "Quick Artist")
|
||||
long_track = _track("long", "Lasting Song", "Lasting Artist")
|
||||
saved_track = _track("saved", "Saved Song", "Saved Artist")
|
||||
profile = TasteProfile(
|
||||
short_term_artists=("Current Artist",),
|
||||
long_term_artists=("Enduring Artist",),
|
||||
short_term_tracks=(short_track,),
|
||||
long_term_tracks=(long_track,),
|
||||
saved_tracks=(saved_track,),
|
||||
)
|
||||
|
||||
compressed = compress_taste_profile(profile)
|
||||
|
||||
assert compressed.text.splitlines() == [
|
||||
"Short-term top artists: Current Artist",
|
||||
"Long-term top artists: Enduring Artist",
|
||||
"Short-term top tracks: Quick Song by Quick Artist",
|
||||
"Long-term top tracks: Lasting Song by Lasting Artist",
|
||||
"Saved-track sample: Saved Song by Saved Artist",
|
||||
]
|
||||
assert compressed.known_track_ids == frozenset({"short", "long", "saved"})
|
||||
|
||||
|
||||
def test_empty_profile_sections_are_explicit() -> None:
|
||||
profile = TasteProfile((), (), (), (), ())
|
||||
|
||||
assert compress_taste_profile(profile).text.count(": none") == 5
|
||||
|
||||
|
||||
def _track(track_id: str, title: str, artist: str) -> Track:
|
||||
return Track(
|
||||
id=track_id,
|
||||
uri=f"spotify:track:{track_id}",
|
||||
title=title,
|
||||
artists=(artist,),
|
||||
album_name="Album",
|
||||
album_art_url=None,
|
||||
external_url=None,
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue