feat(admin): add potential fathers retrieval for character management
All checks were successful
Deploy to production / deploy (push) Successful in 2m47s

- Implemented a new method in AdminService to fetch potential fathers for a given character based on existing relationships.
- Updated AdminController to expose this functionality via a new API endpoint.
- Enhanced adminRouter to include the route for retrieving potential fathers.
- Modified frontend components to allow selection of potential fathers during pregnancy and birth management.
- Updated internationalization files to include new translation keys related to father selection.
This commit is contained in:
Torsten Schulz (local)
2026-03-31 08:50:56 +02:00
parent ee11a989a0
commit 9a78bc7c4b
30 changed files with 3907 additions and 45 deletions

View File

@@ -16,6 +16,7 @@ class AdminController {
this.adminForceFalukantPregnancy = this.adminForceFalukantPregnancy.bind(this); this.adminForceFalukantPregnancy = this.adminForceFalukantPregnancy.bind(this);
this.adminClearFalukantPregnancy = this.adminClearFalukantPregnancy.bind(this); this.adminClearFalukantPregnancy = this.adminClearFalukantPregnancy.bind(this);
this.adminForceFalukantBirth = this.adminForceFalukantBirth.bind(this); this.adminForceFalukantBirth = this.adminForceFalukantBirth.bind(this);
this.adminGetPotentialFathersForCharacter = this.adminGetPotentialFathersForCharacter.bind(this);
this.getFalukantUserBranches = this.getFalukantUserBranches.bind(this); this.getFalukantUserBranches = this.getFalukantUserBranches.bind(this);
this.updateFalukantStock = this.updateFalukantStock.bind(this); this.updateFalukantStock = this.updateFalukantStock.bind(this);
this.addFalukantStock = this.addFalukantStock.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) { async adminClearFalukantPregnancy(req, res) {
try { try {
const { userid: userId } = req.headers; const { userid: userId } = req.headers;

View File

@@ -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;
`);
}
};

View File

@@ -48,6 +48,42 @@ VocabCourseLesson.init({
defaultValue: 'vocab', defaultValue: 'vocab',
field: 'lesson_type' 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: { audioUrl: {
type: DataTypes.TEXT, type: DataTypes.TEXT,
allowNull: true, allowNull: true,

View File

@@ -43,6 +43,7 @@ router.post('/contacts/answer', authenticate, adminController.answerContact);
router.post('/falukant/searchuser', authenticate, adminController.searchUser); router.post('/falukant/searchuser', authenticate, adminController.searchUser);
router.get('/falukant/getuser/:id', authenticate, adminController.getFalukantUserById); router.get('/falukant/getuser/:id', authenticate, adminController.getFalukantUserById);
router.post('/falukant/edituser', authenticate, adminController.changeFalukantUser); 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/force-pregnancy', authenticate, adminController.adminForceFalukantPregnancy);
router.post('/falukant/character/clear-pregnancy', authenticate, adminController.adminClearFalukantPregnancy); router.post('/falukant/character/clear-pregnancy', authenticate, adminController.adminClearFalukantPregnancy);
router.post('/falukant/character/force-birth', authenticate, adminController.adminForceFalukantBirth); router.post('/falukant/character/force-birth', authenticate, adminController.adminForceFalukantBirth);

View File

@@ -9,6 +9,7 @@
import { sequelize } from '../utils/sequelize.js'; import { sequelize } from '../utils/sequelize.js';
import VocabCourseLesson from '../models/community/vocab_course_lesson.js'; import VocabCourseLesson from '../models/community/vocab_course_lesson.js';
import { getBisayaLessonPedagogy } from './bisaya-course-phase2-pedagogy.js';
const LESSONS_TO_ADD = [ const LESSONS_TO_ADD = [
{ {
@@ -98,6 +99,8 @@ async function addBisayaWeek1Lessons() {
continue; continue;
} }
const pedagogy = getBisayaLessonPedagogy(lessonData.lessonNumber) || {};
await VocabCourseLesson.create({ await VocabCourseLesson.create({
courseId: course.id, courseId: course.id,
chapterId: null, chapterId: null,
@@ -113,7 +116,14 @@ async function addBisayaWeek1Lessons() {
speakingPrompts: lessonData.speakingPrompts || [], speakingPrompts: lessonData.speakingPrompts || [],
targetMinutes: lessonData.targetMinutes, targetMinutes: lessonData.targetMinutes,
targetScorePercent: lessonData.targetScorePercent, 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`); console.log(` ✅ Lektion ${lessonData.lessonNumber}: "${lessonData.title}" hinzugefügt`);

View File

@@ -12,6 +12,7 @@ import VocabGrammarExercise from '../models/community/vocab_grammar_exercise.js'
import VocabCourseProgress from '../models/community/vocab_course_progress.js'; import VocabCourseProgress from '../models/community/vocab_course_progress.js';
import VocabGrammarExerciseProgress from '../models/community/vocab_grammar_exercise_progress.js'; import VocabGrammarExerciseProgress from '../models/community/vocab_grammar_exercise_progress.js';
import { Op } from 'sequelize'; import { Op } from 'sequelize';
import { getBisayaLessonPedagogy } from './bisaya-course-phase2-pedagogy.js';
const LESSON_DIDACTICS = { const LESSON_DIDACTICS = {
'Begrüßungen & Höflichkeit': { 'Begrüßungen & Höflichkeit': {
@@ -171,14 +172,23 @@ async function applyBisayaCourseRefresh() {
for (const lesson of lessons) { for (const lesson of lessons) {
const didactics = LESSON_DIDACTICS[lesson.title]; const didactics = LESSON_DIDACTICS[lesson.title];
if (!didactics) continue; const pedagogy = getBisayaLessonPedagogy(lesson.lessonNumber);
if (!didactics && !pedagogy) continue;
const normalizedDidactics = didactics || {};
await lesson.update({ await lesson.update({
learningGoals: didactics.learningGoals || [], learningGoals: normalizedDidactics.learningGoals || [],
corePatterns: didactics.corePatterns || [], corePatterns: normalizedDidactics.corePatterns || [],
grammarFocus: didactics.grammarFocus || [], grammarFocus: normalizedDidactics.grammarFocus || [],
speakingPrompts: didactics.speakingPrompts || [], speakingPrompts: normalizedDidactics.speakingPrompts || [],
practicalTasks: didactics.practicalTasks || [] 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++; updatedLessons++;
} }

View File

@@ -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;
}

View File

@@ -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.' }
];

View File

@@ -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.' }
];

View File

@@ -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.' }
];

View File

@@ -13,6 +13,9 @@ import VocabCourseLesson from '../models/community/vocab_course_lesson.js';
import VocabGrammarExercise from '../models/community/vocab_grammar_exercise.js'; import VocabGrammarExercise from '../models/community/vocab_grammar_exercise.js';
import VocabCourse from '../models/community/vocab_course.js'; import VocabCourse from '../models/community/vocab_course.js';
import User from '../models/community/user.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) { function withTypeName(exerciseTypeName, exercise) {
return { 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 // Bisaya-spezifische Übungen basierend auf Lektionsthemen
const BISAYA_EXERCISES = { const BISAYA_EXERCISES = {
// Lektion 1: Begrüßungen & Höflichkeit // Lektion 1: Begrüßungen & Höflichkeit
@@ -1489,7 +1842,8 @@ async function findOrCreateSystemUser() {
return systemUser; return systemUser;
} }
function getExercisesForLesson(lessonTitle) { function getExercisesForLesson(lesson) {
const lessonTitle = lesson.title;
// Suche nach exaktem Titel // Suche nach exaktem Titel
if (BISAYA_EXERCISES[lessonTitle]) { if (BISAYA_EXERCISES[lessonTitle]) {
return BISAYA_EXERCISES[lessonTitle]; return BISAYA_EXERCISES[lessonTitle];
@@ -1501,9 +1855,8 @@ function getExercisesForLesson(lessonTitle) {
return exercises; return exercises;
} }
} }
// Keine Übungen für unbekannte Lektionen (statt Dummy-Übungen) return generateExercisesFromDidactics(lesson);
return [];
} }
async function createBisayaCourseContent() { async function createBisayaCourseContent() {
@@ -1550,7 +1903,7 @@ async function createBisayaCourseContent() {
console.log(` ${lessons.length} Lektionen gefunden\n`); console.log(` ${lessons.length} Lektionen gefunden\n`);
for (const lesson of lessons) { for (const lesson of lessons) {
const exercises = getExercisesForLesson(lesson.title); const exercises = getExercisesForLesson(lesson);
if (exercises.length === 0) { if (exercises.length === 0) {
const existingCount = await VocabGrammarExercise.count({ where: { lessonId: lesson.id } }); const existingCount = await VocabGrammarExercise.count({ where: { lessonId: lesson.id } });
if (existingCount > 0) { if (existingCount > 0) {

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env node #!/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: * Verwendung:
* node backend/scripts/create-bisaya-course.js <languageId> <ownerHashedId> * node backend/scripts/create-bisaya-course.js <languageId> <ownerHashedId>
@@ -11,6 +11,10 @@ import VocabCourse from '../models/community/vocab_course.js';
import VocabCourseLesson from '../models/community/vocab_course_lesson.js'; import VocabCourseLesson from '../models/community/vocab_course_lesson.js';
import User from '../models/community/user.js'; import User from '../models/community/user.js';
import crypto from 'crypto'; 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 = { const LESSON_DIDACTICS = {
'Begrüßungen & Höflichkeit': { 'Begrüßungen & Höflichkeit': {
@@ -192,7 +196,10 @@ const LESSON_DIDACTICS = {
'Lami', 'Lami',
'Mingaw ko nimo' 'Mingaw ko nimo'
] ]
} },
...BISAYA_PHASE3_DIDACTICS,
...BISAYA_PHASE4_DIDACTICS,
...BISAYA_PHASE5_DIDACTICS
}; };
const LESSONS = [ const LESSONS = [
@@ -398,7 +405,11 @@ const LESSONS = [
{ week: 4, day: 5, num: 40, type: 'culture', title: 'Kulturelle Tipps & Tricks', { week: 4, day: 5, num: 40, type: 'culture', title: 'Kulturelle Tipps & Tricks',
desc: 'Wichtige kulturelle Hinweise für den Alltag', desc: 'Wichtige kulturelle Hinweise für den Alltag',
targetMin: 15, targetScore: 0, review: false, 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) { async function createBisayaCourse(languageId, ownerHashedId) {
@@ -422,8 +433,8 @@ async function createBisayaCourse(languageId, ownerHashedId) {
const shareCode = crypto.randomBytes(8).toString('hex'); const shareCode = crypto.randomBytes(8).toString('hex');
const course = await VocabCourse.create({ const course = await VocabCourse.create({
ownerUserId: user.id, ownerUserId: user.id,
title: 'Bisaya für Familien - Schnellstart in 4 Wochen', title: 'Bisaya für Familien - Alltag & Stabilisierung',
description: 'Lerne Bisaya (Cebuano) schnell und praktisch für den Familienalltag. Fokus auf Sprechen & Hören mit strukturiertem 4-Wochen-Plan.', 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), languageId: Number(languageId),
difficultyLevel: 1, difficultyLevel: 1,
isPublic: true, isPublic: true,
@@ -435,6 +446,7 @@ async function createBisayaCourse(languageId, ownerHashedId) {
// Erstelle Lektionen // Erstelle Lektionen
for (const lessonData of LESSONS) { for (const lessonData of LESSONS) {
const pedagogy = getBisayaLessonPedagogy(lessonData.num) || {};
const lesson = await VocabCourseLesson.create({ const lesson = await VocabCourseLesson.create({
courseId: course.id, courseId: course.id,
chapterId: null, // Wird später mit Vokabeln verknüpft chapterId: null, // Wird später mit Vokabeln verknüpft
@@ -452,7 +464,14 @@ async function createBisayaCourse(languageId, ownerHashedId) {
practicalTasks: LESSON_DIDACTICS[lessonData.title]?.practicalTasks || [], practicalTasks: LESSON_DIDACTICS[lessonData.title]?.practicalTasks || [],
targetMinutes: lessonData.targetMin, targetMinutes: lessonData.targetMin,
targetScorePercent: lessonData.targetScore, 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})`); 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(` - Konversations-Lektionen: ${LESSONS.filter(l => l.type === 'conversation').length}`);
console.log(` - Grammatik-Lektionen: ${LESSONS.filter(l => l.type === 'grammar').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(` - 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(`\n💡 Nächste Schritte:`);
console.log(` 1. Füge Vokabeln zu den Vokabel-Lektionen hinzu`); console.log(` 1. Füge Vokabeln zu den Vokabel-Lektionen hinzu`);
console.log(` 2. Erstelle Grammatik-Übungen für die Grammatik-Lektionen`); console.log(` 2. Erstelle Grammatik-Übungen für die Grammatik-Lektionen`);

View File

@@ -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);
});

View File

@@ -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);
});

View File

@@ -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);
});

View File

@@ -8,6 +8,7 @@
import { sequelize } from '../utils/sequelize.js'; import { sequelize } from '../utils/sequelize.js';
import VocabCourseLesson from '../models/community/vocab_course_lesson.js'; import VocabCourseLesson from '../models/community/vocab_course_lesson.js';
import { getBisayaLessonPedagogy } from './bisaya-course-phase2-pedagogy.js';
const LESSON_DIDACTICS = { const LESSON_DIDACTICS = {
'Begrüßungen & Höflichkeit': { 'Begrüßungen & Höflichkeit': {
@@ -129,14 +130,23 @@ async function updateBisayaDidactics() {
for (const row of lessons) { for (const row of lessons) {
const lesson = await VocabCourseLesson.findByPk(row.id); const lesson = await VocabCourseLesson.findByPk(row.id);
const didactics = LESSON_DIDACTICS[lesson.title]; const didactics = LESSON_DIDACTICS[lesson.title];
if (!didactics) continue; const pedagogy = getBisayaLessonPedagogy(lesson.lessonNumber);
if (!didactics && !pedagogy) continue;
const normalizedDidactics = didactics || {};
await lesson.update({ await lesson.update({
learningGoals: didactics.learningGoals || [], learningGoals: normalizedDidactics.learningGoals || [],
corePatterns: didactics.corePatterns || [], corePatterns: normalizedDidactics.corePatterns || [],
grammarFocus: didactics.grammarFocus || [], grammarFocus: normalizedDidactics.grammarFocus || [],
speakingPrompts: didactics.speakingPrompts || [], speakingPrompts: normalizedDidactics.speakingPrompts || [],
practicalTasks: didactics.practicalTasks || [] 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++; updated++;
console.log(`✅ Didaktik aktualisiert: Lektion ${lesson.lessonNumber} - ${lesson.title}`); console.log(`✅ Didaktik aktualisiert: Lektion ${lesson.lessonNumber} - ${lesson.title}`);

View File

@@ -29,6 +29,9 @@ import EroticVideo from '../models/community/erotic_video.js';
import EroticContentReport from '../models/community/erotic_content_report.js'; import EroticContentReport from '../models/community/erotic_content_report.js';
import TitleOfNobility from "../models/falukant/type/title_of_nobility.js"; import TitleOfNobility from "../models/falukant/type/title_of_nobility.js";
import ChildRelation from "../models/falukant/data/child_relation.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 { sequelize } from '../utils/sequelize.js';
import npcCreationJobService from './npcCreationJobService.js'; import npcCreationJobService from './npcCreationJobService.js';
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
@@ -668,7 +671,7 @@ class AdminService {
required: true, required: true,
attributes: ['money', 'certificate', 'id'], attributes: ['money', 'certificate', 'id'],
include: [{ include: [{
model: FalukantCharacter, model: FalukantCharacter.unscoped(),
as: 'character', as: 'character',
include: [{ include: [{
model: FalukantPredefineFirstname, model: FalukantPredefineFirstname,
@@ -945,10 +948,85 @@ class AdminService {
await character.save(); 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). * Admin: Charakter als schwanger markieren (erwarteter Geburtstermin).
* @param {number} fatherCharacterId - optional; Vater-Charakter-ID * @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 } = {}) { async adminForceFalukantPregnancy(userId, characterId, { fatherCharacterId = null, dueInDays = 21 } = {}) {
if (!(await this.hasUserAccess(userId, 'falukantusers'))) { if (!(await this.hasUserAccess(userId, 'falukantusers'))) {
@@ -960,7 +1038,16 @@ class AdminService {
const father = await FalukantCharacter.findByPk(Number(fatherCharacterId)); const father = await FalukantCharacter.findByPk(Number(fatherCharacterId));
if (!father) throw new Error('fatherNotFound'); 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(); const due = new Date();
due.setDate(due.getDate() + days); due.setDate(due.getDate() + days);
await mother.update({ await mother.update({

View File

@@ -188,6 +188,149 @@ export default class VocabService {
return []; 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) { _buildLessonDidactics(plainLesson) {
const grammarExercises = Array.isArray(plainLesson.grammarExercises) ? plainLesson.grammarExercises : []; const grammarExercises = Array.isArray(plainLesson.grammarExercises) ? plainLesson.grammarExercises : [];
const grammarExplanations = []; const grammarExplanations = [];
@@ -1020,6 +1163,11 @@ export default class VocabService {
return a.lessonNumber - b.lessonNumber; return a.lessonNumber - b.lessonNumber;
}); });
courseData.lessons = courseData.lessons.map((lesson) => ({
...lesson,
pedagogy: this._buildLessonPedagogy(lesson)
}));
return courseData; return courseData;
} }
@@ -1129,6 +1277,7 @@ export default class VocabService {
} }
plainLesson.didactics = this._buildLessonDidactics(plainLesson); plainLesson.didactics = this._buildLessonDidactics(plainLesson);
plainLesson.pedagogy = this._buildLessonPedagogy(plainLesson);
return plainLesson; return plainLesson;
} }
@@ -1301,7 +1450,7 @@ export default class VocabService {
return exercises.map(e => e.get({ plain: true })); 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 user = await this._getUserByHashedId(hashedUserId);
const course = await VocabCourse.findByPk(courseId); const course = await VocabCourse.findByPk(courseId);
@@ -1343,6 +1492,13 @@ export default class VocabService {
weekNumber: weekNumber ? Number(weekNumber) : null, weekNumber: weekNumber ? Number(weekNumber) : null,
dayNumber: dayNumber ? Number(dayNumber) : null, dayNumber: dayNumber ? Number(dayNumber) : null,
lessonType: lessonType || 'vocab', 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, audioUrl: audioUrl || null,
culturalNotes: culturalNotes || null, culturalNotes: culturalNotes || null,
learningGoals: this._normalizeStringList(learningGoals), learningGoals: this._normalizeStringList(learningGoals),
@@ -1358,7 +1514,7 @@ export default class VocabService {
return lesson.get({ plain: true }); 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 user = await this._getUserByHashedId(hashedUserId);
const lesson = await VocabCourseLesson.findByPk(lessonId, { const lesson = await VocabCourseLesson.findByPk(lessonId, {
include: [{ model: VocabCourse, as: 'course' }] include: [{ model: VocabCourse, as: 'course' }]
@@ -1383,6 +1539,13 @@ export default class VocabService {
if (weekNumber !== undefined) updates.weekNumber = weekNumber ? Number(weekNumber) : null; if (weekNumber !== undefined) updates.weekNumber = weekNumber ? Number(weekNumber) : null;
if (dayNumber !== undefined) updates.dayNumber = dayNumber ? Number(dayNumber) : null; if (dayNumber !== undefined) updates.dayNumber = dayNumber ? Number(dayNumber) : null;
if (lessonType !== undefined) updates.lessonType = lessonType; 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 (audioUrl !== undefined) updates.audioUrl = audioUrl;
if (culturalNotes !== undefined) updates.culturalNotes = culturalNotes; if (culturalNotes !== undefined) updates.culturalNotes = culturalNotes;
if (learningGoals !== undefined) updates.learningGoals = this._normalizeStringList(learningGoals); if (learningGoals !== undefined) updates.learningGoals = this._normalizeStringList(learningGoals);

View File

@@ -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.';

View File

@@ -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;

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

File diff suppressed because it is too large Load Diff

View File

@@ -176,17 +176,33 @@
"statusActive": "Schwanger bis", "statusActive": "Schwanger bis",
"statusNone": "Nicht schwanger", "statusNone": "Nicht schwanger",
"fatherId": "Vater-Charakter-ID (optional)", "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", "dueDays": "Tage bis zum Termin",
"dueDaysHint": "0 = Termin heute (Geburt kann je nach Spiel-Logik zeitnah anstehen).",
"force": "Schwangerschaft setzen", "force": "Schwangerschaft setzen",
"clear": "Schwangerschaft entfernen", "clear": "Schwangerschaft entfernen",
"successForce": "Schwangerschaft wurde gesetzt.", "successForce": "Schwangerschaft wurde gesetzt.",
"successClear": "Schwangerschaft wurde entfernt.", "successClear": "Schwangerschaft wurde entfernt.",
"error": "Aktion fehlgeschlagen." "error": "Aktion fehlgeschlagen.",
"relationship": {
"married": "Ehepartner",
"engaged": "Verlobter",
"lover": "Liebhaber"
}
}, },
"birth": { "birth": {
"title": "Geburt erzwingen (Admin)", "title": "Geburt erzwingen (Admin)",
"motherHint": "Es wird der oben genannte Charakter (Mutter) verwendet.", "motherHint": "Es wird der oben genannte Charakter (Mutter) verwendet.",
"fatherId": "Vater-Charakter-ID", "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", "context": "Kontext",
"contextMarriage": "Ehe", "contextMarriage": "Ehe",
"contextLover": "Liebschaft", "contextLover": "Liebschaft",

View File

@@ -231,17 +231,33 @@
"statusActive": "Expecting until", "statusActive": "Expecting until",
"statusNone": "Not pregnant", "statusNone": "Not pregnant",
"fatherId": "Father character ID (optional)", "fatherId": "Father character ID (optional)",
"fatherSelect": "Father (spouse / fiancé(e) / lover)",
"fatherNone": "— no father stored —",
"fatherHintList": "From this characters relationships (marriage, engagement, active affair).",
"fatherHintManual": "No matching partner in the database: enter the fathers character ID manually.",
"fatherManualPlaceholder": "Character ID",
"dueDays": "Days until due date", "dueDays": "Days until due date",
"dueDaysHint": "0 = due today (birth may follow depending on game logic).",
"force": "Set pregnancy", "force": "Set pregnancy",
"clear": "Clear pregnancy", "clear": "Clear pregnancy",
"successForce": "Pregnancy has been set.", "successForce": "Pregnancy has been set.",
"successClear": "Pregnancy has been cleared.", "successClear": "Pregnancy has been cleared.",
"error": "Action failed." "error": "Action failed.",
"relationship": {
"married": "Spouse",
"engaged": "Engaged partner",
"lover": "Lover"
}
}, },
"birth": { "birth": {
"title": "Force birth (admin)", "title": "Force birth (admin)",
"motherHint": "The character listed above is used as the mother.", "motherHint": "The character listed above is used as the mother.",
"fatherId": "Father character ID", "fatherId": "Father character ID",
"fatherSelect": "Father (spouse / fiancé(e) / lover)",
"fatherChoose": "— choose father —",
"fatherHintList": "From this characters relationships.",
"fatherHintManual": "No partner in the list: enter the fathers character ID manually.",
"fatherRequired": "Please select a father or enter the character ID.",
"context": "Context", "context": "Context",
"contextMarriage": "Marriage", "contextMarriage": "Marriage",
"contextLover": "Affair", "contextLover": "Affair",

View File

@@ -176,17 +176,33 @@
"statusActive": "Embarazo hasta", "statusActive": "Embarazo hasta",
"statusNone": "No embarazada", "statusNone": "No embarazada",
"fatherId": "ID del padre (opcional)", "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", "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", "force": "Establecer embarazo",
"clear": "Quitar embarazo", "clear": "Quitar embarazo",
"successForce": "Embarazo establecido.", "successForce": "Embarazo establecido.",
"successClear": "Embarazo eliminado.", "successClear": "Embarazo eliminado.",
"error": "La acción ha fallado." "error": "La acción ha fallado.",
"relationship": {
"married": "Cónyuge",
"engaged": "Prometido",
"lover": "Amante"
}
}, },
"birth": { "birth": {
"title": "Forzar nacimiento (admin)", "title": "Forzar nacimiento (admin)",
"motherHint": "Se usa el personaje indicado arriba como madre.", "motherHint": "Se usa el personaje indicado arriba como madre.",
"fatherId": "ID del padre", "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", "context": "Contexto",
"contextMarriage": "Matrimonio", "contextMarriage": "Matrimonio",
"contextLover": "Amante", "contextLover": "Amante",

View File

@@ -54,13 +54,28 @@
<template v-else>{{ $t('admin.falukant.edituser.pregnancy.statusNone') }}</template> <template v-else>{{ $t('admin.falukant.edituser.pregnancy.statusNone') }}</template>
</p> </p>
<label class="form-field"> <label class="form-field">
{{ $t('admin.falukant.edituser.pregnancy.fatherId') }} {{ $t('admin.falukant.edituser.pregnancy.fatherSelect') }}
<input type="number" v-model.number="adminPregnancyFatherId" min="1" placeholder="—" /> <select v-if="potentialFathers.length" v-model="adminPregnancyFatherSelect">
<option value="">{{ $t('admin.falukant.edituser.pregnancy.fatherNone') }}</option>
<option v-for="p in potentialFathers" :key="p.characterId" :value="String(p.characterId)">
{{ p.displayName }} {{ fatherRelationshipLabel(p.relationshipType) }}
</option>
</select>
<input
v-else
type="number"
v-model.number="adminPregnancyFatherManualId"
min="1"
:placeholder="$t('admin.falukant.edituser.pregnancy.fatherManualPlaceholder')"
/>
</label> </label>
<p v-if="potentialFathers.length" class="admin-family-tools__hint">{{ $t('admin.falukant.edituser.pregnancy.fatherHintList') }}</p>
<p v-else class="admin-family-tools__hint">{{ $t('admin.falukant.edituser.pregnancy.fatherHintManual') }}</p>
<label class="form-field"> <label class="form-field">
{{ $t('admin.falukant.edituser.pregnancy.dueDays') }} {{ $t('admin.falukant.edituser.pregnancy.dueDays') }}
<input type="number" v-model.number="adminDueInDays" min="1" max="365" /> <input type="number" v-model.number="adminDueInDays" min="0" max="365" />
</label> </label>
<p class="admin-family-tools__hint">{{ $t('admin.falukant.edituser.pregnancy.dueDaysHint') }}</p>
<div class="action-buttons"> <div class="action-buttons">
<button type="button" @click="adminForcePregnancy">{{ $t('admin.falukant.edituser.pregnancy.force') }}</button> <button type="button" @click="adminForcePregnancy">{{ $t('admin.falukant.edituser.pregnancy.force') }}</button>
<button type="button" class="button-secondary" @click="adminClearPregnancy">{{ $t('admin.falukant.edituser.pregnancy.clear') }}</button> <button type="button" class="button-secondary" @click="adminClearPregnancy">{{ $t('admin.falukant.edituser.pregnancy.clear') }}</button>
@@ -69,9 +84,24 @@
<h4>{{ $t('admin.falukant.edituser.birth.title') }}</h4> <h4>{{ $t('admin.falukant.edituser.birth.title') }}</h4>
<p class="admin-family-tools__hint">{{ $t('admin.falukant.edituser.birth.motherHint') }}</p> <p class="admin-family-tools__hint">{{ $t('admin.falukant.edituser.birth.motherHint') }}</p>
<label class="form-field"> <label class="form-field">
{{ $t('admin.falukant.edituser.birth.fatherId') }} * {{ $t('admin.falukant.edituser.birth.fatherSelect') }} *
<input type="number" v-model.number="adminBirthFatherId" min="1" required /> <select v-if="potentialFathers.length" v-model="adminBirthFatherSelect">
<option value="">{{ $t('admin.falukant.edituser.birth.fatherChoose') }}</option>
<option v-for="p in potentialFathers" :key="'b-' + p.characterId" :value="String(p.characterId)">
{{ p.displayName }} {{ fatherRelationshipLabel(p.relationshipType) }}
</option>
</select>
<input
v-else
type="number"
v-model.number="adminBirthFatherManualId"
min="1"
required
:placeholder="$t('admin.falukant.edituser.pregnancy.fatherManualPlaceholder')"
/>
</label> </label>
<p v-if="potentialFathers.length" class="admin-family-tools__hint">{{ $t('admin.falukant.edituser.birth.fatherHintList') }}</p>
<p v-else class="admin-family-tools__hint">{{ $t('admin.falukant.edituser.birth.fatherHintManual') }}</p>
<label class="form-field"> <label class="form-field">
{{ $t('admin.falukant.edituser.birth.context') }} {{ $t('admin.falukant.edituser.birth.context') }}
<select v-model="adminBirthContext"> <select v-model="adminBirthContext">
@@ -220,9 +250,12 @@ export default {
branches: false, branches: false,
stockTypes: false stockTypes: false
}, },
adminPregnancyFatherId: null, potentialFathers: [],
adminPregnancyFatherSelect: '',
adminPregnancyFatherManualId: null,
adminDueInDays: 21, adminDueInDays: 21,
adminBirthFatherId: null, adminBirthFatherSelect: '',
adminBirthFatherManualId: null,
adminBirthContext: 'marriage', adminBirthContext: 'marriage',
adminBirthLegitimacy: 'legitimate', adminBirthLegitimacy: 'legitimate',
adminBirthGender: '' adminBirthGender: ''
@@ -300,6 +333,12 @@ export default {
this.originalAge = this.age; this.originalAge = this.age;
this.users = []; this.users = [];
this.activeTab = 'userdata'; this.activeTab = 'userdata';
this.adminPregnancyFatherSelect = '';
this.adminPregnancyFatherManualId = null;
this.adminBirthFatherSelect = '';
this.adminBirthFatherManualId = null;
await this.loadPotentialFathers();
this.syncFatherSelectionsFromCharacter();
}, },
async saveUser() { async saveUser() {
const dataToChange = { const dataToChange = {
@@ -432,11 +471,61 @@ export default {
this.originalUser = JSON.parse(JSON.stringify(this.editableUser)); this.originalUser = JSON.parse(JSON.stringify(this.editableUser));
this.age = Math.floor((Date.now() - new Date(this.editableUser.falukantData[0].character.birthdate)) / (24 * 60 * 60 * 1000)); this.age = Math.floor((Date.now() - new Date(this.editableUser.falukantData[0].character.birthdate)) / (24 * 60 * 60 * 1000));
this.originalAge = this.age; this.originalAge = this.age;
await this.loadPotentialFathers();
this.syncFatherSelectionsFromCharacter();
},
fatherRelationshipLabel(relationshipType) {
const key = `admin.falukant.edituser.pregnancy.relationship.${relationshipType}`;
const t = this.$t(key);
return t === key ? relationshipType : t;
},
async loadPotentialFathers() {
const cid = this.editableUser?.falukantData?.[0]?.character?.id;
if (!cid) {
this.potentialFathers = [];
return;
}
try {
const r = await apiClient.get(`/api/admin/falukant/character/${cid}/potential-fathers`);
this.potentialFathers = Array.isArray(r.data?.options) ? r.data.options : [];
} catch (e) {
console.error('loadPotentialFathers', e);
this.potentialFathers = [];
}
},
syncFatherSelectionsFromCharacter() {
const c = this.editableUser?.falukantData?.[0]?.character;
if (!c) return;
const pf = c.pregnancyFatherCharacterId ?? c.pregnancy_father_character_id;
if (pf != null && this.potentialFathers.some((x) => x.characterId === Number(pf))) {
this.adminPregnancyFatherSelect = String(pf);
}
},
resolvePregnancyFatherCharacterId() {
if (this.potentialFathers.length) {
return this.adminPregnancyFatherSelect ? Number(this.adminPregnancyFatherSelect) : null;
}
return this.adminPregnancyFatherManualId ? Number(this.adminPregnancyFatherManualId) : null;
},
resolveBirthFatherCharacterId() {
if (this.potentialFathers.length) {
return this.adminBirthFatherSelect ? Number(this.adminBirthFatherSelect) : null;
}
return this.adminBirthFatherManualId ? Number(this.adminBirthFatherManualId) : null;
},
resolveDueInDays() {
const raw = this.adminDueInDays;
if (raw === '' || raw === null || raw === undefined) {
return 21;
}
const n = Number(raw);
return Number.isFinite(n) ? n : 21;
}, },
async adminForcePregnancy() { async adminForcePregnancy() {
const characterId = this.editableUser.falukantData[0].character.id; const characterId = this.editableUser.falukantData[0].character.id;
const payload = { characterId, dueInDays: Number(this.adminDueInDays) || 21 }; const payload = { characterId, dueInDays: this.resolveDueInDays() };
if (this.adminPregnancyFatherId) payload.fatherCharacterId = Number(this.adminPregnancyFatherId); const fatherId = this.resolvePregnancyFatherCharacterId();
if (fatherId != null) payload.fatherCharacterId = fatherId;
try { try {
await apiClient.post('/api/admin/falukant/character/force-pregnancy', payload); await apiClient.post('/api/admin/falukant/character/force-pregnancy', payload);
showSuccess(this, 'tr:admin.falukant.edituser.pregnancy.successForce'); showSuccess(this, 'tr:admin.falukant.edituser.pregnancy.successForce');
@@ -457,13 +546,14 @@ export default {
}, },
async adminForceBirth() { async adminForceBirth() {
const motherCharacterId = this.editableUser.falukantData[0].character.id; const motherCharacterId = this.editableUser.falukantData[0].character.id;
if (!this.adminBirthFatherId) { const fatherCharacterId = this.resolveBirthFatherCharacterId();
showError(this, this.$t('admin.falukant.edituser.birth.fatherId')); if (fatherCharacterId == null || Number.isNaN(fatherCharacterId)) {
showError(this, this.$t('admin.falukant.edituser.birth.fatherRequired'));
return; return;
} }
const body = { const body = {
motherCharacterId, motherCharacterId,
fatherCharacterId: Number(this.adminBirthFatherId), fatherCharacterId,
birthContext: this.adminBirthContext, birthContext: this.adminBirthContext,
legitimacy: this.adminBirthLegitimacy legitimacy: this.adminBirthLegitimacy
}; };

View File

@@ -73,6 +73,12 @@
<span class="title-label">{{ lesson.title }}</span> <span class="title-label">{{ lesson.title }}</span>
<span v-if="lesson.description" class="lesson-description">{{ lesson.description }}</span> <span v-if="lesson.description" class="lesson-description">{{ lesson.description }}</span>
</div> </div>
<div class="lesson-pedagogy" v-if="lesson.pedagogy">
<span class="lesson-chip lesson-chip--phase">{{ getPhaseLabel(lesson.pedagogy.phaseLabel) }}</span>
<span class="lesson-chip lesson-chip--mode">{{ getDidacticModeLabel(lesson.pedagogy.didacticMode) }}</span>
<span v-if="lesson.pedagogy.blockNumber" class="lesson-chip lesson-chip--block">Block {{ lesson.pedagogy.blockNumber }}</span>
<span v-if="lesson.pedagogy.isIntensiveReview" class="lesson-chip lesson-chip--intensive">Intensive Wiederholung</span>
</div>
<div class="lesson-actions-content"> <div class="lesson-actions-content">
<button <button
@click="openLesson(lesson.id)" @click="openLesson(lesson.id)"
@@ -328,6 +334,36 @@ export default {
openLanguageAssistantSettings() { openLanguageAssistantSettings() {
this.$router.push('/settings/language-assistant'); this.$router.push('/settings/language-assistant');
}, },
getPhaseLabel(phaseLabel) {
switch (phaseLabel) {
case 'quickstart':
return 'Schnellstart';
case 'daily_life':
return 'Alltag';
case 'stabilization':
return 'Stabilisierung';
default:
return 'Lernphase';
}
},
getDidacticModeLabel(didacticMode) {
switch (didacticMode) {
case 'core_input':
return 'Neuer Stoff';
case 'guided_dialogue':
return 'Geführter Dialog';
case 'pattern_drill':
return 'Mustertraining';
case 'real_life_scenario':
return 'Alltagsszenario';
case 'intensive_review':
return 'Wiederholungsphase';
case 'checkpoint':
return 'Checkpoint';
default:
return 'Lerneinheit';
}
},
editLesson() { editLesson() {
showInfo(this, 'Die Bearbeitung einzelner Lektionen folgt noch.'); showInfo(this, 'Die Bearbeitung einzelner Lektionen folgt noch.');
} }
@@ -517,6 +553,42 @@ export default {
background: rgba(255, 255, 255, 0.7); background: rgba(255, 255, 255, 0.7);
} }
.lesson-pedagogy {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.lesson-chip {
display: inline-flex;
align-items: center;
padding: 4px 10px;
border-radius: 999px;
font-size: 0.78rem;
font-weight: 700;
letter-spacing: 0.02em;
}
.lesson-chip--phase {
background: rgba(120, 195, 138, 0.16);
color: #42634e;
}
.lesson-chip--mode {
background: rgba(248, 162, 43, 0.16);
color: #8a5411;
}
.lesson-chip--block {
background: rgba(93, 64, 55, 0.09);
color: #6d5446;
}
.lesson-chip--intensive {
background: rgba(207, 78, 78, 0.14);
color: #a13f3f;
}
.lesson-card__header { .lesson-card__header {
display: flex; display: flex;
align-items: center; align-items: center;

View File

@@ -43,6 +43,14 @@
<span class="lesson-meta-label">{{ $t('socialnetwork.vocab.courses.lessonTypeLabel') }}</span> <span class="lesson-meta-label">{{ $t('socialnetwork.vocab.courses.lessonTypeLabel') }}</span>
<strong>{{ getLessonTypeLabel(lesson.lessonType) }}</strong> <strong>{{ getLessonTypeLabel(lesson.lessonType) }}</strong>
</div> </div>
<div class="lesson-meta-item" v-if="lessonPedagogy.phaseLabel">
<span class="lesson-meta-label">Phase</span>
<strong>{{ getPhaseLabel(lessonPedagogy.phaseLabel) }}</strong>
</div>
<div class="lesson-meta-item" v-if="lessonPedagogy.didacticMode">
<span class="lesson-meta-label">Fokus</span>
<strong>{{ getDidacticModeLabel(lessonPedagogy.didacticMode) }}</strong>
</div>
<div class="lesson-meta-item"> <div class="lesson-meta-item">
<span class="lesson-meta-label">{{ $t('socialnetwork.vocab.courses.recommendedDuration') }}</span> <span class="lesson-meta-label">{{ $t('socialnetwork.vocab.courses.recommendedDuration') }}</span>
<strong>{{ formatTargetMinutes(lesson.targetMinutes) }}</strong> <strong>{{ formatTargetMinutes(lesson.targetMinutes) }}</strong>
@@ -51,9 +59,22 @@
<span class="lesson-meta-label">{{ $t('socialnetwork.vocab.courses.exerciseLoad') }}</span> <span class="lesson-meta-label">{{ $t('socialnetwork.vocab.courses.exerciseLoad') }}</span>
<strong>{{ effectiveExercises?.length || 0 }} {{ $t('socialnetwork.vocab.courses.exercisesShort') }}</strong> <strong>{{ effectiveExercises?.length || 0 }} {{ $t('socialnetwork.vocab.courses.exercisesShort') }}</strong>
</div> </div>
<div class="lesson-meta-item" v-if="lessonPedagogy.newUnitTarget">
<span class="lesson-meta-label">Neue Einheiten</span>
<strong>{{ lessonPedagogy.newUnitTarget }}</strong>
</div>
<div class="lesson-meta-item" v-if="lessonPedagogy.reviewWeight != null">
<span class="lesson-meta-label">Wiederholung</span>
<strong>{{ lessonPedagogy.reviewWeight }}%</strong>
</div>
</div> </div>
</div> </div>
<div v-if="lessonPedagogy.isIntensiveReview" class="lesson-intensity-banner">
<strong>Intensive Wiederholungsphase</strong>
<p>Diese Lektion priorisiert Wiederholung und Vertiefung. Neuer Stoff wird bewusst reduziert, damit vorhandene Muster stabil werden.</p>
</div>
<div class="learn-grid"> <div class="learn-grid">
<div v-if="lesson && lesson.description" class="lesson-description-box"> <div v-if="lesson && lesson.description" class="lesson-description-box">
<h4>{{ $t('socialnetwork.vocab.courses.lessonDescription') }}</h4> <h4>{{ $t('socialnetwork.vocab.courses.lessonDescription') }}</h4>
@@ -872,6 +893,17 @@ export default {
practicalTasks: [] practicalTasks: []
}; };
}, },
lessonPedagogy() {
return this.lesson?.pedagogy || {
didacticMode: null,
phaseLabel: null,
blockNumber: null,
difficultyWeight: null,
newUnitTarget: null,
reviewWeight: null,
isIntensiveReview: false
};
},
assistantAvailable() { assistantAvailable() {
if (!this.assistantSettings) { if (!this.assistantSettings) {
return false; return false;
@@ -1280,6 +1312,36 @@ export default {
}; };
return labels[lessonType] || lessonType || this.$t('socialnetwork.vocab.courses.lessonTypeVocab'); return labels[lessonType] || lessonType || this.$t('socialnetwork.vocab.courses.lessonTypeVocab');
}, },
getPhaseLabel(phaseLabel) {
switch (phaseLabel) {
case 'quickstart':
return 'Schnellstart';
case 'daily_life':
return 'Alltag';
case 'stabilization':
return 'Stabilisierung';
default:
return 'Lernphase';
}
},
getDidacticModeLabel(didacticMode) {
switch (didacticMode) {
case 'core_input':
return 'Neuer Stoff';
case 'guided_dialogue':
return 'Geführter Dialog';
case 'pattern_drill':
return 'Mustertraining';
case 'real_life_scenario':
return 'Alltagsszenario';
case 'intensive_review':
return 'Wiederholungsphase';
case 'checkpoint':
return 'Checkpoint';
default:
return 'Lernfokus';
}
},
formatTargetMinutes(targetMinutes) { formatTargetMinutes(targetMinutes) {
const minutes = Number(targetMinutes); const minutes = Number(targetMinutes);
if (!minutes) { if (!minutes) {
@@ -2187,6 +2249,24 @@ export default {
color: #7a6848; color: #7a6848;
} }
.lesson-intensity-banner {
margin-bottom: 18px;
padding: 14px 16px;
border-radius: 12px;
border: 1px solid rgba(207, 78, 78, 0.24);
background: rgba(255, 242, 242, 0.95);
color: #8e3d3d;
}
.lesson-intensity-banner strong,
.lesson-intensity-banner p {
margin: 0;
}
.lesson-intensity-banner p {
margin-top: 6px;
}
.learn-grid { .learn-grid {
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));