VibeFinder / src /main.py
Abubakar Diallo
Add VibeFinder app
55ed80a
Raw
History Blame Contribute Delete
8.72 kB
"""
Command-line runner for the Music Recommender Simulation with 4 Advanced Challenges.
Run from the project root:
python -m src.main
Challenges implemented:
1. Advanced Song Features: popularity, release_decade, detailed_mood_tags
2. Multiple Scoring Modes: genre-first, mood-first, energy-focused, popularity-aware
3. Diversity Penalty: prevents same artist from appearing multiple times
4. Visual Summary Table: tabulate-based formatted output (Challenge 4)
"""
from src.recommender import load_songs, recommend_songs
# Try to import tabulate for Challenge 4 (visual tables)
try:
from tabulate import tabulate
TABULATE_AVAILABLE = True
except ImportError:
TABULATE_AVAILABLE = False
print("Note: Install 'tabulate' for prettier table output: pip install tabulate")
# ── User profiles ──────────────────────────────────────────────────────────────
PROFILES = {
"High-Energy Pop Fan": {
"genre": "pop",
"mood": "happy",
"energy": 0.90,
"target_valence": 0.85,
"likes_acoustic": False,
},
"Chill Lofi Student": {
"genre": "lofi",
"mood": "chill",
"energy": 0.38,
"target_valence": 0.58,
"likes_acoustic": True,
},
"Deep Intense Rock": {
"genre": "rock",
"mood": "intense",
"energy": 0.92,
"target_valence": 0.45,
"likes_acoustic": False,
},
"Adversarial: High-Energy Blues (Conflicting)": {
"genre": "blues",
"mood": "sad",
"energy": 0.90,
"target_valence": 0.20,
"likes_acoustic": True,
},
"Edge Case: Genre Not in Catalog (Reggae)": {
"genre": "reggae",
"mood": "relaxed",
"energy": 0.60,
"target_valence": 0.75,
"likes_acoustic": False,
},
}
# Challenge 1: New profile that uses detailed mood tags (advanced feature)
MOOD_TAG_PROFILE = {
"genre": "lofi",
"mood": "chill",
"energy": 0.40,
"target_valence": 0.60,
"likes_acoustic": True,
"mood_tags": ["nostalgic", "dreamy", "peaceful"], # Challenge 1: Uses new feature
}
EXPERIMENT_WEIGHTS = {
"genre": 1.0,
"mood": 1.0,
"energy": 2.0,
"valence": 0.5,
"acoustic": 0.5,
}
def print_results(profile_name: str, user_prefs: dict, results: list, mode: str = "default") -> None:
"""Prints a formatted block of recommendations with optional Challenge 4 table."""
print()
print("=" * 70)
print(f" {profile_name} (Mode: {mode})")
print("=" * 70)
print(
f" genre={user_prefs.get('genre')} | "
f"mood={user_prefs.get('mood')} | "
f"energy={user_prefs.get('energy')}"
)
print("-" * 70)
# Challenge 4: Visual Summary Table
if TABULATE_AVAILABLE and results:
table_data = []
for rank, (song, score, reasons) in enumerate(results, start=1):
table_data.append([
rank,
song['title'],
song['artist'],
song['genre'],
song['mood'],
f"{score:.2f}",
"; ".join(reasons[:1]), # First reason only for table
])
headers = ["#", "Title", "Artist", "Genre", "Mood", "Score", "Why"]
print(tabulate(table_data, headers=headers, tablefmt="grid"))
else:
# Fallback to original bullet format
for rank, (song, score, reasons) in enumerate(results, start=1):
print(f"\n #{rank} {song['title']} β€” {song['artist']}")
print(f" Score : {score:.2f}")
print(f" Genre : {song['genre']} Mood : {song['mood']} Energy : {song['energy']}")
print(" Why :")
for reason in reasons:
print(f" β€’ {reason}")
print()
def main() -> None:
"""Demonstrates all 4 challenges."""
songs = load_songs("data/songs.csv")
# ──────────────────────────────────────────────────────────────────────────
# CHALLENGE 1: Advanced Song Features
# ──────────────────────────────────────────────────────────────────────────
print("\n" + "β–ˆ" * 70)
print(" CHALLENGE 1 β€” Advanced Song Features (Popularity, Decade, Mood Tags)")
print("β–ˆ" * 70)
print("\n Songs now have:")
print(" β€’ Popularity (0-100): " + ", ".join([f"{s['title']} ({s['popularity']})" for s in songs[:3]]))
print(" β€’ Release Decade: " + ", ".join([f"{s['title']} ({s['release_decade']})" for s in songs[:3]]))
print(" β€’ Detailed Mood Tags: " + f"{songs[0]['title']} {songs[0]['detailed_mood_tags']}")
# ──────────────────────────────────────────────────────────────────────────
# CHALLENGE 2: Multiple Scoring Modes
# ──────────────────────────────────────────────────────────────────────────
print("\n" + "β–ˆ" * 70)
print(" CHALLENGE 2 β€” Multiple Scoring Modes")
print("β–ˆ" * 70)
pop_prefs = PROFILES["High-Energy Pop Fan"]
print("\n Mode 1: DEFAULT (Genre-First)")
default_results = recommend_songs(pop_prefs, songs, k=5, mode="default")
print_results("DEFAULT MODE", pop_prefs, default_results, mode="default")
print(" Mode 2: MOOD-FIRST (Mood is primary signal)")
mood_first_results = recommend_songs(pop_prefs, songs, k=5, mode="mood_first")
print_results("MOOD-FIRST MODE", pop_prefs, mood_first_results, mode="mood_first")
print(" Mode 3: ENERGY-FOCUSED (Energy dominates)")
energy_results = recommend_songs(pop_prefs, songs, k=5, mode="energy_focused")
print_results("ENERGY-FOCUSED MODE", pop_prefs, energy_results, mode="energy_focused")
# ──────────────────────────────────────────────────────────────────────────
# CHALLENGE 3: Diversity Penalty
# ──────────────────────────────────────────────────────────────────────────
print("\n" + "β–ˆ" * 70)
print(" CHALLENGE 3 β€” Diversity Penalty (Prevents Duplicate Artists)")
print("β–ˆ" * 70)
print("\n WITHOUT Diversity Penalty:")
results_no_diversity = recommend_songs(pop_prefs, songs, k=5, diversity_penalty=False)
for rank, (song, score, _) in enumerate(results_no_diversity, 1):
print(f" #{rank} {song['title']} by {song['artist']} [{score:.2f}]")
print("\n WITH Diversity Penalty (penalizes duplicate artists):")
results_with_diversity = recommend_songs(pop_prefs, songs, k=5, diversity_penalty=True)
for rank, (song, score, _) in enumerate(results_with_diversity, 1):
print(f" #{rank} {song['title']} by {song['artist']} [{score:.2f}]")
# ──────────────────────────────────────────────────────────────────────────
# CHALLENGE 4: Visual Summary Table (already shown above with tabulate)
# ──────────────────────────────────────────────────────────────────────────
print("\n" + "β–ˆ" * 70)
print(" CHALLENGE 4 β€” Visual Summary Tables (shown above with tabulate)")
print("β–ˆ" * 70)
print("\n Tables display rank, title, artist, genre, mood, score, and reasons.")
if not TABULATE_AVAILABLE:
print(" Install tabulate for better formatting: pip install tabulate")
print()
if __name__ == "__main__":
main()