138 lines
5.1 KiB
Python
138 lines
5.1 KiB
Python
"""Achievement evaluation and awarding logic"""
|
|
from datetime import datetime, timedelta
|
|
from sqlalchemy.orm import Session
|
|
from sqlalchemy import func, distinct
|
|
|
|
from app.models import (
|
|
User,
|
|
Achievement,
|
|
UserAchievement,
|
|
QuizAttempt,
|
|
LoginEvent,
|
|
Enrollment,
|
|
ModuleProgress,
|
|
Course,
|
|
Module,
|
|
ProgressStatus,
|
|
)
|
|
|
|
|
|
def check_and_award_achievements(db: Session, user_id: int):
|
|
"""Check and award all eligible achievements for a user"""
|
|
user = db.get(User, user_id)
|
|
if not user:
|
|
return
|
|
|
|
# Get all achievements
|
|
all_achievements = db.query(Achievement).all()
|
|
existing_user_achievements = {
|
|
ua.achievement_id
|
|
for ua in db.query(UserAchievement).filter(UserAchievement.user_id == user_id).all()
|
|
}
|
|
|
|
for achievement in all_achievements:
|
|
if achievement.id in existing_user_achievements:
|
|
continue
|
|
|
|
earned = False
|
|
|
|
if achievement.code == "FIRST_COURSE_COMPLETE":
|
|
# Check if user has completed any course
|
|
completed_count = (
|
|
db.query(func.count(distinct(ModuleProgress.module_id)))
|
|
.join(Module, Module.id == ModuleProgress.module_id)
|
|
.filter(
|
|
ModuleProgress.user_id == user_id,
|
|
ModuleProgress.status == ProgressStatus.completed,
|
|
)
|
|
.scalar()
|
|
)
|
|
# Check if all modules in at least one course are completed
|
|
user_courses = (
|
|
db.query(Course.id)
|
|
.join(Enrollment, Enrollment.course_id == Course.id)
|
|
.filter(Enrollment.user_id == user_id)
|
|
.all()
|
|
)
|
|
for (course_id,) in user_courses:
|
|
total_modules = db.query(func.count(Module.id)).filter(Module.course_id == course_id).scalar()
|
|
completed_modules = (
|
|
db.query(func.count(Module.id))
|
|
.join(ModuleProgress, ModuleProgress.module_id == Module.id)
|
|
.filter(
|
|
Module.course_id == course_id,
|
|
ModuleProgress.user_id == user_id,
|
|
ModuleProgress.status == ProgressStatus.completed,
|
|
)
|
|
.scalar()
|
|
)
|
|
if total_modules > 0 and completed_modules >= total_modules:
|
|
earned = True
|
|
break
|
|
|
|
elif achievement.code == "PERFECT_FINAL":
|
|
# Check if any quiz attempt has score 100
|
|
perfect = (
|
|
db.query(QuizAttempt)
|
|
.filter(QuizAttempt.user_id == user_id, QuizAttempt.score == 100.0)
|
|
.first()
|
|
)
|
|
earned = perfect is not None
|
|
|
|
elif achievement.code == "THREE_PERFECTS_ROW":
|
|
# Check last 3 attempts for consecutive 100s
|
|
attempts = (
|
|
db.query(QuizAttempt)
|
|
.filter(QuizAttempt.user_id == user_id)
|
|
.order_by(QuizAttempt.submitted_at.desc())
|
|
.limit(3)
|
|
.all()
|
|
)
|
|
if len(attempts) >= 3 and all(a.score == 100.0 for a in attempts):
|
|
earned = True
|
|
|
|
elif achievement.code == "FAST_FINISHER":
|
|
# Check if any course was completed within 24h of enrollment
|
|
user_enrollments = (
|
|
db.query(Enrollment)
|
|
.filter(Enrollment.user_id == user_id)
|
|
.all()
|
|
)
|
|
for enrollment in user_enrollments:
|
|
total_modules = (
|
|
db.query(func.count(Module.id))
|
|
.filter(Module.course_id == enrollment.course_id)
|
|
.scalar()
|
|
)
|
|
completed_modules = (
|
|
db.query(ModuleProgress)
|
|
.join(Module, Module.id == ModuleProgress.module_id)
|
|
.filter(
|
|
Module.course_id == enrollment.course_id,
|
|
ModuleProgress.user_id == user_id,
|
|
ModuleProgress.status == ProgressStatus.completed,
|
|
)
|
|
.all()
|
|
)
|
|
if total_modules > 0 and len(completed_modules) >= total_modules:
|
|
# Check if last completion was within 24h of enrollment
|
|
last_completion = max(m.completed_at for m in completed_modules if m.completed_at)
|
|
if last_completion and (last_completion - enrollment.enrolled_at) <= timedelta(hours=24):
|
|
earned = True
|
|
break
|
|
|
|
elif achievement.code == "CONSISTENT_LEARNER":
|
|
# Check if user has logged in on 5 different days
|
|
distinct_days = (
|
|
db.query(func.date(LoginEvent.logged_in_at))
|
|
.filter(LoginEvent.user_id == user_id)
|
|
.distinct()
|
|
.count()
|
|
)
|
|
earned = distinct_days >= 5
|
|
|
|
if earned:
|
|
db.add(UserAchievement(user_id=user_id, achievement_id=achievement.id))
|
|
|
|
db.commit()
|