@khanacademy/perseus-score 8.13.0 → 9.0.1

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
@@ -1,45 +1,17 @@
1
- 'use strict';
2
-
3
- Object.defineProperty(exports, '__esModule', { value: true });
4
-
5
- var perseusUtils = require('@khanacademy/perseus-utils');
6
- var KAS = require('@khanacademy/kas');
7
- var kmath = require('@khanacademy/kmath');
8
- var perseusCore = require('@khanacademy/perseus-core');
9
- var _ = require('underscore');
10
- var invariant = require('tiny-invariant');
11
-
12
- function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
13
-
14
- function _interopNamespaceCompat(e) {
15
- if (e && typeof e === 'object' && 'default' in e) return e;
16
- var n = Object.create(null);
17
- if (e) {
18
- Object.keys(e).forEach(function (k) {
19
- if (k !== 'default') {
20
- var d = Object.getOwnPropertyDescriptor(e, k);
21
- Object.defineProperty(n, k, d.get ? d : {
22
- enumerable: true,
23
- get: function () { return e[k]; }
24
- });
25
- }
26
- });
27
- }
28
- n.default = e;
29
- return Object.freeze(n);
30
- }
31
-
32
- var KAS__namespace = /*#__PURE__*/_interopNamespaceCompat(KAS);
33
- var ___default = /*#__PURE__*/_interopDefaultCompat(_);
34
- var invariant__default = /*#__PURE__*/_interopDefaultCompat(invariant);
35
-
36
- const libName="@khanacademy/perseus-score";const libVersion="8.13.0";perseusUtils.addLibraryVersionToPerseusDebug(libName,libVersion);
37
-
38
- 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(!Array.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(acceptableForms.includes("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&&!Number.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.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}}}};
1
+ import { addLibraryVersionToPerseusDebug } from '@khanacademy/perseus-utils';
2
+ import * as KAS from '@khanacademy/kas';
3
+ import { KhanMath, geometry, coefficients, angles, number } from '@khanacademy/kmath';
4
+ import { ErrorCodes, PerseusError, Errors, getDivideSymbol, getDecimalSeparator, approximateDeepEqual, GrapherUtil, approximateEqual, deepClone, getMatrixSize, exceedsCardLimit, getWidgetIdsFromContent, Registry } from '@khanacademy/perseus-core';
5
+ import _ from 'underscore';
6
+ import invariant from 'tiny-invariant';
7
+
8
+ const libName="@khanacademy/perseus-score";const libVersion="9.0.1";addLibraryVersionToPerseusDebug(libName,libVersion);
9
+
10
+ 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(!Array.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(acceptableForms.includes("percent")){acceptableForms=_.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)&&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&&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=_.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){_.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&&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=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;_.each(forms,function(form){const anyAreNaN=_.any(form(guess),function(t){return t.value!=null&&!Number.isNaN(t.value)});if(anyAreNaN){interpretedGuess=true;}});if(!interpretedGuess){score.empty=true;score.message=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.parse(solutionString,options);if(!solution.parsed){throw new PerseusError("The provided solution ("+solutionString+") didn't parse.",Errors.InvalidInput)}else if(options.simplified&&!solution.expr.isSimplified()){throw new PerseusError("The provided solution ("+solutionString+") isn't fully expanded and simplified.",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.parse(guess,options);if(!answer.parsed){score.empty=true;return score}if(typeof solution==="string"){solution=KhanAnswerTypes.expression.parseSolution(solution,options);}const result=KAS.compare(answer.expr,solution,options);if(result.equal){score.correct=true;}else if(result.message){score.message=result.message;}else {const answerX=KAS.parse(guess.replace(/[xX]/g,"*"),options);if(answerX.parsed){const resultX=KAS.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}}}};
39
11
 
40
12
  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}}
41
13
 
42
- 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}
14
+ 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}
43
15
 
44
16
  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!"}}
45
17
 
@@ -47,75 +19,75 @@ function scoreDropdown(userInput,rubric){if(userInput==null){return {type:"inval
47
19
 
48
20
  function validateDropdown(userInput){if(userInput==null){return {type:"invalid",message:null}}if(userInput.value===0){return {type:"invalid",message:null}}return null}
49
21
 
50
- function getVarError(studentVars,answerVars,extraKeys){if(studentVars.every(v=>answerVars.includes(v))){return}const studentVarsLower=[...new Set(studentVars.map(v=>v.toLowerCase()))];const answerVarsLower=answerVars.map(v=>v.toLowerCase());if(studentVarsLower.every(v=>answerVarsLower.includes(v))){return perseusCore.ErrorCodes.WRONG_CASE_ERROR}const extraVars=studentVars.filter(v=>!answerVars.includes(v));if(extraVars.some(v=>!extraKeys.includes(v))){return perseusCore.ErrorCodes.WRONG_LETTER_ERROR}return}function scoreExpression(userInput,rubric,locale){if(userInput==null){return {type:"invalid",message:null}}const options={...rubric,decimal_separator:perseusCore.getDecimalSeparator(locale),divide_symbol:perseusCore.getDivideSymbol(locale)};const parsedStudent=KAS__namespace.parse(userInput,options);if(!parsedStudent.parsed){return {type:"invalid",message:perseusCore.ErrorCodes.EXTRA_SYMBOLS_ERROR}}const studentVars=parsedStudent.expr.getVars();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)}})}const varError=getVarError(studentVars,expression.expr.getVars(),rubric.extraKeys??[]);if(varError!=null){return input=>({correct:false,empty:false,ungraded:true,suppressAlmostThere:true,message:varError,guess:input})}return KhanAnswerTypes.expression.createValidatorFunctional(expression.expr,{...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==null){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}}
22
+ function getVarError(studentVars,answerVars,extraKeys){if(studentVars.every(v=>answerVars.includes(v))){return}const studentVarsLower=[...new Set(studentVars.map(v=>v.toLowerCase()))];const answerVarsLower=answerVars.map(v=>v.toLowerCase());if(studentVarsLower.every(v=>answerVarsLower.includes(v))){return ErrorCodes.WRONG_CASE_ERROR}const extraVars=studentVars.filter(v=>!answerVars.includes(v));if(extraVars.some(v=>!extraKeys.includes(v))){return ErrorCodes.WRONG_LETTER_ERROR}return}function scoreExpression(userInput,rubric,locale){if(userInput==null){return {type:"invalid",message:null}}const options={...rubric,decimal_separator:getDecimalSeparator(locale),divide_symbol:getDivideSymbol(locale)};const parsedStudent=KAS.parse(userInput,options);if(!parsedStudent.parsed){return {type:"invalid",message:ErrorCodes.EXTRA_SYMBOLS_ERROR}}const studentVars=parsedStudent.expr.getVars();const createValidator=answer=>{const expression=KAS.parse(answer.value,rubric);if(!expression.parsed){throw new PerseusError("Unable to parse solution answer for expression",Errors.InvalidInput,{metadata:{rubric:JSON.stringify(rubric)}})}const varError=getVarError(studentVars,expression.expr.getVars(),rubric.extraKeys??[]);if(varError!=null){return input=>({correct:false,empty:false,ungraded:true,suppressAlmostThere:true,message:varError,guess:input})}return KhanAnswerTypes.expression.createValidatorFunctional(expression.expr,{...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==null){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}}
51
23
 
52
24
  function validateExpression(userInput){if(userInput===""||userInput==null){return {type:"invalid",message:null}}return null}
53
25
 
54
- function getCoefficientsByType(data){if(data.coords==null){return undefined}if(data.type==="exponential"||data.type==="logarithm"){const grader=perseusCore.GrapherUtil.functionForType(data.type);return grader.getCoefficients(data.coords,data.asymptote)}else if(data.type==="tangent"){const coeffs=kmath.coefficients.getTangentCoefficients(data.coords);if(coeffs.some(c=>!isFinite(c))){return undefined}return coeffs}else if(data.type==="linear"||data.type==="quadratic"||data.type==="absolute_value"||data.type==="sinusoid"){const grader=perseusCore.GrapherUtil.functionForType(data.type);return grader.getCoefficients(data.coords)}else {throw new perseusCore.PerseusError("Invalid grapher type",perseusCore.Errors.InvalidInput)}}function scoreGrapher(userInput,rubric){if(userInput==null){return {type:"invalid",message:null}}invariant__default.default(rubric.correct!=null,"correct is null or undefined in scoreGrapher");if(userInput.type!==rubric.correct.type){return {type:"points",earned:0,total:1,message:null}}if(userInput.coords==null){return {type:"invalid",message:null}}const guessCoeffs=getCoefficientsByType(userInput);const correctCoeffs=getCoefficientsByType(rubric.correct);if(guessCoeffs==null||correctCoeffs==null){return {type:"invalid",message:null}}const isEqual=userInput.type==="tangent"?perseusCore.approximateDeepEqual(kmath.geometry.canonicalTangentCoefficients(guessCoeffs),kmath.geometry.canonicalTangentCoefficients(correctCoeffs)):perseusCore.GrapherUtil.functionForType(userInput.type).areEqual(guessCoeffs,correctCoeffs);if(isEqual){return {type:"points",earned:1,total:1,message:null}}return {type:"points",earned:0,total:1,message:null}}
26
+ function getCoefficientsByType(data){if(data.coords==null){return undefined}if(data.type==="exponential"||data.type==="logarithm"){const grader=GrapherUtil.functionForType(data.type);return grader.getCoefficients(data.coords,data.asymptote)}else if(data.type==="tangent"){const coeffs=coefficients.getTangentCoefficients(data.coords);if(coeffs.some(c=>!isFinite(c))){return undefined}return coeffs}else if(data.type==="linear"||data.type==="quadratic"||data.type==="absolute_value"||data.type==="sinusoid"){const grader=GrapherUtil.functionForType(data.type);return grader.getCoefficients(data.coords)}else {throw new PerseusError("Invalid grapher type",Errors.InvalidInput)}}function scoreGrapher(userInput,rubric){if(userInput==null){return {type:"invalid",message:null}}invariant(rubric.correct!=null,"correct is null or undefined in scoreGrapher");if(userInput.type!==rubric.correct.type){return {type:"points",earned:0,total:1,message:null}}if(userInput.coords==null){return {type:"invalid",message:null}}const guessCoeffs=getCoefficientsByType(userInput);const correctCoeffs=getCoefficientsByType(rubric.correct);if(guessCoeffs==null||correctCoeffs==null){return {type:"invalid",message:null}}const isEqual=userInput.type==="tangent"?approximateDeepEqual(geometry.canonicalTangentCoefficients(guessCoeffs),geometry.canonicalTangentCoefficients(correctCoeffs)):GrapherUtil.functionForType(userInput.type).areEqual(guessCoeffs,correctCoeffs);if(isEqual){return {type:"points",earned:1,total:1,message:null}}return {type:"points",earned:0,total:1,message:null}}
55
27
 
56
28
  function scoreIframe(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!"}}
57
29
 
58
- const{getAbsoluteValueCoefficients}=kmath.coefficients;function scoreAbsoluteValue(userInput,rubric){if(!userInput.coords||!rubric.coords){return {type:"invalid",message:null}}const userCoeffs=getAbsoluteValueCoefficients(userInput.coords);const rubricCoeffs=getAbsoluteValueCoefficients(rubric.coords);const isCorrect=perseusCore.approximateDeepEqual(userCoeffs,rubricCoeffs);return {type:"points",earned:isCorrect?1:0,total:1,message:null}}
30
+ const{getAbsoluteValueCoefficients}=coefficients;function scoreAbsoluteValue(userInput,rubric){if(!userInput.coords||!rubric.coords){return {type:"invalid",message:null}}const userCoeffs=getAbsoluteValueCoefficients(userInput.coords);const rubricCoeffs=getAbsoluteValueCoefficients(rubric.coords);const isCorrect=approximateDeepEqual(userCoeffs,rubricCoeffs);return {type:"points",earned:isCorrect?1:0,total:1,message:null}}
59
31
 
60
- const{collinear: collinear$3,clockwise}=kmath.geometry;const{getClockwiseAngle}=kmath.angles;function scoreAngle(userInput,rubric){const coords=userInput.coords;const correct=rubric.coords;const allowReflexAngles=rubric.allowReflexAngles;if(!coords){return {type:"invalid",message:null}}const areClockwise=clockwise([coords[0],coords[2],coords[1]]);const shouldReverseCoords=areClockwise&&!allowReflexAngles;const guess=shouldReverseCoords?coords.slice().reverse():coords;let match;if(rubric.match==="congruent"){const guessAngle=getClockwiseAngle(guess,allowReflexAngles);const correctAngle=correct?getClockwiseAngle(correct,allowReflexAngles):null;match=correctAngle!==null&&perseusCore.approximateEqual(guessAngle,correctAngle);}else {match=correct!=null&&perseusCore.approximateDeepEqual(guess[1],correct[1])&&collinear$3(correct[1],correct[0],guess[0])&&collinear$3(correct[1],correct[2],guess[2]);}return {type:"points",earned:match?1:0,total:1,message:null}}
32
+ const{collinear: collinear$3,clockwise}=geometry;const{getClockwiseAngle}=angles;function scoreAngle(userInput,rubric){const coords=userInput.coords;const correct=rubric.coords;const allowReflexAngles=rubric.allowReflexAngles;if(!coords){return {type:"invalid",message:null}}const areClockwise=clockwise([coords[0],coords[2],coords[1]]);const shouldReverseCoords=areClockwise&&!allowReflexAngles;const guess=shouldReverseCoords?coords.slice().reverse():coords;let match;if(rubric.match==="congruent"){const guessAngle=getClockwiseAngle(guess,allowReflexAngles);const correctAngle=correct?getClockwiseAngle(correct,allowReflexAngles):null;match=correctAngle!==null&&approximateEqual(guessAngle,correctAngle);}else {match=correct!=null&&approximateDeepEqual(guess[1],correct[1])&&collinear$3(correct[1],correct[0],guess[0])&&collinear$3(correct[1],correct[2],guess[2]);}return {type:"points",earned:match?1:0,total:1,message:null}}
61
33
 
62
- function scoreCircle(userInput,rubric){if(userInput.center==null||userInput.radius==null||rubric.center==null||rubric.radius==null){return {type:"invalid",message:null}}const isCorrect=perseusCore.approximateDeepEqual(userInput.center,rubric.center)&&perseusCore.approximateEqual(userInput.radius,rubric.radius);return {type:"points",earned:isCorrect?1:0,total:1,message:null}}
34
+ function scoreCircle(userInput,rubric){if(userInput.center==null||userInput.radius==null||rubric.center==null||rubric.radius==null){return {type:"invalid",message:null}}const isCorrect=approximateDeepEqual(userInput.center,rubric.center)&&approximateEqual(userInput.radius,rubric.radius);return {type:"points",earned:isCorrect?1:0,total:1,message:null}}
63
35
 
64
- const{getExponentialCoefficients}=kmath.coefficients;function scoreExponential(userInput,rubric){if(!userInput.coords||userInput.asymptote==null||!rubric.coords||rubric.asymptote==null){return {type:"invalid",message:null}}const guessCoeffs=getExponentialCoefficients(userInput.coords,userInput.asymptote);const correctCoeffs=getExponentialCoefficients(rubric.coords,rubric.asymptote);const isCorrect=guessCoeffs!=null&&correctCoeffs!=null&&perseusCore.approximateDeepEqual([guessCoeffs.a,guessCoeffs.b,guessCoeffs.c],[correctCoeffs.a,correctCoeffs.b,correctCoeffs.c]);return {type:"points",earned:isCorrect?1:0,total:1,message:null}}
36
+ const{getExponentialCoefficients}=coefficients;function scoreExponential(userInput,rubric){if(!userInput.coords||userInput.asymptote==null||!rubric.coords||rubric.asymptote==null){return {type:"invalid",message:null}}const guessCoeffs=getExponentialCoefficients(userInput.coords,userInput.asymptote);const correctCoeffs=getExponentialCoefficients(rubric.coords,rubric.asymptote);const isCorrect=guessCoeffs!=null&&correctCoeffs!=null&&approximateDeepEqual([guessCoeffs.a,guessCoeffs.b,guessCoeffs.c],[correctCoeffs.a,correctCoeffs.b,correctCoeffs.c]);return {type:"points",earned:isCorrect?1:0,total:1,message:null}}
65
37
 
66
- const{collinear: collinear$2}=kmath.geometry;function scoreLinear(userInput,rubric){if(!userInput.coords||!rubric.coords){return {type:"invalid",message:null}}const guess=userInput.coords;const correct=rubric.coords;const isCorrect=collinear$2(correct[0],correct[1],guess[0])&&collinear$2(correct[0],correct[1],guess[1]);return {type:"points",earned:isCorrect?1:0,total:1,message:null}}
38
+ const{collinear: collinear$2}=geometry;function scoreLinear(userInput,rubric){if(!userInput.coords||!rubric.coords){return {type:"invalid",message:null}}const guess=userInput.coords;const correct=rubric.coords;const isCorrect=collinear$2(correct[0],correct[1],guess[0])&&collinear$2(correct[0],correct[1],guess[1]);return {type:"points",earned:isCorrect?1:0,total:1,message:null}}
67
39
 
68
- const{collinear: collinear$1}=kmath.geometry;function scoreLinearSystem(userInput,rubric){if(!userInput.coords||!rubric.coords){return {type:"invalid",message:null}}const guess=userInput.coords;const correct=rubric.coords;const isCorrect=collinear$1(correct[0][0],correct[0][1],guess[0][0])&&collinear$1(correct[0][0],correct[0][1],guess[0][1])&&collinear$1(correct[1][0],correct[1][1],guess[1][0])&&collinear$1(correct[1][0],correct[1][1],guess[1][1])||collinear$1(correct[0][0],correct[0][1],guess[1][0])&&collinear$1(correct[0][0],correct[0][1],guess[1][1])&&collinear$1(correct[1][0],correct[1][1],guess[0][0])&&collinear$1(correct[1][0],correct[1][1],guess[0][1]);return {type:"points",earned:isCorrect?1:0,total:1,message:null}}
40
+ const{collinear: collinear$1}=geometry;function scoreLinearSystem(userInput,rubric){if(!userInput.coords||!rubric.coords){return {type:"invalid",message:null}}const guess=userInput.coords;const correct=rubric.coords;const isCorrect=collinear$1(correct[0][0],correct[0][1],guess[0][0])&&collinear$1(correct[0][0],correct[0][1],guess[0][1])&&collinear$1(correct[1][0],correct[1][1],guess[1][0])&&collinear$1(correct[1][0],correct[1][1],guess[1][1])||collinear$1(correct[0][0],correct[0][1],guess[1][0])&&collinear$1(correct[0][0],correct[0][1],guess[1][1])&&collinear$1(correct[1][0],correct[1][1],guess[0][0])&&collinear$1(correct[1][0],correct[1][1],guess[0][1]);return {type:"points",earned:isCorrect?1:0,total:1,message:null}}
69
41
 
70
- const{getLogarithmCoefficients}=kmath.coefficients;function scoreLogarithm(userInput,rubric){if(!userInput.coords||userInput.asymptote==null||!rubric.coords||rubric.asymptote==null){return {type:"invalid",message:null}}const guessCoeffs=getLogarithmCoefficients(userInput.coords,userInput.asymptote);const correctCoeffs=getLogarithmCoefficients(rubric.coords,rubric.asymptote);const isCorrect=guessCoeffs!=null&&correctCoeffs!=null&&perseusCore.approximateDeepEqual([guessCoeffs.a,guessCoeffs.b,guessCoeffs.c],[correctCoeffs.a,correctCoeffs.b,correctCoeffs.c]);return {type:"points",earned:isCorrect?1:0,total:1,message:null}}
42
+ const{getLogarithmCoefficients}=coefficients;function scoreLogarithm(userInput,rubric){if(!userInput.coords||userInput.asymptote==null||!rubric.coords||rubric.asymptote==null){return {type:"invalid",message:null}}const guessCoeffs=getLogarithmCoefficients(userInput.coords,userInput.asymptote);const correctCoeffs=getLogarithmCoefficients(rubric.coords,rubric.asymptote);const isCorrect=guessCoeffs!=null&&correctCoeffs!=null&&approximateDeepEqual([guessCoeffs.a,guessCoeffs.b,guessCoeffs.c],[correctCoeffs.a,correctCoeffs.b,correctCoeffs.c]);return {type:"points",earned:isCorrect?1:0,total:1,message:null}}
71
43
 
72
- function scorePoint(userInput,rubric){if(!userInput.coords){return {type:"invalid",message:null}}if(rubric.coords==null){throw new Error("Point graph rubric has null coords")}const guess=userInput.coords.slice();const correct=rubric.coords.slice();guess.sort();correct.sort();return {type:"points",earned:perseusCore.approximateDeepEqual(guess,correct)?1:0,total:1,message:null}}
44
+ function scorePoint(userInput,rubric){if(!userInput.coords){return {type:"invalid",message:null}}if(rubric.coords==null){throw new Error("Point graph rubric has null coords")}const guess=userInput.coords.slice();const correct=rubric.coords.slice();guess.sort();correct.sort();return {type:"points",earned:approximateDeepEqual(guess,correct)?1:0,total:1,message:null}}
73
45
 
74
- const{similar}=kmath.geometry;function scorePolygon(userInput,rubric){if(!userInput.coords||!rubric.coords){return {type:"invalid",message:null}}const guess=userInput.coords.slice();const correct=rubric.coords.slice();let match;if(rubric.match==="similar"){match=similar(guess,correct,Number.POSITIVE_INFINITY);}else if(rubric.match==="congruent"){match=similar(guess,correct,kmath.number.DEFAULT_TOLERANCE);}else if(rubric.match==="approx"){match=similar(guess,correct,.1);}else {guess.sort();correct.sort();match=perseusCore.approximateDeepEqual(guess,correct);}return {type:"points",earned:match?1:0,total:1,message:null}}
46
+ const{similar}=geometry;function scorePolygon(userInput,rubric){if(!userInput.coords||!rubric.coords){return {type:"invalid",message:null}}const guess=userInput.coords.slice();const correct=rubric.coords.slice();let match;if(rubric.match==="similar"){match=similar(guess,correct,Number.POSITIVE_INFINITY);}else if(rubric.match==="congruent"){match=similar(guess,correct,number.DEFAULT_TOLERANCE);}else if(rubric.match==="approx"){match=similar(guess,correct,.1);}else {guess.sort();correct.sort();match=approximateDeepEqual(guess,correct);}return {type:"points",earned:match?1:0,total:1,message:null}}
75
47
 
76
- const{getQuadraticCoefficients}=kmath.coefficients;function scoreQuadratic(userInput,rubric){if(!userInput.coords||!rubric.coords){return {type:"invalid",message:null}}const guessCoeffs=getQuadraticCoefficients(userInput.coords);const correctCoeffs=getQuadraticCoefficients(rubric.coords);const isCorrect=perseusCore.approximateDeepEqual(guessCoeffs,correctCoeffs);return {type:"points",earned:isCorrect?1:0,total:1,message:null}}
48
+ const{getQuadraticCoefficients}=coefficients;function scoreQuadratic(userInput,rubric){if(!userInput.coords||!rubric.coords){return {type:"invalid",message:null}}const guessCoeffs=getQuadraticCoefficients(userInput.coords);const correctCoeffs=getQuadraticCoefficients(rubric.coords);const isCorrect=approximateDeepEqual(guessCoeffs,correctCoeffs);return {type:"points",earned:isCorrect?1:0,total:1,message:null}}
77
49
 
78
- const{collinear}=kmath.geometry;function scoreRay(userInput,rubric){if(!userInput.coords||!rubric.coords){return {type:"invalid",message:null}}const guess=userInput.coords;const correct=rubric.coords;const isCorrect=perseusCore.approximateDeepEqual(guess[0],correct[0])&&collinear(correct[0],correct[1],guess[1]);return {type:"points",earned:isCorrect?1:0,total:1,message:null}}
50
+ const{collinear}=geometry;function scoreRay(userInput,rubric){if(!userInput.coords||!rubric.coords){return {type:"invalid",message:null}}const guess=userInput.coords;const correct=rubric.coords;const isCorrect=approximateDeepEqual(guess[0],correct[0])&&collinear(correct[0],correct[1],guess[1]);return {type:"points",earned:isCorrect?1:0,total:1,message:null}}
79
51
 
80
- function scoreSegment(userInput,rubric){if(!userInput.coords||!rubric.coords){return {type:"invalid",message:null}}let guess=perseusCore.deepClone(userInput.coords);let correct=perseusCore.deepClone(rubric.coords);guess=___default.default.invoke(guess,"sort").sort();correct=___default.default.invoke(correct,"sort").sort();return {type:"points",earned:perseusCore.approximateDeepEqual(guess,correct)?1:0,total:1,message:null}}
52
+ function scoreSegment(userInput,rubric){if(!userInput.coords||!rubric.coords){return {type:"invalid",message:null}}let guess=deepClone(userInput.coords);let correct=deepClone(rubric.coords);guess=_.invoke(guess,"sort").sort();correct=_.invoke(correct,"sort").sort();return {type:"points",earned:approximateDeepEqual(guess,correct)?1:0,total:1,message:null}}
81
53
 
82
- const{getSinusoidCoefficients}=kmath.coefficients;const{canonicalSineCoefficients}=kmath.geometry;function scoreSinusoid(userInput,rubric){if(!userInput.coords||!rubric.coords){return {type:"invalid",message:null}}const guessCoeffs=getSinusoidCoefficients(userInput.coords);const correctCoeffs=getSinusoidCoefficients(rubric.coords);const canonicalGuessCoeffs=canonicalSineCoefficients(guessCoeffs);const canonicalCorrectCoeffs=canonicalSineCoefficients(correctCoeffs);const isCorrect=perseusCore.approximateDeepEqual(canonicalGuessCoeffs,canonicalCorrectCoeffs);return {type:"points",earned:isCorrect?1:0,total:1,message:null}}
54
+ const{getSinusoidCoefficients}=coefficients;const{canonicalSineCoefficients}=geometry;function scoreSinusoid(userInput,rubric){if(!userInput.coords||!rubric.coords){return {type:"invalid",message:null}}const guessCoeffs=getSinusoidCoefficients(userInput.coords);const correctCoeffs=getSinusoidCoefficients(rubric.coords);const canonicalGuessCoeffs=canonicalSineCoefficients(guessCoeffs);const canonicalCorrectCoeffs=canonicalSineCoefficients(correctCoeffs);const isCorrect=approximateDeepEqual(canonicalGuessCoeffs,canonicalCorrectCoeffs);return {type:"points",earned:isCorrect?1:0,total:1,message:null}}
83
55
 
84
- const{getTangentCoefficients}=kmath.coefficients;const{canonicalTangentCoefficients}=kmath.geometry;function scoreTangent(userInput,rubric){if(!userInput.coords||!rubric.coords){return {type:"invalid",message:null}}const guessCoeffs=getTangentCoefficients(userInput.coords);const correctCoeffs=getTangentCoefficients(rubric.coords);const canonicalGuessCoeffs=canonicalTangentCoefficients(guessCoeffs);const canonicalCorrectCoeffs=canonicalTangentCoefficients(correctCoeffs);const isCorrect=perseusCore.approximateDeepEqual(canonicalGuessCoeffs,canonicalCorrectCoeffs);return {type:"points",earned:isCorrect?1:0,total:1,message:null}}
56
+ const{getTangentCoefficients}=coefficients;const{canonicalTangentCoefficients}=geometry;function scoreTangent(userInput,rubric){if(!userInput.coords||!rubric.coords){return {type:"invalid",message:null}}const guessCoeffs=getTangentCoefficients(userInput.coords);const correctCoeffs=getTangentCoefficients(rubric.coords);const canonicalGuessCoeffs=canonicalTangentCoefficients(guessCoeffs);const canonicalCorrectCoeffs=canonicalTangentCoefficients(correctCoeffs);const isCorrect=approximateDeepEqual(canonicalGuessCoeffs,canonicalCorrectCoeffs);return {type:"points",earned:isCorrect?1:0,total:1,message:null}}
85
57
 
86
- function scoreVector(userInput,rubric){if(!userInput.coords||!rubric.coords){return {type:"invalid",message:null}}const guess=userInput.coords;const correct=rubric.coords;let match;if(rubric.match==="congruent"){const guessDelta=[guess[1][0]-guess[0][0],guess[1][1]-guess[0][1]];const correctDelta=[correct[1][0]-correct[0][0],correct[1][1]-correct[0][1]];match=perseusCore.approximateDeepEqual(guessDelta,correctDelta);}else {match=perseusCore.approximateDeepEqual(guess[0],correct[0])&&perseusCore.approximateDeepEqual(guess[1],correct[1]);}return {type:"points",earned:match?1:0,total:1,message:null}}
58
+ function scoreVector(userInput,rubric){if(!userInput.coords||!rubric.coords){return {type:"invalid",message:null}}const guess=userInput.coords;const correct=rubric.coords;let match;if(rubric.match==="congruent"){const guessDelta=[guess[1][0]-guess[0][0],guess[1][1]-guess[0][1]];const correctDelta=[correct[1][0]-correct[0][0],correct[1][1]-correct[0][1]];match=approximateDeepEqual(guessDelta,correctDelta);}else {match=approximateDeepEqual(guess[0],correct[0])&&approximateDeepEqual(guess[1],correct[1]);}return {type:"points",earned:match?1:0,total:1,message:null}}
87
59
 
88
- function scoreInteractiveGraph(userInput,rubric){if(userInput==null){return {type:"invalid",message:null}}if(userInput.type==="none"&&rubric.correct.type==="none"){return {type:"points",earned:0,total:0,message:null}}const hasValue=Boolean(userInput.coords||userInput.center&&userInput.radius);if(userInput.type==="absolute-value"&&rubric.correct.type==="absolute-value"){return scoreAbsoluteValue(userInput,rubric.correct)}else if(userInput.type==="angle"&&rubric.correct.type==="angle"){return scoreAngle(userInput,rubric.correct)}else if(userInput.type==="circle"&&rubric.correct.type==="circle"){return scoreCircle(userInput,rubric.correct)}else if(userInput.type==="exponential"&&rubric.correct.type==="exponential"&&userInput.asymptote!=null){return scoreExponential(userInput,rubric.correct)}else if(userInput.type==="linear"&&rubric.correct.type==="linear"){return scoreLinear(userInput,rubric.correct)}else if(userInput.type==="linear-system"&&rubric.correct.type==="linear-system"){return scoreLinearSystem(userInput,rubric.correct)}else if(userInput.type==="logarithm"&&rubric.correct.type==="logarithm"&&userInput.asymptote!=null){return scoreLogarithm(userInput,rubric.correct)}else if(userInput.type==="point"&&rubric.correct.type==="point"){return scorePoint(userInput,rubric.correct)}else if(userInput.type==="polygon"&&rubric.correct.type==="polygon"){return scorePolygon(userInput,rubric.correct)}else if(userInput.type==="quadratic"&&rubric.correct.type==="quadratic"){return scoreQuadratic(userInput,rubric.correct)}else if(userInput.type==="ray"&&rubric.correct.type==="ray"){return scoreRay(userInput,rubric.correct)}else if(userInput.type==="segment"&&rubric.correct.type==="segment"){return scoreSegment(userInput,rubric.correct)}else if(userInput.type==="sinusoid"&&rubric.correct.type==="sinusoid"){return scoreSinusoid(userInput,rubric.correct)}else if(userInput.type==="tangent"&&rubric.correct.type==="tangent"){return scoreTangent(userInput,rubric.correct)}else if(userInput.type==="vector"&&rubric.correct.type==="vector"){return scoreVector(userInput,rubric.correct)}if(!hasValue||___default.default.isEqual(userInput,rubric.graph)){return {type:"invalid",message:null}}return {type:"points",earned:0,total:1,message:null}}
60
+ function scoreInteractiveGraph(userInput,rubric){if(userInput==null){return {type:"invalid",message:null}}if(userInput.type==="none"&&rubric.correct.type==="none"){return {type:"points",earned:0,total:0,message:null}}const hasValue=Boolean(userInput.coords||userInput.center&&userInput.radius);if(userInput.type==="absolute-value"&&rubric.correct.type==="absolute-value"){return scoreAbsoluteValue(userInput,rubric.correct)}else if(userInput.type==="angle"&&rubric.correct.type==="angle"){return scoreAngle(userInput,rubric.correct)}else if(userInput.type==="circle"&&rubric.correct.type==="circle"){return scoreCircle(userInput,rubric.correct)}else if(userInput.type==="exponential"&&rubric.correct.type==="exponential"&&userInput.asymptote!=null){return scoreExponential(userInput,rubric.correct)}else if(userInput.type==="linear"&&rubric.correct.type==="linear"){return scoreLinear(userInput,rubric.correct)}else if(userInput.type==="linear-system"&&rubric.correct.type==="linear-system"){return scoreLinearSystem(userInput,rubric.correct)}else if(userInput.type==="logarithm"&&rubric.correct.type==="logarithm"&&userInput.asymptote!=null){return scoreLogarithm(userInput,rubric.correct)}else if(userInput.type==="point"&&rubric.correct.type==="point"){return scorePoint(userInput,rubric.correct)}else if(userInput.type==="polygon"&&rubric.correct.type==="polygon"){return scorePolygon(userInput,rubric.correct)}else if(userInput.type==="quadratic"&&rubric.correct.type==="quadratic"){return scoreQuadratic(userInput,rubric.correct)}else if(userInput.type==="ray"&&rubric.correct.type==="ray"){return scoreRay(userInput,rubric.correct)}else if(userInput.type==="segment"&&rubric.correct.type==="segment"){return scoreSegment(userInput,rubric.correct)}else if(userInput.type==="sinusoid"&&rubric.correct.type==="sinusoid"){return scoreSinusoid(userInput,rubric.correct)}else if(userInput.type==="tangent"&&rubric.correct.type==="tangent"){return scoreTangent(userInput,rubric.correct)}else if(userInput.type==="vector"&&rubric.correct.type==="vector"){return scoreVector(userInput,rubric.correct)}if(!hasValue||_.isEqual(userInput,rubric.graph)){return {type:"invalid",message:null}}return {type:"points",earned:0,total:1,message:null}}
89
61
 
90
62
  function scoreLabelImageMarker(userInput,rubric){const score={hasAnswers:false,isCorrect:false};if(userInput&&userInput.length>0){score.hasAnswers=true;}if(rubric.length>0){if(userInput&&userInput.length===rubric.length){score.isCorrect=userInput.every(choice=>rubric.includes(choice));}}else if(!userInput||userInput.length===0){score.isCorrect=true;}return score}function scoreLabelImage(userInput,rubric){if(userInput==null){return {type:"invalid",message:null}}let numCorrect=0;for(let i=0;i<userInput.markers.length;i++){const score=scoreLabelImageMarker(userInput.markers[i].selected,rubric.markers[i].answers);if(score.isCorrect){numCorrect++;}}return {type:"points",earned:numCorrect===userInput.markers.length?1:0,total:1,message:null}}
91
63
 
92
- function scoreMatcher(userInput,rubric){if(userInput==null){return {type:"invalid",message:null}}const correct=___default.default.isEqual(userInput.left,rubric.left)&&___default.default.isEqual(userInput.right,rubric.right);return {type:"points",earned:correct?1:0,total:1,message:null}}
64
+ function scoreMatcher(userInput,rubric){if(userInput==null){return {type:"invalid",message:null}}const correct=_.isEqual(userInput.left,rubric.left)&&_.isEqual(userInput.right,rubric.right);return {type:"points",earned:correct?1:0,total:1,message:null}}
93
65
 
94
- 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}}
66
+ function scoreMatrix(userInput,rubric){if(userInput==null){return {type:"invalid",message:null}}const solution=rubric.answers;const supplied=userInput.answers;const solutionSize=getMatrixSize(solution);const suppliedSize=getMatrixSize(supplied);const incorrectSize=solutionSize[0]!==suppliedSize[0]||solutionSize[1]!==suppliedSize[1];const createValidator=KhanAnswerTypes.number.createValidatorFunctional;let message=null;let incorrect=false;_(suppliedSize[0]).times(row=>{_(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}}
95
67
 
96
- 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}
68
+ function validateMatrix(userInput){if(userInput==null){return {type:"invalid",message:null}}const supplied=userInput.answers;const suppliedSize=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}
97
69
 
98
- 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}}
70
+ 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=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}}
99
71
 
