import os from typing import Dict, Any, List from datetime import datetime from sqlalchemy.orm import Session from .spotify_client import SpotifyClient from .reccobeats_client import ReccoBeatsClient from .narrative_service import NarrativeService class PlaylistService: def __init__( self, db: Session, spotify_client: SpotifyClient, recco_client: ReccoBeatsClient, narrative_service: NarrativeService, ) -> None: self.db = db self.spotify = spotify_client self.recco = recco_client self.narrative = narrative_service async def ensure_playlists_exist(self, user_id: str) -> Dict[str, str]: """Check/create playlists. Returns {six_hour_id, daily_id}.""" six_hour_env = os.getenv("SIX_HOUR_PLAYLIST_ID") daily_env = os.getenv("DAILY_PLAYLIST_ID") if not six_hour_env: six_hour_data = await self.spotify.create_playlist( user_id=user_id, name="Short and Sweet", description="AI-curated 6-hour playlists based on your listening habits", ) six_hour_env = str(six_hour_data["id"]) if not daily_env: daily_data = await self.spotify.create_playlist( user_id=user_id, name="Proof of Commitment", description="Your daily 24-hour mix showing your music journey", ) daily_env = str(daily_data["id"]) return {"six_hour_id": str(six_hour_env), "daily_id": str(daily_env)} async def curate_six_hour_playlist( self, period_start: datetime, period_end: datetime ) -> Dict[str, Any]: """Generate 6-hour playlist (15 curated + 15 recommendations).""" from app.models import Track from app.services.stats_service import StatsService stats = StatsService(self.db) data = stats.generate_full_report(period_start, period_end) listening_data = { "peak_hour": data["time_habits"]["peak_hour"], "avg_energy": data["vibe"]["avg_energy"], "avg_valence": data["vibe"]["avg_valence"], "total_plays": data["volume"]["total_plays"], "top_artists": data["volume"]["top_artists"][:10], } theme_result = self.narrative.generate_playlist_theme(listening_data) curated_track_names = theme_result.get("curated_tracks", []) curated_tracks: List[str] = [] for name in curated_track_names: track = self.db.query(Track).filter(Track.name.ilike(f"%{name}%")).first() if track: curated_tracks.append(str(track.id)) recommendations: List[str] = [] if curated_tracks: recs = await self.recco.get_recommendations( seed_ids=curated_tracks[:5], size=15, ) recommendations = [ str(r.get("spotify_id") or r.get("id")) for r in recs if r.get("spotify_id") or r.get("id") ] final_tracks = curated_tracks[:15] + recommendations[:15] playlist_id = os.getenv("SIX_HOUR_PLAYLIST_ID") if playlist_id: await self.spotify.update_playlist_details( playlist_id=playlist_id, name=f"Short and Sweet - {theme_result['theme_name']}", description=( f"{theme_result['description']}\n\nCurated: {len(curated_tracks)} tracks + {len(recommendations)} recommendations" ), ) await self.spotify.replace_playlist_tracks( playlist_id=playlist_id, track_uris=[f"spotify:track:{tid}" for tid in final_tracks], ) return { "playlist_id": playlist_id, "theme_name": theme_result["theme_name"], "description": theme_result["description"], "track_count": len(final_tracks), "curated_count": len(curated_tracks), "rec_count": len(recommendations), "refreshed_at": datetime.utcnow().isoformat(), } async def curate_daily_playlist( self, period_start: datetime, period_end: datetime ) -> Dict[str, Any]: """Generate 24-hour playlist (30 favorites + 20 discoveries).""" from app.models import Track from app.services.stats_service import StatsService stats = StatsService(self.db) data = stats.generate_full_report(period_start, period_end) top_all_time = self._get_top_all_time_tracks(limit=30) recent_tracks = [track["id"] for track in data["volume"]["top_tracks"][:20]] final_tracks = (top_all_time + recent_tracks)[:50] playlist_id = os.getenv("DAILY_PLAYLIST_ID") theme_name = f"Proof of Commitment - {datetime.utcnow().date().isoformat()}" if playlist_id: await self.spotify.update_playlist_details( playlist_id=playlist_id, name=theme_name, description=( f"{theme_name} reflects the past 24 hours plus your all-time devotion." ), ) await self.spotify.replace_playlist_tracks( playlist_id=playlist_id, track_uris=[f"spotify:track:{tid}" for tid in final_tracks], ) return { "playlist_id": playlist_id, "theme_name": theme_name, "description": "Daily mix refreshed with your favorites and discoveries.", "track_count": len(final_tracks), "favorites_count": len(top_all_time), "recent_discoveries_count": len(recent_tracks), "refreshed_at": datetime.utcnow().isoformat(), } def _get_top_all_time_tracks(self, limit: int = 30) -> List[str]: """Get top tracks by play count from all-time history.""" from app.models import PlayHistory, Track from sqlalchemy import func result = ( self.db.query(Track.id, func.count(PlayHistory.id).label("play_count")) .join(PlayHistory, Track.id == PlayHistory.track_id) .group_by(Track.id) .order_by(func.count(PlayHistory.id).desc()) .limit(limit) .all() ) return [track_id for track_id, _ in result]