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;
|
||||
}
|
||||
|
||||
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) {
|
||||
if (entry === null || entry === undefined || entry === '') {
|
||||
return null;
|
||||
@@ -4292,6 +4370,7 @@ function getExercisesForLesson(lesson) {
|
||||
async function createBisayaCourseContent() {
|
||||
await sequelize.authenticate();
|
||||
console.log('Datenbankverbindung erfolgreich hergestellt.\n');
|
||||
const forceRebuildAll = process.env.VOCAB_FORCE_REBUILD_ALL === '1';
|
||||
|
||||
const systemUser = await findOrCreateSystemUser();
|
||||
console.log(`Verwende System-Benutzer: ${systemUser.username} (ID: ${systemUser.id})\n`);
|
||||
@@ -4362,33 +4441,44 @@ async function createBisayaCourseContent() {
|
||||
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`);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (replacePlaceholders && existingCount > 0) {
|
||||
if ((replacePlaceholders || forceRebuildAll) && existingCount > 0) {
|
||||
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
|
||||
const lessonDidactics = getLessonDidactics(lesson);
|
||||
let exerciseNumber = 1;
|
||||
for (const exerciseData of exercises) {
|
||||
const { exercise, fixes, warnings } = sanitizeExerciseForConsistency(
|
||||
lesson,
|
||||
exerciseData,
|
||||
lessonDidactics
|
||||
);
|
||||
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}`));
|
||||
}
|
||||
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({
|
||||
lessonId: lesson.id,
|
||||
exerciseTypeId,
|
||||
exerciseNumber: exerciseNumber++,
|
||||
title: exerciseData.title,
|
||||
instruction: exerciseData.instruction,
|
||||
questionData: JSON.stringify(exerciseData.questionData),
|
||||
answerData: JSON.stringify(exerciseData.answerData),
|
||||
explanation: exerciseData.explanation,
|
||||
title: exercise.title,
|
||||
instruction: exercise.instruction,
|
||||
questionData: JSON.stringify(exercise.questionData),
|
||||
answerData: JSON.stringify(exercise.answerData),
|
||||
explanation: exercise.explanation,
|
||||
createdByUserId: course.ownerUserId || systemUser.id
|
||||
});
|
||||
totalExercisesAdded++;
|
||||
|
||||
Reference in New Issue
Block a user