100
72
  function findEndpoint(tex,currentIndex){let bracketDepth=0;for(let i=currentIndex,len=tex.length;i<len;i++){const c=tex[i];if(c==="{"){bracketDepth++;}else if(c==="}"){bracketDepth--;}if(bracketDepth<0){return i}}return tex.length}function parseNextExpression(tex,currentIndex,handler){const openBracketIndex=tex.indexOf("{",currentIndex);if(openBracketIndex===-1){return {endpoint:tex.length,expression:""}}const nextExpIndex=openBracketIndex+1;const endpoint=findEndpoint(tex,nextExpIndex);const expressionTeX=tex.substring(nextExpIndex,endpoint);const parsedExp=walkTex(expressionTeX,handler);return {endpoint:endpoint,expression:parsedExp}}function getNextFracIndex(tex,currentIndex){const dfrac="\\dfrac";const frac="\\frac";const nextFrac=tex.indexOf(frac,currentIndex);const nextDFrac=tex.indexOf(dfrac,currentIndex);if(nextFrac>-1&&nextDFrac>-1){return Math.min(nextFrac,nextDFrac)}if(nextFrac>-1){return nextFrac}if(nextDFrac>-1){return nextDFrac}return -1}function walkTex(tex,handler){if(!tex){return ""}let parsedString="";let currentIndex=0;let nextFrac=getNextFracIndex(tex,currentIndex);while(nextFrac>-1){parsedString+=tex.substring(currentIndex,nextFrac);currentIndex=nextFrac;const firstParsedExpression=parseNextExpression(tex,currentIndex,handler);currentIndex=firstParsedExpression.endpoint+1;const secondParsedExpression=parseNextExpression(tex,currentIndex,handler);currentIndex=secondParsedExpression.endpoint+1;if(parsedString.length){parsedString+=" ";}parsedString+=handler(firstParsedExpression.expression,secondParsedExpression.expression);nextFrac=getNextFracIndex(tex,currentIndex);}parsedString+=tex.slice(currentIndex);return parsedString}function parseTex(tex){const handler=function(exp1,exp2){return exp1+"/"+exp2};const texWithoutFracs=walkTex(tex,handler);return texWithoutFracs.replace("\\%","%")}
