feat: implement AI-curated playlist service and dashboard integration

- Added hierarchical AGENTS.md knowledge base
- Implemented PlaylistService with 6h themed and 24h devotion mix logic
- Integrated AI theme generation for 6h playlists via Gemini/OpenAI
- Added /playlists/refresh and metadata endpoints to API
- Updated background worker with scheduled playlist curation
- Created frontend PlaylistsSection, Tooltip components and integrated into Dashboard
- Added Alembic migration for playlist tracking columns
- Fixed Docker healthcheck with curl installation
This commit is contained in:
bnair123
2025-12-30 09:45:19 +04:00
parent fa28b98c1a
commit 93e7c13f3d
18 changed files with 1037 additions and 295 deletions

View File

@@ -1,3 +1,4 @@
import os
from fastapi import FastAPI, Depends, HTTPException, BackgroundTasks, Query
from sqlalchemy.orm import Session, joinedload
from datetime import datetime, timedelta
@@ -11,9 +12,15 @@ from .models import (
AnalysisSnapshot,
)
from . import schemas
from .ingest import ingest_recently_played
from .ingest import (
ingest_recently_played,
get_spotify_client,
get_reccobeats_client,
get_genius_client,
)
from .services.stats_service import StatsService
from .services.narrative_service import NarrativeService
from .services.playlist_service import PlaylistService
load_dotenv()
@@ -204,3 +211,107 @@ def get_sessions(
"marathon_rate": session_stats.get("marathon_session_rate", 0),
},
}
@app.post("/playlists/refresh/six-hour")
async def refresh_six_hour_playlist(db: Session = Depends(get_db)):
"""Triggers a 6-hour themed playlist refresh."""
try:
end_date = datetime.utcnow()
start_date = end_date - timedelta(hours=6)
playlist_service = PlaylistService(
db=db,
spotify_client=get_spotify_client(),
recco_client=get_reccobeats_client(),
narrative_service=NarrativeService(),
)
result = await playlist_service.curate_six_hour_playlist(start_date, end_date)
snapshot = AnalysisSnapshot(
date=datetime.utcnow(),
period_start=start_date,
period_end=end_date,
period_label="6h_refresh",
metrics_payload={},
narrative_report={},
playlist_theme=result.get("theme_name"),
playlist_theme_reasoning=result.get("description"),
six_hour_playlist_id=result.get("playlist_id"),
)
db.add(snapshot)
db.commit()
return result
except Exception as e:
print(f"Playlist Refresh Failed: {e}")
raise HTTPException(status_code=500, detail=str(e))
@app.post("/playlists/refresh/daily")
async def refresh_daily_playlist(db: Session = Depends(get_db)):
"""Triggers a 24-hour daily playlist refresh."""
try:
end_date = datetime.utcnow()
start_date = end_date - timedelta(days=1)
playlist_service = PlaylistService(
db=db,
spotify_client=get_spotify_client(),
recco_client=get_reccobeats_client(),
narrative_service=NarrativeService(),
)
result = await playlist_service.curate_daily_playlist(start_date, end_date)
snapshot = AnalysisSnapshot(
date=datetime.utcnow(),
period_start=start_date,
period_end=end_date,
period_label="24h_refresh",
metrics_payload={},
narrative_report={},
daily_playlist_id=result.get("playlist_id"),
)
db.add(snapshot)
db.commit()
return result
except Exception as e:
print(f"Daily Playlist Refresh Failed: {e}")
raise HTTPException(status_code=500, detail=str(e))
@app.get("/playlists")
async def get_playlists_metadata(db: Session = Depends(get_db)):
"""Returns metadata for the managed playlists."""
latest_snapshot = (
db.query(AnalysisSnapshot)
.filter(AnalysisSnapshot.six_hour_playlist_id != None)
.order_by(AnalysisSnapshot.date.desc())
.first()
)
return {
"six_hour": {
"id": latest_snapshot.six_hour_playlist_id
if latest_snapshot
else os.getenv("SIX_HOUR_PLAYLIST_ID"),
"theme": latest_snapshot.playlist_theme if latest_snapshot else "N/A",
"reasoning": latest_snapshot.playlist_theme_reasoning
if latest_snapshot
else "N/A",
"last_refresh": latest_snapshot.date.isoformat()
if latest_snapshot
else None,
},
"daily": {
"id": latest_snapshot.daily_playlist_id
if latest_snapshot
else os.getenv("DAILY_PLAYLIST_ID"),
"last_refresh": latest_snapshot.date.isoformat()
if latest_snapshot
else None,
},
}