UNPKG

@coorpacademy/progression-engine

Version:

145 lines (144 loc) 5.57 kB
import _zip from "lodash/fp/zip"; import _trim from "lodash/fp/trim"; import _toLower from "lodash/fp/toLower"; import _split from "lodash/fp/split"; import _some from "lodash/fp/some"; import _reverse from "lodash/fp/reverse"; import _pipe from "lodash/fp/pipe"; import _maxBy from "lodash/fp/maxBy"; import _map from "lodash/fp/map"; import _join from "lodash/fp/join"; import _includes from "lodash/fp/includes"; import _get from "lodash/fp/get"; import _filter from "lodash/fp/filter"; import _every from "lodash/fp/every"; import FuzzyMatching from 'fuzzy-matching'; const reverseString = _pipe(_split(''), _reverse, _join('')); function checkFuzzyAnswer(maxTypos, fm, userAnswer) { if (!userAnswer || userAnswer.length === 0) { return false; } // Find a valid answer resembling userAnswer return !!fm.get(userAnswer, { maxChanges: maxTypos }).value; } function containsAnswer(config, allowedAnswer, givenAnswer) { // Find the allowed answer in the given answer if (!_includes(allowedAnswer, givenAnswer)) { // If not present return false; } // Get the non-space characters surrounding the answer and make sure that there are not too many. const limit = config.answerBoundaryLimit; const [first = '', second = ''] = givenAnswer.split(allowedAnswer); const indexOfSpaceInFirst = reverseString(first).indexOf(' '); const indexOfSpaceInSecond = second.indexOf(' '); return (first.length <= limit || indexOfSpaceInFirst !== -1 && indexOfSpaceInFirst <= limit) && (second.length <= limit || indexOfSpaceInSecond !== -1 && indexOfSpaceInSecond <= limit); } function isTextCorrect(config, _allowedAnswers, answerWithCase, _maxTypos, noFuzzy) { const allowedAnswers = _allowedAnswers.map(_trim); const fm = new FuzzyMatching(allowedAnswers); const maxTypos = _maxTypos === 0 ? _maxTypos : _maxTypos || config.maxTypos; if (noFuzzy) { return _some(allowedAnswer => allowedAnswer === answerWithCase, allowedAnswers); } const answerWithoutCase = _toLower(answerWithCase); return checkFuzzyAnswer(maxTypos, fm, answerWithCase) || maxTypos !== 0 && _some(allowedAnswer => containsAnswer(config, _toLower(allowedAnswer), answerWithoutCase), allowedAnswers); } function matchAnswerForBasic(config, question, givenAnswer) { if (question.content.answers.length === 0) { return []; } const isCorrect = isTextCorrect(config, question.content.answers.map(answers => answers[0]), givenAnswer[0], question.content.maxTypos, false); return [[{ answer: givenAnswer[0], isCorrect }]]; } function matchAnswerForTemplate(config, question, givenAnswer) { if (question.content.answers.length === 0) { return []; } const result = givenAnswer.map((answer, index) => ({ answer, isCorrect: question.content.answers.some(allowedAnswer => isTextCorrect(config, [allowedAnswer[index]], answer, _get(['content', 'choices', `${index}`, 'type'], question) === 'text' ? question.content.maxTypos : 0, _get(['content', 'choices', `${index}`, 'type'], question) !== 'text')) })); const missingAnswers = question.content.answers[0].slice(result.length).map(() => ({ answer: undefined, isCorrect: false })); return [result.concat(missingAnswers)]; } function matchAnswerForUnorderedItems(allowedAnswers, givenAnswer) { return allowedAnswers.map(allowedAnswer => { const givenAnswersMap = _map(answer => ({ answer, isCorrect: _includes(answer, allowedAnswer) }), givenAnswer); if (allowedAnswer.some(answer => !_includes(answer, givenAnswer))) { return givenAnswersMap.concat([{ answer: undefined, isCorrect: false }]); } return givenAnswersMap; }); } function matchAnswerForOrderedItems(allowedAnswers, givenAnswer) { return _map(allowedAnswer => { return _map(([givenAnswerPart, allowedAnswerPart]) => { return { answer: givenAnswerPart, isCorrect: givenAnswerPart === allowedAnswerPart }; }, _zip(givenAnswer, allowedAnswer)); }, allowedAnswers); } const findBestMatch = _maxBy(correction => _filter('isCorrect', correction).length); function matchGivenAnswerToQuestion(config, question, givenAnswer) { const allowedAnswers = question.content.answers.map(answer => answer.map(_trim)); switch (question.type) { case 'basic': { return matchAnswerForBasic(config, question, givenAnswer); } case 'template': { return matchAnswerForTemplate(config, question, givenAnswer); } case 'qcm': { return matchAnswerForUnorderedItems(allowedAnswers, givenAnswer); } case 'qcmGraphic': { return matchAnswerForUnorderedItems(allowedAnswers, givenAnswer); } case 'qcmDrag': { return question.content.matchOrder ? matchAnswerForOrderedItems(allowedAnswers, givenAnswer) : matchAnswerForUnorderedItems(allowedAnswers, givenAnswer); } case 'slider': { return matchAnswerForOrderedItems(allowedAnswers, givenAnswer); } default: return [[]]; } } export default function checkAnswerCorrectness(config, question, givenAnswer) { const matches = matchGivenAnswerToQuestion(config, question, givenAnswer.map(_trim)); if (matches.length === 0) { return { isCorrect: false, corrections: [] }; } const bestMatch = findBestMatch(matches); return { isCorrect: _every('isCorrect', bestMatch), corrections: _filter(item => item.answer !== undefined, bestMatch) }; } //# sourceMappingURL=check-answer-correctness.js.map