Major Changes: - Moved legacy system to Legacy/ folder for archival - Built new modular architecture with strict separation of concerns - Created core system: Module, EventBus, ModuleLoader, Router - Added Application bootstrap with auto-start functionality - Implemented development server with ES6 modules support - Created comprehensive documentation and project context - Converted SBS-7-8 content to JSON format - Copied all legacy games and content to new structure New Architecture Features: - Sealed modules with WeakMap private data - Strict dependency injection system - Event-driven communication only - Inviolable responsibility patterns - Auto-initialization without commands - Component-based UI foundation ready Technical Stack: - Vanilla JS/HTML/CSS only - ES6 modules with proper imports/exports - HTTP development server (no file:// protocol) - Modular CSS with component scoping - Comprehensive error handling and debugging Ready for Phase 2: Converting legacy modules to new architecture 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
393 lines
14 KiB
JavaScript
393 lines
14 KiB
JavaScript
// === VALIDATEUR ET CONVERTISSEUR ULTRA-MODULAIRE ===
|
|
// Utilitaire pour valider et convertir les spécifications JSON ultra-modulaires
|
|
|
|
class UltraModularValidator {
|
|
constructor() {
|
|
this.jsonLoader = new JSONContentLoader();
|
|
this.contentScanner = new ContentScanner();
|
|
this.validationRules = this.initValidationRules();
|
|
}
|
|
|
|
initValidationRules() {
|
|
return {
|
|
// Champs obligatoires
|
|
required: ['id', 'name'],
|
|
|
|
// Champs recommandés pour une expérience optimale
|
|
recommended: ['description', 'difficulty_level', 'original_lang', 'user_lang', 'tags'],
|
|
|
|
// Types de contenu supportés
|
|
contentTypes: ['vocabulary', 'sentences', 'grammar', 'audio', 'dialogues', 'poems', 'culture', 'matching', 'fillInBlanks', 'corrections', 'comprehension'],
|
|
|
|
// Niveaux de difficulté valides
|
|
difficultyLevels: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
|
|
|
|
// Langues supportées
|
|
supportedLanguages: ['english', 'french', 'spanish', 'german', 'chinese', 'japanese']
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Valide une spécification JSON ultra-modulaire
|
|
*/
|
|
async validateSpecification(jsonContent) {
|
|
const validation = {
|
|
valid: true,
|
|
errors: [],
|
|
warnings: [],
|
|
suggestions: [],
|
|
score: 0,
|
|
capabilities: null,
|
|
compatibility: null
|
|
};
|
|
|
|
try {
|
|
// 1. Validation de structure de base
|
|
this.validateBasicStructure(jsonContent, validation);
|
|
|
|
// 2. Validation des métadonnées
|
|
this.validateMetadata(jsonContent, validation);
|
|
|
|
// 3. Validation du contenu éducatif
|
|
this.validateEducationalContent(jsonContent, validation);
|
|
|
|
// 4. Analyse des capacités
|
|
const capabilities = this.contentScanner.analyzeContentCapabilities(jsonContent);
|
|
validation.capabilities = capabilities;
|
|
|
|
// 5. Analyse de compatibilité des jeux
|
|
const compatibility = this.contentScanner.calculateGameCompatibility(capabilities);
|
|
validation.compatibility = compatibility;
|
|
|
|
// 6. Calcul du score de qualité
|
|
validation.score = this.calculateQualityScore(jsonContent, capabilities, compatibility);
|
|
|
|
// 7. Suggestions d'amélioration
|
|
this.generateSuggestions(jsonContent, capabilities, validation);
|
|
|
|
} catch (error) {
|
|
validation.valid = false;
|
|
validation.errors.push(`Erreur fatale de validation: ${error.message}`);
|
|
}
|
|
|
|
return validation;
|
|
}
|
|
|
|
/**
|
|
* Valide la structure de base du JSON
|
|
*/
|
|
validateBasicStructure(jsonContent, validation) {
|
|
// Vérifier les champs obligatoires
|
|
for (const field of this.validationRules.required) {
|
|
if (!jsonContent[field]) {
|
|
validation.errors.push(`Champ obligatoire manquant: ${field}`);
|
|
validation.valid = false;
|
|
}
|
|
}
|
|
|
|
// Vérifier les champs recommandés
|
|
for (const field of this.validationRules.recommended) {
|
|
if (!jsonContent[field]) {
|
|
validation.warnings.push(`Champ recommandé manquant: ${field}`);
|
|
}
|
|
}
|
|
|
|
// Valider l'ID
|
|
if (jsonContent.id && !/^[a-z0-9_]+$/.test(jsonContent.id)) {
|
|
validation.errors.push('ID invalide: utilisez uniquement lettres minuscules, chiffres et underscores');
|
|
validation.valid = false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Valide les métadonnées
|
|
*/
|
|
validateMetadata(jsonContent, validation) {
|
|
// Valider le niveau de difficulté
|
|
if (jsonContent.difficulty_level && !this.validationRules.difficultyLevels.includes(jsonContent.difficulty_level)) {
|
|
validation.errors.push(`Niveau de difficulté invalide: ${jsonContent.difficulty_level}. Utilisez 1-10`);
|
|
validation.valid = false;
|
|
}
|
|
|
|
// Valider les langues
|
|
if (jsonContent.original_lang && !this.validationRules.supportedLanguages.includes(jsonContent.original_lang)) {
|
|
validation.warnings.push(`Langue d'origine non standard: ${jsonContent.original_lang}`);
|
|
}
|
|
|
|
if (jsonContent.user_lang && !this.validationRules.supportedLanguages.includes(jsonContent.user_lang)) {
|
|
validation.warnings.push(`Langue utilisateur non standard: ${jsonContent.user_lang}`);
|
|
}
|
|
|
|
// Valider les tags
|
|
if (jsonContent.tags && !Array.isArray(jsonContent.tags)) {
|
|
validation.errors.push('Les tags doivent être un tableau');
|
|
validation.valid = false;
|
|
}
|
|
|
|
// Valider les compétences couvertes
|
|
if (jsonContent.skills_covered && !Array.isArray(jsonContent.skills_covered)) {
|
|
validation.errors.push('Les compétences couvertes doivent être un tableau');
|
|
validation.valid = false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Valide le contenu éducatif
|
|
*/
|
|
validateEducationalContent(jsonContent, validation) {
|
|
let contentFound = false;
|
|
|
|
// Vérifier qu'il y a au moins un type de contenu
|
|
for (const contentType of this.validationRules.contentTypes) {
|
|
if (jsonContent[contentType]) {
|
|
contentFound = true;
|
|
this.validateContentSection(contentType, jsonContent[contentType], validation);
|
|
}
|
|
}
|
|
|
|
if (!contentFound) {
|
|
validation.errors.push('Aucun contenu éducatif trouvé');
|
|
validation.valid = false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Valide une section de contenu spécifique
|
|
*/
|
|
validateContentSection(contentType, content, validation) {
|
|
switch (contentType) {
|
|
case 'vocabulary':
|
|
this.validateVocabulary(content, validation);
|
|
break;
|
|
case 'matching':
|
|
this.validateMatching(content, validation);
|
|
break;
|
|
case 'fillInBlanks':
|
|
this.validateFillInBlanks(content, validation);
|
|
break;
|
|
// ... autres validations spécifiques
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Valide la section vocabulaire (6 niveaux de complexité)
|
|
*/
|
|
validateVocabulary(vocabulary, validation) {
|
|
if (typeof vocabulary !== 'object') {
|
|
validation.errors.push('Le vocabulaire doit être un objet');
|
|
return;
|
|
}
|
|
|
|
const entries = Object.entries(vocabulary);
|
|
if (entries.length === 0) {
|
|
validation.warnings.push('Section vocabulaire vide');
|
|
return;
|
|
}
|
|
|
|
let maxDepth = 0;
|
|
for (const [word, definition] of entries) {
|
|
if (typeof definition === 'string') {
|
|
maxDepth = Math.max(maxDepth, 1);
|
|
} else if (typeof definition === 'object') {
|
|
maxDepth = Math.max(maxDepth, 2);
|
|
|
|
// Détecter les niveaux avancés
|
|
if (definition.examples || definition.grammar_notes) maxDepth = Math.max(maxDepth, 3);
|
|
if (definition.etymology || definition.word_family) maxDepth = Math.max(maxDepth, 4);
|
|
if (definition.cultural_significance) maxDepth = Math.max(maxDepth, 5);
|
|
if (definition.memory_techniques || definition.visual_associations) maxDepth = Math.max(maxDepth, 6);
|
|
} else {
|
|
validation.warnings.push(`Définition invalide pour "${word}": doit être une chaîne ou un objet`);
|
|
}
|
|
}
|
|
|
|
validation.suggestions.push(`Niveau de vocabulaire détecté: ${maxDepth}/6 - ${this.getVocabularyLevelDescription(maxDepth)}`);
|
|
}
|
|
|
|
/**
|
|
* Valide le système de matching multi-colonnes
|
|
*/
|
|
validateMatching(matching, validation) {
|
|
if (!Array.isArray(matching)) {
|
|
validation.errors.push('La section matching doit être un tableau');
|
|
return;
|
|
}
|
|
|
|
for (const exercise of matching) {
|
|
if (exercise.columns && Array.isArray(exercise.columns)) {
|
|
// Nouveau format multi-colonnes
|
|
if (exercise.columns.length < 2) {
|
|
validation.warnings.push('Exercise matching: au moins 2 colonnes recommandées');
|
|
}
|
|
|
|
if (!exercise.correct_matches || !Array.isArray(exercise.correct_matches)) {
|
|
validation.errors.push('Exercise matching: correct_matches requis pour format multi-colonnes');
|
|
}
|
|
} else if (exercise.leftColumn && exercise.rightColumn) {
|
|
// Format traditionnel
|
|
if (exercise.leftColumn.length !== exercise.rightColumn.length) {
|
|
validation.warnings.push('Exercise matching: colonnes de tailles différentes');
|
|
}
|
|
} else {
|
|
validation.errors.push('Exercise matching: format invalide (colonnes manquantes)');
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Valide les exercices à trous
|
|
*/
|
|
validateFillInBlanks(fillInBlanks, validation) {
|
|
if (!Array.isArray(fillInBlanks)) {
|
|
validation.errors.push('La section fillInBlanks doit être un tableau');
|
|
return;
|
|
}
|
|
|
|
for (const exercise of fillInBlanks) {
|
|
if (!exercise.sentence) {
|
|
validation.errors.push('Exercise fillInBlanks: sentence requis');
|
|
continue;
|
|
}
|
|
|
|
if (!exercise.sentence.includes('___')) {
|
|
validation.warnings.push('Exercise fillInBlanks: aucun blank (___) trouvé dans la phrase');
|
|
}
|
|
|
|
if (exercise.type === 'open_ended') {
|
|
if (!exercise.aiPrompt && !exercise.acceptedAnswers) {
|
|
validation.errors.push('Exercise fillInBlanks open_ended: aiPrompt ou acceptedAnswers requis');
|
|
}
|
|
} else {
|
|
if (!exercise.options || !exercise.correctAnswer) {
|
|
validation.errors.push('Exercise fillInBlanks: options et correctAnswer requis');
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Calcule un score de qualité (0-100)
|
|
*/
|
|
calculateQualityScore(jsonContent, capabilities, compatibility) {
|
|
let score = 0;
|
|
|
|
// Score de base (métadonnées complètes)
|
|
if (jsonContent.id) score += 5;
|
|
if (jsonContent.name) score += 5;
|
|
if (jsonContent.description) score += 5;
|
|
if (jsonContent.difficulty_level) score += 5;
|
|
if (jsonContent.original_lang && jsonContent.user_lang) score += 10;
|
|
if (jsonContent.tags && jsonContent.tags.length > 0) score += 5;
|
|
if (jsonContent.skills_covered && jsonContent.skills_covered.length > 0) score += 5;
|
|
|
|
// Score de contenu éducatif (40 points max)
|
|
if (capabilities.hasVocabulary) score += 10;
|
|
if (capabilities.hasGrammar) score += 5;
|
|
if (capabilities.hasSentences) score += 5;
|
|
if (capabilities.hasExercises) score += 10;
|
|
if (capabilities.hasAudioFiles) score += 5;
|
|
if (capabilities.hasCulture) score += 5;
|
|
|
|
// Bonus pour la richesse du contenu
|
|
score += Math.min(15, capabilities.vocabularyDepth * 2.5);
|
|
score += Math.min(10, capabilities.contentRichness);
|
|
|
|
// Bonus pour la compatibilité des jeux
|
|
const compatibleGames = Object.values(compatibility).filter(game => game.compatible).length;
|
|
score += Math.min(10, compatibleGames * 2);
|
|
|
|
return Math.round(Math.min(100, score));
|
|
}
|
|
|
|
/**
|
|
* Génère des suggestions d'amélioration
|
|
*/
|
|
generateSuggestions(jsonContent, capabilities, validation) {
|
|
// Suggestions basées sur les capacités manquantes
|
|
if (!capabilities.hasAudioFiles) {
|
|
validation.suggestions.push('Ajoutez des fichiers audio pour débloquer les jeux de prononciation');
|
|
}
|
|
|
|
if (!capabilities.hasExercises) {
|
|
validation.suggestions.push('Ajoutez des exercices (fillInBlanks, corrections) pour plus d\'interactivité');
|
|
}
|
|
|
|
if (!capabilities.hasCulture) {
|
|
validation.suggestions.push('Ajoutez du contenu culturel pour enrichir l\'apprentissage');
|
|
}
|
|
|
|
if (capabilities.vocabularyDepth < 3) {
|
|
validation.suggestions.push('Enrichissez le vocabulaire avec des exemples et notes grammaticales');
|
|
}
|
|
|
|
// Suggestions basées sur la compatibilité des jeux
|
|
const incompatibleGames = Object.entries(validation.compatibility)
|
|
.filter(([game, compat]) => !compat.compatible)
|
|
.map(([game]) => game);
|
|
|
|
if (incompatibleGames.length > 0) {
|
|
validation.suggestions.push(`Jeux non compatibles: ${incompatibleGames.join(', ')} - vérifiez les requis de contenu`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Convertit une spécification ultra-modulaire vers le format legacy
|
|
*/
|
|
async convertToLegacy(jsonContent) {
|
|
const adaptedContent = this.jsonLoader.adapt(jsonContent);
|
|
|
|
// Générer un module JavaScript compatible
|
|
const jsModule = this.generateLegacyModule(adaptedContent);
|
|
|
|
return {
|
|
adaptedContent,
|
|
jsModule,
|
|
stats: this.jsonLoader.analyzeContent(adaptedContent)
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Génère un module JavaScript pour compatibilité legacy
|
|
*/
|
|
generateLegacyModule(adaptedContent) {
|
|
const moduleName = this.toPascalCase(adaptedContent.id);
|
|
|
|
return `// Module généré automatiquement depuis la spécification ultra-modulaire
|
|
// ID: ${adaptedContent.id}
|
|
// Généré le: ${new Date().toISOString()}
|
|
|
|
window.ContentModules = window.ContentModules || {};
|
|
window.ContentModules.${moduleName} = ${JSON.stringify(adaptedContent, null, 4)};
|
|
|
|
console.log('📦 Module ${moduleName} chargé depuis spécification ultra-modulaire');`;
|
|
}
|
|
|
|
/**
|
|
* Helpers
|
|
*/
|
|
getVocabularyLevelDescription(level) {
|
|
const descriptions = {
|
|
1: 'Basique (chaînes simples)',
|
|
2: 'Standard (objets avec traduction)',
|
|
3: 'Enrichi (avec exemples)',
|
|
4: 'Avancé (avec étymologie)',
|
|
5: 'Expert (avec contexte culturel)',
|
|
6: 'Maître (avec techniques mnémotechniques)'
|
|
};
|
|
return descriptions[level] || 'Niveau inconnu';
|
|
}
|
|
|
|
toPascalCase(str) {
|
|
return str.split(/[_-]/).map(word =>
|
|
word.charAt(0).toUpperCase() + word.slice(1)
|
|
).join('');
|
|
}
|
|
}
|
|
|
|
// Export global
|
|
window.UltraModularValidator = UltraModularValidator;
|
|
|
|
// Export Node.js (optionnel)
|
|
if (typeof module !== 'undefined' && module.exports) {
|
|
module.exports = UltraModularValidator;
|
|
} |