- Updated VocabCourseView.vue to display CEFR level for courses. - Enhanced VocabLessonView.vue with new sections for listening activities and conversation labs, including audio controls and transcript toggling. - Created migration to add new columns to vocab_course and new tables for conversation sessions and listening activities. - Implemented models for vocab_conversation_session and vocab_listening_activity. - Developed scripts to create new A2 and B1 bridge courses with structured lesson plans. - Documented the A2-B1 expansion plan detailing course structure, learning goals, and assessment criteria.
91 lines
5.9 KiB
JavaScript
91 lines
5.9 KiB
JavaScript
#!/usr/bin/env node
|
||
/**
|
||
* Erstellt den eigenständigen Anschlusskurs „Bisaya A2 – Alltag & Gespräch“.
|
||
* Verwendung: node backend/scripts/create-bisaya-a2-course.js <ownerHashedId>
|
||
*/
|
||
import crypto from 'crypto';
|
||
import { sequelize } from '../utils/sequelize.js';
|
||
import User from '../models/community/user.js';
|
||
import VocabCourse from '../models/community/vocab_course.js';
|
||
import VocabCourseLesson from '../models/community/vocab_course_lesson.js';
|
||
|
||
const BLOCKS = [
|
||
['Wohnen & Reparaturen', 'Wohnung beschreiben, Probleme melden und eine Reparatur organisieren.', 'Naa’y problema sa balay.', 'Es gibt ein Problem im Haus.'],
|
||
['Arbeit & Tagesorganisation', 'Aufgaben, Arbeitszeiten und Prioritäten nachvollziehbar erklären.', 'Humanon nako una kini.', 'Ich mache das zuerst fertig.'],
|
||
['Schule & digitale Kommunikation', 'Schule, Nachrichten und einfache Absprachen verbinden.', 'Magpadala ko ug mensahe unya.', 'Ich schicke später eine Nachricht.'],
|
||
['Familie & Beziehungen', 'Gefühle, Wünsche und Rücksicht in längeren Antworten ausdrücken.', 'Importante ka para nako.', 'Du bist mir wichtig.'],
|
||
['Meinung & Entscheidungen', 'Zustimmen, begründen und höflich eine andere Meinung sagen.', 'Para nako, mas maayo kung…', 'Für mich wäre es besser, wenn …'],
|
||
['Erlebnisse erzählen', 'Erlebtes in sinnvoller Reihenfolge erzählen und nachfragen.', 'Pagkahuman, miadto mi sa merkado.', 'Danach gingen wir zum Markt.'],
|
||
['Reisen & Unterkunft', 'Fahrt, Unterkunft und Änderungen eines Plans klären.', 'Pwede ba nato usbon ang plano?', 'Können wir den Plan ändern?'],
|
||
['Geld, Dokumente & Dienste', 'Zahlung, Dokumente und öffentliche Erledigungen bewältigen.', 'Kinahanglan nako ang resibo.', 'Ich brauche die Quittung.'],
|
||
['Gesundheit & unerwartete Probleme', 'Beschwerden genauer beschreiben und Hilfe organisieren.', 'Dugay na ni nagsakit.', 'Das tut schon länger weh.'],
|
||
['A2-Transfer: Alltag', 'Mehrere Alltagsthemen ohne feste Reihenfolge verbinden.', 'Una, kinahanglan nato…', 'Zuerst müssen wir …'],
|
||
['A2-Transfer: Gespräch', 'Ein längeres Gespräch beginnen, tragen und freundlich beenden.', 'Nasabtan nako imong giingon.', 'Ich verstehe, was du gesagt hast.'],
|
||
['A2-Abschluss & Weiterlernen', 'Eigene Stärken und nächste Lernziele benennen.', 'Gusto pa ko mopadayon ug praktis.', 'Ich möchte weiter üben.']
|
||
];
|
||
|
||
const TYPES = ['vocab', 'grammar', 'conversation', 'listening', 'conversation', 'review'];
|
||
const SUFFIXES = ['Wortschatz im Kontext', 'Muster & Grammatik', 'Geführtes Gespräch', 'Hörverstehen', 'Rollenspiel', 'Checkpoint'];
|
||
|
||
async function main() {
|
||
const ownerHashedId = String(process.argv[2] || '').trim();
|
||
if (!ownerHashedId) throw new Error('Bitte ownerHashedId angeben.');
|
||
await sequelize.authenticate();
|
||
const owner = await User.findOne({ where: { hashedId: ownerHashedId } });
|
||
if (!owner) throw new Error('Nutzer nicht gefunden.');
|
||
const [language] = await sequelize.query("SELECT id FROM community.vocab_language WHERE name = 'Bisaya' LIMIT 1", { type: sequelize.QueryTypes.SELECT });
|
||
if (!language) throw new Error('Bisaya-Sprache nicht gefunden.');
|
||
const baseCourse = await VocabCourse.findOne({
|
||
where: { languageId: language.id, courseKind: 'core' }, order: [['id', 'ASC']]
|
||
});
|
||
const [course] = await VocabCourse.findOrCreate({
|
||
where: { title: 'Bisaya A2 – Alltag & Gespräch', languageId: language.id },
|
||
defaults: {
|
||
ownerUserId: owner.id,
|
||
description: 'Anschlusskurs für variablere Alltagssprache, Hörverstehen und längere Gespräche.',
|
||
nativeLanguageId: null,
|
||
difficultyLevel: 2,
|
||
cefrLevel: 'A2',
|
||
courseKind: 'continuation',
|
||
prerequisiteCourseId: baseCourse?.id || null,
|
||
isPublic: true,
|
||
shareCode: crypto.randomBytes(8).toString('hex')
|
||
}
|
||
});
|
||
let created = 0;
|
||
for (const [blockIndex, block] of BLOCKS.entries()) {
|
||
const [topic, goal, target, gloss] = block;
|
||
for (let offset = 0; offset < 6; offset += 1) {
|
||
const lessonNumber = blockIndex * 6 + offset + 1;
|
||
const [lesson, made] = await VocabCourseLesson.findOrCreate({
|
||
where: { courseId: course.id, lessonNumber },
|
||
defaults: {
|
||
courseId: course.id, chapterId: null, lessonNumber,
|
||
title: `${topic}: ${SUFFIXES[offset]}`,
|
||
description: goal,
|
||
weekNumber: blockIndex + 1, dayNumber: offset + 1,
|
||
lessonType: TYPES[offset], didacticMode: TYPES[offset] === 'review' ? 'checkpoint' : TYPES[offset] === 'listening' ? 'listening' : 'real_life_scenario',
|
||
phaseLabel: 'a2', blockNumber: blockIndex + 1, difficultyWeight: 2,
|
||
newUnitTarget: offset === 5 ? 0 : 6, reviewWeight: offset === 5 ? 70 : 35,
|
||
isIntensiveReview: offset === 5, targetMinutes: offset === 3 ? 25 : 30,
|
||
targetScorePercent: 80, requiresReview: offset === 5,
|
||
learningGoals: [goal, 'Mindestens vier eigene Sätze zum Thema formulieren.', 'Frühere Muster passend wiederverwenden.'],
|
||
corePatterns: [
|
||
{ target, gloss },
|
||
{ target: 'Pwede nimo ko tabangan?', gloss: 'Kannst du mir helfen?' },
|
||
{ target: 'Sa akong hunahuna, maayo ni.', gloss: 'Ich finde, das ist gut.' },
|
||
{ target: 'Kung mahimo, buhaton nato unya.', gloss: 'Wenn möglich, machen wir es später.' }
|
||
],
|
||
grammarFocus: [{ title: 'Muster im Kontext', text: 'Nutze die neuen Formen, um einen vollständigen Gedanken zu verbinden.', example: target }],
|
||
speakingPrompts: [{ title: 'Eigene Antwort', prompt: `${goal} Antworte frei in vier bis sechs Sätzen.`, cue: target }],
|
||
practicalTasks: [{ title: 'Transfer', text: 'Nutze ein Kernmuster heute in einer echten oder simulierten Nachricht.' }]
|
||
}
|
||
});
|
||
if (made) created += 1;
|
||
}
|
||
}
|
||
console.log(`A2-Kurs ${course.id}: ${created} Lektionen angelegt.`);
|
||
}
|
||
|
||
main().then(() => sequelize.close()).catch((error) => { console.error(error); sequelize.close(); process.exitCode = 1; });
|