@coorpacademy/progression-engine
Version:
145 lines (144 loc) • 5.57 kB
JavaScript
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