@pie-element/image-cloze-association 10.1.2-next.2 → 10.1.2

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.
Files changed (130) hide show
  1. package/CHANGELOG.json +437 -0
  2. package/CHANGELOG.md +1997 -0
  3. package/LICENSE.md +5 -0
  4. package/README.md +1 -0
  5. package/configure/CHANGELOG.json +197 -0
  6. package/configure/CHANGELOG.md +1600 -0
  7. package/configure/lib/defaults.js +86 -0
  8. package/configure/lib/defaults.js.map +1 -0
  9. package/configure/lib/index.js +99 -0
  10. package/configure/lib/index.js.map +1 -0
  11. package/configure/lib/root.js +135 -0
  12. package/configure/lib/root.js.map +1 -0
  13. package/configure/package.json +23 -0
  14. package/configure/src/__tests__/index.test.js +155 -0
  15. package/configure/src/defaults.js +59 -0
  16. package/configure/src/index.js +114 -0
  17. package/configure/src/root.jsx +116 -0
  18. package/controller/CHANGELOG.json +137 -0
  19. package/controller/CHANGELOG.md +1149 -0
  20. package/controller/lib/defaults.js +14 -0
  21. package/controller/lib/defaults.js.map +1 -0
  22. package/controller/lib/index.js +304 -0
  23. package/controller/lib/index.js.map +1 -0
  24. package/controller/lib/utils.js +70 -0
  25. package/controller/lib/utils.js.map +1 -0
  26. package/controller/package.json +19 -0
  27. package/controller/src/__tests__/index.test.js +711 -0
  28. package/controller/src/defaults.js +7 -0
  29. package/controller/src/index.js +322 -0
  30. package/controller/src/utils.js +72 -0
  31. package/docs/config-schema.json +1382 -0
  32. package/docs/config-schema.json.md +1021 -0
  33. package/docs/demo/config.js +8 -0
  34. package/docs/demo/generate.js +74 -0
  35. package/docs/demo/index.html +1 -0
  36. package/docs/demo/session.js +16 -0
  37. package/docs/pie-schema.json +1085 -0
  38. package/docs/pie-schema.json.md +810 -0
  39. package/lib/constants.js +12 -0
  40. package/lib/constants.js.map +1 -0
  41. package/lib/evaluation-icon.js +60 -0
  42. package/lib/evaluation-icon.js.map +1 -0
  43. package/lib/image-container.js +94 -0
  44. package/lib/image-container.js.map +1 -0
  45. package/lib/image-drop-target.js +130 -0
  46. package/lib/image-drop-target.js.map +1 -0
  47. package/lib/index.js +220 -0
  48. package/lib/index.js.map +1 -0
  49. package/lib/interactive-section.js +104 -0
  50. package/lib/interactive-section.js.map +1 -0
  51. package/lib/possible-response.js +161 -0
  52. package/lib/possible-response.js.map +1 -0
  53. package/lib/possible-responses.js +58 -0
  54. package/lib/possible-responses.js.map +1 -0
  55. package/lib/root.js +491 -0
  56. package/lib/root.js.map +1 -0
  57. package/lib/static-html-span.js +35 -0
  58. package/lib/static-html-span.js.map +1 -0
  59. package/lib/utils-correctness.js +89 -0
  60. package/lib/utils-correctness.js.map +1 -0
  61. package/package.json +21 -86
  62. package/src/__tests__/index.test.js +174 -0
  63. package/src/__tests__/root.test.jsx +99 -0
  64. package/src/__tests__/utils.test.js +207 -0
  65. package/src/constants.js +5 -0
  66. package/src/evaluation-icon.jsx +54 -0
  67. package/src/image-container.jsx +90 -0
  68. package/src/image-drop-target.jsx +140 -0
  69. package/src/index.js +245 -0
  70. package/src/interactive-section.jsx +94 -0
  71. package/src/possible-response.jsx +152 -0
  72. package/src/possible-responses.jsx +52 -0
  73. package/src/root.jsx +490 -0
  74. package/src/static-html-span.jsx +30 -0
  75. package/src/utils-correctness.js +95 -0
  76. package/configure.js +0 -2
  77. package/controller.js +0 -1
  78. package/dist/author/defaults.d.ts +0 -88
  79. package/dist/author/defaults.js +0 -58
  80. package/dist/author/index.d.ts +0 -34
  81. package/dist/author/index.js +0 -71
  82. package/dist/author/root.d.ts +0 -14
  83. package/dist/author/root.js +0 -80
  84. package/dist/browser/Check-DL1c-mLM.js +0 -10708
  85. package/dist/browser/Check-DL1c-mLM.js.map +0 -1
  86. package/dist/browser/author/index.js +0 -38597
  87. package/dist/browser/author/index.js.map +0 -1
  88. package/dist/browser/controller/index.js +0 -171
  89. package/dist/browser/controller/index.js.map +0 -1
  90. package/dist/browser/delivery/index.js +0 -2699
  91. package/dist/browser/delivery/index.js.map +0 -1
  92. package/dist/browser/dist-BphSS14E.js +0 -346
  93. package/dist/browser/dist-BphSS14E.js.map +0 -1
  94. package/dist/browser/humps-CZ4RCLab.js +0 -67
  95. package/dist/browser/humps-CZ4RCLab.js.map +0 -1
  96. package/dist/browser/image-cloze-association.css +0 -2
  97. package/dist/controller/defaults.d.ts +0 -16
  98. package/dist/controller/defaults.js +0 -10
  99. package/dist/controller/index.d.ts +0 -23
  100. package/dist/controller/index.js +0 -122
  101. package/dist/controller/utils.d.ts +0 -14
  102. package/dist/controller/utils.js +0 -36
  103. package/dist/delivery/constants.d.ts +0 -14
  104. package/dist/delivery/evaluation-icon.d.ts +0 -28
  105. package/dist/delivery/evaluation-icon.js +0 -38
  106. package/dist/delivery/image-container.d.ts +0 -13
  107. package/dist/delivery/image-container.js +0 -61
  108. package/dist/delivery/image-drop-target.d.ts +0 -45
  109. package/dist/delivery/image-drop-target.js +0 -90
  110. package/dist/delivery/index.d.ts +0 -20
  111. package/dist/delivery/index.js +0 -110
  112. package/dist/delivery/interactive-section.d.ts +0 -15
  113. package/dist/delivery/interactive-section.js +0 -72
  114. package/dist/delivery/possible-response.d.ts +0 -34
  115. package/dist/delivery/possible-response.js +0 -100
  116. package/dist/delivery/possible-responses.d.ts +0 -31
  117. package/dist/delivery/possible-responses.js +0 -41
  118. package/dist/delivery/root.d.ts +0 -21
  119. package/dist/delivery/root.js +0 -278
  120. package/dist/delivery/static-html-span.d.ts +0 -14
  121. package/dist/delivery/static-html-span.js +0 -22
  122. package/dist/delivery/utils-correctness.d.ts +0 -10
  123. package/dist/delivery/utils-correctness.js +0 -43
  124. package/dist/index.d.ts +0 -1
  125. package/dist/index.iife.d.ts +0 -8
  126. package/dist/index.iife.js +0 -152
  127. package/dist/index.js +0 -2
  128. package/dist/node_modules/.bun/clsx@2.1.1/node_modules/clsx/dist/clsx.js +0 -16
  129. package/dist/runtime-support.d.ts +0 -12
  130. package/dist/runtime-support.js +0 -12
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+ var _default = exports.default = {
8
+ rationale: '',
9
+ rationaleEnabled: true,
10
+ studentInstructionsEnabled: true,
11
+ teacherInstructions: '',
12
+ teacherInstructionsEnabled: true
13
+ };
14
+ //# sourceMappingURL=defaults.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"defaults.js","names":["rationale","rationaleEnabled","studentInstructionsEnabled","teacherInstructions","teacherInstructionsEnabled"],"sources":["../src/defaults.js"],"sourcesContent":["export default {\n rationale: '',\n rationaleEnabled: true,\n studentInstructionsEnabled: true,\n teacherInstructions: '',\n teacherInstructionsEnabled: true,\n};\n"],"mappings":";;;;;;iCAAe;EACbA,SAAS,EAAE,EAAE;EACbC,gBAAgB,EAAE,IAAI;EACtBC,0BAA0B,EAAE,IAAI;EAChCC,mBAAmB,EAAE,EAAE;EACvBC,0BAA0B,EAAE;AAC9B,CAAC","ignoreList":[]}
@@ -0,0 +1,304 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.validate = exports.outcome = exports.normalize = exports.model = exports.isResponseCorrect = exports.getPartialScore = exports.getLogTrace = exports.createCorrectResponseSession = void 0;
8
+ var _debug = _interopRequireDefault(require("debug"));
9
+ var _humps = require("humps");
10
+ var _controllerUtils = require("@pie-lib/controller-utils");
11
+ var _lodashEs = require("lodash-es");
12
+ var _defaults = _interopRequireDefault(require("./defaults"));
13
+ var _utils = require("./utils");
14
+ const log = (0, _debug.default)('pie-elements:image-cloze-association:controller');
15
+ const normalize = question => ({
16
+ ..._defaults.default,
17
+ ...question
18
+ });
19
+ exports.normalize = normalize;
20
+ const model = (question, session, env) => {
21
+ const questionNormalized = normalize(question);
22
+ const questionCamelized = (0, _humps.camelizeKeys)(questionNormalized);
23
+ return new Promise(resolve => {
24
+ const shouldIncludeCorrectResponse = env.mode === 'evaluate';
25
+ const {
26
+ responseAreasToBeFilled,
27
+ possibleResponses: completeResponses,
28
+ hasUnplacedChoices
29
+ } = (0, _utils.getCompleteResponseDetails)(questionCamelized.validation, questionCamelized.possibleResponses);
30
+ const out = {
31
+ disabled: env.mode !== 'gather',
32
+ mode: env.mode,
33
+ ...questionCamelized,
34
+ responseCorrect: shouldIncludeCorrectResponse ? getScore(questionCamelized, session) === 1 : undefined,
35
+ validation: shouldIncludeCorrectResponse ? questionCamelized.validation : undefined,
36
+ responseAreasToBeFilled,
37
+ completeResponses,
38
+ hasUnplacedChoices
39
+ };
40
+ if (questionNormalized.shuffle) {
41
+ out.possibleResponses = (0, _lodashEs.shuffle)(questionNormalized.possible_responses);
42
+ }
43
+ if (env.role === 'instructor' && (env.mode === 'view' || env.mode === 'evaluate')) {
44
+ out.teacherInstructions = questionCamelized.teacherInstructionsEnabled ? questionCamelized.teacherInstructions : null;
45
+ out.rationale = questionCamelized.rationale ? questionCamelized.rationale : null;
46
+ } else {
47
+ out.teacherInstructions = null;
48
+ out.rationale = null;
49
+ }
50
+ resolve(out);
51
+ });
52
+ };
53
+ exports.model = model;
54
+ const isResponseCorrect = (correctResponses, session) => {
55
+ const responses = (0, _lodashEs.cloneDeep)(correctResponses);
56
+ let isCorrect = true;
57
+ let totalValidResponses = 0;
58
+ if (!session || (0, _lodashEs.isEmpty)(session)) {
59
+ return false;
60
+ }
61
+ responses.forEach(value => totalValidResponses += (value.images || []).length);
62
+ if (session.answers && totalValidResponses === session.answers.length) {
63
+ session.answers.forEach(answer => {
64
+ const index = (responses[answer.containerIndex]?.images || []).indexOf(answer.value);
65
+ if (index >= 0) {
66
+ // remove response from correct responses array to ensure that duplicates are evaluated correctly
67
+ responses[answer.containerIndex].images.splice(index, 1);
68
+ } else {
69
+ isCorrect = false;
70
+ }
71
+ });
72
+ } else {
73
+ isCorrect = false;
74
+ }
75
+ return isCorrect;
76
+ };
77
+
78
+ // This applies for correct responses that have empty values
79
+ exports.isResponseCorrect = isResponseCorrect;
80
+ const keepNonEmptyResponses = responses => {
81
+ const filtered = responses.filter(response => response.images && response.images.length);
82
+ return (0, _lodashEs.cloneDeep)(filtered);
83
+ };
84
+
85
+ // This applies for items that don't support partial scoring.
86
+ const isDefaultOrAltResponseCorrect = (question, session) => {
87
+ const {
88
+ validation: {
89
+ altResponses
90
+ }
91
+ } = question;
92
+ let {
93
+ validation: {
94
+ validResponse: {
95
+ value
96
+ }
97
+ }
98
+ } = question;
99
+ let isCorrect = isResponseCorrect(value, session);
100
+
101
+ // Look for correct answers in alternate responses.
102
+ if (!isCorrect && altResponses && altResponses.length) {
103
+ altResponses.forEach(altResponse => {
104
+ if (isResponseCorrect(altResponse.value, session)) {
105
+ isCorrect = true;
106
+ }
107
+ });
108
+ }
109
+ return isCorrect;
110
+ };
111
+
112
+ // Deduct only the items that exceeded the maximum valid response per container.
113
+ const getDeductionPerContainer = (containerIndex, answers, valid) => {
114
+ const totalStack = answers.filter(item => item.containerIndex === containerIndex);
115
+ const incorrectStack = totalStack.filter(item => !item.isCorrect);
116
+ const maxValid = (valid.value[containerIndex].images || []).length;
117
+ if (totalStack.length > maxValid) {
118
+ const ignored = totalStack.length - maxValid;
119
+ return incorrectStack.slice(-ignored);
120
+ }
121
+ return [];
122
+ };
123
+ const getPartialScore = (question, session) => {
124
+ const {
125
+ validation: {
126
+ validResponse
127
+ },
128
+ maxResponsePerZone
129
+ } = question;
130
+ let correctAnswers = 0;
131
+ let incorrectAnswers = 0;
132
+ let possibleResponses = 0;
133
+ if (!session || (0, _lodashEs.isEmpty)(session)) {
134
+ return 0;
135
+ }
136
+ validResponse.value.forEach(value => possibleResponses += (value.images || []).length);
137
+ if (session.answers && session.answers.length) {
138
+ const all = (0, _utils.getAllUniqueCorrectness)(session.answers, validResponse.value);
139
+ correctAnswers = all.filter(item => item.isCorrect).length;
140
+ incorrectAnswers = all.filter(item => !item.isCorrect).length;
141
+
142
+ // deduction rules: https://docs.google.com/document/d/1Oprm8Qs5fg_Dwoj2pNpsfu4D63QgCZgvcqTgeaVel7I/edit
143
+ session.answers.forEach(answer => {
144
+ if (maxResponsePerZone > 1) {
145
+ const deductionList = getDeductionPerContainer(answer.containerIndex, all, validResponse);
146
+ if (deductionList.length) {
147
+ deductionList.forEach(item => {
148
+ if (item.id === answer.id) {
149
+ correctAnswers -= 1;
150
+ }
151
+ });
152
+ }
153
+ }
154
+ });
155
+ if (!maxResponsePerZone || maxResponsePerZone <= 1) {
156
+ correctAnswers -= incorrectAnswers;
157
+ }
158
+ } else {
159
+ correctAnswers = 0;
160
+ }
161
+ // negative values will implicitly make the score equal to zero
162
+ correctAnswers = correctAnswers < 0 ? 0 : correctAnswers;
163
+
164
+ // use length of validResponse since some containers can be left empty
165
+ const nonEmptyResponses = keepNonEmptyResponses(validResponse.value);
166
+ const denominator = maxResponsePerZone > 1 ? possibleResponses : (nonEmptyResponses || []).length;
167
+ const str = (correctAnswers / denominator).toFixed(2);
168
+ return parseFloat(str);
169
+ };
170
+ exports.getPartialScore = getPartialScore;
171
+ const getScore = (config, session, env = {}) => {
172
+ const isPartialScoring = _controllerUtils.partialScoring.enabled(config, env);
173
+ const correct = isDefaultOrAltResponseCorrect(config, session);
174
+ return isPartialScoring ? getPartialScore(config, session) : correct ? 1 : 0;
175
+ };
176
+
177
+ /**
178
+ * Generates detailed trace log for scoring evaluation
179
+ * @param {Object} model - the question model
180
+ * @param {Object} session - the student session
181
+ * @param {Object} env - the environment
182
+ * @returns {Array} traceLog - array of trace messages
183
+ */
184
+ const getLogTrace = (model, session, env) => {
185
+ const traceLog = [];
186
+ const {
187
+ answers
188
+ } = session || {};
189
+ const validResponse = model.validation?.validResponse?.value || [];
190
+ const totalContainers = validResponse.length;
191
+ traceLog.push(`${totalContainers} placement container(s) defined in this question.`);
192
+ if (answers && answers.length > 0) {
193
+ traceLog.push(`Student placed ${answers.length} image(s) into placement containers.`);
194
+ const answersByContainer = {};
195
+ answers.forEach(answer => {
196
+ if (!answersByContainer[answer.containerIndex]) {
197
+ answersByContainer[answer.containerIndex] = [];
198
+ }
199
+ answersByContainer[answer.containerIndex].push(answer.value);
200
+ });
201
+ validResponse.forEach((container, containerIndex) => {
202
+ const correctImages = container.images || [];
203
+ const studentImages = answersByContainer[containerIndex] || [];
204
+ if (correctImages.length > 0) {
205
+ if (studentImages.length === 0) {
206
+ traceLog.push(`Container ${containerIndex + 1}: student left empty (should contain ${correctImages.length} image(s)).`);
207
+ } else {
208
+ const correctCount = studentImages.filter(img => correctImages.includes(img)).length;
209
+ const incorrectCount = studentImages.length - correctCount;
210
+ if (correctCount > 0 && incorrectCount === 0) {
211
+ traceLog.push(`Container ${containerIndex + 1}: student placed ${correctCount} correct image(s).`);
212
+ } else if (correctCount === 0 && incorrectCount > 0) {
213
+ traceLog.push(`Container ${containerIndex + 1}: student placed ${incorrectCount} incorrect image(s).`);
214
+ } else {
215
+ traceLog.push(`Container ${containerIndex + 1}: student placed ${correctCount} correct and ${incorrectCount} incorrect image(s).`);
216
+ }
217
+ }
218
+ }
219
+ });
220
+ } else {
221
+ traceLog.push('Student did not place any images into placement containers.');
222
+ }
223
+ const altResponses = model.validation?.altResponses || [];
224
+ if (altResponses.length > 0) {
225
+ traceLog.push(`${altResponses.length} alternate response combination(s) are accepted for this question.`);
226
+ }
227
+ const partialScoringEnabled = _controllerUtils.partialScoring.enabled(model, env);
228
+ const scoringMethod = partialScoringEnabled ? 'partial scoring' : 'all-or-nothing scoring';
229
+ traceLog.push(`Score calculated using ${scoringMethod}.`);
230
+ const score = getScore(model, session, env);
231
+ traceLog.push(`Final score: ${score}.`);
232
+ return traceLog;
233
+ };
234
+ exports.getLogTrace = getLogTrace;
235
+ const outcome = (config, session, env = {}) => {
236
+ return new Promise(resolve => {
237
+ log('outcome...');
238
+ if (!session || (0, _lodashEs.isEmpty)(session)) {
239
+ resolve({
240
+ score: 0,
241
+ empty: true,
242
+ traceLog: ['Student did not place any images into placement containers. Score is 0.']
243
+ });
244
+ } else {
245
+ const configCamelized = (0, _humps.camelizeKeys)(config);
246
+ const traceLog = getLogTrace(configCamelized, session, env);
247
+ const score = getScore(configCamelized, session, env);
248
+ resolve({
249
+ score,
250
+ empty: false,
251
+ traceLog
252
+ });
253
+ }
254
+ });
255
+ };
256
+ exports.outcome = outcome;
257
+ const createCorrectResponseSession = (question, env) => {
258
+ return new Promise(resolve => {
259
+ if (env.mode !== 'evaluate' && env.role === 'instructor') {
260
+ const {
261
+ validation: {
262
+ valid_response: {
263
+ value
264
+ }
265
+ }
266
+ } = question;
267
+ const answers = [];
268
+ if (value) {
269
+ value.forEach((container, i) => {
270
+ (container.images || []).forEach(v => {
271
+ answers.push({
272
+ value: v,
273
+ containerIndex: i
274
+ });
275
+ });
276
+ });
277
+ }
278
+ resolve({
279
+ answers,
280
+ id: '1'
281
+ });
282
+ } else {
283
+ resolve(null);
284
+ }
285
+ });
286
+ };
287
+
288
+ // remove all html tags
289
+ exports.createCorrectResponseSession = createCorrectResponseSession;
290
+ const getInnerText = html => (html || '').replaceAll(/<[^>]*>/g, '');
291
+
292
+ // remove all html tags except img, iframe and source tag for audio
293
+ const getContent = html => (html || '').replace(/(<(?!img|iframe|source)([^>]+)>)/gi, '');
294
+ const validate = (model = {}, config = {}) => {
295
+ const errors = {};
296
+ ['teacherInstructions'].forEach(field => {
297
+ if (config[field]?.required && !getContent(model[field])) {
298
+ errors[field] = 'This field is required.';
299
+ }
300
+ });
301
+ return errors;
302
+ };
303
+ exports.validate = validate;
304
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["_debug","_interopRequireDefault","require","_humps","_controllerUtils","_lodashEs","_defaults","_utils","log","debug","normalize","question","defaults","exports","model","session","env","questionNormalized","questionCamelized","camelizeKeys","Promise","resolve","shouldIncludeCorrectResponse","mode","responseAreasToBeFilled","possibleResponses","completeResponses","hasUnplacedChoices","getCompleteResponseDetails","validation","out","disabled","responseCorrect","getScore","undefined","shuffle","possible_responses","role","teacherInstructions","teacherInstructionsEnabled","rationale","isResponseCorrect","correctResponses","responses","cloneDeep","isCorrect","totalValidResponses","isEmpty","forEach","value","images","length","answers","answer","index","containerIndex","indexOf","splice","keepNonEmptyResponses","filtered","filter","response","isDefaultOrAltResponseCorrect","altResponses","validResponse","altResponse","getDeductionPerContainer","valid","totalStack","item","incorrectStack","maxValid","ignored","slice","getPartialScore","maxResponsePerZone","correctAnswers","incorrectAnswers","all","getAllUniqueCorrectness","deductionList","id","nonEmptyResponses","denominator","str","toFixed","parseFloat","config","isPartialScoring","partialScoring","enabled","correct","getLogTrace","traceLog","totalContainers","push","answersByContainer","container","correctImages","studentImages","correctCount","img","includes","incorrectCount","partialScoringEnabled","scoringMethod","score","outcome","empty","configCamelized","createCorrectResponseSession","valid_response","i","v","getInnerText","html","replaceAll","getContent","replace","validate","errors","field","required"],"sources":["../src/index.js"],"sourcesContent":["import debug from 'debug';\nimport { camelizeKeys } from 'humps';\nimport { partialScoring } from '@pie-lib/controller-utils';\nimport { cloneDeep, isEmpty, shuffle } from 'lodash-es';\n\nimport defaults from './defaults';\nimport { getAllUniqueCorrectness, getCompleteResponseDetails } from './utils';\n\nconst log = debug('pie-elements:image-cloze-association:controller');\n\nexport const normalize = (question) => ({ ...defaults, ...question });\n\nexport const model = (question, session, env) => {\n const questionNormalized = normalize(question);\n const questionCamelized = camelizeKeys(questionNormalized);\n\n return new Promise((resolve) => {\n const shouldIncludeCorrectResponse = env.mode === 'evaluate';\n\n const {\n responseAreasToBeFilled,\n possibleResponses: completeResponses,\n hasUnplacedChoices,\n } = getCompleteResponseDetails(questionCamelized.validation, questionCamelized.possibleResponses);\n\n const out = {\n disabled: env.mode !== 'gather',\n mode: env.mode,\n ...questionCamelized,\n responseCorrect: shouldIncludeCorrectResponse ? getScore(questionCamelized, session) === 1 : undefined,\n validation: shouldIncludeCorrectResponse ? questionCamelized.validation : undefined,\n responseAreasToBeFilled,\n completeResponses,\n hasUnplacedChoices,\n };\n\n if (questionNormalized.shuffle) {\n out.possibleResponses = shuffle(questionNormalized.possible_responses);\n }\n\n if (env.role === 'instructor' && (env.mode === 'view' || env.mode === 'evaluate')) {\n out.teacherInstructions = questionCamelized.teacherInstructionsEnabled\n ? questionCamelized.teacherInstructions\n : null;\n out.rationale = questionCamelized.rationale ? questionCamelized.rationale : null;\n } else {\n out.teacherInstructions = null;\n out.rationale = null;\n }\n\n resolve(out);\n });\n};\n\nexport const isResponseCorrect = (correctResponses, session) => {\n const responses = cloneDeep(correctResponses);\n let isCorrect = true;\n let totalValidResponses = 0;\n\n if (!session || isEmpty(session)) {\n return false;\n }\n\n responses.forEach((value) => (totalValidResponses += (value.images || []).length));\n\n if (session.answers && totalValidResponses === session.answers.length) {\n session.answers.forEach((answer) => {\n const index = (responses[answer.containerIndex]?.images || []).indexOf(answer.value);\n\n if (index >= 0) {\n // remove response from correct responses array to ensure that duplicates are evaluated correctly\n responses[answer.containerIndex].images.splice(index, 1);\n } else {\n isCorrect = false;\n }\n });\n } else {\n isCorrect = false;\n }\n\n return isCorrect;\n};\n\n// This applies for correct responses that have empty values\nconst keepNonEmptyResponses = (responses) => {\n const filtered = responses.filter((response) => response.images && response.images.length);\n return cloneDeep(filtered);\n};\n\n// This applies for items that don't support partial scoring.\nconst isDefaultOrAltResponseCorrect = (question, session) => {\n const {\n validation: { altResponses },\n } = question;\n let {\n validation: {\n validResponse: { value },\n },\n } = question;\n\n let isCorrect = isResponseCorrect(value, session);\n\n // Look for correct answers in alternate responses.\n if (!isCorrect && altResponses && altResponses.length) {\n altResponses.forEach((altResponse) => {\n if (isResponseCorrect(altResponse.value, session)) {\n isCorrect = true;\n }\n });\n }\n return isCorrect;\n};\n\n// Deduct only the items that exceeded the maximum valid response per container.\nconst getDeductionPerContainer = (containerIndex, answers, valid) => {\n const totalStack = answers.filter((item) => item.containerIndex === containerIndex);\n const incorrectStack = totalStack.filter((item) => !item.isCorrect);\n const maxValid = (valid.value[containerIndex].images || []).length;\n\n if (totalStack.length > maxValid) {\n const ignored = totalStack.length - maxValid;\n return incorrectStack.slice(-ignored);\n }\n return [];\n};\n\nexport const getPartialScore = (question, session) => {\n const {\n validation: { validResponse },\n maxResponsePerZone,\n } = question;\n let correctAnswers = 0;\n let incorrectAnswers = 0;\n let possibleResponses = 0;\n\n if (!session || isEmpty(session)) {\n return 0;\n }\n\n validResponse.value.forEach((value) => (possibleResponses += (value.images || []).length));\n\n if (session.answers && session.answers.length) {\n const all = getAllUniqueCorrectness(session.answers, validResponse.value);\n correctAnswers = all.filter((item) => item.isCorrect).length;\n incorrectAnswers = all.filter((item) => !item.isCorrect).length;\n\n // deduction rules: https://docs.google.com/document/d/1Oprm8Qs5fg_Dwoj2pNpsfu4D63QgCZgvcqTgeaVel7I/edit\n session.answers.forEach((answer) => {\n if (maxResponsePerZone > 1) {\n const deductionList = getDeductionPerContainer(answer.containerIndex, all, validResponse);\n\n if (deductionList.length) {\n deductionList.forEach((item) => {\n if (item.id === answer.id) {\n correctAnswers -= 1;\n }\n });\n }\n }\n });\n\n if (!maxResponsePerZone || maxResponsePerZone <= 1) {\n correctAnswers -= incorrectAnswers;\n }\n } else {\n correctAnswers = 0;\n }\n // negative values will implicitly make the score equal to zero\n correctAnswers = correctAnswers < 0 ? 0 : correctAnswers;\n\n // use length of validResponse since some containers can be left empty\n const nonEmptyResponses = keepNonEmptyResponses(validResponse.value);\n const denominator = maxResponsePerZone > 1 ? possibleResponses : (nonEmptyResponses || []).length;\n const str = (correctAnswers / denominator).toFixed(2);\n\n return parseFloat(str);\n};\n\nconst getScore = (config, session, env = {}) => {\n const isPartialScoring = partialScoring.enabled(config, env);\n const correct = isDefaultOrAltResponseCorrect(config, session);\n\n return isPartialScoring ? getPartialScore(config, session) : correct ? 1 : 0;\n};\n\n/**\n * Generates detailed trace log for scoring evaluation\n * @param {Object} model - the question model\n * @param {Object} session - the student session\n * @param {Object} env - the environment\n * @returns {Array} traceLog - array of trace messages\n */\nexport const getLogTrace = (model, session, env) => {\n const traceLog = [];\n const { answers } = session || {};\n \n const validResponse = model.validation?.validResponse?.value || [];\n const totalContainers = validResponse.length;\n traceLog.push(`${totalContainers} placement container(s) defined in this question.`);\n\n if (answers && answers.length > 0) {\n traceLog.push(`Student placed ${answers.length} image(s) into placement containers.`);\n \n const answersByContainer = {};\n answers.forEach((answer) => {\n if (!answersByContainer[answer.containerIndex]) {\n answersByContainer[answer.containerIndex] = [];\n }\n answersByContainer[answer.containerIndex].push(answer.value);\n });\n \n validResponse.forEach((container, containerIndex) => {\n const correctImages = container.images || [];\n const studentImages = answersByContainer[containerIndex] || [];\n \n if (correctImages.length > 0) {\n if (studentImages.length === 0) {\n traceLog.push(`Container ${containerIndex + 1}: student left empty (should contain ${correctImages.length} image(s)).`);\n } else {\n const correctCount = studentImages.filter(img => correctImages.includes(img)).length;\n const incorrectCount = studentImages.length - correctCount;\n \n if (correctCount > 0 && incorrectCount === 0) {\n traceLog.push(`Container ${containerIndex + 1}: student placed ${correctCount} correct image(s).`);\n } else if (correctCount === 0 && incorrectCount > 0) {\n traceLog.push(`Container ${containerIndex + 1}: student placed ${incorrectCount} incorrect image(s).`);\n } else {\n traceLog.push(`Container ${containerIndex + 1}: student placed ${correctCount} correct and ${incorrectCount} incorrect image(s).`);\n }\n }\n }\n });\n } else {\n traceLog.push('Student did not place any images into placement containers.');\n }\n\n const altResponses = model.validation?.altResponses || [];\n if (altResponses.length > 0) {\n traceLog.push(`${altResponses.length} alternate response combination(s) are accepted for this question.`);\n }\n\n const partialScoringEnabled = partialScoring.enabled(model, env);\n const scoringMethod = partialScoringEnabled ? 'partial scoring' : 'all-or-nothing scoring';\n traceLog.push(`Score calculated using ${scoringMethod}.`);\n\n const score = getScore(model, session, env);\n traceLog.push(`Final score: ${score}.`);\n\n return traceLog;\n}\n\nexport const outcome = (config, session, env = {}) => {\n return new Promise((resolve) => {\n log('outcome...');\n if (!session || isEmpty(session)) {\n resolve({ \n score: 0, \n empty: true, \n traceLog: ['Student did not place any images into placement containers. Score is 0.'] \n });\n } else {\n const configCamelized = camelizeKeys(config);\n const traceLog = getLogTrace(configCamelized, session, env);\n const score = getScore(configCamelized, session, env);\n \n resolve({ \n score, \n empty: false,\n traceLog \n });\n }\n });\n};\n\nexport const createCorrectResponseSession = (question, env) => {\n return new Promise((resolve) => {\n if (env.mode !== 'evaluate' && env.role === 'instructor') {\n const {\n validation: {\n valid_response: { value },\n },\n } = question;\n const answers = [];\n\n if (value) {\n value.forEach((container, i) => {\n (container.images || []).forEach((v) => {\n answers.push({\n value: v,\n containerIndex: i,\n });\n });\n });\n }\n\n resolve({\n answers,\n id: '1',\n });\n } else {\n resolve(null);\n }\n });\n};\n\n// remove all html tags\nconst getInnerText = (html) => (html || '').replaceAll(/<[^>]*>/g, '');\n\n// remove all html tags except img, iframe and source tag for audio\nconst getContent = (html) => (html || '').replace(/(<(?!img|iframe|source)([^>]+)>)/gi, '');\n\nexport const validate = (model = {}, config = {}) => {\n const errors = {};\n\n ['teacherInstructions'].forEach((field) => {\n if (config[field]?.required && !getContent(model[field])) {\n errors[field] = 'This field is required.';\n }\n });\n\n return errors;\n};\n"],"mappings":"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","ignoreList":[]}
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.getCompleteResponseDetails = exports.getAllUniqueCorrectness = void 0;
7
+ // functions also used in src/utils-correctness.js
8
+
9
+ const getAllCorrectness = (answers, responses) => (answers || []).map(answer => ({
10
+ ...answer,
11
+ isCorrect: (responses[answer.containerIndex] && responses[answer.containerIndex].images || []).includes(answer.value)
12
+ }));
13
+ const getValidAnswer = (answer, response) => (response[answer.containerIndex] && response[answer.containerIndex].images || []).filter(res => res === answer.value);
14
+ const getAllUniqueCorrectness = (answers, validResponses) => {
15
+ let allCorrectness = getAllCorrectness(answers, validResponses);
16
+ answers.forEach(answer1 => {
17
+ const valuesToParse = answers.filter(answer2 => answer2.value === answer1.value && answer2.containerIndex === answer1.containerIndex);
18
+ if (valuesToParse.length > 1) {
19
+ // point only to duplicates but first
20
+ valuesToParse.shift();
21
+ // mark duplicates as incorrect
22
+ valuesToParse.forEach((value, index) => {
23
+ allCorrectness = (allCorrectness || []).map(finalAnswer => {
24
+ if (finalAnswer.id === value.id) {
25
+ let valid = getValidAnswer(finalAnswer, validResponses);
26
+ return {
27
+ ...finalAnswer,
28
+ isCorrect: valid.length > index + 1
29
+ };
30
+ }
31
+ return finalAnswer;
32
+ });
33
+ });
34
+ }
35
+ });
36
+ return allCorrectness;
37
+ };
38
+
39
+ // calculate the minimum number of populated response areas (categories) in the correct answer or alternates
40
+ // and create an array with the possible responses ids
41
+ exports.getAllUniqueCorrectness = getAllUniqueCorrectness;
42
+ const getCompleteResponseDetails = (validation, allChoices) => {
43
+ const extractImages = response => (response?.value || []).map(container => container.images);
44
+ const countFilledResponseAreas = container => (container || []).filter(images => images.length).length;
45
+ const {
46
+ validResponse,
47
+ altResponses
48
+ } = validation || {};
49
+ const imagesPerContainer = extractImages(validResponse);
50
+ const possibleResponses = [imagesPerContainer.flat()];
51
+ let responseAreasToBeFilled = countFilledResponseAreas(imagesPerContainer);
52
+ (altResponses || []).forEach(altResponse => {
53
+ const altImagesPerContainer = extractImages(altResponse);
54
+ const filledResponseAreas = countFilledResponseAreas(altImagesPerContainer);
55
+ possibleResponses.push(altImagesPerContainer.flat());
56
+ if (filledResponseAreas < responseAreasToBeFilled) {
57
+ responseAreasToBeFilled = filledResponseAreas;
58
+ }
59
+ });
60
+
61
+ // check if any correct answer have any unplaced answer choices
62
+ const hasUnplacedChoices = possibleResponses.some(response => !allChoices.every(val => response.includes(val)));
63
+ return {
64
+ responseAreasToBeFilled,
65
+ possibleResponses,
66
+ hasUnplacedChoices
67
+ };
68
+ };
69
+ exports.getCompleteResponseDetails = getCompleteResponseDetails;
70
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","names":["getAllCorrectness","answers","responses","map","answer","isCorrect","containerIndex","images","includes","value","getValidAnswer","response","filter","res","getAllUniqueCorrectness","validResponses","allCorrectness","forEach","answer1","valuesToParse","answer2","length","shift","index","finalAnswer","id","valid","exports","getCompleteResponseDetails","validation","allChoices","extractImages","container","countFilledResponseAreas","validResponse","altResponses","imagesPerContainer","possibleResponses","flat","responseAreasToBeFilled","altResponse","altImagesPerContainer","filledResponseAreas","push","hasUnplacedChoices","some","every","val"],"sources":["../src/utils.js"],"sourcesContent":["// functions also used in src/utils-correctness.js\n\nconst getAllCorrectness = (answers, responses) =>\n (answers || []).map((answer) => ({\n ...answer,\n isCorrect: ((responses[answer.containerIndex] && responses[answer.containerIndex].images) || []).includes(\n answer.value,\n ),\n }));\n\nconst getValidAnswer = (answer, response) =>\n ((response[answer.containerIndex] && response[answer.containerIndex].images) || []).filter(\n (res) => res === answer.value,\n );\n\nexport const getAllUniqueCorrectness = (answers, validResponses) => {\n let allCorrectness = getAllCorrectness(answers, validResponses);\n\n answers.forEach((answer1) => {\n const valuesToParse = answers.filter(\n (answer2) => answer2.value === answer1.value && answer2.containerIndex === answer1.containerIndex,\n );\n\n if (valuesToParse.length > 1) {\n // point only to duplicates but first\n valuesToParse.shift();\n // mark duplicates as incorrect\n valuesToParse.forEach((value, index) => {\n allCorrectness = (allCorrectness || []).map((finalAnswer) => {\n if (finalAnswer.id === value.id) {\n let valid = getValidAnswer(finalAnswer, validResponses);\n return {\n ...finalAnswer,\n isCorrect: valid.length > index + 1,\n };\n }\n return finalAnswer;\n });\n });\n }\n });\n return allCorrectness;\n};\n\n// calculate the minimum number of populated response areas (categories) in the correct answer or alternates\n// and create an array with the possible responses ids\nexport const getCompleteResponseDetails = (validation, allChoices) => {\n const extractImages = (response) => (response?.value || []).map((container) => container.images);\n const countFilledResponseAreas = (container) => (container || []).filter((images) => images.length).length;\n\n const { validResponse, altResponses } = validation || {};\n const imagesPerContainer = extractImages(validResponse);\n const possibleResponses = [imagesPerContainer.flat()];\n let responseAreasToBeFilled = countFilledResponseAreas(imagesPerContainer);\n\n (altResponses || []).forEach((altResponse) => {\n const altImagesPerContainer = extractImages(altResponse);\n const filledResponseAreas = countFilledResponseAreas(altImagesPerContainer);\n possibleResponses.push(altImagesPerContainer.flat());\n\n if (filledResponseAreas < responseAreasToBeFilled) {\n responseAreasToBeFilled = filledResponseAreas;\n }\n });\n\n // check if any correct answer have any unplaced answer choices\n const hasUnplacedChoices = possibleResponses.some(\n (response) => !allChoices.every((val) => response.includes(val)),\n );\n\n return { responseAreasToBeFilled, possibleResponses, hasUnplacedChoices };\n};\n"],"mappings":"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","ignoreList":[]}
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "@pie-element/image-cloze-association-controller",
3
+ "private": true,
4
+ "version": "8.1.1",
5
+ "description": "",
6
+ "scripts": {
7
+ "test": "echo \"Error: no test specified\" && exit 1"
8
+ },
9
+ "author": "",
10
+ "license": "ISC",
11
+ "main": "lib/index.js",
12
+ "module": "src/index.js",
13
+ "dependencies": {
14
+ "@pie-lib/controller-utils": "2.0.2",
15
+ "debug": "^4.1.1",
16
+ "humps": "^2.0.1",
17
+ "lodash-es": "^4.17.23"
18
+ }
19
+ }