101
73
 
102
- const answerFormButtons=[{title:"Integers",value:"integer",content:"6"},{title:"Decimals",value:"decimal",content:"0.75"},{title:"Proper fractions",value:"proper",content:"⅗"},{title:"Improper fractions",value:"improper",content:"⁷⁄₄"},{title:"Mixed numbers",value:"mixed",content:"1¾"},{title:"Numbers with π",value:"pi",content:"π"}];function maybeParsePercentInput(inputValue,normalizedAnswerExpected){if(!(typeof inputValue==="string"&&inputValue.endsWith("%"))){return inputValue}const value=parseFloat(inputValue.slice(0,-1));if(isNaN(value)){return inputValue}if(normalizedAnswerExpected){return value/100}return value}function scoreNumericInput(userInput,rubric,locale){if(userInput==null){return {type:"invalid",message:null}}const defaultAnswerForms=answerFormButtons.map(e=>e["value"]).filter(e=>e!=="pi");const createValidator=answer=>{const stringAnswer=`${answer.value}`;const validatorForms=[...answer.answerForms??[]];if(!answer.strict||validatorForms.length===0){validatorForms.push(...defaultAnswerForms);}return KhanAnswerTypes.number.createValidatorFunctional(stringAnswer,{message:answer.message,simplify:answer.status==="correct"?answer.simplify:"optional",inexact:true,maxError:answer.maxError,forms:validatorForms,...locale&&{decimal_separator:perseusCore.getDecimalSeparator(locale)}})};const currentValue=parseTex(userInput.currentValue);const normalizedAnswerExpected=rubric.answers.filter(answer=>answer.status==="correct").every(answer=>answer.value!=null&&Math.abs(answer.value)<=1);let localValue=currentValue;if(rubric.coefficient){if(!localValue){localValue=1;}else if(localValue==="-"){localValue=-1;}}const matchedAnswer=rubric.answers.map(answer=>{const validateFn=createValidator(answer);const score=validateFn(maybeParsePercentInput(localValue,normalizedAnswerExpected));return {...answer,score}}).find(answer=>{return answer.score.correct||answer.status==="correct"&&answer.score.empty});const result=matchedAnswer?.status==="correct"?matchedAnswer.score:{empty:matchedAnswer?.status==="ungraded",correct:matchedAnswer?.status==="correct",message:matchedAnswer?.message??null};if(result.empty){return {type:"invalid",message:result.message}}return {type:"points",earned:result.correct?1:0,total:1,message:result.message}}
74
+ const answerFormButtons=[{title:"Integers",value:"integer",content:"6"},{title:"Decimals",value:"decimal",content:"0.75"},{title:"Proper fractions",value:"proper",content:"⅗"},{title:"Improper fractions",value:"improper",content:"⁷⁄₄"},{title:"Mixed numbers",value:"mixed",content:"1¾"},{title:"Numbers with π",value:"pi",content:"π"}];function maybeParsePercentInput(inputValue,normalizedAnswerExpected){if(!(typeof inputValue==="string"&&inputValue.endsWith("%"))){return inputValue}const value=parseFloat(inputValue.slice(0,-1));if(isNaN(value)){return inputValue}if(normalizedAnswerExpected){return value/100}return value}function scoreNumericInput(userInput,rubric,locale){if(userInput==null){return {type:"invalid",message:null}}const defaultAnswerForms=answerFormButtons.map(e=>e["value"]).filter(e=>e!=="pi");const createValidator=answer=>{const stringAnswer=`${answer.value}`;const validatorForms=[...answer.answerForms??[]];if(!answer.strict||validatorForms.length===0){validatorForms.push(...defaultAnswerForms);}return KhanAnswerTypes.number.createValidatorFunctional(stringAnswer,{message:answer.message,simplify:answer.status==="correct"?answer.simplify:"optional",inexact:true,maxError:answer.maxError,forms:validatorForms,...locale&&{decimal_separator:getDecimalSeparator(locale)}})};const currentValue=parseTex(userInput.currentValue);const normalizedAnswerExpected=rubric.answers.filter(answer=>answer.status==="correct").every(answer=>answer.value!=null&&Math.abs(answer.value)<=1);let localValue=currentValue;if(rubric.coefficient){if(!localValue){localValue=1;}else if(localValue==="-"){localValue=-1;}}const matchedAnswer=rubric.answers.map(answer=>{const validateFn=createValidator(answer);const score=validateFn(maybeParsePercentInput(localValue,normalizedAnswerExpected));return {...answer,score}}).find(answer=>{return answer.score.correct||answer.status==="correct"&&answer.score.empty});const result=matchedAnswer?.status==="correct"?matchedAnswer.score:{empty:matchedAnswer?.status==="ungraded",correct:matchedAnswer?.status==="correct",message:matchedAnswer?.message??null};if(result.empty){return {type:"invalid",message:result.message}}return {type:"points",earned:result.correct?1:0,total:1,message:result.message}}
103
75
 
