bisaya-kurs korrigiert
All checks were successful
Deploy to production / deploy (push) Successful in 2m13s
All checks were successful
Deploy to production / deploy (push) Successful in 2m13s
This commit is contained in:
@@ -12,6 +12,7 @@
|
||||
import { Op } from 'sequelize';
|
||||
import { sequelize } from '../utils/sequelize.js';
|
||||
import VocabSrsItem from '../models/community/vocab_srs_item.js';
|
||||
import VocabGrammarExercise from '../models/community/vocab_grammar_exercise.js';
|
||||
|
||||
function parseArgs(argv) {
|
||||
return {
|
||||
@@ -76,30 +77,84 @@ function isStaleInstructionCard(left, right) {
|
||||
return isInstructionLikeText(left) || isInstructionLikeText(right);
|
||||
}
|
||||
|
||||
function normalizePairSide(value) {
|
||||
return String(value || '')
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.normalize('NFKC')
|
||||
.replace(/[\p{P}\p{S}]+/gu, ' ')
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim();
|
||||
}
|
||||
|
||||
function pairSignature(left, right) {
|
||||
return `${normalizePairSide(left)}|${normalizePairSide(right)}`;
|
||||
}
|
||||
|
||||
function extractLegacyGapFillHints(text) {
|
||||
return Array.from(String(text || '').matchAll(/\(([^)]+)\)/g), (m) => String(m[1] || '').trim()).filter(Boolean);
|
||||
}
|
||||
|
||||
function extractFixedGapFillHints(text, expectedCount = 0) {
|
||||
const source = String(text || '');
|
||||
const gapRegex = /\{\s*gap\s*\}/gi;
|
||||
const gapMatches = Array.from(source.matchAll(gapRegex));
|
||||
const hints = gapMatches.map((match, index) => {
|
||||
const start = match.index + match[0].length;
|
||||
const nextStart = index + 1 < gapMatches.length ? gapMatches[index + 1].index : source.length;
|
||||
const segment = source.slice(start, nextStart);
|
||||
const hintMatch = segment.match(/\(([^)]+)\)/);
|
||||
return String(hintMatch?.[1] || '').trim();
|
||||
}).filter(Boolean);
|
||||
return expectedCount > 0 ? hints.slice(0, expectedCount) : hints;
|
||||
}
|
||||
|
||||
async function collectStaleLegacyGapFillPairs() {
|
||||
const exercises = await VocabGrammarExercise.findAll({
|
||||
attributes: ['questionData', 'answerData'],
|
||||
});
|
||||
|
||||
const stalePairs = new Set();
|
||||
exercises.forEach((exercise) => {
|
||||
const qData = exercise.questionData || {};
|
||||
const aData = exercise.answerData || {};
|
||||
const exerciseType = qData.type || '';
|
||||
if (exerciseType !== 'gap_fill') return;
|
||||
|
||||
const answers = Array.isArray(aData.answers)
|
||||
? aData.answers
|
||||
: (aData.correct ? (Array.isArray(aData.correct) ? aData.correct : [aData.correct]) : []);
|
||||
if (!answers.length) return;
|
||||
|
||||
const text = String(qData.text || '');
|
||||
const legacyHints = extractLegacyGapFillHints(text);
|
||||
const fixedHints = extractFixedGapFillHints(text, answers.length);
|
||||
const fixedSignatures = new Set(
|
||||
fixedHints.map((hint, index) => pairSignature(hint, answers[index]))
|
||||
);
|
||||
|
||||
legacyHints.slice(0, answers.length).forEach((hint, index) => {
|
||||
const signature = pairSignature(hint, answers[index]);
|
||||
if (!fixedSignatures.has(signature)) {
|
||||
stalePairs.add(signature);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return stalePairs;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const { apply } = parseArgs(process.argv.slice(2));
|
||||
const staleLegacyGapFillPairs = await collectStaleLegacyGapFillPairs();
|
||||
const items = await VocabSrsItem.findAll({
|
||||
where: {
|
||||
[Op.or]: [
|
||||
{ learning: { [Op.like]: '%/%' } },
|
||||
{ reference: { [Op.like]: '%/%' } },
|
||||
{ learning: { [Op.like]: '%?%' } },
|
||||
{ reference: { [Op.like]: '%?%' } },
|
||||
{ learning: { [Op.iLike]: 'Begrüße %' } },
|
||||
{ reference: { [Op.iLike]: 'Begrüße %' } },
|
||||
{ learning: { [Op.iLike]: 'Begruesse %' } },
|
||||
{ reference: { [Op.iLike]: 'Begruesse %' } },
|
||||
{ learning: { [Op.iLike]: 'Drücke %' } },
|
||||
{ reference: { [Op.iLike]: 'Drücke %' } },
|
||||
{ learning: { [Op.iLike]: 'Druecke %' } },
|
||||
{ reference: { [Op.iLike]: 'Druecke %' } },
|
||||
],
|
||||
},
|
||||
order: [['id', 'ASC']],
|
||||
});
|
||||
|
||||
const matches = items.filter((item) =>
|
||||
isStaleQuestionCueCard(item.learning, item.reference) || isStaleInstructionCard(item.learning, item.reference)
|
||||
isStaleQuestionCueCard(item.learning, item.reference)
|
||||
|| isStaleInstructionCard(item.learning, item.reference)
|
||||
|| staleLegacyGapFillPairs.has(pairSignature(item.learning, item.reference))
|
||||
);
|
||||
|
||||
matches.forEach((item) => {
|
||||
|
||||
@@ -840,15 +840,14 @@ export default class VocabService {
|
||||
const answers = Array.isArray(aData.answers)
|
||||
? aData.answers
|
||||
: (aData.correct ? (Array.isArray(aData.correct) ? aData.correct : [aData.correct]) : []);
|
||||
const text = String(qData.text || '');
|
||||
const nativeWords = Array.from(text.matchAll(/\(([^)]+)\)/g), (m) => String(m[1] || '').trim());
|
||||
const hintPairs = this._extractGapFillHintPairs(String(qData.text || ''), answers.length);
|
||||
|
||||
if (!answers.length || !nativeWords.length) {
|
||||
if (!answers.length || !hintPairs.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
answers.forEach((answer, index) => {
|
||||
const nativeWord = nativeWords[index];
|
||||
const nativeWord = hintPairs[index] || '';
|
||||
const normalizedAnswer = String(answer || '').trim();
|
||||
if (!this._isTrainableSrsPair({ learning: nativeWord, reference: normalizedAnswer })) {
|
||||
return;
|
||||
@@ -867,6 +866,28 @@ export default class VocabService {
|
||||
return Array.from(vocabMap.values());
|
||||
}
|
||||
|
||||
_extractGapFillHintPairs(text, expectedCount = 0) {
|
||||
const source = String(text || '');
|
||||
if (!source) return [];
|
||||
|
||||
const gapRegex = /\{\s*gap\s*\}/gi;
|
||||
const gapMatches = Array.from(source.matchAll(gapRegex));
|
||||
if (!gapMatches.length) return [];
|
||||
|
||||
const hints = gapMatches.map((match, index) => {
|
||||
const start = match.index + match[0].length;
|
||||
const nextStart = index + 1 < gapMatches.length ? gapMatches[index + 1].index : source.length;
|
||||
const segment = source.slice(start, nextStart);
|
||||
const hintMatch = segment.match(/\(([^)]+)\)/);
|
||||
return String(hintMatch?.[1] || '').trim();
|
||||
}).filter(Boolean);
|
||||
|
||||
if (expectedCount > 0) {
|
||||
return hints.slice(0, expectedCount);
|
||||
}
|
||||
return hints;
|
||||
}
|
||||
|
||||
_extractTrainerVocabsFromLessonDidactics(lesson) {
|
||||
const vocabMap = new Map();
|
||||
const corePatterns = Array.isArray(lesson?.corePatterns) ? lesson.corePatterns : [];
|
||||
@@ -1913,7 +1934,16 @@ export default class VocabService {
|
||||
['stage', 'ASC']
|
||||
]
|
||||
});
|
||||
const validDueRows = dueRows.filter((item) => this._isTrainableSrsPair(item));
|
||||
const validPool = await this.getCompletedLessonVocabPool(hashedUserId, course.id);
|
||||
const validKeys = new Set(
|
||||
(Array.isArray(validPool?.vocabs) ? validPool.vocabs : [])
|
||||
.map((entry) => String(entry?.itemKey || '').trim())
|
||||
.filter(Boolean)
|
||||
);
|
||||
const validDueRows = dueRows.filter((item) =>
|
||||
this._isTrainableSrsPair(item)
|
||||
&& (!validKeys.size || validKeys.has(String(item.itemKey || '').trim()))
|
||||
);
|
||||
const rows = validDueRows.slice(0, limit);
|
||||
const totalDueCount = validDueRows.length;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user