@khanacademy/perseus-score 7.7.10 → 8.0.0

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.d.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  export { default as KhanAnswerTypes } from "./util/answer-types";
2
2
  export type { Score } from "./util/answer-types";
3
- export { default as ErrorCodes } from "./error-codes";
4
3
  export { default as scoreCategorizer } from "./widgets/categorizer/score-categorizer";
5
4
  export { default as validateCategorizer } from "./widgets/categorizer/validate-categorizer";
6
5
  export { default as scoreCSProgram } from "./widgets/cs-program/score-cs-program";
package/dist/index.js CHANGED
@@ -30,13 +30,11 @@ function _interopNamespaceCompat(e) {
30
30
  var KAS__namespace = /*#__PURE__*/_interopNamespaceCompat(KAS);
31
31
  var ___default = /*#__PURE__*/_interopDefaultCompat(_);
32
32
 
33
- const APPROXIMATED_PI_ERROR="APPROXIMATED_PI_ERROR";const CHOOSE_CORRECT_NUM_ERROR="CHOOSE_CORRECT_NUM_ERROR";const EXTRA_SYMBOLS_ERROR="EXTRA_SYMBOLS_ERROR";const FILL_ALL_CELLS_ERROR="FILL_ALL_CELLS_ERROR";const INVALID_SELECTION_ERROR="INVALID_SELECTION_ERROR";const MISSING_PERCENT_ERROR="MISSING_PERCENT_ERROR";const MULTIPLICATION_SIGN_ERROR="MULTIPLICATION_SIGN_ERROR";const NEEDS_TO_BE_SIMPLIFIED_ERROR="NEEDS_TO_BE_SIMPLIFIED_ERROR";const NOT_NONE_ABOVE_ERROR="NOT_NONE_ABOVE_ERROR";const USER_INPUT_EMPTY="USER_INPUT_EMPTY";const USER_INPUT_TOO_LONG="USER_INPUT_TOO_LONG";const WRONG_CASE_ERROR="WRONG_CASE_ERROR";const WRONG_LETTER_ERROR="WRONG_LETTER_ERROR";const ErrorCodes={APPROXIMATED_PI_ERROR,CHOOSE_CORRECT_NUM_ERROR,EXTRA_SYMBOLS_ERROR,FILL_ALL_CELLS_ERROR,INVALID_SELECTION_ERROR,MISSING_PERCENT_ERROR,MULTIPLICATION_SIGN_ERROR,NEEDS_TO_BE_SIMPLIFIED_ERROR,NOT_NONE_ABOVE_ERROR,USER_INPUT_EMPTY,USER_INPUT_TOO_LONG,WRONG_CASE_ERROR,WRONG_LETTER_ERROR};
34
-
35
- const MAXERROR_EPSILON=Math.pow(2,-42);const KhanAnswerTypes={predicate:{defaultForms:"integer, proper, improper, mixed, decimal",createValidatorFunctional:function(predicate,options){options=___default.default.extend({simplify:"required",ratio:false,forms:KhanAnswerTypes.predicate.defaultForms},options);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;}options.maxError=+options.maxError+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,options.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=ErrorCodes.MISSING_PERCENT_ERROR;}else {if(options.simplify!=="enforced"){score.empty=true;}score.message=ErrorCodes.NEEDS_TO_BE_SIMPLIFIED_ERROR;}return false}if(piApprox&&predicate(val,Math.abs(val*.001))){score.empty=true;score.message=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=ErrorCodes.EXTRA_SYMBOLS_ERROR;return score}}return score}}},number:{convertToPredicate:function(correctAnswer,options){const correctFloat=parseFloat(correctAnswer);return [function(guess,maxError){return Math.abs(guess-correctFloat)<maxError},{...options,type:"predicate"}]},createValidatorFunctional:function(correctAnswer,options){return KhanAnswerTypes.predicate.createValidatorFunctional(...KhanAnswerTypes.number.convertToPredicate(correctAnswer,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?ErrorCodes.WRONG_CASE_ERROR: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=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}}}};
33
+ const MAXERROR_EPSILON=Math.pow(2,-42);const KhanAnswerTypes={predicate:{defaultForms:"integer, proper, improper, mixed, decimal",createValidatorFunctional:function(predicate,options){options=___default.default.extend({simplify:"required",ratio:false,forms:KhanAnswerTypes.predicate.defaultForms},options);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;}options.maxError=+options.maxError+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,options.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:{convertToPredicate:function(correctAnswer,options){const correctFloat=parseFloat(correctAnswer);return [function(guess,maxError){return Math.abs(guess-correctFloat)<maxError},{...options,type:"predicate"}]},createValidatorFunctional:function(correctAnswer,options){return KhanAnswerTypes.predicate.createValidatorFunctional(...KhanAnswerTypes.number.convertToPredicate(correctAnswer,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}}}};
36
34
 
37
35
  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}}
