feat(VocabService): improve SRS pair validation and introduce new text analysis methods
All checks were successful
Deploy to production / deploy (push) Successful in 2m13s
All checks were successful
Deploy to production / deploy (push) Successful in 2m13s
- Refactored SRS pair validation logic by introducing the `_isTrainableSrsPair` method to enhance the criteria for acceptable learning and reference pairs. - Added `_isInstructionLikeText` method to filter out instructional texts from SRS pairs, improving the quality of vocabulary training. - Updated various methods to utilize the new validation logic, ensuring consistent handling of vocabulary entries across the service. - Enhanced the `_courseHasDueSrsItems` and `_extractTrainerVocabsFromLessonDidactics` methods to improve data retrieval and filtering based on the new criteria.
This commit is contained in:
@@ -42,7 +42,7 @@ export default class VocabService {
|
|||||||
.map((entry) => {
|
.map((entry) => {
|
||||||
const learning = String(entry?.learning || '').trim();
|
const learning = String(entry?.learning || '').trim();
|
||||||
const reference = String(entry?.reference || '').trim();
|
const reference = String(entry?.reference || '').trim();
|
||||||
if (!learning || !reference || this._normalizeSrsText(learning) === this._normalizeSrsText(reference)) {
|
if (!this._isTrainableSrsPair({ learning, reference })) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const direction = String(entry?.direction || 'BOTH').toUpperCase();
|
const direction = String(entry?.direction || 'BOTH').toUpperCase();
|
||||||
@@ -61,6 +61,30 @@ export default class VocabService {
|
|||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_isInstructionLikeText(value) {
|
||||||
|
const text = String(value || '').trim();
|
||||||
|
if (!text) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const wordCount = text.split(/\s+/).filter(Boolean).length;
|
||||||
|
if (wordCount < 3) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return /^(sage|sag|frage|frag|bitte|stelle|sprich|erzähle|beschreibe|bilde|wähle|ordne|übersetze|nenne|nenn|beginne|verwende|reagiere|kombiniere|spiele|löse|beantworte|ergänze|formuliere)\b/i.test(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
_isTrainableSrsPair(entry) {
|
||||||
|
const learning = String(entry?.learning || '').trim();
|
||||||
|
const reference = String(entry?.reference || '').trim();
|
||||||
|
if (!learning || !reference || this._normalizeSrsText(learning) === this._normalizeSrsText(reference)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return !this._isInstructionLikeText(learning) && !this._isInstructionLikeText(reference);
|
||||||
|
}
|
||||||
|
|
||||||
_calculateSrsSchedule(item, { correct, rating = null } = {}) {
|
_calculateSrsSchedule(item, { correct, rating = null } = {}) {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const previousStage = Math.max(0, Number(item?.stage) || 0);
|
const previousStage = Math.max(0, Number(item?.stage) || 0);
|
||||||
@@ -459,16 +483,17 @@ export default class VocabService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async _courseHasDueSrsItems(userId, courseId) {
|
async _courseHasDueSrsItems(userId, courseId) {
|
||||||
const due = await VocabSrsItem.count({
|
const dueItems = await VocabSrsItem.findAll({
|
||||||
where: {
|
where: {
|
||||||
userId,
|
userId,
|
||||||
courseId: Number(courseId),
|
courseId: Number(courseId),
|
||||||
nextDueAt: {
|
nextDueAt: {
|
||||||
[Op.lte]: new Date()
|
[Op.lte]: new Date()
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
attributes: ['learning', 'reference']
|
||||||
});
|
});
|
||||||
return due > 0;
|
return dueItems.some((item) => this._isTrainableSrsPair(item));
|
||||||
}
|
}
|
||||||
|
|
||||||
async _getUserByHashedId(hashedUserId) {
|
async _getUserByHashedId(hashedUserId) {
|
||||||
@@ -713,7 +738,7 @@ export default class VocabService {
|
|||||||
const question = String(qData.question || qData.text || '');
|
const question = String(qData.question || qData.text || '');
|
||||||
|
|
||||||
let match = question.match(/Wie sagt man ['"]([^'"]+)['"]/i);
|
let match = question.match(/Wie sagt man ['"]([^'"]+)['"]/i);
|
||||||
if (match && match[1] && correctAnswer && match[1].trim() !== String(correctAnswer).trim()) {
|
if (match && this._isTrainableSrsPair({ learning: match[1], reference: String(correctAnswer) })) {
|
||||||
vocabMap.set(`${match[1]}-${correctAnswer}`, {
|
vocabMap.set(`${match[1]}-${correctAnswer}`, {
|
||||||
learning: match[1],
|
learning: match[1],
|
||||||
reference: String(correctAnswer)
|
reference: String(correctAnswer)
|
||||||
@@ -722,7 +747,7 @@ export default class VocabService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
match = question.match(/Was bedeutet ['"]([^'"]+)['"]/i);
|
match = question.match(/Was bedeutet ['"]([^'"]+)['"]/i);
|
||||||
if (match && match[1] && correctAnswer && match[1].trim() !== String(correctAnswer).trim()) {
|
if (match && this._isTrainableSrsPair({ learning: String(correctAnswer), reference: match[1] })) {
|
||||||
vocabMap.set(`${correctAnswer}-${match[1]}`, {
|
vocabMap.set(`${correctAnswer}-${match[1]}`, {
|
||||||
learning: String(correctAnswer),
|
learning: String(correctAnswer),
|
||||||
reference: match[1]
|
reference: match[1]
|
||||||
@@ -745,7 +770,7 @@ export default class VocabService {
|
|||||||
answers.forEach((answer, index) => {
|
answers.forEach((answer, index) => {
|
||||||
const nativeWord = nativeWords[index];
|
const nativeWord = nativeWords[index];
|
||||||
const normalizedAnswer = String(answer || '').trim();
|
const normalizedAnswer = String(answer || '').trim();
|
||||||
if (!nativeWord || !normalizedAnswer || nativeWord === normalizedAnswer) {
|
if (!this._isTrainableSrsPair({ learning: nativeWord, reference: normalizedAnswer })) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
vocabMap.set(`${nativeWord}-${normalizedAnswer}`, {
|
vocabMap.set(`${nativeWord}-${normalizedAnswer}`, {
|
||||||
@@ -764,31 +789,13 @@ export default class VocabService {
|
|||||||
|
|
||||||
_extractTrainerVocabsFromLessonDidactics(lesson) {
|
_extractTrainerVocabsFromLessonDidactics(lesson) {
|
||||||
const vocabMap = new Map();
|
const vocabMap = new Map();
|
||||||
const speakingPrompts = Array.isArray(lesson?.speakingPrompts) ? lesson.speakingPrompts : [];
|
|
||||||
const practicalTasks = Array.isArray(lesson?.practicalTasks) ? lesson.practicalTasks : [];
|
|
||||||
const corePatterns = Array.isArray(lesson?.corePatterns) ? lesson.corePatterns : [];
|
const corePatterns = Array.isArray(lesson?.corePatterns) ? lesson.corePatterns : [];
|
||||||
|
|
||||||
corePatterns.forEach((entry) => {
|
corePatterns.forEach((entry) => {
|
||||||
const pattern = this._normalizeCorePatternEntry(entry);
|
const pattern = this._normalizeCorePatternEntry(entry);
|
||||||
const reference = String(pattern?.target || '').trim();
|
const reference = String(pattern?.target || '').trim();
|
||||||
const learning = String(pattern?.gloss || '').trim();
|
const learning = String(pattern?.gloss || '').trim();
|
||||||
if (!learning || !reference || learning === reference) return;
|
if (!this._isTrainableSrsPair({ learning, reference })) return;
|
||||||
vocabMap.set(`${learning}-${reference}`, { learning, reference });
|
|
||||||
});
|
|
||||||
|
|
||||||
speakingPrompts.forEach((prompt, index) => {
|
|
||||||
const learning = String(prompt?.prompt || prompt?.title || '').trim();
|
|
||||||
const refEntry = corePatterns[index] ?? corePatterns[0];
|
|
||||||
const reference = String(prompt?.cue || this._corePatternTarget(refEntry) || '').trim();
|
|
||||||
if (!learning || !reference || learning === reference) return;
|
|
||||||
vocabMap.set(`${learning}-${reference}`, { learning, reference });
|
|
||||||
});
|
|
||||||
|
|
||||||
practicalTasks.forEach((task, index) => {
|
|
||||||
const learning = String(task?.text || task?.title || '').trim();
|
|
||||||
const refEntry = corePatterns[index] ?? corePatterns[0];
|
|
||||||
const reference = String(this._corePatternTarget(refEntry) || '').trim();
|
|
||||||
if (!learning || !reference || learning === reference) return;
|
|
||||||
vocabMap.set(`${learning}-${reference}`, { learning, reference });
|
vocabMap.set(`${learning}-${reference}`, { learning, reference });
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1808,16 +1815,17 @@ export default class VocabService {
|
|||||||
[Op.lte]: now
|
[Op.lte]: now
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const totalDueCount = await VocabSrsItem.count({ where: dueWhere });
|
const dueRows = await VocabSrsItem.findAll({
|
||||||
const rows = await VocabSrsItem.findAll({
|
|
||||||
where: dueWhere,
|
where: dueWhere,
|
||||||
order: [
|
order: [
|
||||||
['nextDueAt', 'ASC'],
|
['nextDueAt', 'ASC'],
|
||||||
['wrongCount', 'DESC'],
|
['wrongCount', 'DESC'],
|
||||||
['stage', 'ASC']
|
['stage', 'ASC']
|
||||||
],
|
]
|
||||||
limit
|
|
||||||
});
|
});
|
||||||
|
const validDueRows = dueRows.filter((item) => this._isTrainableSrsPair(item));
|
||||||
|
const rows = validDueRows.slice(0, limit);
|
||||||
|
const totalDueCount = validDueRows.length;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
courseId: course.id,
|
courseId: course.id,
|
||||||
|
|||||||
Reference in New Issue
Block a user