fix(backend): fix 500 error in mastery stats endpoint with robust error handling

This commit is contained in:
Reviflow Bot committed 2026-01-31 13:26:04 +01:00
1 parent a4cc1da0f3
commit 75ebcef87f
1 file changed
+15 -4
+15 -4
View File
@@ -551,6 +551,7 @@ async def get_mastery_stats(
db: AsyncSession = Depends(get_async_session) db: AsyncSession = Depends(get_async_session)
): ):
"""Calculates mastery level per topic.""" """Calculates mastery level per topic."""
try:
from app.modules.quiz.models import RemediationQueue from app.modules.quiz.models import RemediationQueue
# 1. Get all scores # 1. Get all scores
@@ -568,6 +569,7 @@ async def get_mastery_stats(
topic_map = {} topic_map = {}
for s in scores: for s in scores:
if not s.topic: continue
# Normalize topic # Normalize topic
clean_topic = s.topic.replace(" (Remediation)", "").replace(" (Remédiation)", "").strip() clean_topic = s.topic.replace(" (Remediation)", "").replace(" (Remédiation)", "").strip()
@@ -589,12 +591,15 @@ async def get_mastery_stats(
error_counts = {} error_counts = {}
for e in errors: for e in errors:
if not e.topic: continue
# Also clean topic for errors if needed, or rely on exact match? # Also clean topic for errors if needed, or rely on exact match?
# Ideally errors should also be grouped by clean topic. # Ideally errors should also be grouped by clean topic.
t_key = e.topic.replace(" (Remediation)", "").replace(" (Remédiation)", "").strip() t_key = e.topic.replace(" (Remediation)", "").replace(" (Remédiation)", "").strip()
error_counts[t_key] = error_counts.get(t_key, 0) + 1 error_counts[t_key] = error_counts.get(t_key, 0) + 1
for topic, topic_scores in topic_map.items(): for topic, topic_scores in topic_map.items():
if not topic_scores: continue
# Sort by date asc (oldest first) # Sort by date asc (oldest first)
topic_scores.sort(key=lambda x: x.created_at) topic_scores.sort(key=lambda x: x.created_at)
@@ -627,13 +632,12 @@ async def get_mastery_stats(
elif final_mastery >= 50: elif final_mastery >= 50:
status = "REVIEWING" status = "REVIEWING"
last_activity = topic_scores[-1].created_at # Safely get last activity
last_activity = topic_scores[-1].created_at if topic_scores else datetime.utcnow()
# Find the latest revision for this topic to get synthesis and tips # Find the latest revision for this topic to get synthesis and tips
latest_revision = None latest_revision = None
# topic_scores are already sorted by date asc, so let's find latest revision try:
# Actually, let's find the latest revision object directly.
# Revision might have a different topic name? No, normalization should match.
from app.modules.quiz.models import Revision from app.modules.quiz.models import Revision
rev_stmt = select(Revision).where( rev_stmt = select(Revision).where(
Revision.learner_id == learner_id, Revision.learner_id == learner_id,
@@ -641,6 +645,8 @@ async def get_mastery_stats(
).order_by(Revision.created_at.desc()).limit(1) ).order_by(Revision.created_at.desc()).limit(1)
rev_result = await db.execute(rev_stmt) rev_result = await db.execute(rev_stmt)
latest_revision = rev_result.scalar_one_or_none() latest_revision = rev_result.scalar_one_or_none()
except Exception:
pass # Ignore revision fetch errors
mastery_list.append({ mastery_list.append({
"topic": topic, "topic": topic,
@@ -657,6 +663,11 @@ async def get_mastery_stats(
mastery_list.sort(key=lambda x: x['last_activity'], reverse=True) mastery_list.sort(key=lambda x: x['last_activity'], reverse=True)
return mastery_list return mastery_list
except Exception as e:
import traceback
print(f"ERROR get_mastery_stats: {str(e)}\n{traceback.format_exc()}")
# Return empty list instead of 500 to keep dashboard alive
return []
@router.get("/stats/activity") @router.get("/stats/activity")
async def get_activity_stats( async def get_activity_stats(