156 lines
4.9 KiB
Python
156 lines
4.9 KiB
Python
"""
|
|
Recommendation API - Personalized suggestions for users.
|
|
"""
|
|
from typing import List, Optional
|
|
from fastapi import APIRouter, Depends, Query
|
|
from sqlmodel import Session, select, desc, func
|
|
from pydantic import BaseModel
|
|
from datetime import datetime, timedelta
|
|
from database import get_session
|
|
from models import Show, Vertical, UserVerticalPreference, Attendance, Rating, Performance, Song, Venue, BandMembership
|
|
from auth import get_current_user
|
|
from models import User
|
|
|
|
router = APIRouter(prefix="/recommendations", tags=["recommendations"])
|
|
|
|
|
|
class RecommendedShow(BaseModel):
|
|
id: int
|
|
date: str
|
|
venue_name: str | None
|
|
vertical_name: str
|
|
vertical_slug: str
|
|
reason: str # "Recent Show", "Highly Rated", "Trending"
|
|
|
|
|
|
class RecommendedPerformance(BaseModel):
|
|
id: int
|
|
song_title: str
|
|
show_date: str
|
|
vertical_name: str
|
|
avg_rating: float
|
|
notes: str | None
|
|
|
|
|
|
@router.get("/shows/recent", response_model=List[RecommendedShow])
|
|
def get_recent_subscriptions(
|
|
limit: int = 10,
|
|
session: Session = Depends(get_session),
|
|
current_user: User = Depends(get_current_user)
|
|
):
|
|
"""
|
|
Get recent shows from bands the user follows, excluding attended shows.
|
|
"""
|
|
# 1. Get user preferences
|
|
prefs = session.exec(
|
|
select(UserVerticalPreference).where(UserVerticalPreference.user_id == current_user.id)
|
|
).all()
|
|
|
|
if not prefs:
|
|
# Fallback: Just return recent shows from featured verticals
|
|
# For now, return empty or generic
|
|
return []
|
|
|
|
subscribed_vertical_ids = [p.vertical_id for p in prefs]
|
|
|
|
# 2. Get Attended Show IDs
|
|
attended = session.exec(
|
|
select(Attendance.show_id).where(Attendance.user_id == current_user.id)
|
|
).all()
|
|
attended_ids = set(attended)
|
|
|
|
# 3. Query Recent Shows
|
|
query = (
|
|
select(Show)
|
|
.where(Show.vertical_id.in_(subscribed_vertical_ids))
|
|
.where(Show.date <= datetime.now()) # Past shows only
|
|
.where(Show.date >= datetime.now() - timedelta(days=90)) # Last 90 days
|
|
.order_by(desc(Show.date))
|
|
.limit(limit * 2) # Fetch extra to filter
|
|
)
|
|
|
|
shows = session.exec(query).all()
|
|
|
|
results = []
|
|
for show in shows:
|
|
if show.id in attended_ids:
|
|
continue
|
|
|
|
vertical = session.get(Vertical, show.vertical_id)
|
|
venue = session.get(Venue, show.venue_id) if show.venue_id else None
|
|
|
|
results.append(RecommendedShow(
|
|
id=show.id,
|
|
date=show.date.strftime("%Y-%m-%d"),
|
|
venue_name=venue.name if venue else "Unknown Venue",
|
|
vertical_name=vertical.name,
|
|
vertical_slug=vertical.slug,
|
|
reason="Recent from your bands"
|
|
))
|
|
|
|
if len(results) >= limit:
|
|
break
|
|
|
|
return results
|
|
|
|
|
|
@router.get("/performances/top", response_model=List[RecommendedPerformance])
|
|
def get_top_rated_tracks(
|
|
limit: int = 10,
|
|
session: Session = Depends(get_session),
|
|
current_user: User = Depends(get_current_user)
|
|
):
|
|
"""
|
|
Get top rated performances from bands the user follows.
|
|
"""
|
|
prefs = session.exec(
|
|
select(UserVerticalPreference).where(UserVerticalPreference.user_id == current_user.id)
|
|
).all()
|
|
|
|
if not prefs:
|
|
return []
|
|
|
|
subscribed_vertical_ids = [p.vertical_id for p in prefs]
|
|
|
|
# Complex query: Join Performance -> Show -> Vertical, Join Rating
|
|
# Getting avg rating per performance
|
|
|
|
# This might be slow on large datasets without materialized view.
|
|
# Optimized approach: Query Rating table, group by performance_id, filter by subscribed verticals
|
|
|
|
results = session.exec(
|
|
select(
|
|
Rating.performance_id,
|
|
func.avg(Rating.score).label("average"),
|
|
func.count(Rating.id).label("count")
|
|
)
|
|
.join(Performance, Rating.performance_id == Performance.id)
|
|
.join(Show, Performance.show_id == Show.id)
|
|
.where(Show.vertical_id.in_(subscribed_vertical_ids))
|
|
.where(Rating.performance_id.isnot(None))
|
|
.group_by(Rating.performance_id)
|
|
.having(func.count(Rating.id) >= 1) # At least 1 rating
|
|
.order_by(desc("average"))
|
|
.limit(limit)
|
|
).all()
|
|
|
|
recommendations = []
|
|
for row in results:
|
|
perf_id, avg, count = row
|
|
perf = session.get(Performance, perf_id)
|
|
if not perf: continue
|
|
|
|
show = session.get(Show, perf.show_id)
|
|
song = session.get(Song, perf.song_id)
|
|
vertical = session.get(Vertical, show.vertical_id)
|
|
|
|
recommendations.append(RecommendedPerformance(
|
|
id=perf.id,
|
|
song_title=song.title,
|
|
show_date=show.date.strftime("%Y-%m-%d"),
|
|
vertical_name=vertical.name,
|
|
avg_rating=round(avg, 1),
|
|
notes=f"Rated {round(avg, 1)}/10 by {count} fans"
|
|
))
|
|
|
|
return recommendations
|