104
- function scoreOrderer(userInput,rubric){if(userInput==null){return {type:"invalid",message:null}}const correct=___default.default.isEqual(userInput.current,rubric.correctOptions.map(option=>option.content));return {type:"points",earned:correct?1:0,total:1,message:null}}
76
+ function scoreOrderer(userInput,rubric){if(userInput==null){return {type:"invalid",message:null}}const correct=_.isEqual(userInput.current,rubric.correctOptions.map(option=>option.content));return {type:"points",earned:correct?1:0,total:1,message:null}}
105
77
 
106
78
  function validateOrderer(userInput){if(userInput==null||userInput.current.length===0){return {type:"invalid",message:null}}return null}
107
79
 
108
- function scorePlotter(userInput,rubric){return {type:"points",earned:perseusCore.approximateDeepEqual(userInput,rubric.correct)?1:0,total:1,message:null}}
80
+ function scorePlotter(userInput,rubric){return {type:"points",earned:approximateDeepEqual(userInput,rubric.correct)?1:0,total:1,message:null}}
109
81
 
110
- function validatePlotter(userInput,validationData){if(userInput==null||perseusCore.approximateDeepEqual(userInput,validationData.starting)){return {type:"invalid",message:null}}return null}
82
+ function validatePlotter(userInput,validationData){if(userInput==null||approximateDeepEqual(userInput,validationData.starting)){return {type:"invalid",message:null}}return null}
111
83
 
