Add language assistant settings and related features: Introduce new routes and controller methods for managing language assistant settings, including retrieval and saving of LLM configurations. Update navigation structure to include language assistant options. Enhance vocab course model to support additional learning attributes such as learning goals and core patterns. Update SQL scripts to reflect new database schema changes for vocab courses. Improve localization for language assistant settings in German and English.

This commit is contained in:
Torsten Schulz (local)
2026-03-25 15:53:49 +01:00
parent 8af726c65a
commit d50d3c4016
40 changed files with 3145 additions and 56 deletions

View File

@@ -19,6 +19,19 @@ const LESSONS_TO_ADD = [
title: 'Woche 1 - Wiederholung',
description: 'Wiederhole alle Inhalte der ersten Woche',
culturalNotes: 'Wiederholung ist der Schlüssel zum Erfolg!',
learningGoals: [
'Die Kernmuster der ersten Woche ohne Hilfe wiederholen.',
'Zwischen Begrüßung, Familie und Fürsorge schneller wechseln.',
'Eine kurze Alltagssequenz frei sprechen.'
],
corePatterns: ['Kumusta ka?', 'Palangga taka.', 'Nikaon na ka?', 'Wala ko kasabot.'],
speakingPrompts: [
{
title: 'Freie Wiederholung',
prompt: 'Begrüße jemanden, drücke Zuneigung aus und frage fürsorglich nach dem Essen.',
cue: 'Kumusta ka? Palangga taka. Nikaon na ka?'
}
],
targetMinutes: 30,
targetScorePercent: 80,
requiresReview: false
@@ -31,6 +44,12 @@ const LESSONS_TO_ADD = [
title: 'Woche 1 - Vokabeltest',
description: 'Teste dein Wissen aus Woche 1',
culturalNotes: null,
learningGoals: [
'Die wichtigsten Wörter der ersten Woche schnell abrufen.',
'Bedeutung und Gebrauch zentraler Wörter unterscheiden.',
'Von einzelnen Wörtern zu kurzen Sätzen übergehen.'
],
corePatterns: ['Kumusta', 'Salamat', 'Lami', 'Mingaw ko nimo'],
targetMinutes: 15,
targetScorePercent: 80,
requiresReview: true
@@ -89,6 +108,9 @@ async function addBisayaWeek1Lessons() {
dayNumber: lessonData.dayNumber,
lessonType: lessonData.lessonType,
culturalNotes: lessonData.culturalNotes,
learningGoals: lessonData.learningGoals || [],
corePatterns: lessonData.corePatterns || [],
speakingPrompts: lessonData.speakingPrompts || [],
targetMinutes: lessonData.targetMinutes,
targetScorePercent: lessonData.targetScorePercent,
requiresReview: lessonData.requiresReview

View File

@@ -0,0 +1,199 @@
#!/usr/bin/env node
/**
* Spielt die überarbeiteten Bisaya-Kursinhalte ein und setzt den Lernfortschritt zurück.
*
* Verwendung:
* node backend/scripts/apply-bisaya-course-refresh.js
*/
import { sequelize } from '../utils/sequelize.js';
import VocabCourseLesson from '../models/community/vocab_course_lesson.js';
import VocabGrammarExercise from '../models/community/vocab_grammar_exercise.js';
import VocabCourseProgress from '../models/community/vocab_course_progress.js';
import VocabGrammarExerciseProgress from '../models/community/vocab_grammar_exercise_progress.js';
import { Op } from 'sequelize';
const LESSON_DIDACTICS = {
'Begrüßungen & Höflichkeit': {
learningGoals: [
'Einfache Begrüßungen verstehen und selbst verwenden.',
'Höfliche Reaktionen wie Danke und Bitte passend einsetzen.',
'Ein kurzes Begrüßungs-Mini-Gespräch laut üben.'
],
corePatterns: ['Kumusta ka?', 'Maayo ko.', 'Salamat.', 'Palihug.'],
grammarFocus: [
{ title: 'Kurzantworten mit ko', text: 'Mit "ko" sprichst du über dich selbst: "Maayo ko."', example: 'Maayo ko. = Mir geht es gut.' }
],
speakingPrompts: [
{ title: 'Mini-Gespräch', prompt: 'Begrüße eine Person, frage nach dem Befinden und reagiere höflich.', cue: 'Kumusta ka? Maayo ko. Salamat.' }
],
practicalTasks: [{ title: 'Alltag', text: 'Sprich die Begrüßung dreimal laut und variiere die Antwort.' }]
},
'Familienwörter': {
learningGoals: [
'Die wichtigsten Familienbezeichnungen sicher erkennen.',
'Familienmitglieder mit respektvollen Wörtern ansprechen.',
'Kurze Sätze über die eigene Familie bilden.'
],
corePatterns: ['Si Nanay', 'Si Tatay', 'Kuya nako', 'Ate nako'],
grammarFocus: [
{ title: 'Respekt in Familienanreden', text: 'Kuya und Ate werden nicht nur in der Familie, sondern auch respektvoll für ältere Personen benutzt.', example: 'Kuya, palihug.' }
],
speakingPrompts: [
{ title: 'Meine Familie', prompt: 'Stelle zwei Familienmitglieder mit einem kurzen Satz vor.', cue: 'Si Nanay. Si Kuya.' }
],
practicalTasks: [{ title: 'Familienpraxis', text: 'Nenne laut fünf Familienwörter und bilde danach zwei Mini-Sätze.' }]
},
'Essen & Fürsorge': {
learningGoals: [
'Fürsorgliche Fragen rund ums Essen verstehen.',
'Einladungen zum Essen passend beantworten.',
'Kurze Essens-Dialoge laut üben.'
],
corePatterns: ['Nikaon na ka?', 'Kaon ta.', 'Gusto ka mokaon?', 'Lami kaayo.'],
grammarFocus: [
{ title: 'na als Zustandsmarker', text: '"na" markiert oft etwas, das bereits eingetreten ist oder jetzt gilt.', example: 'Nikaon na ka?' }
],
speakingPrompts: [
{ title: 'Fürsorge-Dialog', prompt: 'Frage, ob jemand schon gegessen hat, und biete Essen oder Wasser an.', cue: 'Nikaon na ka? Gusto ka mokaon?' }
],
practicalTasks: [{ title: 'Rollenspiel', text: 'Spiele ein kurzes Gespräch zwischen Gastgeber und Gast beim Essen.' }]
},
'Zeitformen - Grundlagen': {
learningGoals: [
'Ni- und Mo- als einfache Zeitmarker unterscheiden.',
'Kurze Sätze in Vergangenheit und Zukunft bilden.',
'Das Muster laut mit mehreren Verben wiederholen.'
],
corePatterns: ['Ni-kaon ko.', 'Mo-kaon ko.', 'Ni-adto ko.', 'Mo-adto ko.'],
grammarFocus: [
{ title: 'Zeitpräfixe', text: 'Ni- verweist auf Vergangenes, Mo- auf Zukünftiges oder Bevorstehendes.', example: 'Ni-kaon ko. / Mo-kaon ko.' }
],
speakingPrompts: [
{ title: 'Vorher und nachher', prompt: 'Sage einen Satz über etwas, das du getan hast, und einen Satz über etwas, das du tun wirst.', cue: 'Ni-kaon ko. Mo-adto ko.' }
],
practicalTasks: [{ title: 'Mustertraining', text: 'Nimm ein Verb und sprich es einmal mit Ni- und einmal mit Mo-.' }]
},
'Woche 1 - Wiederholung': {
learningGoals: [
'Die Kernmuster der ersten Woche ohne Hilfe wiederholen.',
'Zwischen Begrüßung, Familie und Fürsorge schneller wechseln.',
'Eine kurze Alltagssequenz frei sprechen.'
],
corePatterns: ['Kumusta ka?', 'Palangga taka.', 'Nikaon na ka?', 'Wala ko kasabot.'],
speakingPrompts: [
{ title: 'Freie Wiederholung', prompt: 'Begrüße jemanden, drücke Zuneigung aus und frage fürsorglich nach dem Essen.', cue: 'Kumusta ka? Palangga taka. Nikaon na ka?' }
]
},
'Woche 1 - Vokabeltest': {
learningGoals: [
'Die wichtigsten Wörter der ersten Woche schnell abrufen.',
'Bedeutung und Gebrauch zentraler Wörter unterscheiden.',
'Von einzelnen Wörtern zu kurzen Sätzen übergehen.'
],
corePatterns: ['Kumusta', 'Salamat', 'Lami', 'Mingaw ko nimo']
}
};
async function resetBisayaProgress(courseIds) {
if (courseIds.length === 0) return { lessonProgress: 0, exerciseProgress: 0 };
const lessonIds = await sequelize.query(
`SELECT id FROM community.vocab_course_lesson WHERE course_id = ANY(:courseIds)`,
{
replacements: { courseIds },
type: sequelize.QueryTypes.SELECT
}
);
const exerciseIds = await sequelize.query(
`SELECT id FROM community.vocab_grammar_exercise WHERE lesson_id = ANY(:lessonIds)`,
{
replacements: { lessonIds: lessonIds.map((row) => row.id) || [0] },
type: sequelize.QueryTypes.SELECT
}
);
const deletedLessonProgress = await VocabCourseProgress.destroy({
where: { courseId: { [Op.in]: courseIds } }
});
let deletedExerciseProgress = 0;
if (exerciseIds.length > 0) {
deletedExerciseProgress = await VocabGrammarExerciseProgress.destroy({
where: { exerciseId: { [Op.in]: exerciseIds.map((row) => row.id) } }
});
}
return {
lessonProgress: deletedLessonProgress,
exerciseProgress: deletedExerciseProgress
};
}
async function applyBisayaCourseRefresh() {
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) {
console.error('❌ Bisaya-Sprache nicht gefunden.');
return;
}
const courses = await sequelize.query(
`SELECT id, title FROM community.vocab_course WHERE language_id = :languageId ORDER BY id ASC`,
{
replacements: { languageId: bisayaLanguage.id },
type: sequelize.QueryTypes.SELECT
}
);
const courseIds = courses.map((course) => course.id);
const resetStats = await resetBisayaProgress(courseIds);
let updatedLessons = 0;
for (const course of courses) {
const lessons = await VocabCourseLesson.findAll({
where: { courseId: course.id }
});
for (const lesson of lessons) {
const didactics = LESSON_DIDACTICS[lesson.title];
if (!didactics) continue;
await lesson.update({
learningGoals: didactics.learningGoals || [],
corePatterns: didactics.corePatterns || [],
grammarFocus: didactics.grammarFocus || [],
speakingPrompts: didactics.speakingPrompts || [],
practicalTasks: didactics.practicalTasks || []
});
updatedLessons++;
}
}
console.log('✅ Bisaya-Kursupdate vorbereitet.');
console.log(` Kurse: ${courses.length}`);
console.log(` Didaktisch aktualisierte Lektionen: ${updatedLessons}`);
console.log(` Gelöschte Lektionsfortschritte: ${resetStats.lessonProgress}`);
console.log(` Gelöschte Übungsfortschritte: ${resetStats.exerciseProgress}`);
console.log('');
console.log('Nächste Schritte:');
console.log('1. create-bisaya-course-content.js ausführen, um die neuen Übungen einzuspielen');
console.log('2. optional update-week1-bisaya-exercises.js ausführen, falls Woche 1 separat gepflegt wird');
}
applyBisayaCourseRefresh()
.then(() => {
sequelize.close();
process.exit(0);
})
.catch((error) => {
console.error('❌ Fehler:', error);
sequelize.close();
process.exit(1);
});

View File

@@ -14,6 +14,13 @@ import VocabGrammarExercise from '../models/community/vocab_grammar_exercise.js'
import VocabCourse from '../models/community/vocab_course.js';
import User from '../models/community/user.js';
function withTypeName(exerciseTypeName, exercise) {
return {
...exercise,
exerciseTypeName
};
}
// Bisaya-spezifische Übungen basierend auf Lektionsthemen
const BISAYA_EXERCISES = {
// Lektion 1: Begrüßungen & Höflichkeit
@@ -62,6 +69,35 @@ const BISAYA_EXERCISES = {
correctAnswer: 0
},
explanation: '"Salamat" bedeutet "Danke" auf Bisaya.'
},
withTypeName('dialog_completion', {
title: 'Begrüßungsdialog ergänzen',
instruction: 'Ergänze die passende Antwort im Mini-Dialog.',
questionData: {
type: 'dialog_completion',
question: 'Welche Antwort passt auf die Begrüßung?',
dialog: ['A: Kumusta ka?', 'B: ...']
},
answerData: {
modelAnswer: 'Maayo ko, salamat.',
correct: ['Maayo ko, salamat.', 'Maayo ko. Salamat.']
},
explanation: 'Eine typische kurze Antwort ist "Maayo ko, salamat."'
}),
{
exerciseTypeId: 8,
title: 'Begrüßung frei sprechen',
instruction: 'Sprich eine kurze Begrüßung mit Frage und Antwort frei nach.',
questionData: {
type: 'speaking_from_memory',
question: 'Begrüße eine Person und antworte kurz auf "Kumusta ka?".',
expectedText: 'Kumusta ka? Maayo ko, salamat.',
keywords: ['kumusta', 'maayo', 'salamat']
},
answerData: {
type: 'speaking_from_memory'
},
explanation: 'Wichtig sind hier die Schlüsselwörter für Begrüßung, Antwort und Höflichkeit.'
}
],
@@ -188,7 +224,92 @@ const BISAYA_EXERCISES = {
alternatives: ['Mama', 'Nanay', 'Inahan']
},
explanation: '"Nanay" oder "Mama" bedeutet "Mutter" auf Bisaya.'
}
},
{
exerciseTypeId: 3,
title: 'Familiensatz bauen',
instruction: 'Bilde aus den Wörtern einen kurzen Satz.',
questionData: {
type: 'sentence_building',
question: 'Baue einen Satz: "Das ist meine Mutter."',
tokens: ['Si', 'Nanay', 'nako', 'ni']
},
answerData: {
correct: ['Si Nanay nako ni.', 'Si Nanay ni nako.']
},
explanation: 'Mit "Si Nanay nako ni." stellst du deine Mutter kurz vor.'
},
withTypeName('situational_response', {
title: 'Familie vorstellen',
instruction: 'Antworte kurz auf die Situation.',
questionData: {
type: 'situational_response',
question: 'Jemand fragt dich nach deiner Familie. Stelle kurz Mutter und älteren Bruder vor.',
keywords: ['nanay', 'kuya']
},
answerData: {
modelAnswer: 'Si Nanay ug si Kuya.',
keywords: ['nanay', 'kuya']
},
explanation: 'Für diese Aufgabe reichen kurze, klare Familiennennungen.'
})
],
'Essen & Fürsorge': [
{
exerciseTypeId: 2,
title: 'Fürsorgefrage verstehen',
instruction: 'Wähle die richtige Bedeutung.',
questionData: {
type: 'multiple_choice',
question: 'Was bedeutet "Nikaon na ka?"?',
options: ['Hast du schon gegessen?', 'Bist du müde?', 'Kommst du nach Hause?', 'Möchtest du Wasser?']
},
answerData: { type: 'multiple_choice', correctAnswer: 0 },
explanation: '"Nikaon na ka?" ist eine sehr fürsorgliche Alltagsfrage.'
},
{
exerciseTypeId: 1,
title: 'Essensdialog ergänzen',
instruction: 'Fülle die Lücken mit den passenden Wörtern.',
questionData: {
type: 'gap_fill',
text: 'Nikaon {gap} ka? {gap} ta!',
gaps: 2
},
answerData: {
answers: ['na', 'Kaon']
},
explanation: '"na" markiert hier den bereits eingetretenen Zustand; "Kaon ta!" heißt "Lass uns essen!".'
},
withTypeName('dialog_completion', {
title: 'Einladung zum Essen ergänzen',
instruction: 'Ergänze die passende Antwort.',
questionData: {
type: 'dialog_completion',
question: 'Welche Antwort passt auf die Einladung?',
dialog: ['A: Kaon ta!', 'B: ...']
},
answerData: {
modelAnswer: 'Oo, gusto ko.',
correct: ['Oo, gusto ko.', 'Oo, mokaon ko.']
},
explanation: 'Eine natürliche kurze Reaktion ist "Oo, gusto ko."'
}),
withTypeName('situational_response', {
title: 'Fürsorglich reagieren',
instruction: 'Reagiere passend auf die Situation.',
questionData: {
type: 'situational_response',
question: 'Jemand sieht hungrig aus. Frage fürsorglich nach und biete Essen an.',
keywords: ['nikaon', 'kaon']
},
answerData: {
modelAnswer: 'Nikaon na ka? Kaon ta.',
keywords: ['nikaon', 'kaon']
},
explanation: 'Die Übung trainiert einen sehr typischen fürsorglichen Mini-Dialog.'
})
],
// Lektion: Haus & Familie (Balay, Kwarto, Kusina, Pamilya)
@@ -424,6 +545,34 @@ const BISAYA_EXERCISES = {
answers: ['Ni', 'Mo']
},
explanation: 'Ni- für Vergangenheit, Mo- für Zukunft.'
},
withTypeName('pattern_drill', {
title: 'Zeitmuster anwenden',
instruction: 'Bilde mit demselben Muster einen Zukunftssatz.',
questionData: {
type: 'pattern_drill',
question: 'Verwende das Muster für "gehen".',
pattern: 'Mo- + Verb + ko'
},
answerData: {
modelAnswer: 'Mo-adto ko.',
correct: ['Mo-adto ko.', 'Moadto ko.']
},
explanation: 'Mit "Mo-" kannst du ein einfaches Zukunftsmuster bilden.'
}),
{
exerciseTypeId: 3,
title: 'Vergangenheit und Zukunft bauen',
instruction: 'Schreibe beide Formen nacheinander auf.',
questionData: {
type: 'sentence_building',
question: 'Formuliere: "Ich habe gegessen. Ich werde essen."',
tokens: ['Ni-kaon', 'ko', 'Mo-kaon', 'ko']
},
answerData: {
correct: ['Ni-kaon ko. Mo-kaon ko.', 'Nikaon ko. Mokaon ko.']
},
explanation: 'Die Übung trainiert den direkten Wechsel zwischen den beiden Zeitmarkern.'
}
],
@@ -1103,7 +1252,35 @@ const BISAYA_EXERCISES = {
},
answerData: { type: 'multiple_choice', correctAnswer: 0 },
explanation: '"Wala ko kasabot" bedeutet "Ich verstehe nicht".'
}
},
{
exerciseTypeId: 3,
title: 'Woche 1: Minisatz bauen',
instruction: 'Schreibe eine kurze Sequenz aus Begrüßung und Fürsorge.',
questionData: {
type: 'sentence_building',
question: 'Baue: "Wie geht es dir? Hast du schon gegessen?"',
tokens: ['Kumusta', 'ka', 'Nikaon', 'na', 'ka']
},
answerData: {
correct: ['Kumusta ka? Nikaon na ka?', 'Kumusta ka. Nikaon na ka?']
},
explanation: 'Hier kombinierst du zwei wichtige Muster aus Woche 1.'
},
withTypeName('dialog_completion', {
title: 'Woche 1: Dialog ergänzen',
instruction: 'Ergänze die passende liebevolle Reaktion.',
questionData: {
type: 'dialog_completion',
question: 'Welche Antwort passt?',
dialog: ['A: Mingaw ko nimo.', 'B: ...']
},
answerData: {
modelAnswer: 'Palangga taka.',
correct: ['Palangga taka.']
},
explanation: 'Die Kombination klingt im Familienkontext warm und natürlich.'
})
],
// Woche 1 - Vokabeltest (Lektion 10)
@@ -1167,10 +1344,48 @@ const BISAYA_EXERCISES = {
},
answerData: { type: 'multiple_choice', correctAnswer: 0 },
explanation: '"Mingaw ko nimo" bedeutet "Ich vermisse dich".'
}
},
withTypeName('situational_response', {
title: 'Woche 1: Situative Kurzantwort',
instruction: 'Reagiere passend auf die Situation.',
questionData: {
type: 'situational_response',
question: 'Jemand fragt: "Kumusta ka?" Antworte kurz und höflich.',
keywords: ['maayo', 'salamat']
},
answerData: {
modelAnswer: 'Maayo ko, salamat.',
keywords: ['maayo', 'salamat']
},
explanation: 'Eine kurze höfliche Antwort reicht hier völlig aus.'
})
]
};
async function resolveExerciseTypeId(exercise) {
if (exercise.exerciseTypeId) {
return exercise.exerciseTypeId;
}
if (!exercise.exerciseTypeName) {
throw new Error(`Kein exerciseTypeId oder exerciseTypeName für "${exercise.title}" definiert`);
}
const [type] = await sequelize.query(
`SELECT id FROM community.vocab_grammar_exercise_type WHERE name = :name LIMIT 1`,
{
replacements: { name: exercise.exerciseTypeName },
type: sequelize.QueryTypes.SELECT
}
);
if (!type) {
throw new Error(`Übungstyp "${exercise.exerciseTypeName}" nicht gefunden`);
}
return Number(type.id);
}
async function findOrCreateSystemUser() {
let systemUser = await User.findOne({
where: {
@@ -1270,10 +1485,14 @@ async function createBisayaCourseContent() {
const replacePlaceholders = [
'Woche 1 - Wiederholung',
'Woche 1 - Vokabeltest',
'Begrüßungen & Höflichkeit',
'Familienwörter',
'Essen & Fürsorge',
'Alltagsgespräche - Teil 1',
'Alltagsgespräche - Teil 2',
'Haus & Familie',
'Ort & Richtung'
'Ort & Richtung',
'Zeitformen - Grundlagen'
].includes(lesson.title);
const existingCount = await VocabGrammarExercise.count({
where: { lessonId: lesson.id }
@@ -1292,9 +1511,10 @@ async function createBisayaCourseContent() {
// Erstelle Übungen
let exerciseNumber = 1;
for (const exerciseData of exercises) {
const exerciseTypeId = await resolveExerciseTypeId(exerciseData);
await VocabGrammarExercise.create({
lessonId: lesson.id,
exerciseTypeId: exerciseData.exerciseTypeId,
exerciseTypeId,
exerciseNumber: exerciseNumber++,
title: exerciseData.title,
instruction: exerciseData.instruction,

View File

@@ -12,6 +12,174 @@ import VocabCourseLesson from '../models/community/vocab_course_lesson.js';
import User from '../models/community/user.js';
import crypto from 'crypto';
const LESSON_DIDACTICS = {
'Begrüßungen & Höflichkeit': {
learningGoals: [
'Einfache Begrüßungen verstehen und selbst verwenden.',
'Höfliche Reaktionen wie Danke und Bitte passend einsetzen.',
'Ein kurzes Begrüßungs-Mini-Gespräch laut üben.'
],
corePatterns: [
'Kumusta ka?',
'Maayo ko.',
'Salamat.',
'Palihug.'
],
grammarFocus: [
{
title: 'Kurzantworten mit ko',
text: 'Mit "ko" sprichst du über dich selbst: "Maayo ko."',
example: 'Maayo ko. = Mir geht es gut.'
}
],
speakingPrompts: [
{
title: 'Mini-Gespräch',
prompt: 'Begrüße eine Person, frage nach dem Befinden und reagiere höflich.',
cue: 'Kumusta ka? Maayo ko. Salamat.'
}
],
practicalTasks: [
{
title: 'Alltag',
text: 'Sprich die Begrüßung dreimal laut und variiere die Antwort.'
}
]
},
'Familienwörter': {
learningGoals: [
'Die wichtigsten Familienbezeichnungen sicher erkennen.',
'Familienmitglieder mit respektvollen Wörtern ansprechen.',
'Kurze Sätze über die eigene Familie bilden.'
],
corePatterns: [
'Si Nanay',
'Si Tatay',
'Kuya nako',
'Ate nako'
],
grammarFocus: [
{
title: 'Respekt in Familienanreden',
text: 'Kuya und Ate werden nicht nur in der Familie, sondern auch respektvoll für ältere Personen benutzt.',
example: 'Kuya, palihug.'
}
],
speakingPrompts: [
{
title: 'Meine Familie',
prompt: 'Stelle zwei Familienmitglieder mit einem kurzen Satz vor.',
cue: 'Si Nanay. Si Kuya.'
}
],
practicalTasks: [
{
title: 'Familienpraxis',
text: 'Nenne laut fünf Familienwörter und bilde danach zwei Mini-Sätze.'
}
]
},
'Essen & Fürsorge': {
learningGoals: [
'Fürsorgliche Fragen rund ums Essen verstehen.',
'Einladungen zum Essen passend beantworten.',
'Kurze Essens-Dialoge laut üben.'
],
corePatterns: [
'Nikaon na ka?',
'Kaon ta.',
'Gusto ka mokaon?',
'Lami kaayo.'
],
grammarFocus: [
{
title: 'na als Zustandsmarker',
text: '"na" markiert oft etwas, das bereits eingetreten ist oder jetzt gilt.',
example: 'Nikaon na ka?'
}
],
speakingPrompts: [
{
title: 'Fürsorge-Dialog',
prompt: 'Frage, ob jemand schon gegessen hat, und biete Essen oder Wasser an.',
cue: 'Nikaon na ka? Gusto ka mokaon?'
}
],
practicalTasks: [
{
title: 'Rollenspiel',
text: 'Spiele ein kurzes Gespräch zwischen Gastgeber und Gast beim Essen.'
}
]
},
'Zeitformen - Grundlagen': {
learningGoals: [
'Ni- und Mo- als einfache Zeitmarker unterscheiden.',
'Kurze Sätze in Vergangenheit und Zukunft bilden.',
'Das Muster laut mit mehreren Verben wiederholen.'
],
corePatterns: [
'Ni-kaon ko.',
'Mo-kaon ko.',
'Ni-adto ko.',
'Mo-adto ko.'
],
grammarFocus: [
{
title: 'Zeitpräfixe',
text: 'Ni- verweist auf Vergangenes, Mo- auf Zukünftiges oder Bevorstehendes.',
example: 'Ni-kaon ko. / Mo-kaon ko.'
}
],
speakingPrompts: [
{
title: 'Vorher und nachher',
prompt: 'Sage einen Satz über etwas, das du getan hast, und einen Satz über etwas, das du tun wirst.',
cue: 'Ni-kaon ko. Mo-adto ko.'
}
],
practicalTasks: [
{
title: 'Mustertraining',
text: 'Nimm ein Verb und sprich es einmal mit Ni- und einmal mit Mo-.'
}
]
},
'Woche 1 - Wiederholung': {
learningGoals: [
'Die Kernmuster der ersten Woche ohne Hilfe wiederholen.',
'Zwischen Begrüßung, Familie und Fürsorge schneller wechseln.',
'Eine kurze Alltagssequenz frei sprechen.'
],
corePatterns: [
'Kumusta ka?',
'Palangga taka.',
'Nikaon na ka?',
'Wala ko kasabot.'
],
speakingPrompts: [
{
title: 'Freie Wiederholung',
prompt: 'Begrüße jemanden, drücke Zuneigung aus und frage fürsorglich nach dem Essen.',
cue: 'Kumusta ka? Palangga taka. Nikaon na ka?'
}
]
},
'Woche 1 - Vokabeltest': {
learningGoals: [
'Die wichtigsten Wörter der ersten Woche schnell abrufen.',
'Bedeutung und Gebrauch zentraler Wörter unterscheiden.',
'Von einzelnen Wörtern zu kurzen Sätzen übergehen.'
],
corePatterns: [
'Kumusta',
'Salamat',
'Lami',
'Mingaw ko nimo'
]
}
};
const LESSONS = [
// WOCHE 1: Grundlagen & Aussprache
{ week: 1, day: 1, num: 1, type: 'conversation', title: 'Begrüßungen & Höflichkeit',
@@ -262,6 +430,11 @@ async function createBisayaCourse(languageId, ownerHashedId) {
dayNumber: lessonData.day,
lessonType: lessonData.type,
culturalNotes: lessonData.cultural,
learningGoals: LESSON_DIDACTICS[lessonData.title]?.learningGoals || [],
corePatterns: LESSON_DIDACTICS[lessonData.title]?.corePatterns || [],
grammarFocus: LESSON_DIDACTICS[lessonData.title]?.grammarFocus || [],
speakingPrompts: LESSON_DIDACTICS[lessonData.title]?.speakingPrompts || [],
practicalTasks: LESSON_DIDACTICS[lessonData.title]?.practicalTasks || [],
targetMinutes: lessonData.targetMin,
targetScorePercent: lessonData.targetScore,
requiresReview: lessonData.review

View File

@@ -0,0 +1,154 @@
#!/usr/bin/env node
/**
* Pflegt didaktische Felder in bestehenden Bisaya-Kursen nach.
*
* Verwendung:
* node backend/scripts/update-bisaya-didactics.js
*/
import { sequelize } from '../utils/sequelize.js';
import VocabCourseLesson from '../models/community/vocab_course_lesson.js';
const LESSON_DIDACTICS = {
'Begrüßungen & Höflichkeit': {
learningGoals: [
'Einfache Begrüßungen verstehen und selbst verwenden.',
'Höfliche Reaktionen wie Danke und Bitte passend einsetzen.',
'Ein kurzes Begrüßungs-Mini-Gespräch laut üben.'
],
corePatterns: ['Kumusta ka?', 'Maayo ko.', 'Salamat.', 'Palihug.'],
grammarFocus: [
{ title: 'Kurzantworten mit ko', text: 'Mit "ko" sprichst du über dich selbst: "Maayo ko."', example: 'Maayo ko. = Mir geht es gut.' }
],
speakingPrompts: [
{ title: 'Mini-Gespräch', prompt: 'Begrüße eine Person, frage nach dem Befinden und reagiere höflich.', cue: 'Kumusta ka? Maayo ko. Salamat.' }
],
practicalTasks: [
{ title: 'Alltag', text: 'Sprich die Begrüßung dreimal laut und variiere die Antwort.' }
]
},
'Familienwörter': {
learningGoals: [
'Die wichtigsten Familienbezeichnungen sicher erkennen.',
'Familienmitglieder mit respektvollen Wörtern ansprechen.',
'Kurze Sätze über die eigene Familie bilden.'
],
corePatterns: ['Si Nanay', 'Si Tatay', 'Kuya nako', 'Ate nako'],
grammarFocus: [
{ title: 'Respekt in Familienanreden', text: 'Kuya und Ate werden nicht nur in der Familie, sondern auch respektvoll für ältere Personen benutzt.', example: 'Kuya, palihug.' }
],
speakingPrompts: [
{ title: 'Meine Familie', prompt: 'Stelle zwei Familienmitglieder mit einem kurzen Satz vor.', cue: 'Si Nanay. Si Kuya.' }
],
practicalTasks: [
{ title: 'Familienpraxis', text: 'Nenne laut fünf Familienwörter und bilde danach zwei Mini-Sätze.' }
]
},
'Essen & Fürsorge': {
learningGoals: [
'Fürsorgliche Fragen rund ums Essen verstehen.',
'Einladungen zum Essen passend beantworten.',
'Kurze Essens-Dialoge laut üben.'
],
corePatterns: ['Nikaon na ka?', 'Kaon ta.', 'Gusto ka mokaon?', 'Lami kaayo.'],
grammarFocus: [
{ title: 'na als Zustandsmarker', text: '"na" markiert oft etwas, das bereits eingetreten ist oder jetzt gilt.', example: 'Nikaon na ka?' }
],
speakingPrompts: [
{ title: 'Fürsorge-Dialog', prompt: 'Frage, ob jemand schon gegessen hat, und biete Essen oder Wasser an.', cue: 'Nikaon na ka? Gusto ka mokaon?' }
],
practicalTasks: [
{ title: 'Rollenspiel', text: 'Spiele ein kurzes Gespräch zwischen Gastgeber und Gast beim Essen.' }
]
},
'Zeitformen - Grundlagen': {
learningGoals: [
'Ni- und Mo- als einfache Zeitmarker unterscheiden.',
'Kurze Sätze in Vergangenheit und Zukunft bilden.',
'Das Muster laut mit mehreren Verben wiederholen.'
],
corePatterns: ['Ni-kaon ko.', 'Mo-kaon ko.', 'Ni-adto ko.', 'Mo-adto ko.'],
grammarFocus: [
{ title: 'Zeitpräfixe', text: 'Ni- verweist auf Vergangenes, Mo- auf Zukünftiges oder Bevorstehendes.', example: 'Ni-kaon ko. / Mo-kaon ko.' }
],
speakingPrompts: [
{ title: 'Vorher und nachher', prompt: 'Sage einen Satz über etwas, das du getan hast, und einen Satz über etwas, das du tun wirst.', cue: 'Ni-kaon ko. Mo-adto ko.' }
],
practicalTasks: [
{ title: 'Mustertraining', text: 'Nimm ein Verb und sprich es einmal mit Ni- und einmal mit Mo-.' }
]
},
'Woche 1 - Wiederholung': {
learningGoals: [
'Die Kernmuster der ersten Woche ohne Hilfe wiederholen.',
'Zwischen Begrüßung, Familie und Fürsorge schneller wechseln.',
'Eine kurze Alltagssequenz frei sprechen.'
],
corePatterns: ['Kumusta ka?', 'Palangga taka.', 'Nikaon na ka?', 'Wala ko kasabot.'],
speakingPrompts: [
{ title: 'Freie Wiederholung', prompt: 'Begrüße jemanden, drücke Zuneigung aus und frage fürsorglich nach dem Essen.', cue: 'Kumusta ka? Palangga taka. Nikaon na ka?' }
]
},
'Woche 1 - Vokabeltest': {
learningGoals: [
'Die wichtigsten Wörter der ersten Woche schnell abrufen.',
'Bedeutung und Gebrauch zentraler Wörter unterscheiden.',
'Von einzelnen Wörtern zu kurzen Sätzen übergehen.'
],
corePatterns: ['Kumusta', 'Salamat', 'Lami', 'Mingaw ko nimo']
}
};
async function updateBisayaDidactics() {
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) {
console.error('❌ Bisaya-Sprache nicht gefunden.');
return;
}
const lessons = await sequelize.query(
`SELECT l.id
FROM community.vocab_course_lesson l
JOIN community.vocab_course c ON c.id = l.course_id
WHERE c.language_id = :languageId`,
{
replacements: { languageId: bisayaLanguage.id },
type: sequelize.QueryTypes.SELECT
}
);
let updated = 0;
for (const row of lessons) {
const lesson = await VocabCourseLesson.findByPk(row.id);
const didactics = LESSON_DIDACTICS[lesson.title];
if (!didactics) continue;
await lesson.update({
learningGoals: didactics.learningGoals || [],
corePatterns: didactics.corePatterns || [],
grammarFocus: didactics.grammarFocus || [],
speakingPrompts: didactics.speakingPrompts || [],
practicalTasks: didactics.practicalTasks || []
});
updated++;
console.log(`✅ Didaktik aktualisiert: Lektion ${lesson.lessonNumber} - ${lesson.title}`);
}
console.log(`\n🎉 Fertig. ${updated} Lektion(en) aktualisiert.`);
}
updateBisayaDidactics()
.then(() => {
sequelize.close();
process.exit(0);
})
.catch((error) => {
console.error('❌ Fehler:', error);
sequelize.close();
process.exit(1);
});

View File

@@ -14,6 +14,13 @@ import VocabCourseLesson from '../models/community/vocab_course_lesson.js';
import VocabGrammarExercise from '../models/community/vocab_grammar_exercise.js';
import User from '../models/community/user.js';
function withTypeName(exerciseTypeName, exercise) {
return {
...exercise,
exerciseTypeName
};
}
const LESSON_TITLES = ['Woche 1 - Wiederholung', 'Woche 1 - Vokabeltest'];
const BISAYA_EXERCISES = {
@@ -22,17 +29,40 @@ const BISAYA_EXERCISES = {
{ exerciseTypeId: 2, title: 'Wiederholung: Wie sagt man "Mutter" auf Bisaya?', instruction: 'Wähle die richtige Übersetzung.', questionData: { type: 'multiple_choice', question: 'Wie sagt man "Mutter" auf Bisaya?', options: ['Nanay', 'Tatay', 'Kuya', 'Ate'] }, answerData: { type: 'multiple_choice', correctAnswer: 0 }, explanation: '"Nanay" bedeutet "Mutter" auf Bisaya.' },
{ exerciseTypeId: 2, title: 'Wiederholung: Was bedeutet "Palangga taka"?', instruction: 'Wähle die richtige Bedeutung.', questionData: { type: 'multiple_choice', question: 'Was bedeutet "Palangga taka"?', options: ['Ich hab dich lieb', 'Danke', 'Guten Tag', 'Auf Wiedersehen'] }, answerData: { type: 'multiple_choice', correctAnswer: 0 }, explanation: '"Palangga taka" bedeutet "Ich hab dich lieb" - wärmer als "I love you" im Familienkontext.' },
{ exerciseTypeId: 2, title: 'Wiederholung: Was fragt man mit "Nikaon ka?"?', instruction: 'Wähle die richtige Bedeutung.', questionData: { type: 'multiple_choice', question: 'Was bedeutet "Nikaon ka?"?', options: ['Hast du schon gegessen?', 'Wie geht es dir?', 'Danke', 'Bitte'] }, answerData: { type: 'multiple_choice', correctAnswer: 0 }, explanation: '"Nikaon ka?" bedeutet "Hast du schon gegessen?" - typisch fürsorglich auf den Philippinen.' },
{ exerciseTypeId: 2, title: 'Wiederholung: Wie sagt man "Ich verstehe nicht"?', instruction: 'Wähle die richtige Übersetzung.', questionData: { type: 'multiple_choice', question: 'Wie sagt man "Ich verstehe nicht" auf Bisaya?', options: ['Wala ko kasabot', 'Salamat', 'Maayo', 'Palihug'] }, answerData: { type: 'multiple_choice', correctAnswer: 0 }, explanation: '"Wala ko kasabot" bedeutet "Ich verstehe nicht".' }
{ exerciseTypeId: 2, title: 'Wiederholung: Wie sagt man "Ich verstehe nicht"?', instruction: 'Wähle die richtige Übersetzung.', questionData: { type: 'multiple_choice', question: 'Wie sagt man "Ich verstehe nicht" auf Bisaya?', options: ['Wala ko kasabot', 'Salamat', 'Maayo', 'Palihug'] }, answerData: { type: 'multiple_choice', correctAnswer: 0 }, explanation: '"Wala ko kasabot" bedeutet "Ich verstehe nicht".' },
{ exerciseTypeId: 3, title: 'Woche 1: Minisatz bauen', instruction: 'Schreibe eine kurze Sequenz aus Begrüßung und Fürsorge.', questionData: { type: 'sentence_building', question: 'Baue: "Wie geht es dir? Hast du schon gegessen?"', tokens: ['Kumusta', 'ka', 'Nikaon', 'na', 'ka'] }, answerData: { correct: ['Kumusta ka? Nikaon na ka?', 'Kumusta ka. Nikaon na ka?'] }, explanation: 'Hier kombinierst du zwei wichtige Muster aus Woche 1.' },
withTypeName('dialog_completion', { title: 'Woche 1: Dialog ergänzen', instruction: 'Ergänze die passende liebevolle Reaktion.', questionData: { type: 'dialog_completion', question: 'Welche Antwort passt?', dialog: ['A: Mingaw ko nimo.', 'B: ...'] }, answerData: { modelAnswer: 'Palangga taka.', correct: ['Palangga taka.'] }, explanation: 'Die Kombination klingt im Familienkontext warm und natürlich.' })
],
'Woche 1 - Vokabeltest': [
{ exerciseTypeId: 2, title: 'Vokabeltest: Kumusta', instruction: 'Was bedeutet "Kumusta"?', questionData: { type: 'multiple_choice', question: 'Was bedeutet "Kumusta"?', options: ['Wie geht es dir?', 'Danke', 'Bitte', 'Auf Wiedersehen'] }, answerData: { type: 'multiple_choice', correctAnswer: 0 }, explanation: '"Kumusta" kommt von spanisch "¿Cómo está?" - "Wie geht es dir?"' },
{ exerciseTypeId: 2, title: 'Vokabeltest: Lola', instruction: 'Wähle die richtige Übersetzung.', questionData: { type: 'multiple_choice', question: 'Was bedeutet "Lola"?', options: ['Großmutter', 'Großvater', 'Mutter', 'Vater'] }, answerData: { type: 'multiple_choice', correctAnswer: 0 }, explanation: '"Lola" = Großmutter, "Lolo" = Großvater.' },
{ exerciseTypeId: 2, title: 'Vokabeltest: Salamat', instruction: 'Wähle die richtige Bedeutung.', questionData: { type: 'multiple_choice', question: 'Was bedeutet "Salamat"?', options: ['Danke', 'Bitte', 'Entschuldigung', 'Gern geschehen'] }, answerData: { type: 'multiple_choice', correctAnswer: 0 }, explanation: '"Salamat" bedeutet "Danke".' },
{ exerciseTypeId: 2, title: 'Vokabeltest: Lami', instruction: 'Was bedeutet "Lami"?', questionData: { type: 'multiple_choice', question: 'Was bedeutet "Lami"?', options: ['Lecker', 'Viel', 'Gut', 'Schnell'] }, answerData: { type: 'multiple_choice', correctAnswer: 0 }, explanation: '"Lami" bedeutet "lecker" oder "schmackhaft" - wichtig beim Essen!' },
{ exerciseTypeId: 2, title: 'Vokabeltest: Mingaw ko nimo', instruction: 'Wähle die richtige Bedeutung.', questionData: { type: 'multiple_choice', question: 'Was bedeutet "Mingaw ko nimo"?', options: ['Ich vermisse dich', 'Ich freue mich', 'Ich mag dich', 'Ich liebe dich'] }, answerData: { type: 'multiple_choice', correctAnswer: 0 }, explanation: '"Mingaw ko nimo" bedeutet "Ich vermisse dich".' }
{ exerciseTypeId: 2, title: 'Vokabeltest: Mingaw ko nimo', instruction: 'Wähle die richtige Bedeutung.', questionData: { type: 'multiple_choice', question: 'Was bedeutet "Mingaw ko nimo"?', options: ['Ich vermisse dich', 'Ich freue mich', 'Ich mag dich', 'Ich liebe dich'] }, answerData: { type: 'multiple_choice', correctAnswer: 0 }, explanation: '"Mingaw ko nimo" bedeutet "Ich vermisse dich".' },
withTypeName('situational_response', { title: 'Woche 1: Situative Kurzantwort', instruction: 'Reagiere passend auf die Situation.', questionData: { type: 'situational_response', question: 'Jemand fragt: "Kumusta ka?" Antworte kurz und höflich.', keywords: ['maayo', 'salamat'] }, answerData: { modelAnswer: 'Maayo ko, salamat.', keywords: ['maayo', 'salamat'] }, explanation: 'Eine kurze höfliche Antwort reicht hier völlig aus.' })
]
};
async function resolveExerciseTypeId(exercise) {
if (exercise.exerciseTypeId) {
return exercise.exerciseTypeId;
}
const [type] = await sequelize.query(
`SELECT id FROM community.vocab_grammar_exercise_type WHERE name = :name LIMIT 1`,
{
replacements: { name: exercise.exerciseTypeName },
type: sequelize.QueryTypes.SELECT
}
);
if (!type) {
throw new Error(`Übungstyp "${exercise.exerciseTypeName}" nicht gefunden`);
}
return Number(type.id);
}
async function updateWeek1BisayaExercises() {
await sequelize.authenticate();
console.log('Datenbankverbindung erfolgreich hergestellt.\n');
@@ -93,9 +123,10 @@ async function updateWeek1BisayaExercises() {
let exerciseNumber = 1;
for (const ex of exercises) {
const exerciseTypeId = await resolveExerciseTypeId(ex);
await VocabGrammarExercise.create({
lessonId: lesson.id,
exerciseTypeId: ex.exerciseTypeId,
exerciseTypeId,
exerciseNumber: exerciseNumber++,
title: ex.title,
instruction: ex.instruction,