diff --git a/backend/controllers/adminController.js b/backend/controllers/adminController.js index f6919cf..b7d821b 100644 --- a/backend/controllers/adminController.js +++ b/backend/controllers/adminController.js @@ -16,6 +16,7 @@ class AdminController { this.adminForceFalukantPregnancy = this.adminForceFalukantPregnancy.bind(this); this.adminClearFalukantPregnancy = this.adminClearFalukantPregnancy.bind(this); this.adminForceFalukantBirth = this.adminForceFalukantBirth.bind(this); + this.adminGetPotentialFathersForCharacter = this.adminGetPotentialFathersForCharacter.bind(this); this.getFalukantUserBranches = this.getFalukantUserBranches.bind(this); this.updateFalukantStock = this.updateFalukantStock.bind(this); this.addFalukantStock = this.addFalukantStock.bind(this); @@ -390,6 +391,18 @@ class AdminController { } } + async adminGetPotentialFathersForCharacter(req, res) { + try { + const { userid: userId } = req.headers; + const { characterId } = req.params; + const response = await AdminService.adminGetPotentialFathersForCharacter(userId, characterId); + res.status(200).json(response); + } catch (error) { + console.log(error); + res.status(400).json({ error: error.message }); + } + } + async adminClearFalukantPregnancy(req, res) { try { const { userid: userId } = req.headers; diff --git a/backend/migrations/20260331000000-add-vocab-lesson-phase1-fields.cjs b/backend/migrations/20260331000000-add-vocab-lesson-phase1-fields.cjs new file mode 100644 index 0000000..d64531c --- /dev/null +++ b/backend/migrations/20260331000000-add-vocab-lesson-phase1-fields.cjs @@ -0,0 +1,44 @@ +'use strict'; + +module.exports = { + async up(queryInterface, Sequelize) { + await queryInterface.sequelize.query(` + ALTER TABLE community.vocab_course_lesson + ADD COLUMN IF NOT EXISTS didactic_mode TEXT, + ADD COLUMN IF NOT EXISTS phase_label TEXT, + ADD COLUMN IF NOT EXISTS block_number INTEGER, + ADD COLUMN IF NOT EXISTS difficulty_weight INTEGER, + ADD COLUMN IF NOT EXISTS new_unit_target INTEGER, + ADD COLUMN IF NOT EXISTS review_weight INTEGER, + ADD COLUMN IF NOT EXISTS is_intensive_review BOOLEAN NOT NULL DEFAULT FALSE; + + COMMENT ON COLUMN community.vocab_course_lesson.didactic_mode IS + 'Didaktischer Modus der Lektion, z.B. core_input, guided_dialogue, intensive_review oder checkpoint.'; + COMMENT ON COLUMN community.vocab_course_lesson.phase_label IS + 'Übergeordnete Lernphase, z.B. quickstart, daily_life oder stabilization.'; + COMMENT ON COLUMN community.vocab_course_lesson.block_number IS + 'Inhaltlicher Block für Konsolidierungs- und Wiederholungswellen.'; + COMMENT ON COLUMN community.vocab_course_lesson.difficulty_weight IS + 'Grobe relative Schwierigkeit der Lektion von leicht bis schwer.'; + COMMENT ON COLUMN community.vocab_course_lesson.new_unit_target IS + 'Empfohlene Zahl neuer Spracheinheiten in dieser Lektion.'; + COMMENT ON COLUMN community.vocab_course_lesson.review_weight IS + 'Wie stark Wiederholung in dieser Lektion dominieren soll, typischerweise 0 bis 100.'; + COMMENT ON COLUMN community.vocab_course_lesson.is_intensive_review IS + 'Markiert Lektionen, die als intensive Wiederholungsphase gedacht sind.'; + `); + }, + + async down(queryInterface, Sequelize) { + await queryInterface.sequelize.query(` + ALTER TABLE community.vocab_course_lesson + DROP COLUMN IF EXISTS is_intensive_review, + DROP COLUMN IF EXISTS review_weight, + DROP COLUMN IF EXISTS new_unit_target, + DROP COLUMN IF EXISTS difficulty_weight, + DROP COLUMN IF EXISTS block_number, + DROP COLUMN IF EXISTS phase_label, + DROP COLUMN IF EXISTS didactic_mode; + `); + } +}; diff --git a/backend/models/community/vocab_course_lesson.js b/backend/models/community/vocab_course_lesson.js index 62df690..e06b7a8 100644 --- a/backend/models/community/vocab_course_lesson.js +++ b/backend/models/community/vocab_course_lesson.js @@ -48,6 +48,42 @@ VocabCourseLesson.init({ defaultValue: 'vocab', field: 'lesson_type' }, + didacticMode: { + type: DataTypes.TEXT, + allowNull: true, + field: 'didactic_mode' + }, + phaseLabel: { + type: DataTypes.TEXT, + allowNull: true, + field: 'phase_label' + }, + blockNumber: { + type: DataTypes.INTEGER, + allowNull: true, + field: 'block_number' + }, + difficultyWeight: { + type: DataTypes.INTEGER, + allowNull: true, + field: 'difficulty_weight' + }, + newUnitTarget: { + type: DataTypes.INTEGER, + allowNull: true, + field: 'new_unit_target' + }, + reviewWeight: { + type: DataTypes.INTEGER, + allowNull: true, + field: 'review_weight' + }, + isIntensiveReview: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: false, + field: 'is_intensive_review' + }, audioUrl: { type: DataTypes.TEXT, allowNull: true, diff --git a/backend/routers/adminRouter.js b/backend/routers/adminRouter.js index a8bdec9..5a7a25b 100644 --- a/backend/routers/adminRouter.js +++ b/backend/routers/adminRouter.js @@ -43,6 +43,7 @@ router.post('/contacts/answer', authenticate, adminController.answerContact); router.post('/falukant/searchuser', authenticate, adminController.searchUser); router.get('/falukant/getuser/:id', authenticate, adminController.getFalukantUserById); router.post('/falukant/edituser', authenticate, adminController.changeFalukantUser); +router.get('/falukant/character/:characterId/potential-fathers', authenticate, adminController.adminGetPotentialFathersForCharacter); router.post('/falukant/character/force-pregnancy', authenticate, adminController.adminForceFalukantPregnancy); router.post('/falukant/character/clear-pregnancy', authenticate, adminController.adminClearFalukantPregnancy); router.post('/falukant/character/force-birth', authenticate, adminController.adminForceFalukantBirth); diff --git a/backend/scripts/add-bisaya-week1-lessons.js b/backend/scripts/add-bisaya-week1-lessons.js index 6288709..bb05344 100644 --- a/backend/scripts/add-bisaya-week1-lessons.js +++ b/backend/scripts/add-bisaya-week1-lessons.js @@ -9,6 +9,7 @@ import { sequelize } from '../utils/sequelize.js'; import VocabCourseLesson from '../models/community/vocab_course_lesson.js'; +import { getBisayaLessonPedagogy } from './bisaya-course-phase2-pedagogy.js'; const LESSONS_TO_ADD = [ { @@ -98,6 +99,8 @@ async function addBisayaWeek1Lessons() { continue; } + const pedagogy = getBisayaLessonPedagogy(lessonData.lessonNumber) || {}; + await VocabCourseLesson.create({ courseId: course.id, chapterId: null, @@ -113,7 +116,14 @@ async function addBisayaWeek1Lessons() { speakingPrompts: lessonData.speakingPrompts || [], targetMinutes: lessonData.targetMinutes, targetScorePercent: lessonData.targetScorePercent, - requiresReview: lessonData.requiresReview + requiresReview: lessonData.requiresReview, + didacticMode: pedagogy.didacticMode || null, + phaseLabel: pedagogy.phaseLabel || null, + blockNumber: pedagogy.blockNumber ?? null, + difficultyWeight: pedagogy.difficultyWeight ?? null, + newUnitTarget: pedagogy.newUnitTarget ?? null, + reviewWeight: pedagogy.reviewWeight ?? null, + isIntensiveReview: Boolean(pedagogy.isIntensiveReview) }); console.log(` ✅ Lektion ${lessonData.lessonNumber}: "${lessonData.title}" hinzugefügt`); diff --git a/backend/scripts/apply-bisaya-course-refresh.js b/backend/scripts/apply-bisaya-course-refresh.js index 099d2a6..8cad4ce 100644 --- a/backend/scripts/apply-bisaya-course-refresh.js +++ b/backend/scripts/apply-bisaya-course-refresh.js @@ -12,6 +12,7 @@ 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'; +import { getBisayaLessonPedagogy } from './bisaya-course-phase2-pedagogy.js'; const LESSON_DIDACTICS = { 'Begrüßungen & Höflichkeit': { @@ -171,14 +172,23 @@ async function applyBisayaCourseRefresh() { for (const lesson of lessons) { const didactics = LESSON_DIDACTICS[lesson.title]; - if (!didactics) continue; + const pedagogy = getBisayaLessonPedagogy(lesson.lessonNumber); + if (!didactics && !pedagogy) continue; + const normalizedDidactics = didactics || {}; await lesson.update({ - learningGoals: didactics.learningGoals || [], - corePatterns: didactics.corePatterns || [], - grammarFocus: didactics.grammarFocus || [], - speakingPrompts: didactics.speakingPrompts || [], - practicalTasks: didactics.practicalTasks || [] + learningGoals: normalizedDidactics.learningGoals || [], + corePatterns: normalizedDidactics.corePatterns || [], + grammarFocus: normalizedDidactics.grammarFocus || [], + speakingPrompts: normalizedDidactics.speakingPrompts || [], + practicalTasks: normalizedDidactics.practicalTasks || [], + didacticMode: pedagogy?.didacticMode || null, + phaseLabel: pedagogy?.phaseLabel || null, + blockNumber: pedagogy?.blockNumber ?? null, + difficultyWeight: pedagogy?.difficultyWeight ?? null, + newUnitTarget: pedagogy?.newUnitTarget ?? null, + reviewWeight: pedagogy?.reviewWeight ?? null, + isIntensiveReview: Boolean(pedagogy?.isIntensiveReview) }); updatedLessons++; } diff --git a/backend/scripts/bisaya-course-phase2-pedagogy.js b/backend/scripts/bisaya-course-phase2-pedagogy.js new file mode 100644 index 0000000..a782211 --- /dev/null +++ b/backend/scripts/bisaya-course-phase2-pedagogy.js @@ -0,0 +1,156 @@ +export const BISAYA_LESSON_PEDAGOGY = { + 1: { phaseLabel: 'quickstart', blockNumber: 1, didacticMode: 'guided_dialogue', difficultyWeight: 2, newUnitTarget: 5, reviewWeight: 15, isIntensiveReview: false }, + 2: { phaseLabel: 'quickstart', blockNumber: 1, didacticMode: 'core_input', difficultyWeight: 3, newUnitTarget: 8, reviewWeight: 20, isIntensiveReview: false }, + 3: { phaseLabel: 'quickstart', blockNumber: 1, didacticMode: 'core_input', difficultyWeight: 2, newUnitTarget: 7, reviewWeight: 20, isIntensiveReview: false }, + 4: { phaseLabel: 'quickstart', blockNumber: 1, didacticMode: 'guided_dialogue', difficultyWeight: 3, newUnitTarget: 5, reviewWeight: 25, isIntensiveReview: false }, + 5: { phaseLabel: 'quickstart', blockNumber: 1, didacticMode: 'guided_dialogue', difficultyWeight: 3, newUnitTarget: 4, reviewWeight: 25, isIntensiveReview: false }, + 6: { phaseLabel: 'quickstart', blockNumber: 1, didacticMode: 'core_input', difficultyWeight: 3, newUnitTarget: 7, reviewWeight: 25, isIntensiveReview: false }, + 7: { phaseLabel: 'quickstart', blockNumber: 1, didacticMode: 'guided_dialogue', difficultyWeight: 3, newUnitTarget: 5, reviewWeight: 30, isIntensiveReview: false }, + 8: { phaseLabel: 'quickstart', blockNumber: 1, didacticMode: 'core_input', difficultyWeight: 2, newUnitTarget: 6, reviewWeight: 30, isIntensiveReview: false }, + 9: { phaseLabel: 'quickstart', blockNumber: 1, didacticMode: 'intensive_review', difficultyWeight: 3, newUnitTarget: 0, reviewWeight: 90, isIntensiveReview: true }, + 10: { phaseLabel: 'quickstart', blockNumber: 1, didacticMode: 'checkpoint', difficultyWeight: 3, newUnitTarget: 0, reviewWeight: 95, isIntensiveReview: false }, + 11: { phaseLabel: 'quickstart', blockNumber: 2, didacticMode: 'guided_dialogue', difficultyWeight: 3, newUnitTarget: 5, reviewWeight: 25, isIntensiveReview: false }, + 12: { phaseLabel: 'quickstart', blockNumber: 2, didacticMode: 'core_input', difficultyWeight: 2, newUnitTarget: 7, reviewWeight: 25, isIntensiveReview: false }, + 13: { phaseLabel: 'quickstart', blockNumber: 2, didacticMode: 'guided_dialogue', difficultyWeight: 3, newUnitTarget: 5, reviewWeight: 30, isIntensiveReview: false }, + 14: { phaseLabel: 'quickstart', blockNumber: 2, didacticMode: 'core_input', difficultyWeight: 3, newUnitTarget: 6, reviewWeight: 30, isIntensiveReview: false }, + 15: { phaseLabel: 'quickstart', blockNumber: 2, didacticMode: 'pattern_drill', difficultyWeight: 4, newUnitTarget: 4, reviewWeight: 40, isIntensiveReview: false }, + 16: { phaseLabel: 'quickstart', blockNumber: 2, didacticMode: 'core_input', difficultyWeight: 3, newUnitTarget: 6, reviewWeight: 35, isIntensiveReview: false }, + 17: { phaseLabel: 'quickstart', blockNumber: 2, didacticMode: 'guided_dialogue', difficultyWeight: 4, newUnitTarget: 5, reviewWeight: 35, isIntensiveReview: false }, + 18: { phaseLabel: 'quickstart', blockNumber: 2, didacticMode: 'core_input', difficultyWeight: 4, newUnitTarget: 7, reviewWeight: 35, isIntensiveReview: false }, + 19: { phaseLabel: 'quickstart', blockNumber: 2, didacticMode: 'intensive_review', difficultyWeight: 4, newUnitTarget: 0, reviewWeight: 92, isIntensiveReview: true }, + 20: { phaseLabel: 'quickstart', blockNumber: 2, didacticMode: 'checkpoint', difficultyWeight: 4, newUnitTarget: 0, reviewWeight: 96, isIntensiveReview: false }, + 21: { phaseLabel: 'daily_life', blockNumber: 3, didacticMode: 'guided_dialogue', difficultyWeight: 3, newUnitTarget: 5, reviewWeight: 35, isIntensiveReview: false }, + 22: { phaseLabel: 'daily_life', blockNumber: 3, didacticMode: 'core_input', difficultyWeight: 3, newUnitTarget: 7, reviewWeight: 35, isIntensiveReview: false }, + 23: { phaseLabel: 'daily_life', blockNumber: 3, didacticMode: 'guided_dialogue', difficultyWeight: 4, newUnitTarget: 5, reviewWeight: 40, isIntensiveReview: false }, + 24: { phaseLabel: 'daily_life', blockNumber: 3, didacticMode: 'core_input', difficultyWeight: 4, newUnitTarget: 6, reviewWeight: 40, isIntensiveReview: false }, + 25: { phaseLabel: 'daily_life', blockNumber: 3, didacticMode: 'pattern_drill', difficultyWeight: 4, newUnitTarget: 4, reviewWeight: 45, isIntensiveReview: false }, + 26: { phaseLabel: 'daily_life', blockNumber: 3, didacticMode: 'guided_dialogue', difficultyWeight: 4, newUnitTarget: 5, reviewWeight: 45, isIntensiveReview: false }, + 27: { phaseLabel: 'daily_life', blockNumber: 3, didacticMode: 'guided_dialogue', difficultyWeight: 3, newUnitTarget: 5, reviewWeight: 40, isIntensiveReview: false }, + 28: { phaseLabel: 'daily_life', blockNumber: 3, didacticMode: 'core_input', difficultyWeight: 3, newUnitTarget: 6, reviewWeight: 40, isIntensiveReview: false }, + 29: { phaseLabel: 'daily_life', blockNumber: 3, didacticMode: 'intensive_review', difficultyWeight: 4, newUnitTarget: 0, reviewWeight: 94, isIntensiveReview: true }, + 30: { phaseLabel: 'daily_life', blockNumber: 3, didacticMode: 'checkpoint', difficultyWeight: 4, newUnitTarget: 0, reviewWeight: 97, isIntensiveReview: false }, + 31: { phaseLabel: 'stabilization', blockNumber: 4, didacticMode: 'real_life_scenario', difficultyWeight: 4, newUnitTarget: 4, reviewWeight: 55, isIntensiveReview: false }, + 32: { phaseLabel: 'stabilization', blockNumber: 4, didacticMode: 'intensive_review', difficultyWeight: 4, newUnitTarget: 0, reviewWeight: 95, isIntensiveReview: true }, + 33: { phaseLabel: 'stabilization', blockNumber: 4, didacticMode: 'real_life_scenario', difficultyWeight: 4, newUnitTarget: 4, reviewWeight: 60, isIntensiveReview: false }, + 34: { phaseLabel: 'stabilization', blockNumber: 4, didacticMode: 'intensive_review', difficultyWeight: 4, newUnitTarget: 0, reviewWeight: 95, isIntensiveReview: true }, + 35: { phaseLabel: 'stabilization', blockNumber: 4, didacticMode: 'real_life_scenario', difficultyWeight: 5, newUnitTarget: 3, reviewWeight: 65, isIntensiveReview: false }, + 36: { phaseLabel: 'stabilization', blockNumber: 4, didacticMode: 'intensive_review', difficultyWeight: 5, newUnitTarget: 0, reviewWeight: 98, isIntensiveReview: true }, + 37: { phaseLabel: 'stabilization', blockNumber: 4, didacticMode: 'real_life_scenario', difficultyWeight: 5, newUnitTarget: 3, reviewWeight: 70, isIntensiveReview: false }, + 38: { phaseLabel: 'stabilization', blockNumber: 4, didacticMode: 'checkpoint', difficultyWeight: 5, newUnitTarget: 0, reviewWeight: 99, isIntensiveReview: false }, + 39: { phaseLabel: 'stabilization', blockNumber: 4, didacticMode: 'checkpoint', difficultyWeight: 5, newUnitTarget: 0, reviewWeight: 100, isIntensiveReview: false }, + 40: { phaseLabel: 'stabilization', blockNumber: 4, didacticMode: 'real_life_scenario', difficultyWeight: 2, newUnitTarget: 2, reviewWeight: 50, isIntensiveReview: false }, + 41: { phaseLabel: 'daily_life', blockNumber: 5, didacticMode: 'guided_dialogue', difficultyWeight: 3, newUnitTarget: 5, reviewWeight: 40, isIntensiveReview: false }, + 42: { phaseLabel: 'daily_life', blockNumber: 5, didacticMode: 'core_input', difficultyWeight: 3, newUnitTarget: 6, reviewWeight: 40, isIntensiveReview: false }, + 43: { phaseLabel: 'daily_life', blockNumber: 5, didacticMode: 'pattern_drill', difficultyWeight: 4, newUnitTarget: 4, reviewWeight: 45, isIntensiveReview: false }, + 44: { phaseLabel: 'daily_life', blockNumber: 5, didacticMode: 'guided_dialogue', difficultyWeight: 3, newUnitTarget: 5, reviewWeight: 45, isIntensiveReview: false }, + 45: { phaseLabel: 'daily_life', blockNumber: 5, didacticMode: 'intensive_review', difficultyWeight: 4, newUnitTarget: 0, reviewWeight: 95, isIntensiveReview: true }, + 46: { phaseLabel: 'daily_life', blockNumber: 5, didacticMode: 'intensive_review', difficultyWeight: 3, newUnitTarget: 0, reviewWeight: 90, isIntensiveReview: true }, + 47: { phaseLabel: 'daily_life', blockNumber: 5, didacticMode: 'guided_dialogue', difficultyWeight: 4, newUnitTarget: 5, reviewWeight: 45, isIntensiveReview: false }, + 48: { phaseLabel: 'daily_life', blockNumber: 5, didacticMode: 'core_input', difficultyWeight: 3, newUnitTarget: 6, reviewWeight: 45, isIntensiveReview: false }, + 49: { phaseLabel: 'daily_life', blockNumber: 5, didacticMode: 'intensive_review', difficultyWeight: 4, newUnitTarget: 0, reviewWeight: 96, isIntensiveReview: true }, + 50: { phaseLabel: 'daily_life', blockNumber: 5, didacticMode: 'checkpoint', difficultyWeight: 4, newUnitTarget: 0, reviewWeight: 97, isIntensiveReview: false }, + 51: { phaseLabel: 'stabilization', blockNumber: 6, didacticMode: 'guided_dialogue', difficultyWeight: 4, newUnitTarget: 5, reviewWeight: 50, isIntensiveReview: false }, + 52: { phaseLabel: 'stabilization', blockNumber: 6, didacticMode: 'core_input', difficultyWeight: 3, newUnitTarget: 6, reviewWeight: 50, isIntensiveReview: false }, + 53: { phaseLabel: 'stabilization', blockNumber: 6, didacticMode: 'guided_dialogue', difficultyWeight: 4, newUnitTarget: 5, reviewWeight: 55, isIntensiveReview: false }, + 54: { phaseLabel: 'stabilization', blockNumber: 6, didacticMode: 'core_input', difficultyWeight: 3, newUnitTarget: 6, reviewWeight: 55, isIntensiveReview: false }, + 55: { phaseLabel: 'stabilization', blockNumber: 6, didacticMode: 'real_life_scenario', difficultyWeight: 5, newUnitTarget: 3, reviewWeight: 65, isIntensiveReview: false }, + 56: { phaseLabel: 'stabilization', blockNumber: 6, didacticMode: 'intensive_review', difficultyWeight: 5, newUnitTarget: 0, reviewWeight: 98, isIntensiveReview: true }, + 57: { phaseLabel: 'stabilization', blockNumber: 6, didacticMode: 'real_life_scenario', difficultyWeight: 5, newUnitTarget: 3, reviewWeight: 70, isIntensiveReview: false }, + 58: { phaseLabel: 'stabilization', blockNumber: 6, didacticMode: 'checkpoint', difficultyWeight: 5, newUnitTarget: 0, reviewWeight: 99, isIntensiveReview: false }, + 59: { phaseLabel: 'stabilization', blockNumber: 6, didacticMode: 'checkpoint', difficultyWeight: 5, newUnitTarget: 0, reviewWeight: 100, isIntensiveReview: false }, + 60: { phaseLabel: 'stabilization', blockNumber: 6, didacticMode: 'real_life_scenario', difficultyWeight: 2, newUnitTarget: 2, reviewWeight: 60, isIntensiveReview: false }, + 61: { phaseLabel: 'daily_life', blockNumber: 7, didacticMode: 'guided_dialogue', difficultyWeight: 3, newUnitTarget: 5, reviewWeight: 45, isIntensiveReview: false }, + 62: { phaseLabel: 'daily_life', blockNumber: 7, didacticMode: 'core_input', difficultyWeight: 3, newUnitTarget: 6, reviewWeight: 45, isIntensiveReview: false }, + 63: { phaseLabel: 'daily_life', blockNumber: 7, didacticMode: 'pattern_drill', difficultyWeight: 4, newUnitTarget: 4, reviewWeight: 50, isIntensiveReview: false }, + 64: { phaseLabel: 'daily_life', blockNumber: 7, didacticMode: 'guided_dialogue', difficultyWeight: 3, newUnitTarget: 5, reviewWeight: 50, isIntensiveReview: false }, + 65: { phaseLabel: 'daily_life', blockNumber: 7, didacticMode: 'intensive_review', difficultyWeight: 4, newUnitTarget: 0, reviewWeight: 95, isIntensiveReview: true }, + 66: { phaseLabel: 'daily_life', blockNumber: 7, didacticMode: 'intensive_review', difficultyWeight: 3, newUnitTarget: 0, reviewWeight: 92, isIntensiveReview: true }, + 67: { phaseLabel: 'daily_life', blockNumber: 7, didacticMode: 'guided_dialogue', difficultyWeight: 3, newUnitTarget: 5, reviewWeight: 50, isIntensiveReview: false }, + 68: { phaseLabel: 'daily_life', blockNumber: 7, didacticMode: 'core_input', difficultyWeight: 3, newUnitTarget: 6, reviewWeight: 50, isIntensiveReview: false }, + 69: { phaseLabel: 'daily_life', blockNumber: 7, didacticMode: 'intensive_review', difficultyWeight: 4, newUnitTarget: 0, reviewWeight: 96, isIntensiveReview: true }, + 70: { phaseLabel: 'daily_life', blockNumber: 7, didacticMode: 'checkpoint', difficultyWeight: 4, newUnitTarget: 0, reviewWeight: 97, isIntensiveReview: false }, + 71: { phaseLabel: 'daily_life', blockNumber: 8, didacticMode: 'guided_dialogue', difficultyWeight: 4, newUnitTarget: 5, reviewWeight: 50, isIntensiveReview: false }, + 72: { phaseLabel: 'daily_life', blockNumber: 8, didacticMode: 'core_input', difficultyWeight: 3, newUnitTarget: 6, reviewWeight: 50, isIntensiveReview: false }, + 73: { phaseLabel: 'daily_life', blockNumber: 8, didacticMode: 'pattern_drill', difficultyWeight: 4, newUnitTarget: 4, reviewWeight: 55, isIntensiveReview: false }, + 74: { phaseLabel: 'daily_life', blockNumber: 8, didacticMode: 'guided_dialogue', difficultyWeight: 4, newUnitTarget: 5, reviewWeight: 55, isIntensiveReview: false }, + 75: { phaseLabel: 'daily_life', blockNumber: 8, didacticMode: 'intensive_review', difficultyWeight: 4, newUnitTarget: 0, reviewWeight: 95, isIntensiveReview: true }, + 76: { phaseLabel: 'daily_life', blockNumber: 8, didacticMode: 'intensive_review', difficultyWeight: 3, newUnitTarget: 0, reviewWeight: 92, isIntensiveReview: true }, + 77: { phaseLabel: 'daily_life', blockNumber: 8, didacticMode: 'guided_dialogue', difficultyWeight: 3, newUnitTarget: 5, reviewWeight: 55, isIntensiveReview: false }, + 78: { phaseLabel: 'daily_life', blockNumber: 8, didacticMode: 'core_input', difficultyWeight: 3, newUnitTarget: 6, reviewWeight: 55, isIntensiveReview: false }, + 79: { phaseLabel: 'daily_life', blockNumber: 8, didacticMode: 'intensive_review', difficultyWeight: 4, newUnitTarget: 0, reviewWeight: 96, isIntensiveReview: true }, + 80: { phaseLabel: 'daily_life', blockNumber: 8, didacticMode: 'checkpoint', difficultyWeight: 4, newUnitTarget: 0, reviewWeight: 97, isIntensiveReview: false }, + 81: { phaseLabel: 'daily_life', blockNumber: 9, didacticMode: 'guided_dialogue', difficultyWeight: 4, newUnitTarget: 5, reviewWeight: 50, isIntensiveReview: false }, + 82: { phaseLabel: 'daily_life', blockNumber: 9, didacticMode: 'core_input', difficultyWeight: 3, newUnitTarget: 6, reviewWeight: 50, isIntensiveReview: false }, + 83: { phaseLabel: 'daily_life', blockNumber: 9, didacticMode: 'pattern_drill', difficultyWeight: 4, newUnitTarget: 4, reviewWeight: 55, isIntensiveReview: false }, + 84: { phaseLabel: 'daily_life', blockNumber: 9, didacticMode: 'guided_dialogue', difficultyWeight: 4, newUnitTarget: 5, reviewWeight: 55, isIntensiveReview: false }, + 85: { phaseLabel: 'daily_life', blockNumber: 9, didacticMode: 'intensive_review', difficultyWeight: 4, newUnitTarget: 0, reviewWeight: 95, isIntensiveReview: true }, + 86: { phaseLabel: 'daily_life', blockNumber: 9, didacticMode: 'intensive_review', difficultyWeight: 3, newUnitTarget: 0, reviewWeight: 92, isIntensiveReview: true }, + 87: { phaseLabel: 'daily_life', blockNumber: 9, didacticMode: 'guided_dialogue', difficultyWeight: 4, newUnitTarget: 5, reviewWeight: 55, isIntensiveReview: false }, + 88: { phaseLabel: 'daily_life', blockNumber: 9, didacticMode: 'core_input', difficultyWeight: 3, newUnitTarget: 6, reviewWeight: 55, isIntensiveReview: false }, + 89: { phaseLabel: 'daily_life', blockNumber: 9, didacticMode: 'intensive_review', difficultyWeight: 4, newUnitTarget: 0, reviewWeight: 96, isIntensiveReview: true }, + 90: { phaseLabel: 'daily_life', blockNumber: 9, didacticMode: 'checkpoint', difficultyWeight: 4, newUnitTarget: 0, reviewWeight: 97, isIntensiveReview: false }, + 91: { phaseLabel: 'daily_life', blockNumber: 10, didacticMode: 'guided_dialogue', difficultyWeight: 3, newUnitTarget: 5, reviewWeight: 50, isIntensiveReview: false }, + 92: { phaseLabel: 'daily_life', blockNumber: 10, didacticMode: 'core_input', difficultyWeight: 3, newUnitTarget: 6, reviewWeight: 50, isIntensiveReview: false }, + 93: { phaseLabel: 'daily_life', blockNumber: 10, didacticMode: 'pattern_drill', difficultyWeight: 4, newUnitTarget: 4, reviewWeight: 55, isIntensiveReview: false }, + 94: { phaseLabel: 'daily_life', blockNumber: 10, didacticMode: 'guided_dialogue', difficultyWeight: 3, newUnitTarget: 5, reviewWeight: 55, isIntensiveReview: false }, + 95: { phaseLabel: 'daily_life', blockNumber: 10, didacticMode: 'intensive_review', difficultyWeight: 4, newUnitTarget: 0, reviewWeight: 95, isIntensiveReview: true }, + 96: { phaseLabel: 'daily_life', blockNumber: 10, didacticMode: 'intensive_review', difficultyWeight: 3, newUnitTarget: 0, reviewWeight: 92, isIntensiveReview: true }, + 97: { phaseLabel: 'daily_life', blockNumber: 10, didacticMode: 'guided_dialogue', difficultyWeight: 4, newUnitTarget: 5, reviewWeight: 55, isIntensiveReview: false }, + 98: { phaseLabel: 'daily_life', blockNumber: 10, didacticMode: 'core_input', difficultyWeight: 3, newUnitTarget: 6, reviewWeight: 55, isIntensiveReview: false }, + 99: { phaseLabel: 'daily_life', blockNumber: 10, didacticMode: 'intensive_review', difficultyWeight: 4, newUnitTarget: 0, reviewWeight: 96, isIntensiveReview: true }, + 100: { phaseLabel: 'daily_life', blockNumber: 10, didacticMode: 'checkpoint', difficultyWeight: 4, newUnitTarget: 0, reviewWeight: 97, isIntensiveReview: false }, + 101: { phaseLabel: 'stabilization', blockNumber: 11, didacticMode: 'guided_dialogue', difficultyWeight: 4, newUnitTarget: 5, reviewWeight: 60, isIntensiveReview: false }, + 102: { phaseLabel: 'stabilization', blockNumber: 11, didacticMode: 'core_input', difficultyWeight: 3, newUnitTarget: 6, reviewWeight: 60, isIntensiveReview: false }, + 103: { phaseLabel: 'stabilization', blockNumber: 11, didacticMode: 'pattern_drill', difficultyWeight: 4, newUnitTarget: 4, reviewWeight: 65, isIntensiveReview: false }, + 104: { phaseLabel: 'stabilization', blockNumber: 11, didacticMode: 'guided_dialogue', difficultyWeight: 4, newUnitTarget: 5, reviewWeight: 65, isIntensiveReview: false }, + 105: { phaseLabel: 'stabilization', blockNumber: 11, didacticMode: 'intensive_review', difficultyWeight: 5, newUnitTarget: 0, reviewWeight: 97, isIntensiveReview: true }, + 106: { phaseLabel: 'stabilization', blockNumber: 11, didacticMode: 'intensive_review', difficultyWeight: 4, newUnitTarget: 0, reviewWeight: 94, isIntensiveReview: true }, + 107: { phaseLabel: 'stabilization', blockNumber: 11, didacticMode: 'guided_dialogue', difficultyWeight: 4, newUnitTarget: 5, reviewWeight: 65, isIntensiveReview: false }, + 108: { phaseLabel: 'stabilization', blockNumber: 11, didacticMode: 'core_input', difficultyWeight: 3, newUnitTarget: 6, reviewWeight: 65, isIntensiveReview: false }, + 109: { phaseLabel: 'stabilization', blockNumber: 11, didacticMode: 'intensive_review', difficultyWeight: 5, newUnitTarget: 0, reviewWeight: 98, isIntensiveReview: true }, + 110: { phaseLabel: 'stabilization', blockNumber: 11, didacticMode: 'checkpoint', difficultyWeight: 5, newUnitTarget: 0, reviewWeight: 99, isIntensiveReview: false }, + 111: { phaseLabel: 'stabilization', blockNumber: 12, didacticMode: 'real_life_scenario', difficultyWeight: 5, newUnitTarget: 3, reviewWeight: 70, isIntensiveReview: false }, + 112: { phaseLabel: 'stabilization', blockNumber: 12, didacticMode: 'intensive_review', difficultyWeight: 4, newUnitTarget: 0, reviewWeight: 95, isIntensiveReview: true }, + 113: { phaseLabel: 'stabilization', blockNumber: 12, didacticMode: 'real_life_scenario', difficultyWeight: 5, newUnitTarget: 3, reviewWeight: 72, isIntensiveReview: false }, + 114: { phaseLabel: 'stabilization', blockNumber: 12, didacticMode: 'pattern_drill', difficultyWeight: 5, newUnitTarget: 2, reviewWeight: 80, isIntensiveReview: false }, + 115: { phaseLabel: 'stabilization', blockNumber: 12, didacticMode: 'intensive_review', difficultyWeight: 5, newUnitTarget: 0, reviewWeight: 99, isIntensiveReview: true }, + 116: { phaseLabel: 'stabilization', blockNumber: 12, didacticMode: 'intensive_review', difficultyWeight: 4, newUnitTarget: 0, reviewWeight: 96, isIntensiveReview: true }, + 117: { phaseLabel: 'stabilization', blockNumber: 12, didacticMode: 'real_life_scenario', difficultyWeight: 5, newUnitTarget: 3, reviewWeight: 75, isIntensiveReview: false }, + 118: { phaseLabel: 'stabilization', blockNumber: 12, didacticMode: 'checkpoint', difficultyWeight: 5, newUnitTarget: 0, reviewWeight: 99, isIntensiveReview: false }, + 119: { phaseLabel: 'stabilization', blockNumber: 12, didacticMode: 'checkpoint', difficultyWeight: 5, newUnitTarget: 0, reviewWeight: 100, isIntensiveReview: false }, + 120: { phaseLabel: 'stabilization', blockNumber: 12, didacticMode: 'real_life_scenario', difficultyWeight: 2, newUnitTarget: 2, reviewWeight: 65, isIntensiveReview: false }, + 121: { phaseLabel: 'stabilization', blockNumber: 13, didacticMode: 'real_life_scenario', difficultyWeight: 5, newUnitTarget: 3, reviewWeight: 72, isIntensiveReview: false }, + 122: { phaseLabel: 'stabilization', blockNumber: 13, didacticMode: 'intensive_review', difficultyWeight: 4, newUnitTarget: 0, reviewWeight: 94, isIntensiveReview: true }, + 123: { phaseLabel: 'stabilization', blockNumber: 13, didacticMode: 'pattern_drill', difficultyWeight: 5, newUnitTarget: 2, reviewWeight: 82, isIntensiveReview: false }, + 124: { phaseLabel: 'stabilization', blockNumber: 13, didacticMode: 'real_life_scenario', difficultyWeight: 5, newUnitTarget: 3, reviewWeight: 74, isIntensiveReview: false }, + 125: { phaseLabel: 'stabilization', blockNumber: 13, didacticMode: 'intensive_review', difficultyWeight: 5, newUnitTarget: 0, reviewWeight: 98, isIntensiveReview: true }, + 126: { phaseLabel: 'stabilization', blockNumber: 13, didacticMode: 'intensive_review', difficultyWeight: 4, newUnitTarget: 0, reviewWeight: 95, isIntensiveReview: true }, + 127: { phaseLabel: 'stabilization', blockNumber: 13, didacticMode: 'real_life_scenario', difficultyWeight: 5, newUnitTarget: 3, reviewWeight: 75, isIntensiveReview: false }, + 128: { phaseLabel: 'stabilization', blockNumber: 13, didacticMode: 'intensive_review', difficultyWeight: 4, newUnitTarget: 0, reviewWeight: 95, isIntensiveReview: true }, + 129: { phaseLabel: 'stabilization', blockNumber: 13, didacticMode: 'intensive_review', difficultyWeight: 5, newUnitTarget: 0, reviewWeight: 99, isIntensiveReview: true }, + 130: { phaseLabel: 'stabilization', blockNumber: 13, didacticMode: 'checkpoint', difficultyWeight: 5, newUnitTarget: 0, reviewWeight: 99, isIntensiveReview: false }, + 131: { phaseLabel: 'stabilization', blockNumber: 14, didacticMode: 'real_life_scenario', difficultyWeight: 5, newUnitTarget: 3, reviewWeight: 74, isIntensiveReview: false }, + 132: { phaseLabel: 'stabilization', blockNumber: 14, didacticMode: 'intensive_review', difficultyWeight: 4, newUnitTarget: 0, reviewWeight: 94, isIntensiveReview: true }, + 133: { phaseLabel: 'stabilization', blockNumber: 14, didacticMode: 'pattern_drill', difficultyWeight: 5, newUnitTarget: 2, reviewWeight: 84, isIntensiveReview: false }, + 134: { phaseLabel: 'stabilization', blockNumber: 14, didacticMode: 'real_life_scenario', difficultyWeight: 5, newUnitTarget: 3, reviewWeight: 76, isIntensiveReview: false }, + 135: { phaseLabel: 'stabilization', blockNumber: 14, didacticMode: 'intensive_review', difficultyWeight: 5, newUnitTarget: 0, reviewWeight: 98, isIntensiveReview: true }, + 136: { phaseLabel: 'stabilization', blockNumber: 14, didacticMode: 'intensive_review', difficultyWeight: 4, newUnitTarget: 0, reviewWeight: 95, isIntensiveReview: true }, + 137: { phaseLabel: 'stabilization', blockNumber: 14, didacticMode: 'real_life_scenario', difficultyWeight: 5, newUnitTarget: 3, reviewWeight: 78, isIntensiveReview: false }, + 138: { phaseLabel: 'stabilization', blockNumber: 14, didacticMode: 'intensive_review', difficultyWeight: 4, newUnitTarget: 0, reviewWeight: 95, isIntensiveReview: true }, + 139: { phaseLabel: 'stabilization', blockNumber: 14, didacticMode: 'intensive_review', difficultyWeight: 5, newUnitTarget: 0, reviewWeight: 99, isIntensiveReview: true }, + 140: { phaseLabel: 'stabilization', blockNumber: 14, didacticMode: 'checkpoint', difficultyWeight: 5, newUnitTarget: 0, reviewWeight: 99, isIntensiveReview: false }, + 141: { phaseLabel: 'stabilization', blockNumber: 15, didacticMode: 'intensive_review', difficultyWeight: 5, newUnitTarget: 0, reviewWeight: 99, isIntensiveReview: true }, + 142: { phaseLabel: 'stabilization', blockNumber: 15, didacticMode: 'intensive_review', difficultyWeight: 5, newUnitTarget: 0, reviewWeight: 99, isIntensiveReview: true }, + 143: { phaseLabel: 'stabilization', blockNumber: 15, didacticMode: 'pattern_drill', difficultyWeight: 5, newUnitTarget: 1, reviewWeight: 88, isIntensiveReview: false }, + 144: { phaseLabel: 'stabilization', blockNumber: 15, didacticMode: 'real_life_scenario', difficultyWeight: 5, newUnitTarget: 2, reviewWeight: 80, isIntensiveReview: false }, + 145: { phaseLabel: 'stabilization', blockNumber: 15, didacticMode: 'intensive_review', difficultyWeight: 5, newUnitTarget: 0, reviewWeight: 100, isIntensiveReview: true }, + 146: { phaseLabel: 'stabilization', blockNumber: 15, didacticMode: 'intensive_review', difficultyWeight: 5, newUnitTarget: 0, reviewWeight: 100, isIntensiveReview: true }, + 147: { phaseLabel: 'stabilization', blockNumber: 15, didacticMode: 'real_life_scenario', difficultyWeight: 5, newUnitTarget: 2, reviewWeight: 82, isIntensiveReview: false }, + 148: { phaseLabel: 'stabilization', blockNumber: 15, didacticMode: 'checkpoint', difficultyWeight: 5, newUnitTarget: 0, reviewWeight: 100, isIntensiveReview: false }, + 149: { phaseLabel: 'stabilization', blockNumber: 15, didacticMode: 'checkpoint', difficultyWeight: 5, newUnitTarget: 0, reviewWeight: 100, isIntensiveReview: false }, + 150: { phaseLabel: 'stabilization', blockNumber: 15, didacticMode: 'real_life_scenario', difficultyWeight: 2, newUnitTarget: 1, reviewWeight: 70, isIntensiveReview: false } +}; + +export function getBisayaLessonPedagogy(lessonNumber) { + return BISAYA_LESSON_PEDAGOGY[Number(lessonNumber)] || null; +} diff --git a/backend/scripts/bisaya-course-phase3-extension.js b/backend/scripts/bisaya-course-phase3-extension.js new file mode 100644 index 0000000..8a8003b --- /dev/null +++ b/backend/scripts/bisaya-course-phase3-extension.js @@ -0,0 +1,254 @@ +export const BISAYA_PHASE3_DIDACTICS = { + 'Besuch & Gastfreundschaft': { + learningGoals: [ + 'Einfache Besuchssituationen sprachlich eröffnen.', + 'Gäste begrüßen und höflich ins Haus bitten.', + 'Kurze Standardformeln für Einladung und Reaktion verwenden.' + ], + corePatterns: ['Sulod lang.', 'Lingkod sa.', 'Gimingaw mi nimo.', 'Salamat sa pag-anhi.'], + speakingPrompts: [ + { + title: 'Gast begrüßen', + prompt: 'Begrüße einen Gast, bitte ihn herein und biete einen Sitzplatz an.', + cue: 'Maayong adlaw. Sulod lang. Lingkod sa.' + } + ], + practicalTasks: [ + { + title: 'Mini-Rollenspiel', + text: 'Spiele den Moment nach, wenn ein Familienmitglied zu Besuch kommt.' + } + ] + }, + 'Besuch & Haushalt': { + learningGoals: [ + 'Wichtige Wörter rund um Besuch und Haushalt erkennen.', + 'Einfache Dinge im Haus benennen.', + 'Wortschatz aus Woche 1 und 2 mit neuen Besuchssituationen verbinden.' + ], + corePatterns: ['bisita', 'balay', 'lamisa', 'lingkuranan'] + }, + 'Fragen im Alltag vertiefen': { + learningGoals: [ + 'Fragen mit mehr Varianten bilden.', + 'Rückfragen höflich wiederholen.', + 'Zwischen Frage, Bitte und Bestätigung sicher wechseln.' + ], + corePatterns: ['Asa ka padulong?', 'Unsa imong buhaton?', 'Pwede mangutana?', 'Palihug ka mubalik?'], + grammarFocus: [ + { + title: 'Frageketten', + text: 'Im Alltag werden oft zwei kurze Fragen nacheinander gestellt statt eines langen Satzes.', + example: 'Asa ka padulong? Unsa imong buhaton didto?' + } + ], + speakingPrompts: [ + { + title: 'Nachfragen', + prompt: 'Frage nach Ziel, Zeit und Vorhaben einer Person.', + cue: 'Asa ka padulong? Kanus-a ka moadto?' + } + ] + }, + 'Termine & Verabredungen': { + learningGoals: [ + 'Einfache Treffen verabreden.', + 'Zeitangaben und Zusagen kombinieren.', + 'Kurze Planungsdialoge laut üben.' + ], + corePatterns: ['Magkita ta ugma.', 'Unsa orasa?', 'Pwede ko karon.', 'Sige, kita ta.'], + speakingPrompts: [ + { + title: 'Treffen planen', + prompt: 'Vereinbare ein Treffen für morgen und frage nach der Uhrzeit.', + cue: 'Magkita ta ugma. Unsa orasa?' + } + ] + }, + 'Woche 5 - Intensivwiederholung I': { + learningGoals: [ + 'Muster aus Familie, Fürsorge, Zeit und Besuch zusammen abrufen.', + 'Zwischen alten und neuen Themen schneller wechseln.', + 'Mehrere Mini-Szenen ohne deutsche Stütze lösen.' + ], + corePatterns: ['Kumusta ka?', 'Nikaon na ka?', 'Magkita ta ugma.', 'Sulod lang.'] + }, + 'Spiralwiederholung - Familie & Fürsorge': { + learningGoals: [ + 'Frühe Familien- und Fürsorgemuster aktiv festigen.', + 'Bekannte Wörter in neuen Dialogen wiedererkennen.', + 'Von Einzelwörtern zu flüssigen Alltagsformeln übergehen.' + ], + corePatterns: ['Nanay', 'Tatay', 'Palangga taka.', 'Nikaon na ka?'] + }, + 'Gesundheit im Alltag': { + learningGoals: [ + 'Einfache Beschwerden und Fürsorge im Gespräch ausdrücken.', + 'Nach dem Befinden fragen und Hilfe anbieten.', + 'Kurze Gesundheitsdialoge in der Familie üben.' + ], + corePatterns: ['Sakit imong ulo?', 'Niinom ka og tambal?', 'Magpahuway sa.', 'Maayo ra ko.'], + speakingPrompts: [ + { + title: 'Nach dem Befinden fragen', + prompt: 'Frage nach Schmerzen und biete Ruhe oder Medizin an.', + cue: 'Sakit imong ulo? Magpahuway sa.' + } + ] + }, + 'Medikamente & Beschwerden': { + learningGoals: [ + 'Wichtige Wörter zu Schmerzen und Medizin erkennen.', + 'Zwischen Körperteil, Symptom und Hilfe unterscheiden.', + 'Gesundheitswortschatz mit Fürsorge verbinden.' + ], + corePatterns: ['tambal', 'ubo', 'hilanat', 'kasakit'] + }, + 'Woche 5 - Intensivwiederholung II': { + learningGoals: [ + 'Gesundheit, Fragen, Besuch und Organisation gemischt trainieren.', + 'Wackelige Muster noch einmal unter Zeitdruck abrufen.', + 'Sprechtempo und Reaktionssicherheit erhöhen.' + ], + corePatterns: ['Pwede mangutana?', 'Magkita ta.', 'Niinom ka og tambal?', 'Salamat sa pag-anhi.'] + }, + 'Woche 5 - Checkpoint': { + learningGoals: [ + 'Die wichtigsten Inhalte von Woche 5 abrufen.', + 'Sicherheit bei Alltags- und Besuchsdialogen prüfen.', + 'Stärken und Lücken vor Woche 6 sichtbar machen.' + ], + corePatterns: ['Sulod lang.', 'Unsa orasa?', 'Magpahuway sa.', 'Palihug ka mubalik?'] + }, + 'Unterwegs & Transport': { + learningGoals: [ + 'Unterwegs nach Weg, Fahrt und Ziel fragen.', + 'Kurze Transportsituationen bewältigen.', + 'Richtungen und Verkehrswörter im Kontext nutzen.' + ], + corePatterns: ['Asa ang sakayan?', 'Moadto ko sa lungsod.', 'Hunong lang dinhi.', 'Pila ang plite?'], + speakingPrompts: [ + { + title: 'Transport fragen', + prompt: 'Frage nach Haltestelle, Preis und Zielort.', + cue: 'Asa ang sakayan? Pila ang plite?' + } + ] + }, + 'Wege & Verkehr': { + learningGoals: [ + 'Weg- und Verkehrswortschatz ausbauen.', + 'Transportwörter mit Richtungen verbinden.', + 'Häufige Reise- und Bewegungsverben wiederholen.' + ], + corePatterns: ['sakyanan', 'dalan', 'hunong', 'biyahe'] + }, + 'Arbeit & Aufgaben': { + learningGoals: [ + 'Über einfache Tätigkeiten und Pflichten sprechen.', + 'Arbeit, Haushalt und Tagesaufgaben benennen.', + 'Kurze Aussagen über To-dos machen.' + ], + corePatterns: ['Naa koy trabaho karon.', 'Aduna koy buhaton.', 'Human na ko.', 'Tabang ta.'], + speakingPrompts: [ + { + title: 'Tagesaufgaben', + prompt: 'Erkläre kurz, was du heute noch erledigen musst.', + cue: 'Aduna koy buhaton karon. Human na ko unya.' + } + ] + }, + 'Tätigkeiten & Organisation': { + learningGoals: [ + 'Wörter für Aufgaben, Arbeit und Organisation erkennen.', + 'Bekannte Verben in neue Alltagssituationen übertragen.', + 'Kurze Planungs- und Erledigungssätze vorbereiten.' + ], + corePatterns: ['trabaho', 'buluhaton', 'lista', 'tabang'] + }, + 'Freies Gespräch - Familie & Alltag': { + learningGoals: [ + 'Frühere Themen ohne enge Führung kombinieren.', + 'Längere Alltagsantworten aufbauen.', + 'Im freien Sprechen bei bekannten Themen stabil bleiben.' + ], + corePatterns: ['Maayo ra.', 'Naa mi sa balay.', 'Magkita mi unya.', 'Gimingaw ko nimo.'], + speakingPrompts: [ + { + title: 'Alltagsgespräch', + prompt: 'Erzähle kurz von Familie, Tagesplan und aktuellem Befinden.', + cue: 'Naa mi sa balay. Maayo ra ko. Aduna koy buhaton unya.' + } + ] + }, + 'Spiralwiederholung - Wochen 1 bis 4': { + learningGoals: [ + 'Frühe Kursinhalte blockübergreifend wiederholen.', + 'Ähnliche Muster sauber unterscheiden.', + 'Vor dem Schnellstart-Abschluss zentrale Formen stabilisieren.' + ], + corePatterns: ['Kumusta ka?', 'Tagpila ni?', 'Ni-kaon ko.', 'Nalipay ko.'] + }, + 'Konflikte & Missverständnisse': { + learningGoals: [ + 'Missverständnisse höflich ansprechen.', + 'Um Wiederholung oder langsamere Erklärung bitten.', + 'Kleine Konflikte sprachlich entschärfen.' + ], + corePatterns: ['Wala ko kasabot.', 'Hinay-hinay lang.', 'Pwede nato istoryahan?', 'Pasayloa ko.'], + speakingPrompts: [ + { + title: 'Missverständnis lösen', + prompt: 'Bitte um Wiederholung und entschuldige dich höflich.', + cue: 'Wala ko kasabot. Palihug ka mubalik. Pasayloa ko.' + } + ] + }, + 'Abschlusstest - Schnellstart': { + learningGoals: [ + 'Wichtige Wörter aus dem gesamten Schnellstart sicher abrufen.', + 'Zwischen ähnlichen Antworten sauber unterscheiden.', + 'Den aktiven Grundwortschatz stabilisieren.' + ], + corePatterns: ['Kumusta', 'bisita', 'tambal', 'plite'] + }, + 'Abschlussprüfung - Schnellstart': { + learningGoals: [ + 'Den 6-Wochen-Schnellstart in gemischten Aufgaben überprüfen.', + 'Abruf, Musterverständnis und situative Anwendung kombinieren.', + 'Die Grundlage für die Alltagsphase absichern.' + ], + corePatterns: ['Sulod lang.', 'Magkita ta ugma.', 'Niinom ka og tambal?', 'Asa ang sakayan?'] + }, + 'Kulturelle Vertiefung im Familienalltag': { + learningGoals: [ + 'Sprachgebrauch und Familienkultur besser einordnen.', + 'Höflichkeit, Nähe und indirekte Kommunikation bewusster wahrnehmen.', + 'Kulturelle Unterschiede mit dem gelernten Sprachmaterial verknüpfen.' + ], + corePatterns: ['pakikisama', 'respeto', 'amping', 'palihug'] + } +}; + +export const BISAYA_PHASE3_LESSONS = [ + { week: 5, day: 1, num: 41, type: 'conversation', title: 'Besuch & Gastfreundschaft', desc: 'Besuch empfangen, hereinbitten und freundlich reagieren', targetMin: 18, targetScore: 80, review: false, cultural: 'Gastfreundschaft ist im philippinischen Familienalltag zentral.' }, + { week: 5, day: 1, num: 42, type: 'vocab', title: 'Besuch & Haushalt', desc: 'Wichtige Wörter für Besuch, Haus und gemeinsame Zeit', targetMin: 18, targetScore: 85, review: true, cultural: null }, + { week: 5, day: 2, num: 43, type: 'grammar', title: 'Fragen im Alltag vertiefen', desc: 'Rückfragen, Folgefragen und höfliches Nachhaken', targetMin: 22, targetScore: 78, review: true, cultural: null }, + { week: 5, day: 2, num: 44, type: 'conversation', title: 'Termine & Verabredungen', desc: 'Treffen planen und Uhrzeiten absprechen', targetMin: 18, targetScore: 80, review: false, cultural: null }, + { week: 5, day: 3, num: 45, type: 'review', title: 'Woche 5 - Intensivwiederholung I', desc: 'Erste große Wiederholung zu Besuch, Alltag und Fürsorge', targetMin: 28, targetScore: 80, review: false, cultural: null }, + { week: 5, day: 3, num: 46, type: 'vocab', title: 'Spiralwiederholung - Familie & Fürsorge', desc: 'Alte Kernmuster gezielt wiederholen und festigen', targetMin: 20, targetScore: 85, review: true, cultural: null }, + { week: 5, day: 4, num: 47, type: 'conversation', title: 'Gesundheit im Alltag', desc: 'Nach Beschwerden fragen und Hilfe anbieten', targetMin: 18, targetScore: 80, review: false, cultural: null }, + { week: 5, day: 4, num: 48, type: 'vocab', title: 'Medikamente & Beschwerden', desc: 'Schmerz, Medizin und Fürsorgewortschatz', targetMin: 18, targetScore: 85, review: true, cultural: null }, + { week: 5, day: 5, num: 49, type: 'review', title: 'Woche 5 - Intensivwiederholung II', desc: 'Große Mischwiederholung mit Fokus auf Abruf und Tempo', targetMin: 28, targetScore: 80, review: false, cultural: null }, + { week: 5, day: 5, num: 50, type: 'vocab', title: 'Woche 5 - Checkpoint', desc: 'Checkpoint zu den Inhalten von Woche 5', targetMin: 16, targetScore: 82, review: true, cultural: null }, + { week: 6, day: 1, num: 51, type: 'conversation', title: 'Unterwegs & Transport', desc: 'Nach Weg, Fahrt, Preis und Ziel fragen', targetMin: 18, targetScore: 80, review: false, cultural: null }, + { week: 6, day: 1, num: 52, type: 'vocab', title: 'Wege & Verkehr', desc: 'Verkehrs- und Bewegungswortschatz für den Alltag', targetMin: 18, targetScore: 85, review: true, cultural: null }, + { week: 6, day: 2, num: 53, type: 'conversation', title: 'Arbeit & Aufgaben', desc: 'Über Pflichten, Arbeit und Erledigungen sprechen', targetMin: 18, targetScore: 80, review: false, cultural: null }, + { week: 6, day: 2, num: 54, type: 'vocab', title: 'Tätigkeiten & Organisation', desc: 'Verben und Wörter für Aufgaben, Listen und Hilfe', targetMin: 18, targetScore: 85, review: true, cultural: null }, + { week: 6, day: 3, num: 55, type: 'conversation', title: 'Freies Gespräch - Familie & Alltag', desc: 'Freier sprechen über Familie, Zuhause und Tagesablauf', targetMin: 22, targetScore: 78, review: false, cultural: null }, + { week: 6, day: 3, num: 56, type: 'review', title: 'Spiralwiederholung - Wochen 1 bis 4', desc: 'Frühere Inhalte blockübergreifend wiederholen', targetMin: 28, targetScore: 82, review: false, cultural: null }, + { week: 6, day: 4, num: 57, type: 'conversation', title: 'Konflikte & Missverständnisse', desc: 'Höflich um Wiederholung bitten und Missverständnisse klären', targetMin: 20, targetScore: 80, review: false, cultural: 'Ruhiger, indirekter Umgang hilft in heiklen Gesprächen.' }, + { week: 6, day: 4, num: 58, type: 'vocab', title: 'Abschlusstest - Schnellstart', desc: 'Finaler Wortschatztest über den 6-Wochen-Schnellstart', targetMin: 18, targetScore: 82, review: true, cultural: null }, + { week: 6, day: 5, num: 59, type: 'review', title: 'Abschlussprüfung - Schnellstart', desc: 'Große Abschlussprüfung mit gemischten Inhalten', targetMin: 30, targetScore: 82, review: false, cultural: 'Die Abschlussphase prüft vor allem sichere Alltagsfähigkeit.' }, + { week: 6, day: 5, num: 60, type: 'culture', title: 'Kulturelle Vertiefung im Familienalltag', desc: 'Vertiefe wichtige kulturelle Muster für Sprache und Familienalltag', targetMin: 16, targetScore: 0, review: false, cultural: 'Sprache, Respekt und Familienrollen sind eng miteinander verbunden.' } +]; diff --git a/backend/scripts/bisaya-course-phase4-extension.js b/backend/scripts/bisaya-course-phase4-extension.js new file mode 100644 index 0000000..a16d921 --- /dev/null +++ b/backend/scripts/bisaya-course-phase4-extension.js @@ -0,0 +1,594 @@ +export const BISAYA_PHASE4_DIDACTICS = { + 'Kinder im Alltag': { + learningGoals: [ + 'Über Kinder im Familienalltag sprechen.', + 'Einfache Fragen zu Hunger, Müdigkeit und Schule stellen.', + 'Kurze Fürsorge-Dialoge mit Kindern laut üben.' + ], + corePatterns: ['Nikaon na ang bata?', 'Asa ang bata?', 'Kapoy na ka?', 'Andam na ka sa eskwela?'], + speakingPrompts: [ + { + title: 'Mit Kind sprechen', + prompt: 'Frage ein Kind nach Essen, Müdigkeit und Schule.', + cue: 'Nikaon na ka? Kapoy na ka?' + } + ] + }, + 'Schule & Betreuung': { + learningGoals: [ + 'Wichtige Wörter zu Schule und Betreuung erkennen.', + 'Über Tasche, Hefte, Lehrkraft und Unterricht sprechen.', + 'Kinder- und Schulwortschatz mit Familienalltag verbinden.' + ], + corePatterns: ['eskwela', 'maestra', 'bag', 'leksyon'] + }, + 'Fragen an Kinder vereinfachen': { + learningGoals: [ + 'Kurze, einfache Fragen für Kinder bilden.', + 'Aufforderungen und Fragen freundlich unterscheiden.', + 'Kindgerechte Alltagsmuster wiederholt anwenden.' + ], + corePatterns: ['Unsa ni?', 'Asa imong bag?', 'Andam na ka?', 'Ali diri.'], + grammarFocus: [ + { + title: 'Kurze Fragen', + text: 'Mit Kindern funktionieren kurze, klare Fragen oft besser als lange Sätze.', + example: 'Asa imong bag? Andam na ka?' + } + ] + }, + 'Hausaufgaben & Routine': { + learningGoals: [ + 'Über Lernen, Hausaufgaben und Routine sprechen.', + 'Einfachen Tagesablauf mit Schule und Zuhause verbinden.', + 'Kurze Organisationsgespräche führen.' + ], + corePatterns: ['Naa kay assignment?', 'Human na ka?', 'Magtuon ta.', 'Sunod, matulog na.'] + }, + 'Woche 7 - Intensivwiederholung I': { + learningGoals: [ + 'Kinder-, Familien- und Routinemuster gemeinsam abrufen.', + 'Frühere Fürsorgeformen mit Schulalltag verbinden.', + 'Schneller zwischen Themen wechseln.' + ], + corePatterns: ['Nikaon na ka?', 'Asa ang bata?', 'Magtuon ta.', 'Palihug lingkod.'] + }, + 'Spiralwiederholung - Familie, Kinder & Fürsorge': { + learningGoals: [ + 'Familien- und Kindermuster spiralig wiederholen.', + 'Bekannte Kernwörter schneller abrufen.', + 'Alte Inhalte in neuen Situationen festigen.' + ], + corePatterns: ['Nanay', 'Tatay', 'bata', 'eskwela'] + }, + 'Spielen & Freizeit': { + learningGoals: [ + 'Über Spiel, Freizeit und kurze Aktivitäten sprechen.', + 'Kinder zu Spiel und Pause begleiten.', + 'Leichte Alltagsdialoge mit Bewegung verbinden.' + ], + corePatterns: ['Magdula ta.', 'Ganahan ka modula?', 'Lingaw ka?', 'Human na ang duwa.'] + }, + 'Spielsachen & Aktivitäten': { + learningGoals: [ + 'Wortschatz für Spiel und Freizeit ausbauen.', + 'Aktivitäten benennen und vergleichen.', + 'Kinderalltag mit neuen Nomen anreichern.' + ], + corePatterns: ['duwa', 'bola', 'libro', 'kanta'] + }, + 'Woche 7 - Intensivwiederholung II': { + learningGoals: [ + 'Kinder-, Spiel- und Schulmuster unter Zeitdruck abrufen.', + 'Schwierige Formen gezielt wiederholen.', + 'Mehr Sicherheit in Alltagsszenen mit Kindern gewinnen.' + ], + corePatterns: ['Andam na ka?', 'Magdula ta.', 'Human na ka?', 'Asa imong bag?'] + }, + 'Woche 7 - Checkpoint': { + learningGoals: [ + 'Die wichtigsten Inhalte der Kinder- und Schulwoche überprüfen.', + 'Abruf und Anwendung im Familienalltag testen.', + 'Lücken vor Woche 8 sichtbar machen.' + ], + corePatterns: ['eskwela', 'assignment', 'bata', 'magdula'] + }, + 'Arzt & Termin': { + learningGoals: [ + 'Arzttermine sprachlich vorbereiten.', + 'Über Beschwerden, Termin und Wartezeit sprechen.', + 'Einfache Gesundheitsgespräche strukturieren.' + ], + corePatterns: ['Adto ta sa doktor.', 'Naa moy appointment?', 'Unsay gibati nimo?', 'Maghulat ta.'] + }, + 'Apotheke & Medikamente': { + learningGoals: [ + 'Wortschatz aus Apotheke und Medizin sicher erkennen.', + 'Zwischen Medikament, Rezept und Dosierung unterscheiden.', + 'Gesundheitswortschatz in Erledigungen einordnen.' + ], + corePatterns: ['tambal', 'botika', 'reseta', 'dose'] + }, + 'Beschwerden genauer beschreiben': { + learningGoals: [ + 'Beschwerden konkreter benennen.', + 'Zwischen leicht, stark und wiederkehrend unterscheiden.', + 'Fragen und Antworten zu Schmerzen vertiefen.' + ], + corePatterns: ['Sakit kaayo.', 'Gamaya ra ang sakit.', 'Nagsugod ganiha.', 'Mas maayo na karon.'], + grammarFocus: [ + { + title: 'Beschwerden abstufen', + text: 'Im Alltag helfen kurze Verstärker und Zeitangaben, um Schmerzen genauer zu beschreiben.', + example: 'Sakit kaayo. Nagsugod ganiha.' + } + ] + }, + 'Notfälle & Hilfe': { + learningGoals: [ + 'In einfachen Notlagen Hilfe rufen und organisieren.', + 'Klare, kurze Anweisungen geben.', + 'Grundmuster für schnelle Reaktion beherrschen.' + ], + corePatterns: ['Tabang!', 'Tawag ug doktor.', 'Dali lang.', 'Asa ang tambal?'] + }, + 'Woche 8 - Intensivwiederholung I': { + learningGoals: [ + 'Arzt-, Apotheke- und Hilfemuster gebündelt wiederholen.', + 'Beschwerden schneller abrufen und zuordnen.', + 'Gesundheitskommunikation stabilisieren.' + ], + corePatterns: ['doktor', 'tambal', 'tabang', 'sakit kaayo'] + }, + 'Spiralwiederholung - Gesundheit': { + learningGoals: [ + 'Frühere Fürsorge- und Gesundheitsmuster zusammenführen.', + 'Bekannte Wörter in neuen Gesundheitsdialogen wiederholen.', + 'Gesundheitswortschatz langfristig verankern.' + ], + corePatterns: ['Nikaon na ka?', 'Magpahuway sa.', 'doktor', 'tambal'] + }, + 'Essen, Ruhe & Genesung': { + learningGoals: [ + 'Über Ruhe, Essen und Erholung bei Krankheit sprechen.', + 'Kurze Pflegegespräche im Familienalltag führen.', + 'Fürsorge und Gesundheit verbinden.' + ], + corePatterns: ['Magpahuway sa.', 'Mokaon sa ka.', 'Uminom og tubig.', 'Mas maayo na ka?'] + }, + 'Körper, Symptome & Pflege': { + learningGoals: [ + 'Körperteile, Symptome und Pflegewörter ausbauen.', + 'Zwischen Ort und Art einer Beschwerde unterscheiden.', + 'Pflegealltag mit konkreterem Wortschatz stützen.' + ], + corePatterns: ['ulo', 'tiyan', 'hilanat', 'alaga'] + }, + 'Woche 8 - Intensivwiederholung II': { + learningGoals: [ + 'Gesundheitswortschatz in Mischsituationen wiederholen.', + 'Fragen, Antworten und Reaktionen zügig abrufen.', + 'Vor dem Wochenabschluss Sicherheit erhöhen.' + ], + corePatterns: ['Mas maayo na ka?', 'Adto ta sa doktor.', 'Uminom og tubig.', 'Tabang!'] + }, + 'Woche 8 - Checkpoint': { + learningGoals: [ + 'Die Gesundheitswoche diagnostisch abschließen.', + 'Abruf, Verständnis und situative Anwendung prüfen.', + 'Stärken und Lücken für Woche 9 festhalten.' + ], + corePatterns: ['doktor', 'botika', 'hilanat', 'tabang'] + }, + 'Einkaufen vertiefen': { + learningGoals: [ + 'Komplexere Einkaufssituationen meistern.', + 'Zwischen Auswahl, Menge und Preis sicher wechseln.', + 'Kürzere und längere Kaufdialoge kombinieren.' + ], + corePatterns: ['Pila ni tanan?', 'Pwede tulo kabuok?', 'Naa moy mas barato?', 'Kuhaon na nako.'] + }, + 'Markt & Mengen': { + learningGoals: [ + 'Mengen- und Marktwortschatz ausbauen.', + 'Zwischen Stück, Kilo und Portion unterscheiden.', + 'Preise und Mengen natürlicher kombinieren.' + ], + corePatterns: ['kilo', 'buok', 'merkado', 'sobra'] + }, + 'Wünsche, Bedarf und Bitte': { + learningGoals: [ + 'Wünsche höflich formulieren.', + 'Bedarf, Bitte und Auswahl sprachlich unterscheiden.', + 'Kauf- und Alltagsbitten variieren.' + ], + corePatterns: ['Gusto ko ani.', 'Kinahanglan nako ni.', 'Pwede palihug?', 'Mas maayo ni para nako.'], + grammarFocus: [ + { + title: 'Wunsch vs. Bedarf', + text: 'Mit "gusto" und "kinahanglan" lassen sich Wunsch und Notwendigkeit klar unterscheiden.', + example: 'Gusto ko ani. Kinahanglan nako ni.' + } + ] + }, + 'Behördengänge & Formulare': { + learningGoals: [ + 'Einfache Behördensituationen sprachlich vorbereiten.', + 'Nach Formularen, Schaltern und Terminen fragen.', + 'Erledigungssprache über den Einkauf hinaus ausbauen.' + ], + corePatterns: ['Asa ang porma?', 'Asa ko mulinya?', 'Naa koy appointment.', 'Unsa ang sunod?'] + }, + 'Woche 9 - Intensivwiederholung I': { + learningGoals: [ + 'Einkaufs- und Erledigungsmuster zusammen wiederholen.', + 'Mengen, Preise und Bitten schneller abrufen.', + 'Alltagsorganisation sprachlich absichern.' + ], + corePatterns: ['Pila ni tanan?', 'Kinahanglan nako ni.', 'Asa ang porma?', 'Pwede palihug?'] + }, + 'Spiralwiederholung - Preise & Erledigungen': { + learningGoals: [ + 'Preis- und Erledigungsmuster blockübergreifend verknüpfen.', + 'Alte und neue Einkaufsformen sauber unterscheiden.', + 'Häufige Alltagsfragen festigen.' + ], + corePatterns: ['Tagpila ni?', 'Pila ni tanan?', 'appointment', 'linya'] + }, + 'Bank, Geld & Bezahlen': { + learningGoals: [ + 'Über Bargeld, Wechselgeld und Bezahlen sprechen.', + 'Einfache Geldsituationen im Alltag bewältigen.', + 'Einkauf und Finanzalltag verbinden.' + ], + corePatterns: ['Mubayad ko.', 'Naa kay sinsilyo?', 'Wala koy sukli.', 'Asa ang ATM?'] + }, + 'Dokumente & Termine': { + learningGoals: [ + 'Wichtige Wörter für Dokumente und Termine lernen.', + 'Papierkram und Terminorganisation sprachlich stützen.', + 'Erledigungssituationen besser strukturieren.' + ], + corePatterns: ['ID', 'papel', 'appointment', 'resibo'] + }, + 'Woche 9 - Intensivwiederholung II': { + learningGoals: [ + 'Erledigungen, Bezahlen und Behördensprache verdichten.', + 'Fehleranfällige Muster gezielt festigen.', + 'Vor dem Checkpoint die Abrufsicherheit erhöhen.' + ], + corePatterns: ['Mubayad ko.', 'Wala koy sukli.', 'Asa ko mulinya?', 'Naa koy appointment.'] + }, + 'Woche 9 - Checkpoint': { + learningGoals: [ + 'Die wichtigsten Inhalte rund um Erledigungen überprüfen.', + 'Sprachliche Sicherheit bei Kauf, Formular und Termin messen.', + 'Übergang zur Sozialphase vorbereiten.' + ], + corePatterns: ['merkado', 'ATM', 'resibo', 'porma'] + }, + 'Nachbarschaft & Besuche': { + learningGoals: [ + 'Über Nachbarn, Besuche und kurze Begegnungen sprechen.', + 'Freundliche Alltagsgespräche im Umfeld führen.', + 'Soziale Nähe sprachlich ausdrücken.' + ], + corePatterns: ['Kumusta mo?', 'Niadto mi sa silingan.', 'Moadto ko didto unya.', 'Bisita mo unya.'] + }, + 'Hilfe & Unterstützung': { + learningGoals: [ + 'Um Hilfe bitten und Hilfe anbieten.', + 'Unterstützung im Alltag sprachlich strukturieren.', + 'Kurze soziale Hilfsdialoge üben.' + ], + corePatterns: ['Pwede ka motabang?', 'Tabangan tika.', 'Salamat sa tabang.', 'Kinahanglan ko og tabang.'] + }, + 'Höflich reagieren und ablehnen': { + learningGoals: [ + 'Höflich zusagen, verschieben oder ablehnen.', + 'Soziale Reaktionen natürlicher gestalten.', + 'Hilfe und Einladungen fein abstufen.' + ], + corePatterns: ['Sige lang.', 'Dili lang sa karon.', 'Sunod na lang.', 'Salamat pero dili ko mahimo.'], + grammarFocus: [ + { + title: 'Sanft ablehnen', + text: 'Im Alltag klingt eine weiche Ablehnung oft natürlicher als ein direktes Nein.', + example: 'Dili lang sa karon. Sunod na lang.' + } + ] + }, + 'Feste & Einladungen': { + learningGoals: [ + 'Über Feiern und Einladungen sprechen.', + 'Einladungen aussprechen, annehmen oder verschieben.', + 'Soziale Ereignisse sprachlich einordnen.' + ], + corePatterns: ['Aduna moy handa?', 'Moadto ka sa pista?', 'Giinvite tika.', 'Magkita ta didto.'] + }, + 'Woche 10 - Intensivwiederholung I': { + learningGoals: [ + 'Soziale Muster mit Hilfe, Besuch und Einladung bündeln.', + 'Schneller auf soziale Situationen reagieren.', + 'Höfliche Ablehnung und Zusage wiederholen.' + ], + corePatterns: ['Pwede ka motabang?', 'Bisita mo unya.', 'Sunod na lang.', 'Giinvite tika.'] + }, + 'Spiralwiederholung - Soziale Situationen': { + learningGoals: [ + 'Mehrere Sozialthemen gemischt wiederholen.', + 'Hilfe, Besuch und Fürsorge verbinden.', + 'Reaktionssicherheit im Gespräch erhöhen.' + ], + corePatterns: ['tabang', 'bisita', 'amping', 'sunod na lang'] + }, + 'Konflikte ruhig lösen': { + learningGoals: [ + 'Kleine Spannungen ruhig ansprechen.', + 'Missverständnisse sozial abfedern.', + 'Bitten, Erklärung und Entschuldigung kombinieren.' + ], + corePatterns: ['Pwede nato istoryahan?', 'Pasayloa ko.', 'Dili mao ang akong pasabot.', 'Sabta lang ko.'] + }, + 'Gefühle im Gespräch vertiefen': { + learningGoals: [ + 'Gefühle sozial differenzierter ausdrücken.', + 'Auf Sorge, Enttäuschung oder Freude reagieren.', + 'Emotionen mit Beziehungssprache verbinden.' + ], + corePatterns: ['Naguol ko.', 'Nalipay ko.', 'Nasayod ko sa imong gibati.', 'Salamat sa pagsabot.'] + }, + 'Woche 10 - Intensivwiederholung II': { + learningGoals: [ + 'Soziale und emotionale Gesprächsmuster verdichten.', + 'Höflich reagieren, helfen und erklären trainieren.', + 'Vor dem Wochenabschluss Sicherheit erhöhen.' + ], + corePatterns: ['Pasayloa ko.', 'Tabangan tika.', 'Nalipay ko.', 'Sunod na lang.'] + }, + 'Woche 10 - Checkpoint': { + learningGoals: [ + 'Die Sozialwoche abschließen.', + 'Hilfe, Einladung, Ablehnung und Emotionen gemischt prüfen.', + 'Stabilität für die Schlussmodule vorbereiten.' + ], + corePatterns: ['giinvite', 'tabang', 'pasayloa', 'nalipay'] + }, + 'Zuhause organisieren': { + learningGoals: [ + 'Haushalt, Ordnung und Aufgaben zuhause besprechen.', + 'Kurze Organisationsgespräche führen.', + 'Familienalltag mit Haushaltssprache stützen.' + ], + corePatterns: ['Ato ning limpyohan.', 'Asa nato ibutang?', 'Humanon nato karon.', 'Ayaw kalimot.'] + }, + 'Haushalt & Reparaturen': { + learningGoals: [ + 'Wörter zu Haushalt und kleinen Reparaturen lernen.', + 'Kaputte Dinge und Lösungen benennen.', + 'Haus- und Alltagswortschatz erweitern.' + ], + corePatterns: ['limpyo', 'guba', 'ayo', 'susi'] + }, + 'Vergangenes und Pläne im Alltag': { + learningGoals: [ + 'Vergangenes und Bevorstehendes im Alltag flüssiger ausdrücken.', + 'Ni- und Mo-/Mag- in Routinen wiederholen.', + 'Kurze Erzähl- und Planungssequenzen verbinden.' + ], + corePatterns: ['Nihapit ko ganiha.', 'Moadto ko unya.', 'Nahuman na namo.', 'Magluto mi ugma.'], + grammarFocus: [ + { + title: 'Alltagspläne verbinden', + text: 'In Alltagserzählungen wechseln Vergangenheit und Plan oft direkt nacheinander.', + example: 'Nihapit ko ganiha. Moadto ko unya.' + } + ] + }, + 'Arbeit, Schule und Termine verbinden': { + learningGoals: [ + 'Mehrere Tagesbereiche in einem Gespräch kombinieren.', + 'Arbeit, Schule und Termine als Ablauf beschreiben.', + 'Längere Alltagsketten sprachlich organisieren.' + ], + corePatterns: ['Una, moadto ko sa trabaho.', 'Pagkahuman, kuhaon nako ang bata.', 'Aduna mi appointment unya.', 'Busy kaayo ang adlaw.'] + }, + 'Woche 11 - Intensivwiederholung I': { + learningGoals: [ + 'Haushalt, Planung und Alltagslogistik zusammen wiederholen.', + 'Mehrschrittige Tagesabläufe stabilisieren.', + 'Organisation natürlicher versprachlichen.' + ], + corePatterns: ['Asa nato ibutang?', 'Moadto ko unya.', 'Aduna mi appointment.', 'Busy kaayo ang adlaw.'] + }, + 'Spiralwiederholung - Alltagsmodule': { + learningGoals: [ + 'Module 6 bis 10 zusammenziehen.', + 'Thematische Übergänge zwischen Familie, Gesundheit, Erledigungen und Sozialem üben.', + 'Den aktiven Alltagswortschatz stabilisieren.' + ], + corePatterns: ['bata', 'doktor', 'ATM', 'tabang'] + }, + 'Unterwegs im Familienalltag': { + learningGoals: [ + 'Transport, Termine und Familie in einer Situation verbinden.', + 'Unterwegs spontan reagieren.', + 'Alltagswege mit Personen und Aufgaben koppeln.' + ], + corePatterns: ['Kuhaon nako sila.', 'Moadto mi sa lungsod.', 'Asa ta manaog?', 'Dali lang, hapit na ta.'] + }, + 'Nachbarschaft & Orte': { + learningGoals: [ + 'Wichtige Orte im Wohnumfeld beschreiben.', + 'Nachbarschaftswortschatz mit Bewegung und Besuch verknüpfen.', + 'Alltagswege präziser benennen.' + ], + corePatterns: ['silingan', 'merkado', 'eskwelahan', 'botika'] + }, + 'Woche 11 - Intensivwiederholung II': { + learningGoals: [ + 'Die Alltagsmodule vor der Schlussphase verdichten.', + 'Schwache Bereiche gezielt wiederholen.', + 'Sicherheit im größeren Alltagskontext erhöhen.' + ], + corePatterns: ['kuhaon nako sila.', 'Ato ning limpyohan.', 'Aduna mi appointment.', 'Pwede ka motabang?'] + }, + 'Woche 11 - Checkpoint': { + learningGoals: [ + 'Den Übergang zur großen Integrationswoche absichern.', + 'Vernetztes Alltagsverständnis messen.', + 'Restliche Lücken vor Woche 12 sichtbar machen.' + ], + corePatterns: ['limpyo', 'appointment', 'silingan', 'busy'] + }, + 'Freies Gespräch - Alltag I': { + learningGoals: [ + 'Längere Alltagsgespräche freier führen.', + 'Mehrere Themen in einer Antwort verbinden.', + 'Mit wenig Stütze sprachlich handlungsfähig bleiben.' + ], + corePatterns: ['Maayo ra ang adlaw.', 'Daghan kog buhaton.', 'Moadto pa ko unya.', 'Human na mi sa balay.'] + }, + 'Mischtraining - Kernwortschatz': { + learningGoals: [ + 'Den Kernwortschatz quer über alle Module wiederholen.', + 'Ähnliche Wörter sicher unterscheiden.', + 'Abrufgeschwindigkeit erhöhen.' + ], + corePatterns: ['palihug', 'tabang', 'tambal', 'resibo'] + }, + 'Freies Gespräch - Alltag II': { + learningGoals: [ + 'Freies Sprechen unter leichtem Druck fortsetzen.', + 'Alltagserzählung, Fürsorge und Organisation kombinieren.', + 'Längere Reaktionen mit mehr Eigenproduktion wagen.' + ], + corePatterns: ['Gikapoy ko pero okay ra.', 'Naa pa koy lakaw.', 'Magkita mi unya.', 'Nasabtan na nako.'] + }, + 'Wiederholung schwieriger Muster': { + learningGoals: [ + 'Fehleranfällige Strukturen gezielt wiederholen.', + 'Verwechslungen bei Zeit, Bitte und Reaktion reduzieren.', + 'Vor der Schlussprüfung Stabilität erhöhen.' + ], + corePatterns: ['Niadto ko.', 'Moadto ko.', 'Palihug ka mubalik.', 'Dili lang sa karon.'], + grammarFocus: [ + { + title: 'Schwierige Kontraste', + text: 'Kurzformen, Zeitmarker und höfliche Reaktionen werden leicht verwechselt und sollten direkt kontrastiert werden.', + example: 'Niadto ko ganiha. Moadto ko ugma.' + } + ] + }, + 'Großes Alltagsreview I': { + learningGoals: [ + 'Den Alltagswortschatz großflächig abrufen.', + 'Mehrere Themenblöcke in gemischter Form wiederholen.', + 'Auf die Schlussaufgaben vorbereiten.' + ], + corePatterns: ['doktor', 'merkado', 'tabang', 'eskwela'] + }, + 'Fehlerschwerpunkte Alltag': { + learningGoals: [ + 'Typische Fehlercluster gezielt trainieren.', + 'Unsichere Antworten in stabilere Muster überführen.', + 'Schwächen vor dem Abschluss konzentriert bearbeiten.' + ], + corePatterns: ['salamat', 'palihug', 'appointment', 'mas maayo'] + }, + 'Rollenspiele - echte Situationen': { + learningGoals: [ + 'Mehrere reale Alltagsszenen zusammenhängend durchspielen.', + 'Mit spontanen Situationen flexibler umgehen.', + 'Alltagsfähigkeit vor dem Abschluss verdichten.' + ], + corePatterns: ['Pwede ka motabang?', 'Asa ang sakayan?', 'Adto ta sa doktor.', 'Kuhaon nako ang bata.'] + }, + 'Abschlusstest - Alltagspfad': { + learningGoals: [ + 'Den Wortschatz des Alltagskurses diagnostisch überprüfen.', + 'Ähnliche Bedeutungen und Wortfelder sauber trennen.', + 'Die aktive Basis für die Stabilisierungsphase absichern.' + ], + corePatterns: ['silingan', 'resibo', 'guba', 'eskwelahan'] + }, + 'Abschlussprüfung - Alltagsphase': { + learningGoals: [ + 'Den 3-Monats-Alltagspfad gemischt überprüfen.', + 'Abruf, Dialogfähigkeit und situative Flexibilität kombinieren.', + 'Die Grundlage für Phase 5 sichern.' + ], + corePatterns: ['Moadto mi sa lungsod.', 'Aduna mi appointment.', 'Pasayloa ko.', 'Ato ning limpyohan.'] + }, + 'Kultur, Höflichkeit & Familienleben vertiefen': { + learningGoals: [ + 'Höflichkeit, indirekte Kommunikation und Familienrollen tiefer verstehen.', + 'Sprachmuster kulturell besser einordnen.', + 'Den Übergang zur Stabilisierungsphase kulturell absichern.' + ], + corePatterns: ['respeto', 'pakikisama', 'palihug', 'amping'] + } +}; + +export const BISAYA_PHASE4_LESSONS = [ + { week: 7, day: 1, num: 61, type: 'conversation', title: 'Kinder im Alltag', desc: 'Mit Kindern sprechen und Fürsorge im Alltag ausdrücken', targetMin: 18, targetScore: 80, review: false, cultural: null }, + { week: 7, day: 1, num: 62, type: 'vocab', title: 'Schule & Betreuung', desc: 'Wortschatz für Schule, Tasche, Unterricht und Betreuung', targetMin: 18, targetScore: 85, review: true, cultural: null }, + { week: 7, day: 2, num: 63, type: 'grammar', title: 'Fragen an Kinder vereinfachen', desc: 'Kurze, klare Fragen für Kinder und Betreuungssituationen', targetMin: 20, targetScore: 78, review: true, cultural: null }, + { week: 7, day: 2, num: 64, type: 'conversation', title: 'Hausaufgaben & Routine', desc: 'Über Schule, Lernen und Routinen zuhause sprechen', targetMin: 18, targetScore: 80, review: false, cultural: null }, + { week: 7, day: 3, num: 65, type: 'review', title: 'Woche 7 - Intensivwiederholung I', desc: 'Kinder, Schule und Familienroutine intensiv wiederholen', targetMin: 28, targetScore: 80, review: false, cultural: null }, + { week: 7, day: 3, num: 66, type: 'vocab', title: 'Spiralwiederholung - Familie, Kinder & Fürsorge', desc: 'Frühe Kernmuster mit Kinderalltag verbinden', targetMin: 20, targetScore: 85, review: true, cultural: null }, + { week: 7, day: 4, num: 67, type: 'conversation', title: 'Spielen & Freizeit', desc: 'Mit Kindern über Spiel, Pause und Freizeit sprechen', targetMin: 18, targetScore: 80, review: false, cultural: null }, + { week: 7, day: 4, num: 68, type: 'vocab', title: 'Spielsachen & Aktivitäten', desc: 'Spiel- und Freizeitwortschatz für Familienalltag', targetMin: 18, targetScore: 85, review: true, cultural: null }, + { week: 7, day: 5, num: 69, type: 'review', title: 'Woche 7 - Intensivwiederholung II', desc: 'Große Mischwiederholung zur Kinder- und Schulwoche', targetMin: 28, targetScore: 80, review: false, cultural: null }, + { week: 7, day: 5, num: 70, type: 'vocab', title: 'Woche 7 - Checkpoint', desc: 'Checkpoint zu Familie, Kindern und Schule', targetMin: 16, targetScore: 82, review: true, cultural: null }, + { week: 8, day: 1, num: 71, type: 'conversation', title: 'Arzt & Termin', desc: 'Arzttermine vereinbaren und Gesundheitsfragen stellen', targetMin: 18, targetScore: 80, review: false, cultural: null }, + { week: 8, day: 1, num: 72, type: 'vocab', title: 'Apotheke & Medikamente', desc: 'Wichtiger Wortschatz für Apotheke und Medizin', targetMin: 18, targetScore: 85, review: true, cultural: null }, + { week: 8, day: 2, num: 73, type: 'grammar', title: 'Beschwerden genauer beschreiben', desc: 'Schmerz, Verlauf und Stärke konkreter ausdrücken', targetMin: 20, targetScore: 78, review: true, cultural: null }, + { week: 8, day: 2, num: 74, type: 'conversation', title: 'Notfälle & Hilfe', desc: 'In einfachen Notfällen Hilfe holen und reagieren', targetMin: 18, targetScore: 80, review: false, cultural: null }, + { week: 8, day: 3, num: 75, type: 'review', title: 'Woche 8 - Intensivwiederholung I', desc: 'Gesundheit, Arzt und Hilfe intensiv wiederholen', targetMin: 28, targetScore: 80, review: false, cultural: null }, + { week: 8, day: 3, num: 76, type: 'vocab', title: 'Spiralwiederholung - Gesundheit', desc: 'Frühere Fürsorge mit Gesundheit und Pflege verbinden', targetMin: 20, targetScore: 85, review: true, cultural: null }, + { week: 8, day: 4, num: 77, type: 'conversation', title: 'Essen, Ruhe & Genesung', desc: 'Pflege, Ruhe und Essen bei Krankheit besprechen', targetMin: 18, targetScore: 80, review: false, cultural: null }, + { week: 8, day: 4, num: 78, type: 'vocab', title: 'Körper, Symptome & Pflege', desc: 'Körper- und Pflegewortschatz ausbauen', targetMin: 18, targetScore: 85, review: true, cultural: null }, + { week: 8, day: 5, num: 79, type: 'review', title: 'Woche 8 - Intensivwiederholung II', desc: 'Große Mischwiederholung zu Gesundheit und Hilfe', targetMin: 28, targetScore: 80, review: false, cultural: null }, + { week: 8, day: 5, num: 80, type: 'vocab', title: 'Woche 8 - Checkpoint', desc: 'Checkpoint zu Arzt, Apotheke und Pflege', targetMin: 16, targetScore: 82, review: true, cultural: null }, + { week: 9, day: 1, num: 81, type: 'conversation', title: 'Einkaufen vertiefen', desc: 'Komplexere Einkaufs- und Auswahlgespräche führen', targetMin: 18, targetScore: 80, review: false, cultural: null }, + { week: 9, day: 1, num: 82, type: 'vocab', title: 'Markt & Mengen', desc: 'Wortschatz für Markt, Mengen und Auswahl', targetMin: 18, targetScore: 85, review: true, cultural: null }, + { week: 9, day: 2, num: 83, type: 'grammar', title: 'Wünsche, Bedarf und Bitte', desc: 'Wunsch, Notwendigkeit und höfliche Bitte unterscheiden', targetMin: 20, targetScore: 78, review: true, cultural: null }, + { week: 9, day: 2, num: 84, type: 'conversation', title: 'Behördengänge & Formulare', desc: 'Nach Formularen, Schaltern und Terminen fragen', targetMin: 18, targetScore: 80, review: false, cultural: null }, + { week: 9, day: 3, num: 85, type: 'review', title: 'Woche 9 - Intensivwiederholung I', desc: 'Einkaufen und Erledigungen intensiv wiederholen', targetMin: 28, targetScore: 80, review: false, cultural: null }, + { week: 9, day: 3, num: 86, type: 'vocab', title: 'Spiralwiederholung - Preise & Erledigungen', desc: 'Preis- und Erledigungsmuster spiralig festigen', targetMin: 20, targetScore: 85, review: true, cultural: null }, + { week: 9, day: 4, num: 87, type: 'conversation', title: 'Bank, Geld & Bezahlen', desc: 'Über Bezahlen, Wechselgeld und Geldsituationen sprechen', targetMin: 18, targetScore: 80, review: false, cultural: null }, + { week: 9, day: 4, num: 88, type: 'vocab', title: 'Dokumente & Termine', desc: 'Papierkram, Termine und Dokumente benennen', targetMin: 18, targetScore: 85, review: true, cultural: null }, + { week: 9, day: 5, num: 89, type: 'review', title: 'Woche 9 - Intensivwiederholung II', desc: 'Große Mischwiederholung zu Geld, Formularen und Terminen', targetMin: 28, targetScore: 80, review: false, cultural: null }, + { week: 9, day: 5, num: 90, type: 'vocab', title: 'Woche 9 - Checkpoint', desc: 'Checkpoint zu Einkauf, Bezahlen und Erledigungen', targetMin: 16, targetScore: 82, review: true, cultural: null }, + { week: 10, day: 1, num: 91, type: 'conversation', title: 'Nachbarschaft & Besuche', desc: 'Mit Nachbarn sprechen und Besuche im Umfeld einordnen', targetMin: 18, targetScore: 80, review: false, cultural: null }, + { week: 10, day: 1, num: 92, type: 'vocab', title: 'Hilfe & Unterstützung', desc: 'Wortschatz für Hilfe, Unterstützung und soziale Nähe', targetMin: 18, targetScore: 85, review: true, cultural: null }, + { week: 10, day: 2, num: 93, type: 'grammar', title: 'Höflich reagieren und ablehnen', desc: 'Einladungen, Hilfe und Vorschläge fein abstufen', targetMin: 20, targetScore: 78, review: true, cultural: null }, + { week: 10, day: 2, num: 94, type: 'conversation', title: 'Feste & Einladungen', desc: 'Über Feste, Besuche und Einladungen sprechen', targetMin: 18, targetScore: 80, review: false, cultural: null }, + { week: 10, day: 3, num: 95, type: 'review', title: 'Woche 10 - Intensivwiederholung I', desc: 'Soziale Situationen und Hilfe intensiv wiederholen', targetMin: 28, targetScore: 80, review: false, cultural: null }, + { week: 10, day: 3, num: 96, type: 'vocab', title: 'Spiralwiederholung - Soziale Situationen', desc: 'Besuche, Hilfe und Reaktionen blockübergreifend festigen', targetMin: 20, targetScore: 85, review: true, cultural: null }, + { week: 10, day: 4, num: 97, type: 'conversation', title: 'Konflikte ruhig lösen', desc: 'Missverständnisse ansprechen und Spannungen abfedern', targetMin: 18, targetScore: 80, review: false, cultural: null }, + { week: 10, day: 4, num: 98, type: 'vocab', title: 'Gefühle im Gespräch vertiefen', desc: 'Emotionen in sozialen Gesprächen genauer ausdrücken', targetMin: 18, targetScore: 85, review: true, cultural: null }, + { week: 10, day: 5, num: 99, type: 'review', title: 'Woche 10 - Intensivwiederholung II', desc: 'Große Mischwiederholung zu sozialen Situationen und Emotionen', targetMin: 28, targetScore: 80, review: false, cultural: null }, + { week: 10, day: 5, num: 100, type: 'vocab', title: 'Woche 10 - Checkpoint', desc: 'Checkpoint zu Hilfe, Besuchen und Gefühlen', targetMin: 16, targetScore: 82, review: true, cultural: null }, + { week: 11, day: 1, num: 101, type: 'conversation', title: 'Zuhause organisieren', desc: 'Ordnung, Haushalt und kleine Aufgaben zuhause besprechen', targetMin: 18, targetScore: 80, review: false, cultural: null }, + { week: 11, day: 1, num: 102, type: 'vocab', title: 'Haushalt & Reparaturen', desc: 'Wörter für Haushalt, Ordnung und kleine Reparaturen', targetMin: 18, targetScore: 85, review: true, cultural: null }, + { week: 11, day: 2, num: 103, type: 'grammar', title: 'Vergangenes und Pläne im Alltag', desc: 'Vergangenheit und kommende Schritte in Routinen verbinden', targetMin: 20, targetScore: 78, review: true, cultural: null }, + { week: 11, day: 2, num: 104, type: 'conversation', title: 'Arbeit, Schule und Termine verbinden', desc: 'Mehrere Alltagsbereiche in einer Erzählung kombinieren', targetMin: 18, targetScore: 80, review: false, cultural: null }, + { week: 11, day: 3, num: 105, type: 'review', title: 'Woche 11 - Intensivwiederholung I', desc: 'Haushalt, Planung und Alltagslogistik intensiv wiederholen', targetMin: 28, targetScore: 80, review: false, cultural: null }, + { week: 11, day: 3, num: 106, type: 'vocab', title: 'Spiralwiederholung - Alltagsmodule', desc: 'Module 6 bis 10 zusammenziehen und wiederholen', targetMin: 20, targetScore: 85, review: true, cultural: null }, + { week: 11, day: 4, num: 107, type: 'conversation', title: 'Unterwegs im Familienalltag', desc: 'Familie, Wege und Termine im Alltag verbinden', targetMin: 18, targetScore: 80, review: false, cultural: null }, + { week: 11, day: 4, num: 108, type: 'vocab', title: 'Nachbarschaft & Orte', desc: 'Wichtige Orte und Nachbarschaftswortschatz vertiefen', targetMin: 18, targetScore: 85, review: true, cultural: null }, + { week: 11, day: 5, num: 109, type: 'review', title: 'Woche 11 - Intensivwiederholung II', desc: 'Große Mischwiederholung zu Organisation und Familienlogistik', targetMin: 28, targetScore: 80, review: false, cultural: null }, + { week: 11, day: 5, num: 110, type: 'vocab', title: 'Woche 11 - Checkpoint', desc: 'Checkpoint zu Organisation, Haushalt und Alltagslogistik', targetMin: 16, targetScore: 82, review: true, cultural: null }, + { week: 12, day: 1, num: 111, type: 'conversation', title: 'Freies Gespräch - Alltag I', desc: 'Längere Alltagsgespräche freier und stabiler führen', targetMin: 22, targetScore: 78, review: false, cultural: null }, + { week: 12, day: 1, num: 112, type: 'vocab', title: 'Mischtraining - Kernwortschatz', desc: 'Kernwortschatz aus allen Modulen gemischt trainieren', targetMin: 20, targetScore: 85, review: true, cultural: null }, + { week: 12, day: 2, num: 113, type: 'conversation', title: 'Freies Gespräch - Alltag II', desc: 'Freies Sprechen mit mehr eigener Produktion vertiefen', targetMin: 22, targetScore: 78, review: false, cultural: null }, + { week: 12, day: 2, num: 114, type: 'grammar', title: 'Wiederholung schwieriger Muster', desc: 'Zeit, Bitte und Reaktion kontrastiv wiederholen', targetMin: 20, targetScore: 78, review: true, cultural: null }, + { week: 12, day: 3, num: 115, type: 'review', title: 'Großes Alltagsreview I', desc: 'Große Wiederholung aller Alltagsmodule', targetMin: 30, targetScore: 82, review: false, cultural: null }, + { week: 12, day: 3, num: 116, type: 'vocab', title: 'Fehlerschwerpunkte Alltag', desc: 'Typische Fehlercluster gezielt wiederholen', targetMin: 20, targetScore: 85, review: true, cultural: null }, + { week: 12, day: 4, num: 117, type: 'conversation', title: 'Rollenspiele - echte Situationen', desc: 'Mehrere reale Alltagsszenen in Rollenspielen üben', targetMin: 24, targetScore: 78, review: false, cultural: null }, + { week: 12, day: 4, num: 118, type: 'vocab', title: 'Abschlusstest - Alltagspfad', desc: 'Finaler Wortschatztest über den 3-Monats-Alltagspfad', targetMin: 20, targetScore: 82, review: true, cultural: null }, + { week: 12, day: 5, num: 119, type: 'review', title: 'Abschlussprüfung - Alltagsphase', desc: 'Große Abschlussprüfung zum 3-Monats-Alltagspfad', targetMin: 30, targetScore: 82, review: false, cultural: null }, + { week: 12, day: 5, num: 120, type: 'culture', title: 'Kultur, Höflichkeit & Familienleben vertiefen', desc: 'Kulturelle Muster für Sprache und Familienalltag weiter vertiefen', targetMin: 16, targetScore: 0, review: false, cultural: 'Die Alltagsphase endet mit einem stärkeren kulturellen Verständnis für Höflichkeit, Familie und indirekte Kommunikation.' } +]; diff --git a/backend/scripts/bisaya-course-phase5-extension.js b/backend/scripts/bisaya-course-phase5-extension.js new file mode 100644 index 0000000..3af222d --- /dev/null +++ b/backend/scripts/bisaya-course-phase5-extension.js @@ -0,0 +1,274 @@ +export const BISAYA_PHASE5_DIDACTICS = { + 'Dialogtag - Familie & Planung': { + learningGoals: [ + 'Mehrere Familienthemen in längeren Dialogen verbinden.', + 'Planung, Fürsorge und Organisation ohne enge Stütze kombinieren.', + 'Sprechfluss über mehrere Züge stabil halten.' + ], + corePatterns: ['Unsa atong plano karon?', 'Kuhaon nato ang bata unya.', 'Nikaon na ba silang tanan?', 'Pagkahuman, mouli ta.'] + }, + 'Mischtraining - Familie, Gesundheit, Alltag': { + learningGoals: [ + 'Kernwortschatz aus mehreren Modulen gemischt abrufen.', + 'Zwischen ähnlichen Alltagssituationen schneller unterscheiden.', + 'Stabile Wiedererkennung in gemischten Aufgaben sichern.' + ], + corePatterns: ['tambal', 'eskwela', 'merkado', 'silingan'] + }, + 'Fehlertraining - häufige Verwechslungen I': { + learningGoals: [ + 'Typische Verwechslungen bei Zeit, Bitte und Reaktion reduzieren.', + 'Kontrastpaare aktiv gegeneinander trainieren.', + 'Unsichere Routinemuster gezielt festigen.' + ], + corePatterns: ['Niadto ko.', 'Moadto ko.', 'Palihug.', 'Pasayloa ko.'], + grammarFocus: [ + { + title: 'Kontrasttraining', + text: 'Ähnliche Alltagsformen werden in der Stabilisierung bewusst direkt gegenübergestellt.', + example: 'Niadto ko ganiha. Moadto ko ugma.' + } + ] + }, + 'Rollenspiel - Schule, Arzt, Besuch': { + learningGoals: [ + 'Mehrere reale Situationen in längeren Rollenspielen verbinden.', + 'Schnell zwischen Schule, Arzt und Familienbesuch wechseln.', + 'Sprachliche Flexibilität im Alltag ausbauen.' + ], + corePatterns: ['Andam na ka sa eskwela?', 'Adto ta sa doktor.', 'Sulod lang.', 'Maghulat ta gamay.'] + }, + 'Stabilisierungsblock 1 - Intensiv I': { + learningGoals: [ + 'Zentrale Schnellstart- und Alltagsmuster in einem engen Block wiederholen.', + 'Abrufgeschwindigkeit und Reaktionssicherheit erhöhen.', + 'Schwache Felder sichtbar machen.' + ], + corePatterns: ['Kumusta ka?', 'Nikaon na ka?', 'Asa ang sakayan?', 'Pwede ka motabang?'] + }, + 'Fehlerschwerpunkte - Familie & Fürsorge': { + learningGoals: [ + 'Fehler bei Familien- und Fürsorgemustern gezielt abbauen.', + 'Nahe Bedeutungen in stabilere Antworten überführen.', + 'Häufige Stolperstellen transparent machen.' + ], + corePatterns: ['Palangga taka.', 'Mingaw ko nimo.', 'Magpahuway sa.', 'Andam na ka?'] + }, + 'Freies Erzählen - Mein Alltag': { + learningGoals: [ + 'Den eigenen Alltag zusammenhängend erzählen.', + 'Mehrere Tagesbereiche in einer freien Antwort verbinden.', + 'Mit weniger Stütze längere Produktion halten.' + ], + corePatterns: ['Sa buntag...', 'Pagkahuman...', 'Sa hapon...', 'Sa gabii...'] + }, + 'Mischtraining - Reale Situationen I': { + learningGoals: [ + 'Reale Alltagssituationen ohne Themenblockgrenzen mischen.', + 'Abruf in wechselnden Kontexten stabilisieren.', + 'Transfer über Modulgrenzen hinweg trainieren.' + ], + corePatterns: ['appointment', 'tabang', 'resibo', 'amping'] + }, + 'Stabilisierungsblock 1 - Intensiv II': { + learningGoals: [ + 'Fehlercluster und Mischtraining verdichten.', + 'Aufgaben schneller und sicherer lösen.', + 'Vor dem Checkpoint Stabilität erhöhen.' + ], + corePatterns: ['Pasayloa ko.', 'Dili lang sa karon.', 'Mubayad ko.', 'Mas maayo na ka?'] + }, + 'Stabilisierungsblock 1 - Checkpoint': { + learningGoals: [ + 'Den ersten Stabilisierungsschritt diagnostisch abschließen.', + 'Langzeitabruf, Transfer und Fehlerrisiken messen.', + 'Die zweite Stabilisierungsschleife vorbereiten.' + ], + corePatterns: ['appointment', 'palihug', 'silingan', 'eskwela'] + }, + 'Dialogtag - Organisation & Wege': { + learningGoals: [ + 'Organisation, Termine und Wege in längeren Dialogen verbinden.', + 'Planung über mehrere Schritte sprachlich strukturieren.', + 'Reaktionsfähigkeit in logistischer Alltagssprache stärken.' + ], + corePatterns: ['Una moadto ko didto.', 'Pagkahuman, mubalik ko diri.', 'Asa ta manaog?', 'Unsa ang sunod?'] + }, + 'Mischtraining - Gesundheit, Schule, Erledigungen': { + learningGoals: [ + 'Drei große Themenblöcke gemischt wiederholen.', + 'Kontextwechsel in Aufgaben trainieren.', + 'Langsamere Vergessenskurven abfangen.' + ], + corePatterns: ['doktor', 'assignment', 'resibo', 'botika'] + }, + 'Fehlertraining - häufige Verwechslungen II': { + learningGoals: [ + 'Zweite Runde der häufigsten Verwechslungen gezielt trainieren.', + 'Schwierige Antworten über Kontrast und Wiederholung stabilisieren.', + 'Präzisere Produktion fördern.' + ], + corePatterns: ['Gusto ko.', 'Kinahanglan nako.', 'Sunod na lang.', 'Dili mao ang akong pasabot.'] + }, + 'Rollenspiel - Konflikt und Hilfe': { + learningGoals: [ + 'Konflikt, Hilfe und Erklärung in Rollenspielen zusammenführen.', + 'Höflichkeit unter Druck bewahren.', + 'Soziale Reparaturmuster festigen.' + ], + corePatterns: ['Pwede nato istoryahan?', 'Tabangan tika.', 'Pasayloa ko.', 'Sabta lang ko.'] + }, + 'Stabilisierungsblock 2 - Intensiv I': { + learningGoals: [ + 'Schwierige Alltagsblöcke nochmals verdichtet wiederholen.', + 'Langzeitabruf gezielt trainieren.', + 'Vor dem Schlussabschnitt Fehlerquellen reduzieren.' + ], + corePatterns: ['Kinahanglan nako ni.', 'Adto ta sa doktor.', 'Giinvite tika.', 'Ato ning limpyohan.'] + }, + 'Fehlerschwerpunkte - Termine, Zeit, Reaktion': { + learningGoals: [ + 'Termine, Zeitbezüge und Reaktionen enger kontrastieren.', + 'Fehleranfällige Übergänge abbauen.', + 'Bessere Sicherheit in Planungsdialogen aufbauen.' + ], + corePatterns: ['ugma', 'ganiha', 'unya', 'sunod na lang'] + }, + 'Freies Erzählen - Familie, Sorgen, Pläne': { + learningGoals: [ + 'Familiensituation, Sorgen und Zukunftsplan zusammenhängend erzählen.', + 'Emotion, Organisation und Hilfe verbinden.', + 'Freie Produktion vertiefen.' + ], + corePatterns: ['Naguol ko gamay.', 'Pero okay ra.', 'Aduna koy plano unya.', 'Tabangan mi nila.'] + }, + 'Mischtraining - Reale Situationen II': { + learningGoals: [ + 'Eine zweite große Mischrunde mit neuen Kombinationen durchführen.', + 'Transfer über die ganze Alltagsphase absichern.', + 'Robustheit bei ungeordnetem Themenwechsel erhöhen.' + ], + corePatterns: ['doktor', 'ATM', 'duwa', 'linya'] + }, + 'Stabilisierungsblock 2 - Intensiv II': { + learningGoals: [ + 'Die zweite Stabilisierungsschleife verdichten.', + 'Restliche Schwächen noch einmal fokussieren.', + 'Vor dem Gesamtabschluss maximale Sicherheit aufbauen.' + ], + corePatterns: ['Palihug ka mubalik.', 'Moadto ko unya.', 'Salamat sa tabang.', 'Asa ang porma?'] + }, + 'Stabilisierungsblock 2 - Checkpoint': { + learningGoals: [ + 'Die zweite Stabilisierungsrunde abschließen.', + 'Diagnostisch prüfen, welche Langzeitmuster noch wackeln.', + 'Den Schlussblock vorbereiten.' + ], + corePatterns: ['porma', 'sukli', 'pasayloa', 'amping'] + }, + 'Großes Mischreview I': { + learningGoals: [ + 'Den gesamten bisherigen Kurs breit wiederholen.', + 'Thematische Grenzen in der Wiederholung auflösen.', + 'Schnelles, flexibles Abrufen vorbereiten.' + ], + corePatterns: ['bata', 'doktor', 'merkado', 'tabang'] + }, + 'Großes Mischreview II': { + learningGoals: [ + 'Eine zweite große Mischrunde mit höherer Dichte trainieren.', + 'Ähnliche Muster unter Zeitdruck stabilisieren.', + 'Abschlussreife aufbauen.' + ], + corePatterns: ['appointment', 'resibo', 'palihug', 'silingan'] + }, + 'Fehlertraining - letzte Schwächen': { + learningGoals: [ + 'Die letzten wackeligen Muster gezielt bearbeiten.', + 'Unsichere Antworten in belastbare Routinen überführen.', + 'Vor dem Abschluss die Fehlerquote senken.' + ], + corePatterns: ['Niadto ko.', 'Moadto ko.', 'Dili lang sa karon.', 'Mas maayo na ka?'] + }, + 'Freies Sprechen - Alltag ohne Stütze': { + learningGoals: [ + 'Alltagsgespräche mit minimaler Hilfe frei führen.', + 'Eigenständige Produktion gegenüber Wiedererkennung priorisieren.', + 'Selbstsicherheit in längeren Antworten ausbauen.' + ], + corePatterns: ['Sa tinuod...', 'Kasagaran...', 'Usahay...', 'Apan...'] + }, + 'Langzeitreview - Intensiv I': { + learningGoals: [ + 'Früh gelernte Inhalte gezielt gegen Vergessen absichern.', + 'Langfristige Wiederaufnahme trainieren.', + 'Kernmuster mit hoher Alltagsrelevanz stabil halten.' + ], + corePatterns: ['Kumusta ka?', 'Salamat.', 'Nikaon na ka?', 'Tagpila ni?'] + }, + 'Langzeitreview - Intensiv II': { + learningGoals: [ + 'Zweite Langzeitwiederholung mit Schwerpunkt auf Transfer.', + 'Frühe und späte Inhalte gemeinsam verankern.', + 'Vergessene Muster reaktivieren.' + ], + corePatterns: ['doktor', 'eskwela', 'tabang', 'resibo'] + }, + 'Abschlusstest - Stabilisierung': { + learningGoals: [ + 'Den gesamten Stabilisierungspfad diagnostisch überprüfen.', + 'Wortschatz, Muster und Kontrastpaare testen.', + 'Die Abschlussprüfung vorbereiten.' + ], + corePatterns: ['appointment', 'amping', 'sukli', 'palangga'] + }, + 'Abschlussprüfung - Gesamtpfad': { + learningGoals: [ + 'Den gesamten Bisaya-Pfad bis zur Stabilisierungsphase abschließen.', + 'Abruf, freie Produktion und Alltagstransfer kombinieren.', + 'Das Fundament für späteres Langzeitlernen sichern.' + ], + corePatterns: ['Moadto mi sa lungsod.', 'Aduna mi appointment.', 'Pasayloa ko.', 'Tabangan tika.'] + }, + 'Kultur, Familie & Sprache langfristig': { + learningGoals: [ + 'Sprachgebrauch, Höflichkeit und Familienrollen langfristig reflektieren.', + 'Kulturelle Muster mit stabilem Sprachwissen verbinden.', + 'Den Übergang in offenes Weiterlernen begleiten.' + ], + corePatterns: ['respeto', 'pakikisama', 'amping', 'palihug'] + } +}; + +export const BISAYA_PHASE5_LESSONS = [ + { week: 13, day: 1, num: 121, type: 'conversation', title: 'Dialogtag - Familie & Planung', desc: 'Längere Dialoge zu Familie, Planung und Fürsorge', targetMin: 22, targetScore: 78, review: false, cultural: null }, + { week: 13, day: 1, num: 122, type: 'vocab', title: 'Mischtraining - Familie, Gesundheit, Alltag', desc: 'Gemischter Wortschatz aus zentralen Alltagsfeldern', targetMin: 20, targetScore: 85, review: true, cultural: null }, + { week: 13, day: 2, num: 123, type: 'grammar', title: 'Fehlertraining - häufige Verwechslungen I', desc: 'Kontrasttraining für typische Zeit- und Reaktionsfehler', targetMin: 20, targetScore: 78, review: true, cultural: null }, + { week: 13, day: 2, num: 124, type: 'conversation', title: 'Rollenspiel - Schule, Arzt, Besuch', desc: 'Mehrere reale Situationen in Rollenspielen verbinden', targetMin: 22, targetScore: 78, review: false, cultural: null }, + { week: 13, day: 3, num: 125, type: 'review', title: 'Stabilisierungsblock 1 - Intensiv I', desc: 'Erste große Stabilisierungsschleife', targetMin: 30, targetScore: 82, review: false, cultural: null }, + { week: 13, day: 3, num: 126, type: 'vocab', title: 'Fehlerschwerpunkte - Familie & Fürsorge', desc: 'Gezieltes Fehlertraining zu Nähe und Fürsorge', targetMin: 20, targetScore: 85, review: true, cultural: null }, + { week: 13, day: 4, num: 127, type: 'conversation', title: 'Freies Erzählen - Mein Alltag', desc: 'Den eigenen Alltag freier und länger erzählen', targetMin: 24, targetScore: 78, review: false, cultural: null }, + { week: 13, day: 4, num: 128, type: 'vocab', title: 'Mischtraining - Reale Situationen I', desc: 'Gemischtes Training realer Alltagssituationen', targetMin: 20, targetScore: 85, review: true, cultural: null }, + { week: 13, day: 5, num: 129, type: 'review', title: 'Stabilisierungsblock 1 - Intensiv II', desc: 'Zweite verdichtete Wiederholung im ersten Stabilisierungsschritt', targetMin: 30, targetScore: 82, review: false, cultural: null }, + { week: 13, day: 5, num: 130, type: 'vocab', title: 'Stabilisierungsblock 1 - Checkpoint', desc: 'Checkpoint zum ersten Stabilisierungsschritt', targetMin: 18, targetScore: 84, review: true, cultural: null }, + { week: 14, day: 1, num: 131, type: 'conversation', title: 'Dialogtag - Organisation & Wege', desc: 'Planung, Wege und Termine in längeren Dialogen verbinden', targetMin: 22, targetScore: 78, review: false, cultural: null }, + { week: 14, day: 1, num: 132, type: 'vocab', title: 'Mischtraining - Gesundheit, Schule, Erledigungen', desc: 'Gemischter Wortschatz aus drei großen Themenblöcken', targetMin: 20, targetScore: 85, review: true, cultural: null }, + { week: 14, day: 2, num: 133, type: 'grammar', title: 'Fehlertraining - häufige Verwechslungen II', desc: 'Zweite Runde Kontrasttraining für schwierige Alltagsmuster', targetMin: 20, targetScore: 78, review: true, cultural: null }, + { week: 14, day: 2, num: 134, type: 'conversation', title: 'Rollenspiel - Konflikt und Hilfe', desc: 'Konflikt, Hilfe und Erklärung in Rollenspielen zusammenführen', targetMin: 22, targetScore: 78, review: false, cultural: null }, + { week: 14, day: 3, num: 135, type: 'review', title: 'Stabilisierungsblock 2 - Intensiv I', desc: 'Erste verdichtete Wiederholung im zweiten Stabilisierungsschritt', targetMin: 30, targetScore: 82, review: false, cultural: null }, + { week: 14, day: 3, num: 136, type: 'vocab', title: 'Fehlerschwerpunkte - Termine, Zeit, Reaktion', desc: 'Gezieltes Fehlertraining zu Zeit, Reaktion und Terminmustern', targetMin: 20, targetScore: 85, review: true, cultural: null }, + { week: 14, day: 4, num: 137, type: 'conversation', title: 'Freies Erzählen - Familie, Sorgen, Pläne', desc: 'Familie, Sorgen und Zukunftspläne frei verbinden', targetMin: 24, targetScore: 78, review: false, cultural: null }, + { week: 14, day: 4, num: 138, type: 'vocab', title: 'Mischtraining - Reale Situationen II', desc: 'Zweite große Mischrunde realer Alltagssituationen', targetMin: 20, targetScore: 85, review: true, cultural: null }, + { week: 14, day: 5, num: 139, type: 'review', title: 'Stabilisierungsblock 2 - Intensiv II', desc: 'Zweite verdichtete Wiederholung im zweiten Stabilisierungsschritt', targetMin: 30, targetScore: 82, review: false, cultural: null }, + { week: 14, day: 5, num: 140, type: 'vocab', title: 'Stabilisierungsblock 2 - Checkpoint', desc: 'Checkpoint zum zweiten Stabilisierungsschritt', targetMin: 18, targetScore: 84, review: true, cultural: null }, + { week: 15, day: 1, num: 141, type: 'review', title: 'Großes Mischreview I', desc: 'Große blockübergreifende Wiederholung des Gesamtpfads', targetMin: 30, targetScore: 82, review: false, cultural: null }, + { week: 15, day: 1, num: 142, type: 'vocab', title: 'Großes Mischreview II', desc: 'Zweite große Mischrunde mit höherer Dichte', targetMin: 20, targetScore: 85, review: true, cultural: null }, + { week: 15, day: 2, num: 143, type: 'grammar', title: 'Fehlertraining - letzte Schwächen', desc: 'Letzte typische Fehler vor dem Gesamtabschluss bearbeiten', targetMin: 20, targetScore: 78, review: true, cultural: null }, + { week: 15, day: 2, num: 144, type: 'conversation', title: 'Freies Sprechen - Alltag ohne Stütze', desc: 'Alltagsgespräche mit minimaler Hilfe frei führen', targetMin: 24, targetScore: 78, review: false, cultural: null }, + { week: 15, day: 3, num: 145, type: 'review', title: 'Langzeitreview - Intensiv I', desc: 'Frühe Inhalte gezielt gegen Vergessen absichern', targetMin: 30, targetScore: 82, review: false, cultural: null }, + { week: 15, day: 3, num: 146, type: 'vocab', title: 'Langzeitreview - Intensiv II', desc: 'Frühe und späte Inhalte gemeinsam reaktivieren', targetMin: 20, targetScore: 85, review: true, cultural: null }, + { week: 15, day: 4, num: 147, type: 'conversation', title: 'Rollenspiele - echte Situationen', desc: 'Mehrere reale Alltagsszenen in längeren Rollenspielen üben', targetMin: 24, targetScore: 78, review: false, cultural: null }, + { week: 15, day: 4, num: 148, type: 'vocab', title: 'Abschlusstest - Stabilisierung', desc: 'Finaler Test über den Stabilisierungspfad', targetMin: 20, targetScore: 84, review: true, cultural: null }, + { week: 15, day: 5, num: 149, type: 'review', title: 'Abschlussprüfung - Gesamtpfad', desc: 'Große Abschlussprüfung über den gesamten Bisaya-Pfad', targetMin: 32, targetScore: 84, review: false, cultural: null }, + { week: 15, day: 5, num: 150, type: 'culture', title: 'Kultur, Familie & Sprache langfristig', desc: 'Kultur, Familie und Höflichkeit langfristig einordnen', targetMin: 16, targetScore: 0, review: false, cultural: 'Die Stabilisierung endet mit einem bewussten Blick auf Sprache, Familie und kulturelle Langzeitmuster.' } +]; diff --git a/backend/scripts/create-bisaya-course-content.js b/backend/scripts/create-bisaya-course-content.js index b4b8294..0cdab3e 100644 --- a/backend/scripts/create-bisaya-course-content.js +++ b/backend/scripts/create-bisaya-course-content.js @@ -13,6 +13,9 @@ import VocabCourseLesson from '../models/community/vocab_course_lesson.js'; import VocabGrammarExercise from '../models/community/vocab_grammar_exercise.js'; import VocabCourse from '../models/community/vocab_course.js'; import User from '../models/community/user.js'; +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'; function withTypeName(exerciseTypeName, exercise) { return { @@ -21,6 +24,356 @@ function withTypeName(exerciseTypeName, exercise) { }; } +const GENERATED_BISAYA_DIDACTICS = { + ...BISAYA_PHASE3_DIDACTICS, + ...BISAYA_PHASE4_DIDACTICS, + ...BISAYA_PHASE5_DIDACTICS +}; + +const GENERIC_DISTRACTOR_PATTERNS = Array.from(new Set( + Object.values(GENERATED_BISAYA_DIDACTICS) + .flatMap((entry) => Array.isArray(entry?.corePatterns) ? entry.corePatterns : []) + .map((pattern) => String(pattern || '').trim()) + .filter(Boolean) +)).slice(0, 200); + +function normalizeText(value) { + return String(value || '') + .trim() + .replace(/\s+/g, ' '); +} + +function simpleHash(value) { + return Array.from(String(value || '')).reduce((sum, char) => sum + char.charCodeAt(0), 0); +} + +function rotateArray(values, offset) { + if (!Array.isArray(values) || values.length === 0) return []; + const normalizedOffset = ((offset % values.length) + values.length) % values.length; + return values.slice(normalizedOffset).concat(values.slice(0, normalizedOffset)); +} + +function getLessonDidactics(lesson) { + const staticDidactics = GENERATED_BISAYA_DIDACTICS[lesson.title] || {}; + const learningGoals = Array.isArray(lesson.learningGoals) ? lesson.learningGoals : (staticDidactics.learningGoals || []); + const corePatterns = Array.isArray(lesson.corePatterns) ? lesson.corePatterns : (staticDidactics.corePatterns || []); + const grammarFocus = Array.isArray(lesson.grammarFocus) ? lesson.grammarFocus : (staticDidactics.grammarFocus || []); + const speakingPrompts = Array.isArray(lesson.speakingPrompts) ? lesson.speakingPrompts : (staticDidactics.speakingPrompts || []); + const practicalTasks = Array.isArray(lesson.practicalTasks) ? lesson.practicalTasks : (staticDidactics.practicalTasks || []); + + return { + learningGoals, + corePatterns: corePatterns.map((entry) => normalizeText(entry)).filter(Boolean), + grammarFocus, + speakingPrompts, + practicalTasks + }; +} + +function getScenarioPrompt(lesson, didactics) { + const speakingPrompt = Array.isArray(didactics.speakingPrompts) ? didactics.speakingPrompts[0] : null; + const practicalTask = Array.isArray(didactics.practicalTasks) ? didactics.practicalTasks[0] : null; + + if (speakingPrompt?.prompt) return speakingPrompt.prompt; + if (practicalTask?.text) return practicalTask.text; + if (lesson.description) return lesson.description; + return `Reagiere passend in einer Situation aus der Lektion "${lesson.title}".`; +} + +function getChoiceQuestion(lesson, didactics) { + const scenarioPrompt = getScenarioPrompt(lesson, didactics); + + switch (lesson.lessonType) { + case 'conversation': + return `${scenarioPrompt} Welche Bisaya-Formulierung passt am besten?`; + case 'grammar': + return `Welche Formulierung passt als kurze Alltagsstruktur zu "${lesson.title}"?`; + case 'review': + return `Welche Formulierung solltest du aus "${lesson.title}" sicher wiedererkennen?`; + case 'culture': + return `Welcher Ausdruck gehört am ehesten zum kulturellen Schwerpunkt "${lesson.title}"?`; + case 'vocab': + default: + return `Welcher Ausdruck gehört thematisch zu "${lesson.title}"?`; + } +} + +function pickDistractors(pattern, allPatterns, count) { + return allPatterns + .filter((entry) => entry !== pattern) + .slice(0, count); +} + +function buildChoiceExercise(lesson, didactics, pattern, allPatterns, variant = 0) { + const distractors = pickDistractors(pattern, allPatterns, 3); + if (distractors.length < 3) return null; + + const seed = simpleHash(`${lesson.title}:${pattern}:${variant}`); + const options = rotateArray([pattern, ...distractors], seed % 4); + const correctAnswer = options.indexOf(pattern); + + return { + exerciseTypeId: 2, + title: `${lesson.title}: Passende Formulierung wählen`, + instruction: 'Wähle die natürlichste Formulierung für die Situation oder den Schwerpunkt der Lektion.', + questionData: { + type: 'multiple_choice', + question: getChoiceQuestion(lesson, didactics), + options + }, + answerData: { + type: 'multiple_choice', + correctAnswer + }, + explanation: `"${pattern}" ist ein zentrales Muster dieser Lektion.` + }; +} + +function pickGapTarget(pattern) { + const tokens = normalizeText(pattern) + .split(' ') + .map((token) => token.trim()) + .filter(Boolean); + + const candidates = tokens + .map((token, index) => ({ token, index, score: token.replace(/[.,?!]/g, '').length })) + .filter(({ token }) => token.replace(/[.,?!]/g, '').length >= 4); + + if (candidates.length === 0) { + return null; + } + + candidates.sort((left, right) => right.score - left.score); + return candidates[0]; +} + +function buildGapExercise(lessonTitle, pattern) { + const gapTarget = pickGapTarget(pattern); + if (!gapTarget) return null; + + const tokens = normalizeText(pattern).split(' '); + tokens[gapTarget.index] = '{gap}'; + + return { + exerciseTypeId: 1, + title: `${lessonTitle}: Muster vervollständigen`, + instruction: 'Fülle die Lücke mit dem passenden Bisaya-Ausdruck.', + questionData: { + type: 'gap_fill', + text: tokens.join(' '), + gaps: 1 + }, + answerData: { + type: 'gap_fill', + answers: [gapTarget.token.replace(/[.,?!]/g, '')] + }, + explanation: `Das Kernmuster lautet: "${pattern}".` + }; +} + +function buildContextGapExercise(lesson, didactics, pattern) { + const gapExercise = buildGapExercise(lesson.title, pattern); + if (!gapExercise) return null; + + return { + ...gapExercise, + title: `${lesson.title}: Kernmuster ergänzen`, + instruction: `Vervollständige die Formulierung passend zur Situation: ${getScenarioPrompt(lesson, didactics)}` + }; +} + +function buildSentenceExercise(lessonTitle, pattern) { + const tokens = normalizeText(pattern) + .replace(/[?!]/g, '') + .split(' ') + .filter(Boolean); + + if (tokens.length < 2) return null; + + return { + exerciseTypeId: 3, + title: `${lessonTitle}: Satzmuster bauen`, + instruction: 'Ordne die Wörter zu einem korrekten Bisaya-Satz.', + questionData: { + type: 'sentence_building', + question: `Baue das Kernmuster aus der Lektion "${lessonTitle}".`, + tokens + }, + answerData: { + correct: [normalizeText(pattern)] + }, + explanation: `Dieses Kernmuster gehört zur Lektion "${lessonTitle}".` + }; +} + +function buildTaskSentenceExercise(lesson, didactics, pattern) { + const sentenceExercise = buildSentenceExercise(lesson.title, pattern); + if (!sentenceExercise) return null; + + const practicalTask = Array.isArray(didactics.practicalTasks) ? didactics.practicalTasks[0] : null; + + return { + ...sentenceExercise, + title: `${lesson.title}: Satz aus dem Alltag bauen`, + instruction: practicalTask?.text + ? `Baue die passende Formulierung für diese Aufgabe: ${practicalTask.text}` + : 'Ordne die Wörter zu einem natürlichen Bisaya-Satz aus dem Alltag.' + }; +} + +function buildSpeakingExercise(lessonTitle, didactics, fallbackPattern) { + const speakingPrompt = Array.isArray(didactics.speakingPrompts) ? didactics.speakingPrompts[0] : null; + const expectedText = normalizeText(speakingPrompt?.cue || fallbackPattern); + if (!expectedText) return null; + + const keywords = expectedText + .toLowerCase() + .replace(/[.,?!]/g, '') + .split(' ') + .filter((token) => token.length >= 4) + .slice(0, 4); + + return { + exerciseTypeId: 8, + title: `${lessonTitle}: Laut sprechen`, + instruction: 'Sprich das zentrale Muster oder den Dialog frei nach.', + questionData: { + type: 'speaking_from_memory', + question: speakingPrompt?.prompt || `Sprich ein zentrales Muster aus der Lektion "${lessonTitle}".`, + expectedText, + keywords + }, + answerData: { + type: 'speaking_from_memory' + }, + explanation: 'Wichtig sind hier ein flüssiger Abruf und die zentralen Schlüsselwörter.' + }; +} + +function buildReviewChoiceExercise(lesson, didactics, pattern, allPatterns) { + const choiceExercise = buildChoiceExercise(lesson, didactics, pattern, allPatterns, 1); + if (!choiceExercise) return null; + + return { + ...choiceExercise, + title: `${lesson.title}: Sicheren Abruf prüfen`, + instruction: 'Wähle die Formulierung, die du aus dem aktiven Wiederholungsblock sicher können solltest.' + }; +} + +function buildCultureExercise(lesson, didactics, pattern, allPatterns) { + const distractors = pickDistractors(pattern, allPatterns, 3); + if (distractors.length < 3) return null; + + const culturalNote = normalizeText(lesson.culturalNotes || ''); + const options = rotateArray([pattern, ...distractors], simpleHash(`${lesson.title}:culture`) % 4); + + return { + exerciseTypeId: 2, + title: `${lesson.title}: Ausdruck kulturell einordnen`, + instruction: 'Ordne den Ausdruck dem kulturellen Schwerpunkt der Lektion zu.', + questionData: { + type: 'multiple_choice', + question: culturalNote + ? `${culturalNote} Welcher Ausdruck passt dazu besonders gut?` + : `Welcher Ausdruck gehört besonders gut zum Schwerpunkt "${lesson.title}"?`, + options + }, + answerData: { + type: 'multiple_choice', + correctAnswer: options.indexOf(pattern) + }, + explanation: `Der Ausdruck "${pattern}" gehört eng zum kulturellen Schwerpunkt dieser Lektion.` + }; +} + +function buildSituationalExercise(lessonTitle, didactics, fallbackPattern) { + const speakingPrompt = Array.isArray(didactics.speakingPrompts) ? didactics.speakingPrompts[0] : null; + const modelAnswer = normalizeText(speakingPrompt?.cue || fallbackPattern); + if (!modelAnswer) return null; + + const keywords = modelAnswer + .toLowerCase() + .replace(/[.,?!]/g, '') + .split(' ') + .filter((token) => token.length >= 4) + .slice(0, 4); + + return withTypeName('situational_response', { + title: `${lessonTitle}: Situativ reagieren`, + instruction: 'Antworte kurz und passend auf die Situation.', + questionData: { + type: 'situational_response', + question: speakingPrompt?.prompt || `Reagiere passend mit einem Ausdruck aus der Lektion "${lessonTitle}".`, + keywords + }, + answerData: { + modelAnswer, + keywords + }, + explanation: `Das Kernmuster "${modelAnswer}" passt natürlich zu dieser Situation.` + }); +} + +function generateExercisesFromDidactics(lesson) { + const didactics = getLessonDidactics(lesson); + const corePatterns = didactics.corePatterns; + + if (corePatterns.length === 0) { + return []; + } + + const patternA = corePatterns[0]; + const patternB = corePatterns[1] || corePatterns[0]; + const lessonPool = Array.from(new Set([ + ...corePatterns, + ...GENERIC_DISTRACTOR_PATTERNS + ])); + let generated = []; + + if (lesson.lessonType === 'conversation') { + generated = [ + buildChoiceExercise(lesson, didactics, patternA, lessonPool, 0), + buildContextGapExercise(lesson, didactics, patternA), + buildTaskSentenceExercise(lesson, didactics, patternB), + buildSituationalExercise(lesson.title, didactics, patternA), + buildSpeakingExercise(lesson.title, didactics, patternB) + ]; + } else if (lesson.lessonType === 'grammar') { + generated = [ + buildChoiceExercise(lesson, didactics, patternA, lessonPool, 0), + buildChoiceExercise(lesson, didactics, patternB, lessonPool, 1), + buildContextGapExercise(lesson, didactics, patternA), + buildTaskSentenceExercise(lesson, didactics, patternB), + buildSpeakingExercise(lesson.title, didactics, patternA) + ]; + } else if (lesson.lessonType === 'review' || lesson.didacticMode === 'intensive_review') { + generated = [ + buildReviewChoiceExercise(lesson, didactics, patternA, lessonPool), + buildReviewChoiceExercise(lesson, didactics, patternB, lessonPool), + buildContextGapExercise(lesson, didactics, patternA), + buildTaskSentenceExercise(lesson, didactics, patternB), + buildSituationalExercise(lesson.title, didactics, patternA), + buildSpeakingExercise(lesson.title, didactics, patternB) + ]; + } else if (lesson.lessonType === 'culture') { + generated = [ + buildCultureExercise(lesson, didactics, patternA, lessonPool), + buildContextGapExercise(lesson, didactics, patternA), + buildSpeakingExercise(lesson.title, didactics, patternB) + ]; + } else { + generated = [ + buildChoiceExercise(lesson, didactics, patternA, lessonPool, 0), + buildChoiceExercise(lesson, didactics, patternB, lessonPool, 1), + buildContextGapExercise(lesson, didactics, patternA), + buildTaskSentenceExercise(lesson, didactics, patternB) + ]; + } + + return generated.filter(Boolean); +} + // Bisaya-spezifische Übungen basierend auf Lektionsthemen const BISAYA_EXERCISES = { // Lektion 1: Begrüßungen & Höflichkeit @@ -1489,7 +1842,8 @@ async function findOrCreateSystemUser() { return systemUser; } -function getExercisesForLesson(lessonTitle) { +function getExercisesForLesson(lesson) { + const lessonTitle = lesson.title; // Suche nach exaktem Titel if (BISAYA_EXERCISES[lessonTitle]) { return BISAYA_EXERCISES[lessonTitle]; @@ -1501,9 +1855,8 @@ function getExercisesForLesson(lessonTitle) { return exercises; } } - - // Keine Übungen für unbekannte Lektionen (statt Dummy-Übungen) - return []; + + return generateExercisesFromDidactics(lesson); } async function createBisayaCourseContent() { @@ -1550,7 +1903,7 @@ async function createBisayaCourseContent() { console.log(` ${lessons.length} Lektionen gefunden\n`); for (const lesson of lessons) { - const exercises = getExercisesForLesson(lesson.title); + const exercises = getExercisesForLesson(lesson); if (exercises.length === 0) { const existingCount = await VocabGrammarExercise.count({ where: { lessonId: lesson.id } }); if (existingCount > 0) { diff --git a/backend/scripts/create-bisaya-course.js b/backend/scripts/create-bisaya-course.js index bc596be..9c8154c 100755 --- a/backend/scripts/create-bisaya-course.js +++ b/backend/scripts/create-bisaya-course.js @@ -1,6 +1,6 @@ #!/usr/bin/env node /** - * Script zum Erstellen eines vollständigen 4-Wochen Bisaya-Kurses + * Script zum Erstellen eines vollständigen 6-Wochen Bisaya-Kurses * * Verwendung: * node backend/scripts/create-bisaya-course.js @@ -11,6 +11,10 @@ import VocabCourse from '../models/community/vocab_course.js'; import VocabCourseLesson from '../models/community/vocab_course_lesson.js'; import User from '../models/community/user.js'; import crypto from 'crypto'; +import { getBisayaLessonPedagogy } from './bisaya-course-phase2-pedagogy.js'; +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'; const LESSON_DIDACTICS = { 'Begrüßungen & Höflichkeit': { @@ -192,7 +196,10 @@ const LESSON_DIDACTICS = { 'Lami', 'Mingaw ko nimo' ] - } + }, + ...BISAYA_PHASE3_DIDACTICS, + ...BISAYA_PHASE4_DIDACTICS, + ...BISAYA_PHASE5_DIDACTICS }; const LESSONS = [ @@ -398,7 +405,11 @@ const LESSONS = [ { week: 4, day: 5, num: 40, type: 'culture', title: 'Kulturelle Tipps & Tricks', desc: 'Wichtige kulturelle Hinweise für den Alltag', targetMin: 15, targetScore: 0, review: false, - cultural: 'Kulturelles Verständnis ist genauso wichtig wie die Sprache selbst.' } + cultural: 'Kulturelles Verständnis ist genauso wichtig wie die Sprache selbst.' }, + + ...BISAYA_PHASE3_LESSONS, + ...BISAYA_PHASE4_LESSONS, + ...BISAYA_PHASE5_LESSONS ]; async function createBisayaCourse(languageId, ownerHashedId) { @@ -422,8 +433,8 @@ async function createBisayaCourse(languageId, ownerHashedId) { const shareCode = crypto.randomBytes(8).toString('hex'); const course = await VocabCourse.create({ ownerUserId: user.id, - title: 'Bisaya für Familien - Schnellstart in 4 Wochen', - description: 'Lerne Bisaya (Cebuano) schnell und praktisch für den Familienalltag. Fokus auf Sprechen & Hören mit strukturiertem 4-Wochen-Plan.', + title: 'Bisaya für Familien - Alltag & Stabilisierung', + description: 'Lerne Bisaya (Cebuano) praxisnah für den Familienalltag. Der Pfad verbindet Schnellstart, Alltagsmodule und Stabilisierungsblöcke mit Spiralwiederholung, Fehlertraining und freier Produktion.', languageId: Number(languageId), difficultyLevel: 1, isPublic: true, @@ -435,6 +446,7 @@ async function createBisayaCourse(languageId, ownerHashedId) { // Erstelle Lektionen for (const lessonData of LESSONS) { + const pedagogy = getBisayaLessonPedagogy(lessonData.num) || {}; const lesson = await VocabCourseLesson.create({ courseId: course.id, chapterId: null, // Wird später mit Vokabeln verknüpft @@ -452,7 +464,14 @@ async function createBisayaCourse(languageId, ownerHashedId) { practicalTasks: LESSON_DIDACTICS[lessonData.title]?.practicalTasks || [], targetMinutes: lessonData.targetMin, targetScorePercent: lessonData.targetScore, - requiresReview: lessonData.review + requiresReview: lessonData.review, + didacticMode: pedagogy.didacticMode || null, + phaseLabel: pedagogy.phaseLabel || null, + blockNumber: pedagogy.blockNumber ?? null, + difficultyWeight: pedagogy.difficultyWeight ?? null, + newUnitTarget: pedagogy.newUnitTarget ?? null, + reviewWeight: pedagogy.reviewWeight ?? null, + isIntensiveReview: Boolean(pedagogy.isIntensiveReview) }); console.log(` ✅ Lektion ${lessonData.num}: ${lessonData.title} (Woche ${lessonData.week}, Tag ${lessonData.day})`); } @@ -464,7 +483,7 @@ async function createBisayaCourse(languageId, ownerHashedId) { console.log(` - Konversations-Lektionen: ${LESSONS.filter(l => l.type === 'conversation').length}`); console.log(` - Grammatik-Lektionen: ${LESSONS.filter(l => l.type === 'grammar').length}`); console.log(` - Wiederholungs-Lektionen: ${LESSONS.filter(l => l.type === 'review').length}`); - console.log(` - Durchschnittliche Zeit pro Tag: ~${Math.round(LESSONS.reduce((sum, l) => sum + l.targetMin, 0) / (4 * 5))} Minuten`); + console.log(` - Durchschnittliche Zeit pro Tag: ~${Math.round(LESSONS.reduce((sum, l) => sum + l.targetMin, 0) / (15 * 5))} Minuten`); console.log(`\n💡 Nächste Schritte:`); console.log(` 1. Füge Vokabeln zu den Vokabel-Lektionen hinzu`); console.log(` 2. Erstelle Grammatik-Übungen für die Grammatik-Lektionen`); diff --git a/backend/scripts/extend-bisaya-course-phase3.js b/backend/scripts/extend-bisaya-course-phase3.js new file mode 100644 index 0000000..b7aace9 --- /dev/null +++ b/backend/scripts/extend-bisaya-course-phase3.js @@ -0,0 +1,102 @@ +#!/usr/bin/env node +/** + * Erweitert bestehende Bisaya-Kurse von 4 auf 6 Wochen. + * + * Verwendung: + * node backend/scripts/extend-bisaya-course-phase3.js + */ + +import { sequelize } from '../utils/sequelize.js'; +import VocabCourse from '../models/community/vocab_course.js'; +import VocabCourseLesson from '../models/community/vocab_course_lesson.js'; +import { getBisayaLessonPedagogy } from './bisaya-course-phase2-pedagogy.js'; +import { BISAYA_PHASE3_DIDACTICS, BISAYA_PHASE3_LESSONS } from './bisaya-course-phase3-extension.js'; + +async function extendBisayaCoursePhase3() { + 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 VocabCourse.findAll({ + where: { languageId: bisayaLanguage.id } + }); + + let addedLessons = 0; + + for (const course of courses) { + await course.update({ + title: 'Bisaya für Familien - Schnellstart in 6 Wochen', + description: 'Lerne Bisaya (Cebuano) schnell und praktisch für den Familienalltag. Fokus auf Sprechen, Hören, Spiralwiederholung und einem strukturierten 6-Wochen-Plan.' + }); + + for (const lessonData of BISAYA_PHASE3_LESSONS) { + const existing = await VocabCourseLesson.findOne({ + where: { + courseId: course.id, + lessonNumber: lessonData.num + } + }); + + if (existing) { + continue; + } + + const didactics = BISAYA_PHASE3_DIDACTICS[lessonData.title] || {}; + const pedagogy = getBisayaLessonPedagogy(lessonData.num) || {}; + + 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: pedagogy.didacticMode || null, + phaseLabel: pedagogy.phaseLabel || null, + blockNumber: pedagogy.blockNumber ?? null, + difficultyWeight: pedagogy.difficultyWeight ?? null, + newUnitTarget: pedagogy.newUnitTarget ?? null, + reviewWeight: pedagogy.reviewWeight ?? null, + isIntensiveReview: Boolean(pedagogy.isIntensiveReview) + }); + + addedLessons++; + console.log(`✅ Kurs ${course.id}: Lektion ${lessonData.num} - ${lessonData.title} ergänzt`); + } + } + + console.log(`\n🎉 Phase 3 vorbereitet.`); + console.log(` Kurse: ${courses.length}`); + console.log(` Neue Lektionen ergänzt: ${addedLessons}`); + console.log(' Danach create-bisaya-course-content.js ausführen, wenn für die neuen Lektionen zusätzliche Übungen eingespielt werden sollen.'); +} + +extendBisayaCoursePhase3() + .then(() => { + sequelize.close(); + process.exit(0); + }) + .catch((error) => { + console.error('❌ Fehler:', error); + sequelize.close(); + process.exit(1); + }); diff --git a/backend/scripts/extend-bisaya-course-phase4.js b/backend/scripts/extend-bisaya-course-phase4.js new file mode 100644 index 0000000..ffb7209 --- /dev/null +++ b/backend/scripts/extend-bisaya-course-phase4.js @@ -0,0 +1,102 @@ +#!/usr/bin/env node +/** + * Erweitert bestehende Bisaya-Kurse auf den 3-Monats-Alltagspfad. + * + * Verwendung: + * node backend/scripts/extend-bisaya-course-phase4.js + */ + +import { sequelize } from '../utils/sequelize.js'; +import VocabCourse from '../models/community/vocab_course.js'; +import VocabCourseLesson from '../models/community/vocab_course_lesson.js'; +import { getBisayaLessonPedagogy } from './bisaya-course-phase2-pedagogy.js'; +import { BISAYA_PHASE4_DIDACTICS, BISAYA_PHASE4_LESSONS } from './bisaya-course-phase4-extension.js'; + +async function extendBisayaCoursePhase4() { + 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 VocabCourse.findAll({ + where: { languageId: bisayaLanguage.id } + }); + + let addedLessons = 0; + + for (const course of courses) { + await course.update({ + title: 'Bisaya für Familien - Alltag in 3 Monaten', + description: 'Lerne Bisaya (Cebuano) praxisnah für den Familienalltag. Fokus auf Sprechen, Hören, Spiralwiederholung und einem strukturierten 3-Monats-Pfad vom Schnellstart bis zur stabilen Alltagskommunikation.' + }); + + for (const lessonData of BISAYA_PHASE4_LESSONS) { + const existing = await VocabCourseLesson.findOne({ + where: { + courseId: course.id, + lessonNumber: lessonData.num + } + }); + + if (existing) { + continue; + } + + const didactics = BISAYA_PHASE4_DIDACTICS[lessonData.title] || {}; + const pedagogy = getBisayaLessonPedagogy(lessonData.num) || {}; + + 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: pedagogy.didacticMode || null, + phaseLabel: pedagogy.phaseLabel || null, + blockNumber: pedagogy.blockNumber ?? null, + difficultyWeight: pedagogy.difficultyWeight ?? null, + newUnitTarget: pedagogy.newUnitTarget ?? null, + reviewWeight: pedagogy.reviewWeight ?? null, + isIntensiveReview: Boolean(pedagogy.isIntensiveReview) + }); + + addedLessons++; + console.log(`✅ Kurs ${course.id}: Lektion ${lessonData.num} - ${lessonData.title} ergänzt`); + } + } + + console.log(`\n🎉 Phase 4 vorbereitet.`); + console.log(` Kurse: ${courses.length}`); + console.log(` Neue Lektionen ergänzt: ${addedLessons}`); + console.log(' Das Einspielen in die DB kann später gesammelt mit den übrigen Phasen erfolgen.'); +} + +extendBisayaCoursePhase4() + .then(() => { + sequelize.close(); + process.exit(0); + }) + .catch((error) => { + console.error('❌ Fehler:', error); + sequelize.close(); + process.exit(1); + }); diff --git a/backend/scripts/extend-bisaya-course-phase5.js b/backend/scripts/extend-bisaya-course-phase5.js new file mode 100644 index 0000000..fd055db --- /dev/null +++ b/backend/scripts/extend-bisaya-course-phase5.js @@ -0,0 +1,102 @@ +#!/usr/bin/env node +/** + * Erweitert bestehende Bisaya-Kurse um die Stabilisierungsphase. + * + * Verwendung: + * node backend/scripts/extend-bisaya-course-phase5.js + */ + +import { sequelize } from '../utils/sequelize.js'; +import VocabCourse from '../models/community/vocab_course.js'; +import VocabCourseLesson from '../models/community/vocab_course_lesson.js'; +import { getBisayaLessonPedagogy } from './bisaya-course-phase2-pedagogy.js'; +import { BISAYA_PHASE5_DIDACTICS, BISAYA_PHASE5_LESSONS } from './bisaya-course-phase5-extension.js'; + +async function extendBisayaCoursePhase5() { + 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 VocabCourse.findAll({ + where: { languageId: bisayaLanguage.id } + }); + + let addedLessons = 0; + + for (const course of courses) { + await course.update({ + title: 'Bisaya für Familien - Alltag & Stabilisierung', + description: 'Lerne Bisaya (Cebuano) praxisnah für den Familienalltag. Der Pfad verbindet Schnellstart, Alltagsmodule und Stabilisierungsblöcke mit Spiralwiederholung, Fehlertraining und freier Produktion.' + }); + + for (const lessonData of BISAYA_PHASE5_LESSONS) { + const existing = await VocabCourseLesson.findOne({ + where: { + courseId: course.id, + lessonNumber: lessonData.num + } + }); + + if (existing) { + continue; + } + + const didactics = BISAYA_PHASE5_DIDACTICS[lessonData.title] || {}; + const pedagogy = getBisayaLessonPedagogy(lessonData.num) || {}; + + 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: pedagogy.didacticMode || null, + phaseLabel: pedagogy.phaseLabel || null, + blockNumber: pedagogy.blockNumber ?? null, + difficultyWeight: pedagogy.difficultyWeight ?? null, + newUnitTarget: pedagogy.newUnitTarget ?? null, + reviewWeight: pedagogy.reviewWeight ?? null, + isIntensiveReview: Boolean(pedagogy.isIntensiveReview) + }); + + addedLessons++; + console.log(`✅ Kurs ${course.id}: Lektion ${lessonData.num} - ${lessonData.title} ergänzt`); + } + } + + console.log(`\n🎉 Phase 5 vorbereitet.`); + console.log(` Kurse: ${courses.length}`); + console.log(` Neue Lektionen ergänzt: ${addedLessons}`); + console.log(' Das Einspielen in die DB kann später gesammelt mit den übrigen Phasen erfolgen.'); +} + +extendBisayaCoursePhase5() + .then(() => { + sequelize.close(); + process.exit(0); + }) + .catch((error) => { + console.error('❌ Fehler:', error); + sequelize.close(); + process.exit(1); + }); diff --git a/backend/scripts/update-bisaya-didactics.js b/backend/scripts/update-bisaya-didactics.js index 882f3eb..2666812 100644 --- a/backend/scripts/update-bisaya-didactics.js +++ b/backend/scripts/update-bisaya-didactics.js @@ -8,6 +8,7 @@ import { sequelize } from '../utils/sequelize.js'; import VocabCourseLesson from '../models/community/vocab_course_lesson.js'; +import { getBisayaLessonPedagogy } from './bisaya-course-phase2-pedagogy.js'; const LESSON_DIDACTICS = { 'Begrüßungen & Höflichkeit': { @@ -129,14 +130,23 @@ async function updateBisayaDidactics() { for (const row of lessons) { const lesson = await VocabCourseLesson.findByPk(row.id); const didactics = LESSON_DIDACTICS[lesson.title]; - if (!didactics) continue; + const pedagogy = getBisayaLessonPedagogy(lesson.lessonNumber); + if (!didactics && !pedagogy) continue; + const normalizedDidactics = didactics || {}; await lesson.update({ - learningGoals: didactics.learningGoals || [], - corePatterns: didactics.corePatterns || [], - grammarFocus: didactics.grammarFocus || [], - speakingPrompts: didactics.speakingPrompts || [], - practicalTasks: didactics.practicalTasks || [] + learningGoals: normalizedDidactics.learningGoals || [], + corePatterns: normalizedDidactics.corePatterns || [], + grammarFocus: normalizedDidactics.grammarFocus || [], + speakingPrompts: normalizedDidactics.speakingPrompts || [], + practicalTasks: normalizedDidactics.practicalTasks || [], + didacticMode: pedagogy?.didacticMode || null, + phaseLabel: pedagogy?.phaseLabel || null, + blockNumber: pedagogy?.blockNumber ?? null, + difficultyWeight: pedagogy?.difficultyWeight ?? null, + newUnitTarget: pedagogy?.newUnitTarget ?? null, + reviewWeight: pedagogy?.reviewWeight ?? null, + isIntensiveReview: Boolean(pedagogy?.isIntensiveReview) }); updated++; console.log(`✅ Didaktik aktualisiert: Lektion ${lesson.lessonNumber} - ${lesson.title}`); diff --git a/backend/services/adminService.js b/backend/services/adminService.js index 2071b93..d43d239 100644 --- a/backend/services/adminService.js +++ b/backend/services/adminService.js @@ -29,6 +29,9 @@ import EroticVideo from '../models/community/erotic_video.js'; import EroticContentReport from '../models/community/erotic_content_report.js'; import TitleOfNobility from "../models/falukant/type/title_of_nobility.js"; import ChildRelation from "../models/falukant/data/child_relation.js"; +import Relationship from "../models/falukant/data/relationship.js"; +import RelationshipType from "../models/falukant/type/relationship.js"; +import RelationshipState from "../models/falukant/data/relationship_state.js"; import { sequelize } from '../utils/sequelize.js'; import npcCreationJobService from './npcCreationJobService.js'; import { v4 as uuidv4 } from 'uuid'; @@ -668,7 +671,7 @@ class AdminService { required: true, attributes: ['money', 'certificate', 'id'], include: [{ - model: FalukantCharacter, + model: FalukantCharacter.unscoped(), as: 'character', include: [{ model: FalukantPredefineFirstname, @@ -945,10 +948,85 @@ class AdminService { await character.save(); } + /** + * Ehepartner/Verlobte/Liebhaber eines Charakters (für Admin-Auswahl Vater). + */ + async adminGetPotentialFathersForCharacter(userId, motherCharacterId) { + if (!(await this.hasUserAccess(userId, 'falukantusers'))) { + throw new Error('noaccess'); + } + const mid = Number(motherCharacterId); + if (!Number.isFinite(mid)) { + throw new Error('invalidCharacter'); + } + const mother = await FalukantCharacter.findByPk(mid, { attributes: ['id'] }); + if (!mother) { + throw new Error('notfound'); + } + + const rels = await Relationship.findAll({ + where: { + [Op.or]: [ + { character1Id: mid }, + { character2Id: mid } + ] + }, + include: [ + { model: RelationshipType, as: 'relationshipType', attributes: ['tr'] }, + { model: RelationshipState, as: 'state', required: false }, + { + model: FalukantCharacter, + as: 'character1', + attributes: ['id'], + required: true, + include: [{ model: FalukantPredefineFirstname, as: 'definedFirstName', attributes: ['name'] }] + }, + { + model: FalukantCharacter, + as: 'character2', + attributes: ['id'], + required: true, + include: [{ model: FalukantPredefineFirstname, as: 'definedFirstName', attributes: ['name'] }] + } + ] + }); + + const allowedTypes = new Set(['married', 'engaged', 'lover']); + const typeOrder = { married: 0, engaged: 1, lover: 2 }; + const seen = new Set(); + const options = []; + + for (const rel of rels) { + const typeTr = rel.relationshipType?.tr; + if (!typeTr || !allowedTypes.has(typeTr)) { + continue; + } + if (typeTr === 'lover' && rel.state && rel.state.active === false) { + continue; + } + const partnerId = rel.character1Id === mid ? rel.character2Id : rel.character1Id; + if (partnerId === mid || seen.has(partnerId)) { + continue; + } + seen.add(partnerId); + const partner = rel.character1Id === mid ? rel.character2 : rel.character1; + const displayName = partner?.definedFirstName?.name?.trim() || `Charakter #${partnerId}`; + options.push({ + characterId: partnerId, + displayName, + relationshipType: typeTr + }); + } + + options.sort((a, b) => (typeOrder[a.relationshipType] ?? 9) - (typeOrder[b.relationshipType] ?? 9)); + + return { options }; + } + /** * Admin: Charakter als schwanger markieren (erwarteter Geburtstermin). * @param {number} fatherCharacterId - optional; Vater-Charakter-ID - * @param {number} dueInDays - Tage bis zur „Geburt“ (Default 21) + * @param {number} dueInDays - Tage bis zum Termin (0 = heute; Default 21) */ async adminForceFalukantPregnancy(userId, characterId, { fatherCharacterId = null, dueInDays = 21 } = {}) { if (!(await this.hasUserAccess(userId, 'falukantusers'))) { @@ -960,7 +1038,16 @@ class AdminService { const father = await FalukantCharacter.findByPk(Number(fatherCharacterId)); if (!father) throw new Error('fatherNotFound'); } - const days = Math.max(1, Math.min(365, Number(dueInDays) || 21)); + let rawDays = dueInDays; + if (rawDays === undefined || rawDays === null || rawDays === '') { + rawDays = 21; + } else { + rawDays = Number(rawDays); + if (!Number.isFinite(rawDays)) { + rawDays = 21; + } + } + const days = Math.max(0, Math.min(365, rawDays)); const due = new Date(); due.setDate(due.getDate() + days); await mother.update({ diff --git a/backend/services/vocabService.js b/backend/services/vocabService.js index 1d59415..89d3b77 100644 --- a/backend/services/vocabService.js +++ b/backend/services/vocabService.js @@ -188,6 +188,149 @@ export default class VocabService { return []; } + _normalizeOptionalInteger(value) { + if (value === undefined || value === null || value === '') { + return null; + } + const parsed = Number(value); + return Number.isFinite(parsed) ? parsed : null; + } + + _normalizeOptionalString(value) { + if (value === undefined || value === null) { + return null; + } + const trimmed = String(value).trim(); + return trimmed || null; + } + + _inferLessonPhaseLabel(plainLesson) { + if (plainLesson.phaseLabel) { + return plainLesson.phaseLabel; + } + const weekNumber = Number(plainLesson.weekNumber) || 0; + if (weekNumber > 0 && weekNumber <= 2) { + return 'quickstart'; + } + if (weekNumber === 3) { + return 'daily_life'; + } + if (weekNumber >= 4) { + return 'stabilization'; + } + return 'quickstart'; + } + + _inferLessonDidacticMode(plainLesson) { + if (plainLesson.didacticMode) { + return plainLesson.didacticMode; + } + const lessonType = String(plainLesson.lessonType || '').toLowerCase(); + const title = String(plainLesson.title || '').toLowerCase(); + if (title.includes('abschluss') || title.includes('prüfung') || title.includes('test')) { + return 'checkpoint'; + } + if (plainLesson.isIntensiveReview || lessonType === 'review' || lessonType === 'vocab_review' || title.includes('wiederholung')) { + return 'intensive_review'; + } + if (lessonType === 'grammar') { + return 'pattern_drill'; + } + if (lessonType === 'conversation' || lessonType === 'dialogue' || lessonType === 'phrases' || lessonType === 'survival') { + return 'guided_dialogue'; + } + if (lessonType === 'culture') { + return 'real_life_scenario'; + } + return 'core_input'; + } + + _inferLessonDifficultyWeight(plainLesson, didacticMode) { + if (plainLesson.difficultyWeight != null) { + return plainLesson.difficultyWeight; + } + switch (didacticMode) { + case 'pattern_drill': + return 3; + case 'guided_dialogue': + case 'real_life_scenario': + return 2; + case 'intensive_review': + case 'checkpoint': + return 2; + default: + return 1; + } + } + + _inferLessonNewUnitTarget(plainLesson, didacticMode) { + if (plainLesson.newUnitTarget != null) { + return plainLesson.newUnitTarget; + } + switch (didacticMode) { + case 'core_input': + return 8; + case 'guided_dialogue': + return 5; + case 'pattern_drill': + return 4; + case 'real_life_scenario': + return 3; + case 'checkpoint': + return 2; + case 'intensive_review': + return 1; + default: + return 4; + } + } + + _inferLessonReviewWeight(plainLesson, didacticMode) { + if (plainLesson.reviewWeight != null) { + return plainLesson.reviewWeight; + } + switch (didacticMode) { + case 'intensive_review': + return 90; + case 'checkpoint': + return 70; + case 'pattern_drill': + return 55; + case 'real_life_scenario': + return 45; + case 'guided_dialogue': + return 40; + default: + return 30; + } + } + + _inferLessonBlockNumber(plainLesson) { + if (plainLesson.blockNumber != null) { + return plainLesson.blockNumber; + } + const weekNumber = Number(plainLesson.weekNumber) || 1; + return Math.max(1, Math.ceil(weekNumber / 2)); + } + + _buildLessonPedagogy(plainLesson) { + const didacticMode = this._inferLessonDidacticMode(plainLesson); + const phaseLabel = this._inferLessonPhaseLabel(plainLesson); + const isIntensiveReview = plainLesson.isIntensiveReview != null + ? Boolean(plainLesson.isIntensiveReview) + : didacticMode === 'intensive_review'; + + return { + didacticMode, + phaseLabel, + blockNumber: this._inferLessonBlockNumber(plainLesson), + difficultyWeight: this._inferLessonDifficultyWeight(plainLesson, didacticMode), + newUnitTarget: this._inferLessonNewUnitTarget(plainLesson, didacticMode), + reviewWeight: this._inferLessonReviewWeight(plainLesson, didacticMode), + isIntensiveReview + }; + } + _buildLessonDidactics(plainLesson) { const grammarExercises = Array.isArray(plainLesson.grammarExercises) ? plainLesson.grammarExercises : []; const grammarExplanations = []; @@ -1020,6 +1163,11 @@ export default class VocabService { return a.lessonNumber - b.lessonNumber; }); + courseData.lessons = courseData.lessons.map((lesson) => ({ + ...lesson, + pedagogy: this._buildLessonPedagogy(lesson) + })); + return courseData; } @@ -1129,6 +1277,7 @@ export default class VocabService { } plainLesson.didactics = this._buildLessonDidactics(plainLesson); + plainLesson.pedagogy = this._buildLessonPedagogy(plainLesson); return plainLesson; } @@ -1301,7 +1450,7 @@ export default class VocabService { return exercises.map(e => e.get({ plain: true })); } - async addLessonToCourse(hashedUserId, courseId, { chapterId, lessonNumber, title, description, weekNumber, dayNumber, lessonType, audioUrl, culturalNotes, learningGoals, corePatterns, grammarFocus, speakingPrompts, practicalTasks, targetMinutes, targetScorePercent, requiresReview }) { + async addLessonToCourse(hashedUserId, courseId, { chapterId, lessonNumber, title, description, weekNumber, dayNumber, lessonType, didacticMode, phaseLabel, blockNumber, difficultyWeight, newUnitTarget, reviewWeight, isIntensiveReview, audioUrl, culturalNotes, learningGoals, corePatterns, grammarFocus, speakingPrompts, practicalTasks, targetMinutes, targetScorePercent, requiresReview }) { const user = await this._getUserByHashedId(hashedUserId); const course = await VocabCourse.findByPk(courseId); @@ -1343,6 +1492,13 @@ export default class VocabService { weekNumber: weekNumber ? Number(weekNumber) : null, dayNumber: dayNumber ? Number(dayNumber) : null, lessonType: lessonType || 'vocab', + didacticMode: this._normalizeOptionalString(didacticMode), + phaseLabel: this._normalizeOptionalString(phaseLabel), + blockNumber: this._normalizeOptionalInteger(blockNumber), + difficultyWeight: this._normalizeOptionalInteger(difficultyWeight), + newUnitTarget: this._normalizeOptionalInteger(newUnitTarget), + reviewWeight: this._normalizeOptionalInteger(reviewWeight), + isIntensiveReview: isIntensiveReview !== undefined ? Boolean(isIntensiveReview) : false, audioUrl: audioUrl || null, culturalNotes: culturalNotes || null, learningGoals: this._normalizeStringList(learningGoals), @@ -1358,7 +1514,7 @@ export default class VocabService { return lesson.get({ plain: true }); } - async updateLesson(hashedUserId, lessonId, { title, description, lessonNumber, weekNumber, dayNumber, lessonType, audioUrl, culturalNotes, learningGoals, corePatterns, grammarFocus, speakingPrompts, practicalTasks, targetMinutes, targetScorePercent, requiresReview }) { + async updateLesson(hashedUserId, lessonId, { title, description, lessonNumber, weekNumber, dayNumber, lessonType, didacticMode, phaseLabel, blockNumber, difficultyWeight, newUnitTarget, reviewWeight, isIntensiveReview, audioUrl, culturalNotes, learningGoals, corePatterns, grammarFocus, speakingPrompts, practicalTasks, targetMinutes, targetScorePercent, requiresReview }) { const user = await this._getUserByHashedId(hashedUserId); const lesson = await VocabCourseLesson.findByPk(lessonId, { include: [{ model: VocabCourse, as: 'course' }] @@ -1383,6 +1539,13 @@ export default class VocabService { if (weekNumber !== undefined) updates.weekNumber = weekNumber ? Number(weekNumber) : null; if (dayNumber !== undefined) updates.dayNumber = dayNumber ? Number(dayNumber) : null; if (lessonType !== undefined) updates.lessonType = lessonType; + if (didacticMode !== undefined) updates.didacticMode = this._normalizeOptionalString(didacticMode); + if (phaseLabel !== undefined) updates.phaseLabel = this._normalizeOptionalString(phaseLabel); + if (blockNumber !== undefined) updates.blockNumber = this._normalizeOptionalInteger(blockNumber); + if (difficultyWeight !== undefined) updates.difficultyWeight = this._normalizeOptionalInteger(difficultyWeight); + if (newUnitTarget !== undefined) updates.newUnitTarget = this._normalizeOptionalInteger(newUnitTarget); + if (reviewWeight !== undefined) updates.reviewWeight = this._normalizeOptionalInteger(reviewWeight); + if (isIntensiveReview !== undefined) updates.isIntensiveReview = Boolean(isIntensiveReview); if (audioUrl !== undefined) updates.audioUrl = audioUrl; if (culturalNotes !== undefined) updates.culturalNotes = culturalNotes; if (learningGoals !== undefined) updates.learningGoals = this._normalizeStringList(learningGoals); diff --git a/backend/sql/add_vocab_lesson_phase1_fields.sql b/backend/sql/add_vocab_lesson_phase1_fields.sql new file mode 100644 index 0000000..3122df4 --- /dev/null +++ b/backend/sql/add_vocab_lesson_phase1_fields.sql @@ -0,0 +1,23 @@ +ALTER TABLE community.vocab_course_lesson +ADD COLUMN IF NOT EXISTS didactic_mode TEXT, +ADD COLUMN IF NOT EXISTS phase_label TEXT, +ADD COLUMN IF NOT EXISTS block_number INTEGER, +ADD COLUMN IF NOT EXISTS difficulty_weight INTEGER, +ADD COLUMN IF NOT EXISTS new_unit_target INTEGER, +ADD COLUMN IF NOT EXISTS review_weight INTEGER, +ADD COLUMN IF NOT EXISTS is_intensive_review BOOLEAN NOT NULL DEFAULT FALSE; + +COMMENT ON COLUMN community.vocab_course_lesson.didactic_mode IS + 'Didaktischer Modus der Lektion, z.B. core_input, guided_dialogue, intensive_review oder checkpoint.'; +COMMENT ON COLUMN community.vocab_course_lesson.phase_label IS + 'Übergeordnete Lernphase, z.B. quickstart, daily_life oder stabilization.'; +COMMENT ON COLUMN community.vocab_course_lesson.block_number IS + 'Inhaltlicher Block für Konsolidierungs- und Wiederholungswellen.'; +COMMENT ON COLUMN community.vocab_course_lesson.difficulty_weight IS + 'Grobe relative Schwierigkeit der Lektion von leicht bis schwer.'; +COMMENT ON COLUMN community.vocab_course_lesson.new_unit_target IS + 'Empfohlene Zahl neuer Spracheinheiten in dieser Lektion.'; +COMMENT ON COLUMN community.vocab_course_lesson.review_weight IS + 'Wie stark Wiederholung in dieser Lektion dominieren soll, typischerweise 0 bis 100.'; +COMMENT ON COLUMN community.vocab_course_lesson.is_intensive_review IS + 'Markiert Lektionen, die als intensive Wiederholungsphase gedacht sind.'; diff --git a/backend/sql/backfill_bisaya_phase2_pedagogy.sql b/backend/sql/backfill_bisaya_phase2_pedagogy.sql new file mode 100644 index 0000000..53e595b --- /dev/null +++ b/backend/sql/backfill_bisaya_phase2_pedagogy.sql @@ -0,0 +1,147 @@ +UPDATE community.vocab_course_lesson AS lesson +SET + phase_label = CASE lesson.lesson_number + WHEN 1 THEN 'quickstart' + WHEN 2 THEN 'quickstart' + WHEN 3 THEN 'quickstart' + WHEN 4 THEN 'quickstart' + WHEN 5 THEN 'quickstart' + WHEN 6 THEN 'quickstart' + WHEN 7 THEN 'quickstart' + WHEN 8 THEN 'quickstart' + WHEN 9 THEN 'quickstart' + WHEN 10 THEN 'quickstart' + WHEN 11 THEN 'quickstart' + WHEN 12 THEN 'quickstart' + WHEN 13 THEN 'quickstart' + WHEN 14 THEN 'quickstart' + WHEN 15 THEN 'quickstart' + WHEN 16 THEN 'quickstart' + WHEN 17 THEN 'quickstart' + WHEN 18 THEN 'quickstart' + WHEN 19 THEN 'quickstart' + WHEN 20 THEN 'quickstart' + WHEN 21 THEN 'daily_life' + WHEN 22 THEN 'daily_life' + WHEN 23 THEN 'daily_life' + WHEN 24 THEN 'daily_life' + WHEN 25 THEN 'daily_life' + WHEN 26 THEN 'daily_life' + WHEN 27 THEN 'daily_life' + WHEN 28 THEN 'daily_life' + WHEN 29 THEN 'daily_life' + WHEN 30 THEN 'daily_life' + WHEN 31 THEN 'stabilization' + WHEN 32 THEN 'stabilization' + WHEN 33 THEN 'stabilization' + WHEN 34 THEN 'stabilization' + WHEN 35 THEN 'stabilization' + WHEN 36 THEN 'stabilization' + WHEN 37 THEN 'stabilization' + WHEN 38 THEN 'stabilization' + WHEN 39 THEN 'stabilization' + WHEN 40 THEN 'stabilization' + ELSE lesson.phase_label + END, + block_number = CASE lesson.lesson_number + WHEN 1 THEN 1 WHEN 2 THEN 1 WHEN 3 THEN 1 WHEN 4 THEN 1 WHEN 5 THEN 1 + WHEN 6 THEN 1 WHEN 7 THEN 1 WHEN 8 THEN 1 WHEN 9 THEN 1 WHEN 10 THEN 1 + WHEN 11 THEN 2 WHEN 12 THEN 2 WHEN 13 THEN 2 WHEN 14 THEN 2 WHEN 15 THEN 2 + WHEN 16 THEN 2 WHEN 17 THEN 2 WHEN 18 THEN 2 WHEN 19 THEN 2 WHEN 20 THEN 2 + WHEN 21 THEN 3 WHEN 22 THEN 3 WHEN 23 THEN 3 WHEN 24 THEN 3 WHEN 25 THEN 3 + WHEN 26 THEN 3 WHEN 27 THEN 3 WHEN 28 THEN 3 WHEN 29 THEN 3 WHEN 30 THEN 3 + WHEN 31 THEN 4 WHEN 32 THEN 4 WHEN 33 THEN 4 WHEN 34 THEN 4 WHEN 35 THEN 4 + WHEN 36 THEN 4 WHEN 37 THEN 4 WHEN 38 THEN 4 WHEN 39 THEN 4 WHEN 40 THEN 4 + ELSE lesson.block_number + END, + didactic_mode = CASE lesson.lesson_number + WHEN 1 THEN 'guided_dialogue' + WHEN 2 THEN 'core_input' + WHEN 3 THEN 'core_input' + WHEN 4 THEN 'guided_dialogue' + WHEN 5 THEN 'guided_dialogue' + WHEN 6 THEN 'core_input' + WHEN 7 THEN 'guided_dialogue' + WHEN 8 THEN 'core_input' + WHEN 9 THEN 'intensive_review' + WHEN 10 THEN 'checkpoint' + WHEN 11 THEN 'guided_dialogue' + WHEN 12 THEN 'core_input' + WHEN 13 THEN 'guided_dialogue' + WHEN 14 THEN 'core_input' + WHEN 15 THEN 'pattern_drill' + WHEN 16 THEN 'core_input' + WHEN 17 THEN 'guided_dialogue' + WHEN 18 THEN 'core_input' + WHEN 19 THEN 'intensive_review' + WHEN 20 THEN 'checkpoint' + WHEN 21 THEN 'guided_dialogue' + WHEN 22 THEN 'core_input' + WHEN 23 THEN 'guided_dialogue' + WHEN 24 THEN 'core_input' + WHEN 25 THEN 'pattern_drill' + WHEN 26 THEN 'guided_dialogue' + WHEN 27 THEN 'guided_dialogue' + WHEN 28 THEN 'core_input' + WHEN 29 THEN 'intensive_review' + WHEN 30 THEN 'checkpoint' + WHEN 31 THEN 'real_life_scenario' + WHEN 32 THEN 'intensive_review' + WHEN 33 THEN 'real_life_scenario' + WHEN 34 THEN 'intensive_review' + WHEN 35 THEN 'real_life_scenario' + WHEN 36 THEN 'intensive_review' + WHEN 37 THEN 'real_life_scenario' + WHEN 38 THEN 'checkpoint' + WHEN 39 THEN 'checkpoint' + WHEN 40 THEN 'real_life_scenario' + ELSE lesson.didactic_mode + END, + difficulty_weight = CASE lesson.lesson_number + WHEN 1 THEN 2 WHEN 2 THEN 3 WHEN 3 THEN 2 WHEN 4 THEN 3 WHEN 5 THEN 3 + WHEN 6 THEN 3 WHEN 7 THEN 3 WHEN 8 THEN 2 WHEN 9 THEN 3 WHEN 10 THEN 3 + WHEN 11 THEN 3 WHEN 12 THEN 2 WHEN 13 THEN 3 WHEN 14 THEN 3 WHEN 15 THEN 4 + WHEN 16 THEN 3 WHEN 17 THEN 4 WHEN 18 THEN 4 WHEN 19 THEN 4 WHEN 20 THEN 4 + WHEN 21 THEN 3 WHEN 22 THEN 3 WHEN 23 THEN 4 WHEN 24 THEN 4 WHEN 25 THEN 4 + WHEN 26 THEN 4 WHEN 27 THEN 3 WHEN 28 THEN 3 WHEN 29 THEN 4 WHEN 30 THEN 4 + WHEN 31 THEN 4 WHEN 32 THEN 4 WHEN 33 THEN 4 WHEN 34 THEN 4 WHEN 35 THEN 5 + WHEN 36 THEN 5 WHEN 37 THEN 5 WHEN 38 THEN 5 WHEN 39 THEN 5 WHEN 40 THEN 2 + ELSE lesson.difficulty_weight + END, + new_unit_target = CASE lesson.lesson_number + WHEN 1 THEN 5 WHEN 2 THEN 8 WHEN 3 THEN 7 WHEN 4 THEN 5 WHEN 5 THEN 4 + WHEN 6 THEN 7 WHEN 7 THEN 5 WHEN 8 THEN 6 WHEN 9 THEN 0 WHEN 10 THEN 0 + WHEN 11 THEN 5 WHEN 12 THEN 7 WHEN 13 THEN 5 WHEN 14 THEN 6 WHEN 15 THEN 4 + WHEN 16 THEN 6 WHEN 17 THEN 5 WHEN 18 THEN 7 WHEN 19 THEN 0 WHEN 20 THEN 0 + WHEN 21 THEN 5 WHEN 22 THEN 7 WHEN 23 THEN 5 WHEN 24 THEN 6 WHEN 25 THEN 4 + WHEN 26 THEN 5 WHEN 27 THEN 5 WHEN 28 THEN 6 WHEN 29 THEN 0 WHEN 30 THEN 0 + WHEN 31 THEN 4 WHEN 32 THEN 0 WHEN 33 THEN 4 WHEN 34 THEN 0 WHEN 35 THEN 3 + WHEN 36 THEN 0 WHEN 37 THEN 3 WHEN 38 THEN 0 WHEN 39 THEN 0 WHEN 40 THEN 2 + ELSE lesson.new_unit_target + END, + review_weight = CASE lesson.lesson_number + WHEN 1 THEN 15 WHEN 2 THEN 20 WHEN 3 THEN 20 WHEN 4 THEN 25 WHEN 5 THEN 25 + WHEN 6 THEN 25 WHEN 7 THEN 30 WHEN 8 THEN 30 WHEN 9 THEN 90 WHEN 10 THEN 95 + WHEN 11 THEN 25 WHEN 12 THEN 25 WHEN 13 THEN 30 WHEN 14 THEN 30 WHEN 15 THEN 40 + WHEN 16 THEN 35 WHEN 17 THEN 35 WHEN 18 THEN 35 WHEN 19 THEN 92 WHEN 20 THEN 96 + WHEN 21 THEN 35 WHEN 22 THEN 35 WHEN 23 THEN 40 WHEN 24 THEN 40 WHEN 25 THEN 45 + WHEN 26 THEN 45 WHEN 27 THEN 40 WHEN 28 THEN 40 WHEN 29 THEN 94 WHEN 30 THEN 97 + WHEN 31 THEN 55 WHEN 32 THEN 95 WHEN 33 THEN 60 WHEN 34 THEN 95 WHEN 35 THEN 65 + WHEN 36 THEN 98 WHEN 37 THEN 70 WHEN 38 THEN 99 WHEN 39 THEN 100 WHEN 40 THEN 50 + ELSE lesson.review_weight + END, + is_intensive_review = CASE lesson.lesson_number + WHEN 9 THEN TRUE + WHEN 19 THEN TRUE + WHEN 29 THEN TRUE + WHEN 32 THEN TRUE + WHEN 34 THEN TRUE + WHEN 36 THEN TRUE + ELSE FALSE + END +FROM community.vocab_course AS course +JOIN community.vocab_language AS language + ON language.id = course.language_id +WHERE lesson.course_id = course.id + AND language.name = 'Bisaya' + AND lesson.lesson_number BETWEEN 1 AND 40; diff --git a/backend/sql/extend_bisaya_course_phase3_to_6_weeks.sql b/backend/sql/extend_bisaya_course_phase3_to_6_weeks.sql new file mode 100644 index 0000000..35c3f42 --- /dev/null +++ b/backend/sql/extend_bisaya_course_phase3_to_6_weeks.sql @@ -0,0 +1,12 @@ +UPDATE community.vocab_course AS course +SET + title = 'Bisaya für Familien - Schnellstart in 6 Wochen', + description = 'Lerne Bisaya (Cebuano) schnell und praktisch für den Familienalltag. Fokus auf Sprechen, Hören, Spiralwiederholung und einem strukturierten 6-Wochen-Plan.' +FROM community.vocab_language AS language +WHERE course.language_id = language.id + AND language.name = 'Bisaya'; + +-- Für die eigentlichen neuen Lektionen 41-60 wird das Skript +-- backend/scripts/extend-bisaya-course-phase3.js empfohlen, weil dort +-- auch JSON-Felder wie learning_goals, core_patterns, grammar_focus, +-- speaking_prompts und practical_tasks konsistent gepflegt werden. diff --git a/backend/sql/extend_bisaya_course_phase4_to_3_months.sql b/backend/sql/extend_bisaya_course_phase4_to_3_months.sql new file mode 100644 index 0000000..ab72ccd --- /dev/null +++ b/backend/sql/extend_bisaya_course_phase4_to_3_months.sql @@ -0,0 +1,11 @@ +UPDATE community.vocab_course AS course +SET + title = 'Bisaya für Familien - Alltag in 3 Monaten', + description = 'Lerne Bisaya (Cebuano) praxisnah für den Familienalltag. Fokus auf Sprechen, Hören, Spiralwiederholung und einem strukturierten 3-Monats-Pfad vom Schnellstart bis zur stabilen Alltagskommunikation.' +FROM community.vocab_language AS language +WHERE course.language_id = language.id + AND language.name = 'Bisaya'; + +-- Für die neuen Lektionen 61-120 wird das Skript +-- backend/scripts/extend-bisaya-course-phase4.js empfohlen, weil dort +-- auch die JSON-Felder der Lektionen konsistent gepflegt werden. diff --git a/backend/sql/extend_bisaya_course_phase5_to_stabilization.sql b/backend/sql/extend_bisaya_course_phase5_to_stabilization.sql new file mode 100644 index 0000000..c1706c0 --- /dev/null +++ b/backend/sql/extend_bisaya_course_phase5_to_stabilization.sql @@ -0,0 +1,11 @@ +UPDATE community.vocab_course AS course +SET + title = 'Bisaya für Familien - Alltag & Stabilisierung', + description = 'Lerne Bisaya (Cebuano) praxisnah für den Familienalltag. Der Pfad verbindet Schnellstart, Alltagsmodule und Stabilisierungsblöcke mit Spiralwiederholung, Fehlertraining und freier Produktion.' +FROM community.vocab_language AS language +WHERE course.language_id = language.id + AND language.name = 'Bisaya'; + +-- Für die neuen Lektionen 121-150 wird das Skript +-- backend/scripts/extend-bisaya-course-phase5.js empfohlen, weil dort +-- auch die JSON-Felder der Lektionen konsistent gepflegt werden. diff --git a/docs/BISAYA_COURSE_EXPANSION_IMPLEMENTATION_SPEC.md b/docs/BISAYA_COURSE_EXPANSION_IMPLEMENTATION_SPEC.md new file mode 100644 index 0000000..a807fcb --- /dev/null +++ b/docs/BISAYA_COURSE_EXPANSION_IMPLEMENTATION_SPEC.md @@ -0,0 +1,1038 @@ +# Bisaya-Kursausbau: Umsetzungsdokument + +## 1. Ziel + +Der bestehende Bisaya-Kurs soll von einem kompakten Schnellstart zu einem belastbaren Sprachlernangebot ausgebaut werden. + +Das Ziel ist nicht nur: + +- Wörter schnell einprägen +- einzelne Sätze wiedererkennen + +Sondern: + +- häufige Muster aktiv abrufen +- Alltagssituationen sicher bewältigen +- alte Inhalte vertiefen statt nur wiederholen +- neue Inhalte dosiert und adaptiv einführen + +Der Kurs soll damit zwischen zwei schlechten Extremen vermeiden: + +- zu viel neuer Stoff ohne Konsolidierung +- zu viel stumpfe Wiederholung ohne Fortschritt + +## 2. Aktueller Stand + +Der aktuelle Bisaya-Kurs ist im System klar als kompakter Einstieg angelegt. + +Quelle: +- [create-bisaya-course.js](/mnt/share/torsten/Programs/YourPart3/backend/scripts/create-bisaya-course.js) + +Der Kurs umfasst nach Phase 5: + +- `15 Wochen` +- `150 Lektionen` +- Fokus auf: + - Begrüßung + - Familie + - Fürsorge + - Alltag + - Preise + - einfache Zeitformen + - erste freie Gespräche + +Die aktuelle Bezeichnung ist: + +- `Bisaya für Familien - Alltag & Stabilisierung` + +Das ist als Startkurs sinnvoll, aber nicht ausreichend für stabile Sprachbeherrschung. + +## 3. Produktentscheidung + +Der bisherige Kurs bleibt fachlich erhalten, wird aber neu eingeordnet: + +- nicht mehr als „der Bisaya-Kurs“ +- sondern als `Phase 1: Schnellstart / Grundkommunikation` + +Darauf bauen weitere Phasen auf. + +## 4. Zielbild des neuen Kursmodells + +Der neue Bisaya-Lernpfad besteht aus drei Ebenen. + +### 4.1 Phase A: Schnellstart + +Ziel: + +- erste Gespräche führen +- Fürsorge, Familie und Höflichkeit verstehen +- typische Alltagssätze direkt benutzen + +Dauer: + +- `6 Wochen` + +Lernfokus: + +- hochfrequente Phrasen +- feste Muster +- erste freie Antworten + +### 4.2 Phase B: Alltag + +Ziel: + +- Alltagskommunikation verbreitern +- mehr Variabilität im Ausdruck +- längere Gespräche verstehen und mittragen + +Dauer: + +- `8 bis 12 Wochen` + +Lernfokus: + +- Wohnen +- Kinder +- Gesundheit +- Wege +- Zeit +- Organisation +- soziale Situationen + +### 4.3 Phase C: Stabilisierung + +Ziel: + +- Inhalte langfristig verfügbar machen +- alte Muster flexibel in neuen Situationen verwenden +- freie Produktion deutlich stärken + +Dauer: + +- `3 bis 6 Monate` + +Lernfokus: + +- Mischtraining +- Wiederaufnahme alter Inhalte +- aktives Formulieren +- Fehlerreduktion bei ähnlichen Mustern + +## 4.4 Konkrete Phasenlabels im System + +Für die technische Umsetzung werden drei feste Phasenlabels verwendet: + +- `quickstart` +- `daily_life` +- `stabilization` + +Diese Phasen sind nicht nur Beschreibung, sondern sollen in Lektionen gespeichert und in der UI sichtbar gemacht werden. + +### `quickstart` + +Zeitfenster: + +- ungefähr Woche `1 bis 6` + +Ziel: + +- schnell sprechfähige Kernmuster aufbauen + +Didaktischer Schwerpunkt: + +- häufige Phrasen +- gebrauchsfertige Satzmuster +- einfache Dialoge +- wenig Grammatikballast + +Typische Modi: + +- `core_input` +- `guided_dialogue` +- erste `pattern_drill` + +### `daily_life` + +Zeitfenster: + +- ungefähr Woche `7 bis 18` + +Ziel: + +- Alltagssituationen verbreitern und kombinieren + +Didaktischer Schwerpunkt: + +- Familie +- Besuch +- Kinder +- Gesundheit +- Termine +- Wege +- soziale Reaktionen + +Typische Modi: + +- `guided_dialogue` +- `pattern_drill` +- `real_life_scenario` + +### `stabilization` + +Zeitfenster: + +- ab ungefähr Woche `19` + +Ziel: + +- Langzeitverfügbarkeit und Transfer + +Didaktischer Schwerpunkt: + +- Mischtraining +- Abruf +- Kontrastierung ähnlicher Muster +- freie Produktion +- blockübergreifende Wiederholung + +Typische Modi: + +- `intensive_review` +- `checkpoint` +- `real_life_scenario` + +## 4.5 Konkrete Blocklogik + +Die Inhalte werden in Blöcken organisiert. + +Empfohlene Größe je Block: + +- `6 bis 10` Inhaltslektionen +- danach `1 bis 3` Konsolidierungslektionen +- danach `1` Checkpoint + +Blockeinteilung für Bisaya: + +1. `Block 1` + - Begrüßung, Höflichkeit, Familie +2. `Block 2` + - Zuhause, Essen, Fürsorge, Wege +3. `Block 3` + - Alltag, Zeit, Preise, Organisation +4. `Block 4` + - Gefühle, Gesundheit, Beziehungen +5. `Block 5` + - Alltagsvertiefung und freie Situationen +6. `Block 6+` + - Stabilisierung und Transfer + +## 5. Grundprinzipien für den Ausbau + +### 5.1 Nicht nach starrer Vokabelzahl planen + +Die Lernmenge pro Lektion wird nicht mehr pauschal über „x Vokabeln“ definiert. + +Stattdessen wird pro Lektion die `Lernlast` geschätzt. + +Einflussfaktoren: + +- Anzahl neuer Einheiten +- Abstraktionsgrad +- Nähe zu bereits Bekanntem +- Grammatikkomplexität +- Produktionsanforderung + +Empfehlung: + +- leichte Lektion: `6 bis 10` neue Einheiten +- mittlere Lektion: `4 bis 7` neue Einheiten +- schwere Lektion: `2 bis 5` neue Einheiten + +Eine Einheit kann sein: + +- Wort +- Phrase +- Satzmuster +- Mini-Dialogformel + +### 5.2 Muster vor Einzelwörtern + +Bisaya soll stärker über gebrauchsfertige Muster gelernt werden. + +Beispiel: + +- nicht nur `kaon` +- sondern `Nikaon na ka?` +- `Kaon ta.` +- `Gusto ka mokaon?` + +### 5.3 Jede Einheit durchläuft vier Stufen + +Jede wichtige Spracheinheit soll im Kurs mehrfach auftauchen: + +1. `Erkennen` +2. `Abrufen` +3. `Anwenden` +4. `Variieren` + +Beispiel: + +- erkennen: richtige Übersetzung auswählen +- abrufen: Phrase selbst tippen +- anwenden: passende Dialogantwort wählen +- variieren: ähnliche Situation frei beantworten + +## 6. Wiederholungsmodell + +## 6.1 Ziel + +Wiederholung soll intensiv genug sein, um Vergessen zu verhindern, aber nicht so dominant, dass Lernende stehenbleiben. + +## 6.2 Vier Ebenen der Wiederholung + +### A. Mikro-Wiederholung innerhalb derselben Lektion + +Neuer Stoff taucht in derselben Lektion mehrfach wieder auf, aber in anderer Form. + +Beispiel: + +- Einführung +- Multiple Choice +- Lückentext +- Dialog +- Sprechimpuls + +### B. Kurzintervall-Wiederholung + +Neue Inhalte werden nach kurzen Abständen erneut aufgegriffen: + +- nach `1 Tag` +- nach `3 Tagen` +- nach `7 Tagen` + +Allerdings nur, wenn sie noch nicht stabil wirken. + +### C. Intensiv-Wiederholungsphasen + +Nach jedem inhaltlichen Block folgt eine gezielte Konsolidierungsphase. + +Empfehlung: + +- `2 Wochen Aufbau` +- danach `3 bis 5 Tage Intensiv-Wiederholung` + +Merkmale: + +- kaum neuer Stoff +- hoher Abrufanteil +- viele Mischaufgaben +- mehr aktive Produktion + +### D. Spiral-Wiederholung + +Frühere Inhalte kommen in späteren Modulen wieder, aber in neuen Kontexten. + +Nicht: + +- dieselbe Karte nochmal + +Sondern: + +- neues Umfeld +- neue Satzkombination +- neue Gesprächssituation + +Beispiel: + +- `Nanay = Mutter` +- später: + - `Asa si Nanay?` + - `Nikaon na si Nanay?` + - `Si Nanay naa sa balay.` + +## 6.3 Zielverhältnis neu vs. alt + +Die App soll neue und alte Inhalte dynamisch mischen. + +Empfohlene Verteilung: + +- Start einer neuen Lektionsphase: + - `70 % neu` + - `30 % alt` + +- Mitte eines Blocks: + - `50 % neu` + - `50 % alt` + +- kurz vor Blockabschluss: + - `30 % neu` + - `70 % alt` + +- Intensiv-Wiederholungsphase: + - `10 % neu` + - `90 % alt` + +Wichtig: + +Nicht alles Alte wiederholen, sondern vor allem: + +- fällige Inhalte +- oft falsch beantwortete Inhalte +- ähnliche/verwechselbare Inhalte +- Kernmuster mit hoher Alltagshäufigkeit + +## 7. Neue Kursstruktur für Bisaya + +Der Ausbau soll den Kurs in inhaltliche Module statt nur in lineare Wochen gliedern. + +## 7.1 Phase A: Schnellstart + +### Modul 1: Begrüßen, reagieren, höflich sein + +- Begrüßungen +- Verabschiedungen +- Danke / Bitte +- nach dem Befinden fragen +- einfache Reaktionen + +### Modul 2: Familie und Nähe + +- Familienbezeichnungen +- Anredeformen +- Fürsorge +- Nähe und Zuneigung + +### Modul 3: Zuhause und Alltag + +- Haus +- Räume +- Gegenstände +- Routinen +- Essen und Trinken + +### Modul 4: Unterwegs und organisieren + +- Richtungen +- Orte +- Fragen nach Wegen +- Zeit und Tagesablauf +- Zahlen, Preise, Einkauf + +### Modul 5: Schnellstart-Konsolidierung + +- große Wiederholungsblöcke +- Mischdialoge +- erste freie Aufgaben +- blockübergreifender Checkpoint + +## 7.2 Phase B: Alltag + +### Modul 6: Familie, Kinder, Besuch + +- mit Verwandten sprechen +- Kinder und Betreuung +- Besuche und gemeinsamer Alltag + +### Modul 7: Gefühle, Bedürfnisse, soziale Reaktionen + +- Sorgen +- Freude +- Müdigkeit +- Hunger +- Unsicherheit +- Trost und Reaktion + +### Modul 8: Gesundheit und Hilfe + +- Beschwerden +- Medikamente +- Arzt / Hilfe holen +- erklären, was los ist + +### Modul 9: Termine, Vergangenheit, Zukunft + +- gestern / heute / morgen +- Vorhaben +- einfache Erzählung +- Vergangenes und Zukünftiges im Alltag + +### Modul 10: Alltag stabilisieren + +- längere Szenarien +- Rollenspiele +- Fehlerspots +- Wortschatzfestigung + +## 7.3 Phase C: Stabilisierung + +### Modul 11: Freies Alltagsgespräch + +- längere Reaktionsketten +- Folgefragen +- Umschreiben +- Rückfragen + +### Modul 12: Tiefenwiederholung + +- Kernmuster aus Phase A und B +- Kontrasttraining +- häufige Fehler +- aktive Produktionsphasen + +### Modul 13: Praxis- und Transferphase + +- offene Situationen +- variierte Antworten +- Mini-Gespräche +- Abschlussdiagnosen + +## 8. Lektionstypen + +Die bisherigen Typen `vocab`, `conversation`, `grammar`, `review`, `culture` reichen technisch als Basis, sind didaktisch aber zu grob. + +Empfohlen ist eine Erweiterung um fachliche Typen. + +### Neue didaktische Typen + +- `core_input` +- `pattern_drill` +- `guided_dialogue` +- `recall_session` +- `contrast_session` +- `real_life_scenario` +- `intensive_review` +- `checkpoint` + +Diese können technisch entweder: + +- als neue `lessonType`-Werte eingeführt werden + +oder: + +- als neues Feld `didactic_mode` + +Erste Empfehlung: + +- bestehendes `lessonType` beibehalten +- zusätzlich `didacticMode` ergänzen + +So bleiben bestehende Inhalte kompatibel. + +## 9. Vorschlag für Datenmodell-Erweiterung + +Aktuell vorhanden in [vocab_course_lesson.js](/mnt/share/torsten/Programs/YourPart3/backend/models/community/vocab_course_lesson.js): + +- `weekNumber` +- `dayNumber` +- `lessonType` +- `learningGoals` +- `corePatterns` +- `grammarFocus` +- `speakingPrompts` +- `practicalTasks` +- `targetMinutes` +- `targetScorePercent` +- `requiresReview` + +Für den Ausbau werden zusätzliche Felder empfohlen. + +## 9.1 Neue Felder auf Lektionsebene + +- `difficulty_weight` + - numerischer Schwierigkeitswert + - Grundlage für adaptive Freigabe + +- `new_unit_target` + - wie viele neue Einheiten diese Lektion ungefähr einführt + +- `review_weight` + - wie stark Wiederholung hier dominieren soll + +- `didactic_mode` + - z. B. `core_input`, `intensive_review`, `checkpoint` + +- `block_number` + - ordnet Lektionen einem Konsolidierungsblock zu + +- `phase_label` + - z. B. `quickstart`, `daily_life`, `stabilization` + +## 9.2 Neue Felder auf Kurs- oder Fortschrittsebene + +Empfehlung für `course progress` bzw. neue itembasierte Progress-Tabelle: + +- `item_strength` +- `error_rate` +- `last_seen_at` +- `next_due_at` +- `confusion_group` +- `production_level_reached` + +Wichtig: + +Langfristig reicht reiner Lektionsfortschritt nicht aus. Sprachlernen muss itembasiert werden. + +## 10. Fortschrittslogik + +Die Kursseite in [VocabCourseView.vue](/mnt/share/torsten/Programs/YourPart3/frontend/src/views/social/VocabCourseView.vue) ist aktuell stark linear aufgebaut: + +- Lektion 1 +- Lektion 2 +- Lektion 3 + +Für den Ausbau gilt: + +- lineare Struktur darf bleiben +- aber die eigentliche Tageslogik darf nicht nur „nächste Lektion“ sein + +## 10.1 Neuer Tagesablauf + +Der empfohlene Tagesablauf: + +1. `fällige Wiederholungen` +2. `Intensivphase`, falls aktiv +3. `neuer Stoff`, wenn Kapazität frei +4. `kurze Abschlussaktivierung` + +## 10.2 Freigabe neuer Inhalte + +Neue Lektionen werden nicht mehr nur über die vorige Lektion freigeschaltet. + +Stattdessen soll die Freigabe berücksichtigen: + +- ob kritische Wiederholungen offen sind +- ob Kernmuster des letzten Blocks grob sitzen +- ob der Nutzer gerade in einer Intensivphase ist + +Aber: + +- keine totale Blockade +- Fortschritt muss motivierend bleiben + +Empfehlung: + +- `soft gate` statt `hard gate` + +## 11. Trainerlogik + +Die Traineransicht soll drei Zustände unterscheiden: + +- `neu` +- `wiederholen` +- `vertiefen` + +## 11.1 Vertiefen ist kein normales Wiederholen + +`Vertiefen` bedeutet: + +- alte Inhalte in neuer Form +- mehr Produktion +- mehr Kontrast +- mehr Dialognähe + +Beispiele: + +- aus MC wird freie Antwort +- aus Wortpaar wird Dialogergänzung +- aus kurzer Phrase wird Mini-Szenario + +## 11.2 Intensiv-Wiederholungsphase + +Während einer Intensivphase sollen Nutzer nicht das Gefühl haben, festzustecken. + +Darum braucht die Oberfläche: + +- klare Kennzeichnung +- sichtbaren Sinn +- sichtbares Ende + +Beispiel: + +- `Intensivphase 2/4` +- `Heute festigst du Woche 1 und 2` +- `Danach öffnet sich Modul 3` + +## 12. UI-Anpassungen + +## 12.1 Kursübersicht + +In [VocabCourseView.vue](/mnt/share/torsten/Programs/YourPart3/frontend/src/views/social/VocabCourseView.vue): + +- statt nur `x Lektionen` +- auch sichtbar machen: + - Phase + - Modul + - Intensivblöcke + - Checkpoints + +## 12.2 Lektion + +In [VocabLessonView.vue](/mnt/share/torsten/Programs/YourPart3/frontend/src/views/social/VocabLessonView.vue): + +- stärker unterscheiden zwischen: + - neuer Stoff + - vertiefender Stoff + - Wiederholung + - Intensivphase + +- sichtbare Lernabsicht pro Lektion: + - `Heute neu` + - `Heute festigen` + - `Heute abrufen` + +## 12.3 Startseite für Sprachkurse + +Empfohlen: + +- `Heute fällig` +- `Heute neu möglich` +- `kritische Muster` +- `nächster Intensivblock` + +## 13. Inhalte für Bisaya konkret + +Für Bisaya sollen zunächst diese Ausbaupakete entstehen. + +## Paket A: Schnellstart vertiefen + +Ziel: + +- bestehenden 4-Wochen-Kurs auf `6 Wochen` erweitern +- mehr Muster +- mehr freie Antworten +- erste echte Intensivphasen + +Umfang: + +- vorhandene Lektionen überarbeiten +- zusätzliche Lektionen für: + - Hör-/Sprechmuster + - Kontrasttraining + - Alltagsszenarien + +## Paket B: Alltagsmodule ergänzen + +Neue Module: + +- Besuche und Gastgeber-Situationen +- Kinder und Familie +- Gesundheit +- Erledigungen und Termine +- soziale Fragen und Hilfen + +## Paket C: Stabilisierung + +Neue Blöcke: + +- Dialogtage +- Mischtraining +- Fehlerschwerpunkte +- große Wiederholungsphasen + +## 14. Empfohlene Umsetzung in Phasen + +## Phase 1: Didaktische Infrastruktur + +Ziel: + +- System auf adaptive Kurslogik vorbereiten + +Umfang: + +- neue didaktische Felder ergänzen +- Intensivphasen im Progress-Modell vorbereiten +- UI-Kennzeichnung für `neu`, `wiederholen`, `vertiefen` + +Konkreter Lieferumfang: + +- Phase- und Fokusfelder auf Lektionsebene +- Datenbankmigration +- SQL-Skript für manuelle Einspielung +- Service-Fallbacks für bestehende Lektionen ohne gepflegte Felder +- Kursübersicht zeigt Phase, Fokus, Block und Intensivwiederholung +- Lektionsansicht zeigt Phase, Fokus, neue Einheiten und Wiederholungsgewicht + +Bewusst noch nicht enthalten: + +- adaptive itembasierte Terminierung +- neue große Bisaya-Contentpakete +- kompletter Umbau der Progress-Logik + +## Phase 2: Bestehenden Bisaya-Kurs überarbeiten + +Ziel: + +- vorhandenen Kurs nicht wegwerfen, sondern fachlich umbauen + +Umfang: + +- 40 Lektionen neu clustern +- Intensivblöcke einziehen +- leichte und schwere Lektionen neu gewichten +- mehr Produktionsaufgaben ergänzen + +Konkrete Zuordnung für den bestehenden 4-Wochen-Kurs: + +1. `Block 1 / quickstart / Lektion 1-10` + - Begrüßung, Überlebenssätze, Familie, Fürsorge + - Abschluss mit `Woche 1 - Wiederholung` als `intensive_review` + - Abschluss mit `Woche 1 - Vokabeltest` als `checkpoint` +2. `Block 2 / quickstart / Lektion 11-20` + - Alltag, Wege, Zeit, Einkauf + - Abschluss mit `Woche 2 - Wiederholung` als `intensive_review` + - Abschluss mit `Woche 2 - Vokabeltest` als `checkpoint` +3. `Block 3 / daily_life / Lektion 21-30` + - Gefühle, Gesundheit, Höflichkeit, Kinder + - Abschluss mit `Woche 3 - Wiederholung` als `intensive_review` + - Abschluss mit `Woche 3 - Vokabeltest` als `checkpoint` +4. `Block 4 / stabilization / Lektion 31-40` + - freie Gespräche, Transfer, große Wiederholungswellen + - `32`, `34` und `36` als intensive Wiederholungsphasen + - `38` und `39` als abschließende Checkpoints + +Technische Umsetzung von Phase 2: + +- neues Mapping für alle 40 Bisaya-Lektionen in + - [bisaya-course-phase2-pedagogy.js](/mnt/share/torsten/Programs/YourPart3/backend/scripts/bisaya-course-phase2-pedagogy.js) +- Erzeugung neuer Kurse schreibt diese Felder direkt mit + - [create-bisaya-course.js](/mnt/share/torsten/Programs/YourPart3/backend/scripts/create-bisaya-course.js) +- Refresh bestehender Kurse pflegt dieselben Werte nach + - [apply-bisaya-course-refresh.js](/mnt/share/torsten/Programs/YourPart3/backend/scripts/apply-bisaya-course-refresh.js) + - [update-bisaya-didactics.js](/mnt/share/torsten/Programs/YourPart3/backend/scripts/update-bisaya-didactics.js) +- manuelles SQL-Backfill für bestehende Datenbanken + - [backfill_bisaya_phase2_pedagogy.sql](/mnt/share/torsten/Programs/YourPart3/backend/sql/backfill_bisaya_phase2_pedagogy.sql) + +## Phase 3: Kurs in 6-Wochen-Schnellstart ausbauen + +Ziel: + +- aktuelles Niveau sinnvoll vertiefen + +Umfang: + +- zusätzliche Lektionen +- mehr Dialoge +- mehr Abruftraining +- mehr Spiralwiederholung + +Konkrete Umsetzung: + +1. `Woche 5` + - Besuch & Gastfreundschaft + - Besuch & Haushalt + - Fragen im Alltag vertiefen + - Termine & Verabredungen + - zwei intensive Wiederholungswellen plus Checkpoint +2. `Woche 6` + - Unterwegs & Transport + - Arbeit & Aufgaben + - freies Gespräch zu Familie und Alltag + - Konflikte & Missverständnisse + - große Spiralwiederholung + - Schnellstart-Abschlusstest und Abschlussprüfung + +Technische Umsetzung: + +- neue Lektionen `41` bis `60` in + - [bisaya-course-phase3-extension.js](/mnt/share/torsten/Programs/YourPart3/backend/scripts/bisaya-course-phase3-extension.js) +- bestehender Kursgenerator erweitert + - [create-bisaya-course.js](/mnt/share/torsten/Programs/YourPart3/backend/scripts/create-bisaya-course.js) +- Nachrüstskript für bestehende Kurse + - [extend-bisaya-course-phase3.js](/mnt/share/torsten/Programs/YourPart3/backend/scripts/extend-bisaya-course-phase3.js) +- SQL für Titel-/Beschreibungsumstellung + - [extend_bisaya_course_phase3_to_6_weeks.sql](/mnt/share/torsten/Programs/YourPart3/backend/sql/extend_bisaya_course_phase3_to_6_weeks.sql) + +## Phase 4: Alltagsphase ergänzen + +Ziel: + +- aus Einstieg einen echten Alltagskurs machen + +Umfang: + +- neue Module 6 bis 10 +- Ausbau auf 3-Monats-Pfad + +Konkrete Umsetzung: + +1. `Woche 7` + - Kinder im Alltag + - Schule & Betreuung + - Hausaufgaben, Routine und Spiel +2. `Woche 8` + - Arzt, Apotheke und Pflege + - Beschwerden, Hilfe und Genesung +3. `Woche 9` + - Einkaufen vertiefen + - Markt, Bank, Dokumente und Behördengänge +4. `Woche 10` + - Nachbarschaft, Hilfe, Einladungen und soziale Reaktionen + - Konflikte und Gefühle im Gespräch +5. `Woche 11` + - Zuhause organisieren + - Haushalt, Reparaturen, Planung und Alltagslogistik +6. `Woche 12` + - freie Alltagsgespräche + - Mischtraining + - Fehlerschwerpunkte + - Abschlussprüfung der Alltagsphase + +Technische Umsetzung: + +- neue Lektionen `61` bis `120` in + - [bisaya-course-phase4-extension.js](/mnt/share/torsten/Programs/YourPart3/backend/scripts/bisaya-course-phase4-extension.js) +- Pädagogik-Mapping erweitert bis Lektion `120` + - [bisaya-course-phase2-pedagogy.js](/mnt/share/torsten/Programs/YourPart3/backend/scripts/bisaya-course-phase2-pedagogy.js) +- bestehender Kursgenerator erweitert + - [create-bisaya-course.js](/mnt/share/torsten/Programs/YourPart3/backend/scripts/create-bisaya-course.js) +- Nachrüstskript für bestehende Kurse + - [extend-bisaya-course-phase4.js](/mnt/share/torsten/Programs/YourPart3/backend/scripts/extend-bisaya-course-phase4.js) +- SQL für Titel-/Beschreibungsumstellung + - [extend_bisaya_course_phase4_to_3_months.sql](/mnt/share/torsten/Programs/YourPart3/backend/sql/extend_bisaya_course_phase4_to_3_months.sql) + +## Phase 5: Stabilisierungsphase ergänzen + +Ziel: + +- langfristige Verfügbarkeit + +Umfang: + +- Tiefenwiederholung +- freie Produktion +- große Misch- und Checkpointformate + +Konkrete Umsetzung: + +1. `Woche 13` + - erster Stabilisierungsblock + - große Dialogtage + - Fehlerschwerpunkte + - freie Produktion +2. `Woche 14` + - zweiter Stabilisierungsblock + - erneutes Fehlertraining + - mehr Rollenspiele und Transfer +3. `Woche 15` + - großes Mischreview + - Langzeitreview + - Gesamtabschluss des Pfads + +Technische Umsetzung: + +- neue Lektionen `121` bis `150` in + - [bisaya-course-phase5-extension.js](/mnt/share/torsten/Programs/YourPart3/backend/scripts/bisaya-course-phase5-extension.js) +- Pädagogik-Mapping erweitert bis Lektion `150` + - [bisaya-course-phase2-pedagogy.js](/mnt/share/torsten/Programs/YourPart3/backend/scripts/bisaya-course-phase2-pedagogy.js) +- bestehender Kursgenerator erweitert + - [create-bisaya-course.js](/mnt/share/torsten/Programs/YourPart3/backend/scripts/create-bisaya-course.js) +- Nachrüstskript für bestehende Kurse + - [extend-bisaya-course-phase5.js](/mnt/share/torsten/Programs/YourPart3/backend/scripts/extend-bisaya-course-phase5.js) +- SQL für Titel-/Beschreibungsumstellung + - [extend_bisaya_course_phase5_to_stabilization.sql](/mnt/share/torsten/Programs/YourPart3/backend/sql/extend_bisaya_course_phase5_to_stabilization.sql) + +## 15. Konkrete nächste technische Schritte + +### 15.1 Backend + +- Migration für zusätzliche Lektionsfelder +- Anpassung der Course-/Lesson-APIs +- Vorbereitung itembasierten Fortschritts + +### 15.1.1 Echte SQLs für Phase 1 + +Die SQL-Datei liegt in: + +- [add_vocab_lesson_phase1_fields.sql](/mnt/share/torsten/Programs/YourPart3/backend/sql/add_vocab_lesson_phase1_fields.sql) + +Verwendeter SQL-Block: + +```sql +ALTER TABLE community.vocab_course_lesson +ADD COLUMN IF NOT EXISTS didactic_mode TEXT, +ADD COLUMN IF NOT EXISTS phase_label TEXT, +ADD COLUMN IF NOT EXISTS block_number INTEGER, +ADD COLUMN IF NOT EXISTS difficulty_weight INTEGER, +ADD COLUMN IF NOT EXISTS new_unit_target INTEGER, +ADD COLUMN IF NOT EXISTS review_weight INTEGER, +ADD COLUMN IF NOT EXISTS is_intensive_review BOOLEAN NOT NULL DEFAULT FALSE; +``` + +Kommentare: + +```sql +COMMENT ON COLUMN community.vocab_course_lesson.didactic_mode IS + 'Didaktischer Modus der Lektion, z.B. core_input, guided_dialogue, intensive_review oder checkpoint.'; +COMMENT ON COLUMN community.vocab_course_lesson.phase_label IS + 'Übergeordnete Lernphase, z.B. quickstart, daily_life oder stabilization.'; +COMMENT ON COLUMN community.vocab_course_lesson.block_number IS + 'Inhaltlicher Block für Konsolidierungs- und Wiederholungswellen.'; +COMMENT ON COLUMN community.vocab_course_lesson.difficulty_weight IS + 'Grobe relative Schwierigkeit der Lektion von leicht bis schwer.'; +COMMENT ON COLUMN community.vocab_course_lesson.new_unit_target IS + 'Empfohlene Zahl neuer Spracheinheiten in dieser Lektion.'; +COMMENT ON COLUMN community.vocab_course_lesson.review_weight IS + 'Wie stark Wiederholung in dieser Lektion dominieren soll, typischerweise 0 bis 100.'; +COMMENT ON COLUMN community.vocab_course_lesson.is_intensive_review IS + 'Markiert Lektionen, die als intensive Wiederholungsphase gedacht sind.'; +``` + +### 15.1.2 Echte SQLs für Phase 2 + +Die SQL-Datei liegt in: + +- [backfill_bisaya_phase2_pedagogy.sql](/mnt/share/torsten/Programs/YourPart3/backend/sql/backfill_bisaya_phase2_pedagogy.sql) + +Sie pflegt für bestehende Bisaya-Kurse die Felder + +- `phase_label` +- `block_number` +- `didactic_mode` +- `difficulty_weight` +- `new_unit_target` +- `review_weight` +- `is_intensive_review` + +für die Lektionen `1` bis `40` explizit nach. + +### 15.2 Frontend + +- Kursübersicht mit Phasen- und Blockdarstellung +- Traineransicht mit Zustand `neu / vertiefen / wiederholen` +- Kennzeichnung von Intensivphasen + +### 15.3 Content + +- Bestandsaudit des Bisaya-Kurses +- Definition der Kernmuster pro Modul +- Ausbau der Lektionen um Kontrast- und Produktionsaufgaben + +## 16. Entscheidungsempfehlung + +Empfohlen ist: + +1. den bisherigen Kurs offiziell als `Schnellstart` einordnen +2. die Wiederholungslogik zuerst verbessern +3. erst danach große Mengen neuer Inhalte hinzufügen + +Begründung: + +Ein größerer Kurs ohne bessere Konsolidierung verschärft nur das bestehende Problem. + +Die richtige Reihenfolge ist: + +- erst didaktische Struktur +- dann Ausbau +- dann Langzeitstabilisierung + +## 17. Priorisierte erste Lieferung + +Wenn nur ein begrenzter erster Schritt umgesetzt werden soll, dann dieser: + +1. Bisaya-Kurs auf `6 Wochen` erweitern +2. nach je 2 Wochen eine `Intensiv-Wiederholungsphase` einbauen +3. Trainer technisch auf `neu / vertiefen / wiederholen` umstellen +4. die aktuellen 40 Lektionen nach Lernlast neu gewichten + +Das ist der kleinste sinnvolle Ausbau, der didaktisch bereits einen echten Unterschied macht. diff --git a/frontend/src/i18n/locales/de/admin.json b/frontend/src/i18n/locales/de/admin.json index e76d231..7eedd29 100644 --- a/frontend/src/i18n/locales/de/admin.json +++ b/frontend/src/i18n/locales/de/admin.json @@ -176,17 +176,33 @@ "statusActive": "Schwanger bis", "statusNone": "Nicht schwanger", "fatherId": "Vater-Charakter-ID (optional)", + "fatherSelect": "Vater (Ehepartner / Verlobter / Liebhaber)", + "fatherNone": "— kein Vater gespeichert —", + "fatherHintList": "Liste aus Beziehungen dieses Charakters (Ehe, Verlobung, aktive Liebschaft).", + "fatherHintManual": "Kein passender Partner in der Datenbank: Vater-Charakter-ID manuell eintragen.", + "fatherManualPlaceholder": "Charakter-ID", "dueDays": "Tage bis zum Termin", + "dueDaysHint": "0 = Termin heute (Geburt kann je nach Spiel-Logik zeitnah anstehen).", "force": "Schwangerschaft setzen", "clear": "Schwangerschaft entfernen", "successForce": "Schwangerschaft wurde gesetzt.", "successClear": "Schwangerschaft wurde entfernt.", - "error": "Aktion fehlgeschlagen." + "error": "Aktion fehlgeschlagen.", + "relationship": { + "married": "Ehepartner", + "engaged": "Verlobter", + "lover": "Liebhaber" + } }, "birth": { "title": "Geburt erzwingen (Admin)", "motherHint": "Es wird der oben genannte Charakter (Mutter) verwendet.", "fatherId": "Vater-Charakter-ID", + "fatherSelect": "Vater (Ehepartner / Verlobter / Liebhaber)", + "fatherChoose": "— Vater wählen —", + "fatherHintList": "Liste aus Beziehungen dieses Charakters.", + "fatherHintManual": "Kein Partner in der Liste: Vater-Charakter-ID manuell eintragen.", + "fatherRequired": "Bitte einen Vater auswählen oder die Charakter-ID angeben.", "context": "Kontext", "contextMarriage": "Ehe", "contextLover": "Liebschaft", diff --git a/frontend/src/i18n/locales/en/admin.json b/frontend/src/i18n/locales/en/admin.json index 3072668..987bb50 100644 --- a/frontend/src/i18n/locales/en/admin.json +++ b/frontend/src/i18n/locales/en/admin.json @@ -231,17 +231,33 @@ "statusActive": "Expecting until", "statusNone": "Not pregnant", "fatherId": "Father character ID (optional)", + "fatherSelect": "Father (spouse / fiancé(e) / lover)", + "fatherNone": "— no father stored —", + "fatherHintList": "From this character’s relationships (marriage, engagement, active affair).", + "fatherHintManual": "No matching partner in the database: enter the father’s character ID manually.", + "fatherManualPlaceholder": "Character ID", "dueDays": "Days until due date", + "dueDaysHint": "0 = due today (birth may follow depending on game logic).", "force": "Set pregnancy", "clear": "Clear pregnancy", "successForce": "Pregnancy has been set.", "successClear": "Pregnancy has been cleared.", - "error": "Action failed." + "error": "Action failed.", + "relationship": { + "married": "Spouse", + "engaged": "Engaged partner", + "lover": "Lover" + } }, "birth": { "title": "Force birth (admin)", "motherHint": "The character listed above is used as the mother.", "fatherId": "Father character ID", + "fatherSelect": "Father (spouse / fiancé(e) / lover)", + "fatherChoose": "— choose father —", + "fatherHintList": "From this character’s relationships.", + "fatherHintManual": "No partner in the list: enter the father’s character ID manually.", + "fatherRequired": "Please select a father or enter the character ID.", "context": "Context", "contextMarriage": "Marriage", "contextLover": "Affair", diff --git a/frontend/src/i18n/locales/es/admin.json b/frontend/src/i18n/locales/es/admin.json index 4780dca..479bf88 100644 --- a/frontend/src/i18n/locales/es/admin.json +++ b/frontend/src/i18n/locales/es/admin.json @@ -176,17 +176,33 @@ "statusActive": "Embarazo hasta", "statusNone": "No embarazada", "fatherId": "ID del padre (opcional)", + "fatherSelect": "Padre (cónyuge / prometido / amante)", + "fatherNone": "— sin padre guardado —", + "fatherHintList": "Según las relaciones de este personaje (matrimonio, prometido, amante activo).", + "fatherHintManual": "Sin pareja adecuada en la base de datos: introduce manualmente el ID del padre.", + "fatherManualPlaceholder": "ID de personaje", "dueDays": "Días hasta el parto previsto", + "dueDaysHint": "0 = parto previsto hoy (el nacimiento puede seguir según la lógica del juego).", "force": "Establecer embarazo", "clear": "Quitar embarazo", "successForce": "Embarazo establecido.", "successClear": "Embarazo eliminado.", - "error": "La acción ha fallado." + "error": "La acción ha fallado.", + "relationship": { + "married": "Cónyuge", + "engaged": "Prometido", + "lover": "Amante" + } }, "birth": { "title": "Forzar nacimiento (admin)", "motherHint": "Se usa el personaje indicado arriba como madre.", "fatherId": "ID del padre", + "fatherSelect": "Padre (cónyuge / prometido / amante)", + "fatherChoose": "— elegir padre —", + "fatherHintList": "Según las relaciones de este personaje.", + "fatherHintManual": "Sin pareja en la lista: introduce el ID del padre manualmente.", + "fatherRequired": "Elige un padre o introduce el ID de personaje.", "context": "Contexto", "contextMarriage": "Matrimonio", "contextLover": "Amante", diff --git a/frontend/src/views/admin/falukant/EditUserView.vue b/frontend/src/views/admin/falukant/EditUserView.vue index eadc376..436308a 100644 --- a/frontend/src/views/admin/falukant/EditUserView.vue +++ b/frontend/src/views/admin/falukant/EditUserView.vue @@ -54,13 +54,28 @@

+

{{ $t('admin.falukant.edituser.pregnancy.fatherHintList') }}

+

{{ $t('admin.falukant.edituser.pregnancy.fatherHintManual') }}

+

{{ $t('admin.falukant.edituser.pregnancy.dueDaysHint') }}

@@ -69,9 +84,24 @@

{{ $t('admin.falukant.edituser.birth.title') }}

{{ $t('admin.falukant.edituser.birth.motherHint') }}

+

{{ $t('admin.falukant.edituser.birth.fatherHintList') }}

+

{{ $t('admin.falukant.edituser.birth.fatherHintManual') }}