@khanacademy/perseus-score 8.0.10 → 8.1.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.d.ts CHANGED
@@ -27,8 +27,9 @@ export { default as validateSorter } from "./widgets/sorter/validate-sorter";
27
27
  export { default as scoreTable } from "./widgets/table/score-table";
28
28
  export { default as validateTable } from "./widgets/table/validate-table";
29
29
  export { default as scoreInputNumber, inputNumberAnswerTypes, } from "./widgets/input-number/score-input-number";
30
- export { scorePerseusItem, scoreWidgetsFunctional, flattenScores } from "./score";
31
- export { emptyWidgetsFunctional } from "./validate";
30
+ export { scorePerseusItem, scoreWidgetsFunctional } from "./score";
31
+ export { default as flattenScores } from "./util/flatten-scores";
32
+ export { validateUserInput, emptyWidgetsFunctional } from "./validate";
32
33
  export { default as hasEmptyDINERWidgets } from "./has-empty-diner-widgets";
33
34
  export type { PerseusMockWidgetRubric, PerseusMockWidgetUserInput, } from "./widgets/mock-widget/mock-widget-validation.types";
34
35
  export * from "./widgets/widget-registry";
package/dist/index.js CHANGED
@@ -90,6 +90,12 @@ function scoreTable(userInput,rubric){const validationResult=validateTable(userI
90
90
 
91
91
  const inputNumberAnswerTypes={number:{name:"Numbers",forms:"integer, decimal, proper, improper, mixed"},decimal:{name:"Decimals",forms:"decimal"},integer:{name:"Integers",forms:"integer"},rational:{name:"Fractions and mixed numbers",forms:"integer, proper, improper, mixed"},improper:{name:"Improper numbers (no mixed)",forms:"integer, proper, improper"},mixed:{name:"Mixed numbers (no improper)",forms:"integer, proper, mixed"},percent:{name:"Numbers or percents",forms:"integer, decimal, proper, improper, mixed, percent"},pi:{name:"Numbers with pi",forms:"pi"}};function scoreInputNumber(userInput,rubric,locale){if(userInput==null){return {type:"invalid",message:null}}if(rubric.answerType==null){rubric.answerType="number";}const stringValue=`${rubric.value}`;const val=KhanAnswerTypes.number.createValidatorFunctional(stringValue,{simplify:rubric.simplify,inexact:rubric.inexact||undefined,maxError:rubric.maxError,forms:inputNumberAnswerTypes[rubric.answerType].forms,...locale&&{decimal_separator:perseusCore.getDecimalSeparator(locale)}});const currentValue=parseTex(userInput.currentValue);const result=val(currentValue);if(result.empty){return {type:"invalid",message:result.message}}return {type:"points",earned:result.correct?1:0,total:1,message:result.message}}
92
92
 
93
+ 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)}
94
+
95
+ function isWidgetScoreable(widget){if(!widget){return false}const widgetIsGraded=widget.graded==null||widget.graded;const widgetIsStatic=!!widget.static;return widgetIsGraded&&!widgetIsStatic}
96
+
97
+ function getScoreableWidgets(perseusRenderData){const usedWidgetIds=perseusCore.getWidgetIdsFromContent(perseusRenderData.content);const upgradedWidgets=perseusCore.applyDefaultsToWidgets(perseusRenderData.widgets);const scoreableWidgetIds=usedWidgetIds.filter(id=>isWidgetScoreable(upgradedWidgets[id]));return {upgradedWidgets,scoreableWidgetIds}}
98
+
93
99
  function scoreNoop(points=0){return {type:"points",earned:points,total:points,message:null}}
94
100
 
95
101
  function scoreFreeResponse(userInput,rubric,locale){return scoreNoop()}
@@ -98,9 +104,11 @@ function validateFreeResponse(userInput,widgetOptions){const userInputLength=use
98
104
 
99
105
  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)}
