#!/usr/bin/env node /** * Repariert die alten Bisaya-Lektionen "Gefühle & Emotionen". * * Nutzung: * node backend/scripts/repair-bisaya-feelings-emotions-lessons.js * * Behebt: * - alte Platzhalter-Prüfungen mit "Option A/B/C/D" * - falsch gesplittete Glossare wie "Bist du besorgt" / "traurig?" * - fehlende Prüfungen in bestehenden Bisaya-Kursen */ 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: 'Gefühle im Alltag', description: 'Gefühle ausdrücken und im Gespräch passend darauf reagieren', learningGoals: [ 'Eigene Gefühle mit kurzen, natürlichen Sätzen ausdrücken.', 'Auf Gefühle einer nahen Person passend reagieren.', 'Nähe und Alltag nicht isoliert, sondern im Gespräch verbinden.' ], corePatterns: [ { target: 'Nalipay ko karon.', gloss: 'Ich bin heute froh.' }, { target: 'Naguol ka?', gloss: 'Bist du besorgt oder traurig?' }, { target: 'Kapoy ko karon.', gloss: 'Ich bin jetzt müde.' }, { target: 'Gimingaw ko nimo.', gloss: 'Ich vermisse dich.' }, { target: 'Naa ra ko diri.', gloss: 'Ich bin hier.' }, { target: 'Ayaw kabalaka.', gloss: 'Mach dir keine Sorgen.' }, { target: 'Okay ra ko.', gloss: 'Mir geht es okay.' }, { target: 'Nalipay ko nga nakita ka.', gloss: 'Ich freue mich, dich zu sehen.' } ], grammarFocus: [ { title: 'Gefühl + ko / ka', text: 'Viele Gefühlsaussagen nutzen ein Gefühlswort oder einen Zustand direkt mit ko oder ka.', example: 'Nalipay ko. Naguol ka?' }, { title: 'Fürsorglich reagieren', text: 'Nach einem Gefühl folgt im Alltag oft eine kurze beruhigende Antwort.', example: 'Ayaw kabalaka. Naa ra ko diri.' } ], speakingPrompts: [ { title: 'Gefühl plus Fürsorge', prompt: 'Sage dein Gefühl, frage nach dem Gefühl der anderen Person und reagiere fürsorglich.', cue: 'Kapoy ko karon. Naguol ka? Naa ra ko diri.' } ], practicalTasks: [ { title: 'Gefühlsantwort', text: 'Wähle drei Gefühle und bilde zu jedem eine kurze Antwort, die du einer nahen Person sagen könntest.' } ], exercises: [ { exerciseTypeId: 2, title: 'Gefühl erkennen', instruction: 'Wähle die richtige Übersetzung.', questionData: { type: 'multiple_choice', question: 'Was bedeutet "Nalipay ko karon."?', options: [ 'Ich bin heute froh.', 'Ich bin jetzt müde.', 'Ich bin hier.', 'Ich vermisse dich.' ] }, answerData: { type: 'multiple_choice', correctAnswer: 0 }, explanation: '"Nalipay ko karon" bedeutet "Ich bin heute froh."' }, { exerciseTypeId: 1, title: 'Gefühlssatz vervollständigen', instruction: 'Fülle die Lücke mit dem passenden Bisaya-Wort.', questionData: { type: 'gap_fill', text: 'Naguol {gap}? (Bist du besorgt oder traurig?)', gaps: 1 }, answerData: { type: 'gap_fill', answers: ['ka'] }, explanation: 'Mit "ka" fragst du nach dem Zustand der anderen Person.' }, { exerciseTypeId: 4, title: 'Fürsorglich übersetzen', instruction: 'Übersetze ins Bisaya.', questionData: { type: 'transformation', text: 'Mach dir keine Sorgen. Ich bin hier.', sourceLanguage: 'Deutsch', targetLanguage: 'Bisaya' }, answerData: { type: 'transformation', correct: 'Ayaw kabalaka. Naa ra ko diri.', alternatives: ['Ayaw kabalaka, naa ra ko diri.', 'Ayaw kabalaka. Ania ra ko diri.'] }, explanation: '"Ayaw kabalaka" beruhigt, "Naa ra ko diri" bietet Nähe an.' }, { exerciseTypeId: 3, title: 'Kurze Antwort bauen', instruction: 'Ordne die Satzteile zu einer natürlichen Antwort.', questionData: { type: 'sentence_building', question: 'Baue: Ich bin müde. Bist du traurig? Ich bin hier.', tokens: ['Kapoy ko karon.', 'Naguol ka?', 'Naa ra ko diri.'] }, answerData: { correct: ['Kapoy ko karon. Naguol ka? Naa ra ko diri.'] }, explanation: 'Kurze Sätze wirken in emotionalen Situationen natürlicher.' }, { exerciseTypeId: 10, title: 'Passend reagieren', instruction: 'Antworte kurz und fürsorglich.', questionData: { type: 'situational_response', question: 'Eine nahestehende Person sagt, dass sie besorgt ist. Reagiere beruhigend.', keywords: ['ayaw', 'kabalaka', 'naa', 'diri'] }, answerData: { modelAnswer: 'Ayaw kabalaka. Naa ra ko diri.', keywords: ['ayaw', 'kabalaka', 'naa', 'diri'] }, explanation: 'Die Antwort verbindet Beruhigung mit Anwesenheit.' } ] }; const VOCAB_PROFILE = { title: 'Gefühlswortschatz & Reaktionen', description: 'Gefühlswörter und kurze Reaktionen sicher verwenden', learningGoals: [ 'Zentrale Gefühlswörter und kurze Reaktionen sicher erkennen.', 'Zwischen Sorge, Freude, Müdigkeit und Vermissen unterscheiden.', 'Zu einfachen Situationen eine passende Reaktion auswählen.' ], corePatterns: [ { target: 'lipay', gloss: 'froh / glücklich' }, { target: 'guol', gloss: 'besorgt / traurig' }, { target: 'kapoy', gloss: 'müde' }, { target: 'mingaw', gloss: 'vermissend / einsam' }, { target: 'kabalaka', gloss: 'Sorge' }, { target: 'hilom', gloss: 'still / ruhig' }, { target: 'Nalipay ko para nimo.', gloss: 'Ich freue mich für dich.' }, { target: 'Amping kanunay.', gloss: 'Pass immer auf dich auf.' } ], grammarFocus: [ { title: 'Wort zu Satz', text: 'Aus einem Gefühlswort wird mit ko oder ka schnell ein alltagstauglicher Satz.', example: 'Kapoy ko. Naguol ka?' } ], speakingPrompts: [ { title: 'Passend reagieren', prompt: 'Reagiere auf Sorge, Müdigkeit und Vermissen mit je einem kurzen Satz.', cue: 'Ayaw kabalaka. Magpahuway sa. Gimingaw ko nimo.' } ], practicalTasks: [ { title: 'Situationskarten', text: 'Ordne lipay, guol, kapoy und mingaw vier Alltagssituationen zu und sprich eine passende Reaktion.' } ], exercises: [ { exerciseTypeId: 2, title: 'Gefühlswort erkennen', instruction: 'Wähle die richtige Bedeutung.', questionData: { type: 'multiple_choice', question: 'Was bedeutet "kapoy"?', options: ['müde', 'froh', 'Sorge', 'ruhig'] }, answerData: { type: 'multiple_choice', correctAnswer: 0 }, explanation: '"kapoy" bedeutet "müde".' }, { exerciseTypeId: 2, title: 'Reaktion auswählen', instruction: 'Wähle die passende Reaktion.', questionData: { type: 'multiple_choice', question: 'Welche Antwort passt zu Sorge?', options: ['Ayaw kabalaka.', 'Tagpila ni?', 'Asa ang kusina?', 'Maayong buntag.'] }, answerData: { type: 'multiple_choice', correctAnswer: 0 }, explanation: '"Ayaw kabalaka" bedeutet "Mach dir keine Sorgen."' }, { exerciseTypeId: 1, title: 'Satz aus Gefühlswort', instruction: 'Fülle die Lücke.', questionData: { type: 'gap_fill', text: 'Kapoy {gap}. (Ich bin müde.)', gaps: 1 }, answerData: { type: 'gap_fill', answers: ['ko'] }, explanation: 'Mit "ko" sprichst du über dich selbst.' }, { exerciseTypeId: 4, title: 'Freude ausdrücken', instruction: 'Übersetze ins Bisaya.', questionData: { type: 'transformation', text: 'Ich freue mich für dich.', sourceLanguage: 'Deutsch', targetLanguage: 'Bisaya' }, answerData: { type: 'transformation', correct: 'Nalipay ko para nimo.', alternatives: ['Nalipay ko alang nimo.'] }, explanation: '"para nimo" bindet die Freude an die andere Person.' } ] }; 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())); } function profileForLesson(lesson) { const type = String(lesson.lessonType || '').toLowerCase(); if (type === 'conversation') return CONVERSATION_PROFILE; if (type === 'vocab') return VOCAB_PROFILE; const num = Number(lesson.lessonNumber); return num === 22 || num === 25 ? VOCAB_PROFILE : CONVERSATION_PROFILE; } async function repairLesson(lesson, ownerUserId, { force = false } = {}) { const profile = profileForLesson(lesson); 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, grammarFocus: profile.grammarFocus, 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.title = 'Gefühle & Emotionen' AND l.lesson_type IN ('conversation', 'vocab') ORDER BY c.id, l.lesson_number`, { replacements: { languageId: bisayaLanguage.id }, type: sequelize.QueryTypes.SELECT } ); console.log(`Gefundene Bisaya-Emotionslektionen: ${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); });