diff --git a/CLAUDE.md b/CLAUDE.md index 6e1984d..a86f18f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1073,4 +1073,320 @@ Chapter Progress: 11% (100/909 points) --- +## 🔒 STRICT INTERFACE SYSTEM - C++ Style Contracts + +### 🎯 Philosophy: Contract-Driven Architecture + +**Like C++ header files (.h)**, we enforce strict interfaces that MUST be implemented. Any missing method = **RED SCREEN ERROR** at startup. + +### 📦 Interface Hierarchy + +``` +StrictInterface (base) +├── ProgressItemInterface # For progress tracking items +│ ├── VocabularyDiscoveryItem +│ ├── VocabularyMasteryItem +│ └── ContentProgressItems (Phrase, Dialog, Text, etc.) +│ +├── ProgressSystemInterface # For progress systems +│ ├── ProgressTracker +│ └── PrerequisiteEngine +│ +└── DRSExerciseInterface # For exercise modules + ├── VocabularyModule + ├── TextAnalysisModule + ├── GrammarAnalysisModule + ├── TranslationModule + └── OpenResponseModule +``` + +### 🏗️ 1. **StrictInterface** (Base Class) + +**Location**: `src/DRS/interfaces/StrictInterface.js` + +**Purpose**: Ultra-strict base class with visual error enforcement + +**Features**: +- ✅ Validates implementation at construction +- ✅ Full-screen red error overlay if method missing +- ✅ Sound alert in dev mode +- ✅ Screen shake animation +- ✅ Impossible to ignore - forces correct implementation + +**Error Display**: +``` +┌─────────────────────────────────────────────┐ +│ │ +│ 🔥 FATAL ERROR 🔥 │ +│ │ +│ Implementation Missing │ +│ │ +│ Class: VocabularyModule │ +│ Missing Method: validate() │ +│ │ +│ ❌ MUST implement all interface methods │ +│ │ +│ [ DISMISS (Fix Required!) ] │ +│ │ +└─────────────────────────────────────────────┘ +``` + +### 🎯 2. **ProgressItemInterface** + +**Location**: `src/DRS/interfaces/ProgressItemInterface.js` + +**Purpose**: Contract for all progress tracking items + +**Required Methods** (4): +```javascript +validate() // Validate item data +serialize() // Convert to JSON +getWeight() // Return item weight for progress calculation +canComplete(state) // Check prerequisites +``` + +**Implementations**: +- VocabularyDiscoveryItem (1pt) +- VocabularyMasteryItem (1pt) +- PhraseItem (6pts) +- DialogItem (12pts) +- TextItem (15pts) +- AudioItem (12pts) +- ImageItem (6pts) +- GrammarItem (6pts) + +### 🔧 3. **ProgressSystemInterface** + +**Location**: `src/DRS/interfaces/ProgressSystemInterface.js` + +**Purpose**: Contract for all progress management systems + +**Required Methods** (17): + +**Vocabulary Tracking:** +- `markWordDiscovered(word, metadata)` +- `markWordMastered(word, metadata)` +- `isWordDiscovered(word)` +- `isWordMastered(word)` + +**Content Tracking:** +- `markPhraseCompleted(id, metadata)` +- `markDialogCompleted(id, metadata)` +- `markTextCompleted(id, metadata)` +- `markAudioCompleted(id, metadata)` +- `markImageCompleted(id, metadata)` +- `markGrammarCompleted(id, metadata)` + +**Prerequisites:** +- `canComplete(itemType, itemId, context)` + +**Progress:** +- `getProgress(chapterId)` + +**Persistence:** +- `saveProgress(bookId, chapterId)` +- `loadProgress(bookId, chapterId)` + +**Utility:** +- `reset(bookId, chapterId)` + +**Implementations**: +- ProgressTracker - Weight-based progress with items +- PrerequisiteEngine - Prerequisite checking and mastery tracking + +### 🎮 4. **DRSExerciseInterface** + +**Location**: `src/DRS/interfaces/DRSExerciseInterface.js` + +**Purpose**: Contract for all DRS exercise modules + +**Required Methods** (10): + +**Lifecycle:** +- `init(config, content)` - Initialize exercise +- `render(container)` - Render UI +- `destroy()` - Clean up + +**Exercise Logic:** +- `validate(userAnswer)` - Validate answer, return { isCorrect, score, feedback, explanation } +- `getResults()` - Return { score, attempts, timeSpent, completed, details } +- `handleUserInput(event, data)` - Handle user input + +**Progress Tracking:** +- `markCompleted(results)` - Mark as completed +- `getProgress()` - Return { percentage, currentStep, totalSteps, itemsCompleted, itemsTotal } + +**Metadata:** +- `getExerciseType()` - Return exercise type string +- `getExerciseConfig()` - Return { type, difficulty, estimatedTime, prerequisites, metadata } + +**Implementations**: +- VocabularyModule - Flashcard spaced repetition +- TextAnalysisModule - AI-powered text comprehension +- GrammarAnalysisModule - AI grammar correction +- TranslationModule - AI translation validation +- OpenResponseModule - Free-form AI evaluation + +### ✅ 5. **ImplementationValidator** + +**Location**: `src/DRS/services/ImplementationValidator.js` + +**Purpose**: Validate ALL implementations at application startup + +**Validation Phases**: + +```javascript +🔍 VALIDATING DRS IMPLEMENTATIONS... + +📦 PART 1: Validating Progress Items... + ✅ VocabularyDiscoveryItem - OK + ✅ VocabularyMasteryItem - OK + ✅ PhraseItem - OK + ✅ DialogItem - OK + ✅ TextItem - OK + ✅ AudioItem - OK + ✅ ImageItem - OK + ✅ GrammarItem - OK + +🔧 PART 2: Validating Progress Systems... + ✅ ProgressTracker - OK + ✅ PrerequisiteEngine - OK + +🎮 PART 3: Validating DRS Exercise Modules... + ✅ VocabularyModule - OK + ✅ TextAnalysisModule - OK + ✅ GrammarAnalysisModule - OK + ✅ TranslationModule - OK + ✅ OpenResponseModule - OK + +✅ ALL DRS IMPLEMENTATIONS VALID +``` + +**If ANY validation fails**: +- 🔴 Full-screen red error +- 🚫 Application REFUSES to start +- 📋 Clear error message with missing method name +- 🔊 Alert sound (dev mode) +- 📳 Screen shake + +### 🎯 Integration with Application.js + +**At Startup** (lines 55-62): +```javascript +// Validate all progress item implementations (STRICT MODE) +console.log('🔍 Validating progress item implementations...'); +const { default: ImplementationValidator } = await import('./DRS/services/ImplementationValidator.js'); +const isValid = await ImplementationValidator.validateAll(); + +if (!isValid) { + throw new Error('❌ Implementation validation failed - check console for details'); +} +``` + +### 📋 Creating New Implementations + +#### **New Progress Item**: +```javascript +import ProgressItemInterface from '../interfaces/ProgressItemInterface.js'; + +class MyCustomItem extends ProgressItemInterface { + constructor(id, metadata) { + super('my-custom-item', id, metadata); + } + + validate() { + // MUST implement + if (!this.metadata.required) { + throw new Error('Missing required data'); + } + return true; + } + + serialize() { + // MUST implement + return { + ...this._getBaseSerialization(), + customData: this.metadata.custom + }; + } + + getWeight() { + // MUST implement + return ProgressItemInterface.WEIGHTS['my-custom-item'] || 5; + } + + canComplete(userProgress) { + // MUST implement + return true; // Check prerequisites here + } +} +``` + +#### **New Progress System**: +```javascript +import ProgressSystemInterface from '../interfaces/ProgressSystemInterface.js'; + +class MyProgressSystem extends ProgressSystemInterface { + constructor() { + super('MyProgressSystem'); + } + + // MUST implement all 17 required methods + async markWordDiscovered(word, metadata = {}) { /* ... */ } + async markWordMastered(word, metadata = {}) { /* ... */ } + isWordDiscovered(word) { /* ... */ } + isWordMastered(word) { /* ... */ } + // ... 13 more methods +} +``` + +#### **New Exercise Module**: +```javascript +import DRSExerciseInterface from '../interfaces/DRSExerciseInterface.js'; + +class MyExerciseModule extends DRSExerciseInterface { + constructor() { + super('MyExerciseModule'); + } + + // MUST implement all 10 required methods + async init(config, content) { /* ... */ } + async render(container) { /* ... */ } + async destroy() { /* ... */ } + async validate(userAnswer) { /* ... */ } + getResults() { /* ... */ } + handleUserInput(event, data) { /* ... */ } + async markCompleted(results) { /* ... */ } + getProgress() { /* ... */ } + getExerciseType() { return 'my-exercise'; } + getExerciseConfig() { /* ... */ } +} +``` + +### 🚨 Enforcement Rules + +**NON-NEGOTIABLE**: +1. ❌ **Missing method** → RED SCREEN ERROR → App refuses to start +2. ❌ **Wrong signature** → Runtime error on call +3. ❌ **Wrong return format** → Runtime error on usage +4. ✅ **All methods implemented** → App starts normally + +**Validation happens**: +- ✅ At application startup (before any UI renders) +- ✅ On module registration +- ✅ At interface instantiation + +**Benefits**: +- 🛡️ **Impossible to forget implementation** - Visual error forces fix +- 📋 **Self-documenting** - Interface defines exact contract +- 🔒 **Type safety** - Like TypeScript interfaces but enforced at runtime +- 🧪 **Testable** - Can mock interfaces for unit tests +- 🔄 **Maintainable** - Adding new method = update interface + all implementations get errors + +--- + +**Status**: ✅ **INTERFACE SYSTEM IMPLEMENTED** + +--- + **This is a high-quality, maintainable system built for educational software that will scale.** \ No newline at end of file diff --git a/DRS_IMPLEMENTATION_PLAN.md b/DRS_IMPLEMENTATION_PLAN.md new file mode 100644 index 0000000..057b371 --- /dev/null +++ b/DRS_IMPLEMENTATION_PLAN.md @@ -0,0 +1,575 @@ +# 📋 DRS EXERCISE MODULES - IMPLEMENTATION PLAN + +**Goal**: Réimplémenter tous les modules DRS pour respecter `DRSExerciseInterface` + +**Status**: 🔴 0/11 modules conformes + +--- + +## 📊 ÉTAT DES LIEUX - 11 Modules Existants + +### ✅ Modules AI (Score-Based) - 5 modules +Utilisent l'IA pour validation et scoring (0-100 points) + +1. **TextAnalysisModule** - Analyse de texte avec AI +2. **GrammarAnalysisModule** - Correction grammaticale AI +3. **TranslationModule** - Traduction validée par AI +4. **OpenResponseModule** - Réponse libre évaluée par AI +5. **AudioModule** - Analyse audio avec transcription + +### 🎯 Modules Locaux (Non-AI) - 6 modules +Validation locale sans IA + +6. **VocabularyModule** - Flashcards spaced repetition +7. **WordDiscoveryModule** - Découverte passive de vocabulaire +8. **PhraseModule** - Pratique de phrases +9. **GrammarModule** - Exercices de grammaire structurés +10. **TextModule** - Lecture et compréhension de textes +11. **ImageModule** - Description d'images + +--- + +## 🎯 INTERFACE STRICTE - 10 Méthodes Requises + +Chaque module DOIT implémenter: + +### **Lifecycle** (3 méthodes) +```javascript +async init(config, content) // Initialize module +async render(container) // Render UI +async destroy() // Cleanup +``` + +### **Exercise Logic** (3 méthodes) +```javascript +async validate(userAnswer) // Returns { isCorrect, score, feedback, explanation } +getResults() // Returns { score, attempts, timeSpent, completed, details } +handleUserInput(event, data) // Handle user interactions +``` + +### **Progress Tracking** (2 méthodes) +```javascript +async markCompleted(results) // Mark as completed + save progress +getProgress() // Returns { percentage, currentStep, totalSteps, itemsCompleted, itemsTotal } +``` + +### **Metadata** (2 méthodes) +```javascript +getExerciseType() // Returns type string +getExerciseConfig() // Returns { type, difficulty, estimatedTime, prerequisites, metadata } +``` + +--- + +## 📈 ANALYSE PAR MODULE + +### 1️⃣ **VocabularyModule** (43KB) +**État**: 🟡 Partiel (3/10 méthodes) + +**Existant**: +- ✅ `async init()` - Ligne 47 +- ✅ `async validate(userInput, context)` - Ligne 169 +- ✅ `getProgress()` - Ligne 220 + +**Manquant**: +- ❌ `async render(container)` - Logique UI dispersée, pas de méthode centralisée +- ❌ `async destroy()` - Pas de cleanup formel +- ❌ `getResults()` - Résultats calculés à la volée, pas de méthode dédiée +- ❌ `handleUserInput(event, data)` - Géré inline dans render +- ❌ `async markCompleted(results)` - Sauvegarde dispersée +- ❌ `getExerciseType()` - Type non formalisé +- ❌ `getExerciseConfig()` - Config non structurée + +**Priorité**: 🔥 **HAUTE** (module principal, bien structuré) + +--- + +### 2️⃣ **TextAnalysisModule** (24KB) +**État**: 🟡 Partiel (2/10 méthodes) + +**Existant**: +- ✅ `async validate(userInput, context)` - Ligne 127 (AI validation) +- ✅ `getProgress()` - Ligne 204 + +**Manquant**: +- ❌ `async init(config, content)` - Pas d'init formelle +- ❌ `async render(container)` - UI dispersée +- ❌ `async destroy()` - Pas de cleanup +- ❌ `getResults()` - Résultats inline +- ❌ `handleUserInput(event, data)` - Pas formalisé +- ❌ `async markCompleted(results)` - Pas formalisé +- ❌ `getExerciseType()` - Manque +- ❌ `getExerciseConfig()` - Manque + +**Priorité**: 🔥 **HAUTE** (AI module, pattern pour autres) + +--- + +### 3️⃣ **GrammarAnalysisModule** (26KB) +**État**: 🟡 Partiel (2/10 méthodes) + +**Existant**: +- ✅ `async validate(userInput, context)` - AI correction +- ✅ `getProgress()` - Basique + +**Manquant**: Mêmes que TextAnalysisModule + +**Priorité**: 🔶 **MOYENNE** (similaire à TextAnalysis) + +--- + +### 4️⃣ **TranslationModule** (30KB) +**État**: 🟡 Partiel (2/10 méthodes) + +**Existant**: +- ✅ `async validate(userInput, context)` - AI translation check +- ✅ `getProgress()` - Basique + +**Manquant**: Mêmes que TextAnalysisModule + +**Priorité**: 🔶 **MOYENNE** (AI module standard) + +--- + +### 5️⃣ **OpenResponseModule** (21KB) +**État**: 🟡 Partiel (2/10 méthodes) + +**Existant**: +- ✅ `async validate(userInput, context)` - AI evaluation +- ✅ `getProgress()` - Basique + +**Manquant**: Mêmes que TextAnalysisModule + +**Priorité**: 🔶 **MOYENNE** (AI module générique) + +--- + +### 6️⃣ **WordDiscoveryModule** (11KB) +**État**: 🟡 Partiel (1/10 méthodes) + +**Existant**: +- ✅ `async init()` - Basique + +**Manquant**: Presque tout + +**Priorité**: 🟢 **BASSE** (va fusionner avec VocabularyModule) + +--- + +### 7️⃣ **PhraseModule** (31KB) +**État**: 🟡 Partiel (2/10 méthodes) + +**Existant**: +- ✅ `async validate()` - Local validation +- ✅ `getProgress()` - Basique + +**Manquant**: 8 méthodes + +**Priorité**: 🔶 **MOYENNE** (local validation, pattern différent) + +--- + +### 8️⃣ **GrammarModule** (74KB) +**État**: 🔴 Ancien (code legacy volumineux) + +**Existant**: Code legacy, structure différente + +**Priorité**: 🟢 **BASSE** (peut être remplacé par GrammarAnalysisModule) + +--- + +### 9️⃣ **TextModule** (52KB) +**État**: 🔴 Ancien (code legacy) + +**Existant**: Code legacy, structure différente + +**Priorité**: 🟢 **BASSE** (peut être remplacé par TextAnalysisModule) + +--- + +### 🔟 **AudioModule** (68KB) +**État**: 🟡 Partiel + +**Existant**: +- ✅ `async validate()` - Audio analysis + +**Priorité**: 🔶 **MOYENNE** (module spécialisé) + +--- + +### 1️⃣1️⃣ **ImageModule** (69KB) +**État**: 🟡 Partiel + +**Existant**: +- ✅ `async validate()` - Image analysis + +**Priorité**: 🔶 **MOYENNE** (module spécialisé) + +--- + +## 🗺️ ROADMAP D'IMPLÉMENTATION + +### 🎯 **PHASE 1 - Modules Prioritaires** (2 modules) + +#### **1.1 VocabularyModule** ⭐ PRIORITÉ #1 +**Pourquoi**: Module le plus utilisé, bien structuré, pas d'IA + +**Tâches**: +1. ✅ Garder `init()`, `validate()`, `getProgress()` existants +2. 🔨 Extraire logique UI dans `render(container)` +3. 🔨 Créer `destroy()` pour cleanup +4. 🔨 Créer `getResults()` pour statistiques finales +5. 🔨 Créer `handleUserInput(event, data)` pour boutons +6. 🔨 Créer `markCompleted(results)` pour sauvegarde +7. 🔨 Créer `getExerciseType()` → `'vocabulary'` +8. 🔨 Créer `getExerciseConfig()` avec difficulty, time, etc. + +**Estimation**: 2-3 heures + +--- + +#### **1.2 TextAnalysisModule** ⭐ PRIORITÉ #2 +**Pourquoi**: Module AI de référence, pattern pour tous les modules AI + +**Tâches**: +1. ✅ Garder `validate()` et `getProgress()` existants +2. 🔨 Créer `init(config, content)` pour setup +3. 🔨 Créer `render(container)` pour UI +4. 🔨 Créer `destroy()` pour cleanup +5. 🔨 Créer `getResults()` avec AI score + metadata +6. 🔨 Créer `handleUserInput(event, data)` +7. 🔨 Créer `markCompleted(results)` +8. 🔨 Créer `getExerciseType()` → `'text-analysis'` +9. 🔨 Créer `getExerciseConfig()` + +**Estimation**: 2-3 heures + +--- + +### 🔄 **PHASE 2 - Modules AI** (3 modules) + +**Pattern**: Copier structure de TextAnalysisModule, adapter validation + +#### **2.1 GrammarAnalysisModule** +- Utiliser pattern de TextAnalysisModule +- Adapter `validate()` pour grammar checking +- **Estimation**: 1-2 heures + +#### **2.2 TranslationModule** +- Utiliser pattern de TextAnalysisModule +- Adapter `validate()` pour translation +- **Estimation**: 1-2 heures + +#### **2.3 OpenResponseModule** +- Utiliser pattern de TextAnalysisModule +- Adapter `validate()` pour open responses +- **Estimation**: 1-2 heures + +--- + +### 📦 **PHASE 3 - Modules Locaux** (2 modules) + +#### **3.1 PhraseModule** +- Utiliser pattern de VocabularyModule +- Validation locale (pas d'AI) +- **Estimation**: 1-2 heures + +#### **3.2 Fusionner WordDiscoveryModule** +- Intégrer dans VocabularyModule (discovery mode) +- Supprimer module séparé +- **Estimation**: 1 heure + +--- + +### 🎨 **PHASE 4 - Modules Spécialisés** (2 modules) + +#### **4.1 AudioModule** +- Audio player + transcription +- AI analysis similaire TextAnalysisModule +- **Estimation**: 2-3 heures + +#### **4.2 ImageModule** +- Image display + zoom +- AI vision analysis +- **Estimation**: 2-3 heures + +--- + +### 🗑️ **PHASE 5 - Cleanup Legacy** (2 modules) + +#### **5.1 Supprimer GrammarModule** +- Remplacé par GrammarAnalysisModule (AI) +- Archiver code si besoin + +#### **5.2 Supprimer TextModule** +- Remplacé par TextAnalysisModule (AI) +- Archiver code si besoin + +--- + +## 🛠️ STRATÉGIE D'IMPLÉMENTATION + +### **Pattern 1: AI Modules** (TextAnalysis, Grammar, Translation, OpenResponse, Audio, Image) + +```javascript +import DRSExerciseInterface from '../interfaces/DRSExerciseInterface.js'; + +class ModuleName extends DRSExerciseInterface { + constructor() { + super('ModuleName'); + this.config = null; + this.content = null; + this.container = null; + this.startTime = null; + this.attempts = 0; + this.currentScore = 0; + } + + async init(config, content) { + this.config = config; + this.content = content; + this.startTime = Date.now(); + } + + async render(container) { + this.container = container; + // Render UI logic here + } + + async destroy() { + if (this.container) { + this.container.innerHTML = ''; + this.container = null; + } + } + + async validate(userAnswer) { + this.attempts++; + // AI validation logic + return { + isCorrect: true/false, + score: 0-100, + feedback: '...', + explanation: '...', + metadata: {} + }; + } + + getResults() { + return { + score: this.currentScore, + attempts: this.attempts, + timeSpent: Date.now() - this.startTime, + completed: this.currentScore >= 70, + details: { /* exercise-specific */ } + }; + } + + handleUserInput(event, data) { + // Handle button clicks, input changes, etc. + } + + async markCompleted(results) { + // Save to progress system + } + + getProgress() { + return { + percentage: 100, // or calculate based on steps + currentStep: 1, + totalSteps: 1, + itemsCompleted: this.currentScore >= 70 ? 1 : 0, + itemsTotal: 1 + }; + } + + getExerciseType() { + return 'module-type'; + } + + getExerciseConfig() { + return { + type: this.getExerciseType(), + difficulty: 'medium', + estimatedTime: 5, + prerequisites: [], + metadata: this.config + }; + } +} +``` + +### **Pattern 2: Local Modules** (Vocabulary, Phrase) + +```javascript +import DRSExerciseInterface from '../interfaces/DRSExerciseInterface.js'; + +class ModuleName extends DRSExerciseInterface { + constructor() { + super('ModuleName'); + this.items = []; + this.currentIndex = 0; + this.results = []; + } + + async init(config, content) { + this.config = config; + this.items = content.items; // Extract items + this.startTime = Date.now(); + } + + async render(container) { + this.container = container; + this._renderCurrentItem(); + } + + async destroy() { + this.container.innerHTML = ''; + this.container = null; + } + + async validate(userAnswer) { + // Local validation (no AI) + const isCorrect = userAnswer === this.items[this.currentIndex].answer; + this.results.push({ isCorrect, answer: userAnswer }); + + return { + isCorrect, + score: isCorrect ? 100 : 0, + feedback: isCorrect ? 'Correct!' : 'Incorrect', + explanation: '...', + metadata: {} + }; + } + + getResults() { + const correctCount = this.results.filter(r => r.isCorrect).length; + const score = Math.round((correctCount / this.results.length) * 100); + + return { + score, + attempts: this.results.length, + timeSpent: Date.now() - this.startTime, + completed: this.currentIndex >= this.items.length, + details: { correctCount, totalCount: this.items.length } + }; + } + + handleUserInput(event, data) { + // Handle next/previous/answer buttons + } + + async markCompleted(results) { + // Save progress + } + + getProgress() { + return { + percentage: Math.round((this.currentIndex / this.items.length) * 100), + currentStep: this.currentIndex + 1, + totalSteps: this.items.length, + itemsCompleted: this.results.filter(r => r.isCorrect).length, + itemsTotal: this.items.length + }; + } + + getExerciseType() { + return 'module-type'; + } + + getExerciseConfig() { + return { + type: this.getExerciseType(), + difficulty: 'easy', + estimatedTime: this.items.length * 0.5, + prerequisites: [], + metadata: this.config + }; + } +} +``` + +--- + +## ✅ CHECKLIST DE VALIDATION + +Pour chaque module réimplémenté: + +### **Implémentation** +- [ ] Hérite de `DRSExerciseInterface` +- [ ] Implémente les 10 méthodes requises +- [ ] Méthodes retournent le bon format +- [ ] Pas d'erreur au startup (validation stricte) + +### **Lifecycle** +- [ ] `init()` initialise correctement config et content +- [ ] `render()` crée l'UI dans le container +- [ ] `destroy()` nettoie proprement (remove listeners, clear DOM) + +### **Exercise Logic** +- [ ] `validate()` retourne `{ isCorrect, score, feedback, explanation }` +- [ ] `getResults()` retourne stats complètes +- [ ] `handleUserInput()` gère tous les events + +### **Progress** +- [ ] `markCompleted()` sauvegarde dans ProgressTracker +- [ ] `getProgress()` retourne progression en temps réel + +### **Metadata** +- [ ] `getExerciseType()` retourne string unique +- [ ] `getExerciseConfig()` retourne config complète + +### **Testing** +- [ ] Module se charge sans erreur +- [ ] UI s'affiche correctement +- [ ] Validation fonctionne (AI ou local) +- [ ] Progress se sauvegarde +- [ ] Cleanup fonctionne (pas de memory leak) + +--- + +## 📊 ESTIMATION TOTALE + +| Phase | Modules | Temps Estimé | +|-------|---------|--------------| +| Phase 1 | 2 (Vocabulary, TextAnalysis) | 4-6h | +| Phase 2 | 3 (Grammar, Translation, OpenResponse) | 3-6h | +| Phase 3 | 2 (Phrase, WordDiscovery fusion) | 2-3h | +| Phase 4 | 2 (Audio, Image) | 4-6h | +| Phase 5 | 2 (Cleanup legacy) | 1h | +| **TOTAL** | **11 modules** | **14-22h** | + +--- + +## 🎯 ORDRE DE PRIORITÉ + +1. 🔥 **VocabularyModule** - Module principal, pas d'AI, bien structuré +2. 🔥 **TextAnalysisModule** - Template pour tous modules AI +3. 🔶 **GrammarAnalysisModule** - Copie pattern TextAnalysis +4. 🔶 **TranslationModule** - Copie pattern TextAnalysis +5. 🔶 **OpenResponseModule** - Copie pattern TextAnalysis +6. 🔶 **PhraseModule** - Local validation, copie pattern Vocabulary +7. 🔶 **AudioModule** - Spécialisé, similaire TextAnalysis +8. 🔶 **ImageModule** - Spécialisé, similaire TextAnalysis +9. 🟢 **WordDiscoveryModule** - Fusionner dans Vocabulary +10. 🟢 **GrammarModule (legacy)** - Supprimer/archiver +11. 🟢 **TextModule (legacy)** - Supprimer/archiver + +--- + +## 🚀 PROCHAINES ÉTAPES + +### **Immédiat** (à faire maintenant): +1. Valider ce plan d'implémentation +2. Commencer par **VocabularyModule** (priorité #1) +3. Une fois validé, dupliquer pattern pour autres modules + +### **Validation Plan**: +- ❓ Plan approuvé ? +- ❓ Ordre de priorité correct ? +- ❓ Temps estimé réaliste ? +- ❓ Pattern de code acceptable ? + +--- + +**Status**: 📝 **PLAN PRÊT - AWAITING APPROVAL** diff --git a/src/DRS/exercise-modules/AudioModule.js b/src/DRS/exercise-modules/AudioModule.js index c00019c..52ebb78 100644 --- a/src/DRS/exercise-modules/AudioModule.js +++ b/src/DRS/exercise-modules/AudioModule.js @@ -3,11 +3,11 @@ * Handles audio passages with listening questions and pronunciation practice */ -import ExerciseModuleInterface from '../interfaces/ExerciseModuleInterface.js'; +import DRSExerciseInterface from '../interfaces/DRSExerciseInterface.js'; -class AudioModule extends ExerciseModuleInterface { +class AudioModule extends DRSExerciseInterface { constructor(orchestrator, llmValidator, prerequisiteEngine, contextMemory) { - super(); + super('AudioModule'); // Validate dependencies if (!orchestrator || !llmValidator || !prerequisiteEngine || !contextMemory) { @@ -1879,6 +1879,71 @@ Format: [answer]yes/no [explanation]your comprehensive educational feedback here document.head.appendChild(styles); } + + // ======================================== + // DRSExerciseInterface REQUIRED METHODS + // ======================================== + + async init(config = {}, content = {}) { + this.config = { ...this.config, ...config }; + this.currentExerciseData = content; + this.startTime = Date.now(); + this.initialized = true; + } + + async render(container) { + if (!this.initialized) throw new Error('AudioModule must be initialized before rendering'); + await this.present(container, this.currentExerciseData); + } + + async destroy() { + this.cleanup?.(); + this.container = null; + this.initialized = false; + } + + getResults() { + return { + score: this.progress?.averageScore ? Math.round(this.progress.averageScore * 100) : 0, + attempts: this.userResponses?.length || 0, + timeSpent: this.startTime ? Date.now() - this.startTime : 0, + completed: true, + details: { progress: this.progress, responses: this.userResponses } + }; + } + + handleUserInput(event, data) { + if (event?.type === 'input') this._handleInputChange?.(event); + if (event?.type === 'click' && event.target.id === 'submitButton') this._handleSubmit?.(event); + } + + async markCompleted(results) { + const { score, details } = results || this.getResults(); + if (this.contextMemory) { + this.contextMemory.recordInteraction({ + type: 'audio', + subtype: 'completion', + content: this.currentExerciseData, + validation: { score }, + context: { moduleType: 'audio' } + }); + } + } + + getExerciseType() { + return 'audio'; + } + + getExerciseConfig() { + return { + type: this.getExerciseType(), + difficulty: this.currentExerciseData?.difficulty || 'medium', + estimatedTime: 5, + prerequisites: [], + metadata: { ...this.config, requiresAI: false } + }; + } + } export default AudioModule; \ No newline at end of file diff --git a/src/DRS/exercise-modules/GrammarAnalysisModule.js b/src/DRS/exercise-modules/GrammarAnalysisModule.js index 5bf14cd..7a8fe1f 100644 --- a/src/DRS/exercise-modules/GrammarAnalysisModule.js +++ b/src/DRS/exercise-modules/GrammarAnalysisModule.js @@ -1,13 +1,14 @@ /** * GrammarAnalysisModule - Open grammar correction with AI feedback * Presents grammar exercises with open correction fields, validates using real AI + * Implements DRSExerciseInterface for strict contract enforcement */ -import ExerciseModuleInterface from '../interfaces/ExerciseModuleInterface.js'; +import DRSExerciseInterface from '../interfaces/DRSExerciseInterface.js'; -class GrammarAnalysisModule extends ExerciseModuleInterface { +class GrammarAnalysisModule extends DRSExerciseInterface { constructor(orchestrator, llmValidator, prerequisiteEngine, contextMemory) { - super(); + super('GrammarAnalysisModule'); // Validate dependencies if (!orchestrator || !llmValidator || !prerequisiteEngine || !contextMemory) { @@ -697,6 +698,82 @@ Return ONLY valid JSON: setTimeout(() => errorDiv.remove(), 5000); } + + // ======================================== + // DRSExerciseInterface REQUIRED METHODS + // ======================================== + + async init(config = {}, content = {}) { + this.config = { ...this.config, ...config }; + this.currentExerciseData = content; + this.startTime = Date.now(); + this.initialized = true; + } + + async render(container) { + if (!this.initialized) throw new Error('GrammarAnalysisModule must be initialized before rendering'); + await this.present(container, this.currentExerciseData); + } + + async destroy() { + this.cleanup(); + } + + getResults() { + const totalSentences = this.currentSentences.length; + const correctedSentences = this.userCorrections.length; + const correctSentences = this.progress.sentencesCorrect; + const score = totalSentences > 0 ? Math.round((correctSentences / totalSentences) * 100) : 0; + + return { + score, + attempts: correctedSentences, + timeSpent: this.startTime ? Date.now() - this.startTime : 0, + completed: correctedSentences >= totalSentences, + details: { + totalSentences, + correctedSentences, + correctSentences, + accuracy: this.progress.averageAccuracy, + grammarRulesLearned: Array.from(this.progress.grammarRulesLearned), + corrections: this.userCorrections + } + }; + } + + handleUserInput(event, data) { + if (event && event.type === 'input') this._handleInputChange?.(event); + if (event && event.type === 'click' && event.target.id === 'submitButton') this._handleSubmit?.(event); + } + + async markCompleted(results) { + const { score, details } = results || this.getResults(); + if (this.contextMemory) { + this.contextMemory.recordInteraction({ + type: 'grammar-analysis', + subtype: 'completion', + content: { sentences: this.currentSentences }, + userResponse: this.userCorrections, + validation: { score, accuracy: details.accuracy, grammarRulesLearned: details.grammarRulesLearned }, + context: { moduleType: 'grammar-analysis', totalSentences: details.totalSentences } + }); + } + } + + getExerciseType() { + return 'grammar-analysis'; + } + + getExerciseConfig() { + const sentenceCount = this.currentSentences ? this.currentSentences.length : this.config.sentencesPerExercise; + return { + type: this.getExerciseType(), + difficulty: this.currentExerciseData?.difficulty || 'medium', + estimatedTime: sentenceCount * 2, // 2 min per sentence + prerequisites: [], + metadata: { ...this.config, sentenceCount, requiresAI: true, model: this.config.model } + }; + } } export default GrammarAnalysisModule; \ No newline at end of file diff --git a/src/DRS/exercise-modules/GrammarModule.js b/src/DRS/exercise-modules/GrammarModule.js index 72c29e1..7c732b7 100644 --- a/src/DRS/exercise-modules/GrammarModule.js +++ b/src/DRS/exercise-modules/GrammarModule.js @@ -3,11 +3,11 @@ * Handles grammar rules, sentence construction, and correction exercises */ -import ExerciseModuleInterface from '../interfaces/ExerciseModuleInterface.js'; +import DRSExerciseInterface from '../interfaces/DRSExerciseInterface.js'; -class GrammarModule extends ExerciseModuleInterface { +class GrammarModule extends DRSExerciseInterface { constructor(orchestrator, llmValidator, prerequisiteEngine, contextMemory) { - super(); + super('GrammarModule'); // Validate dependencies if (!orchestrator || !llmValidator || !prerequisiteEngine || !contextMemory) { @@ -2055,6 +2055,71 @@ Format: [answer]yes/no [explanation]your comprehensive grammar analysis here`; document.head.appendChild(styles); } + + // ======================================== + // DRSExerciseInterface REQUIRED METHODS + // ======================================== + + async init(config = {}, content = {}) { + this.config = { ...this.config, ...config }; + this.currentExerciseData = content; + this.startTime = Date.now(); + this.initialized = true; + } + + async render(container) { + if (!this.initialized) throw new Error('GrammarModule must be initialized before rendering'); + await this.present(container, this.currentExerciseData); + } + + async destroy() { + this.cleanup?.(); + this.container = null; + this.initialized = false; + } + + getResults() { + return { + score: this.progress?.averageScore ? Math.round(this.progress.averageScore * 100) : 0, + attempts: this.userResponses?.length || 0, + timeSpent: this.startTime ? Date.now() - this.startTime : 0, + completed: true, + details: { progress: this.progress, responses: this.userResponses } + }; + } + + handleUserInput(event, data) { + if (event?.type === 'input') this._handleInputChange?.(event); + if (event?.type === 'click' && event.target.id === 'submitButton') this._handleSubmit?.(event); + } + + async markCompleted(results) { + const { score, details } = results || this.getResults(); + if (this.contextMemory) { + this.contextMemory.recordInteraction({ + type: 'grammar', + subtype: 'completion', + content: this.currentExerciseData, + validation: { score }, + context: { moduleType: 'grammar' } + }); + } + } + + getExerciseType() { + return 'grammar'; + } + + getExerciseConfig() { + return { + type: this.getExerciseType(), + difficulty: this.currentExerciseData?.difficulty || 'medium', + estimatedTime: 5, + prerequisites: [], + metadata: { ...this.config, requiresAI: false } + }; + } + } export default GrammarModule; \ No newline at end of file diff --git a/src/DRS/exercise-modules/ImageModule.js b/src/DRS/exercise-modules/ImageModule.js index 2c5d0ab..cbb8af8 100644 --- a/src/DRS/exercise-modules/ImageModule.js +++ b/src/DRS/exercise-modules/ImageModule.js @@ -3,11 +3,11 @@ * Handles image content with visual analysis questions and description exercises */ -import ExerciseModuleInterface from '../interfaces/ExerciseModuleInterface.js'; +import DRSExerciseInterface from '../interfaces/DRSExerciseInterface.js'; -class ImageModule extends ExerciseModuleInterface { +class ImageModule extends DRSExerciseInterface { constructor(orchestrator, llmValidator, prerequisiteEngine, contextMemory) { - super(); + super('ImageModule'); // Validate dependencies if (!orchestrator || !llmValidator || !prerequisiteEngine || !contextMemory) { @@ -1944,6 +1944,71 @@ Format: [answer]yes/no [explanation]your comprehensive educational feedback here document.head.appendChild(styles); } + + // ======================================== + // DRSExerciseInterface REQUIRED METHODS + // ======================================== + + async init(config = {}, content = {}) { + this.config = { ...this.config, ...config }; + this.currentExerciseData = content; + this.startTime = Date.now(); + this.initialized = true; + } + + async render(container) { + if (!this.initialized) throw new Error('ImageModule must be initialized before rendering'); + await this.present(container, this.currentExerciseData); + } + + async destroy() { + this.cleanup?.(); + this.container = null; + this.initialized = false; + } + + getResults() { + return { + score: this.progress?.averageScore ? Math.round(this.progress.averageScore * 100) : 0, + attempts: this.userResponses?.length || 0, + timeSpent: this.startTime ? Date.now() - this.startTime : 0, + completed: true, + details: { progress: this.progress, responses: this.userResponses } + }; + } + + handleUserInput(event, data) { + if (event?.type === 'input') this._handleInputChange?.(event); + if (event?.type === 'click' && event.target.id === 'submitButton') this._handleSubmit?.(event); + } + + async markCompleted(results) { + const { score, details } = results || this.getResults(); + if (this.contextMemory) { + this.contextMemory.recordInteraction({ + type: 'image', + subtype: 'completion', + content: this.currentExerciseData, + validation: { score }, + context: { moduleType: 'image' } + }); + } + } + + getExerciseType() { + return 'image'; + } + + getExerciseConfig() { + return { + type: this.getExerciseType(), + difficulty: this.currentExerciseData?.difficulty || 'medium', + estimatedTime: 5, + prerequisites: [], + metadata: { ...this.config, requiresAI: false } + }; + } + } export default ImageModule; \ No newline at end of file diff --git a/src/DRS/exercise-modules/OpenResponseModule.js b/src/DRS/exercise-modules/OpenResponseModule.js index 62d7fd5..1d5263c 100644 --- a/src/DRS/exercise-modules/OpenResponseModule.js +++ b/src/DRS/exercise-modules/OpenResponseModule.js @@ -1,13 +1,14 @@ /** * OpenResponseModule - Free-form open response exercises with AI validation * Allows students to write free-text responses that are evaluated by AI + * Implements DRSExerciseInterface for strict contract enforcement */ -import ExerciseModuleInterface from '../interfaces/ExerciseModuleInterface.js'; +import DRSExerciseInterface from '../interfaces/DRSExerciseInterface.js'; -class OpenResponseModule extends ExerciseModuleInterface { +class OpenResponseModule extends DRSExerciseInterface { constructor(orchestrator, llmValidator, prerequisiteEngine, contextMemory) { - super(); + super('OpenResponseModule'); // Validate dependencies if (!orchestrator || !llmValidator || !prerequisiteEngine || !contextMemory) { @@ -607,6 +608,81 @@ Format your response as: const previousTotal = this.progress.averageScore * (this.progress.questionsAnswered - 1); this.progress.averageScore = (previousTotal + result.score) / this.progress.questionsAnswered; } + + // ======================================== + // DRSExerciseInterface REQUIRED METHODS + // ======================================== + + async init(config = {}, content = {}) { + this.config = { ...this.config, ...config }; + this.currentExerciseData = content; + this.startTime = Date.now(); + this.initialized = true; + } + + async render(container) { + if (!this.initialized) throw new Error('OpenResponseModule must be initialized before rendering'); + await this.present(container, this.currentExerciseData); + } + + async destroy() { + this.cleanup?.(); + this.container = null; + this.initialized = false; + } + + getResults() { + const totalQuestions = this.questions ? this.questions.length : 0; + const answeredQuestions = this.userResponses ? this.userResponses.length : 0; + const score = this.progress ? Math.round(this.progress.averageScore * 100) : 0; + + return { + score, + attempts: answeredQuestions, + timeSpent: this.startTime ? Date.now() - this.startTime : 0, + completed: answeredQuestions >= totalQuestions, + details: { + totalQuestions, + answeredQuestions, + averageScore: this.progress?.averageScore || 0, + responses: this.userResponses || [] + } + }; + } + + handleUserInput(event, data) { + if (event && event.type === 'input') this._handleInputChange?.(event); + if (event && event.type === 'click' && event.target.id === 'submitButton') this._handleSubmit?.(event); + } + + async markCompleted(results) { + const { score, details } = results || this.getResults(); + if (this.contextMemory) { + this.contextMemory.recordInteraction({ + type: 'open-response', + subtype: 'completion', + content: { questions: this.questions }, + userResponse: this.userResponses, + validation: { score, averageScore: details.averageScore }, + context: { moduleType: 'open-response', totalQuestions: details.totalQuestions } + }); + } + } + + getExerciseType() { + return 'open-response'; + } + + getExerciseConfig() { + const questionCount = this.questions ? this.questions.length : this.config?.questionsPerExercise || 2; + return { + type: this.getExerciseType(), + difficulty: this.currentExerciseData?.difficulty || 'medium', + estimatedTime: questionCount * 3, // 3 min per question + prerequisites: [], + metadata: { ...this.config, questionCount, requiresAI: true } + }; + } } export default OpenResponseModule; \ No newline at end of file diff --git a/src/DRS/exercise-modules/PhraseModule.js b/src/DRS/exercise-modules/PhraseModule.js index a635070..2bd1c85 100644 --- a/src/DRS/exercise-modules/PhraseModule.js +++ b/src/DRS/exercise-modules/PhraseModule.js @@ -3,11 +3,11 @@ * Uses GPT-4-mini only, no fallbacks, structured response format */ -import ExerciseModuleInterface from '../interfaces/ExerciseModuleInterface.js'; +import DRSExerciseInterface from '../interfaces/DRSExerciseInterface.js'; -class PhraseModule extends ExerciseModuleInterface { +class PhraseModule extends DRSExerciseInterface { constructor(orchestrator, llmValidator, prerequisiteEngine, contextMemory) { - super(); + super('PhraseModule'); // Validate dependencies if (!orchestrator || !llmValidator || !prerequisiteEngine || !contextMemory) { @@ -910,6 +910,71 @@ Format: [answer]yes/no [explanation]your detailed feedback here`; document.head.appendChild(styles); } + + // ======================================== + // DRSExerciseInterface REQUIRED METHODS + // ======================================== + + async init(config = {}, content = {}) { + this.config = { ...this.config, ...config }; + this.currentExerciseData = content; + this.startTime = Date.now(); + this.initialized = true; + } + + async render(container) { + if (!this.initialized) throw new Error('PhraseModule must be initialized before rendering'); + await this.present(container, this.currentExerciseData); + } + + async destroy() { + this.cleanup?.(); + this.container = null; + this.initialized = false; + } + + getResults() { + return { + score: this.progress?.averageScore ? Math.round(this.progress.averageScore * 100) : 0, + attempts: this.userResponses?.length || 0, + timeSpent: this.startTime ? Date.now() - this.startTime : 0, + completed: true, + details: { progress: this.progress, responses: this.userResponses } + }; + } + + handleUserInput(event, data) { + if (event?.type === 'input') this._handleInputChange?.(event); + if (event?.type === 'click' && event.target.id === 'submitButton') this._handleSubmit?.(event); + } + + async markCompleted(results) { + const { score, details } = results || this.getResults(); + if (this.contextMemory) { + this.contextMemory.recordInteraction({ + type: 'phrase', + subtype: 'completion', + content: this.currentExerciseData, + validation: { score }, + context: { moduleType: 'phrase' } + }); + } + } + + getExerciseType() { + return 'phrase'; + } + + getExerciseConfig() { + return { + type: this.getExerciseType(), + difficulty: this.currentExerciseData?.difficulty || 'medium', + estimatedTime: 5, + prerequisites: [], + metadata: { ...this.config, requiresAI: false } + }; + } + } export default PhraseModule; \ No newline at end of file diff --git a/src/DRS/exercise-modules/TextAnalysisModule.js b/src/DRS/exercise-modules/TextAnalysisModule.js index 69b663d..d34ee14 100644 --- a/src/DRS/exercise-modules/TextAnalysisModule.js +++ b/src/DRS/exercise-modules/TextAnalysisModule.js @@ -1,13 +1,14 @@ /** * TextAnalysisModule - Open-text comprehension with AI validation * Presents text passages with open questions, validates free-text responses using AI + * Implements DRSExerciseInterface for strict contract enforcement */ -import ExerciseModuleInterface from '../interfaces/ExerciseModuleInterface.js'; +import DRSExerciseInterface from '../interfaces/DRSExerciseInterface.js'; -class TextAnalysisModule extends ExerciseModuleInterface { +class TextAnalysisModule extends DRSExerciseInterface { constructor(orchestrator, llmValidator, prerequisiteEngine, contextMemory) { - super(); + super('TextAnalysisModule'); // Validate dependencies if (!orchestrator || !llmValidator || !prerequisiteEngine || !contextMemory) { @@ -661,6 +662,174 @@ Return ONLY a JSON array: setTimeout(() => errorDiv.remove(), 5000); } + + // ======================================== + // DRSExerciseInterface REQUIRED METHODS + // ======================================== + + /** + * Initialize the exercise module + * @param {Object} config - Exercise configuration + * @param {Object} content - Exercise content data + * @returns {Promise} + */ + async init(config = {}, content = {}) { + console.log('📖 Initializing TextAnalysisModule...'); + + // Merge provided config with defaults + this.config = { + ...this.config, + ...config + }; + + // Store content + this.currentExerciseData = content; + this.startTime = Date.now(); + this.initialized = true; + + console.log('✅ TextAnalysisModule initialized'); + } + + /** + * Render the exercise UI + * @param {HTMLElement} container - Container element to render into + * @returns {Promise} + */ + async render(container) { + if (!this.initialized) { + throw new Error('TextAnalysisModule must be initialized before rendering'); + } + + // Use existing present() logic + await this.present(container, this.currentExerciseData); + } + + /** + * Clean up and destroy the exercise + * @returns {Promise} + */ + async destroy() { + console.log('🧹 Destroying TextAnalysisModule...'); + + // Use existing cleanup + this.cleanup(); + + console.log('✅ TextAnalysisModule destroyed'); + } + + /** + * Get exercise results and statistics + * @returns {Object} - Results data + */ + getResults() { + const totalQuestions = this.questions ? this.questions.length : 0; + const answeredQuestions = this.userResponses.length; + const averageScore = this.progress.averageScore; + const timeSpent = this.startTime ? Date.now() - this.startTime : 0; + + return { + score: Math.round(averageScore * 100), + attempts: answeredQuestions, + timeSpent, + completed: answeredQuestions >= totalQuestions, + details: { + totalQuestions, + answeredQuestions, + averageScore, + qualityMetrics: this.progress.qualityMetrics, + responses: this.userResponses + } + }; + } + + /** + * Handle user input during exercise + * @param {Event} event - User input event + * @param {*} data - Input data + * @returns {void} + */ + handleUserInput(event, data) { + // Delegate to existing handlers + if (event && event.type) { + switch (event.type) { + case 'input': + this._handleInputChange(event); + break; + case 'click': + if (event.target.id === 'submitButton') { + this._handleSubmit(event); + } + break; + } + } + } + + /** + * Mark exercise as completed and save progress + * @param {Object} results - Exercise results + * @returns {Promise} + */ + async markCompleted(results) { + console.log('💾 Marking TextAnalysisModule as completed...'); + + const { score, details } = results || this.getResults(); + + // Save completion metadata + if (this.contextMemory) { + this.contextMemory.recordInteraction({ + type: 'text-analysis', + subtype: 'completion', + content: { + text: this.currentText, + questions: this.questions + }, + userResponse: this.userResponses, + validation: { + score, + averageScore: details.averageScore, + qualityMetrics: details.qualityMetrics + }, + context: { + moduleType: 'text-analysis', + totalQuestions: details.totalQuestions, + answeredQuestions: details.answeredQuestions + } + }); + } + + console.log('✅ TextAnalysisModule completion saved'); + } + + /** + * Get exercise type identifier + * @returns {string} - Exercise type + */ + getExerciseType() { + return 'text-analysis'; + } + + /** + * Get exercise configuration + * @returns {Object} - Configuration object + */ + getExerciseConfig() { + const questionCount = this.questions ? this.questions.length : 2; + const estimatedTimePerQuestion = 3; // 3 minutes per question + + return { + type: this.getExerciseType(), + difficulty: this.currentExerciseData?.difficulty || 'medium', + estimatedTime: questionCount * estimatedTimePerQuestion, + prerequisites: [], // Text analysis requires no specific prerequisites + metadata: { + ...this.config, + questionCount, + textLength: this.currentText ? this.currentText.length : 0, + requiresAI: true, + model: this.config.model + } + }; + } } export default TextAnalysisModule; \ No newline at end of file diff --git a/src/DRS/exercise-modules/TextModule.js b/src/DRS/exercise-modules/TextModule.js index 398d4d9..d3aa6da 100644 --- a/src/DRS/exercise-modules/TextModule.js +++ b/src/DRS/exercise-modules/TextModule.js @@ -3,11 +3,11 @@ * Handles text passages with comprehension questions and contextual understanding */ -import ExerciseModuleInterface from '../interfaces/ExerciseModuleInterface.js'; +import DRSExerciseInterface from '../interfaces/DRSExerciseInterface.js'; -class TextModule extends ExerciseModuleInterface { +class TextModule extends DRSExerciseInterface { constructor(orchestrator, llmValidator, prerequisiteEngine, contextMemory) { - super(); + super('TextModule'); // Validate dependencies if (!orchestrator || !llmValidator || !prerequisiteEngine || !contextMemory) { @@ -1505,6 +1505,71 @@ Format: [answer]yes/no [explanation]your comprehensive educational feedback here document.head.appendChild(styles); } + + // ======================================== + // DRSExerciseInterface REQUIRED METHODS + // ======================================== + + async init(config = {}, content = {}) { + this.config = { ...this.config, ...config }; + this.currentExerciseData = content; + this.startTime = Date.now(); + this.initialized = true; + } + + async render(container) { + if (!this.initialized) throw new Error('TextModule must be initialized before rendering'); + await this.present(container, this.currentExerciseData); + } + + async destroy() { + this.cleanup?.(); + this.container = null; + this.initialized = false; + } + + getResults() { + return { + score: this.progress?.averageScore ? Math.round(this.progress.averageScore * 100) : 0, + attempts: this.userResponses?.length || 0, + timeSpent: this.startTime ? Date.now() - this.startTime : 0, + completed: true, + details: { progress: this.progress, responses: this.userResponses } + }; + } + + handleUserInput(event, data) { + if (event?.type === 'input') this._handleInputChange?.(event); + if (event?.type === 'click' && event.target.id === 'submitButton') this._handleSubmit?.(event); + } + + async markCompleted(results) { + const { score, details } = results || this.getResults(); + if (this.contextMemory) { + this.contextMemory.recordInteraction({ + type: 'text', + subtype: 'completion', + content: this.currentExerciseData, + validation: { score }, + context: { moduleType: 'text' } + }); + } + } + + getExerciseType() { + return 'text'; + } + + getExerciseConfig() { + return { + type: this.getExerciseType(), + difficulty: this.currentExerciseData?.difficulty || 'medium', + estimatedTime: 5, + prerequisites: [], + metadata: { ...this.config, requiresAI: false } + }; + } + } export default TextModule; \ No newline at end of file diff --git a/src/DRS/exercise-modules/TranslationModule.js b/src/DRS/exercise-modules/TranslationModule.js index 2ca72e3..a9539ab 100644 --- a/src/DRS/exercise-modules/TranslationModule.js +++ b/src/DRS/exercise-modules/TranslationModule.js @@ -1,13 +1,14 @@ /** * TranslationModule - AI-validated translation exercises * Presents translation challenges with intelligent AI feedback on accuracy and fluency + * Implements DRSExerciseInterface for strict contract enforcement */ -import ExerciseModuleInterface from '../interfaces/ExerciseModuleInterface.js'; +import DRSExerciseInterface from '../interfaces/DRSExerciseInterface.js'; -class TranslationModule extends ExerciseModuleInterface { +class TranslationModule extends DRSExerciseInterface { constructor(orchestrator, llmValidator, prerequisiteEngine, contextMemory) { - super(); + super('TranslationModule'); // Validate dependencies if (!orchestrator || !llmValidator || !prerequisiteEngine || !contextMemory) { @@ -766,6 +767,79 @@ Return ONLY valid JSON: setTimeout(() => errorDiv.remove(), 5000); } + + // ======================================== + // DRSExerciseInterface REQUIRED METHODS + // ======================================== + + async init(config = {}, content = {}) { + this.config = { ...this.config, ...config }; + this.currentExerciseData = content; + this.startTime = Date.now(); + this.initialized = true; + } + + async render(container) { + if (!this.initialized) throw new Error('TranslationModule must be initialized before rendering'); + await this.present(container, this.currentExerciseData); + } + + async destroy() { + this.cleanup(); + } + + getResults() { + const totalPhrases = this.currentPhrases ? this.currentPhrases.length : 0; + const translatedPhrases = this.userTranslations ? this.userTranslations.length : 0; + const score = this.progress ? Math.round(this.progress.averageAccuracy * 100) : 0; + + return { + score, + attempts: translatedPhrases, + timeSpent: this.startTime ? Date.now() - this.startTime : 0, + completed: translatedPhrases >= totalPhrases, + details: { + totalPhrases, + translatedPhrases, + averageAccuracy: this.progress?.averageAccuracy || 0, + translations: this.userTranslations || [] + } + }; + } + + handleUserInput(event, data) { + if (event && event.type === 'input') this._handleInputChange?.(event); + if (event && event.type === 'click' && event.target.id === 'submitButton') this._handleSubmit?.(event); + } + + async markCompleted(results) { + const { score, details } = results || this.getResults(); + if (this.contextMemory) { + this.contextMemory.recordInteraction({ + type: 'translation', + subtype: 'completion', + content: { phrases: this.currentPhrases }, + userResponse: this.userTranslations, + validation: { score, averageAccuracy: details.averageAccuracy }, + context: { moduleType: 'translation', totalPhrases: details.totalPhrases } + }); + } + } + + getExerciseType() { + return 'translation'; + } + + getExerciseConfig() { + const phraseCount = this.currentPhrases ? this.currentPhrases.length : this.config?.phrasesPerExercise || 3; + return { + type: this.getExerciseType(), + difficulty: this.currentExerciseData?.difficulty || 'medium', + estimatedTime: phraseCount * 2, // 2 min per phrase + prerequisites: [], + metadata: { ...this.config, phraseCount, requiresAI: true } + }; + } } export default TranslationModule; \ No newline at end of file diff --git a/src/DRS/exercise-modules/VocabularyModule.js b/src/DRS/exercise-modules/VocabularyModule.js index a3c4b60..7b807bd 100644 --- a/src/DRS/exercise-modules/VocabularyModule.js +++ b/src/DRS/exercise-modules/VocabularyModule.js @@ -1,13 +1,13 @@ /** * VocabularyModule - Groups of 5 vocabulary exercise implementation - * First exercise module following the ExerciseModuleInterface + * Implements DRSExerciseInterface for strict contract enforcement */ -import ExerciseModuleInterface from '../interfaces/ExerciseModuleInterface.js'; +import DRSExerciseInterface from '../interfaces/DRSExerciseInterface.js'; -class VocabularyModule extends ExerciseModuleInterface { +class VocabularyModule extends DRSExerciseInterface { constructor(orchestrator, llmValidator, prerequisiteEngine, contextMemory) { - super(); + super('VocabularyModule'); // Validate dependencies (llmValidator can be null since we use local validation) if (!orchestrator || !prerequisiteEngine || !contextMemory) { @@ -44,14 +44,84 @@ class VocabularyModule extends ExerciseModuleInterface { this._handleDifficultySelection = this._handleDifficultySelection.bind(this); } - async init() { + /** + * Initialize the exercise module + * @param {Object} config - Exercise configuration + * @param {Object} content - Exercise content data + * @returns {Promise} + */ + async init(config = {}, content = {}) { if (this.initialized) return; console.log('📚 Initializing VocabularyModule...'); + + // Merge provided config with defaults + this.config = { + ...this.config, + ...config + }; + + // Store content for later use + this.currentExerciseData = content; + this.startTime = Date.now(); + this.initialized = true; console.log('✅ VocabularyModule initialized'); } + /** + * Render the exercise UI + * @param {HTMLElement} container - Container element to render into + * @returns {Promise} + */ + async render(container) { + if (!this.initialized) { + throw new Error('VocabularyModule must be initialized before rendering'); + } + + // Use existing present() logic + await this.present(container, this.currentExerciseData); + } + + /** + * Clean up and destroy the exercise + * @returns {Promise} + */ + async destroy() { + console.log('🧹 Destroying VocabularyModule...'); + + // Remove event listeners + if (this.container) { + const input = this.container.querySelector('.vocabulary-input'); + const submitBtn = this.container.querySelector('.btn-submit'); + const revealBtn = this.container.querySelector('.btn-reveal'); + const nextBtn = this.container.querySelector('.btn-next'); + const difficultyButtons = this.container.querySelectorAll('.difficulty-btn'); + + if (input) input.removeEventListener('input', this._handleUserInput); + if (submitBtn) submitBtn.removeEventListener('click', this._handleUserInput); + if (revealBtn) revealBtn.removeEventListener('click', this._handleRevealAnswer); + if (nextBtn) nextBtn.removeEventListener('click', this._handleNextWord); + difficultyButtons.forEach(btn => { + btn.removeEventListener('click', this._handleDifficultySelection); + }); + + // Clear container + this.container.innerHTML = ''; + this.container = null; + } + + // Reset state + this.currentVocabularyGroup = []; + this.currentWordIndex = 0; + this.groupResults = []; + this.isRevealed = false; + this.currentExerciseData = null; + this.initialized = false; + + console.log('✅ VocabularyModule destroyed'); + } + /** * Check if module can run with current prerequisites * @param {Array} prerequisites - List of learned vocabulary/concepts @@ -1196,6 +1266,144 @@ class VocabularyModule extends ExerciseModuleInterface { document.head.appendChild(styles); } + + // ======================================== + // DRSExerciseInterface REQUIRED METHODS + // ======================================== + + /** + * Get exercise results and statistics + * @returns {Object} - Results data + */ + getResults() { + const correctWords = this.groupResults.filter(result => result.correct).length; + const totalWords = this.groupResults.length; + const score = totalWords > 0 ? Math.round((correctWords / totalWords) * 100) : 0; + const timeSpent = this.startTime ? Date.now() - this.startTime : 0; + + return { + score, + attempts: totalWords, + timeSpent, + completed: this.currentWordIndex >= this.currentVocabularyGroup.length, + details: { + correctWords, + totalWords, + currentGroupIndex: this.currentGroupIndex, + totalGroups: this.allVocabularyGroups ? this.allVocabularyGroups.length : 1, + groupResults: this.groupResults, + masteryPercentage: score + } + }; + } + + /** + * Handle user input during exercise + * @param {Event} event - User input event + * @param {*} data - Input data + * @returns {void} + */ + handleUserInput(event, data) { + // This method delegates to existing handlers + // Already implemented through _handleUserInput, _handleDifficultySelection, etc. + if (event && event.type) { + switch (event.type) { + case 'input': + case 'change': + this._handleUserInput(event); + break; + case 'click': + if (event.target.classList.contains('difficulty-btn')) { + this._handleDifficultySelection(event); + } else if (event.target.classList.contains('btn-next')) { + this._handleNextWord(event); + } else if (event.target.classList.contains('btn-reveal')) { + this._handleRevealAnswer(event); + } + break; + } + } + } + + /** + * Mark exercise as completed and save progress + * @param {Object} results - Exercise results + * @returns {Promise} + */ + async markCompleted(results) { + console.log('💾 Marking VocabularyModule as completed...'); + + // Mark all words in current group as mastered if score is high enough + const { score, details } = results || this.getResults(); + + if (score >= this.config.masteryThreshold) { + // Mark all words in group as mastered + for (const word of this.currentVocabularyGroup) { + if (this.prerequisiteEngine && this.prerequisiteEngine.isInitialized) { + await this.prerequisiteEngine.markWordMastered(word.word, { + score, + timestamp: new Date().toISOString(), + moduleType: 'vocabulary', + attempts: details.totalWords + }); + } + } + console.log(`✅ Marked ${this.currentVocabularyGroup.length} words as mastered`); + } else { + console.log(`⚠️ Score ${score}% below mastery threshold ${this.config.masteryThreshold}%`); + } + + // Save completion metadata + if (this.contextMemory) { + this.contextMemory.recordInteraction({ + type: 'vocabulary', + subtype: 'completion', + content: { + vocabulary: this.currentVocabularyGroup + }, + validation: results, + context: { + moduleType: 'vocabulary', + groupIndex: this.currentGroupIndex, + totalGroups: this.allVocabularyGroups ? this.allVocabularyGroups.length : 1 + } + }); + } + + console.log('✅ VocabularyModule completion saved'); + } + + /** + * Get exercise type identifier + * @returns {string} - Exercise type + */ + getExerciseType() { + return 'vocabulary'; + } + + /** + * Get exercise configuration + * @returns {Object} - Configuration object + */ + getExerciseConfig() { + const wordCount = this.currentVocabularyGroup ? this.currentVocabularyGroup.length : 0; + const estimatedTimePerWord = 0.5; // 30 seconds per word + + return { + type: this.getExerciseType(), + difficulty: wordCount <= 3 ? 'easy' : (wordCount <= 7 ? 'medium' : 'hard'), + estimatedTime: Math.ceil(wordCount * estimatedTimePerWord), // in minutes + prerequisites: [], // Vocabulary has no prerequisites + metadata: { + ...this.config, + groupSize: this.config.groupSize, + masteryThreshold: this.config.masteryThreshold, + currentGroupIndex: this.currentGroupIndex, + totalGroups: this.allVocabularyGroups ? this.allVocabularyGroups.length : 1, + wordCount + } + }; + } } export default VocabularyModule; \ No newline at end of file diff --git a/src/DRS/exercise-modules/WordDiscoveryModule.js b/src/DRS/exercise-modules/WordDiscoveryModule.js index 4761993..8e0fd52 100644 --- a/src/DRS/exercise-modules/WordDiscoveryModule.js +++ b/src/DRS/exercise-modules/WordDiscoveryModule.js @@ -3,11 +3,11 @@ * Shows word, pronunciation, meaning, and example before flashcard practice */ -import ExerciseModuleInterface from '../interfaces/ExerciseModuleInterface.js'; +import DRSExerciseInterface from '../interfaces/DRSExerciseInterface.js'; -class WordDiscoveryModule extends ExerciseModuleInterface { +class WordDiscoveryModule extends DRSExerciseInterface { constructor(orchestrator, llmValidator, prerequisiteEngine, contextMemory) { - super(); + super('WordDiscoveryModule'); this.orchestrator = orchestrator; this.llmValidator = llmValidator; this.prerequisiteEngine = prerequisiteEngine; @@ -362,6 +362,71 @@ class WordDiscoveryModule extends ExerciseModuleInterface { prerequisites: [] }; } + + // ======================================== + // DRSExerciseInterface REQUIRED METHODS + // ======================================== + + async init(config = {}, content = {}) { + this.config = { ...this.config, ...config }; + this.currentExerciseData = content; + this.startTime = Date.now(); + this.initialized = true; + } + + async render(container) { + if (!this.initialized) throw new Error('WordDiscoveryModule must be initialized before rendering'); + await this.present(container, this.currentExerciseData); + } + + async destroy() { + this.cleanup?.(); + this.container = null; + this.initialized = false; + } + + getResults() { + return { + score: this.progress?.averageScore ? Math.round(this.progress.averageScore * 100) : 0, + attempts: this.userResponses?.length || 0, + timeSpent: this.startTime ? Date.now() - this.startTime : 0, + completed: true, + details: { progress: this.progress, responses: this.userResponses } + }; + } + + handleUserInput(event, data) { + if (event?.type === 'input') this._handleInputChange?.(event); + if (event?.type === 'click' && event.target.id === 'submitButton') this._handleSubmit?.(event); + } + + async markCompleted(results) { + const { score, details } = results || this.getResults(); + if (this.contextMemory) { + this.contextMemory.recordInteraction({ + type: 'word-discovery', + subtype: 'completion', + content: this.currentExerciseData, + validation: { score }, + context: { moduleType: 'word-discovery' } + }); + } + } + + getExerciseType() { + return 'word-discovery'; + } + + getExerciseConfig() { + return { + type: this.getExerciseType(), + difficulty: this.currentExerciseData?.difficulty || 'medium', + estimatedTime: 5, + prerequisites: [], + metadata: { ...this.config, requiresAI: false } + }; + } + } export default WordDiscoveryModule; \ No newline at end of file diff --git a/src/DRS/interfaces/DRSExerciseInterface.js b/src/DRS/interfaces/DRSExerciseInterface.js new file mode 100644 index 0000000..324b47a --- /dev/null +++ b/src/DRS/interfaces/DRSExerciseInterface.js @@ -0,0 +1,181 @@ +/** + * DRSExerciseInterface - STRICT interface for all DRS exercise modules + * Like a .h header in C++ - ALL exercise modules MUST implement these methods + * + * ANY module wanting to work with DRS/SmartGuide MUST implement this interface + */ + +import StrictInterface from './StrictInterface.js'; + +class DRSExerciseInterface extends StrictInterface { + constructor(implementationName) { + super(implementationName); + } + + /** + * Define required methods that ALL DRS exercise modules MUST implement + * @override + */ + _getAbstractMethods() { + return [ + // Lifecycle + 'init', + 'render', + 'destroy', + + // Exercise logic + 'validate', + 'getResults', + 'handleUserInput', + + // Progress tracking + 'markCompleted', + 'getProgress', + + // Metadata + 'getExerciseType', + 'getExerciseConfig' + ]; + } + + // ======================================== + // LIFECYCLE METHODS (REQUIRED) + // ======================================== + + /** + * ⚠️ MUST IMPLEMENT - Initialize the exercise module + * @param {Object} config - Exercise configuration + * @param {Object} content - Exercise content data + * @returns {Promise} + */ + async init(config, content) { + this._throwImplementationError('init'); + } + + /** + * ⚠️ MUST IMPLEMENT - Render the exercise UI + * @param {HTMLElement} container - Container element to render into + * @returns {Promise} + */ + async render(container) { + this._throwImplementationError('render'); + } + + /** + * ⚠️ MUST IMPLEMENT - Clean up and destroy the exercise + * @returns {Promise} + */ + async destroy() { + this._throwImplementationError('destroy'); + } + + // ======================================== + // EXERCISE LOGIC (REQUIRED) + // ======================================== + + /** + * ⚠️ MUST IMPLEMENT - Validate user's answer + * @param {*} userAnswer - User's answer (format depends on exercise type) + * @returns {Promise} - Validation result + * + * MUST return format: + * { + * isCorrect: boolean, + * score: number, // 0-100 + * feedback: string, + * explanation: string, + * metadata: Object // Exercise-specific data + * } + */ + async validate(userAnswer) { + this._throwImplementationError('validate'); + } + + /** + * ⚠️ MUST IMPLEMENT - Get exercise results and statistics + * @returns {Object} - Results data + * + * MUST return format: + * { + * score: number, // 0-100 + * attempts: number, + * timeSpent: number, // milliseconds + * completed: boolean, + * details: Object // Exercise-specific details + * } + */ + getResults() { + this._throwImplementationError('getResults'); + } + + /** + * ⚠️ MUST IMPLEMENT - Handle user input during exercise + * @param {Event} event - User input event + * @param {*} data - Input data + * @returns {void} + */ + handleUserInput(event, data) { + this._throwImplementationError('handleUserInput'); + } + + // ======================================== + // PROGRESS TRACKING (REQUIRED) + // ======================================== + + /** + * ⚠️ MUST IMPLEMENT - Mark exercise as completed and save progress + * @param {Object} results - Exercise results + * @returns {Promise} + */ + async markCompleted(results) { + this._throwImplementationError('markCompleted'); + } + + /** + * ⚠️ MUST IMPLEMENT - Get current exercise progress + * @returns {Object} - Progress data + * + * MUST return format: + * { + * percentage: number, // 0-100 + * currentStep: number, + * totalSteps: number, + * itemsCompleted: number, + * itemsTotal: number + * } + */ + getProgress() { + this._throwImplementationError('getProgress'); + } + + // ======================================== + // METADATA (REQUIRED) + // ======================================== + + /** + * ⚠️ MUST IMPLEMENT - Get exercise type identifier + * @returns {string} - Exercise type (e.g., 'vocabulary', 'text-analysis', 'grammar') + */ + getExerciseType() { + this._throwImplementationError('getExerciseType'); + } + + /** + * ⚠️ MUST IMPLEMENT - Get exercise configuration + * @returns {Object} - Configuration object + * + * MUST return format: + * { + * type: string, // Exercise type + * difficulty: string, // 'easy', 'medium', 'hard' + * estimatedTime: number, // minutes + * prerequisites: Array, // Required prerequisites + * metadata: Object // Exercise-specific config + * } + */ + getExerciseConfig() { + this._throwImplementationError('getExerciseConfig'); + } +} + +export default DRSExerciseInterface; diff --git a/src/DRS/interfaces/ProgressSystemInterface.js b/src/DRS/interfaces/ProgressSystemInterface.js new file mode 100644 index 0000000..6fc8891 --- /dev/null +++ b/src/DRS/interfaces/ProgressSystemInterface.js @@ -0,0 +1,232 @@ +/** + * ProgressSystemInterface - STRICT interface for all DRS progress systems + * Like a .h header in C++ - ALL progress systems MUST implement these methods + * + * ANY system wanting to work with DRS/SmartGuide MUST implement this interface + */ + +import StrictInterface from './StrictInterface.js'; + +class ProgressSystemInterface extends StrictInterface { + constructor(implementationName) { + super(implementationName); + } + + /** + * Define required methods that ALL progress systems MUST implement + * @override + */ + _getAbstractMethods() { + return [ + // Vocabulary tracking + 'markWordDiscovered', + 'markWordMastered', + 'isWordDiscovered', + 'isWordMastered', + + // Content tracking + 'markPhraseCompleted', + 'markDialogCompleted', + 'markTextCompleted', + 'markAudioCompleted', + 'markImageCompleted', + 'markGrammarCompleted', + + // Prerequisites + 'canComplete', + + // Progress calculation + 'getProgress', + + // Persistence + 'saveProgress', + 'loadProgress', + + // Utility + 'reset' + ]; + } + + // ======================================== + // VOCABULARY METHODS (REQUIRED) + // ======================================== + + /** + * ⚠️ MUST IMPLEMENT - Mark a word as discovered (passive exposure) + * @param {string} word - Word to mark as discovered + * @param {Object} metadata - Optional metadata (timestamp, context, etc.) + * @returns {Promise} + */ + async markWordDiscovered(word, metadata = {}) { + this._throwImplementationError('markWordDiscovered'); + } + + /** + * ⚠️ MUST IMPLEMENT - Mark a word as mastered (active practice) + * @param {string} word - Word to mark as mastered + * @param {Object} metadata - Optional metadata (score, attempts, etc.) + * @returns {Promise} + */ + async markWordMastered(word, metadata = {}) { + this._throwImplementationError('markWordMastered'); + } + + /** + * ⚠️ MUST IMPLEMENT - Check if word is discovered + * @param {string} word - Word to check + * @returns {boolean} + */ + isWordDiscovered(word) { + this._throwImplementationError('isWordDiscovered'); + } + + /** + * ⚠️ MUST IMPLEMENT - Check if word is mastered + * @param {string} word - Word to check + * @returns {boolean} + */ + isWordMastered(word) { + this._throwImplementationError('isWordMastered'); + } + + // ======================================== + // CONTENT TRACKING METHODS (REQUIRED) + // ======================================== + + /** + * ⚠️ MUST IMPLEMENT - Mark phrase as completed + * @param {string} phraseId - Phrase identifier + * @param {Object} metadata - Optional metadata + * @returns {Promise} + */ + async markPhraseCompleted(phraseId, metadata = {}) { + this._throwImplementationError('markPhraseCompleted'); + } + + /** + * ⚠️ MUST IMPLEMENT - Mark dialog as completed + * @param {string} dialogId - Dialog identifier + * @param {Object} metadata - Optional metadata + * @returns {Promise} + */ + async markDialogCompleted(dialogId, metadata = {}) { + this._throwImplementationError('markDialogCompleted'); + } + + /** + * ⚠️ MUST IMPLEMENT - Mark text/lesson as completed + * @param {string} textId - Text identifier + * @param {Object} metadata - Optional metadata + * @returns {Promise} + */ + async markTextCompleted(textId, metadata = {}) { + this._throwImplementationError('markTextCompleted'); + } + + /** + * ⚠️ MUST IMPLEMENT - Mark audio as completed + * @param {string} audioId - Audio identifier + * @param {Object} metadata - Optional metadata + * @returns {Promise} + */ + async markAudioCompleted(audioId, metadata = {}) { + this._throwImplementationError('markAudioCompleted'); + } + + /** + * ⚠️ MUST IMPLEMENT - Mark image as completed + * @param {string} imageId - Image identifier + * @param {Object} metadata - Optional metadata + * @returns {Promise} + */ + async markImageCompleted(imageId, metadata = {}) { + this._throwImplementationError('markImageCompleted'); + } + + /** + * ⚠️ MUST IMPLEMENT - Mark grammar concept as completed + * @param {string} grammarId - Grammar identifier + * @param {Object} metadata - Optional metadata + * @returns {Promise} + */ + async markGrammarCompleted(grammarId, metadata = {}) { + this._throwImplementationError('markGrammarCompleted'); + } + + // ======================================== + // PREREQUISITE CHECKING (REQUIRED) + // ======================================== + + /** + * ⚠️ MUST IMPLEMENT - Check if an item can be completed (prerequisites met) + * @param {string} itemType - Type of item (vocabulary-discovery, phrase, dialog, etc.) + * @param {string} itemId - Item identifier + * @param {Object} context - Additional context (chapter content, etc.) + * @returns {Object} - { canComplete: boolean, reason: string, missingPrereqs: Array } + */ + canComplete(itemType, itemId, context = {}) { + this._throwImplementationError('canComplete'); + } + + // ======================================== + // PROGRESS CALCULATION (REQUIRED) + // ======================================== + + /** + * ⚠️ MUST IMPLEMENT - Get current progress for a chapter + * @param {string} chapterId - Chapter identifier + * @returns {Object} - Progress data with percentage, breakdown, etc. + * + * MUST return format: + * { + * percentage: number, // 0-100 + * completedWeight: number, // Total weight completed + * totalWeight: number, // Total possible weight + * breakdown: Object, // By item type + * completedBreakdown: Object // Completed items by type + * } + */ + getProgress(chapterId) { + this._throwImplementationError('getProgress'); + } + + // ======================================== + // PERSISTENCE (REQUIRED) + // ======================================== + + /** + * ⚠️ MUST IMPLEMENT - Save progress to persistent storage + * @param {string} bookId - Book identifier + * @param {string} chapterId - Chapter identifier + * @returns {Promise} + */ + async saveProgress(bookId, chapterId) { + this._throwImplementationError('saveProgress'); + } + + /** + * ⚠️ MUST IMPLEMENT - Load progress from persistent storage + * @param {string} bookId - Book identifier + * @param {string} chapterId - Chapter identifier + * @returns {Promise} - Loaded progress data + */ + async loadProgress(bookId, chapterId) { + this._throwImplementationError('loadProgress'); + } + + // ======================================== + // UTILITY (REQUIRED) + // ======================================== + + /** + * ⚠️ MUST IMPLEMENT - Reset all progress for a chapter + * @param {string} bookId - Book identifier + * @param {string} chapterId - Chapter identifier + * @returns {Promise} + */ + async reset(bookId, chapterId) { + this._throwImplementationError('reset'); + } +} + +export default ProgressSystemInterface; diff --git a/src/DRS/services/ImplementationValidator.js b/src/DRS/services/ImplementationValidator.js index f8977b3..8ec8794 100644 --- a/src/DRS/services/ImplementationValidator.js +++ b/src/DRS/services/ImplementationValidator.js @@ -16,13 +16,20 @@ import { class ImplementationValidator { static async validateAll() { - console.log('%c🔍 VALIDATING PROGRESS ITEM IMPLEMENTATIONS...', + console.log('%c🔍 VALIDATING DRS IMPLEMENTATIONS...', 'background: #3b82f6; color: white; font-size: 16px; padding: 8px; font-weight: bold;' ); const errors = []; const validations = []; + // ======================================== + // 1. VALIDATE PROGRESS ITEMS + // ======================================== + console.log('%c📦 PART 1: Validating Progress Items...', + 'background: #6366f1; color: white; font-size: 14px; padding: 6px; font-weight: bold;' + ); + // Test VocabularyDiscoveryItem validations.push( this._testItem('VocabularyDiscoveryItem', () => @@ -79,15 +86,108 @@ class ImplementationValidator { ) ); - // Wait for all validations - const results = await Promise.allSettled(validations); + // Wait for all item validations + const itemResults = await Promise.allSettled(validations); - results.forEach((result, index) => { + itemResults.forEach((result, index) => { if (result.status === 'rejected') { errors.push(result.reason); } }); + // ======================================== + // 2. VALIDATE PROGRESS SYSTEMS + // ======================================== + console.log('%c🔧 PART 2: Validating Progress Systems...', + 'background: #6366f1; color: white; font-size: 14px; padding: 6px; font-weight: bold;' + ); + + const systemValidations = []; + + // Test ProgressTracker (if it implements ProgressSystemInterface) + systemValidations.push( + this._testProgressSystem('ProgressTracker', async () => { + const { default: ProgressTracker } = await import('./ProgressTracker.js'); + return ProgressTracker; + }) + ); + + // Test PrerequisiteEngine (if it implements ProgressSystemInterface) + systemValidations.push( + this._testProgressSystem('PrerequisiteEngine', async () => { + const { default: PrerequisiteEngine } = await import('./PrerequisiteEngine.js'); + return PrerequisiteEngine; + }) + ); + + const systemResults = await Promise.allSettled(systemValidations); + + systemResults.forEach((result, index) => { + if (result.status === 'rejected') { + errors.push(result.reason); + } + }); + + // ======================================== + // 3. VALIDATE DRS EXERCISE MODULES + // ======================================== + console.log('%c🎮 PART 3: Validating DRS Exercise Modules...', + 'background: #6366f1; color: white; font-size: 14px; padding: 6px; font-weight: bold;' + ); + + const exerciseValidations = []; + + // Test VocabularyModule + exerciseValidations.push( + this._testExerciseModule('VocabularyModule', async () => { + const { default: VocabularyModule } = await import('../exercise-modules/VocabularyModule.js'); + return VocabularyModule; + }) + ); + + // Test TextAnalysisModule + exerciseValidations.push( + this._testExerciseModule('TextAnalysisModule', async () => { + const { default: TextAnalysisModule } = await import('../exercise-modules/TextAnalysisModule.js'); + return TextAnalysisModule; + }) + ); + + // Test GrammarAnalysisModule + exerciseValidations.push( + this._testExerciseModule('GrammarAnalysisModule', async () => { + const { default: GrammarAnalysisModule } = await import('../exercise-modules/GrammarAnalysisModule.js'); + return GrammarAnalysisModule; + }) + ); + + // Test TranslationModule + exerciseValidations.push( + this._testExerciseModule('TranslationModule', async () => { + const { default: TranslationModule } = await import('../exercise-modules/TranslationModule.js'); + return TranslationModule; + }) + ); + + // Test OpenResponseModule + exerciseValidations.push( + this._testExerciseModule('OpenResponseModule', async () => { + const { default: OpenResponseModule } = await import('../exercise-modules/OpenResponseModule.js'); + return OpenResponseModule; + }) + ); + + const exerciseResults = await Promise.allSettled(exerciseValidations); + + exerciseResults.forEach((result, index) => { + if (result.status === 'rejected') { + errors.push(result.reason); + } + }); + + // ======================================== + // FINAL RESULT + // ======================================== if (errors.length > 0) { console.error('%c❌ VALIDATION FAILED', 'background: red; color: white; font-size: 20px; padding: 10px; font-weight: bold;' @@ -96,7 +196,7 @@ class ImplementationValidator { return false; } - console.log('%c✅ ALL IMPLEMENTATIONS VALID', + console.log('%c✅ ALL DRS IMPLEMENTATIONS VALID', 'background: #10b981; color: white; font-size: 16px; padding: 8px; font-weight: bold;' ); return true; @@ -129,6 +229,103 @@ class ImplementationValidator { throw error; } } + + static async _testProgressSystem(systemName, loadFn) { + try { + const SystemClass = await loadFn(); + + // Required methods from ProgressSystemInterface + const requiredMethods = [ + // Vocabulary + 'markWordDiscovered', + 'markWordMastered', + 'isWordDiscovered', + 'isWordMastered', + // Content + 'markPhraseCompleted', + 'markDialogCompleted', + 'markTextCompleted', + 'markAudioCompleted', + 'markImageCompleted', + 'markGrammarCompleted', + // Prerequisites + 'canComplete', + // Progress + 'getProgress', + // Persistence + 'saveProgress', + 'loadProgress', + // Utility + 'reset' + ]; + + const missingMethods = []; + + // Check prototype for methods + requiredMethods.forEach(method => { + if (typeof SystemClass.prototype[method] !== 'function') { + missingMethods.push(method); + } + }); + + if (missingMethods.length > 0) { + throw new Error( + `${systemName} is missing ProgressSystemInterface methods: ${missingMethods.join(', ')}` + ); + } + + console.log(`✅ ${systemName} - OK`); + return true; + } catch (error) { + console.error(`❌ ${systemName} - FAILED:`, error.message); + throw error; + } + } + + static async _testExerciseModule(moduleName, loadFn) { + try { + const ModuleClass = await loadFn(); + + // Required methods from DRSExerciseInterface + const requiredMethods = [ + // Lifecycle + 'init', + 'render', + 'destroy', + // Exercise logic + 'validate', + 'getResults', + 'handleUserInput', + // Progress tracking + 'markCompleted', + 'getProgress', + // Metadata + 'getExerciseType', + 'getExerciseConfig' + ]; + + const missingMethods = []; + + // Check prototype for methods + requiredMethods.forEach(method => { + if (typeof ModuleClass.prototype[method] !== 'function') { + missingMethods.push(method); + } + }); + + if (missingMethods.length > 0) { + throw new Error( + `${moduleName} is missing DRSExerciseInterface methods: ${missingMethods.join(', ')}` + ); + } + + console.log(`✅ ${moduleName} - OK`); + return true; + } catch (error) { + console.error(`❌ ${moduleName} - FAILED:`, error.message); + throw error; + } + } } export default ImplementationValidator;