feat(bisaya-course): enhance gap-fill exercise processing and validation
All checks were successful
Deploy to production / deploy (push) Successful in 2m54s
All checks were successful
Deploy to production / deploy (push) Successful in 2m54s
- Added `buildCorePatternGlossLookup` to create a mapping of core patterns for improved hint sanitization. - Implemented `sanitizeGapFillHintText` to validate and replace hints based on answer word count and gloss availability, generating fixes and warnings as needed. - Updated `sanitizeExerciseForConsistency` to incorporate new sanitization logic for gap-fill exercises, ensuring consistent exercise data handling. - Enhanced logging in `createBisayaCourseContent` to provide detailed feedback on exercise processing and validation outcomes.
This commit is contained in:
@@ -74,6 +74,84 @@ function collectExerciseAuditWarnings(lessonTitle, exerciseData, exerciseNumber)
|
|||||||
return warnings;
|
return warnings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildCorePatternGlossLookup(didactics) {
|
||||||
|
const map = new Map();
|
||||||
|
const patterns = Array.isArray(didactics?.corePatterns) ? didactics.corePatterns : [];
|
||||||
|
patterns.forEach((entry) => {
|
||||||
|
const normalized = normalizeCorePatternEntry(entry);
|
||||||
|
if (!normalized?.target || !normalized?.gloss) return;
|
||||||
|
map.set(normalized.target.toLowerCase(), normalized.gloss);
|
||||||
|
});
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sanitizeGapFillHintText(lessonTitle, text, answers, glossLookup) {
|
||||||
|
const source = String(text || '');
|
||||||
|
const normalizedAnswers = Array.isArray(answers)
|
||||||
|
? answers.map((answer) => normalizeText(answer))
|
||||||
|
: [];
|
||||||
|
if (!source || normalizedAnswers.length === 0) {
|
||||||
|
return { text: source, fixes: [], warnings: [] };
|
||||||
|
}
|
||||||
|
|
||||||
|
const hintRegex = /\(([^)]+)\)/g;
|
||||||
|
let hintIndex = 0;
|
||||||
|
const fixes = [];
|
||||||
|
const warnings = [];
|
||||||
|
const rebuilt = source.replace(hintRegex, (full, inner) => {
|
||||||
|
const answer = normalizedAnswers[hintIndex] || '';
|
||||||
|
const hint = normalizeText(inner);
|
||||||
|
hintIndex += 1;
|
||||||
|
if (!answer || !hint) return full;
|
||||||
|
|
||||||
|
const answerWords = countWords(answer);
|
||||||
|
const hintWords = countWords(hint);
|
||||||
|
if (!(answerWords <= 2 && hintWords >= 4)) {
|
||||||
|
return full;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mappedGloss = normalizeText(glossLookup.get(answer.toLowerCase()) || '');
|
||||||
|
if (mappedGloss && countWords(mappedGloss) <= 3) {
|
||||||
|
fixes.push(`[${lessonTitle}] "${answer}": "${hint}" -> "${mappedGloss}"`);
|
||||||
|
return `(${mappedGloss})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
warnings.push(
|
||||||
|
`[${lessonTitle}] Keine sichere Gloss für "${answer}" gefunden; langer Hinweis bleibt: "${hint}"`
|
||||||
|
);
|
||||||
|
return full;
|
||||||
|
});
|
||||||
|
|
||||||
|
return { text: rebuilt, fixes, warnings };
|
||||||
|
}
|
||||||
|
|
||||||
|
function sanitizeExerciseForConsistency(lesson, exerciseData, didactics) {
|
||||||
|
const exercise = { ...exerciseData };
|
||||||
|
const questionData = { ...(exerciseData?.questionData || {}) };
|
||||||
|
const answerData = { ...(exerciseData?.answerData || {}) };
|
||||||
|
const fixes = [];
|
||||||
|
const warnings = [];
|
||||||
|
|
||||||
|
if (questionData.type === 'gap_fill') {
|
||||||
|
const glossLookup = buildCorePatternGlossLookup(didactics);
|
||||||
|
const sanitized = sanitizeGapFillHintText(
|
||||||
|
lesson.title,
|
||||||
|
questionData.text,
|
||||||
|
Array.isArray(answerData.answers) ? answerData.answers : [],
|
||||||
|
glossLookup
|
||||||
|
);
|
||||||
|
if (sanitized.text !== questionData.text) {
|
||||||
|
questionData.text = sanitized.text;
|
||||||
|
fixes.push(...sanitized.fixes);
|
||||||
|
}
|
||||||
|
warnings.push(...sanitized.warnings);
|
||||||
|
}
|
||||||
|
|
||||||
|
exercise.questionData = questionData;
|
||||||
|
exercise.answerData = answerData;
|
||||||
|
return { exercise, fixes, warnings };
|
||||||
|
}
|
||||||
|
|
||||||
function normalizeCorePatternEntry(entry) {
|
function normalizeCorePatternEntry(entry) {
|
||||||
if (entry === null || entry === undefined || entry === '') {
|
if (entry === null || entry === undefined || entry === '') {
|
||||||
return null;
|
return null;
|
||||||
@@ -4292,6 +4370,7 @@ function getExercisesForLesson(lesson) {
|
|||||||
async function createBisayaCourseContent() {
|
async function createBisayaCourseContent() {
|
||||||
await sequelize.authenticate();
|
await sequelize.authenticate();
|
||||||
console.log('Datenbankverbindung erfolgreich hergestellt.\n');
|
console.log('Datenbankverbindung erfolgreich hergestellt.\n');
|
||||||
|
const forceRebuildAll = process.env.VOCAB_FORCE_REBUILD_ALL === '1';
|
||||||
|
|
||||||
const systemUser = await findOrCreateSystemUser();
|
const systemUser = await findOrCreateSystemUser();
|
||||||
console.log(`Verwende System-Benutzer: ${systemUser.username} (ID: ${systemUser.id})\n`);
|
console.log(`Verwende System-Benutzer: ${systemUser.username} (ID: ${systemUser.id})\n`);
|
||||||
@@ -4362,33 +4441,44 @@ async function createBisayaCourseContent() {
|
|||||||
where: { lessonId: lesson.id }
|
where: { lessonId: lesson.id }
|
||||||
});
|
});
|
||||||
|
|
||||||
if (existingCount > 0 && !replacePlaceholders) {
|
if (existingCount > 0 && !replacePlaceholders && !forceRebuildAll) {
|
||||||
console.log(` ⏭️ Lektion ${lesson.lessonNumber}: "${lesson.title}" - bereits ${existingCount} Übung(en) vorhanden`);
|
console.log(` ⏭️ Lektion ${lesson.lessonNumber}: "${lesson.title}" - bereits ${existingCount} Übung(en) vorhanden`);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (replacePlaceholders && existingCount > 0) {
|
if ((replacePlaceholders || forceRebuildAll) && existingCount > 0) {
|
||||||
const deleted = await VocabGrammarExercise.destroy({ where: { lessonId: lesson.id } });
|
const deleted = await VocabGrammarExercise.destroy({ where: { lessonId: lesson.id } });
|
||||||
console.log(` 🗑️ Lektion ${lesson.lessonNumber}: "${lesson.title}" - ${deleted} Platzhalter entfernt`);
|
const reason = forceRebuildAll ? 'vollständig neu aufgebaut' : 'Platzhalter entfernt';
|
||||||
|
console.log(` 🗑️ Lektion ${lesson.lessonNumber}: "${lesson.title}" - ${deleted} Übungen entfernt (${reason})`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Erstelle Übungen
|
// Erstelle Übungen
|
||||||
|
const lessonDidactics = getLessonDidactics(lesson);
|
||||||
let exerciseNumber = 1;
|
let exerciseNumber = 1;
|
||||||
for (const exerciseData of exercises) {
|
for (const exerciseData of exercises) {
|
||||||
|
const { exercise, fixes, warnings } = sanitizeExerciseForConsistency(
|
||||||
|
lesson,
|
||||||
|
exerciseData,
|
||||||
|
lessonDidactics
|
||||||
|
);
|
||||||
if (process.env.VOCAB_STRICT_AUDIT === '1') {
|
if (process.env.VOCAB_STRICT_AUDIT === '1') {
|
||||||
const warnings = collectExerciseAuditWarnings(lesson.title, exerciseData, exerciseNumber);
|
fixes.forEach((fix) => console.log(` 🛠️ ${fix}`));
|
||||||
warnings.forEach((warning) => console.warn(` ⚠️ ${warning}`));
|
warnings.forEach((warning) => console.warn(` ⚠️ ${warning}`));
|
||||||
}
|
}
|
||||||
const exerciseTypeId = await resolveExerciseTypeId(exerciseData);
|
if (process.env.VOCAB_STRICT_AUDIT === '1') {
|
||||||
|
const warnings = collectExerciseAuditWarnings(lesson.title, exercise, exerciseNumber);
|
||||||
|
warnings.forEach((warning) => console.warn(` ⚠️ ${warning}`));
|
||||||
|
}
|
||||||
|
const exerciseTypeId = await resolveExerciseTypeId(exercise);
|
||||||
await VocabGrammarExercise.create({
|
await VocabGrammarExercise.create({
|
||||||
lessonId: lesson.id,
|
lessonId: lesson.id,
|
||||||
exerciseTypeId,
|
exerciseTypeId,
|
||||||
exerciseNumber: exerciseNumber++,
|
exerciseNumber: exerciseNumber++,
|
||||||
title: exerciseData.title,
|
title: exercise.title,
|
||||||
instruction: exerciseData.instruction,
|
instruction: exercise.instruction,
|
||||||
questionData: JSON.stringify(exerciseData.questionData),
|
questionData: JSON.stringify(exercise.questionData),
|
||||||
answerData: JSON.stringify(exerciseData.answerData),
|
answerData: JSON.stringify(exercise.answerData),
|
||||||
explanation: exerciseData.explanation,
|
explanation: exercise.explanation,
|
||||||
createdByUserId: course.ownerUserId || systemUser.id
|
createdByUserId: course.ownerUserId || systemUser.id
|
||||||
});
|
});
|
||||||
totalExercisesAdded++;
|
totalExercisesAdded++;
|
||||||
|
|||||||
Reference in New Issue
Block a user