feat(bisaya-course): add new exercises for situational role plays and enhance lesson normalization
All checks were successful
Deploy to production / deploy (push) Successful in 3m3s

- Introduced a new section titled "Rollenspiele - echte Situationen" with multiple exercises focusing on real-life scenarios in Bisaya, including multiple-choice, gap-fill, and transformation tasks.
- Implemented a normalization function for lesson titles to improve matching accuracy and facilitate the rebuilding of placeholder lessons.
- Updated the course content generation logic to replace outdated exercises with new ones based on normalized titles, ensuring a more relevant learning experience.
This commit is contained in:
Torsten Schulz (local)
2026-04-16 22:28:10 +02:00
parent 68ac5ec281
commit c1421db72c

View File

@@ -4323,6 +4323,73 @@ const BISAYA_EXERCISES = {
})
],
'Rollenspiele - echte Situationen': [
{
exerciseTypeId: 2,
title: 'Szene wählen',
instruction: 'Wähle die Bisaya-Formulierung, die am besten zu einer gemischten Alltagsszene (Weg + Termin + Hilfe) passt.',
questionData: {
type: 'multiple_choice',
question: 'Ihr müsst in die Stadt, habt einen Termin und willst kurz Hilfe anbieten. Was passt am ehesten?',
options: [
'Moadto mi sa lungsod. Aduna mi appointment. Tabangan tika.',
'Asa ang CR?',
'Nikaon na ka?',
'Magdula ta.'
]
},
answerData: { type: 'multiple_choice', correctAnswer: 0 },
explanation: 'Rollenspiele verbinden oft Weg, Termin und Hilfe in einer kurzen Kette.'
},
{
exerciseTypeId: 1,
title: 'Arzt und Weg',
instruction: 'Fülle die Lücke: Arzt und Bewegung.',
questionData: {
type: 'gap_fill',
text: 'Adto ta sa {gap}.',
gaps: 1
},
answerData: {
type: 'gap_fill',
answers: ['doktor']
},
explanation: '„Adto ta sa doktor." ist ein typischer Rollenspiel-Satz zum Arztbesuch.'
},
{
exerciseTypeId: 4,
title: 'Kind sicher holen',
instruction: 'Übersetze ins Bisaya.',
questionData: {
type: 'transformation',
text: 'Ich hole das Kind.',
sourceLanguage: 'Deutsch',
targetLanguage: 'Bisaya'
},
answerData: {
type: 'transformation',
correct: 'Kuhaon nako ang bata.',
alternatives: ['Kuhaon nako si bata.', 'Akong kuhaon ang bata.']
},
explanation: 'Im Familienalltag taucht „bata" in Rollenspielen sehr häufig auf.'
},
withTypeName('situational_response', {
title: 'Drei Mini-Sätze',
instruction: 'Antworte in drei kurzen Sätzen (Rollenspiel).',
questionData: {
type: 'situational_response',
question:
'Spiel eine kurze Szene: ihr geht zum Arzt, braucht den Weg zur Haltestelle und bietet jemandem Hilfe an.',
keywords: ['doktor', 'sakayan', 'tabang']
},
answerData: {
modelAnswer: 'Adto ta sa doktor. Asa ang sakayan? Pwede ko motabang nimo?',
keywords: ['doktor', 'sakayan', 'tabang']
},
explanation: 'Mehrere kurze Sätze hintereinander üben typische Rollenspiel-Ketten.'
})
],
'Freies Sprechen - Alltag ohne Stütze': [
{
exerciseTypeId: 2,
@@ -5167,16 +5234,64 @@ async function findOrCreateSystemUser() {
return systemUser;
}
function normalizeLessonTitleForMatch(title) {
return String(title || '')
.normalize('NFKC')
.replace(/\s+/g, ' ')
.trim();
}
/** Lektionen, bei denen alte Platzhalter-Übungen verworfen und neu erzeugt werden sollen. */
const PLACEHOLDER_REBUILD_TITLES = new Set([
'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',
'Zeitformen - Grundlagen',
'Zeit & Datum',
'Einkaufen & Preise',
'Zahlen & Preise',
'Zahlen 120',
'Zahlen: Zehner',
'Zahlen: Hunderter',
'Zahlen: Tausender',
'Woche 2 - Wiederholung',
'Woche 2 - Vokabeltest',
'Familie - Verwandte & Stieffamilie',
'Rollenspiele - echte Situationen'
]);
function lessonMatchesPlaceholderRebuildList(lesson) {
const n = normalizeLessonTitleForMatch(lesson.title);
if (PLACEHOLDER_REBUILD_TITLES.has(n)) return true;
// Lektion 18: alte Bezeichnung „Zahlen & Preise“ trotz Leerzeichen/Varianten
const num = Number(lesson.lessonNumber);
if (num === 18 && /\bzahlen\b/i.test(n) && /\bpreis/i.test(n)) return true;
return false;
}
function getExercisesForLesson(lesson) {
const lessonTitle = lesson.title;
const normalizedTitle = normalizeLessonTitleForMatch(lessonTitle);
// Alte Kurstitel (DB noch nicht migriert)
if (lessonTitle === 'Zahlen & Preise' && BISAYA_EXERCISES['Zahlen 120']) {
const isLegacyZahlenPreise =
normalizedTitle === 'Zahlen & Preise' ||
(Number(lesson.lessonNumber) === 18 && /\bzahlen\b/i.test(normalizedTitle) && /\bpreis/i.test(normalizedTitle));
if (isLegacyZahlenPreise && BISAYA_EXERCISES['Zahlen 120']) {
return BISAYA_EXERCISES['Zahlen 120'];
}
// Suche nach exaktem Titel
if (BISAYA_EXERCISES[lessonTitle]) {
return BISAYA_EXERCISES[lessonTitle];
}
if (BISAYA_EXERCISES[normalizedTitle]) {
return BISAYA_EXERCISES[normalizedTitle];
}
// Fallback: Suche nach Teilstring
for (const [key, exercises] of Object.entries(BISAYA_EXERCISES)) {
@@ -5245,28 +5360,7 @@ async function createBisayaCourseContent() {
}
// Lektionen mit Platzhalter-Ersetzung: alte Übungen entfernen und durch echte ersetzen
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',
'Zeitformen - Grundlagen',
'Zeit & Datum',
'Einkaufen & Preise',
'Zahlen & Preise',
'Zahlen 120',
'Zahlen: Zehner',
'Zahlen: Hunderter',
'Zahlen: Tausender',
'Woche 2 - Wiederholung',
'Woche 2 - Vokabeltest',
'Familie - Verwandte & Stieffamilie'
].includes(lesson.title);
const replacePlaceholders = lessonMatchesPlaceholderRebuildList(lesson);
const existingCount = await VocabGrammarExercise.count({
where: { lessonId: lesson.id }
});