@khanacademy/perseus-score 8.2.8 → 8.2.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js
CHANGED
|
@@ -31,9 +31,9 @@ function _interopNamespaceCompat(e) {
|
|
|
31
31
|
var KAS__namespace = /*#__PURE__*/_interopNamespaceCompat(KAS);
|
|
32
32
|
var ___default = /*#__PURE__*/_interopDefaultCompat(_);
|
|
33
33
|
|
|
34
|
-
const libName="@khanacademy/perseus-score";const libVersion="8.2.
|
|
34
|
+
const libName="@khanacademy/perseus-score";const libVersion="8.2.10";perseusUtils.addLibraryVersionToPerseusDebug(libName,libVersion);
|
|
35
35
|
|
|
36
|
-
const MAXERROR_EPSILON=Math.pow(2,-42);const KhanAnswerTypes={predicate:{defaultForms:"integer, proper, improper, mixed, decimal",createValidatorFunctional:function(predicate,
|
|
36
|
+
const MAXERROR_EPSILON=Math.pow(2,-42);const KhanAnswerTypes={predicate:{defaultForms:"integer, proper, improper, mixed, decimal",createValidatorFunctional:function(predicate,rawOptions){const options={simplify:"required",ratio:false,forms:KhanAnswerTypes.predicate.defaultForms,...rawOptions};let acceptableForms;if(!___default.default.isArray(options.forms)){acceptableForms=options.forms.split(/\s*,\s*/);}else {acceptableForms=options.forms;}if(options.inexact===undefined){options.maxError=0;}const maxError=+(options.maxError??0)+MAXERROR_EPSILON;if(___default.default.contains(acceptableForms,"percent")){acceptableForms=___default.default.without(acceptableForms,"percent");acceptableForms.push("percent");}const fractionTransformer=function(text){text=text.replace(/\u2212/,"-").replace(/([+-])\s+/g,"$1").replace(/(^\s*)|(\s*$)/gi,"");const match=text.match(/^([+-]?\d+)\s*\/\s*([+-]?\d+)$/);const mobileDeviceMatch=text.match(/^([+-]?)\\frac\{([+-]?\d+)\}\{([+-]?\d+)\}$/);const parsedInt=parseInt(text,10);if(match||mobileDeviceMatch){let num;let denom;let simplified=true;if(match){num=parseFloat(match[1]);denom=parseFloat(match[2]);}else {num=parseFloat(mobileDeviceMatch[2]);if(mobileDeviceMatch[1]==="-"){if(num<0){simplified=false;}num=-num;}denom=parseFloat(mobileDeviceMatch[3]);}simplified=simplified&&denom>0&&(options.ratio||denom!==1)&&kmath.KhanMath.getGCD(num,denom)===1;return [{value:num/denom,exact:simplified}]}if(!isNaN(parsedInt)&&""+parsedInt===text){return [{value:parsedInt,exact:true}]}return []};const forms={integer:function(text){const decimal=forms.decimal(text);const rounded=forms.decimal(text,1);if(decimal[0]?.value!=null&&decimal[0].value===rounded[0]?.value||decimal[1]?.value!=null&&decimal[1].value===rounded[1]?.value){return decimal}return []},proper:function(text){const transformed=fractionTransformer(text);return transformed.flatMap(o=>{if(Math.abs(o.value)<1){return [o]}return []})},improper:function(text){const fractionExists=text.includes("/")||text.match(/\\(d?frac)/);if(!fractionExists){return []}const transformed=fractionTransformer(text);return transformed.flatMap(o=>{if(Math.abs(o.value)>=1){return [o]}return []})},pi:function(text){let match;let possibilities=[];text=text.replace(/\u2212/,"-");if(match=text.match(/^([+-]?)\s*(\\?pi|p|\u03c0|\\?tau|t|\u03c4|pau)$/i)){possibilities=[{value:parseFloat(match[1]+"1"),exact:true}];}else if(match=text.match(/^([+-]?\s*\d+\s*(?:\/\s*[+-]?\s*\d+)?)\s*\*?\s*(\\?pi|p|\u03c0|\\?tau|t|\u03c4|pau)$/i)){possibilities=fractionTransformer(match[1]);}else if(match=text.match(/^([+-]?)\s*(\d+)\s*([+-]?\d+)\s*\/\s*([+-]?\d+)\s*\*?\s*(\\?pi|p|\u03c0|\\?tau|t|\u03c4|pau)$/i)){const sign=parseFloat(match[1]+"1");const integ=parseFloat(match[2]);const num=parseFloat(match[3]);const denom=parseFloat(match[4]);const simplified=num<denom&&kmath.KhanMath.getGCD(num,denom)===1;possibilities=[{value:sign*(integ+num/denom),exact:simplified}];}else if(match=text.match(/^([+-]?\s*\d+)\s*\*?\s*(\\?pi|p|\u03c0|\\?tau|t|\u03c4|pau)\s*(?:\/\s*([+-]?\s*\d+))?$/i)){possibilities=fractionTransformer(match[1]+"/"+match[3]);}else if(match=text.match(/^([+-]?)\s*\*?\s*(\\?pi|p|\u03c0|\\?tau|t|\u03c4|pau)\s*(?:\/\s*([+-]?\d+))?$/i)){possibilities=fractionTransformer(match[1]+"1/"+match[3]);}else if(text==="0"){possibilities=[{value:0,exact:true}];}else if(match=text.match(/^(.+)\s*\*?\s*(\\?pi|p|\u03c0|\\?tau|t|\u03c4|pau)$/i)){possibilities=forms.decimal(match[1]);}else {possibilities=___default.default.reduce(KhanAnswerTypes.predicate.defaultForms.split(/\s*,\s*/),function(memo,form){return memo.concat(forms[form](text))},[]);let approximatesPi=false;const number=parseFloat(text);if(!isNaN(number)&&number!==parseInt(text)){const piMult=Math.PI/12;const roundedNumber=piMult*Math.round(number/piMult);if(Math.abs(number-roundedNumber)<.01){approximatesPi=true;}}else if(text.match(/\/\s*7/)){approximatesPi=true;}if(approximatesPi){___default.default.each(possibilities,function(possibility){possibility.piApprox=true;});}return possibilities}let multiplier=Math.PI;if(text.match(/\\?tau|t|\u03c4/)){multiplier=Math.PI*2;}if(text.match(/pau/)){multiplier=Math.PI*1.5;}possibilities.forEach(possibility=>{possibility.value*=multiplier;});return possibilities},coefficient:function(text){let possibilities=[];text=text.replace(/\u2212/,"-");if(text===""){possibilities=[{value:1,exact:true}];}else if(text==="-"){possibilities=[{value:-1,exact:true}];}return possibilities},log:function(text){let match;let possibilities=[];text=text.replace(/\u2212/,"-");text=text.replace(/[ ()]/g,"");if(match=text.match(/^log\s*(\S+)\s*$/i)){possibilities=forms.decimal(match[1]);}else if(text==="0"){possibilities=[{value:0,exact:true}];}return possibilities},percent:function(text){text=String(text).trim();let hasPercentSign=false;if(text.indexOf("%")===text.length-1){text=text.substring(0,text.length-1).trim();hasPercentSign=true;}const transformed=forms.decimal(text);transformed.forEach(t=>{t.exact=hasPercentSign;t.value=t.value/100;});return transformed},mixed:function(text){const match=text.replace(/\u2212/,"-").replace(/([+-])\s+/g,"$1").match(/^([+-]?)(\d+)\s+(\d+)\s*\/\s*(\d+)$/);if(match){const sign=parseFloat(match[1]+"1");const integ=parseFloat(match[2]);const num=parseFloat(match[3]);const denom=parseFloat(match[4]);const simplified=num<denom&&kmath.KhanMath.getGCD(num,denom)===1;return [{value:sign*(integ+num/denom),exact:simplified}]}return []},decimal:function(text,precision=1e10){const normal=function(text){text=String(text).trim();const match=text.replace(/\u2212/,"-").replace(/([+-])\s+/g,"$1").match(/^([+-]?(?:\d{1,3}(?:[, ]?\d{3})*\.?|\d{0,3}(?:[, ]?\d{3})*\.(?:\d{3}[, ]?)*\d{1,3}))$/);const badLeadingZero=text.match(/^0[0,]*,/);if(match&&!badLeadingZero){let x=parseFloat(match[1].replace(/[, ]/g,""));if(options.inexact===undefined){x=Math.round(x*precision)/precision;}return x}};const commas=function(text){text=text.replace(/([.,])/g,function(_,c){return c==="."?",":"."});return normal(text)};const results=[{value:normal(text),exact:true}];if(options.decimal_separator===","){results.push({value:commas(text),exact:true});}return results}};return function(guess){const fallback=options.fallback!=null?""+options.fallback:"";guess=String(guess).trim()||fallback;const score={empty:guess==="",correct:false,message:null,guess:guess};const findCorrectAnswer=()=>{for(const form of acceptableForms){const transformed=forms[form](guess);for(let j=0,l=transformed.length;j<l;j++){const val=transformed[j].value;const exact=transformed[j].exact;const piApprox=transformed[j].piApprox;if(predicate(val,maxError)){if(exact||options.simplify==="optional"){score.correct=true;score.message=options.message||null;score.empty=false;}else if(form==="percent"){score.empty=true;score.message=perseusCore.ErrorCodes.MISSING_PERCENT_ERROR;}else {if(options.simplify!=="enforced"){score.empty=true;}score.message=perseusCore.ErrorCodes.NEEDS_TO_BE_SIMPLIFIED_ERROR;}return false}if(piApprox&&predicate(val,Math.abs(val*.001))){score.empty=true;score.message=perseusCore.ErrorCodes.APPROXIMATED_PI_ERROR;}}}};findCorrectAnswer();if(score.correct===false){let interpretedGuess=false;___default.default.each(forms,function(form){const anyAreNaN=___default.default.any(form(guess),function(t){return t.value!=null&&!___default.default.isNaN(t.value)});if(anyAreNaN){interpretedGuess=true;}});if(!interpretedGuess){score.empty=true;score.message=perseusCore.ErrorCodes.EXTRA_SYMBOLS_ERROR;return score}}return score}}},number:{createValidatorFunctional:function(correctAnswer,options){const correctFloat=parseFloat(correctAnswer);return KhanAnswerTypes.predicate.createValidatorFunctional((guess,maxError)=>Math.abs(guess-correctFloat)<maxError,{...options})}},expression:{parseSolution:function(solutionString,options){let solution=KAS__namespace.parse(solutionString,options);if(!solution.parsed){throw new perseusCore.PerseusError("The provided solution ("+solutionString+") didn't parse.",perseusCore.Errors.InvalidInput)}else if(options.simplified&&!solution.expr.isSimplified()){throw new perseusCore.PerseusError("The provided solution ("+solutionString+") isn't fully expanded and simplified.",perseusCore.Errors.InvalidInput)}else {solution=solution.expr;}return solution},createValidatorFunctional:function(solution,options){return function(guess){const score={empty:false,correct:false,message:null,guess:guess,ungraded:false};if(!guess){score.empty=true;return score}const answer=KAS__namespace.parse(guess,options);if(!answer.parsed){score.empty=true;return score}if(typeof solution==="string"){solution=KhanAnswerTypes.expression.parseSolution(solution,options);}const result=KAS__namespace.compare(answer.expr,solution,options);if(result.equal){score.correct=true;}else if(result.wrongVariableNames||result.wrongVariableCase){score.ungraded=true;score.message=result.wrongVariableCase?perseusCore.ErrorCodes.WRONG_CASE_ERROR:perseusCore.ErrorCodes.WRONG_LETTER_ERROR;score.suppressAlmostThere=true;}else if(result.message){score.message=result.message;}else {const answerX=KAS__namespace.parse(guess.replace(/[xX]/g,"*"),options);if(answerX.parsed){const resultX=KAS__namespace.compare(answerX.expr,solution,options);if(resultX.equal){score.ungraded=true;score.message=perseusCore.ErrorCodes.MULTIPLICATION_SIGN_ERROR;}else if(resultX.message){score.message=resultX.message+" Also, I'm a computer. I only understand "+"multiplication if you use an "+"asterisk (*) as the multiplication "+"sign.";}}}return score}}}};
|
|
37
37
|
|
|
38
38
|
function scoreCategorizer(userInput,rubric){if(userInput==null){return {type:"invalid",message:null}}let allCorrect=true;rubric.values.forEach((value,i)=>{if(userInput.values[i]!==value){allCorrect=false;}});return {type:"points",earned:allCorrect?1:0,total:1,message:null}}
|
|
39
39
|
|
|
@@ -105,7 +105,7 @@ function scoreGroup(userInput,rubric,locale){if(userInput==null){return {type:"i
|
|
|
105
105
|
|
|
106
106
|
function scoreIsEmpty(score){return score.type==="invalid"&&(!score.message||score.message.length===0)}
|
|
107
107
|
|
|
108
|
-
function validateUserInput(perseusRenderData,userInputMap,locale){const
|
|
108
|
+
function validateUserInput(perseusRenderData,userInputMap,locale){const scoreableWidgetIds=getScoreableWidgets(perseusRenderData);const validationErrors={};scoreableWidgetIds.forEach(id=>{const widget=perseusRenderData.widgets[id];const userInput=userInputMap[id];const validator=getWidgetValidator(widget.type);const validationError=validator?.(userInput,widget.options,locale);if(validationError!=null){validationErrors[id]=validationError;}});return Object.keys(validationErrors).length>0?flattenScores(validationErrors):null}function emptyWidgetsFunctional(widgets,widgetIds,userInputMap,locale){return widgetIds.filter(id=>{const widget=widgets[id];if(!isWidgetScoreable(widget)){return false}const validator=getWidgetValidator(widget.type);const userInput=userInputMap[id];const validationData=widget.options;const score=validator?.(userInput,validationData,locale);if(score){return scoreIsEmpty(score)}return false})}
|
|
109
109
|
|
|
110
110
|
function validateGroup(userInput,validationData,locale){if(userInput==null){return {type:"invalid",message:null}}const widgetIds=perseusCore.getWidgetIdsFromContent(validationData.content);const emptyWidgets=emptyWidgetsFunctional(validationData.widgets,widgetIds,userInput,locale);if(emptyWidgets.length===0){return null}return {type:"invalid",message:null}}
|
|
111
111
|
|
|
@@ -119,9 +119,9 @@ const widgets=new perseusCore.Registry("Score widget registry");function registe
|
|
|
119
119
|
|
|
120
120
|
function isWidgetScoreable(widget){if(!widget){return false}const widgetIsGraded=widget.graded==null||widget.graded;const widgetIsStatic=!!widget.static;const widgetHasScorer=getWidgetScorer(widget.type)!==null;return widgetIsGraded&&!widgetIsStatic&&widgetHasScorer}
|
|
121
121
|
|
|
122
|
-
function getScoreableWidgets(perseusRenderData){const usedWidgetIds=perseusCore.getWidgetIdsFromContent(perseusRenderData.content);
|
|
122
|
+
function getScoreableWidgets(perseusRenderData){const usedWidgetIds=perseusCore.getWidgetIdsFromContent(perseusRenderData.content);return usedWidgetIds.filter(id=>isWidgetScoreable(perseusRenderData.widgets[id]))}
|
|
123
123
|
|
|
124
|
-
function scorePerseusItem(perseusRenderData,userInputMap,locale){const
|
|
124
|
+
function scorePerseusItem(perseusRenderData,userInputMap,locale){const scoreableWidgetIds=getScoreableWidgets(perseusRenderData);const scores=scoreWidgetsFunctional(perseusRenderData.widgets,scoreableWidgetIds,userInputMap,locale);return flattenScores(scores)}function scoreWidgetsFunctional(widgets,widgetIds,userInputMap,locale){const gradedWidgetIds=widgetIds.filter(id=>isWidgetScoreable(widgets[id]));const widgetScores={};gradedWidgetIds.forEach(id=>{const widget=widgets[id];const userInput=userInputMap[id];const validator=getWidgetValidator(widget.type);const scorer=getWidgetScorer(widget.type);const score=validator?.(userInput,widget.options,locale)??scorer?.(userInput,widget.options,locale);if(score!=null){widgetScores[id]=score;}});return widgetScores}
|
|
125
125
|
|
|
126
126
|
function hasEmptyDINERWidgets(itemData,userInputMap){const usedWidgetIds=perseusCore.getWidgetIdsFromContent(itemData.question.content);const widgets=itemData.question.widgets;for(const widgetId of usedWidgetIds){const widget=widgets[widgetId];const input=userInputMap[widgetId];switch(widget.type){case "dropdown":{if(input.value===0){return true}break}case "interactive-graph":{break}case "numeric-input":{if(!input.currentValue&&!widget.options.coefficient){return true}break}case "expression":{if(!input){return true}break}case "radio":{if(input.selectedChoiceIds.length===0){return true}break}}}return false}
|
|
127
127
|
|