refactor(exercises): standardize answer language handling across exercise scripts
All checks were successful
Deploy to production / deploy (push) Successful in 2m48s
All checks were successful
Deploy to production / deploy (push) Successful in 2m48s
- Introduced a mechanism to infer answer language based on question phrasing in multiple exercise scripts, enhancing consistency in exercise data. - Updated question formats to clarify the intent of exercises, improving user understanding and engagement. - Streamlined the code for better maintainability and clarity in exercise generation processes.
This commit is contained in:
@@ -349,7 +349,8 @@ function createFamilyConversationExercises(nativeLanguageName) {
|
||||
instruction: 'Übersetze den Bisaya-Satz ins ' + nativeLanguageName,
|
||||
questionData: JSON.stringify({
|
||||
type: 'multiple_choice',
|
||||
question: `Wie sagt man "${conv.bisaya}" auf ${nativeLanguageName}?`,
|
||||
answerLanguage: 'native',
|
||||
question: `Was bedeutet "${conv.bisaya}"?`,
|
||||
options: options
|
||||
}),
|
||||
answerData: JSON.stringify({
|
||||
@@ -379,6 +380,7 @@ function createFamilyConversationExercises(nativeLanguageName) {
|
||||
instruction: 'Was bedeutet dieser Bisaya-Satz?',
|
||||
questionData: JSON.stringify({
|
||||
type: 'multiple_choice',
|
||||
answerLanguage: 'native',
|
||||
question: `Was bedeutet "${conv.bisaya}"?`,
|
||||
options: options
|
||||
}),
|
||||
|
||||
@@ -134,6 +134,7 @@ function createFamilyWordsExercises(nativeLanguageName) {
|
||||
instruction: 'Wähle die richtige Übersetzung.',
|
||||
questionData: {
|
||||
type: 'multiple_choice',
|
||||
answerLanguage: 'target',
|
||||
question: `Wie sagt man "${nativeWord}" auf Bisaya?`,
|
||||
options: options
|
||||
},
|
||||
|
||||
@@ -349,7 +349,8 @@ function createFeelingsAffectionExercises(nativeLanguageName) {
|
||||
instruction: 'Übersetze den Bisaya-Satz ins ' + nativeLanguageName,
|
||||
questionData: JSON.stringify({
|
||||
type: 'multiple_choice',
|
||||
question: `Wie sagt man "${conv.bisaya}" auf ${nativeLanguageName}?`,
|
||||
answerLanguage: 'native',
|
||||
question: `Was bedeutet "${conv.bisaya}"?`,
|
||||
options: options
|
||||
}),
|
||||
answerData: JSON.stringify({
|
||||
@@ -379,6 +380,7 @@ function createFeelingsAffectionExercises(nativeLanguageName) {
|
||||
instruction: 'Was bedeutet dieser Bisaya-Satz?',
|
||||
questionData: JSON.stringify({
|
||||
type: 'multiple_choice',
|
||||
answerLanguage: 'native',
|
||||
question: `Was bedeutet "${conv.bisaya}"?`,
|
||||
options: options
|
||||
}),
|
||||
|
||||
@@ -582,6 +582,7 @@ async function updateFoodCareExercises() {
|
||||
instruction: 'Wähle die richtige Übersetzung.',
|
||||
questionData: JSON.stringify({
|
||||
type: 'multiple_choice',
|
||||
answerLanguage: 'target',
|
||||
question: `Wie sagt man "${conv.native}" auf Bisaya?`,
|
||||
options: [
|
||||
conv.bisaya,
|
||||
@@ -608,6 +609,7 @@ async function updateFoodCareExercises() {
|
||||
instruction: 'Wähle die richtige Übersetzung.',
|
||||
questionData: JSON.stringify({
|
||||
type: 'multiple_choice',
|
||||
answerLanguage: 'native',
|
||||
question: `Was bedeutet "${conv.bisaya}"?`,
|
||||
options: [
|
||||
conv.native,
|
||||
|
||||
@@ -13,6 +13,31 @@ import VocabCourseLesson from '../models/community/vocab_course_lesson.js';
|
||||
import VocabGrammarExercise from '../models/community/vocab_grammar_exercise.js';
|
||||
import User from '../models/community/user.js';
|
||||
|
||||
function normalizeMcAnswerLanguage(question = '') {
|
||||
const q = String(question || '').trim();
|
||||
if (!q) return null;
|
||||
if (/Wie sagt man/i.test(q) || /auf Bisaya\?/i.test(q)) return 'target';
|
||||
if (/Was bedeutet/i.test(q) || /Was heißt/i.test(q)) return 'native';
|
||||
return null;
|
||||
}
|
||||
|
||||
function enrichAnswerLanguage(exercises = []) {
|
||||
return exercises.map((exercise) => {
|
||||
const qd = exercise?.questionData;
|
||||
if (!qd || qd.type !== 'multiple_choice') return exercise;
|
||||
if (qd.answerLanguage || qd.answerLanguageId) return exercise;
|
||||
const inferred = normalizeMcAnswerLanguage(qd.question);
|
||||
if (!inferred) return exercise;
|
||||
return {
|
||||
...exercise,
|
||||
questionData: {
|
||||
...qd,
|
||||
answerLanguage: inferred
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// Spezifische Übungen für Überlebenssätze
|
||||
const SURVIVAL_EXERCISES = {
|
||||
'Überlebenssätze - Teil 1': [
|
||||
@@ -399,7 +424,7 @@ async function updateSurvivalExercises() {
|
||||
console.log(` ${lessons.length} "Überlebenssätze"-Lektionen gefunden\n`);
|
||||
|
||||
for (const lesson of lessons) {
|
||||
const exercises = SURVIVAL_EXERCISES[lesson.title];
|
||||
const exercises = enrichAnswerLanguage(SURVIVAL_EXERCISES[lesson.title]);
|
||||
|
||||
if (!exercises || exercises.length === 0) {
|
||||
console.log(` ⚠️ Lektion ${lesson.lessonNumber}: "${lesson.title}" - keine Übungen definiert`);
|
||||
|
||||
@@ -23,6 +23,31 @@ function withTypeName(exerciseTypeName, exercise) {
|
||||
|
||||
const LESSON_TITLES = ['Woche 1 - Wiederholung', 'Woche 1 - Vokabeltest'];
|
||||
|
||||
function normalizeMcAnswerLanguage(question = '') {
|
||||
const q = String(question || '').trim();
|
||||
if (!q) return null;
|
||||
if (/Wie sagt man/i.test(q) || /auf Bisaya\?/i.test(q)) return 'target';
|
||||
if (/Was bedeutet/i.test(q) || /Was heißt/i.test(q)) return 'native';
|
||||
return null;
|
||||
}
|
||||
|
||||
function enrichAnswerLanguage(exercises = []) {
|
||||
return exercises.map((exercise) => {
|
||||
const qd = exercise?.questionData;
|
||||
if (!qd || qd.type !== 'multiple_choice') return exercise;
|
||||
if (qd.answerLanguage || qd.answerLanguageId) return exercise;
|
||||
const inferred = normalizeMcAnswerLanguage(qd.question);
|
||||
if (!inferred) return exercise;
|
||||
return {
|
||||
...exercise,
|
||||
questionData: {
|
||||
...qd,
|
||||
answerLanguage: inferred
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
const BISAYA_EXERCISES = {
|
||||
'Woche 1 - Wiederholung': [
|
||||
{ exerciseTypeId: 2, title: 'Wiederholung: Wie sagt man "Wie geht es dir?"?', instruction: 'Wähle die richtige Begrüßung aus.', questionData: { type: 'multiple_choice', question: 'Wie sagt man "Wie geht es dir?" auf Bisaya?', options: ['Kumusta ka?', 'Maayo', 'Salamat', 'Palihug'] }, answerData: { type: 'multiple_choice', correctAnswer: 0 }, explanation: '"Kumusta ka?" ist die Standard-Begrüßung auf Bisaya.' },
|
||||
@@ -111,7 +136,7 @@ async function updateWeek1BisayaExercises() {
|
||||
console.log(`📚 Kurs: ${course.title} (ID: ${course.id})`);
|
||||
|
||||
for (const lessonTitle of LESSON_TITLES) {
|
||||
const exercises = BISAYA_EXERCISES[lessonTitle];
|
||||
const exercises = enrichAnswerLanguage(BISAYA_EXERCISES[lessonTitle]);
|
||||
if (!exercises || exercises.length === 0) continue;
|
||||
|
||||
const lessons = await VocabCourseLesson.findAll({
|
||||
|
||||
@@ -1251,9 +1251,53 @@ export default {
|
||||
},
|
||||
lessonVocab() {
|
||||
const vocabByReference = new Map();
|
||||
const targetTokenWeight = new Map();
|
||||
const nativeTokenWeight = new Map();
|
||||
const addTokens = (text, map, weight = 1) => {
|
||||
const tokens = String(text || '')
|
||||
.toLowerCase()
|
||||
.normalize('NFKC')
|
||||
.replace(/[\p{P}\p{S}]+/gu, ' ')
|
||||
.split(/\s+/)
|
||||
.map((t) => t.trim())
|
||||
.filter((t) => t.length >= 2);
|
||||
tokens.forEach((token) => {
|
||||
map.set(token, (map.get(token) || 0) + weight);
|
||||
});
|
||||
};
|
||||
const sideScore = (text, map) => {
|
||||
const tokens = String(text || '')
|
||||
.toLowerCase()
|
||||
.normalize('NFKC')
|
||||
.replace(/[\p{P}\p{S}]+/gu, ' ')
|
||||
.split(/\s+/)
|
||||
.map((t) => t.trim())
|
||||
.filter((t) => t.length >= 2);
|
||||
return tokens.reduce((sum, token) => sum + (map.get(token) || 0), 0);
|
||||
};
|
||||
(this.normalizedCorePatterns || []).forEach((p) => {
|
||||
addTokens(p?.target, targetTokenWeight, 3);
|
||||
addTokens(p?.gloss, nativeTokenWeight, 3);
|
||||
});
|
||||
(this.importantVocab || []).forEach((v) => {
|
||||
addTokens(v?.reference, targetTokenWeight, 1);
|
||||
addTokens(v?.learning, nativeTokenWeight, 1);
|
||||
});
|
||||
const orientPair = (learning, reference) => {
|
||||
const l = String(learning || '').trim();
|
||||
const r = String(reference || '').trim();
|
||||
if (!l || !r) return { learning: l, reference: r };
|
||||
const directScore = sideScore(r, targetTokenWeight) + sideScore(l, nativeTokenWeight);
|
||||
const swappedScore = sideScore(l, targetTokenWeight) + sideScore(r, nativeTokenWeight);
|
||||
if (swappedScore > directScore) {
|
||||
return { learning: r, reference: l };
|
||||
}
|
||||
return { learning: l, reference: r };
|
||||
};
|
||||
const addEntry = (entry) => {
|
||||
const reference = String(entry?.reference || '').trim();
|
||||
const learning = String(entry?.learning || '').trim();
|
||||
const oriented = orientPair(entry?.learning, entry?.reference);
|
||||
const reference = String(oriented.reference || '').trim();
|
||||
const learning = String(oriented.learning || '').trim();
|
||||
if (!reference) return;
|
||||
const key = this.normalizeLessonVocabTerm(reference);
|
||||
if (!vocabByReference.has(key)) {
|
||||
|
||||
Reference in New Issue
Block a user