100
106
 
101
- function emptyWidgetsFunctional(widgets,widgetIds,userInputMap,locale){return widgetIds.filter(id=>{const widget=widgets[id];if(!widget||widget.static===true){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})}
107
+ function scoreIsEmpty(score){return score.type==="invalid"&&(!score.message||score.message.length===0)}
108
+
109
+ function validateUserInput(perseusRenderData,userInputMap,locale){const{upgradedWidgets,scoreableWidgetIds}=getScoreableWidgets(perseusRenderData);const validationErrors={};scoreableWidgetIds.forEach(id=>{const widget=upgradedWidgets[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})}
102
110
 
103
- function validateGroup(userInput,validationData,locale){if(userInput==null){return {type:"invalid",message:null}}const emptyWidgets=emptyWidgetsFunctional(validationData.widgets,Object.keys(validationData.widgets),userInput,locale);if(emptyWidgets.length===0){return null}return {type:"invalid",message:null}}
111
+ 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}}
104
112
 
105
113
  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}
106
114
 
@@ -110,7 +118,7 @@ function scoreMockWidget(userInput,rubric){const validationResult=validateMockWi
110
118
 
111
119
  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("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",scoreInputNumber);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);registerWidget("deprecated-standin",()=>scoreNoop(1));registerWidget("measurer",()=>scoreNoop(1));registerWidget("definition",scoreNoop);registerWidget("explanation",scoreNoop);registerWidget("image",scoreNoop);registerWidget("interaction",scoreNoop);registerWidget("molecule",scoreNoop);registerWidget("passage",scoreNoop);registerWidget("passage-ref",scoreNoop);registerWidget("passage-ref-target",scoreNoop);registerWidget("video",scoreNoop);
112
120
 
113
- const noScore={type:"points",earned:0,total:0,message:null};function scoreIsEmpty(score){return score.type==="invalid"&&(!score.message||score.message.length===0)}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)}function scorePerseusItem(perseusRenderData,userInputMap,locale){const usedWidgetIds=perseusCore.getWidgetIdsFromContent(perseusRenderData.content);const scores=scoreWidgetsFunctional(perseusRenderData.widgets,usedWidgetIds,userInputMap,locale);return flattenScores(scores)}function scoreWidgetsFunctional(widgets,widgetIds,userInputMap,locale){const upgradedWidgets=perseusCore.applyDefaultsToWidgets(widgets);const gradedWidgetIds=widgetIds.filter(id=>{const props=upgradedWidgets[id];const widgetIsGraded=props?.graded==null||props.graded;const widgetIsStatic=!!props?.static;return widgetIsGraded&&!widgetIsStatic});const widgetScores={};gradedWidgetIds.forEach(id=>{const widget=upgradedWidgets[id];if(!widget){return}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}
121
+ function scorePerseusItem(perseusRenderData,userInputMap,locale){const{upgradedWidgets,scoreableWidgetIds}=getScoreableWidgets(perseusRenderData);const scores=scoreWidgetsFunctional(upgradedWidgets,scoreableWidgetIds,userInputMap,locale);return flattenScores(scores)}function scoreWidgetsFunctional(widgets,widgetIds,userInputMap,locale){const upgradedWidgets=perseusCore.applyDefaultsToWidgets(widgets);const gradedWidgetIds=widgetIds.filter(id=>isWidgetScoreable(upgradedWidgets[id]));const widgetScores={};gradedWidgetIds.forEach(id=>{const widget=upgradedWidgets[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}
114
122
 
115
123
  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}
116
124
 
@@ -152,4 +160,5 @@ exports.validatePlotter = validatePlotter;
152
160
  exports.validateRadio = validateRadio;
153
161
  exports.validateSorter = validateSorter;
154
162
  exports.validateTable = validateTable;
163
+ exports.validateUserInput = validateUserInput;
155
164
  //# sourceMappingURL=index.js.map