File size: 6,926 Bytes
25732fb | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 | 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]]
} |