112
- 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}}
84
+ 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: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: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}}
113
85
 
114
86
  function validateRadio(userInput){if(userInput==null||userInput.selectedChoiceIds.length===0){return {type:"invalid",message:null}}return null}
115
87
 
116
- function scoreSorter(userInput,rubric){if(perseusCore.exceedsCardLimit(rubric.correct)){return {type:"points",earned:1,total:1,message:null}}const correct=perseusCore.approximateDeepEqual(userInput.options,rubric.correct);return {type:"points",earned:correct?1:0,total:1,message:null}}
88
+ function scoreSorter(userInput,rubric){if(exceedsCardLimit(rubric.correct)){return {type:"points",earned:1,total:1,message:null}}const correct=approximateDeepEqual(userInput.options,rubric.correct);return {type:"points",earned:correct?1:0,total:1,message:null}}
117
89
 
118
- function validateSorter(userInput,rubric){if(perseusCore.exceedsCardLimit(rubric.correct)){return null}if(!userInput?.changed){return {type:"invalid",message:null}}return null}
90
+ function validateSorter(userInput,rubric){if(exceedsCardLimit(rubric.correct)){return null}if(!userInput?.changed){return {type:"invalid",message:null}}return null}
119
91
 
120
92
  const filterNonEmpty=function(table){return table.filter(function(row){return row.some(cell=>cell)})};
