138 lines
5.5 KiB
Python
138 lines
5.5 KiB
Python
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
|