File size: 5,351 Bytes
1af10cc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""
Learning path generation tools for TutorX.
"""
import random
from typing import Dict, Any, List, Optional
from datetime import datetime, timedelta
import sys
import os
from pathlib import Path

# Add the parent directory to the Python path
current_dir = Path(__file__).parent
parent_dir = current_dir.parent.parent
sys.path.insert(0, str(parent_dir))

import sys
import os
from pathlib import Path

# Add the parent directory to the Python path
current_dir = Path(__file__).parent
parent_dir = current_dir.parent
sys.path.insert(0, str(parent_dir))

# Import from local resources
from resources.concept_graph import CONCEPT_GRAPH

# Import MCP
from mcp_server.mcp_instance import mcp

def get_prerequisites(concept_id: str, visited: Optional[set] = None) -> List[Dict[str, Any]]:
    """
    Get all prerequisites for a concept recursively.
    
    Args:
        concept_id: ID of the concept to get prerequisites for
        visited: Set of already visited concepts to avoid cycles
        
    Returns:
        List of prerequisite concepts in order
    """
    if visited is None:
        visited = set()
    
    if concept_id not in CONCEPT_GRAPH or concept_id in visited:
        return []
    
    visited.add(concept_id)
    prerequisites = []
    
    # Get direct prerequisites
    for prereq_id in CONCEPT_GRAPH[concept_id].get("prerequisites", []):
        if prereq_id in CONCEPT_GRAPH and prereq_id not in visited:
            prerequisites.extend(get_prerequisites(prereq_id, visited))
    
    # Add the current concept
    prerequisites.append(CONCEPT_GRAPH[concept_id])
    return prerequisites

def generate_learning_path(concept_ids: List[str], student_level: str = "beginner") -> Dict[str, Any]:
    """
    Generate a personalized learning path for a student.
    
    Args:
        concept_ids: List of concept IDs to include in the learning path
        student_level: Student's current level (beginner, intermediate, advanced)
        
    Returns:
        Dictionary containing the learning path
    """
    if not concept_ids:
        return {"error": "At least one concept ID is required"}
    
    # Get all prerequisites for each concept
    all_prerequisites = []
    visited = set()
    
    for concept_id in concept_ids:
        if concept_id in CONCEPT_GRAPH:
            prereqs = get_prerequisites(concept_id, visited)
            all_prerequisites.extend(prereqs)
    
    # Remove duplicates while preserving order
    unique_concepts = []
    seen = set()
    for concept in all_prerequisites:
        if concept["id"] not in seen:
            seen.add(concept["id"])
            unique_concepts.append(concept)
    
    # Add any target concepts not already in the path
    for concept_id in concept_ids:
        if concept_id in CONCEPT_GRAPH and concept_id not in seen:
            unique_concepts.append(CONCEPT_GRAPH[concept_id])
    
    # Estimate time required for each concept based on student level
    time_estimates = {
        "beginner": {"min": 30, "max": 60},    # 30-60 minutes per concept
        "intermediate": {"min": 20, "max": 45},  # 20-45 minutes per concept
        "advanced": {"min": 15, "max": 30}      # 15-30 minutes per concept
    }
    
    level = student_level.lower()
    if level not in time_estimates:
        level = "beginner"
    
    time_min = time_estimates[level]["min"]
    time_max = time_estimates[level]["max"]
    
    # Generate learning path with estimated times
    learning_path = []
    total_minutes = 0
    
    for i, concept in enumerate(unique_concepts, 1):
        # Random time estimate within range
        minutes = random.randint(time_min, time_max)
        total_minutes += minutes
        
        learning_path.append({
            "step": i,
            "concept_id": concept["id"],
            "concept_name": concept["name"],
            "description": concept.get("description", ""),
            "estimated_time_minutes": minutes,
            "resources": [
                f"Video tutorial on {concept['name']}",
                f"{concept['name']} documentation",
                f"Practice exercises for {concept['name']}"
            ]
        })
    
    # Calculate total time
    hours, minutes = divmod(total_minutes, 60)
    total_time = f"{hours}h {minutes}m" if hours > 0 else f"{minutes}m"
    
    return {
        "learning_path": learning_path,
        "total_steps": len(learning_path),
        "total_time_minutes": total_minutes,
        "total_time_display": total_time,
        "student_level": student_level,
        "generated_at": datetime.utcnow().isoformat() + "Z"
    }

@mcp.tool()
async def get_learning_path(student_id: str, concept_ids: List[str], student_level: Optional[str] = None) -> Dict[str, Any]:
    """
    Generate a personalized learning path for a student.
    
    Args:
        student_id: Unique identifier for the student
        concept_ids: List of concept IDs to include in the learning path
        student_level: Optional student level (beginner, intermediate, advanced)
        
    Returns:
        Dictionary containing the learning path
    """
    # In a real implementation, we would look up the student's level from their profile
    if not student_level:
        student_level = "beginner"  # Default level
    
    return generate_learning_path(concept_ids, student_level)