38
36
 
39
- function validateCategorizer(userInput,validationData){if(userInput==null){return {type:"invalid",message:null}}const incomplete=validationData.items.some((_,i)=>userInput.values[i]==null);if(incomplete){return {type:"invalid",message:ErrorCodes.INVALID_SELECTION_ERROR}}return null}
37
+ function validateCategorizer(userInput,validationData){if(userInput==null){return {type:"invalid",message:null}}const incomplete=validationData.items.some((_,i)=>userInput.values[i]==null);if(incomplete){return {type:"invalid",message:perseusCore.ErrorCodes.INVALID_SELECTION_ERROR}}return null}
40
38
 
41
39
  function scoreCSProgram(userInput){if(userInput==null){return {type:"invalid",message:null}}if(userInput.status==="correct"){return {type:"points",earned:1,total:1,message:userInput.message||null}}if(userInput.status==="incorrect"){return {type:"points",earned:0,total:1,message:userInput.message||null}}return {type:"invalid",message:"Keep going, you're not there yet!"}}
42
40
 
@@ -44,7 +42,7 @@ function scoreDropdown(userInput,rubric){if(userInput==null){return {type:"inval
44
42
 
45
43
  function validateDropdown(userInput){if(userInput==null){return {type:"invalid",message:null}}if(userInput.value===0){return {type:"invalid",message:null}}return null}
46
44
 
47
- function scoreExpression(userInput,rubric,locale){if(userInput==null){return {type:"invalid",message:null}}const options=___default.default.clone(rubric);___default.default.extend(options,{decimal_separator:perseusCore.getDecimalSeparator(locale),divide_symbol:perseusCore.getDivideSymbol(locale)});if(!KAS__namespace.parse(userInput,options).parsed){return {type:"invalid",message:ErrorCodes.EXTRA_SYMBOLS_ERROR}}const createValidator=answer=>{const expression=KAS__namespace.parse(answer.value,rubric);if(!expression.parsed){throw new perseusCore.PerseusError("Unable to parse solution answer for expression",perseusCore.Errors.InvalidInput,{metadata:{rubric:JSON.stringify(rubric)}})}return KhanAnswerTypes.expression.createValidatorFunctional(expression.expr,___default.default({}).extend(options,{simplify:answer.simplify,form:answer.form}))};let matchingAnswerForm;let matchMessage;let allEmpty=true;let firstUngradedResult;for(const answerForm of rubric.answerForms||[]){const validator=createValidator(answerForm);if(!validator){continue}const result=validator(userInput);if(result.correct){matchingAnswerForm=answerForm;matchMessage=result.message||"";break}allEmpty=allEmpty&&result.empty;if(answerForm.considered==="correct"&&result.ungraded&&!firstUngradedResult){firstUngradedResult=result;}}if(!matchingAnswerForm){if(firstUngradedResult){return {type:"invalid",message:firstUngradedResult.message,suppressAlmostThere:firstUngradedResult.suppressAlmostThere}}if(allEmpty){return {type:"invalid",message:null}}return {type:"points",earned:0,total:1}}if(matchingAnswerForm.considered==="ungraded"){return {type:"invalid",message:matchMessage}}return {type:"points",earned:matchingAnswerForm.considered==="correct"?1:0,total:1,message:matchMessage}}
45
+ function scoreExpression(userInput,rubric,locale){if(userInput==null){return {type:"invalid",message:null}}const options=___default.default.clone(rubric);___default.default.extend(options,{decimal_separator:perseusCore.getDecimalSeparator(locale),divide_symbol:perseusCore.getDivideSymbol(locale)});if(!KAS__namespace.parse(userInput,options).parsed){return {type:"invalid",message:perseusCore.ErrorCodes.EXTRA_SYMBOLS_ERROR}}const createValidator=answer=>{const expression=KAS__namespace.parse(answer.value,rubric);if(!expression.parsed){throw new perseusCore.PerseusError("Unable to parse solution answer for expression",perseusCore.Errors.InvalidInput,{metadata:{rubric:JSON.stringify(rubric)}})}return KhanAnswerTypes.expression.createValidatorFunctional(expression.expr,___default.default({}).extend(options,{simplify:answer.simplify,form:answer.form}))};let matchingAnswerForm;let matchMessage;let allEmpty=true;let firstUngradedResult;for(const answerForm of rubric.answerForms||[]){const validator=createValidator(answerForm);if(!validator){continue}const result=validator(userInput);if(result.correct){matchingAnswerForm=answerForm;matchMessage=result.message||"";break}allEmpty=allEmpty&&result.empty;if(answerForm.considered==="correct"&&result.ungraded&&!firstUngradedResult){firstUngradedResult=result;}}if(!matchingAnswerForm){if(firstUngradedResult){return {type:"invalid",message:firstUngradedResult.message,suppressAlmostThere:firstUngradedResult.suppressAlmostThere}}if(allEmpty){return {type:"invalid",message:null}}return {type:"points",earned:0,total:1}}if(matchingAnswerForm.considered==="ungraded"){return {type:"invalid",message:matchMessage}}return {type:"points",earned:matchingAnswerForm.considered==="correct"?1:0,total:1,message:matchMessage}}
48
46
 
49
47
  function validateExpression(userInput){if(userInput===""||userInput==null){return {type:"invalid",message:null}}return null}
50
48
 
@@ -60,7 +58,7 @@ function scoreMatcher(userInput,rubric){if(userInput==null){return {type:"invali
60
58
 
61
59
  function scoreMatrix(userInput,rubric){if(userInput==null){return {type:"invalid",message:null}}const solution=rubric.answers;const supplied=userInput.answers;const solutionSize=perseusCore.getMatrixSize(solution);const suppliedSize=perseusCore.getMatrixSize(supplied);const incorrectSize=solutionSize[0]!==suppliedSize[0]||solutionSize[1]!==suppliedSize[1];const createValidator=KhanAnswerTypes.number.createValidatorFunctional;let message=null;let incorrect=false;___default.default(suppliedSize[0]).times(row=>{___default.default(suppliedSize[1]).times(col=>{if(!incorrectSize){const validator=createValidator(solution[row][col],{simplify:true});const result=validator(supplied[row][col]);if(result.message){message=result.message;}if(!result.correct){incorrect=true;}}});});if(incorrectSize){return {type:"points",earned:0,total:1,message:null}}return {type:"points",earned:incorrect?0:1,total:1,message:message}}
62
60
 
63
- function validateMatrix(userInput){if(userInput==null){return {type:"invalid",message:null}}const supplied=userInput.answers;const suppliedSize=perseusCore.getMatrixSize(supplied);for(let row=0;row<suppliedSize[0];row++){for(let col=0;col<suppliedSize[1];col++){const rowData=supplied[row];const cellValue=rowData?.[col];if(cellValue==null||cellValue.toString().length===0){return {type:"invalid",message:ErrorCodes.FILL_ALL_CELLS_ERROR}}}}return null}
61
+ function validateMatrix(userInput){if(userInput==null){return {type:"invalid",message:null}}const supplied=userInput.answers;const suppliedSize=perseusCore.getMatrixSize(supplied);for(let row=0;row<suppliedSize[0];row++){for(let col=0;col<suppliedSize[1];col++){const rowData=supplied[row];const cellValue=rowData?.[col];if(cellValue==null||cellValue.toString().length===0){return {type:"invalid",message:perseusCore.ErrorCodes.FILL_ALL_CELLS_ERROR}}}}return null}
64
62
 
65
63
  function scoreNumberLine(userInput,rubric){if(userInput==null){return {type:"invalid",message:null}}const divisionRange=rubric.divisionRange;const outsideAllowedRange=userInput.numDivisions>divisionRange[1]||userInput.numDivisions<divisionRange[0];if(rubric.isTickCtrl&&outsideAllowedRange){return {type:"invalid",message:"Number of divisions is outside the allowed range."}}const range=rubric.range;const start=rubric.initialX!=null?rubric.initialX:range[0];const startRel=rubric.isInequality?"ge":"eq";const correctRel=rubric.correctRel||"eq";const correctPos=kmath.number.equal(userInput.numLinePosition,rubric.correctX||0);if(correctPos&&correctRel===userInput.rel){return {type:"points",earned:1,total:1,message:null}}if(userInput.numLinePosition===start&&userInput.rel===startRel){return {type:"invalid",message:null}}return {type:"points",earned:0,total:1,message:null}}
66
64
 
@@ -76,7 +74,7 @@ function scorePlotter(userInput,rubric){return {type:"points",earned:perseusCore
76
74
 
77
75
  function validatePlotter(userInput,validationData){if(userInput==null||perseusCore.approximateDeepEqual(userInput,validationData.starting)){return {type:"invalid",message:null}}return null}
78
76
 
79
- function scoreRadio(userInput,rubric){if(userInput==null){return {type:"invalid",message:null}}const invalidIds=userInput.selectedChoiceIds.filter(id=>!rubric.choices.some(choice=>choice.id===id));if(invalidIds.length>0){return {type:"invalid",message:"Invalid choice selection"}}const numSelected=userInput.selectedChoiceIds.length;const numCorrect=rubric.choices.reduce((sum,currentChoice)=>{return currentChoice.correct?sum+1:sum},0);if(numCorrect>1&&numSelected!==numCorrect&&rubric.countChoices){return {type:"invalid",message:ErrorCodes.CHOOSE_CORRECT_NUM_ERROR}}const noneOfTheAboveSelected=rubric.choices.some(choice=>choice.isNoneOfTheAbove&&userInput.selectedChoiceIds.includes(choice.id));if(noneOfTheAboveSelected&&numSelected>1){return {type:"invalid",message:ErrorCodes.NOT_NONE_ABOVE_ERROR}}const correct=rubric.choices.every(choice=>{const isSelected=userInput.selectedChoiceIds.includes(choice.id);const isCorrect=!!choice.correct;return isCorrect===isSelected});return {type:"points",earned:correct?1:0,total:1,message:null}}
77
+ function scoreRadio(userInput,rubric){if(userInput==null){return {type:"invalid",message:null}}const invalidIds=userInput.selectedChoiceIds.filter(id=>!rubric.choices.some(choice=>choice.id===id));if(invalidIds.length>0){return {type:"invalid",message:perseusCore.ErrorCodes.INVALID_CHOICE_SELECTION}}const numSelected=userInput.selectedChoiceIds.length;const numCorrect=rubric.choices.reduce((sum,currentChoice)=>{return currentChoice.correct?sum+1:sum},0);if(numCorrect>1&&numSelected!==numCorrect&&rubric.countChoices){return {type:"invalid",message:perseusCore.ErrorCodes.CHOOSE_CORRECT_NUM_ERROR}}const noneOfTheAboveSelected=rubric.choices.some(choice=>choice.isNoneOfTheAbove&&userInput.selectedChoiceIds.includes(choice.id));if(noneOfTheAboveSelected&&numSelected>1){return {type:"invalid",message:perseusCore.ErrorCodes.NOT_NONE_ABOVE_ERROR}}const correct=rubric.choices.every(choice=>{const isSelected=userInput.selectedChoiceIds.includes(choice.id);const isCorrect=!!choice.correct;return isCorrect===isSelected});return {type:"points",earned:correct?1:0,total:1,message:null}}
80
78
 
81
79
  function validateRadio(userInput){if(userInput==null||userInput.selectedChoiceIds.length===0){return {type:"invalid",message:null}}return null}
82
80
 
@@ -96,7 +94,7 @@ function scoreNoop(points=0){return {type:"points",earned:points,total:points,me
96
94
 
97
95
  function scoreFreeResponse(userInput,rubric,locale){return scoreNoop()}
98
96
 
99
- function validateFreeResponse(userInput,widgetOptions){const userInputLength=userInput?.currentValue.trim().length??0;if(userInputLength===0){return {type:"invalid",message:ErrorCodes.USER_INPUT_EMPTY}}if(!widgetOptions.allowUnlimitedCharacters&&userInputLength>widgetOptions.characterLimit){return {type:"invalid",message:ErrorCodes.USER_INPUT_TOO_LONG}}return null}
97
+ function validateFreeResponse(userInput,widgetOptions){const userInputLength=userInput?.currentValue.trim().length??0;if(userInputLength===0){return {type:"invalid",message:perseusCore.ErrorCodes.USER_INPUT_EMPTY}}if(!widgetOptions.allowUnlimitedCharacters&&userInputLength>widgetOptions.characterLimit){return {type:"invalid",message:perseusCore.ErrorCodes.USER_INPUT_TOO_LONG}}return null}
100
98
 
101
99
  function scoreGroup(userInput,rubric,locale){if(userInput==null){return {type:"invalid",message:null}}const scores=scoreWidgetsFunctional(rubric.widgets,Object.keys(rubric.widgets),userInput,locale);return flattenScores(scores)}
102
100
 
@@ -116,7 +114,6 @@ const noScore={type:"points",earned:0,total:0,message:null};function scoreIsEmpt
116
114
 
117
115
  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}
118
116
 
119
- exports.ErrorCodes = ErrorCodes;
120
117
  exports.KhanAnswerTypes = KhanAnswerTypes;
121
118
  exports.emptyWidgetsFunctional = emptyWidgetsFunctional;
122
119
  exports.flattenScores = flattenScores;