89 lines
3.2 KiB
Python
89 lines
3.2 KiB
Python
from sqlmodel import Session, select
|
|
from uuid import UUID
|
|
import models
|
|
|
|
def calculate_readiness_score(user_id: UUID, session: Session) -> dict:
|
|
"""
|
|
Calculates the Exam Readiness Score (0-100) based on PRD A-05 parameters.
|
|
Weights: Accuracy (40%), Speed (20%), Syllabus Coverage (20%),
|
|
Consistency (10%), Mistake Recovery (10%).
|
|
"""
|
|
user = session.get(models.User, user_id)
|
|
if not user:
|
|
return {}
|
|
|
|
attempts = session.exec(
|
|
select(models.Attempt).where(models.Attempt.user_id == user_id)
|
|
).all()
|
|
|
|
if not attempts:
|
|
return {
|
|
"score": 0,
|
|
"status": "Not enough data",
|
|
"prediction": "Start practice to calculate",
|
|
"target_type": "Unranked",
|
|
"breakdown": {
|
|
"accuracy": 0,
|
|
"speed": 0,
|
|
"consistency": 0,
|
|
"recovery": 0,
|
|
"coverage": 0,
|
|
}
|
|
}
|
|
|
|
# 1. Accuracy (40% Weight)
|
|
avg_accuracy = sum(a.accuracy for a in attempts) / len(attempts)
|
|
w_accuracy = (avg_accuracy / 100) * 40
|
|
|
|
# 2. Speed (20% Weight) - Assuming benchmark is 120s per question
|
|
time_per_q = [ (a.time_taken / a.total_questions) for a in attempts if a.total_questions > 0 and a.time_taken ]
|
|
avg_time = sum(time_per_q) / len(time_per_q) if time_per_q else 120
|
|
speed_factor = min(1.0, 120 / max(avg_time, 1)) # Cap at 1.0
|
|
w_speed = speed_factor * 20
|
|
|
|
# 3. Consistency (10% Weight)
|
|
streak = min(user.streak_count, 30) # Max consistency score hit at 30 days
|
|
w_consistency = (streak / 30) * 10
|
|
|
|
# 4. Mistake Recovery (10% Weight)
|
|
mistakes = session.exec(select(models.Mistake).where(models.Mistake.user_id == user_id)).all()
|
|
mastered_count = sum(1 for m in mistakes if m.is_mastered)
|
|
total_mistakes = max(len(mistakes), 1)
|
|
w_recovery = (mastered_count / total_mistakes) * 10
|
|
|
|
# 5. Syllabus Coverage (20% Weight)
|
|
# For local sandbox, we will estimate based on total questions attempted
|
|
total_q_attempted = sum(a.total_questions for a in attempts)
|
|
coverage_factor = min(total_q_attempted / 500, 1.0) # Assume 500 questions = decent baseline coverage
|
|
w_coverage = coverage_factor * 20
|
|
|
|
# Total Calculation
|
|
total_score = round(w_accuracy + w_speed + w_consistency + w_recovery + w_coverage)
|
|
|
|
# Calculate College Prediction based on PRD A-07
|
|
prediction = "Keep Practicing"
|
|
target_type = "State Level"
|
|
|
|
if total_score >= 85:
|
|
prediction = "IIT Bombay / IIT Delhi within reach!"
|
|
target_type = "Reach"
|
|
elif total_score >= 70:
|
|
prediction = "NIT Trichy / NIT Surathkal on track."
|
|
target_type = "Target"
|
|
elif total_score >= 50:
|
|
prediction = "Top GFTIs / State Engineering Colleges."
|
|
target_type = "Safety"
|
|
|
|
return {
|
|
"score": total_score,
|
|
"breakdown": {
|
|
"accuracy": round(w_accuracy, 1),
|
|
"speed": round(w_speed, 1),
|
|
"consistency": round(w_consistency, 1),
|
|
"recovery": round(w_recovery, 1),
|
|
"coverage": round(w_coverage, 1)
|
|
},
|
|
"college_prediction": prediction,
|
|
"prediction_category": target_type
|
|
}
|