devjas1 commited on
Commit
5824afe
·
1 Parent(s): 6b58aaa

Adds educational framework for interactive learning system

Browse files

Implements a comprehensive educational framework for POLYMEROS, enabling interactive learning with adaptive progression and competency tracking.

Introduces classes for managing learning objectives, user progress, competency assessments, personalized learning paths, and virtual laboratory experiments.

Enhances user engagement through personalized feedback and learning recommendations based on user performance.

Improves educational resource accessibility and tracking, facilitating a better learning experience.

Files changed (1) hide show
  1. modules/educational_framework.py +657 -0
modules/educational_framework.py ADDED
@@ -0,0 +1,657 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Educational Framework for POLYMEROS
3
+ Interactive learning system with adaptive progression and competency tracking
4
+ """
5
+
6
+ import json
7
+ import numpy as np
8
+ from typing import Dict, List, Any, Optional, Tuple
9
+ from dataclasses import dataclass, asdict
10
+ from datetime import datetime
11
+ from pathlib import Path
12
+ import streamlit as st
13
+
14
+
15
+ @dataclass
16
+ class LearningObjective:
17
+ """Individual learning objective with assessment criteria"""
18
+
19
+ id: str
20
+ title: str
21
+ description: str
22
+ prerequisite_ids: List[str]
23
+ difficulty_level: int # 1-5 scale
24
+ estimated_time: int # minutes
25
+ assessment_criteria: List[str]
26
+ resources: List[Dict[str, str]]
27
+
28
+ def to_dict(self) -> Dict[str, Any]:
29
+ return asdict(self)
30
+
31
+ @classmethod
32
+ def from_dict(cls, data: Dict[str, Any]) -> "LearningObjective":
33
+ return cls(**data)
34
+
35
+
36
+ @dataclass
37
+ class UserProgress:
38
+ """Track user progress and competency"""
39
+
40
+ user_id: str
41
+ completed_objectives: List[str]
42
+ competency_scores: Dict[str, float] # objective_id -> score
43
+ learning_path: List[str]
44
+ session_history: List[Dict[str, Any]]
45
+ preferred_learning_style: str
46
+ current_level: str
47
+
48
+ def to_dict(self) -> Dict[str, Any]:
49
+ return asdict(self)
50
+
51
+ @classmethod
52
+ def from_dict(cls, data: Dict[str, Any]) -> "UserProgress":
53
+ return cls(**data)
54
+
55
+
56
+ class CompetencyAssessment:
57
+ """Assess user competency through interactive tasks"""
58
+
59
+ def __init__(self):
60
+ self.assessment_tasks = {
61
+ "spectroscopy_basics": [
62
+ {
63
+ "type": "spectrum_identification",
64
+ "question": "Which spectral region typically shows C-H stretching vibrations?",
65
+ "options": [
66
+ "400-1500 cm⁻¹",
67
+ "1500-1700 cm⁻¹",
68
+ "2800-3100 cm⁻¹",
69
+ "3200-3600 cm⁻¹",
70
+ ],
71
+ "correct": 2,
72
+ "explanation": "C-H stretching vibrations appear in the 2800-3100 cm⁻¹ region",
73
+ },
74
+ {
75
+ "type": "peak_interpretation",
76
+ "question": "A peak at 1715 cm⁻¹ in a polymer spectrum most likely indicates:",
77
+ "options": [
78
+ "C-H bending",
79
+ "C=O stretching",
80
+ "O-H stretching",
81
+ "C-C stretching",
82
+ ],
83
+ "correct": 1,
84
+ "explanation": "C=O stretching typically appears around 1715 cm⁻¹, indicating carbonyl groups",
85
+ },
86
+ ],
87
+ "polymer_aging": [
88
+ {
89
+ "type": "degradation_mechanism",
90
+ "question": "Which process is most commonly responsible for polymer degradation?",
91
+ "options": [
92
+ "Hydrolysis",
93
+ "Oxidation",
94
+ "Thermal decomposition",
95
+ "UV radiation",
96
+ ],
97
+ "correct": 1,
98
+ "explanation": "Oxidation is the most common degradation mechanism in polymers",
99
+ }
100
+ ],
101
+ "ai_ml_concepts": [
102
+ {
103
+ "type": "model_interpretation",
104
+ "question": "What does a confidence score of 0.95 indicate?",
105
+ "options": [
106
+ "95% accuracy",
107
+ "95% probability",
108
+ "95% certainty",
109
+ "95% training success",
110
+ ],
111
+ "correct": 1,
112
+ "explanation": "Confidence score represents the model's estimated probability of the prediction",
113
+ }
114
+ ],
115
+ }
116
+
117
+ def assess_competency(self, domain: str, user_responses: List[int]) -> float:
118
+ """Assess user competency in a specific domain"""
119
+ if domain not in self.assessment_tasks:
120
+ return 0.0
121
+
122
+ tasks = self.assessment_tasks[domain]
123
+ if len(user_responses) != len(tasks):
124
+ # Handle mismatched response count gracefully
125
+ min_len = min(len(user_responses), len(tasks))
126
+ user_responses = user_responses[:min_len]
127
+ tasks = tasks[:min_len]
128
+
129
+ if not tasks: # No tasks to assess
130
+ return 0.0
131
+
132
+ correct_count = sum(
133
+ 1
134
+ for i, response in enumerate(user_responses)
135
+ if response == tasks[i]["correct"]
136
+ )
137
+
138
+ return correct_count / len(tasks)
139
+
140
+ def get_personalized_feedback(
141
+ self, domain: str, user_responses: List[int]
142
+ ) -> List[str]:
143
+ """Provide personalized feedback based on assessment results"""
144
+ feedback = []
145
+
146
+ if domain not in self.assessment_tasks:
147
+ return ["Domain not found"]
148
+
149
+ tasks = self.assessment_tasks[domain]
150
+
151
+ # Handle mismatched response count
152
+ min_len = min(len(user_responses), len(tasks))
153
+ user_responses = user_responses[:min_len]
154
+ tasks = tasks[:min_len]
155
+
156
+ for i, response in enumerate(user_responses):
157
+ if i < len(tasks):
158
+ task = tasks[i]
159
+ if response == task["correct"]:
160
+ feedback.append(f"✅ Correct! {task['explanation']}")
161
+ else:
162
+ feedback.append(f"❌ Incorrect. {task['explanation']}")
163
+
164
+ return feedback
165
+
166
+
167
+ class AdaptiveLearningPath:
168
+ """Generate personalized learning paths based on user competency and goals"""
169
+
170
+ def __init__(self):
171
+ self.learning_objectives = self._initialize_objectives()
172
+ self.learning_styles = ["visual", "hands-on", "theoretical", "collaborative"]
173
+
174
+ def _initialize_objectives(self) -> Dict[str, LearningObjective]:
175
+ """Initialize learning objectives database"""
176
+ objectives = {}
177
+
178
+ # Basic spectroscopy objectives
179
+ objectives["spec_001"] = LearningObjective(
180
+ id="spec_001",
181
+ title="Introduction to Vibrational Spectroscopy",
182
+ description="Understand the principles of Raman and FTIR spectroscopy",
183
+ prerequisite_ids=[],
184
+ difficulty_level=1,
185
+ estimated_time=15,
186
+ assessment_criteria=[
187
+ "Identify spectral regions",
188
+ "Explain molecular vibrations",
189
+ ],
190
+ resources=[
191
+ {"type": "tutorial", "url": "interactive_spectroscopy_tutorial"},
192
+ {"type": "video", "url": "spectroscopy_basics_video"},
193
+ ],
194
+ )
195
+
196
+ objectives["spec_002"] = LearningObjective(
197
+ id="spec_002",
198
+ title="Spectral Interpretation",
199
+ description="Learn to interpret peaks and identify functional groups",
200
+ prerequisite_ids=["spec_001"],
201
+ difficulty_level=2,
202
+ estimated_time=25,
203
+ assessment_criteria=[
204
+ "Identify functional groups",
205
+ "Interpret peak intensities",
206
+ ],
207
+ resources=[
208
+ {"type": "interactive", "url": "peak_identification_tool"},
209
+ {"type": "practice", "url": "spectral_analysis_exercises"},
210
+ ],
211
+ )
212
+
213
+ # Polymer science objectives
214
+ objectives["poly_001"] = LearningObjective(
215
+ id="poly_001",
216
+ title="Polymer Structure and Properties",
217
+ description="Understand polymer chemistry and structure-property relationships",
218
+ prerequisite_ids=[],
219
+ difficulty_level=2,
220
+ estimated_time=20,
221
+ assessment_criteria=[
222
+ "Explain polymer structures",
223
+ "Relate structure to properties",
224
+ ],
225
+ resources=[
226
+ {"type": "tutorial", "url": "polymer_basics_tutorial"},
227
+ {"type": "simulation", "url": "molecular_structure_viewer"},
228
+ ],
229
+ )
230
+
231
+ objectives["poly_002"] = LearningObjective(
232
+ id="poly_002",
233
+ title="Polymer Degradation Mechanisms",
234
+ description="Learn about polymer aging and degradation pathways",
235
+ prerequisite_ids=["poly_001"],
236
+ difficulty_level=3,
237
+ estimated_time=30,
238
+ assessment_criteria=[
239
+ "Identify degradation mechanisms",
240
+ "Predict aging effects",
241
+ ],
242
+ resources=[
243
+ {"type": "case_study", "url": "degradation_case_studies"},
244
+ {"type": "interactive", "url": "aging_simulation"},
245
+ ],
246
+ )
247
+
248
+ # AI/ML objectives
249
+ objectives["ai_001"] = LearningObjective(
250
+ id="ai_001",
251
+ title="Introduction to Machine Learning",
252
+ description="Basic concepts of ML for scientific applications",
253
+ prerequisite_ids=[],
254
+ difficulty_level=2,
255
+ estimated_time=20,
256
+ assessment_criteria=["Explain ML concepts", "Understand model types"],
257
+ resources=[
258
+ {"type": "tutorial", "url": "ml_basics_tutorial"},
259
+ {"type": "interactive", "url": "model_playground"},
260
+ ],
261
+ )
262
+
263
+ objectives["ai_002"] = LearningObjective(
264
+ id="ai_002",
265
+ title="Model Interpretation and Validation",
266
+ description="Understanding model outputs and reliability assessment",
267
+ prerequisite_ids=["ai_001"],
268
+ difficulty_level=3,
269
+ estimated_time=25,
270
+ assessment_criteria=["Interpret model outputs", "Assess model reliability"],
271
+ resources=[
272
+ {"type": "hands-on", "url": "model_interpretation_lab"},
273
+ {"type": "case_study", "url": "validation_examples"},
274
+ ],
275
+ )
276
+
277
+ return objectives
278
+
279
+ def generate_learning_path(
280
+ self, user_progress: UserProgress, target_competencies: List[str]
281
+ ) -> List[str]:
282
+ """Generate personalized learning path"""
283
+ available_objectives = []
284
+
285
+ # Find objectives that meet prerequisites
286
+ for obj_id, objective in self.learning_objectives.items():
287
+ if obj_id not in user_progress.completed_objectives:
288
+ prerequisites_met = all(
289
+ prereq in user_progress.completed_objectives
290
+ for prereq in objective.prerequisite_ids
291
+ )
292
+ if prerequisites_met:
293
+ available_objectives.append(obj_id)
294
+
295
+ # Sort by difficulty and relevance to target competencies
296
+ def objective_priority(obj_id):
297
+ obj = self.learning_objectives[obj_id]
298
+ relevance = (
299
+ 1.0
300
+ if any(comp in obj.title.lower() for comp in target_competencies)
301
+ else 0.5
302
+ )
303
+ difficulty_penalty = obj.difficulty_level * 0.1
304
+ return relevance - difficulty_penalty
305
+
306
+ sorted_objectives = sorted(
307
+ available_objectives, key=objective_priority, reverse=True
308
+ )
309
+
310
+ return sorted_objectives[:5] # Return top 5 recommendations
311
+
312
+ def adapt_to_learning_style(
313
+ self, objective_id: str, learning_style: str
314
+ ) -> Dict[str, Any]:
315
+ """Adapt content delivery to user's learning style"""
316
+ objective = self.learning_objectives[objective_id]
317
+ adapted_content = {
318
+ "objective": objective.to_dict(),
319
+ "recommended_approach": "",
320
+ "priority_resources": [],
321
+ }
322
+
323
+ if learning_style == "visual":
324
+ adapted_content["recommended_approach"] = (
325
+ "Start with visualizations and diagrams"
326
+ )
327
+ adapted_content["priority_resources"] = [
328
+ r for r in objective.resources if r["type"] in ["video", "simulation"]
329
+ ]
330
+
331
+ elif learning_style == "hands-on":
332
+ adapted_content["recommended_approach"] = "Begin with interactive exercises"
333
+ adapted_content["priority_resources"] = [
334
+ r
335
+ for r in objective.resources
336
+ if r["type"] in ["interactive", "hands-on"]
337
+ ]
338
+
339
+ elif learning_style == "theoretical":
340
+ adapted_content["recommended_approach"] = (
341
+ "Focus on conceptual understanding"
342
+ )
343
+ adapted_content["priority_resources"] = [
344
+ r
345
+ for r in objective.resources
346
+ if r["type"] in ["tutorial", "case_study"]
347
+ ]
348
+
349
+ elif learning_style == "collaborative":
350
+ adapted_content["recommended_approach"] = (
351
+ "Engage with community discussions"
352
+ )
353
+ adapted_content["priority_resources"] = [
354
+ r
355
+ for r in objective.resources
356
+ if r["type"] in ["practice", "case_study"]
357
+ ]
358
+
359
+ return adapted_content
360
+
361
+
362
+ class VirtualLaboratory:
363
+ """Simulated laboratory environment for hands-on learning"""
364
+
365
+ def __init__(self):
366
+ self.experiments = {
367
+ "polymer_identification": {
368
+ "title": "Polymer Identification Challenge",
369
+ "description": "Identify unknown polymers using spectroscopic analysis",
370
+ "difficulty": 2,
371
+ "estimated_time": 20,
372
+ "learning_objectives": ["spec_002", "poly_001"],
373
+ },
374
+ "aging_simulation": {
375
+ "title": "Polymer Aging Simulation",
376
+ "description": "Observe spectral changes during accelerated aging",
377
+ "difficulty": 3,
378
+ "estimated_time": 30,
379
+ "learning_objectives": ["poly_002", "spec_002"],
380
+ },
381
+ "model_training": {
382
+ "title": "Train Your Own Model",
383
+ "description": "Build and train a classification model",
384
+ "difficulty": 4,
385
+ "estimated_time": 45,
386
+ "learning_objectives": ["ai_001", "ai_002"],
387
+ },
388
+ }
389
+
390
+ def run_experiment(
391
+ self, experiment_id: str, user_inputs: Dict[str, Any]
392
+ ) -> Dict[str, Any]:
393
+ """Run virtual experiment with user inputs"""
394
+ if experiment_id not in self.experiments:
395
+ return {"error": "Experiment not found"}
396
+
397
+ # The experiment details are not used directly here
398
+ # Removed unused variable assignment
399
+
400
+ if experiment_id == "polymer_identification":
401
+ return self._run_identification_experiment(user_inputs)
402
+ elif experiment_id == "aging_simulation":
403
+ return self._run_aging_simulation(user_inputs)
404
+ elif experiment_id == "model_training":
405
+ return self._run_model_training(user_inputs)
406
+
407
+ return {"error": "Experiment not implemented"}
408
+
409
+ def _run_identification_experiment(self, inputs: Dict[str, Any]) -> Dict[str, Any]:
410
+ """Simulate polymer identification experiment"""
411
+ # Generate synthetic spectrum for learning
412
+ wavenumbers = np.linspace(400, 4000, 500)
413
+
414
+ # Simple synthetic spectrum generation
415
+ polymer_type = inputs.get("polymer_type", "PE")
416
+ if polymer_type == "PE":
417
+ # Polyethylene-like spectrum
418
+ spectrum = (
419
+ np.exp(-(((wavenumbers - 2920) / 50) ** 2)) * 0.8
420
+ + np.exp(-(((wavenumbers - 2850) / 30) ** 2)) * 0.6
421
+ + np.random.normal(0, 0.02, len(wavenumbers))
422
+ )
423
+ else:
424
+ # Generic polymer spectrum
425
+ spectrum = np.exp(
426
+ -(((wavenumbers - 1600) / 100) ** 2)
427
+ ) * 0.5 + np.random.normal(0, 0.02, len(wavenumbers))
428
+
429
+ return {
430
+ "wavenumbers": wavenumbers.tolist(),
431
+ "spectrum": spectrum.tolist(),
432
+ "hints": [
433
+ "Look for C-H stretching around 2900 cm⁻¹",
434
+ "Check the fingerprint region for characteristic patterns",
435
+ ],
436
+ "success": True,
437
+ }
438
+
439
+ def _run_aging_simulation(self, inputs: Dict[str, Any]) -> Dict[str, Any]:
440
+ """Simulate polymer aging experiment"""
441
+ aging_time = inputs.get("aging_time", 0)
442
+
443
+ # Generate time-series data showing spectral changes
444
+ wavenumbers = np.linspace(400, 4000, 500)
445
+
446
+ # Base spectrum
447
+ base_spectrum = np.exp(-(((wavenumbers - 2900) / 100) ** 2)) * 0.8
448
+
449
+ # Add aging effects
450
+ oxidation_peak = np.exp(-(((wavenumbers - 1715) / 20) ** 2)) * (
451
+ aging_time / 100
452
+ )
453
+ degraded_spectrum = base_spectrum + oxidation_peak
454
+ degraded_spectrum += np.random.normal(0, 0.01, len(wavenumbers))
455
+
456
+ return {
457
+ "wavenumbers": wavenumbers.tolist(),
458
+ "initial_spectrum": base_spectrum.tolist(),
459
+ "aged_spectrum": degraded_spectrum.tolist(),
460
+ "aging_time": aging_time,
461
+ "observations": [
462
+ "New peak emerging at 1715 cm⁻¹ (carbonyl)",
463
+ f"Aging time: {aging_time} hours",
464
+ "Oxidative degradation pathway activated",
465
+ ],
466
+ "success": True,
467
+ }
468
+
469
+ def _run_model_training(self, inputs: Dict[str, Any]) -> Dict[str, Any]:
470
+ """Simulate model training experiment"""
471
+ model_type = inputs.get("model_type", "CNN")
472
+ epochs = inputs.get("epochs", 10)
473
+
474
+ # Simulate training metrics
475
+ train_losses = [
476
+ 1.0 - i * 0.08 + np.random.normal(0, 0.02) for i in range(epochs)
477
+ ]
478
+ val_accuracies = [
479
+ 0.5 + i * 0.04 + np.random.normal(0, 0.01) for i in range(epochs)
480
+ ]
481
+
482
+ return {
483
+ "model_type": model_type,
484
+ "epochs": epochs,
485
+ "train_losses": train_losses,
486
+ "val_accuracies": val_accuracies,
487
+ "final_accuracy": val_accuracies[-1],
488
+ "insights": [
489
+ "Model converged after 8 epochs",
490
+ "Validation accuracy plateau suggests good generalization",
491
+ "Consider data augmentation for further improvement",
492
+ ],
493
+ "success": True,
494
+ }
495
+
496
+
497
+ class EducationalFramework:
498
+ """Main educational framework interface"""
499
+
500
+ def __init__(self, user_data_dir: str = "user_data"):
501
+ self.user_data_dir = Path(user_data_dir)
502
+ self.user_data_dir.mkdir(exist_ok=True)
503
+
504
+ self.competency_assessor = CompetencyAssessment()
505
+ self.learning_path_generator = AdaptiveLearningPath()
506
+ self.virtual_lab = VirtualLaboratory()
507
+
508
+ self.current_user: Optional[UserProgress] = None
509
+
510
+ def initialize_user(self, user_id: str) -> UserProgress:
511
+ """Initialize or load user progress"""
512
+ user_file = self.user_data_dir / f"{user_id}.json"
513
+
514
+ if user_file.exists():
515
+ with open(user_file, "r", encoding="utf-8") as f:
516
+ data = json.load(f)
517
+ user_progress = UserProgress.from_dict(data)
518
+ else:
519
+ user_progress = UserProgress(
520
+ user_id=user_id,
521
+ completed_objectives=[],
522
+ competency_scores={},
523
+ learning_path=[],
524
+ session_history=[],
525
+ preferred_learning_style="visual",
526
+ current_level="beginner",
527
+ )
528
+
529
+ self.current_user = user_progress
530
+ return user_progress
531
+
532
+ def assess_user_competency(
533
+ self, domain: str, responses: List[int]
534
+ ) -> Dict[str, Any]:
535
+ """Assess user competency and update progress"""
536
+ if not self.current_user:
537
+ return {"error": "No user initialized"}
538
+
539
+ score = self.competency_assessor.assess_competency(domain, responses)
540
+ feedback = self.competency_assessor.get_personalized_feedback(domain, responses)
541
+
542
+ # Update user progress
543
+ self.current_user.competency_scores[domain] = score
544
+
545
+ # Determine user level based on overall competency
546
+ avg_score = np.mean(list(self.current_user.competency_scores.values()))
547
+ if avg_score >= 0.8:
548
+ self.current_user.current_level = "advanced"
549
+ elif avg_score >= 0.6:
550
+ self.current_user.current_level = "intermediate"
551
+ else:
552
+ self.current_user.current_level = "beginner"
553
+
554
+ self.save_user_progress()
555
+
556
+ return {
557
+ "score": score,
558
+ "feedback": feedback,
559
+ "level": self.current_user.current_level,
560
+ "recommendations": self._get_learning_recommendations(),
561
+ }
562
+
563
+ def get_personalized_learning_path(
564
+ self, target_competencies: List[str]
565
+ ) -> List[Dict[str, Any]]:
566
+ """Get personalized learning path for user"""
567
+ if not self.current_user:
568
+ return []
569
+
570
+ path_ids = self.learning_path_generator.generate_learning_path(
571
+ self.current_user, target_competencies
572
+ )
573
+
574
+ adapted_path = []
575
+ for obj_id in path_ids:
576
+ adapted_content = self.learning_path_generator.adapt_to_learning_style(
577
+ obj_id, self.current_user.preferred_learning_style
578
+ )
579
+ adapted_path.append(adapted_content)
580
+
581
+ return adapted_path
582
+
583
+ def run_virtual_experiment(
584
+ self, experiment_id: str, user_inputs: Dict[str, Any]
585
+ ) -> Dict[str, Any]:
586
+ """Run virtual laboratory experiment"""
587
+ result = self.virtual_lab.run_experiment(experiment_id, user_inputs)
588
+
589
+ # Track experiment in user history
590
+ if self.current_user and result.get("success"):
591
+ experiment_record = {
592
+ "experiment_id": experiment_id,
593
+ "timestamp": datetime.now().isoformat(),
594
+ "inputs": user_inputs,
595
+ "completed": True,
596
+ }
597
+ self.current_user.session_history.append(experiment_record)
598
+ self.save_user_progress()
599
+
600
+ return result
601
+
602
+ def _get_learning_recommendations(self) -> List[str]:
603
+ """Get learning recommendations based on current progress"""
604
+ recommendations = []
605
+
606
+ if not self.current_user or not self.current_user.competency_scores:
607
+ recommendations.append("Start with basic spectroscopy concepts")
608
+ recommendations.append("Complete the introductory assessment")
609
+ else:
610
+ weak_areas = [
611
+ domain
612
+ for domain, score in (
613
+ self.current_user.competency_scores.items()
614
+ if self.current_user
615
+ else {}
616
+ )
617
+ if score < 0.6
618
+ ]
619
+
620
+ for area in weak_areas:
621
+ recommendations.append(f"Review {area} concepts")
622
+
623
+ if not weak_areas:
624
+ recommendations.append(
625
+ "Explore advanced topics in your areas of interest"
626
+ )
627
+ recommendations.append("Try hands-on virtual experiments")
628
+
629
+ return recommendations
630
+
631
+ def save_user_progress(self):
632
+ """Save user progress to file"""
633
+ if self.current_user:
634
+ user_file = self.user_data_dir / f"{self.current_user.user_id}.json"
635
+ with open(user_file, "w", encoding="utf-8") as f:
636
+ json.dump(self.current_user.to_dict(), f, indent=2)
637
+
638
+ def get_learning_analytics(self) -> Dict[str, Any]:
639
+ """Get learning analytics for the current user"""
640
+ if not self.current_user:
641
+ return {}
642
+
643
+ total_time = sum(
644
+ obj.estimated_time
645
+ for obj_id in self.current_user.completed_objectives
646
+ for obj in [self.learning_path_generator.learning_objectives.get(obj_id)]
647
+ if obj
648
+ )
649
+
650
+ return {
651
+ "completed_objectives": len(self.current_user.completed_objectives),
652
+ "total_study_time": total_time,
653
+ "competency_scores": self.current_user.competency_scores,
654
+ "current_level": self.current_user.current_level,
655
+ "learning_style": self.current_user.preferred_learning_style,
656
+ "session_count": len(self.current_user.session_history),
657
+ }