#!/usr/bin/env node /** * Repariert alte/platzhalterhafte Prüfungen für Kinder‑Lektionen (z.B. Lektion 28). * * Nutzung: * node backend/scripts/repair-bisaya-children-lessons.js * * Optional: * VOCAB_FORCE_REBUILD_ALL=1 node backend/scripts/repair-bisaya-children-lessons.js * * Das Script sucht in der DB nach Kurslektionen mit lesson_number=28 oder * mit typischen alten Titeln und ersetzt Platzhalter‑Exercises ("Option A/B/C/D") * durch didaktisch passende Übungen. */ import { sequelize } from '../utils/sequelize.js'; import VocabCourseLesson from '../models/community/vocab_course_lesson.js'; import VocabGrammarExercise from '../models/community/vocab_grammar_exercise.js'; const CONVERSATION_PROFILE = { title: 'Kinder, Spiel & Routine', description: 'Kinderalltag: kurze Anweisungen, Routinen und spielerische Aufforderungen', learningGoals: [ 'Kurze Aufforderungen und Routinen klar und freundlich formulieren', 'Wichtige Alltagswörter (Tasche, Hände, Schlaf) sicher abrufen', 'Routinen in der richtigen Reihenfolge ausdrücken' ], corePatterns: [ { target: 'Hugas sa kamot.', gloss: 'Wasch dir die Hände.' }, { target: 'Kuhaa imong bag.', gloss: 'Hol deine Tasche.' }, { target: 'Matulog na ta.', gloss: 'Jetzt schlafen wir.' }, { target: 'Patya ang suga.', gloss: 'Mach das Licht aus.' } ], speakingPrompts: [ { title: 'Morgenroutine', prompt: 'Sprich eine kurze Morgenroutine: Tasche holen, Hände waschen, Essen.', cue: 'Kuhaa imong bag. Hugas sa kamot. Nikaon na ka?' } ], practicalTasks: [ { title: 'Routinekette', text: 'Bilde eine Abfolge von vier kurzen Handlungsanweisungen für Kinder.' } ], exercises: [ { exerciseTypeId: 2, title: 'Routinebefehl erkennen', instruction: 'Wähle die richtige Bedeutung.', questionData: { type: 'multiple_choice', question: 'Was bedeutet "Hugas sa kamot"?', options: ['Wasch dir die Hände.', 'Hol deine Tasche.', 'Geh schlafen.', 'Iss zuerst.'] }, answerData: { type: 'multiple_choice', correctAnswer: 0 }, explanation: '"Hugas sa kamot" ist eine typische Aufforderung zur Hygiene.' }, { exerciseTypeId: 1, title: 'Lücken: Morgenroutine', instruction: 'Fülle die Lücken.', questionData: { type: 'gap_fill', text: 'Kuhaa imong {gap}. Hugas sa {gap}.', gaps: 2 }, answerData: { type: 'gap_fill', answers: ['bag', 'kamot'] }, explanation: 'Tasche und Hände sind Kernwörter in Morgenroutinen.' }, { exerciseTypeId: 3, title: 'Routine reihenfolge', instruction: 'Ordne die Sätze zu einer natürlichen Reihenfolge.', questionData: { type: 'sentence_building', question: 'Ordne: Hände waschen, Tasche holen, Licht aus, Schlafen.', tokens: ['Kuhaa imong bag.', 'Hugas sa kamot.', 'Patya ang suga.', 'Matulog na ta.'] }, answerData: { correct: ['Kuhaa imong bag. Hugas sa kamot. Patya ang suga. Matulog na ta.'] }, explanation: 'Typische Abfolge für Abend-/Morgenroutinen.' }, { exerciseTypeId: 10, title: 'Kind-Ansage', instruction: 'Gib eine kurze, freundliche Anweisung.', questionData: { type: 'situational_response', question: 'Du möchtest, dass ein Kind seine Tasche holt und sich die Hände wäscht.', keywords: ['kuhaa', 'bag', 'hugas', 'kamot'] }, answerData: { modelAnswer: 'Kuhaa imong bag. Hugas sa kamot.', keywords: ['kuhaa', 'bag', 'hugas', 'kamot'] }, explanation: 'Kurze klare Sätze funktionieren bei Kindern am besten.' } ] }; function isPlaceholderExercise(exercise) { const q = exercise?.questionData || {}; const questionData = typeof q === 'string' ? JSON.parse(q) : q; return Array.isArray(questionData?.options) && questionData.options.some((option) => /^Option [A-D]$/i.test(String(option || '').trim())); } async function repairLesson(lesson, ownerUserId, { force = false } = {}) { const profile = CONVERSATION_PROFILE; const existing = await VocabGrammarExercise.findAll({ where: { lessonId: lesson.id }, order: [['exerciseNumber', 'ASC']] }); const hasPlaceholder = existing.some((exercise) => isPlaceholderExercise(exercise)); const shouldReplaceExercises = force || hasPlaceholder || existing.length < profile.exercises.length; await lesson.update({ title: profile.title, description: profile.description, learningGoals: profile.learningGoals, corePatterns: profile.corePatterns, speakingPrompts: profile.speakingPrompts, practicalTasks: profile.practicalTasks, targetScorePercent: Math.max(Number(lesson.targetScorePercent) || 0, 80) }); if (!shouldReplaceExercises) return { updated: true, exercisesReplaced: false, exerciseCount: existing.length }; await VocabGrammarExercise.destroy({ where: { lessonId: lesson.id } }); let exerciseNumber = 1; for (const exercise of profile.exercises) { await VocabGrammarExercise.create({ lessonId: lesson.id, exerciseTypeId: exercise.exerciseTypeId, exerciseNumber, title: exercise.title, instruction: exercise.instruction, questionData: exercise.questionData, answerData: exercise.answerData, explanation: exercise.explanation, createdByUserId: ownerUserId }); exerciseNumber += 1; } return { updated: true, exercisesReplaced: true, exerciseCount: profile.exercises.length }; } async function main() { await sequelize.authenticate(); const [bisayaLanguage] = await sequelize.query( `SELECT id FROM community.vocab_language WHERE name = 'Bisaya' LIMIT 1`, { type: sequelize.QueryTypes.SELECT } ); if (!bisayaLanguage) throw new Error('Bisaya language not found'); const rows = await sequelize.query( `SELECT l.id, l.course_id, l.lesson_number, l.title, l.lesson_type, c.owner_user_id FROM community.vocab_course_lesson l JOIN community.vocab_course c ON c.id = l.course_id WHERE c.language_id = :languageId AND (l.lesson_number = 28 OR l.title IN ('Kinder & Spiel', 'Kinder, Spiel & Routine', 'Kinder & Familie')) ORDER BY c.id, l.lesson_number`, { replacements: { languageId: bisayaLanguage.id }, type: sequelize.QueryTypes.SELECT } ); console.log(`Gefundene Kinderlektionen: ${rows.length}`); let replaced = 0; for (const row of rows) { const lesson = await VocabCourseLesson.findByPk(row.id); const result = await repairLesson(lesson, row.owner_user_id, { force: process.env.VOCAB_FORCE_REBUILD_ALL === '1' }); if (result.exercisesReplaced) replaced += 1; console.log(`✅ Kurs ${row.course_id}, Lektion ${row.lesson_number}: ${lesson.title} (${result.exerciseCount} Prüfung(en))`); } console.log(`Fertig. Aktualisiert: ${rows.length}, Prüfungen ersetzt/erstellt: ${replaced}`); } main() .then(async () => { await sequelize.close(); }) .catch(async (error) => { console.error('❌ Reparatur fehlgeschlagen:', error); await sequelize.close(); process.exit(1); });