Files
yourpart3/backend/scripts/repair-bisaya-children-lessons.js
Torsten Schulz (local) d371df7ac4
All checks were successful
Deploy to production / deploy (push) Successful in 31s
feat: füge Skript zur Reparatur von Kinderlektionen hinzu, das Platzhalterübungen ersetzt
2026-05-22 11:44:15 +02:00

172 lines
6.9 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
/**
* Repariert alte/platzhalterhafte Prüfungen für KinderLektionen (z.B. Lektion 28).
*
* Nutzung:
* node backend/scripts/repair-bisaya-children-lessons.js
*
* Optional:
* VOCAB_FORCE_REBUILD_ALL=1 node backend/scripts/repair-bisaya-children-lessons.js
*
* Das Script sucht in der DB nach Kurslektionen mit lesson_number=28 oder
* mit typischen alten Titeln und ersetzt PlatzhalterExercises ("Option A/B/C/D")
* durch didaktisch passende Übungen.
*/
import { sequelize } from '../utils/sequelize.js';
import VocabCourseLesson from '../models/community/vocab_course_lesson.js';
import VocabGrammarExercise from '../models/community/vocab_grammar_exercise.js';
const CONVERSATION_PROFILE = {
title: 'Kinder, Spiel & Routine',
description: 'Kinderalltag: kurze Anweisungen, Routinen und spielerische Aufforderungen',
learningGoals: [
'Kurze Aufforderungen und Routinen klar und freundlich formulieren',
'Wichtige Alltagswörter (Tasche, Hände, Schlaf) sicher abrufen',
'Routinen in der richtigen Reihenfolge ausdrücken'
],
corePatterns: [
{ target: 'Hugas sa kamot.', gloss: 'Wasch dir die Hände.' },
{ target: 'Kuhaa imong bag.', gloss: 'Hol deine Tasche.' },
{ target: 'Matulog na ta.', gloss: 'Jetzt schlafen wir.' },
{ target: 'Patya ang suga.', gloss: 'Mach das Licht aus.' }
],
speakingPrompts: [
{ title: 'Morgenroutine', prompt: 'Sprich eine kurze Morgenroutine: Tasche holen, Hände waschen, Essen.', cue: 'Kuhaa imong bag. Hugas sa kamot. Nikaon na ka?' }
],
practicalTasks: [
{ title: 'Routinekette', text: 'Bilde eine Abfolge von vier kurzen Handlungsanweisungen für Kinder.' }
],
exercises: [
{
exerciseTypeId: 2,
title: 'Routinebefehl erkennen',
instruction: 'Wähle die richtige Bedeutung.',
questionData: {
type: 'multiple_choice',
question: 'Was bedeutet "Hugas sa kamot"?',
options: ['Wasch dir die Hände.', 'Hol deine Tasche.', 'Geh schlafen.', 'Iss zuerst.']
},
answerData: { type: 'multiple_choice', correctAnswer: 0 },
explanation: '"Hugas sa kamot" ist eine typische Aufforderung zur Hygiene.'
},
{
exerciseTypeId: 1,
title: 'Lücken: Morgenroutine',
instruction: 'Fülle die Lücken.',
questionData: { type: 'gap_fill', text: 'Kuhaa imong {gap}. Hugas sa {gap}.', gaps: 2 },
answerData: { type: 'gap_fill', answers: ['bag', 'kamot'] },
explanation: 'Tasche und Hände sind Kernwörter in Morgenroutinen.'
},
{
exerciseTypeId: 3,
title: 'Routine reihenfolge',
instruction: 'Ordne die Sätze zu einer natürlichen Reihenfolge.',
questionData: {
type: 'sentence_building',
question: 'Ordne: Hände waschen, Tasche holen, Licht aus, Schlafen.',
tokens: ['Kuhaa imong bag.', 'Hugas sa kamot.', 'Patya ang suga.', 'Matulog na ta.']
},
answerData: { correct: ['Kuhaa imong bag. Hugas sa kamot. Patya ang suga. Matulog na ta.'] },
explanation: 'Typische Abfolge für Abend-/Morgenroutinen.'
},
{
exerciseTypeId: 10,
title: 'Kind-Ansage',
instruction: 'Gib eine kurze, freundliche Anweisung.',
questionData: {
type: 'situational_response',
question: 'Du möchtest, dass ein Kind seine Tasche holt und sich die Hände wäscht.',
keywords: ['kuhaa', 'bag', 'hugas', 'kamot']
},
answerData: {
modelAnswer: 'Kuhaa imong bag. Hugas sa kamot.',
keywords: ['kuhaa', 'bag', 'hugas', 'kamot']
},
explanation: 'Kurze klare Sätze funktionieren bei Kindern am besten.'
}
]
};
function isPlaceholderExercise(exercise) {
const q = exercise?.questionData || {};
const questionData = typeof q === 'string' ? JSON.parse(q) : q;
return Array.isArray(questionData?.options)
&& questionData.options.some((option) => /^Option [A-D]$/i.test(String(option || '').trim()));
}
async function repairLesson(lesson, ownerUserId, { force = false } = {}) {
const profile = CONVERSATION_PROFILE;
const existing = await VocabGrammarExercise.findAll({ where: { lessonId: lesson.id }, order: [['exerciseNumber', 'ASC']] });
const hasPlaceholder = existing.some((exercise) => isPlaceholderExercise(exercise));
const shouldReplaceExercises = force || hasPlaceholder || existing.length < profile.exercises.length;
await lesson.update({
title: profile.title,
description: profile.description,
learningGoals: profile.learningGoals,
corePatterns: profile.corePatterns,
speakingPrompts: profile.speakingPrompts,
practicalTasks: profile.practicalTasks,
targetScorePercent: Math.max(Number(lesson.targetScorePercent) || 0, 80)
});
if (!shouldReplaceExercises) return { updated: true, exercisesReplaced: false, exerciseCount: existing.length };
await VocabGrammarExercise.destroy({ where: { lessonId: lesson.id } });
let exerciseNumber = 1;
for (const exercise of profile.exercises) {
await VocabGrammarExercise.create({
lessonId: lesson.id,
exerciseTypeId: exercise.exerciseTypeId,
exerciseNumber,
title: exercise.title,
instruction: exercise.instruction,
questionData: exercise.questionData,
answerData: exercise.answerData,
explanation: exercise.explanation,
createdByUserId: ownerUserId
});
exerciseNumber += 1;
}
return { updated: true, exercisesReplaced: true, exerciseCount: profile.exercises.length };
}
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) throw new Error('Bisaya language not found');
const rows = await sequelize.query(
`SELECT l.id, l.course_id, l.lesson_number, l.title, l.lesson_type, c.owner_user_id
FROM community.vocab_course_lesson l
JOIN community.vocab_course c ON c.id = l.course_id
WHERE c.language_id = :languageId
AND (l.lesson_number = 28 OR l.title IN ('Kinder & Spiel', 'Kinder, Spiel & Routine', 'Kinder & Familie'))
ORDER BY c.id, l.lesson_number`,
{ replacements: { languageId: bisayaLanguage.id }, type: sequelize.QueryTypes.SELECT }
);
console.log(`Gefundene Kinderlektionen: ${rows.length}`);
let replaced = 0;
for (const row of rows) {
const lesson = await VocabCourseLesson.findByPk(row.id);
const result = await repairLesson(lesson, row.owner_user_id, {
force: process.env.VOCAB_FORCE_REBUILD_ALL === '1'
});
if (result.exercisesReplaced) replaced += 1;
console.log(`✅ Kurs ${row.course_id}, Lektion ${row.lesson_number}: ${lesson.title} (${result.exerciseCount} Prüfung(en))`);
}
console.log(`Fertig. Aktualisiert: ${rows.length}, Prüfungen ersetzt/erstellt: ${replaced}`);
}
main()
.then(async () => { await sequelize.close(); })
.catch(async (error) => { console.error('❌ Reparatur fehlgeschlagen:', error); await sequelize.close(); process.exit(1); });