Files
yourpart3/backend/scripts/migrate-bisaya-zahlen-split.js
Torsten Schulz (local) 68ac5ec281
All checks were successful
Deploy to production / deploy (push) Successful in 2m51s
feat(bisaya-course): add new lesson and update course creation logic
- Introduced a new lesson titled "Zahlen & Preise" to enhance the numerical curriculum in Bisaya.
- Updated the course creation logic to check for existing "Familien"-Bisaya courses, ensuring idempotency in course creation.
- Enhanced the lesson didactics by mapping legacy titles to current lesson keys, improving data consistency.
- Adjusted the course generation script to limit Bisaya courses to German-speaking learners only, streamlining course offerings.
2026-04-16 22:16:23 +02:00

203 lines
6.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env node
/**
* Migriert ältere Bisaya-Kurse von einer kombinierten Lektion „Zahlen & Preise“ (nur #18)
* zur viergeteilten Zahlenreihe (#18#21). Erwartet vorher noch das alte Layout:
* Lektion 19 = „Woche 2 - Wiederholung“ (liegt im neuen Kurs an Position 22).
*
* Ablauf je Kurs:
* - Lektionen mit lesson_number >= 19 um +3 nach oben schieben (von oben nach unten)
* - Lektion 18 auf „Zahlen 120“ setzen
* - Neue Lektionen 1921 („Zahlen: Zehner/Hunderter/Tausender“) einfügen
*
* Verwendung:
* node backend/scripts/migrate-bisaya-zahlen-split.js
*
* Anschließend:
* node backend/scripts/update-bisaya-didactics.js
* node backend/scripts/create-bisaya-course-content.js
*/
import { sequelize } from '../utils/sequelize.js';
import VocabCourseLesson from '../models/community/vocab_course_lesson.js';
import { LESSON_DIDACTICS } from './update-bisaya-didactics.js';
import { getBisayaLessonPedagogy } from './bisaya-course-phase2-pedagogy.js';
const INSERTED_LESSONS = [
{
lessonNumber: 19,
title: 'Zahlen: Zehner',
description: 'Runde Zehner von 20 bis 90',
weekNumber: 2,
dayNumber: 4,
lessonType: 'vocab',
culturalNotes: null,
targetMinutes: 20,
targetScorePercent: 85,
requiresReview: true
},
{
lessonNumber: 20,
title: 'Zahlen: Hunderter',
description: 'Hunderter bis 900 (usa ka gatos … siyam ka gatos)',
weekNumber: 2,
dayNumber: 4,
lessonType: 'vocab',
culturalNotes: null,
targetMinutes: 20,
targetScorePercent: 85,
requiresReview: true
},
{
lessonNumber: 21,
title: 'Zahlen: Tausender',
description: 'Tausender und große Beträge (libo)',
weekNumber: 2,
dayNumber: 4,
lessonType: 'vocab',
culturalNotes: null,
targetMinutes: 18,
targetScorePercent: 85,
requiresReview: true
}
];
function applyPedagogy(patch, lessonNumber) {
const pedagogy = getBisayaLessonPedagogy(lessonNumber) || {};
patch.didacticMode = pedagogy.didacticMode || null;
patch.phaseLabel = pedagogy.phaseLabel || null;
patch.blockNumber = pedagogy.blockNumber ?? null;
patch.difficultyWeight = pedagogy.difficultyWeight ?? null;
patch.newUnitTarget = pedagogy.newUnitTarget ?? null;
patch.reviewWeight = pedagogy.reviewWeight ?? null;
patch.isIntensiveReview = Boolean(pedagogy.isIntensiveReview);
}
function applyDidactics(patch, title) {
const d = LESSON_DIDACTICS[title];
if (!d) return;
patch.learningGoals = d.learningGoals || [];
patch.corePatterns = d.corePatterns || [];
patch.grammarFocus = d.grammarFocus || [];
patch.speakingPrompts = d.speakingPrompts || [];
patch.practicalTasks = d.practicalTasks || [];
}
async function migrateCourse(courseId) {
const maxRow = await sequelize.query(
`SELECT MAX(lesson_number) AS max FROM community.vocab_course_lesson WHERE course_id = :courseId`,
{ replacements: { courseId }, type: sequelize.QueryTypes.SELECT }
);
const maxNum = Number(maxRow[0]?.max || 0);
if (maxNum < 19) {
return { status: 'skip', reason: 'weniger als 19 Lektionen' };
}
const l19 = await VocabCourseLesson.findOne({
where: { courseId, lessonNumber: 19 }
});
if (!l19) {
return { status: 'skip', reason: 'keine Lektion 19' };
}
if (l19.title !== 'Woche 2 - Wiederholung') {
return { status: 'skip', reason: 'Lektion 19 ist nicht „Woche 2 - Wiederholung“ (bereits migriert oder anderer Stand)' };
}
const l18 = await VocabCourseLesson.findOne({
where: { courseId, lessonNumber: 18 }
});
if (!l18) {
return { status: 'skip', reason: 'keine Lektion 18' };
}
if (l18.title !== 'Zahlen & Preise' && l18.title !== 'Zahlen 120') {
return { status: 'skip', reason: `Lektion 18 unerwartet: „${l18.title}` };
}
await sequelize.transaction(async (t) => {
for (let n = maxNum; n >= 19; n -= 1) {
await VocabCourseLesson.update(
{ lessonNumber: n + 3 },
{ where: { courseId, lessonNumber: n }, transaction: t }
);
}
const lesson18 = await VocabCourseLesson.findOne({
where: { courseId, lessonNumber: 18 },
transaction: t
});
const patch18 = {
title: 'Zahlen 120',
description: 'Grundzahlen und Zahlen bis 20 (usa … baynte)',
weekNumber: 2,
dayNumber: 4,
lessonType: 'vocab',
culturalNotes: null,
targetMinutes: 22,
targetScorePercent: 85,
requiresReview: true
};
applyDidactics(patch18, 'Zahlen 120');
applyPedagogy(patch18, 18);
await lesson18.update(patch18, { transaction: t });
for (const def of INSERTED_LESSONS) {
const createPayload = {
courseId,
chapterId: null,
lessonNumber: def.lessonNumber,
title: def.title,
description: def.description,
weekNumber: def.weekNumber,
dayNumber: def.dayNumber,
lessonType: def.lessonType,
culturalNotes: def.culturalNotes,
targetMinutes: def.targetMinutes,
targetScorePercent: def.targetScorePercent,
requiresReview: def.requiresReview
};
applyDidactics(createPayload, def.title);
applyPedagogy(createPayload, def.lessonNumber);
await VocabCourseLesson.create(createPayload, { transaction: t });
}
});
return { status: 'ok' };
}
async function main() {
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.');
process.exit(1);
}
const courses = await sequelize.query(
`SELECT id, title FROM community.vocab_course WHERE language_id = :languageId ORDER BY id`,
{ replacements: { languageId: bisayaLanguage.id }, type: sequelize.QueryTypes.SELECT }
);
console.log(`Gefunden: ${courses.length} Bisaya-Kurs(e)\n`);
for (const course of courses) {
const result = await migrateCourse(course.id);
if (result.status === 'ok') {
console.log(`✅ Kurs ${course.id}: „${course.title} Zahlen-Split angewendet (Lektionen verschoben, 1921 eingefügt).`);
} else {
console.log(`⏭️ Kurs ${course.id}: „${course.title} ${result.reason}`);
}
}
console.log('\nFertig. Danach: node backend/scripts/update-bisaya-didactics.js && node backend/scripts/create-bisaya-course-content.js');
await sequelize.close();
}
main().catch((e) => {
console.error('❌ Fehler:', e);
sequelize.close();
process.exit(1);
});