From b2e5d3d950eccfe2190dfe25fbe7e972edb09314 Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Wed, 2 Sep 2026 14:03:39 +0200 Subject: [PATCH] feat(course): add Cebu travel path with lessons and didactics for Bisaya courses --- .../bisaya-course-cebu-travel-extension.js | 176 ++++++++++++++++++ .../scripts/create-bisaya-course-content.js | 7 +- backend/scripts/create-bisaya-course.js | 11 +- .../extend-bisaya-course-cebu-travel.js | 69 +++++++ backend/scripts/sync-vocab-course-content.js | 1 + backend/scripts/update-bisaya-didactics.js | 4 +- 6 files changed, 261 insertions(+), 7 deletions(-) create mode 100644 backend/scripts/bisaya-course-cebu-travel-extension.js create mode 100644 backend/scripts/extend-bisaya-course-cebu-travel.js diff --git a/backend/scripts/bisaya-course-cebu-travel-extension.js b/backend/scripts/bisaya-course-cebu-travel-extension.js new file mode 100644 index 0000000..ff9c8eb --- /dev/null +++ b/backend/scripts/bisaya-course-cebu-travel-extension.js @@ -0,0 +1,176 @@ +/** Ergänzende Lektionen für Reisen und längere Aufenthalte auf Cebu. */ +export const BISAYA_CEBU_TRAVEL_DIDACTICS = { + 'Cebu ankommen & einchecken': { + learningGoals: [ + 'Bei der Ankunft höflich grüßen und einchecken.', + 'Nach Zimmer und Schlüssel fragen.', + 'Eine Buchung kurz bestätigen.' + ], + corePatterns: [ + { target: 'Maayong adlaw.', gloss: 'Guten Tag.' }, + { target: 'Naa koy reservation.', gloss: 'Ich habe eine Reservierung.' }, + { target: 'Pwede ko mag-check in?', gloss: 'Kann ich einchecken?' }, + { target: 'Asa ang akong kwarto?', gloss: 'Wo ist mein Zimmer?' }, + { target: 'Salamat kaayo.', gloss: 'Vielen Dank.' } + ], + speakingPrompts: [{ + title: 'Ankunft im Hotel', + prompt: 'Begrüße die Rezeption, sage, dass du eine Reservierung hast, und frage nach deinem Zimmer.', + cue: 'Maayong adlaw. Naa koy reservation. Asa ang akong kwarto?' + }], + practicalTasks: [{ + title: 'Check-in', + text: 'Führe einen kurzen Check-in mit Begrüßung, Reservierung und Zimmerfrage.' + }] + }, + 'Unterkunft & kleine Probleme': { + learningGoals: [ + 'Wichtige Wörter zur Unterkunft verstehen.', + 'Freundlich nach Schlüssel, Wasser oder Handtuch fragen.', + 'Ein kleines Problem im Zimmer kurz benennen.' + ], + corePatterns: [ + { target: 'kwarto', gloss: 'Zimmer' }, + { target: 'yawe', gloss: 'Schlüssel' }, + { target: 'tuwalya', gloss: 'Handtuch' }, + { target: 'tubig', gloss: 'Wasser' }, + { target: 'Asa ang yawe sa kwarto?', gloss: 'Wo ist der Zimmerschlüssel?' }, + { target: 'Pwede mangayo ug tubig?', gloss: 'Kann ich um Wasser bitten?' }, + { target: 'Naay problema sa kwarto.', gloss: 'Es gibt ein Problem im Zimmer.' } + ], + speakingPrompts: [{ + title: 'An der Rezeption', + prompt: 'Frage nach dem Zimmerschlüssel und bitte anschließend um Wasser oder ein Handtuch.', + cue: 'Asa ang yawe sa kwarto? Pwede mangayo ug tubig?' + }], + practicalTasks: [{ + title: 'Kleines Hotelproblem', + text: 'Beschreibe höflich ein kleines Problem im Zimmer und bitte um Hilfe.' + }] + }, + 'Restaurant & Bestellen': { + learningGoals: [ + 'Im Restaurant höflich bestellen.', + 'Nach Wasser und Preis fragen.', + 'Auf Essen freundlich reagieren.' + ], + corePatterns: [ + { target: 'Gusto ko ani.', gloss: 'Ich möchte das.' }, + { target: 'Pwede mangayo ug tubig?', gloss: 'Kann ich um Wasser bitten?' }, + { target: 'Tagpila ni?', gloss: 'Wie viel kostet das?' }, + { target: 'Lami kaayo.', gloss: 'Sehr lecker.' }, + { target: 'Mubayad ko.', gloss: 'Ich möchte bezahlen.' } + ], + speakingPrompts: [{ + title: 'Bestellung', + prompt: 'Bestelle etwas, bitte um Wasser, frage nach dem Preis und reagiere auf das Essen.', + cue: 'Gusto ko ani. Pwede mangayo ug tubig? Tagpila ni? Lami kaayo.' + }], + practicalTasks: [{ + title: 'Im Restaurant', + text: 'Führe eine kurze Bestellung vom Wunsch bis zum Bezahlen.' + }] + }, + 'Markt, Souvenirs & Bezahlen': { + learningGoals: [ + 'Am Markt nach Preis und Menge fragen.', + 'Freundlich auf einen Preis reagieren.', + 'Einen Einkauf abschließen.' + ], + corePatterns: [ + { target: 'Pila ni tanan?', gloss: 'Wie viel kostet das alles?' }, + { target: 'Mahal ra.', gloss: 'Das ist teuer.' }, + { target: 'Barato ra.', gloss: 'Das ist günstig.' }, + { target: 'Kuhaon nako ni.', gloss: 'Ich nehme das.' }, + { target: 'Naay sukli?', gloss: 'Gibt es Wechselgeld?' } + ], + speakingPrompts: [{ + title: 'Souvenir kaufen', + prompt: 'Frage nach dem Gesamtpreis, reagiere freundlich und sage, dass du den Artikel nimmst.', + cue: 'Pila ni tanan? Mahal ra. Kuhaon nako ni.' + }], + practicalTasks: [{ + title: 'Marktgespräch', + text: 'Führe ein kurzes, höfliches Gespräch beim Kauf eines Souvenirs.' + }] + }, + 'Ausflüge, Inseln & Transport': { + learningGoals: [ + 'Nach Hafen, Fahrt und Ausstieg fragen.', + 'Einen einfachen Ausflug auf Cebu oder zu einer Insel planen.', + 'Richtungs- und Transportwörter im Zusammenhang anwenden.' + ], + corePatterns: [ + { target: 'Asa ang pantalan?', gloss: 'Wo ist der Hafen?' }, + { target: 'Kanus-a ang biyahe?', gloss: 'Wann ist die Fahrt?' }, + { target: 'Moadto ko sa isla.', gloss: 'Ich fahre auf die Insel.' }, + { target: 'Asa ta manaog?', gloss: 'Wo steigen wir aus?' }, + { target: 'Diretso lang.', gloss: 'Einfach geradeaus.' } + ], + speakingPrompts: [{ + title: 'Zum Hafen', + prompt: 'Frage nach dem Hafen und der Abfahrtszeit. Sage danach, dass du auf eine Insel fahren möchtest.', + cue: 'Asa ang pantalan? Kanus-a ang biyahe? Moadto ko sa isla.' + }], + practicalTasks: [{ + title: 'Ausflug planen', + text: 'Plane einen kurzen Ausflug: Hafen finden, Fahrtzeit erfragen und richtig aussteigen.' + }] + }, + 'Hilfe unterwegs & Reiseprobleme': { + learningGoals: [ + 'In einer einfachen Problemsituation Hilfe holen.', + 'Eine verlorene Tasche oder ein gesundheitliches Problem benennen.', + 'Höflich und klar reagieren.' + ], + corePatterns: [ + { target: 'Kinahanglan nako ug tabang.', gloss: 'Ich brauche Hilfe.' }, + { target: 'Nawala ang akong bag.', gloss: 'Meine Tasche ist verloren gegangen.' }, + { target: 'Asa ang botika?', gloss: 'Wo ist die Apotheke?' }, + { target: 'Tawag ug tabang, palihug.', gloss: 'Ruf bitte Hilfe.' }, + { target: 'Okay ra ko.', gloss: 'Mir geht es gut / Es ist in Ordnung.' } + ], + speakingPrompts: [{ + title: 'Unterwegs Hilfe holen', + prompt: 'Sage, dass du Hilfe brauchst, und erkläre kurz, ob du deine Tasche verloren hast oder eine Apotheke suchst.', + cue: 'Kinahanglan nako ug tabang. Nawala ang akong bag. Asa ang botika?' + }], + practicalTasks: [{ + title: 'Notfall-Minirolle', + text: 'Bitte klar und höflich um Hilfe und nenne ein konkretes Problem.' + }] + }, + 'Cebu-Reisepfad – Abschluss': { + learningGoals: [ + 'Die wichtigsten Reisesituationen auf Cebu zusammenhängend anwenden.', + 'Bei Unterkunft, Essen, Transport und Hilfe selbstständig reagieren.', + 'Höflichkeit und kurze, klare Sätze sicher einsetzen.' + ], + corePatterns: [ + { target: 'Naa koy reservation.', gloss: 'Ich habe eine Reservierung.' }, + { target: 'Gusto ko ani.', gloss: 'Ich möchte das.' }, + { target: 'Asa ang pantalan?', gloss: 'Wo ist der Hafen?' }, + { target: 'Kinahanglan nako ug tabang.', gloss: 'Ich brauche Hilfe.' }, + { target: 'Amping.', gloss: 'Pass auf dich auf.' } + ], + speakingPrompts: [{ + title: 'Ein Reisetag auf Cebu', + prompt: 'Verbinde Unterkunft, Essen, Transport und Hilfe in einem kurzen Reisetag.', + cue: 'Naa koy reservation. Gusto ko ani. Asa ang pantalan? Amping.' + }], + practicalTasks: [{ + title: 'Cebu-Reiseprobe', + text: 'Spiele einen Reisetag nach: einchecken, essen, zum Hafen fahren und bei Bedarf Hilfe holen.' + }] + } +}; + +export const BISAYA_CEBU_TRAVEL_LESSONS = [ + { week: 16, day: 2, num: 155, type: 'conversation', title: 'Cebu ankommen & einchecken', desc: 'Einchecken, Reservierung und Zimmerfrage auf Cebu', targetMin: 18, targetScore: 78, review: false, cultural: 'Ein höflicher Gruß und ein kurzes Salamat kaayo öffnen viele Gespräche freundlich.' }, + { week: 16, day: 2, num: 156, type: 'vocab', title: 'Unterkunft & kleine Probleme', desc: 'Zimmer, Schlüssel, Wasser und einfache Bitten an der Rezeption', targetMin: 16, targetScore: 82, review: true, cultural: null }, + { week: 16, day: 3, num: 157, type: 'conversation', title: 'Restaurant & Bestellen', desc: 'Bestellen, nach Wasser und Preis fragen und bezahlen', targetMin: 18, targetScore: 78, review: false, cultural: 'Lami kaayo ist eine warme, natürliche Reaktion auf gutes Essen.' }, + { week: 16, day: 3, num: 158, type: 'vocab', title: 'Markt, Souvenirs & Bezahlen', desc: 'Preise, Wechselgeld und höflicher Einkauf auf dem Markt', targetMin: 16, targetScore: 82, review: true, cultural: null }, + { week: 16, day: 4, num: 159, type: 'conversation', title: 'Ausflüge, Inseln & Transport', desc: 'Hafen, Fahrt und Ausstieg für Ausflüge auf Cebu', targetMin: 18, targetScore: 78, review: false, cultural: 'Bei Fahrten mit Jeepney, Bus oder Boot helfen kurze Orts- und Zeitfragen am meisten.' }, + { week: 16, day: 4, num: 160, type: 'conversation', title: 'Hilfe unterwegs & Reiseprobleme', desc: 'Tasche verloren, Apotheke finden und Hilfe holen', targetMin: 18, targetScore: 78, review: false, cultural: null }, + { week: 16, day: 5, num: 161, type: 'review', title: 'Cebu-Reisepfad – Abschluss', desc: 'Prüfung und Wiederholung der wichtigsten Reisesituationen', targetMin: 22, targetScore: 82, review: false, cultural: 'Kurze, klare und höfliche Sätze sind unterwegs wirksamer als lange Erklärungen.' } +]; diff --git a/backend/scripts/create-bisaya-course-content.js b/backend/scripts/create-bisaya-course-content.js index 42815db..944770c 100755 --- a/backend/scripts/create-bisaya-course-content.js +++ b/backend/scripts/create-bisaya-course-content.js @@ -18,6 +18,7 @@ import { BISAYA_DIDACTICS_24_43, BISAYA_LESSONS_24_43_BY_NUMBER, BISAYA_RELATION import { BISAYA_PHASE3_DIDACTICS, BISAYA_PHASE3_LESSONS } from './bisaya-course-phase3-extension.js'; import { BISAYA_PHASE4_DIDACTICS, BISAYA_PHASE4_LESSONS } from './bisaya-course-phase4-extension.js'; import { BISAYA_PHASE5_DIDACTICS, BISAYA_PHASE5_LESSONS } from './bisaya-course-phase5-extension.js'; +import { BISAYA_CEBU_TRAVEL_DIDACTICS, BISAYA_CEBU_TRAVEL_LESSONS } from './bisaya-course-cebu-travel-extension.js'; function withTypeName(exerciseTypeName, exercise) { return { @@ -33,7 +34,8 @@ const GENERATED_BISAYA_DIDACTICS = { ...BISAYA_DIDACTICS_24_43, ...BISAYA_PHASE3_DIDACTICS, ...BISAYA_PHASE4_DIDACTICS, - ...BISAYA_PHASE5_DIDACTICS + ...BISAYA_PHASE5_DIDACTICS, + ...BISAYA_CEBU_TRAVEL_DIDACTICS }; const SAFE_EXERCISE_UPDATE_TITLES = new Set([ @@ -49,7 +51,8 @@ const SAFE_EXERCISE_UPDATE_TITLES = new Set([ 'Bitten & Fragen', ...BISAYA_PHASE3_LESSONS.map((lesson) => lesson.title), ...BISAYA_PHASE4_LESSONS.map((lesson) => lesson.title), - ...BISAYA_PHASE5_LESSONS.map((lesson) => lesson.title) + ...BISAYA_PHASE5_LESSONS.map((lesson) => lesson.title), + ...BISAYA_CEBU_TRAVEL_LESSONS.map((lesson) => lesson.title) ]); function normalizeText(value) { diff --git a/backend/scripts/create-bisaya-course.js b/backend/scripts/create-bisaya-course.js index 92977ef..18d69a9 100755 --- a/backend/scripts/create-bisaya-course.js +++ b/backend/scripts/create-bisaya-course.js @@ -16,6 +16,7 @@ import { BISAYA_DIDACTICS_24_43, BISAYA_LESSONS_24_43, BISAYA_RELATIONSHIP_ANCHO import { BISAYA_PHASE3_DIDACTICS, BISAYA_PHASE3_LESSONS } from './bisaya-course-phase3-extension.js'; import { BISAYA_PHASE4_DIDACTICS, BISAYA_PHASE4_LESSONS } from './bisaya-course-phase4-extension.js'; import { BISAYA_PHASE5_DIDACTICS, BISAYA_PHASE5_LESSONS } from './bisaya-course-phase5-extension.js'; +import { BISAYA_CEBU_TRAVEL_DIDACTICS, BISAYA_CEBU_TRAVEL_LESSONS } from './bisaya-course-cebu-travel-extension.js'; const LESSON_DIDACTICS = { 'Begrüßungen & Höflichkeit': { @@ -868,7 +869,8 @@ const LESSON_DIDACTICS = { ...BISAYA_DIDACTICS_24_43, ...BISAYA_PHASE3_DIDACTICS, ...BISAYA_PHASE4_DIDACTICS, - ...BISAYA_PHASE5_DIDACTICS + ...BISAYA_PHASE5_DIDACTICS, + ...BISAYA_CEBU_TRAVEL_DIDACTICS }; const LESSONS = [ @@ -993,10 +995,11 @@ const LESSONS = [ ...BISAYA_PHASE3_LESSONS, ...BISAYA_PHASE4_LESSONS, - ...BISAYA_PHASE5_LESSONS + ...BISAYA_PHASE5_LESSONS, + ...BISAYA_CEBU_TRAVEL_LESSONS ]; -const BISAYA_FAMILY_COURSE_TITLE = 'Bisaya für Familien - Alltag & Stabilisierung'; +const BISAYA_FAMILY_COURSE_TITLE = 'Bisaya für Cebu – Reise, Familie & Alltag'; async function createBisayaCourse(languageId, ownerHashedId) { try { @@ -1034,7 +1037,7 @@ async function createBisayaCourse(languageId, ownerHashedId) { const course = await VocabCourse.create({ ownerUserId: user.id, title: BISAYA_FAMILY_COURSE_TITLE, - description: 'Lerne Bisaya (Cebuano) praxisnah für den Familienalltag. Der Pfad verbindet Schnellstart, Alltagsmodule und Stabilisierungsblöcke mit Spiralwiederholung, Fehlertraining und freier Produktion.', + description: 'Lerne Bisaya (Cebuano) praxisnah für Reisen und längere Aufenthalte auf Cebu – mit Unterkunft, Essen, Markt, Ausflügen, Familie, Höflichkeit und Hilfe unterwegs.', languageId: Number(languageId), difficultyLevel: 1, isPublic: true, diff --git a/backend/scripts/extend-bisaya-course-cebu-travel.js b/backend/scripts/extend-bisaya-course-cebu-travel.js new file mode 100644 index 0000000..850d062 --- /dev/null +++ b/backend/scripts/extend-bisaya-course-cebu-travel.js @@ -0,0 +1,69 @@ +#!/usr/bin/env node +/** Fügt Bisaya-Kursen den optionalen Reisepfad „Cebu – Reise & Alltag“ hinzu. */ + +import { sequelize } from '../utils/sequelize.js'; +import VocabCourse from '../models/community/vocab_course.js'; +import VocabCourseLesson from '../models/community/vocab_course_lesson.js'; +import { BISAYA_CEBU_TRAVEL_DIDACTICS, BISAYA_CEBU_TRAVEL_LESSONS } from './bisaya-course-cebu-travel-extension.js'; + +async function extendBisayaCourseForCebuTravel() { + await sequelize.authenticate(); + const [language] = await sequelize.query( + `SELECT id FROM community.vocab_language WHERE name = 'Bisaya' LIMIT 1`, + { type: sequelize.QueryTypes.SELECT } + ); + if (!language) throw new Error('Bisaya-Sprache nicht gefunden.'); + + const courses = await VocabCourse.findAll({ where: { languageId: language.id } }); + let addedLessons = 0; + for (const course of courses) { + await course.update({ + title: 'Bisaya für Cebu – Reise, Familie & Alltag', + description: 'Praxisnahes Cebuano für Reisen und längere Aufenthalte auf Cebu: Unterkunft, Essen, Markt, Ausflüge, Familie, Höflichkeit und Hilfe unterwegs.' + }); + for (const lessonData of BISAYA_CEBU_TRAVEL_LESSONS) { + const existing = await VocabCourseLesson.findOne({ + where: { courseId: course.id, lessonNumber: lessonData.num } + }); + if (existing) continue; + const didactics = BISAYA_CEBU_TRAVEL_DIDACTICS[lessonData.title]; + await VocabCourseLesson.create({ + courseId: course.id, + chapterId: null, + lessonNumber: lessonData.num, + title: lessonData.title, + description: lessonData.desc, + weekNumber: lessonData.week, + dayNumber: lessonData.day, + lessonType: lessonData.type, + culturalNotes: lessonData.cultural, + learningGoals: didactics.learningGoals, + corePatterns: didactics.corePatterns, + grammarFocus: didactics.grammarFocus || [], + speakingPrompts: didactics.speakingPrompts || [], + practicalTasks: didactics.practicalTasks || [], + targetMinutes: lessonData.targetMin, + targetScorePercent: lessonData.targetScore, + requiresReview: lessonData.review, + didacticMode: 'travel_practice', + phaseLabel: 'Cebu – Reise & Alltag', + blockNumber: 8, + difficultyWeight: 1, + newUnitTarget: 8, + reviewWeight: lessonData.review ? 2 : 1, + isIntensiveReview: false + }); + addedLessons += 1; + console.log(`✅ Kurs ${course.id}: Lektion ${lessonData.num} – ${lessonData.title} ergänzt`); + } + } + console.log(`Cebu-Reisepfad: ${addedLessons} Lektionen ergänzt.`); +} + +extendBisayaCourseForCebuTravel() + .then(() => sequelize.close()) + .catch((error) => { + console.error('❌ Fehler:', error); + sequelize.close(); + process.exitCode = 1; + }); diff --git a/backend/scripts/sync-vocab-course-content.js b/backend/scripts/sync-vocab-course-content.js index 2a1337e..d3a5f64 100755 --- a/backend/scripts/sync-vocab-course-content.js +++ b/backend/scripts/sync-vocab-course-content.js @@ -27,6 +27,7 @@ const SAFE_SYNC_STEPS = { 'backend/scripts/extend-bisaya-course-phase3.js', 'backend/scripts/extend-bisaya-course-phase4.js', 'backend/scripts/extend-bisaya-course-phase5.js', + 'backend/scripts/extend-bisaya-course-cebu-travel.js', // Alte Zusatzlektionen 44/45 teilen Nummern mit Phase 3. Erst der // Content-Schritt stellt ihre sichtbaren Zahlentitel wieder her; danach // kann die Didaktik die Zahlmuster statt der Besuchsmuster einspielen. diff --git a/backend/scripts/update-bisaya-didactics.js b/backend/scripts/update-bisaya-didactics.js index 5c4517d..377b545 100755 --- a/backend/scripts/update-bisaya-didactics.js +++ b/backend/scripts/update-bisaya-didactics.js @@ -19,6 +19,7 @@ import { import { BISAYA_PHASE3_DIDACTICS } from './bisaya-course-phase3-extension.js'; import { BISAYA_PHASE4_DIDACTICS } from './bisaya-course-phase4-extension.js'; import { BISAYA_PHASE5_DIDACTICS } from './bisaya-course-phase5-extension.js'; +import { BISAYA_CEBU_TRAVEL_DIDACTICS } from './bisaya-course-cebu-travel-extension.js'; /** Alte Kurstitel → aktueller Schlüssel in LESSON_DIDACTICS (bestehende Datenbanken). */ export const LEGACY_DIDACTICS_TITLE_MAP = { @@ -501,7 +502,8 @@ export const LESSON_DIDACTICS = { ...BISAYA_DIDACTICS_24_43, ...BISAYA_PHASE3_DIDACTICS, ...BISAYA_PHASE4_DIDACTICS, - ...BISAYA_PHASE5_DIDACTICS + ...BISAYA_PHASE5_DIDACTICS, + ...BISAYA_CEBU_TRAVEL_DIDACTICS }; function resolveDidacticsForLesson(lesson) {