121
93
 
@@ -123,13 +95,13 @@ function validateTable(userInput){const supplied=filterNonEmpty(userInput);const
123
95
 
124
96
  function scoreTable(userInput,rubric){const validationResult=validateTable(userInput);if(validationResult!=null){return validationResult}const supplied=filterNonEmpty(userInput);const solution=filterNonEmpty(rubric.answers);if(supplied.length!==solution.length){return {type:"points",earned:0,total:1,message:null}}const createValidator=KhanAnswerTypes.number.createValidatorFunctional;let message=null;const allCorrect=solution.every(function(rowSolution){for(let i=0;i<supplied.length;i++){const rowSupplied=supplied[i];const correct=rowSupplied.every(function(cellSupplied,i){const cellSolution=rowSolution[i];const validator=createValidator(cellSolution,{simplify:true});const result=validator(cellSupplied);if(result.message){message=result.message;}return result.correct});if(correct){supplied.splice(i,1);return true}}return false});return {type:"points",earned:allCorrect?1:0,total:1,message}}
125
97
 
126
- const noScore={type:"points",earned:0,total:0,message:null};function combineScores(scoreA,scoreB){let message;if(scoreA.type==="points"&&scoreB.type==="points"){if(scoreA.message&&scoreB.message&&scoreA.message!==scoreB.message){message=null;}else {message=scoreA.message||scoreB.message;}return {type:"points",earned:scoreA.earned+scoreB.earned,total:scoreA.total+scoreB.total,message:message}}if(scoreA.type==="points"&&scoreB.type==="invalid"){return scoreB}if(scoreA.type==="invalid"&&scoreB.type==="points"){return scoreA}if(scoreA.type==="invalid"&&scoreB.type==="invalid"){if(scoreA.message&&scoreB.message&&scoreA.message!==scoreB.message){message=null;}else {message=scoreA.message||scoreB.message;}return {type:"invalid",message:message}}throw new perseusCore.PerseusError("PerseusScore with unknown type encountered",perseusCore.Errors.InvalidInput,{metadata:{scoreA:JSON.stringify(scoreA),scoreB:JSON.stringify(scoreB)}})}function flattenScores(widgetScoreMap){return Object.values(widgetScoreMap).reduce(combineScores,noScore)}
98
+ const noScore={type:"points",earned:0,total:0,message:null};function combineScores(scoreA,scoreB){let message;if(scoreA.type==="points"&&scoreB.type==="points"){if(scoreA.message&&scoreB.message&&scoreA.message!==scoreB.message){message=null;}else {message=scoreA.message||scoreB.message;}return {type:"points",earned:scoreA.earned+scoreB.earned,total:scoreA.total+scoreB.total,message:message}}if(scoreA.type==="points"&&scoreB.type==="invalid"){return scoreB}if(scoreA.type==="invalid"&&scoreB.type==="points"){return scoreA}if(scoreA.type==="invalid"&&scoreB.type==="invalid"){if(scoreA.message&&scoreB.message&&scoreA.message!==scoreB.message){message=null;}else {message=scoreA.message||scoreB.message;}return {type:"invalid",message:message}}throw new PerseusError("PerseusScore with unknown type encountered",Errors.InvalidInput,{metadata:{scoreA:JSON.stringify(scoreA),scoreB:JSON.stringify(scoreB)}})}function flattenScores(widgetScoreMap){return Object.values(widgetScoreMap).reduce(combineScores,noScore)}
127
99
 
128
100
  function scoreDeprecatedStandin(userInput,rubric,locale){return {type:"points",earned:1,total:1,message:null}}
129
101
 
130
102
  function scoreFreeResponse(userInput,rubric,locale){return {type:"points",earned:0,total:0,message:null}}
131
103
 
132
- 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}
104
+ 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}
133
105
 
