All checks were successful
Deploy to production / deploy (push) Successful in 2m51s
- 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.
203 lines
6.5 KiB
JavaScript
203 lines
6.5 KiB
JavaScript
#!/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 1–20“ setzen
|
||
* - Neue Lektionen 19–21 („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 1–20') {
|
||
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 1–20',
|
||
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 1–20');
|
||
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, 19–21 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);
|
||
});
|