| 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) |
| |
| |
| 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 |
| |
| |
| difficulty_weight = self._get_difficulty_weight(difficulty) |
| |
| if is_correct: |
| |
| 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: |
| |
| 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) |
| |
| |
| 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 |
| |
| |
| all_topics = Topic.query.all() |
| |
| |
| knowledge_states = KnowledgeState.query.filter_by(user_id=user_id).all() |
| knowledge_map = {ks.topic_id: ks.knowledge_level for ks in knowledge_states} |
| |
| |
| topic_scores = [] |
| for topic in all_topics: |
| if topic.id == current_topic_id: |
| continue |
| |
| score = 0 |
| knowledge_level = knowledge_map.get(topic.id, 0) |
| |
| |
| if knowledge_level == 0: |
| score += 3 |
| elif 0 < knowledge_level < 0.7: |
| score += 2 |
| elif knowledge_level >= 0.7: |
| score += 0.5 |
| |
| |
| 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 |
| else: |
| score -= 1 |
| else: |
| score += 1 |
| |
| topic_scores.append((topic, score)) |
| |
| |
| 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]] |
| } |