134
106
  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)}
135
107
 
@@ -137,7 +109,7 @@ function scoreIsEmpty(score){return score.type==="invalid"&&(!score.message||sco
137
109
 
138
110
  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})}
139
111
 
140
- 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}}
112
+ function validateGroup(userInput,validationData,locale){if(userInput==null){return {type:"invalid",message:null}}const widgetIds=getWidgetIdsFromContent(validationData.content);const emptyWidgets=emptyWidgetsFunctional(validationData.widgets,widgetIds,userInput,locale);if(emptyWidgets.length===0){return null}return {type:"invalid",message:null}}
141
113
 
142
114
  function validateLabelImage(userInput){if(userInput==null){return {type:"invalid",message:null}}let numAnswered=0;for(let i=0;i<userInput.markers.length;i++){const userSelection=userInput.markers[i].selected;if(userSelection&&userSelection.length>0){numAnswered++;}}if(numAnswered!==userInput.markers.length){return {type:"invalid",message:null}}return null}
143
115
 
@@ -145,54 +117,15 @@ function validateMockWidget(userInput){if(userInput?.currentValue==null||userInp
145
117
 
146
118
  function scoreMockWidget(userInput,rubric){const validationResult=validateMockWidget(userInput);if(validationResult!=null){return validationResult}return {type:"points",earned:userInput?.currentValue===rubric.value?1:0,total:1,message:""}}
147
119
 
148
- const widgets=new perseusCore.Registry("Score widget registry");function registerWidget(type,scorer,validator){const logic={scorer,validator};widgets.set(type,logic);}const getWidgetValidator=type=>{return widgets.get(type)?.validator??null};const getWidgetScorer=type=>{return widgets.get(type)?.scorer??null};registerWidget("categorizer",scoreCategorizer,validateCategorizer);registerWidget("cs-program",scoreCSProgram);registerWidget("deprecated-standin",scoreDeprecatedStandin);registerWidget("dropdown",scoreDropdown,validateDropdown);registerWidget("expression",scoreExpression,validateExpression);registerWidget("free-response",scoreFreeResponse,validateFreeResponse);registerWidget("grapher",scoreGrapher);registerWidget("group",scoreGroup,validateGroup);registerWidget("iframe",scoreIframe);registerWidget("input-number",scoreNumericInput);registerWidget("interactive-graph",scoreInteractiveGraph);registerWidget("label-image",scoreLabelImage,validateLabelImage);registerWidget("matcher",scoreMatcher);registerWidget("matrix",scoreMatrix,validateMatrix);registerWidget("mock-widget",scoreMockWidget,scoreMockWidget);registerWidget("number-line",scoreNumberLine);registerWidget("numeric-input",scoreNumericInput);registerWidget("orderer",scoreOrderer,validateOrderer);registerWidget("plotter",scorePlotter,validatePlotter);registerWidget("radio",scoreRadio,validateRadio);registerWidget("sorter",scoreSorter,validateSorter);registerWidget("table",scoreTable,validateTable);
120
+ const widgets=new Registry("Score widget registry");function registerWidget(type,scorer,validator){const logic={scorer,validator};widgets.set(type,logic);}const getWidgetValidator=type=>{return widgets.get(type)?.validator??null};const getWidgetScorer=type=>{return widgets.get(type)?.scorer??null};registerWidget("categorizer",scoreCategorizer,validateCategorizer);registerWidget("cs-program",scoreCSProgram);registerWidget("deprecated-standin",scoreDeprecatedStandin);registerWidget("dropdown",scoreDropdown,validateDropdown);registerWidget("expression",scoreExpression,validateExpression);registerWidget("free-response",scoreFreeResponse,validateFreeResponse);registerWidget("grapher",scoreGrapher);registerWidget("group",scoreGroup,validateGroup);registerWidget("iframe",scoreIframe);registerWidget("input-number",scoreNumericInput);registerWidget("interactive-graph",scoreInteractiveGraph);registerWidget("label-image",scoreLabelImage,validateLabelImage);registerWidget("matcher",scoreMatcher);registerWidget("matrix",scoreMatrix,validateMatrix);registerWidget("mock-widget",scoreMockWidget,scoreMockWidget);registerWidget("number-line",scoreNumberLine);registerWidget("numeric-input",scoreNumericInput);registerWidget("orderer",scoreOrderer,validateOrderer);registerWidget("plotter",scorePlotter,validatePlotter);registerWidget("radio",scoreRadio,validateRadio);registerWidget("sorter",scoreSorter,validateSorter);registerWidget("table",scoreTable,validateTable);
149
121
 
150
122
  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}
151
123
 
152
- function getScoreableWidgets(perseusRenderData){const usedWidgetIds=perseusCore.getWidgetIdsFromContent(perseusRenderData.content);return usedWidgetIds.filter(id=>isWidgetScoreable(perseusRenderData.widgets[id]))}
124
+ function getScoreableWidgets(perseusRenderData){const usedWidgetIds=getWidgetIdsFromContent(perseusRenderData.content);return usedWidgetIds.filter(id=>isWidgetScoreable(perseusRenderData.widgets[id]))}
153
125
 
154
126
  function onlyInvalidScores(scores){return Object.fromEntries(Object.entries(scores).filter(([key,value])=>value.type==="invalid"))}function combineScoreWithWidgetScores(score,widgetScores){if(score.type==="invalid"){return {...score,widgetScores:onlyInvalidScores(widgetScores)}}else {return {...score,widgetScores}}}function scorePerseusItem(perseusRenderData,userInputMap,locale){const scoreableWidgetIds=getScoreableWidgets(perseusRenderData);const widgetScores=scoreWidgetsFunctional(perseusRenderData.widgets,scoreableWidgetIds,userInputMap,locale);return combineScoreWithWidgetScores(flattenScores(widgetScores),widgetScores)}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}
