from fastapi import APIRouter from enum import Enum import duckdb # Added for specific exception handling from models.result import Result, TeamResult from models.msg import Message from lib.db import get_connection from lib.logger import logger router = APIRouter(prefix="/results", tags=["results"]) # It's good practice for FastAPI to have Enum derive from str as well class ResultType(str, Enum): DEVIATION = "deviation" COSTUME = "costume" SHOW = "show" SONG = "song" @router.get("/{result_type}", response_model=list[Result] | Message) async def list_results(result_type: ResultType): query = "" try: with get_connection() as conn: if result_type == ResultType.DEVIATION: # Selects the song with the highest standard deviation in its total review scores query = """ WITH SongReviewDeviations AS ( SELECT song_id, COALESCE(STDDEV_SAMP((score_song + score_show + score_costume) / 3.0), 0) AS deviation_value FROM Review GROUP BY song_id ) SELECT ras.song_id, ras.country_fi, ras.country_sv, ras.artist, ras.title, ras.avg_score_song, ras.avg_score_show, ras.avg_score_costume, ras.avg_total_score FROM ReviewAllSongs ras JOIN SongReviewDeviations srd ON ras.song_id = srd.song_id ORDER BY srd.deviation_value DESC LIMIT 1; """ df = conn.execute(query).fetchdf() logger.debug(f"Query for DEVIATION: {query}") logger.debug(f"Result DataFrame for DEVIATION: {df}") elif result_type == ResultType.COSTUME: # Selects the song with the highest average costume score query = """SELECT * FROM ReviewAllSongs ORDER BY avg_score_costume DESC LIMIT 1 """ df = conn.execute(query).fetchdf() elif result_type == ResultType.SHOW: # Selects the song with the highest average show score query = """SELECT * FROM ReviewAllSongs ORDER BY avg_score_show DESC LIMIT 1 """ df = conn.execute(query).fetchdf() elif result_type == ResultType.SONG: # Selects the song with the highest average song score query = """SELECT * FROM ReviewAllSongs ORDER BY avg_score_song DESC LIMIT 1 """ df = conn.execute(query).fetchdf() else: # This case should ideally not be reached if using Enums properly with FastAPI return Message(type="error", message="Invalid result type") if df.empty: # If the dataframe is empty, return an empty list. # This applies if a view exists but has no data, or LIMIT 1 returns no row. return [] # The existing code implies Result model can handle various structures. # For DEVIATION, row will be {'deviation_value': X} # For COSTUME/SHOW/SONG (LIMIT 1), it will be a single row from ReviewAllSongs. # For others, it will be multiple rows from their respective views. # All these are converted to a list of Result objects. results = [Result(**row) for row in df.to_dict(orient="records")] return results except duckdb.CatalogException as e: # Handles errors like "View not found" (e.g., ReviewAllSongs doesn't exist) # You might want to log the error e print(f"Database Catalog Error: {e}") return Message( type="error", message=f"Data source for '{result_type.value}' not found or query error. Details: {str(e)}", ) except duckdb.Error as e: # Catch other DuckDB specific errors print(f"DuckDB Error: {e}") return Message( type="error", message=f"Database query error for '{result_type.value}'. Details: {str(e)}", ) except Exception as e: # Catch any other unexpected errors print(f"Unexpected Error: {e}") return Message( type="error", message=f"An unexpected error occurred while fetching results for '{result_type.value}'.", ) @router.get("/global", response_model=list[Result]) async def list_global_results(): with get_connection() as conn: df = conn.execute("SELECT * FROM ReviewSummaryGlobal").fetchdf() if df.empty: return [] results = [Result(**row) for row in df.to_dict(orient="records")] return results @router.get("/user/{user_id}", response_model=list[Result]) async def list_user_results(user_id: int): with get_connection() as conn: df = conn.execute( "SELECT * FROM ReviewSummaryByUser WHERE user_id = ?", (user_id,) ).fetchdf() if df.empty: return [] results = [Result(**row) for row in df.to_dict(orient="records")] return results @router.get("/team/{team_id}", response_model=list[TeamResult]) async def list_team_results(team_id: int): with get_connection() as conn: df = conn.execute( "SELECT * FROM ReviewSummaryByTeam WHERE team_id = ?", (team_id,) ).fetchdf() if df.empty: return [] results = [TeamResult(**row) for row in df.to_dict(orient="records")] return results