diff --git a/backend/scripts/create-bisaya-course-content.js b/backend/scripts/create-bisaya-course-content.js index 17da6a3..0fb0823 100644 --- a/backend/scripts/create-bisaya-course-content.js +++ b/backend/scripts/create-bisaya-course-content.js @@ -820,21 +820,7 @@ const BISAYA_EXERCISES = { }, explanation: '"Kumusta ka?" ist die Standard-Begrüßung auf Bisaya, ähnlich wie "Wie geht es dir?"' }, - { - exerciseTypeId: 1, // gap_fill - title: 'Begrüßungen vervollständigen', - instruction: 'Fülle die Lücken mit den richtigen Bisaya-Wörtern.', - questionData: { - type: 'gap_fill', - text: 'Kumusta ka? Maayo {gap} (ich). Salamat.', - gaps: 1 - }, - answerData: { - type: 'gap_fill', - answers: ['ko'] - }, - explanation: '"Maayo ko" bedeutet "Mir geht es gut". "ko" ist "ich" auf Bisaya.' - }, + { exerciseTypeId: 2, // multiple_choice title: 'Was bedeutet "Salamat"?', @@ -3064,7 +3050,7 @@ const BISAYA_EXERCISES = { instruction: 'Fülle die Lücken mit den richtigen Bisaya-Wörtern.', questionData: { type: 'gap_fill', - text: 'Unsa imong ginabuhat? (Was machst du?) - {gap} ko. (Ich arbeite.) | Kapoy {gap}. (Ich bin müde.)', + text: 'Unsa imong ginabuhat? (Was machst du?) - Nagtrabaho ko. (Ich arbeite.) | Kapoy ko. (Ich bin müde.)', gaps: 2 }, answerData: { diff --git a/backend/scripts/repair-bisaya-feelings-emotions-lessons.js b/backend/scripts/repair-bisaya-feelings-emotions-lessons.js new file mode 100644 index 0000000..b0a28bd --- /dev/null +++ b/backend/scripts/repair-bisaya-feelings-emotions-lessons.js @@ -0,0 +1,343 @@ +#!/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); + }); diff --git a/backend/scripts/update-family-conversations-exercises.js b/backend/scripts/update-family-conversations-exercises.js index 9f6df9a..4a33db0 100755 --- a/backend/scripts/update-family-conversations-exercises.js +++ b/backend/scripts/update-family-conversations-exercises.js @@ -55,7 +55,7 @@ const FAMILY_CONVERSATIONS = { { bisaya: 'Gutom na ko, Nanay.', native: 'Ich bin hungrig, Mama.', - explanation: '"Gutom" bedeutet "hungrig", "na" zeigt einen Zustand, "ko" ist "ich"' + explanation: '"Gutom" bedeutet "hungrig", "na" zeigt einen Zustand, "ko" ist die angehängte Form von "ich" (unabhängiges Pronomen: "ako")' }, { bisaya: 'Hulata lang, hapit na ang pagkaon.', diff --git a/backend/scripts/update-feelings-affection-exercises.js b/backend/scripts/update-feelings-affection-exercises.js index 8a8388f..a332d13 100755 --- a/backend/scripts/update-feelings-affection-exercises.js +++ b/backend/scripts/update-feelings-affection-exercises.js @@ -25,7 +25,7 @@ const FEELINGS_AFFECTION = { { bisaya: 'Gihigugma ko ikaw.', native: 'Ich liebe dich.', - explanation: '"Gihigugma" bedeutet "lieben", "ko" ist "ich", "ikaw" ist "dich"' + explanation: '"Gihigugma" bedeutet "lieben", "ko" ist die angehängte Form von "ich" (unabhängiges Pronomen: "ako"), "ikaw" ist "dich"' }, { bisaya: 'Nahigugma ko nimo.', @@ -35,27 +35,27 @@ const FEELINGS_AFFECTION = { { bisaya: 'Ganahan ko nimo.', native: 'Ich mag dich.', - explanation: '"Ganahan" bedeutet "mögen/gefallen", "ko" ist "ich", "nimo" ist "dich"' + explanation: '"Ganahan" bedeutet "mögen/gefallen", "ko" ist die angehängte Form von "ich" (unabhängiges Pronomen: "ako"), "nimo" ist "dich"' }, { bisaya: 'Nalipay ko nga nakita ka.', native: 'Ich bin glücklich, dich zu sehen.', - explanation: '"Nalipay" bedeutet "glücklich", "ko" ist "ich", "nga nakita ka" ist "dich zu sehen"' + explanation: '"Nalipay" bedeutet "glücklich", "ko" ist die angehängte Form von "ich" (unabhängiges Pronomen: "ako"), "nga nakita ka" ist "dich zu sehen"' }, { bisaya: 'Gimingaw ko nimo.', native: 'Ich vermisse dich.', - explanation: '"Gimingaw" bedeutet "vermissen", "ko" ist "ich", "nimo" ist "dich"' + explanation: '"Gimingaw" bedeutet "vermissen", "ko" ist die angehängte Form von "ich" (unabhängiges Pronomen: "ako"), "nimo" ist "dich"' }, { bisaya: 'Nalipay ko.', native: 'Ich bin glücklich.', - explanation: '"Nalipay" bedeutet "glücklich", "ko" ist "ich"' + explanation: '"Nalipay" bedeutet "glücklich", "ko" ist die angehängte Form von "ich" (unabhängiges Pronomen: "ako")' }, { bisaya: 'Nasubo ko.', native: 'Ich bin traurig.', - explanation: '"Nasubo" bedeutet "traurig", "ko" ist "ich"' + explanation: '"Nasubo" bedeutet "traurig", "ko" ist die angehängte Form von "ich" (unabhängiges Pronomen: "ako")' }, { bisaya: 'Nalipay ko nga naa ka dinhi.', diff --git a/backend/scripts/update-food-care-exercises.js b/backend/scripts/update-food-care-exercises.js index c91d939..106ebc4 100755 --- a/backend/scripts/update-food-care-exercises.js +++ b/backend/scripts/update-food-care-exercises.js @@ -25,12 +25,12 @@ const FOOD_CARE_CONVERSATIONS = { { bisaya: 'Gutom na ko.', native: 'Ich habe Hunger.', - explanation: '"Gutom" bedeutet "Hunger", "na" ist "schon", "ko" ist "ich"' + explanation: '"Gutom" bedeutet "Hunger", "na" ist "schon", "ko" ist die angehängte Form von "ich" (unabhängiges Pronomen: "ako")' }, { bisaya: 'Gihikap ko.', native: 'Ich habe Durst.', - explanation: '"Gihikap" bedeutet "Durst haben", "ko" ist "ich"' + explanation: '"Gihikap" bedeutet "Durst haben", "ko" ist die angehängte Form von "ich" (unabhängiges Pronomen: "ako")' }, { bisaya: 'Gusto ka mokaon?', @@ -55,7 +55,7 @@ const FOOD_CARE_CONVERSATIONS = { { bisaya: 'Palihug, hatagi ko ug tubig.', native: 'Bitte gib mir Wasser.', - explanation: '"Palihug" ist "Bitte", "hatagi" ist "geben", "ko" ist "mir", "ug tubig" ist "Wasser"' + explanation: '"Palihug" ist "Bitte", "hatagi" ist "geben", "ko" ist die angehängte Form für "mir/ich" (unabhängiges Pronomen: "ako"), "ug tubig" ist "Wasser"' }, { bisaya: 'Salamat sa pagkaon.', @@ -70,7 +70,7 @@ const FOOD_CARE_CONVERSATIONS = { { bisaya: 'Busog na ko.', native: 'Ich bin satt.', - explanation: '"Busog" bedeutet "satt", "na" ist "schon", "ko" ist "ich"' + explanation: '"Busog" bedeutet "satt", "na" ist "schon", "ko" ist die angehängte Form von "ich" (unabhängiges Pronomen: "ako")' }, { bisaya: 'Kumusta ang pagkaon?', @@ -701,6 +701,35 @@ async function updateFoodCareExercises() { createdByUserId: course.owner_user_id || systemUser.id }); totalExercisesCreated++; + + // Gap-Fill: Erzeuge eine Lückentext-Übung, wenn das Bisaya die enclitische Form "ko" enthält + try { + if (vocab.bisaya && /\bko\b/.test(vocab.bisaya)) { + const gapText = vocab.bisaya.replace(/\bko\b/g, '{gap}'); + await VocabGrammarExercise.create({ + lessonId: lesson.id, + exerciseTypeId: 1, // gap_fill + exerciseNumber: exerciseNumber++, + title: `Lückentext: "${vocab.native}"`, + instruction: 'Fülle die Lücke mit dem passenden Wort.', + questionData: JSON.stringify({ + type: 'gap_fill', + text: gapText, + gaps: 1, + answerLanguage: 'target' + }), + answerData: JSON.stringify({ + type: 'gap_fill', + answers: ['ko'] + }), + explanation: vocab.explanation, + createdByUserId: course.owner_user_id || systemUser.id + }); + totalExercisesCreated++; + } + } catch (e) { + console.error('Fehler beim Erzeugen von gap_fill für Vokabel:', vocab, e); + } } for (let i = 0; i < n; i++) {