adaptive-elearning-backend / backend /knowledge_tracker.py
DarainHyder
Initial clean deploy commit: removing binary files and venv
25732fb
import math
from datetime import datetime, timedelta
from models import db, KnowledgeState, QuizAttempt
from config import Config
class KnowledgeTracker:
def __init__(self):
self.learning_rate = Config.LEARNING_RATE
self.forgetting_rate = Config.FORGETTING_RATE
def get_or_create_knowledge_state(self, user_id, topic_id):
"""Get or create knowledge state for a user-topic pair"""
state = KnowledgeState.query.filter_by(
user_id=user_id,
topic_id=topic_id
).first()
if not state:
state = KnowledgeState(
user_id=user_id,
topic_id=topic_id,
knowledge_level=Config.INITIAL_KNOWLEDGE,
confidence=0.0
)
db.session.add(state)
db.session.commit()
return state
def update_knowledge(self, user_id, topic_id, is_correct, difficulty, time_taken=None):
"""Update knowledge state based on quiz performance"""
state = self.get_or_create_knowledge_state(user_id, topic_id)
# Apply forgetting curve based on time since last practice (only if knowledge > 0)
if state.knowledge_level > 0:
time_diff = (datetime.utcnow() - state.last_practiced).days
if time_diff > 0:
forgetting_factor = math.exp(-self.forgetting_rate * time_diff)
state.knowledge_level *= forgetting_factor
# Update based on current performance
difficulty_weight = self._get_difficulty_weight(difficulty)
if is_correct:
# Increase knowledge level
delta = self.learning_rate * difficulty_weight * (1 - state.knowledge_level)
state.knowledge_level = min(1.0, state.knowledge_level + delta)
state.confidence = min(1.0, state.confidence + 0.1)
else:
# Decrease knowledge level slightly (only if there was knowledge to begin with)
if state.knowledge_level > 0:
delta = self.learning_rate * difficulty_weight * state.knowledge_level * 0.3
state.knowledge_level = max(0.0, state.knowledge_level - delta)
state.confidence = max(0.0, state.confidence - 0.1)
# Update metadata
state.last_practiced = datetime.utcnow()
state.practice_count += 1
db.session.commit()
return state
def _get_difficulty_weight(self, difficulty):
"""Get weight based on difficulty level"""
weights = {
'beginner': 0.7,
'intermediate': 1.0,
'advanced': 1.3
}
return weights.get(difficulty, 1.0)
def get_recommended_difficulty(self, user_id, topic_id):
"""Recommend difficulty level based on knowledge state"""
state = self.get_or_create_knowledge_state(user_id, topic_id)
if state.knowledge_level < 0.3:
return 'beginner'
elif state.knowledge_level < 0.7:
return 'intermediate'
else:
return 'advanced'
def get_next_topic(self, user_id, current_topic_id=None):
"""Recommend next topic to learn based on knowledge states"""
from models import Topic
# Get all topics
all_topics = Topic.query.all()
# Get user's knowledge states
knowledge_states = KnowledgeState.query.filter_by(user_id=user_id).all()
knowledge_map = {ks.topic_id: ks.knowledge_level for ks in knowledge_states}
# Score topics based on various factors
topic_scores = []
for topic in all_topics:
if topic.id == current_topic_id:
continue
score = 0
knowledge_level = knowledge_map.get(topic.id, 0)
# Prefer topics that haven't been started or are in progress
if knowledge_level == 0:
score += 3 # Prioritize unstarted topics
elif 0 < knowledge_level < 0.7:
score += 2 # Then in-progress topics
elif knowledge_level >= 0.7:
score += 0.5 # Lower priority for mastered topics
# Check prerequisites
if topic.prerequisites:
prereq_ids = [int(x.strip()) for x in topic.prerequisites.split(',') if x.strip()]
prereq_met = all(knowledge_map.get(pid, 0) > 0.5 for pid in prereq_ids)
if prereq_met:
score += 2
elif any(knowledge_map.get(pid, 0) > 0 for pid in prereq_ids):
score += 1 # Some prerequisites started
else:
score -= 1 # Prerequisites not met, but don't exclude completely
else:
score += 1 # No prerequisites, easier to start
topic_scores.append((topic, score))
# Sort by score and return top topic
topic_scores.sort(key=lambda x: x[1], reverse=True)
if topic_scores:
return topic_scores[0][0]
return None
def get_progress_summary(self, user_id):
"""Get overall learning progress for a user"""
knowledge_states = KnowledgeState.query.filter_by(user_id=user_id).all()
if not knowledge_states:
return {
'average_knowledge': 0,
'topics_mastered': 0,
'topics_in_progress': 0,
'total_practice_count': 0,
'weak_topics': [],
'strong_topics': []
}
total_knowledge = sum(ks.knowledge_level for ks in knowledge_states)
avg_knowledge = total_knowledge / len(knowledge_states)
mastered = [ks for ks in knowledge_states if ks.knowledge_level >= 0.8]
in_progress = [ks for ks in knowledge_states if 0 < ks.knowledge_level < 0.8]
weak = [ks for ks in knowledge_states if ks.knowledge_level <= 0.3 and ks.knowledge_level > 0]
not_started = [ks for ks in knowledge_states if ks.knowledge_level == 0]
total_practice = sum(ks.practice_count for ks in knowledge_states)
return {
'average_knowledge': round(avg_knowledge, 2),
'topics_mastered': len(mastered),
'topics_in_progress': len(in_progress),
'total_practice_count': total_practice,
'weak_topics': [{'id': ks.topic_id, 'level': round(ks.knowledge_level, 2)}
for ks in sorted(weak + not_started, key=lambda x: x.knowledge_level)[:5]],
'strong_topics': [{'id': ks.topic_id, 'level': round(ks.knowledge_level, 2)}
for ks in sorted(mastered, key=lambda x: x.knowledge_level, reverse=True)[:5]]
}