155
127
 
156
- 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}
157
-
158
- exports.KhanAnswerTypes = KhanAnswerTypes;
159
- exports.emptyWidgetsFunctional = emptyWidgetsFunctional;
160
- exports.flattenScores = flattenScores;
161
- exports.getWidgetScorer = getWidgetScorer;
162
- exports.getWidgetValidator = getWidgetValidator;
163
- exports.hasEmptyDINERWidgets = hasEmptyDINERWidgets;
164
- exports.isWidgetScoreable = isWidgetScoreable;
165
- exports.libVersion = libVersion;
166
- exports.registerWidget = registerWidget;
167
- exports.scoreCSProgram = scoreCSProgram;
168
- exports.scoreCategorizer = scoreCategorizer;
169
- exports.scoreDropdown = scoreDropdown;
170
- exports.scoreExpression = scoreExpression;
171
- exports.scoreGrapher = scoreGrapher;
172
- exports.scoreIframe = scoreIframe;
173
- exports.scoreInputNumber = scoreNumericInput;
174
- exports.scoreInteractiveGraph = scoreInteractiveGraph;
175
- exports.scoreLabelImage = scoreLabelImage;
176
- exports.scoreLabelImageMarker = scoreLabelImageMarker;
177
- exports.scoreMatcher = scoreMatcher;
178
- exports.scoreMatrix = scoreMatrix;
179
- exports.scoreNumberLine = scoreNumberLine;
180
- exports.scoreNumericInput = scoreNumericInput;
181
- exports.scoreOrderer = scoreOrderer;
182
- exports.scorePerseusItem = scorePerseusItem;
183
- exports.scorePlotter = scorePlotter;
184
- exports.scoreRadio = scoreRadio;
185
- exports.scoreSorter = scoreSorter;
186
- exports.scoreTable = scoreTable;
187
- exports.scoreWidgetsFunctional = scoreWidgetsFunctional;
188
- exports.validateCategorizer = validateCategorizer;
189
- exports.validateDropdown = validateDropdown;
190
- exports.validateExpression = validateExpression;
191
- exports.validateMatrix = validateMatrix;
192
- exports.validateOrderer = validateOrderer;
193
- exports.validatePlotter = validatePlotter;
194
- exports.validateRadio = validateRadio;
195
- exports.validateSorter = validateSorter;
196
- exports.validateTable = validateTable;
197
- exports.validateUserInput = validateUserInput;
128
+ function hasEmptyDINERWidgets(itemData,userInputMap){const usedWidgetIds=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}
129
+
130
+ export { KhanAnswerTypes, emptyWidgetsFunctional, flattenScores, getWidgetScorer, getWidgetValidator, hasEmptyDINERWidgets, isWidgetScoreable, libVersion, registerWidget, scoreCSProgram, scoreCategorizer, scoreDropdown, scoreExpression, scoreGrapher, scoreIframe, scoreNumericInput as scoreInputNumber, scoreInteractiveGraph, scoreLabelImage, scoreLabelImageMarker, scoreMatcher, scoreMatrix, scoreNumberLine, scoreNumericInput, scoreOrderer, scorePerseusItem, scorePlotter, scoreRadio, scoreSorter, scoreTable, scoreWidgetsFunctional, validateCategorizer, validateDropdown, validateExpression, validateMatrix, validateOrderer, validatePlotter, validateRadio, validateSorter, validateTable, validateUserInput };
198
131
  //# sourceMappingURL=index.js.map