feat(admin): add potential fathers retrieval for character management
All checks were successful
Deploy to production / deploy (push) Successful in 2m47s
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:
@@ -16,6 +16,7 @@ class AdminController {
|
||||
this.adminForceFalukantPregnancy = this.adminForceFalukantPregnancy.bind(this);
|
||||
this.adminClearFalukantPregnancy = this.adminClearFalukantPregnancy.bind(this);
|
||||
this.adminForceFalukantBirth = this.adminForceFalukantBirth.bind(this);
|
||||
this.adminGetPotentialFathersForCharacter = this.adminGetPotentialFathersForCharacter.bind(this);
|
||||
this.getFalukantUserBranches = this.getFalukantUserBranches.bind(this);
|
||||
this.updateFalukantStock = this.updateFalukantStock.bind(this);
|
||||
this.addFalukantStock = this.addFalukantStock.bind(this);
|
||||
@@ -390,6 +391,18 @@ class AdminController {
|
||||
}
|
||||
}
|
||||
|
||||
async adminGetPotentialFathersForCharacter(req, res) {
|
||||
try {
|
||||
const { userid: userId } = req.headers;
|
||||
const { characterId } = req.params;
|
||||
const response = await AdminService.adminGetPotentialFathersForCharacter(userId, characterId);
|
||||
res.status(200).json(response);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
res.status(400).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
async adminClearFalukantPregnancy(req, res) {
|
||||
try {
|
||||
const { userid: userId } = req.headers;
|
||||
|
||||
@@ -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;
|
||||
`);
|
||||
}
|
||||
};
|
||||
@@ -48,6 +48,42 @@ VocabCourseLesson.init({
|
||||
defaultValue: 'vocab',
|
||||
field: 'lesson_type'
|
||||
},
|
||||
didacticMode: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
field: 'didactic_mode'
|
||||
},
|
||||
phaseLabel: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
field: 'phase_label'
|
||||
},
|
||||
blockNumber: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
field: 'block_number'
|
||||
},
|
||||
difficultyWeight: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
field: 'difficulty_weight'
|
||||
},
|
||||
newUnitTarget: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
field: 'new_unit_target'
|
||||
},
|
||||
reviewWeight: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
field: 'review_weight'
|
||||
},
|
||||
isIntensiveReview: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: false,
|
||||
field: 'is_intensive_review'
|
||||
},
|
||||
audioUrl: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
|
||||
@@ -43,6 +43,7 @@ router.post('/contacts/answer', authenticate, adminController.answerContact);
|
||||
router.post('/falukant/searchuser', authenticate, adminController.searchUser);
|
||||
router.get('/falukant/getuser/:id', authenticate, adminController.getFalukantUserById);
|
||||
router.post('/falukant/edituser', authenticate, adminController.changeFalukantUser);
|
||||
router.get('/falukant/character/:characterId/potential-fathers', authenticate, adminController.adminGetPotentialFathersForCharacter);
|
||||
router.post('/falukant/character/force-pregnancy', authenticate, adminController.adminForceFalukantPregnancy);
|
||||
router.post('/falukant/character/clear-pregnancy', authenticate, adminController.adminClearFalukantPregnancy);
|
||||
router.post('/falukant/character/force-birth', authenticate, adminController.adminForceFalukantBirth);
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
import { sequelize } from '../utils/sequelize.js';
|
||||
import VocabCourseLesson from '../models/community/vocab_course_lesson.js';
|
||||
import { getBisayaLessonPedagogy } from './bisaya-course-phase2-pedagogy.js';
|
||||
|
||||
const LESSONS_TO_ADD = [
|
||||
{
|
||||
@@ -98,6 +99,8 @@ async function addBisayaWeek1Lessons() {
|
||||
continue;
|
||||
}
|
||||
|
||||
const pedagogy = getBisayaLessonPedagogy(lessonData.lessonNumber) || {};
|
||||
|
||||
await VocabCourseLesson.create({
|
||||
courseId: course.id,
|
||||
chapterId: null,
|
||||
@@ -113,7 +116,14 @@ async function addBisayaWeek1Lessons() {
|
||||
speakingPrompts: lessonData.speakingPrompts || [],
|
||||
targetMinutes: lessonData.targetMinutes,
|
||||
targetScorePercent: lessonData.targetScorePercent,
|
||||
requiresReview: lessonData.requiresReview
|
||||
requiresReview: lessonData.requiresReview,
|
||||
didacticMode: pedagogy.didacticMode || null,
|
||||
phaseLabel: pedagogy.phaseLabel || null,
|
||||
blockNumber: pedagogy.blockNumber ?? null,
|
||||
difficultyWeight: pedagogy.difficultyWeight ?? null,
|
||||
newUnitTarget: pedagogy.newUnitTarget ?? null,
|
||||
reviewWeight: pedagogy.reviewWeight ?? null,
|
||||
isIntensiveReview: Boolean(pedagogy.isIntensiveReview)
|
||||
});
|
||||
|
||||
console.log(` ✅ Lektion ${lessonData.lessonNumber}: "${lessonData.title}" hinzugefügt`);
|
||||
|
||||
@@ -12,6 +12,7 @@ import VocabGrammarExercise from '../models/community/vocab_grammar_exercise.js'
|
||||
import VocabCourseProgress from '../models/community/vocab_course_progress.js';
|
||||
import VocabGrammarExerciseProgress from '../models/community/vocab_grammar_exercise_progress.js';
|
||||
import { Op } from 'sequelize';
|
||||
import { getBisayaLessonPedagogy } from './bisaya-course-phase2-pedagogy.js';
|
||||
|
||||
const LESSON_DIDACTICS = {
|
||||
'Begrüßungen & Höflichkeit': {
|
||||
@@ -171,14 +172,23 @@ async function applyBisayaCourseRefresh() {
|
||||
|
||||
for (const lesson of lessons) {
|
||||
const didactics = LESSON_DIDACTICS[lesson.title];
|
||||
if (!didactics) continue;
|
||||
const pedagogy = getBisayaLessonPedagogy(lesson.lessonNumber);
|
||||
if (!didactics && !pedagogy) continue;
|
||||
const normalizedDidactics = didactics || {};
|
||||
|
||||
await lesson.update({
|
||||
learningGoals: didactics.learningGoals || [],
|
||||
corePatterns: didactics.corePatterns || [],
|
||||
grammarFocus: didactics.grammarFocus || [],
|
||||
speakingPrompts: didactics.speakingPrompts || [],
|
||||
practicalTasks: didactics.practicalTasks || []
|
||||
learningGoals: normalizedDidactics.learningGoals || [],
|
||||
corePatterns: normalizedDidactics.corePatterns || [],
|
||||
grammarFocus: normalizedDidactics.grammarFocus || [],
|
||||
speakingPrompts: normalizedDidactics.speakingPrompts || [],
|
||||
practicalTasks: normalizedDidactics.practicalTasks || [],
|
||||
didacticMode: pedagogy?.didacticMode || null,
|
||||
phaseLabel: pedagogy?.phaseLabel || null,
|
||||
blockNumber: pedagogy?.blockNumber ?? null,
|
||||
difficultyWeight: pedagogy?.difficultyWeight ?? null,
|
||||
newUnitTarget: pedagogy?.newUnitTarget ?? null,
|
||||
reviewWeight: pedagogy?.reviewWeight ?? null,
|
||||
isIntensiveReview: Boolean(pedagogy?.isIntensiveReview)
|
||||
});
|
||||
updatedLessons++;
|
||||
}
|
||||
|
||||
156
backend/scripts/bisaya-course-phase2-pedagogy.js
Normal file
156
backend/scripts/bisaya-course-phase2-pedagogy.js
Normal 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;
|
||||
}
|
||||
254
backend/scripts/bisaya-course-phase3-extension.js
Normal file
254
backend/scripts/bisaya-course-phase3-extension.js
Normal 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.' }
|
||||
];
|
||||
594
backend/scripts/bisaya-course-phase4-extension.js
Normal file
594
backend/scripts/bisaya-course-phase4-extension.js
Normal 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.' }
|
||||
];
|
||||
274
backend/scripts/bisaya-course-phase5-extension.js
Normal file
274
backend/scripts/bisaya-course-phase5-extension.js
Normal 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.' }
|
||||
];
|
||||
@@ -13,6 +13,9 @@ import VocabCourseLesson from '../models/community/vocab_course_lesson.js';
|
||||
import VocabGrammarExercise from '../models/community/vocab_grammar_exercise.js';
|
||||
import VocabCourse from '../models/community/vocab_course.js';
|
||||
import User from '../models/community/user.js';
|
||||
import { BISAYA_PHASE3_DIDACTICS } from './bisaya-course-phase3-extension.js';
|
||||
import { BISAYA_PHASE4_DIDACTICS } from './bisaya-course-phase4-extension.js';
|
||||
import { BISAYA_PHASE5_DIDACTICS } from './bisaya-course-phase5-extension.js';
|
||||
|
||||
function withTypeName(exerciseTypeName, exercise) {
|
||||
return {
|
||||
@@ -21,6 +24,356 @@ function withTypeName(exerciseTypeName, exercise) {
|
||||
};
|
||||
}
|
||||
|
||||
const GENERATED_BISAYA_DIDACTICS = {
|
||||
...BISAYA_PHASE3_DIDACTICS,
|
||||
...BISAYA_PHASE4_DIDACTICS,
|
||||
...BISAYA_PHASE5_DIDACTICS
|
||||
};
|
||||
|
||||
const GENERIC_DISTRACTOR_PATTERNS = Array.from(new Set(
|
||||
Object.values(GENERATED_BISAYA_DIDACTICS)
|
||||
.flatMap((entry) => Array.isArray(entry?.corePatterns) ? entry.corePatterns : [])
|
||||
.map((pattern) => String(pattern || '').trim())
|
||||
.filter(Boolean)
|
||||
)).slice(0, 200);
|
||||
|
||||
function normalizeText(value) {
|
||||
return String(value || '')
|
||||
.trim()
|
||||
.replace(/\s+/g, ' ');
|
||||
}
|
||||
|
||||
function simpleHash(value) {
|
||||
return Array.from(String(value || '')).reduce((sum, char) => sum + char.charCodeAt(0), 0);
|
||||
}
|
||||
|
||||
function rotateArray(values, offset) {
|
||||
if (!Array.isArray(values) || values.length === 0) return [];
|
||||
const normalizedOffset = ((offset % values.length) + values.length) % values.length;
|
||||
return values.slice(normalizedOffset).concat(values.slice(0, normalizedOffset));
|
||||
}
|
||||
|
||||
function getLessonDidactics(lesson) {
|
||||
const staticDidactics = GENERATED_BISAYA_DIDACTICS[lesson.title] || {};
|
||||
const learningGoals = Array.isArray(lesson.learningGoals) ? lesson.learningGoals : (staticDidactics.learningGoals || []);
|
||||
const corePatterns = Array.isArray(lesson.corePatterns) ? lesson.corePatterns : (staticDidactics.corePatterns || []);
|
||||
const grammarFocus = Array.isArray(lesson.grammarFocus) ? lesson.grammarFocus : (staticDidactics.grammarFocus || []);
|
||||
const speakingPrompts = Array.isArray(lesson.speakingPrompts) ? lesson.speakingPrompts : (staticDidactics.speakingPrompts || []);
|
||||
const practicalTasks = Array.isArray(lesson.practicalTasks) ? lesson.practicalTasks : (staticDidactics.practicalTasks || []);
|
||||
|
||||
return {
|
||||
learningGoals,
|
||||
corePatterns: corePatterns.map((entry) => normalizeText(entry)).filter(Boolean),
|
||||
grammarFocus,
|
||||
speakingPrompts,
|
||||
practicalTasks
|
||||
};
|
||||
}
|
||||
|
||||
function getScenarioPrompt(lesson, didactics) {
|
||||
const speakingPrompt = Array.isArray(didactics.speakingPrompts) ? didactics.speakingPrompts[0] : null;
|
||||
const practicalTask = Array.isArray(didactics.practicalTasks) ? didactics.practicalTasks[0] : null;
|
||||
|
||||
if (speakingPrompt?.prompt) return speakingPrompt.prompt;
|
||||
if (practicalTask?.text) return practicalTask.text;
|
||||
if (lesson.description) return lesson.description;
|
||||
return `Reagiere passend in einer Situation aus der Lektion "${lesson.title}".`;
|
||||
}
|
||||
|
||||
function getChoiceQuestion(lesson, didactics) {
|
||||
const scenarioPrompt = getScenarioPrompt(lesson, didactics);
|
||||
|
||||
switch (lesson.lessonType) {
|
||||
case 'conversation':
|
||||
return `${scenarioPrompt} Welche Bisaya-Formulierung passt am besten?`;
|
||||
case 'grammar':
|
||||
return `Welche Formulierung passt als kurze Alltagsstruktur zu "${lesson.title}"?`;
|
||||
case 'review':
|
||||
return `Welche Formulierung solltest du aus "${lesson.title}" sicher wiedererkennen?`;
|
||||
case 'culture':
|
||||
return `Welcher Ausdruck gehört am ehesten zum kulturellen Schwerpunkt "${lesson.title}"?`;
|
||||
case 'vocab':
|
||||
default:
|
||||
return `Welcher Ausdruck gehört thematisch zu "${lesson.title}"?`;
|
||||
}
|
||||
}
|
||||
|
||||
function pickDistractors(pattern, allPatterns, count) {
|
||||
return allPatterns
|
||||
.filter((entry) => entry !== pattern)
|
||||
.slice(0, count);
|
||||
}
|
||||
|
||||
function buildChoiceExercise(lesson, didactics, pattern, allPatterns, variant = 0) {
|
||||
const distractors = pickDistractors(pattern, allPatterns, 3);
|
||||
if (distractors.length < 3) return null;
|
||||
|
||||
const seed = simpleHash(`${lesson.title}:${pattern}:${variant}`);
|
||||
const options = rotateArray([pattern, ...distractors], seed % 4);
|
||||
const correctAnswer = options.indexOf(pattern);
|
||||
|
||||
return {
|
||||
exerciseTypeId: 2,
|
||||
title: `${lesson.title}: Passende Formulierung wählen`,
|
||||
instruction: 'Wähle die natürlichste Formulierung für die Situation oder den Schwerpunkt der Lektion.',
|
||||
questionData: {
|
||||
type: 'multiple_choice',
|
||||
question: getChoiceQuestion(lesson, didactics),
|
||||
options
|
||||
},
|
||||
answerData: {
|
||||
type: 'multiple_choice',
|
||||
correctAnswer
|
||||
},
|
||||
explanation: `"${pattern}" ist ein zentrales Muster dieser Lektion.`
|
||||
};
|
||||
}
|
||||
|
||||
function pickGapTarget(pattern) {
|
||||
const tokens = normalizeText(pattern)
|
||||
.split(' ')
|
||||
.map((token) => token.trim())
|
||||
.filter(Boolean);
|
||||
|
||||
const candidates = tokens
|
||||
.map((token, index) => ({ token, index, score: token.replace(/[.,?!]/g, '').length }))
|
||||
.filter(({ token }) => token.replace(/[.,?!]/g, '').length >= 4);
|
||||
|
||||
if (candidates.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
candidates.sort((left, right) => right.score - left.score);
|
||||
return candidates[0];
|
||||
}
|
||||
|
||||
function buildGapExercise(lessonTitle, pattern) {
|
||||
const gapTarget = pickGapTarget(pattern);
|
||||
if (!gapTarget) return null;
|
||||
|
||||
const tokens = normalizeText(pattern).split(' ');
|
||||
tokens[gapTarget.index] = '{gap}';
|
||||
|
||||
return {
|
||||
exerciseTypeId: 1,
|
||||
title: `${lessonTitle}: Muster vervollständigen`,
|
||||
instruction: 'Fülle die Lücke mit dem passenden Bisaya-Ausdruck.',
|
||||
questionData: {
|
||||
type: 'gap_fill',
|
||||
text: tokens.join(' '),
|
||||
gaps: 1
|
||||
},
|
||||
answerData: {
|
||||
type: 'gap_fill',
|
||||
answers: [gapTarget.token.replace(/[.,?!]/g, '')]
|
||||
},
|
||||
explanation: `Das Kernmuster lautet: "${pattern}".`
|
||||
};
|
||||
}
|
||||
|
||||
function buildContextGapExercise(lesson, didactics, pattern) {
|
||||
const gapExercise = buildGapExercise(lesson.title, pattern);
|
||||
if (!gapExercise) return null;
|
||||
|
||||
return {
|
||||
...gapExercise,
|
||||
title: `${lesson.title}: Kernmuster ergänzen`,
|
||||
instruction: `Vervollständige die Formulierung passend zur Situation: ${getScenarioPrompt(lesson, didactics)}`
|
||||
};
|
||||
}
|
||||
|
||||
function buildSentenceExercise(lessonTitle, pattern) {
|
||||
const tokens = normalizeText(pattern)
|
||||
.replace(/[?!]/g, '')
|
||||
.split(' ')
|
||||
.filter(Boolean);
|
||||
|
||||
if (tokens.length < 2) return null;
|
||||
|
||||
return {
|
||||
exerciseTypeId: 3,
|
||||
title: `${lessonTitle}: Satzmuster bauen`,
|
||||
instruction: 'Ordne die Wörter zu einem korrekten Bisaya-Satz.',
|
||||
questionData: {
|
||||
type: 'sentence_building',
|
||||
question: `Baue das Kernmuster aus der Lektion "${lessonTitle}".`,
|
||||
tokens
|
||||
},
|
||||
answerData: {
|
||||
correct: [normalizeText(pattern)]
|
||||
},
|
||||
explanation: `Dieses Kernmuster gehört zur Lektion "${lessonTitle}".`
|
||||
};
|
||||
}
|
||||
|
||||
function buildTaskSentenceExercise(lesson, didactics, pattern) {
|
||||
const sentenceExercise = buildSentenceExercise(lesson.title, pattern);
|
||||
if (!sentenceExercise) return null;
|
||||
|
||||
const practicalTask = Array.isArray(didactics.practicalTasks) ? didactics.practicalTasks[0] : null;
|
||||
|
||||
return {
|
||||
...sentenceExercise,
|
||||
title: `${lesson.title}: Satz aus dem Alltag bauen`,
|
||||
instruction: practicalTask?.text
|
||||
? `Baue die passende Formulierung für diese Aufgabe: ${practicalTask.text}`
|
||||
: 'Ordne die Wörter zu einem natürlichen Bisaya-Satz aus dem Alltag.'
|
||||
};
|
||||
}
|
||||
|
||||
function buildSpeakingExercise(lessonTitle, didactics, fallbackPattern) {
|
||||
const speakingPrompt = Array.isArray(didactics.speakingPrompts) ? didactics.speakingPrompts[0] : null;
|
||||
const expectedText = normalizeText(speakingPrompt?.cue || fallbackPattern);
|
||||
if (!expectedText) return null;
|
||||
|
||||
const keywords = expectedText
|
||||
.toLowerCase()
|
||||
.replace(/[.,?!]/g, '')
|
||||
.split(' ')
|
||||
.filter((token) => token.length >= 4)
|
||||
.slice(0, 4);
|
||||
|
||||
return {
|
||||
exerciseTypeId: 8,
|
||||
title: `${lessonTitle}: Laut sprechen`,
|
||||
instruction: 'Sprich das zentrale Muster oder den Dialog frei nach.',
|
||||
questionData: {
|
||||
type: 'speaking_from_memory',
|
||||
question: speakingPrompt?.prompt || `Sprich ein zentrales Muster aus der Lektion "${lessonTitle}".`,
|
||||
expectedText,
|
||||
keywords
|
||||
},
|
||||
answerData: {
|
||||
type: 'speaking_from_memory'
|
||||
},
|
||||
explanation: 'Wichtig sind hier ein flüssiger Abruf und die zentralen Schlüsselwörter.'
|
||||
};
|
||||
}
|
||||
|
||||
function buildReviewChoiceExercise(lesson, didactics, pattern, allPatterns) {
|
||||
const choiceExercise = buildChoiceExercise(lesson, didactics, pattern, allPatterns, 1);
|
||||
if (!choiceExercise) return null;
|
||||
|
||||
return {
|
||||
...choiceExercise,
|
||||
title: `${lesson.title}: Sicheren Abruf prüfen`,
|
||||
instruction: 'Wähle die Formulierung, die du aus dem aktiven Wiederholungsblock sicher können solltest.'
|
||||
};
|
||||
}
|
||||
|
||||
function buildCultureExercise(lesson, didactics, pattern, allPatterns) {
|
||||
const distractors = pickDistractors(pattern, allPatterns, 3);
|
||||
if (distractors.length < 3) return null;
|
||||
|
||||
const culturalNote = normalizeText(lesson.culturalNotes || '');
|
||||
const options = rotateArray([pattern, ...distractors], simpleHash(`${lesson.title}:culture`) % 4);
|
||||
|
||||
return {
|
||||
exerciseTypeId: 2,
|
||||
title: `${lesson.title}: Ausdruck kulturell einordnen`,
|
||||
instruction: 'Ordne den Ausdruck dem kulturellen Schwerpunkt der Lektion zu.',
|
||||
questionData: {
|
||||
type: 'multiple_choice',
|
||||
question: culturalNote
|
||||
? `${culturalNote} Welcher Ausdruck passt dazu besonders gut?`
|
||||
: `Welcher Ausdruck gehört besonders gut zum Schwerpunkt "${lesson.title}"?`,
|
||||
options
|
||||
},
|
||||
answerData: {
|
||||
type: 'multiple_choice',
|
||||
correctAnswer: options.indexOf(pattern)
|
||||
},
|
||||
explanation: `Der Ausdruck "${pattern}" gehört eng zum kulturellen Schwerpunkt dieser Lektion.`
|
||||
};
|
||||
}
|
||||
|
||||
function buildSituationalExercise(lessonTitle, didactics, fallbackPattern) {
|
||||
const speakingPrompt = Array.isArray(didactics.speakingPrompts) ? didactics.speakingPrompts[0] : null;
|
||||
const modelAnswer = normalizeText(speakingPrompt?.cue || fallbackPattern);
|
||||
if (!modelAnswer) return null;
|
||||
|
||||
const keywords = modelAnswer
|
||||
.toLowerCase()
|
||||
.replace(/[.,?!]/g, '')
|
||||
.split(' ')
|
||||
.filter((token) => token.length >= 4)
|
||||
.slice(0, 4);
|
||||
|
||||
return withTypeName('situational_response', {
|
||||
title: `${lessonTitle}: Situativ reagieren`,
|
||||
instruction: 'Antworte kurz und passend auf die Situation.',
|
||||
questionData: {
|
||||
type: 'situational_response',
|
||||
question: speakingPrompt?.prompt || `Reagiere passend mit einem Ausdruck aus der Lektion "${lessonTitle}".`,
|
||||
keywords
|
||||
},
|
||||
answerData: {
|
||||
modelAnswer,
|
||||
keywords
|
||||
},
|
||||
explanation: `Das Kernmuster "${modelAnswer}" passt natürlich zu dieser Situation.`
|
||||
});
|
||||
}
|
||||
|
||||
function generateExercisesFromDidactics(lesson) {
|
||||
const didactics = getLessonDidactics(lesson);
|
||||
const corePatterns = didactics.corePatterns;
|
||||
|
||||
if (corePatterns.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const patternA = corePatterns[0];
|
||||
const patternB = corePatterns[1] || corePatterns[0];
|
||||
const lessonPool = Array.from(new Set([
|
||||
...corePatterns,
|
||||
...GENERIC_DISTRACTOR_PATTERNS
|
||||
]));
|
||||
let generated = [];
|
||||
|
||||
if (lesson.lessonType === 'conversation') {
|
||||
generated = [
|
||||
buildChoiceExercise(lesson, didactics, patternA, lessonPool, 0),
|
||||
buildContextGapExercise(lesson, didactics, patternA),
|
||||
buildTaskSentenceExercise(lesson, didactics, patternB),
|
||||
buildSituationalExercise(lesson.title, didactics, patternA),
|
||||
buildSpeakingExercise(lesson.title, didactics, patternB)
|
||||
];
|
||||
} else if (lesson.lessonType === 'grammar') {
|
||||
generated = [
|
||||
buildChoiceExercise(lesson, didactics, patternA, lessonPool, 0),
|
||||
buildChoiceExercise(lesson, didactics, patternB, lessonPool, 1),
|
||||
buildContextGapExercise(lesson, didactics, patternA),
|
||||
buildTaskSentenceExercise(lesson, didactics, patternB),
|
||||
buildSpeakingExercise(lesson.title, didactics, patternA)
|
||||
];
|
||||
} else if (lesson.lessonType === 'review' || lesson.didacticMode === 'intensive_review') {
|
||||
generated = [
|
||||
buildReviewChoiceExercise(lesson, didactics, patternA, lessonPool),
|
||||
buildReviewChoiceExercise(lesson, didactics, patternB, lessonPool),
|
||||
buildContextGapExercise(lesson, didactics, patternA),
|
||||
buildTaskSentenceExercise(lesson, didactics, patternB),
|
||||
buildSituationalExercise(lesson.title, didactics, patternA),
|
||||
buildSpeakingExercise(lesson.title, didactics, patternB)
|
||||
];
|
||||
} else if (lesson.lessonType === 'culture') {
|
||||
generated = [
|
||||
buildCultureExercise(lesson, didactics, patternA, lessonPool),
|
||||
buildContextGapExercise(lesson, didactics, patternA),
|
||||
buildSpeakingExercise(lesson.title, didactics, patternB)
|
||||
];
|
||||
} else {
|
||||
generated = [
|
||||
buildChoiceExercise(lesson, didactics, patternA, lessonPool, 0),
|
||||
buildChoiceExercise(lesson, didactics, patternB, lessonPool, 1),
|
||||
buildContextGapExercise(lesson, didactics, patternA),
|
||||
buildTaskSentenceExercise(lesson, didactics, patternB)
|
||||
];
|
||||
}
|
||||
|
||||
return generated.filter(Boolean);
|
||||
}
|
||||
|
||||
// Bisaya-spezifische Übungen basierend auf Lektionsthemen
|
||||
const BISAYA_EXERCISES = {
|
||||
// Lektion 1: Begrüßungen & Höflichkeit
|
||||
@@ -1489,7 +1842,8 @@ async function findOrCreateSystemUser() {
|
||||
return systemUser;
|
||||
}
|
||||
|
||||
function getExercisesForLesson(lessonTitle) {
|
||||
function getExercisesForLesson(lesson) {
|
||||
const lessonTitle = lesson.title;
|
||||
// Suche nach exaktem Titel
|
||||
if (BISAYA_EXERCISES[lessonTitle]) {
|
||||
return BISAYA_EXERCISES[lessonTitle];
|
||||
@@ -1501,9 +1855,8 @@ function getExercisesForLesson(lessonTitle) {
|
||||
return exercises;
|
||||
}
|
||||
}
|
||||
|
||||
// Keine Übungen für unbekannte Lektionen (statt Dummy-Übungen)
|
||||
return [];
|
||||
|
||||
return generateExercisesFromDidactics(lesson);
|
||||
}
|
||||
|
||||
async function createBisayaCourseContent() {
|
||||
@@ -1550,7 +1903,7 @@ async function createBisayaCourseContent() {
|
||||
console.log(` ${lessons.length} Lektionen gefunden\n`);
|
||||
|
||||
for (const lesson of lessons) {
|
||||
const exercises = getExercisesForLesson(lesson.title);
|
||||
const exercises = getExercisesForLesson(lesson);
|
||||
if (exercises.length === 0) {
|
||||
const existingCount = await VocabGrammarExercise.count({ where: { lessonId: lesson.id } });
|
||||
if (existingCount > 0) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Script zum Erstellen eines vollständigen 4-Wochen Bisaya-Kurses
|
||||
* Script zum Erstellen eines vollständigen 6-Wochen Bisaya-Kurses
|
||||
*
|
||||
* Verwendung:
|
||||
* node backend/scripts/create-bisaya-course.js <languageId> <ownerHashedId>
|
||||
@@ -11,6 +11,10 @@ import VocabCourse from '../models/community/vocab_course.js';
|
||||
import VocabCourseLesson from '../models/community/vocab_course_lesson.js';
|
||||
import User from '../models/community/user.js';
|
||||
import crypto from 'crypto';
|
||||
import { getBisayaLessonPedagogy } from './bisaya-course-phase2-pedagogy.js';
|
||||
import { BISAYA_PHASE3_DIDACTICS, BISAYA_PHASE3_LESSONS } from './bisaya-course-phase3-extension.js';
|
||||
import { BISAYA_PHASE4_DIDACTICS, BISAYA_PHASE4_LESSONS } from './bisaya-course-phase4-extension.js';
|
||||
import { BISAYA_PHASE5_DIDACTICS, BISAYA_PHASE5_LESSONS } from './bisaya-course-phase5-extension.js';
|
||||
|
||||
const LESSON_DIDACTICS = {
|
||||
'Begrüßungen & Höflichkeit': {
|
||||
@@ -192,7 +196,10 @@ const LESSON_DIDACTICS = {
|
||||
'Lami',
|
||||
'Mingaw ko nimo'
|
||||
]
|
||||
}
|
||||
},
|
||||
...BISAYA_PHASE3_DIDACTICS,
|
||||
...BISAYA_PHASE4_DIDACTICS,
|
||||
...BISAYA_PHASE5_DIDACTICS
|
||||
};
|
||||
|
||||
const LESSONS = [
|
||||
@@ -398,7 +405,11 @@ const LESSONS = [
|
||||
{ week: 4, day: 5, num: 40, type: 'culture', title: 'Kulturelle Tipps & Tricks',
|
||||
desc: 'Wichtige kulturelle Hinweise für den Alltag',
|
||||
targetMin: 15, targetScore: 0, review: false,
|
||||
cultural: 'Kulturelles Verständnis ist genauso wichtig wie die Sprache selbst.' }
|
||||
cultural: 'Kulturelles Verständnis ist genauso wichtig wie die Sprache selbst.' },
|
||||
|
||||
...BISAYA_PHASE3_LESSONS,
|
||||
...BISAYA_PHASE4_LESSONS,
|
||||
...BISAYA_PHASE5_LESSONS
|
||||
];
|
||||
|
||||
async function createBisayaCourse(languageId, ownerHashedId) {
|
||||
@@ -422,8 +433,8 @@ async function createBisayaCourse(languageId, ownerHashedId) {
|
||||
const shareCode = crypto.randomBytes(8).toString('hex');
|
||||
const course = await VocabCourse.create({
|
||||
ownerUserId: user.id,
|
||||
title: 'Bisaya für Familien - Schnellstart in 4 Wochen',
|
||||
description: 'Lerne Bisaya (Cebuano) schnell und praktisch für den Familienalltag. Fokus auf Sprechen & Hören mit strukturiertem 4-Wochen-Plan.',
|
||||
title: 'Bisaya für Familien - Alltag & Stabilisierung',
|
||||
description: 'Lerne Bisaya (Cebuano) praxisnah für den Familienalltag. Der Pfad verbindet Schnellstart, Alltagsmodule und Stabilisierungsblöcke mit Spiralwiederholung, Fehlertraining und freier Produktion.',
|
||||
languageId: Number(languageId),
|
||||
difficultyLevel: 1,
|
||||
isPublic: true,
|
||||
@@ -435,6 +446,7 @@ async function createBisayaCourse(languageId, ownerHashedId) {
|
||||
|
||||
// Erstelle Lektionen
|
||||
for (const lessonData of LESSONS) {
|
||||
const pedagogy = getBisayaLessonPedagogy(lessonData.num) || {};
|
||||
const lesson = await VocabCourseLesson.create({
|
||||
courseId: course.id,
|
||||
chapterId: null, // Wird später mit Vokabeln verknüpft
|
||||
@@ -452,7 +464,14 @@ async function createBisayaCourse(languageId, ownerHashedId) {
|
||||
practicalTasks: LESSON_DIDACTICS[lessonData.title]?.practicalTasks || [],
|
||||
targetMinutes: lessonData.targetMin,
|
||||
targetScorePercent: lessonData.targetScore,
|
||||
requiresReview: lessonData.review
|
||||
requiresReview: lessonData.review,
|
||||
didacticMode: pedagogy.didacticMode || null,
|
||||
phaseLabel: pedagogy.phaseLabel || null,
|
||||
blockNumber: pedagogy.blockNumber ?? null,
|
||||
difficultyWeight: pedagogy.difficultyWeight ?? null,
|
||||
newUnitTarget: pedagogy.newUnitTarget ?? null,
|
||||
reviewWeight: pedagogy.reviewWeight ?? null,
|
||||
isIntensiveReview: Boolean(pedagogy.isIntensiveReview)
|
||||
});
|
||||
console.log(` ✅ Lektion ${lessonData.num}: ${lessonData.title} (Woche ${lessonData.week}, Tag ${lessonData.day})`);
|
||||
}
|
||||
@@ -464,7 +483,7 @@ async function createBisayaCourse(languageId, ownerHashedId) {
|
||||
console.log(` - Konversations-Lektionen: ${LESSONS.filter(l => l.type === 'conversation').length}`);
|
||||
console.log(` - Grammatik-Lektionen: ${LESSONS.filter(l => l.type === 'grammar').length}`);
|
||||
console.log(` - Wiederholungs-Lektionen: ${LESSONS.filter(l => l.type === 'review').length}`);
|
||||
console.log(` - Durchschnittliche Zeit pro Tag: ~${Math.round(LESSONS.reduce((sum, l) => sum + l.targetMin, 0) / (4 * 5))} Minuten`);
|
||||
console.log(` - Durchschnittliche Zeit pro Tag: ~${Math.round(LESSONS.reduce((sum, l) => sum + l.targetMin, 0) / (15 * 5))} Minuten`);
|
||||
console.log(`\n💡 Nächste Schritte:`);
|
||||
console.log(` 1. Füge Vokabeln zu den Vokabel-Lektionen hinzu`);
|
||||
console.log(` 2. Erstelle Grammatik-Übungen für die Grammatik-Lektionen`);
|
||||
|
||||
102
backend/scripts/extend-bisaya-course-phase3.js
Normal file
102
backend/scripts/extend-bisaya-course-phase3.js
Normal 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);
|
||||
});
|
||||
102
backend/scripts/extend-bisaya-course-phase4.js
Normal file
102
backend/scripts/extend-bisaya-course-phase4.js
Normal 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);
|
||||
});
|
||||
102
backend/scripts/extend-bisaya-course-phase5.js
Normal file
102
backend/scripts/extend-bisaya-course-phase5.js
Normal 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);
|
||||
});
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
import { sequelize } from '../utils/sequelize.js';
|
||||
import VocabCourseLesson from '../models/community/vocab_course_lesson.js';
|
||||
import { getBisayaLessonPedagogy } from './bisaya-course-phase2-pedagogy.js';
|
||||
|
||||
const LESSON_DIDACTICS = {
|
||||
'Begrüßungen & Höflichkeit': {
|
||||
@@ -129,14 +130,23 @@ async function updateBisayaDidactics() {
|
||||
for (const row of lessons) {
|
||||
const lesson = await VocabCourseLesson.findByPk(row.id);
|
||||
const didactics = LESSON_DIDACTICS[lesson.title];
|
||||
if (!didactics) continue;
|
||||
const pedagogy = getBisayaLessonPedagogy(lesson.lessonNumber);
|
||||
if (!didactics && !pedagogy) continue;
|
||||
const normalizedDidactics = didactics || {};
|
||||
|
||||
await lesson.update({
|
||||
learningGoals: didactics.learningGoals || [],
|
||||
corePatterns: didactics.corePatterns || [],
|
||||
grammarFocus: didactics.grammarFocus || [],
|
||||
speakingPrompts: didactics.speakingPrompts || [],
|
||||
practicalTasks: didactics.practicalTasks || []
|
||||
learningGoals: normalizedDidactics.learningGoals || [],
|
||||
corePatterns: normalizedDidactics.corePatterns || [],
|
||||
grammarFocus: normalizedDidactics.grammarFocus || [],
|
||||
speakingPrompts: normalizedDidactics.speakingPrompts || [],
|
||||
practicalTasks: normalizedDidactics.practicalTasks || [],
|
||||
didacticMode: pedagogy?.didacticMode || null,
|
||||
phaseLabel: pedagogy?.phaseLabel || null,
|
||||
blockNumber: pedagogy?.blockNumber ?? null,
|
||||
difficultyWeight: pedagogy?.difficultyWeight ?? null,
|
||||
newUnitTarget: pedagogy?.newUnitTarget ?? null,
|
||||
reviewWeight: pedagogy?.reviewWeight ?? null,
|
||||
isIntensiveReview: Boolean(pedagogy?.isIntensiveReview)
|
||||
});
|
||||
updated++;
|
||||
console.log(`✅ Didaktik aktualisiert: Lektion ${lesson.lessonNumber} - ${lesson.title}`);
|
||||
|
||||
@@ -29,6 +29,9 @@ import EroticVideo from '../models/community/erotic_video.js';
|
||||
import EroticContentReport from '../models/community/erotic_content_report.js';
|
||||
import TitleOfNobility from "../models/falukant/type/title_of_nobility.js";
|
||||
import ChildRelation from "../models/falukant/data/child_relation.js";
|
||||
import Relationship from "../models/falukant/data/relationship.js";
|
||||
import RelationshipType from "../models/falukant/type/relationship.js";
|
||||
import RelationshipState from "../models/falukant/data/relationship_state.js";
|
||||
import { sequelize } from '../utils/sequelize.js';
|
||||
import npcCreationJobService from './npcCreationJobService.js';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
@@ -668,7 +671,7 @@ class AdminService {
|
||||
required: true,
|
||||
attributes: ['money', 'certificate', 'id'],
|
||||
include: [{
|
||||
model: FalukantCharacter,
|
||||
model: FalukantCharacter.unscoped(),
|
||||
as: 'character',
|
||||
include: [{
|
||||
model: FalukantPredefineFirstname,
|
||||
@@ -945,10 +948,85 @@ class AdminService {
|
||||
await character.save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ehepartner/Verlobte/Liebhaber eines Charakters (für Admin-Auswahl Vater).
|
||||
*/
|
||||
async adminGetPotentialFathersForCharacter(userId, motherCharacterId) {
|
||||
if (!(await this.hasUserAccess(userId, 'falukantusers'))) {
|
||||
throw new Error('noaccess');
|
||||
}
|
||||
const mid = Number(motherCharacterId);
|
||||
if (!Number.isFinite(mid)) {
|
||||
throw new Error('invalidCharacter');
|
||||
}
|
||||
const mother = await FalukantCharacter.findByPk(mid, { attributes: ['id'] });
|
||||
if (!mother) {
|
||||
throw new Error('notfound');
|
||||
}
|
||||
|
||||
const rels = await Relationship.findAll({
|
||||
where: {
|
||||
[Op.or]: [
|
||||
{ character1Id: mid },
|
||||
{ character2Id: mid }
|
||||
]
|
||||
},
|
||||
include: [
|
||||
{ model: RelationshipType, as: 'relationshipType', attributes: ['tr'] },
|
||||
{ model: RelationshipState, as: 'state', required: false },
|
||||
{
|
||||
model: FalukantCharacter,
|
||||
as: 'character1',
|
||||
attributes: ['id'],
|
||||
required: true,
|
||||
include: [{ model: FalukantPredefineFirstname, as: 'definedFirstName', attributes: ['name'] }]
|
||||
},
|
||||
{
|
||||
model: FalukantCharacter,
|
||||
as: 'character2',
|
||||
attributes: ['id'],
|
||||
required: true,
|
||||
include: [{ model: FalukantPredefineFirstname, as: 'definedFirstName', attributes: ['name'] }]
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const allowedTypes = new Set(['married', 'engaged', 'lover']);
|
||||
const typeOrder = { married: 0, engaged: 1, lover: 2 };
|
||||
const seen = new Set();
|
||||
const options = [];
|
||||
|
||||
for (const rel of rels) {
|
||||
const typeTr = rel.relationshipType?.tr;
|
||||
if (!typeTr || !allowedTypes.has(typeTr)) {
|
||||
continue;
|
||||
}
|
||||
if (typeTr === 'lover' && rel.state && rel.state.active === false) {
|
||||
continue;
|
||||
}
|
||||
const partnerId = rel.character1Id === mid ? rel.character2Id : rel.character1Id;
|
||||
if (partnerId === mid || seen.has(partnerId)) {
|
||||
continue;
|
||||
}
|
||||
seen.add(partnerId);
|
||||
const partner = rel.character1Id === mid ? rel.character2 : rel.character1;
|
||||
const displayName = partner?.definedFirstName?.name?.trim() || `Charakter #${partnerId}`;
|
||||
options.push({
|
||||
characterId: partnerId,
|
||||
displayName,
|
||||
relationshipType: typeTr
|
||||
});
|
||||
}
|
||||
|
||||
options.sort((a, b) => (typeOrder[a.relationshipType] ?? 9) - (typeOrder[b.relationshipType] ?? 9));
|
||||
|
||||
return { options };
|
||||
}
|
||||
|
||||
/**
|
||||
* Admin: Charakter als schwanger markieren (erwarteter Geburtstermin).
|
||||
* @param {number} fatherCharacterId - optional; Vater-Charakter-ID
|
||||
* @param {number} dueInDays - Tage bis zur „Geburt“ (Default 21)
|
||||
* @param {number} dueInDays - Tage bis zum Termin (0 = heute; Default 21)
|
||||
*/
|
||||
async adminForceFalukantPregnancy(userId, characterId, { fatherCharacterId = null, dueInDays = 21 } = {}) {
|
||||
if (!(await this.hasUserAccess(userId, 'falukantusers'))) {
|
||||
@@ -960,7 +1038,16 @@ class AdminService {
|
||||
const father = await FalukantCharacter.findByPk(Number(fatherCharacterId));
|
||||
if (!father) throw new Error('fatherNotFound');
|
||||
}
|
||||
const days = Math.max(1, Math.min(365, Number(dueInDays) || 21));
|
||||
let rawDays = dueInDays;
|
||||
if (rawDays === undefined || rawDays === null || rawDays === '') {
|
||||
rawDays = 21;
|
||||
} else {
|
||||
rawDays = Number(rawDays);
|
||||
if (!Number.isFinite(rawDays)) {
|
||||
rawDays = 21;
|
||||
}
|
||||
}
|
||||
const days = Math.max(0, Math.min(365, rawDays));
|
||||
const due = new Date();
|
||||
due.setDate(due.getDate() + days);
|
||||
await mother.update({
|
||||
|
||||
@@ -188,6 +188,149 @@ export default class VocabService {
|
||||
return [];
|
||||
}
|
||||
|
||||
_normalizeOptionalInteger(value) {
|
||||
if (value === undefined || value === null || value === '') {
|
||||
return null;
|
||||
}
|
||||
const parsed = Number(value);
|
||||
return Number.isFinite(parsed) ? parsed : null;
|
||||
}
|
||||
|
||||
_normalizeOptionalString(value) {
|
||||
if (value === undefined || value === null) {
|
||||
return null;
|
||||
}
|
||||
const trimmed = String(value).trim();
|
||||
return trimmed || null;
|
||||
}
|
||||
|
||||
_inferLessonPhaseLabel(plainLesson) {
|
||||
if (plainLesson.phaseLabel) {
|
||||
return plainLesson.phaseLabel;
|
||||
}
|
||||
const weekNumber = Number(plainLesson.weekNumber) || 0;
|
||||
if (weekNumber > 0 && weekNumber <= 2) {
|
||||
return 'quickstart';
|
||||
}
|
||||
if (weekNumber === 3) {
|
||||
return 'daily_life';
|
||||
}
|
||||
if (weekNumber >= 4) {
|
||||
return 'stabilization';
|
||||
}
|
||||
return 'quickstart';
|
||||
}
|
||||
|
||||
_inferLessonDidacticMode(plainLesson) {
|
||||
if (plainLesson.didacticMode) {
|
||||
return plainLesson.didacticMode;
|
||||
}
|
||||
const lessonType = String(plainLesson.lessonType || '').toLowerCase();
|
||||
const title = String(plainLesson.title || '').toLowerCase();
|
||||
if (title.includes('abschluss') || title.includes('prüfung') || title.includes('test')) {
|
||||
return 'checkpoint';
|
||||
}
|
||||
if (plainLesson.isIntensiveReview || lessonType === 'review' || lessonType === 'vocab_review' || title.includes('wiederholung')) {
|
||||
return 'intensive_review';
|
||||
}
|
||||
if (lessonType === 'grammar') {
|
||||
return 'pattern_drill';
|
||||
}
|
||||
if (lessonType === 'conversation' || lessonType === 'dialogue' || lessonType === 'phrases' || lessonType === 'survival') {
|
||||
return 'guided_dialogue';
|
||||
}
|
||||
if (lessonType === 'culture') {
|
||||
return 'real_life_scenario';
|
||||
}
|
||||
return 'core_input';
|
||||
}
|
||||
|
||||
_inferLessonDifficultyWeight(plainLesson, didacticMode) {
|
||||
if (plainLesson.difficultyWeight != null) {
|
||||
return plainLesson.difficultyWeight;
|
||||
}
|
||||
switch (didacticMode) {
|
||||
case 'pattern_drill':
|
||||
return 3;
|
||||
case 'guided_dialogue':
|
||||
case 'real_life_scenario':
|
||||
return 2;
|
||||
case 'intensive_review':
|
||||
case 'checkpoint':
|
||||
return 2;
|
||||
default:
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
_inferLessonNewUnitTarget(plainLesson, didacticMode) {
|
||||
if (plainLesson.newUnitTarget != null) {
|
||||
return plainLesson.newUnitTarget;
|
||||
}
|
||||
switch (didacticMode) {
|
||||
case 'core_input':
|
||||
return 8;
|
||||
case 'guided_dialogue':
|
||||
return 5;
|
||||
case 'pattern_drill':
|
||||
return 4;
|
||||
case 'real_life_scenario':
|
||||
return 3;
|
||||
case 'checkpoint':
|
||||
return 2;
|
||||
case 'intensive_review':
|
||||
return 1;
|
||||
default:
|
||||
return 4;
|
||||
}
|
||||
}
|
||||
|
||||
_inferLessonReviewWeight(plainLesson, didacticMode) {
|
||||
if (plainLesson.reviewWeight != null) {
|
||||
return plainLesson.reviewWeight;
|
||||
}
|
||||
switch (didacticMode) {
|
||||
case 'intensive_review':
|
||||
return 90;
|
||||
case 'checkpoint':
|
||||
return 70;
|
||||
case 'pattern_drill':
|
||||
return 55;
|
||||
case 'real_life_scenario':
|
||||
return 45;
|
||||
case 'guided_dialogue':
|
||||
return 40;
|
||||
default:
|
||||
return 30;
|
||||
}
|
||||
}
|
||||
|
||||
_inferLessonBlockNumber(plainLesson) {
|
||||
if (plainLesson.blockNumber != null) {
|
||||
return plainLesson.blockNumber;
|
||||
}
|
||||
const weekNumber = Number(plainLesson.weekNumber) || 1;
|
||||
return Math.max(1, Math.ceil(weekNumber / 2));
|
||||
}
|
||||
|
||||
_buildLessonPedagogy(plainLesson) {
|
||||
const didacticMode = this._inferLessonDidacticMode(plainLesson);
|
||||
const phaseLabel = this._inferLessonPhaseLabel(plainLesson);
|
||||
const isIntensiveReview = plainLesson.isIntensiveReview != null
|
||||
? Boolean(plainLesson.isIntensiveReview)
|
||||
: didacticMode === 'intensive_review';
|
||||
|
||||
return {
|
||||
didacticMode,
|
||||
phaseLabel,
|
||||
blockNumber: this._inferLessonBlockNumber(plainLesson),
|
||||
difficultyWeight: this._inferLessonDifficultyWeight(plainLesson, didacticMode),
|
||||
newUnitTarget: this._inferLessonNewUnitTarget(plainLesson, didacticMode),
|
||||
reviewWeight: this._inferLessonReviewWeight(plainLesson, didacticMode),
|
||||
isIntensiveReview
|
||||
};
|
||||
}
|
||||
|
||||
_buildLessonDidactics(plainLesson) {
|
||||
const grammarExercises = Array.isArray(plainLesson.grammarExercises) ? plainLesson.grammarExercises : [];
|
||||
const grammarExplanations = [];
|
||||
@@ -1020,6 +1163,11 @@ export default class VocabService {
|
||||
return a.lessonNumber - b.lessonNumber;
|
||||
});
|
||||
|
||||
courseData.lessons = courseData.lessons.map((lesson) => ({
|
||||
...lesson,
|
||||
pedagogy: this._buildLessonPedagogy(lesson)
|
||||
}));
|
||||
|
||||
return courseData;
|
||||
}
|
||||
|
||||
@@ -1129,6 +1277,7 @@ export default class VocabService {
|
||||
}
|
||||
|
||||
plainLesson.didactics = this._buildLessonDidactics(plainLesson);
|
||||
plainLesson.pedagogy = this._buildLessonPedagogy(plainLesson);
|
||||
return plainLesson;
|
||||
}
|
||||
|
||||
@@ -1301,7 +1450,7 @@ export default class VocabService {
|
||||
return exercises.map(e => e.get({ plain: true }));
|
||||
}
|
||||
|
||||
async addLessonToCourse(hashedUserId, courseId, { chapterId, lessonNumber, title, description, weekNumber, dayNumber, lessonType, audioUrl, culturalNotes, learningGoals, corePatterns, grammarFocus, speakingPrompts, practicalTasks, targetMinutes, targetScorePercent, requiresReview }) {
|
||||
async addLessonToCourse(hashedUserId, courseId, { chapterId, lessonNumber, title, description, weekNumber, dayNumber, lessonType, didacticMode, phaseLabel, blockNumber, difficultyWeight, newUnitTarget, reviewWeight, isIntensiveReview, audioUrl, culturalNotes, learningGoals, corePatterns, grammarFocus, speakingPrompts, practicalTasks, targetMinutes, targetScorePercent, requiresReview }) {
|
||||
const user = await this._getUserByHashedId(hashedUserId);
|
||||
const course = await VocabCourse.findByPk(courseId);
|
||||
|
||||
@@ -1343,6 +1492,13 @@ export default class VocabService {
|
||||
weekNumber: weekNumber ? Number(weekNumber) : null,
|
||||
dayNumber: dayNumber ? Number(dayNumber) : null,
|
||||
lessonType: lessonType || 'vocab',
|
||||
didacticMode: this._normalizeOptionalString(didacticMode),
|
||||
phaseLabel: this._normalizeOptionalString(phaseLabel),
|
||||
blockNumber: this._normalizeOptionalInteger(blockNumber),
|
||||
difficultyWeight: this._normalizeOptionalInteger(difficultyWeight),
|
||||
newUnitTarget: this._normalizeOptionalInteger(newUnitTarget),
|
||||
reviewWeight: this._normalizeOptionalInteger(reviewWeight),
|
||||
isIntensiveReview: isIntensiveReview !== undefined ? Boolean(isIntensiveReview) : false,
|
||||
audioUrl: audioUrl || null,
|
||||
culturalNotes: culturalNotes || null,
|
||||
learningGoals: this._normalizeStringList(learningGoals),
|
||||
@@ -1358,7 +1514,7 @@ export default class VocabService {
|
||||
return lesson.get({ plain: true });
|
||||
}
|
||||
|
||||
async updateLesson(hashedUserId, lessonId, { title, description, lessonNumber, weekNumber, dayNumber, lessonType, audioUrl, culturalNotes, learningGoals, corePatterns, grammarFocus, speakingPrompts, practicalTasks, targetMinutes, targetScorePercent, requiresReview }) {
|
||||
async updateLesson(hashedUserId, lessonId, { title, description, lessonNumber, weekNumber, dayNumber, lessonType, didacticMode, phaseLabel, blockNumber, difficultyWeight, newUnitTarget, reviewWeight, isIntensiveReview, audioUrl, culturalNotes, learningGoals, corePatterns, grammarFocus, speakingPrompts, practicalTasks, targetMinutes, targetScorePercent, requiresReview }) {
|
||||
const user = await this._getUserByHashedId(hashedUserId);
|
||||
const lesson = await VocabCourseLesson.findByPk(lessonId, {
|
||||
include: [{ model: VocabCourse, as: 'course' }]
|
||||
@@ -1383,6 +1539,13 @@ export default class VocabService {
|
||||
if (weekNumber !== undefined) updates.weekNumber = weekNumber ? Number(weekNumber) : null;
|
||||
if (dayNumber !== undefined) updates.dayNumber = dayNumber ? Number(dayNumber) : null;
|
||||
if (lessonType !== undefined) updates.lessonType = lessonType;
|
||||
if (didacticMode !== undefined) updates.didacticMode = this._normalizeOptionalString(didacticMode);
|
||||
if (phaseLabel !== undefined) updates.phaseLabel = this._normalizeOptionalString(phaseLabel);
|
||||
if (blockNumber !== undefined) updates.blockNumber = this._normalizeOptionalInteger(blockNumber);
|
||||
if (difficultyWeight !== undefined) updates.difficultyWeight = this._normalizeOptionalInteger(difficultyWeight);
|
||||
if (newUnitTarget !== undefined) updates.newUnitTarget = this._normalizeOptionalInteger(newUnitTarget);
|
||||
if (reviewWeight !== undefined) updates.reviewWeight = this._normalizeOptionalInteger(reviewWeight);
|
||||
if (isIntensiveReview !== undefined) updates.isIntensiveReview = Boolean(isIntensiveReview);
|
||||
if (audioUrl !== undefined) updates.audioUrl = audioUrl;
|
||||
if (culturalNotes !== undefined) updates.culturalNotes = culturalNotes;
|
||||
if (learningGoals !== undefined) updates.learningGoals = this._normalizeStringList(learningGoals);
|
||||
|
||||
23
backend/sql/add_vocab_lesson_phase1_fields.sql
Normal file
23
backend/sql/add_vocab_lesson_phase1_fields.sql
Normal 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.';
|
||||
147
backend/sql/backfill_bisaya_phase2_pedagogy.sql
Normal file
147
backend/sql/backfill_bisaya_phase2_pedagogy.sql
Normal 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;
|
||||
12
backend/sql/extend_bisaya_course_phase3_to_6_weeks.sql
Normal file
12
backend/sql/extend_bisaya_course_phase3_to_6_weeks.sql
Normal 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.
|
||||
11
backend/sql/extend_bisaya_course_phase4_to_3_months.sql
Normal file
11
backend/sql/extend_bisaya_course_phase4_to_3_months.sql
Normal 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.
|
||||
11
backend/sql/extend_bisaya_course_phase5_to_stabilization.sql
Normal file
11
backend/sql/extend_bisaya_course_phase5_to_stabilization.sql
Normal 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.
|
||||
1038
docs/BISAYA_COURSE_EXPANSION_IMPLEMENTATION_SPEC.md
Normal file
1038
docs/BISAYA_COURSE_EXPANSION_IMPLEMENTATION_SPEC.md
Normal file
File diff suppressed because it is too large
Load Diff
@@ -176,17 +176,33 @@
|
||||
"statusActive": "Schwanger bis",
|
||||
"statusNone": "Nicht schwanger",
|
||||
"fatherId": "Vater-Charakter-ID (optional)",
|
||||
"fatherSelect": "Vater (Ehepartner / Verlobter / Liebhaber)",
|
||||
"fatherNone": "— kein Vater gespeichert —",
|
||||
"fatherHintList": "Liste aus Beziehungen dieses Charakters (Ehe, Verlobung, aktive Liebschaft).",
|
||||
"fatherHintManual": "Kein passender Partner in der Datenbank: Vater-Charakter-ID manuell eintragen.",
|
||||
"fatherManualPlaceholder": "Charakter-ID",
|
||||
"dueDays": "Tage bis zum Termin",
|
||||
"dueDaysHint": "0 = Termin heute (Geburt kann je nach Spiel-Logik zeitnah anstehen).",
|
||||
"force": "Schwangerschaft setzen",
|
||||
"clear": "Schwangerschaft entfernen",
|
||||
"successForce": "Schwangerschaft wurde gesetzt.",
|
||||
"successClear": "Schwangerschaft wurde entfernt.",
|
||||
"error": "Aktion fehlgeschlagen."
|
||||
"error": "Aktion fehlgeschlagen.",
|
||||
"relationship": {
|
||||
"married": "Ehepartner",
|
||||
"engaged": "Verlobter",
|
||||
"lover": "Liebhaber"
|
||||
}
|
||||
},
|
||||
"birth": {
|
||||
"title": "Geburt erzwingen (Admin)",
|
||||
"motherHint": "Es wird der oben genannte Charakter (Mutter) verwendet.",
|
||||
"fatherId": "Vater-Charakter-ID",
|
||||
"fatherSelect": "Vater (Ehepartner / Verlobter / Liebhaber)",
|
||||
"fatherChoose": "— Vater wählen —",
|
||||
"fatherHintList": "Liste aus Beziehungen dieses Charakters.",
|
||||
"fatherHintManual": "Kein Partner in der Liste: Vater-Charakter-ID manuell eintragen.",
|
||||
"fatherRequired": "Bitte einen Vater auswählen oder die Charakter-ID angeben.",
|
||||
"context": "Kontext",
|
||||
"contextMarriage": "Ehe",
|
||||
"contextLover": "Liebschaft",
|
||||
|
||||
@@ -231,17 +231,33 @@
|
||||
"statusActive": "Expecting until",
|
||||
"statusNone": "Not pregnant",
|
||||
"fatherId": "Father character ID (optional)",
|
||||
"fatherSelect": "Father (spouse / fiancé(e) / lover)",
|
||||
"fatherNone": "— no father stored —",
|
||||
"fatherHintList": "From this character’s relationships (marriage, engagement, active affair).",
|
||||
"fatherHintManual": "No matching partner in the database: enter the father’s character ID manually.",
|
||||
"fatherManualPlaceholder": "Character ID",
|
||||
"dueDays": "Days until due date",
|
||||
"dueDaysHint": "0 = due today (birth may follow depending on game logic).",
|
||||
"force": "Set pregnancy",
|
||||
"clear": "Clear pregnancy",
|
||||
"successForce": "Pregnancy has been set.",
|
||||
"successClear": "Pregnancy has been cleared.",
|
||||
"error": "Action failed."
|
||||
"error": "Action failed.",
|
||||
"relationship": {
|
||||
"married": "Spouse",
|
||||
"engaged": "Engaged partner",
|
||||
"lover": "Lover"
|
||||
}
|
||||
},
|
||||
"birth": {
|
||||
"title": "Force birth (admin)",
|
||||
"motherHint": "The character listed above is used as the mother.",
|
||||
"fatherId": "Father character ID",
|
||||
"fatherSelect": "Father (spouse / fiancé(e) / lover)",
|
||||
"fatherChoose": "— choose father —",
|
||||
"fatherHintList": "From this character’s relationships.",
|
||||
"fatherHintManual": "No partner in the list: enter the father’s character ID manually.",
|
||||
"fatherRequired": "Please select a father or enter the character ID.",
|
||||
"context": "Context",
|
||||
"contextMarriage": "Marriage",
|
||||
"contextLover": "Affair",
|
||||
|
||||
@@ -176,17 +176,33 @@
|
||||
"statusActive": "Embarazo hasta",
|
||||
"statusNone": "No embarazada",
|
||||
"fatherId": "ID del padre (opcional)",
|
||||
"fatherSelect": "Padre (cónyuge / prometido / amante)",
|
||||
"fatherNone": "— sin padre guardado —",
|
||||
"fatherHintList": "Según las relaciones de este personaje (matrimonio, prometido, amante activo).",
|
||||
"fatherHintManual": "Sin pareja adecuada en la base de datos: introduce manualmente el ID del padre.",
|
||||
"fatherManualPlaceholder": "ID de personaje",
|
||||
"dueDays": "Días hasta el parto previsto",
|
||||
"dueDaysHint": "0 = parto previsto hoy (el nacimiento puede seguir según la lógica del juego).",
|
||||
"force": "Establecer embarazo",
|
||||
"clear": "Quitar embarazo",
|
||||
"successForce": "Embarazo establecido.",
|
||||
"successClear": "Embarazo eliminado.",
|
||||
"error": "La acción ha fallado."
|
||||
"error": "La acción ha fallado.",
|
||||
"relationship": {
|
||||
"married": "Cónyuge",
|
||||
"engaged": "Prometido",
|
||||
"lover": "Amante"
|
||||
}
|
||||
},
|
||||
"birth": {
|
||||
"title": "Forzar nacimiento (admin)",
|
||||
"motherHint": "Se usa el personaje indicado arriba como madre.",
|
||||
"fatherId": "ID del padre",
|
||||
"fatherSelect": "Padre (cónyuge / prometido / amante)",
|
||||
"fatherChoose": "— elegir padre —",
|
||||
"fatherHintList": "Según las relaciones de este personaje.",
|
||||
"fatherHintManual": "Sin pareja en la lista: introduce el ID del padre manualmente.",
|
||||
"fatherRequired": "Elige un padre o introduce el ID de personaje.",
|
||||
"context": "Contexto",
|
||||
"contextMarriage": "Matrimonio",
|
||||
"contextLover": "Amante",
|
||||
|
||||
@@ -54,13 +54,28 @@
|
||||
<template v-else>{{ $t('admin.falukant.edituser.pregnancy.statusNone') }}</template>
|
||||
</p>
|
||||
<label class="form-field">
|
||||
{{ $t('admin.falukant.edituser.pregnancy.fatherId') }}
|
||||
<input type="number" v-model.number="adminPregnancyFatherId" min="1" placeholder="—" />
|
||||
{{ $t('admin.falukant.edituser.pregnancy.fatherSelect') }}
|
||||
<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>
|
||||
<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">
|
||||
{{ $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>
|
||||
<p class="admin-family-tools__hint">{{ $t('admin.falukant.edituser.pregnancy.dueDaysHint') }}</p>
|
||||
<div class="action-buttons">
|
||||
<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>
|
||||
@@ -69,9 +84,24 @@
|
||||
<h4>{{ $t('admin.falukant.edituser.birth.title') }}</h4>
|
||||
<p class="admin-family-tools__hint">{{ $t('admin.falukant.edituser.birth.motherHint') }}</p>
|
||||
<label class="form-field">
|
||||
{{ $t('admin.falukant.edituser.birth.fatherId') }} *
|
||||
<input type="number" v-model.number="adminBirthFatherId" min="1" required />
|
||||
{{ $t('admin.falukant.edituser.birth.fatherSelect') }} *
|
||||
<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>
|
||||
<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">
|
||||
{{ $t('admin.falukant.edituser.birth.context') }}
|
||||
<select v-model="adminBirthContext">
|
||||
@@ -220,9 +250,12 @@ export default {
|
||||
branches: false,
|
||||
stockTypes: false
|
||||
},
|
||||
adminPregnancyFatherId: null,
|
||||
potentialFathers: [],
|
||||
adminPregnancyFatherSelect: '',
|
||||
adminPregnancyFatherManualId: null,
|
||||
adminDueInDays: 21,
|
||||
adminBirthFatherId: null,
|
||||
adminBirthFatherSelect: '',
|
||||
adminBirthFatherManualId: null,
|
||||
adminBirthContext: 'marriage',
|
||||
adminBirthLegitimacy: 'legitimate',
|
||||
adminBirthGender: ''
|
||||
@@ -300,6 +333,12 @@ export default {
|
||||
this.originalAge = this.age;
|
||||
this.users = [];
|
||||
this.activeTab = 'userdata';
|
||||
this.adminPregnancyFatherSelect = '';
|
||||
this.adminPregnancyFatherManualId = null;
|
||||
this.adminBirthFatherSelect = '';
|
||||
this.adminBirthFatherManualId = null;
|
||||
await this.loadPotentialFathers();
|
||||
this.syncFatherSelectionsFromCharacter();
|
||||
},
|
||||
async saveUser() {
|
||||
const dataToChange = {
|
||||
@@ -432,11 +471,61 @@ export default {
|
||||
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.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() {
|
||||
const characterId = this.editableUser.falukantData[0].character.id;
|
||||
const payload = { characterId, dueInDays: Number(this.adminDueInDays) || 21 };
|
||||
if (this.adminPregnancyFatherId) payload.fatherCharacterId = Number(this.adminPregnancyFatherId);
|
||||
const payload = { characterId, dueInDays: this.resolveDueInDays() };
|
||||
const fatherId = this.resolvePregnancyFatherCharacterId();
|
||||
if (fatherId != null) payload.fatherCharacterId = fatherId;
|
||||
try {
|
||||
await apiClient.post('/api/admin/falukant/character/force-pregnancy', payload);
|
||||
showSuccess(this, 'tr:admin.falukant.edituser.pregnancy.successForce');
|
||||
@@ -457,13 +546,14 @@ export default {
|
||||
},
|
||||
async adminForceBirth() {
|
||||
const motherCharacterId = this.editableUser.falukantData[0].character.id;
|
||||
if (!this.adminBirthFatherId) {
|
||||
showError(this, this.$t('admin.falukant.edituser.birth.fatherId'));
|
||||
const fatherCharacterId = this.resolveBirthFatherCharacterId();
|
||||
if (fatherCharacterId == null || Number.isNaN(fatherCharacterId)) {
|
||||
showError(this, this.$t('admin.falukant.edituser.birth.fatherRequired'));
|
||||
return;
|
||||
}
|
||||
const body = {
|
||||
motherCharacterId,
|
||||
fatherCharacterId: Number(this.adminBirthFatherId),
|
||||
fatherCharacterId,
|
||||
birthContext: this.adminBirthContext,
|
||||
legitimacy: this.adminBirthLegitimacy
|
||||
};
|
||||
|
||||
@@ -73,6 +73,12 @@
|
||||
<span class="title-label">{{ lesson.title }}</span>
|
||||
<span v-if="lesson.description" class="lesson-description">{{ lesson.description }}</span>
|
||||
</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">
|
||||
<button
|
||||
@click="openLesson(lesson.id)"
|
||||
@@ -328,6 +334,36 @@ export default {
|
||||
openLanguageAssistantSettings() {
|
||||
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() {
|
||||
showInfo(this, 'Die Bearbeitung einzelner Lektionen folgt noch.');
|
||||
}
|
||||
@@ -517,6 +553,42 @@ export default {
|
||||
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 {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -43,6 +43,14 @@
|
||||
<span class="lesson-meta-label">{{ $t('socialnetwork.vocab.courses.lessonTypeLabel') }}</span>
|
||||
<strong>{{ getLessonTypeLabel(lesson.lessonType) }}</strong>
|
||||
</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">
|
||||
<span class="lesson-meta-label">{{ $t('socialnetwork.vocab.courses.recommendedDuration') }}</span>
|
||||
<strong>{{ formatTargetMinutes(lesson.targetMinutes) }}</strong>
|
||||
@@ -51,9 +59,22 @@
|
||||
<span class="lesson-meta-label">{{ $t('socialnetwork.vocab.courses.exerciseLoad') }}</span>
|
||||
<strong>{{ effectiveExercises?.length || 0 }} {{ $t('socialnetwork.vocab.courses.exercisesShort') }}</strong>
|
||||
</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 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 v-if="lesson && lesson.description" class="lesson-description-box">
|
||||
<h4>{{ $t('socialnetwork.vocab.courses.lessonDescription') }}</h4>
|
||||
@@ -872,6 +893,17 @@ export default {
|
||||
practicalTasks: []
|
||||
};
|
||||
},
|
||||
lessonPedagogy() {
|
||||
return this.lesson?.pedagogy || {
|
||||
didacticMode: null,
|
||||
phaseLabel: null,
|
||||
blockNumber: null,
|
||||
difficultyWeight: null,
|
||||
newUnitTarget: null,
|
||||
reviewWeight: null,
|
||||
isIntensiveReview: false
|
||||
};
|
||||
},
|
||||
assistantAvailable() {
|
||||
if (!this.assistantSettings) {
|
||||
return false;
|
||||
@@ -1280,6 +1312,36 @@ export default {
|
||||
};
|
||||
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) {
|
||||
const minutes = Number(targetMinutes);
|
||||
if (!minutes) {
|
||||
@@ -2187,6 +2249,24 @@ export default {
|
||||
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 {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
||||
|
||||
Reference in New Issue
Block a user