@intellectif/lk-core 0.9.0 → 0.10.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/CHANGELOG.md +38 -0
- package/dist/{activity-CPtJUBek.d.cts → activity-B2QxK15j.d.cts} +177 -2
- package/dist/{activity-CPtJUBek.d.ts → activity-B2QxK15j.d.ts} +177 -2
- package/dist/{chunk-YVZCFUGP.cjs → chunk-3FEAEO3D.cjs} +18 -13
- package/dist/chunk-3FEAEO3D.cjs.map +1 -0
- package/dist/{chunk-CVRALNUK.cjs → chunk-7ODQRNR4.cjs} +618 -45
- package/dist/chunk-7ODQRNR4.cjs.map +1 -0
- package/dist/{chunk-AHEUYOT3.js → chunk-FIS5KBCE.js} +670 -97
- package/dist/chunk-FIS5KBCE.js.map +1 -0
- package/dist/{chunk-ELTP5P6V.js → chunk-IXXMYJI7.js} +2 -2
- package/dist/{chunk-MOLL5KXO.cjs → chunk-VBM5H6P5.cjs} +8 -8
- package/dist/{chunk-MOLL5KXO.cjs.map → chunk-VBM5H6P5.cjs.map} +1 -1
- package/dist/{chunk-RL2PQLCY.cjs → chunk-VTTRXGDD.cjs} +4 -4
- package/dist/{chunk-RL2PQLCY.cjs.map → chunk-VTTRXGDD.cjs.map} +1 -1
- package/dist/{chunk-6DM2H6BD.js → chunk-XGCTAQSS.js} +7 -2
- package/dist/chunk-XGCTAQSS.js.map +1 -0
- package/dist/{chunk-QX7P3CHP.js → chunk-ZTY4UIUD.js} +2 -2
- package/dist/{index-BvrA8nIV.d.ts → index-B_scInLM.d.cts} +321 -3
- package/dist/{index-gIN564mV.d.cts → index-CRKtWP5G.d.ts} +321 -3
- package/dist/index.cjs +53 -31
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +26 -4
- package/dist/index.js.map +1 -1
- package/dist/schemas.cjs +25 -3
- package/dist/schemas.cjs.map +1 -1
- package/dist/schemas.d.cts +2 -2
- package/dist/schemas.d.ts +2 -2
- package/dist/schemas.js +24 -2
- package/dist/scoring.cjs +3 -3
- package/dist/scoring.d.cts +2 -2
- package/dist/scoring.d.ts +2 -2
- package/dist/scoring.js +2 -2
- package/dist/xapi.cjs +3 -3
- package/dist/xapi.d.cts +1 -1
- package/dist/xapi.d.ts +1 -1
- package/dist/xapi.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-6DM2H6BD.js.map +0 -1
- package/dist/chunk-AHEUYOT3.js.map +0 -1
- package/dist/chunk-CVRALNUK.cjs.map +0 -1
- package/dist/chunk-YVZCFUGP.cjs.map +0 -1
- /package/dist/{chunk-ELTP5P6V.js.map → chunk-IXXMYJI7.js.map} +0 -0
- /package/dist/{chunk-QX7P3CHP.js.map → chunk-ZTY4UIUD.js.map} +0 -0
|
@@ -179,19 +179,19 @@ var FillInTheBlanksDataSchema = _v4.z.looseObject({
|
|
|
179
179
|
difficultyLevel: _v4.z.literal([1, 2, 3, 4, 5]).optional()
|
|
180
180
|
}).refine(
|
|
181
181
|
(data) => {
|
|
182
|
-
const
|
|
182
|
+
const placeholderCounts2 = /* @__PURE__ */ new Map();
|
|
183
183
|
for (const match of data.passage.matchAll(PLACEHOLDER_RE)) {
|
|
184
184
|
const id = match[1];
|
|
185
|
-
|
|
185
|
+
placeholderCounts2.set(id, (_nullishCoalesce(placeholderCounts2.get(id), () => ( 0))) + 1);
|
|
186
186
|
}
|
|
187
187
|
const blankIds = data.blanks.map((blank) => blank.id);
|
|
188
188
|
if (new Set(blankIds).size !== blankIds.length) {
|
|
189
189
|
return false;
|
|
190
190
|
}
|
|
191
|
-
if (
|
|
191
|
+
if (placeholderCounts2.size !== blankIds.length) {
|
|
192
192
|
return false;
|
|
193
193
|
}
|
|
194
|
-
return blankIds.every((id) =>
|
|
194
|
+
return blankIds.every((id) => placeholderCounts2.get(id) === 1);
|
|
195
195
|
},
|
|
196
196
|
{
|
|
197
197
|
error: "Each blank id must appear exactly once in blanks[] and have exactly one matching {{id}} placeholder in the passage, and vice versa.",
|
|
@@ -199,25 +199,29 @@ var FillInTheBlanksDataSchema = _v4.z.looseObject({
|
|
|
199
199
|
}
|
|
200
200
|
);
|
|
201
201
|
|
|
202
|
-
// src/count-words.ts
|
|
203
|
-
function countWords(text) {
|
|
204
|
-
if (typeof text !== "string") {
|
|
205
|
-
return 0;
|
|
206
|
-
}
|
|
207
|
-
const trimmed = text.trim();
|
|
208
|
-
if (trimmed === "") {
|
|
209
|
-
return 0;
|
|
210
|
-
}
|
|
211
|
-
return trimmed.split(/\s+/).length;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
202
|
// src/schemas/multiple-choice.ts
|
|
215
203
|
|
|
204
|
+
var MultipleChoiceOptionMediaSchema = _v4.z.looseObject({
|
|
205
|
+
// No `embed`: an iframe swallows the click that selects the option, so the
|
|
206
|
+
// learner could not choose it. No `video`: a native control bar inside the
|
|
207
|
+
// option's label eats the same click.
|
|
208
|
+
type: _v4.z.enum(["image", "audio"]),
|
|
209
|
+
url: MediaUrlSchema,
|
|
210
|
+
alt: _v4.z.string().min(1).optional(),
|
|
211
|
+
captionsUrl: MediaUrlSchema.optional()
|
|
212
|
+
}).refine((media) => media.type !== "image" || media.alt !== void 0 && media.alt !== "", {
|
|
213
|
+
error: "An image option requires a non-empty `alt`.",
|
|
214
|
+
path: ["alt"]
|
|
215
|
+
}).refine((media) => !("playback" in media), {
|
|
216
|
+
error: "An option carries no playback policy. maxPlays, seek and rate are enforced only on the media above the question.",
|
|
217
|
+
path: ["playback"]
|
|
218
|
+
});
|
|
216
219
|
var MultipleChoiceOptionSchema = _v4.z.looseObject({
|
|
217
220
|
id: _v4.z.string().min(1),
|
|
218
221
|
text: _v4.z.string().min(1),
|
|
219
222
|
isCorrect: _v4.z.boolean(),
|
|
220
|
-
feedback: _v4.z.string().optional()
|
|
223
|
+
feedback: _v4.z.string().optional(),
|
|
224
|
+
media: MultipleChoiceOptionMediaSchema.optional()
|
|
221
225
|
});
|
|
222
226
|
var MultipleChoiceDataSchema = _v4.z.looseObject({
|
|
223
227
|
schemaVersion: _v4.z.literal("1.0"),
|
|
@@ -250,6 +254,122 @@ var MultipleChoiceDataSchema = _v4.z.looseObject({
|
|
|
250
254
|
path: ["options"]
|
|
251
255
|
});
|
|
252
256
|
|
|
257
|
+
// src/count-words.ts
|
|
258
|
+
function countWords(text) {
|
|
259
|
+
if (typeof text !== "string") {
|
|
260
|
+
return 0;
|
|
261
|
+
}
|
|
262
|
+
const trimmed = text.trim();
|
|
263
|
+
if (trimmed === "") {
|
|
264
|
+
return 0;
|
|
265
|
+
}
|
|
266
|
+
return trimmed.split(/\s+/).length;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// src/schemas/gap-select.ts
|
|
270
|
+
|
|
271
|
+
var GapSelectChoiceSchema = _v4.z.looseObject({
|
|
272
|
+
id: _v4.z.string().min(1),
|
|
273
|
+
text: _v4.z.string().min(1)
|
|
274
|
+
});
|
|
275
|
+
var GapSelectBankSchema = _v4.z.looseObject({
|
|
276
|
+
id: _v4.z.string().min(1),
|
|
277
|
+
choices: _v4.z.array(GapSelectChoiceSchema).min(2)
|
|
278
|
+
});
|
|
279
|
+
var GapSelectGapSchema = _v4.z.looseObject({
|
|
280
|
+
id: _v4.z.string().min(1),
|
|
281
|
+
choices: _v4.z.array(GapSelectChoiceSchema).min(2).optional(),
|
|
282
|
+
bankId: _v4.z.string().min(1).optional(),
|
|
283
|
+
correctChoiceId: _v4.z.string().min(1),
|
|
284
|
+
feedback: _v4.z.string().optional()
|
|
285
|
+
});
|
|
286
|
+
function resolveChoices(gap, banks) {
|
|
287
|
+
if (gap.choices !== void 0) {
|
|
288
|
+
return gap.bankId === void 0 ? gap.choices : void 0;
|
|
289
|
+
}
|
|
290
|
+
if (gap.bankId === void 0) {
|
|
291
|
+
return void 0;
|
|
292
|
+
}
|
|
293
|
+
return _optionalChain([banks, 'access', _10 => _10.find, 'call', _11 => _11((bank) => bank.id === gap.bankId), 'optionalAccess', _12 => _12.choices]);
|
|
294
|
+
}
|
|
295
|
+
function placeholderCounts(passage) {
|
|
296
|
+
const counts = /* @__PURE__ */ new Map();
|
|
297
|
+
for (const match of passage.matchAll(PLACEHOLDER_RE)) {
|
|
298
|
+
const id = match[1];
|
|
299
|
+
counts.set(id, (_nullishCoalesce(counts.get(id), () => ( 0))) + 1);
|
|
300
|
+
}
|
|
301
|
+
return counts;
|
|
302
|
+
}
|
|
303
|
+
var GapSelectDataSchema = _v4.z.looseObject({
|
|
304
|
+
schemaVersion: _v4.z.literal("1.0"),
|
|
305
|
+
type: _v4.z.literal("gap-select"),
|
|
306
|
+
id: _v4.z.string().min(1),
|
|
307
|
+
title: _v4.z.string().min(1),
|
|
308
|
+
passage: _v4.z.string().min(1),
|
|
309
|
+
passageHtml: _v4.z.string().optional(),
|
|
310
|
+
gaps: _v4.z.array(GapSelectGapSchema).min(1),
|
|
311
|
+
banks: _v4.z.array(GapSelectBankSchema).optional(),
|
|
312
|
+
scoringStrategy: _v4.z.enum(["all-or-nothing", "partial"]),
|
|
313
|
+
presentation: _v4.z.literal("dropdown").optional(),
|
|
314
|
+
shuffleChoices: _v4.z.boolean().optional(),
|
|
315
|
+
media: MediaSchema.optional(),
|
|
316
|
+
feedback: FeedbackSchema.optional(),
|
|
317
|
+
passThreshold: _v4.z.number().min(0).max(1).optional(),
|
|
318
|
+
locale: _v4.z.string().optional(),
|
|
319
|
+
learningObjectives: _v4.z.array(_v4.z.string()).optional(),
|
|
320
|
+
difficultyLevel: _v4.z.literal([1, 2, 3, 4, 5]).optional()
|
|
321
|
+
}).refine(
|
|
322
|
+
(data) => {
|
|
323
|
+
const ids = (_nullishCoalesce(data.banks, () => ( []))).map((bank) => bank.id);
|
|
324
|
+
return new Set(ids).size === ids.length;
|
|
325
|
+
},
|
|
326
|
+
{ error: "Word bank ids must be unique.", path: ["banks"] }
|
|
327
|
+
).refine(
|
|
328
|
+
(data) => {
|
|
329
|
+
const ids = data.gaps.map((gap) => gap.id);
|
|
330
|
+
return new Set(ids).size === ids.length;
|
|
331
|
+
},
|
|
332
|
+
{ error: "Gap ids must be unique.", path: ["gaps"] }
|
|
333
|
+
).refine(
|
|
334
|
+
(data) => {
|
|
335
|
+
const counts = placeholderCounts(data.passage);
|
|
336
|
+
const ids = data.gaps.map((gap) => gap.id);
|
|
337
|
+
return counts.size === new Set(ids).size && ids.every((id) => counts.get(id) === 1) && [...counts.values()].every((count) => count === 1);
|
|
338
|
+
},
|
|
339
|
+
{
|
|
340
|
+
error: "Every gap must appear exactly once in the passage as {{gap_id}}, and every {{gap_id}} must have a gap.",
|
|
341
|
+
path: ["passage"]
|
|
342
|
+
}
|
|
343
|
+
).refine(
|
|
344
|
+
(data) => data.gaps.every((gap) => gap.choices === void 0 !== (gap.bankId === void 0)),
|
|
345
|
+
{
|
|
346
|
+
error: "Each gap needs exactly one choice source: its own `choices`, or a `bankId`.",
|
|
347
|
+
path: ["gaps"]
|
|
348
|
+
}
|
|
349
|
+
).refine(
|
|
350
|
+
(data) => {
|
|
351
|
+
const bankIds = new Set((_nullishCoalesce(data.banks, () => ( []))).map((bank) => bank.id));
|
|
352
|
+
return data.gaps.every((gap) => gap.bankId === void 0 || bankIds.has(gap.bankId));
|
|
353
|
+
},
|
|
354
|
+
{ error: "A gap references a `bankId` that no word bank defines.", path: ["gaps"] }
|
|
355
|
+
).refine(
|
|
356
|
+
(data) => {
|
|
357
|
+
const banks = _nullishCoalesce(data.banks, () => ( []));
|
|
358
|
+
return data.gaps.every((gap) => {
|
|
359
|
+
const choices = resolveChoices(gap, banks);
|
|
360
|
+
if (choices === void 0) {
|
|
361
|
+
return true;
|
|
362
|
+
}
|
|
363
|
+
const ids = choices.map((choice) => choice.id);
|
|
364
|
+
return new Set(ids).size === ids.length && ids.includes(gap.correctChoiceId);
|
|
365
|
+
});
|
|
366
|
+
},
|
|
367
|
+
{
|
|
368
|
+
error: "Each gap needs unique choice ids and a `correctChoiceId` that is one of the choices it offers.",
|
|
369
|
+
path: ["gaps"]
|
|
370
|
+
}
|
|
371
|
+
);
|
|
372
|
+
|
|
253
373
|
// src/schemas/written-response.ts
|
|
254
374
|
|
|
255
375
|
var WrittenResponseRubricCriterionSchema = _v4.z.looseObject({
|
|
@@ -308,9 +428,16 @@ var redactedBase = {
|
|
|
308
428
|
learningObjectives: _v4.z.array(_v4.z.string()).optional(),
|
|
309
429
|
difficultyLevel: _v4.z.literal([1, 2, 3, 4, 5]).optional()
|
|
310
430
|
};
|
|
431
|
+
var RedactedMultipleChoiceOptionMediaSchema = _v4.z.strictObject({
|
|
432
|
+
type: _v4.z.enum(["image", "audio"]),
|
|
433
|
+
url: _v4.z.string().min(1),
|
|
434
|
+
alt: _v4.z.string().min(1).optional(),
|
|
435
|
+
captionsUrl: _v4.z.string().min(1).optional()
|
|
436
|
+
});
|
|
311
437
|
var RedactedMultipleChoiceOptionSchema = _v4.z.strictObject({
|
|
312
438
|
id: _v4.z.string().min(1),
|
|
313
|
-
text: _v4.z.string().min(1)
|
|
439
|
+
text: _v4.z.string().min(1),
|
|
440
|
+
media: RedactedMultipleChoiceOptionMediaSchema.optional()
|
|
314
441
|
});
|
|
315
442
|
var RedactedMultipleChoiceDataSchema = _v4.z.strictObject({
|
|
316
443
|
...redactedBase,
|
|
@@ -332,6 +459,29 @@ var RedactedFillInTheBlanksDataSchema = _v4.z.strictObject({
|
|
|
332
459
|
passageHtml: _v4.z.string().optional(),
|
|
333
460
|
blanks: _v4.z.array(RedactedBlankConfigSchema).min(1)
|
|
334
461
|
});
|
|
462
|
+
var RedactedGapSelectChoiceSchema = _v4.z.strictObject({
|
|
463
|
+
id: _v4.z.string().min(1),
|
|
464
|
+
text: _v4.z.string().min(1)
|
|
465
|
+
});
|
|
466
|
+
var RedactedGapSelectBankSchema = _v4.z.strictObject({
|
|
467
|
+
id: _v4.z.string().min(1),
|
|
468
|
+
choices: _v4.z.array(RedactedGapSelectChoiceSchema).min(2)
|
|
469
|
+
});
|
|
470
|
+
var RedactedGapSelectGapSchema = _v4.z.strictObject({
|
|
471
|
+
id: _v4.z.string().min(1),
|
|
472
|
+
choices: _v4.z.array(RedactedGapSelectChoiceSchema).min(2).optional(),
|
|
473
|
+
bankId: _v4.z.string().min(1).optional()
|
|
474
|
+
});
|
|
475
|
+
var RedactedGapSelectDataSchema = _v4.z.strictObject({
|
|
476
|
+
...redactedBase,
|
|
477
|
+
type: _v4.z.literal("gap-select"),
|
|
478
|
+
passage: _v4.z.string().min(1),
|
|
479
|
+
passageHtml: _v4.z.string().optional(),
|
|
480
|
+
gaps: _v4.z.array(RedactedGapSelectGapSchema).min(1),
|
|
481
|
+
banks: _v4.z.array(RedactedGapSelectBankSchema).optional(),
|
|
482
|
+
presentation: _v4.z.literal("dropdown").optional(),
|
|
483
|
+
shuffleChoices: _v4.z.boolean().optional()
|
|
484
|
+
});
|
|
335
485
|
var RedactedWrittenResponseDataSchema = _v4.z.strictObject({
|
|
336
486
|
...redactedBase,
|
|
337
487
|
type: _v4.z.literal("written-response"),
|
|
@@ -484,6 +634,7 @@ var DRAFT_ISSUE_SEVERITY = {
|
|
|
484
634
|
mc_option_id_duplicate: "invalid",
|
|
485
635
|
mc_option_text_required: "incomplete",
|
|
486
636
|
mc_option_correctness_required: "incomplete",
|
|
637
|
+
mc_option_media_kind: "invalid",
|
|
487
638
|
mc_correct_option_required: "incomplete",
|
|
488
639
|
mc_single_mode_one_correct: "invalid",
|
|
489
640
|
// fill-in-the-blanks
|
|
@@ -500,6 +651,27 @@ var DRAFT_ISSUE_SEVERITY = {
|
|
|
500
651
|
fib_placeholder_missing: "incomplete",
|
|
501
652
|
fib_placeholder_duplicate: "invalid",
|
|
502
653
|
fib_blanks_mismatch: "invalid",
|
|
654
|
+
// gap-select
|
|
655
|
+
gs_passage_required: "incomplete",
|
|
656
|
+
gs_gaps_required: "incomplete",
|
|
657
|
+
gs_gap_id_required: "invalid",
|
|
658
|
+
gs_gap_id_duplicate: "invalid",
|
|
659
|
+
gs_gap_missing: "incomplete",
|
|
660
|
+
gs_placeholder_missing: "incomplete",
|
|
661
|
+
gs_placeholder_duplicate: "invalid",
|
|
662
|
+
gs_gaps_mismatch: "invalid",
|
|
663
|
+
gs_presentation_invalid: "invalid",
|
|
664
|
+
gs_bank_id_required: "invalid",
|
|
665
|
+
gs_bank_id_duplicate: "invalid",
|
|
666
|
+
gs_bank_unknown: "invalid",
|
|
667
|
+
gs_choice_source_required: "incomplete",
|
|
668
|
+
gs_choice_source_conflict: "invalid",
|
|
669
|
+
gs_choices_too_few: "incomplete",
|
|
670
|
+
gs_choice_id_required: "invalid",
|
|
671
|
+
gs_choice_id_duplicate: "invalid",
|
|
672
|
+
gs_choice_text_required: "incomplete",
|
|
673
|
+
gs_correct_choice_required: "incomplete",
|
|
674
|
+
gs_correct_choice_unknown: "invalid",
|
|
503
675
|
// written-response
|
|
504
676
|
wr_prompt_required: "incomplete",
|
|
505
677
|
wr_min_words_required: "incomplete",
|
|
@@ -633,51 +805,42 @@ function checkSharedOptional(draft) {
|
|
|
633
805
|
}
|
|
634
806
|
var URL_POLICY = 'Use an https:, http:, data: or blob: address, or a path on the same site that starts with a single "/".';
|
|
635
807
|
var EMBED_URL = "An embed needs the provider's full http(s) embed address, such as https://www.youtube.com/embed/VIDEO_ID.";
|
|
636
|
-
function checkMedia(media) {
|
|
808
|
+
function checkMedia(media, prefix = ["media"], schema = MediaSchema) {
|
|
637
809
|
const issues = [];
|
|
638
|
-
const
|
|
810
|
+
const at = (...rest) => [...prefix, ...rest];
|
|
811
|
+
const parsed = schema.safeParse(media, { reportInput: true });
|
|
639
812
|
const schemaIssues = parsed.success ? [] : parsed.error.issues.filter((found) => !refusesEmpty(media, found));
|
|
640
813
|
const refusal = (field) => schemaIssues.find((found) => found.path[0] === field);
|
|
641
814
|
const typeUnset = isUnwritten(media.type);
|
|
642
815
|
if (typeUnset) {
|
|
643
|
-
issues.push(
|
|
644
|
-
issue("media_type_required", ["media", "type"], "Choose what kind of media this is.")
|
|
645
|
-
);
|
|
816
|
+
issues.push(issue("media_type_required", at("type"), "Choose what kind of media this is."));
|
|
646
817
|
}
|
|
647
818
|
if (isUnwritten(media.url)) {
|
|
648
|
-
issues.push(
|
|
649
|
-
issue("media_url_required", ["media", "url"], "Add the address of the media file.")
|
|
650
|
-
);
|
|
819
|
+
issues.push(issue("media_url_required", at("url"), "Add the address of the media file."));
|
|
651
820
|
} else {
|
|
652
821
|
const refused = refusal("url");
|
|
653
822
|
if (refused !== void 0) {
|
|
654
823
|
issues.push(
|
|
655
|
-
issue(
|
|
656
|
-
"media_url_invalid",
|
|
657
|
-
["media", "url"],
|
|
658
|
-
refused.code === "custom" ? EMBED_URL : URL_POLICY
|
|
659
|
-
)
|
|
824
|
+
issue("media_url_invalid", at("url"), refused.code === "custom" ? EMBED_URL : URL_POLICY)
|
|
660
825
|
);
|
|
661
826
|
}
|
|
662
827
|
}
|
|
663
828
|
const needsAlt = media.type === "image" || media.type === "embed";
|
|
664
829
|
const alt = media.alt;
|
|
665
830
|
if (needsAlt && isUnwritten(alt) || typeof alt === "string" && alt.trim() === "") {
|
|
666
|
-
issues.push(
|
|
667
|
-
issue("media_alt_required", ["media", "alt"], "Add a text description of the media.")
|
|
668
|
-
);
|
|
831
|
+
issues.push(issue("media_alt_required", at("alt"), "Add a text description of the media."));
|
|
669
832
|
}
|
|
670
833
|
const captions = media.captionsUrl;
|
|
671
834
|
if (typeof captions === "string" && captions.trim() === "") {
|
|
672
835
|
issues.push(
|
|
673
836
|
issue(
|
|
674
837
|
"media_url_required",
|
|
675
|
-
|
|
838
|
+
at("captionsUrl"),
|
|
676
839
|
"Add the address of the captions file, or remove the captions."
|
|
677
840
|
)
|
|
678
841
|
);
|
|
679
842
|
} else if (refusal("captionsUrl") !== void 0) {
|
|
680
|
-
issues.push(issue("media_url_invalid",
|
|
843
|
+
issues.push(issue("media_url_invalid", at("captionsUrl"), URL_POLICY));
|
|
681
844
|
}
|
|
682
845
|
for (const schemaIssue of schemaIssues) {
|
|
683
846
|
const field = schemaIssue.path[0];
|
|
@@ -687,7 +850,7 @@ function checkMedia(media) {
|
|
|
687
850
|
issues.push(
|
|
688
851
|
issue(
|
|
689
852
|
field === "playback" ? "media_playback_invalid" : "media_invalid",
|
|
690
|
-
|
|
853
|
+
at(...schemaIssue.path.map(String)),
|
|
691
854
|
schemaIssue.message
|
|
692
855
|
)
|
|
693
856
|
);
|
|
@@ -888,6 +1051,301 @@ function pairsOneToOne(placeholders, blanks) {
|
|
|
888
1051
|
return blankIds.every((id) => typeof id === "string" && placeholders.get(id) === 1);
|
|
889
1052
|
}
|
|
890
1053
|
|
|
1054
|
+
// src/authoring/gap-select.ts
|
|
1055
|
+
var MIN_CHOICES = 2;
|
|
1056
|
+
var gapSelectAuthoring = {
|
|
1057
|
+
createDraft: ({ newId }) => ({
|
|
1058
|
+
schemaVersion: "1.0",
|
|
1059
|
+
type: "gap-select",
|
|
1060
|
+
id: newId(),
|
|
1061
|
+
title: "",
|
|
1062
|
+
passage: "",
|
|
1063
|
+
gaps: [],
|
|
1064
|
+
scoringStrategy: "all-or-nothing"
|
|
1065
|
+
}),
|
|
1066
|
+
checkDraft: checkGapSelectDraft
|
|
1067
|
+
};
|
|
1068
|
+
function checkGapSelectDraft(draft) {
|
|
1069
|
+
const issues = checkIdentity(draft, "gap-select");
|
|
1070
|
+
const passage = draft.passage;
|
|
1071
|
+
if (isUnwritten(passage)) {
|
|
1072
|
+
issues.push(issue("gs_passage_required", ["passage"], "Write the passage."));
|
|
1073
|
+
}
|
|
1074
|
+
issues.push(...checkScoringStrategy(draft));
|
|
1075
|
+
if (!isUnset(draft.presentation) && draft.presentation !== "dropdown") {
|
|
1076
|
+
issues.push(
|
|
1077
|
+
issue(
|
|
1078
|
+
"gs_presentation_invalid",
|
|
1079
|
+
["presentation"],
|
|
1080
|
+
'The only presentation is "dropdown". Leave it out unless you mean to set it.'
|
|
1081
|
+
)
|
|
1082
|
+
);
|
|
1083
|
+
}
|
|
1084
|
+
const banks = isUnset(draft.banks) ? [] : draft.banks;
|
|
1085
|
+
const bankIds = /* @__PURE__ */ new Set();
|
|
1086
|
+
if (Array.isArray(banks)) {
|
|
1087
|
+
issues.push(...checkBanks(banks, bankIds));
|
|
1088
|
+
}
|
|
1089
|
+
const gaps = isUnset(draft.gaps) ? [] : draft.gaps;
|
|
1090
|
+
const gapIds = [];
|
|
1091
|
+
if (Array.isArray(gaps)) {
|
|
1092
|
+
if (gaps.length === 0) {
|
|
1093
|
+
issues.push(
|
|
1094
|
+
issue(
|
|
1095
|
+
"gs_gaps_required",
|
|
1096
|
+
["gaps"],
|
|
1097
|
+
"Add at least one gap, and mark where it goes in the passage with {{id}}."
|
|
1098
|
+
)
|
|
1099
|
+
);
|
|
1100
|
+
}
|
|
1101
|
+
issues.push(...checkGaps(gaps, banks, bankIds, gapIds));
|
|
1102
|
+
if (typeof passage === "string" && passage.trim() !== "") {
|
|
1103
|
+
issues.push(...checkPairing2(passage, gaps, gapIds));
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
issues.push(...checkSharedOptional(draft));
|
|
1107
|
+
return issues;
|
|
1108
|
+
}
|
|
1109
|
+
function checkBanks(banks, bankIds) {
|
|
1110
|
+
const issues = [];
|
|
1111
|
+
const repeated = /* @__PURE__ */ new Set();
|
|
1112
|
+
for (const [index, bank] of banks.entries()) {
|
|
1113
|
+
if (!isRecord(bank)) {
|
|
1114
|
+
continue;
|
|
1115
|
+
}
|
|
1116
|
+
if (isMissingId(bank.id)) {
|
|
1117
|
+
issues.push(
|
|
1118
|
+
issue("gs_bank_id_required", ["banks", index, "id"], `Word bank ${index + 1} has no id.`)
|
|
1119
|
+
);
|
|
1120
|
+
} else if (typeof bank.id === "string") {
|
|
1121
|
+
if (bankIds.has(bank.id)) {
|
|
1122
|
+
repeated.add(bank.id);
|
|
1123
|
+
}
|
|
1124
|
+
bankIds.add(bank.id);
|
|
1125
|
+
}
|
|
1126
|
+
const name = typeof bank.id === "string" && bank.id !== "" ? `"${bank.id}"` : index + 1;
|
|
1127
|
+
issues.push(...checkChoiceList(bank.choices, ["banks", index, "choices"], `word bank ${name}`));
|
|
1128
|
+
}
|
|
1129
|
+
for (const id of repeated) {
|
|
1130
|
+
issues.push(
|
|
1131
|
+
issue("gs_bank_id_duplicate", ["banks"], `More than one word bank has the id "${id}".`)
|
|
1132
|
+
);
|
|
1133
|
+
}
|
|
1134
|
+
return issues;
|
|
1135
|
+
}
|
|
1136
|
+
function checkGaps(gaps, banks, bankIds, gapIds) {
|
|
1137
|
+
const issues = [];
|
|
1138
|
+
const atGaps = [];
|
|
1139
|
+
const repeated = /* @__PURE__ */ new Set();
|
|
1140
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1141
|
+
for (const [index, gap] of gaps.entries()) {
|
|
1142
|
+
if (!isRecord(gap)) {
|
|
1143
|
+
continue;
|
|
1144
|
+
}
|
|
1145
|
+
const ordinal = index + 1;
|
|
1146
|
+
const name = typeof gap.id === "string" && gap.id !== "" ? `"${gap.id}"` : ordinal;
|
|
1147
|
+
if (isMissingId(gap.id)) {
|
|
1148
|
+
issues.push(issue("gs_gap_id_required", ["gaps", index, "id"], `Gap ${ordinal} has no id.`));
|
|
1149
|
+
} else if (typeof gap.id === "string") {
|
|
1150
|
+
if (seen.has(gap.id)) {
|
|
1151
|
+
repeated.add(gap.id);
|
|
1152
|
+
}
|
|
1153
|
+
seen.add(gap.id);
|
|
1154
|
+
gapIds.push(gap.id);
|
|
1155
|
+
}
|
|
1156
|
+
const hasOwn = !isUnset(gap.choices);
|
|
1157
|
+
const hasBank = !isUnwritten(gap.bankId);
|
|
1158
|
+
const bankBlank = typeof gap.bankId === "string" && gap.bankId.trim() === "";
|
|
1159
|
+
if (bankBlank) {
|
|
1160
|
+
issues.push(
|
|
1161
|
+
issue(
|
|
1162
|
+
"gs_choice_source_required",
|
|
1163
|
+
["gaps", index, "bankId"],
|
|
1164
|
+
`Choose a word bank for gap ${name}, or remove the empty field.`
|
|
1165
|
+
)
|
|
1166
|
+
);
|
|
1167
|
+
} else if (hasOwn && hasBank) {
|
|
1168
|
+
atGaps.push(
|
|
1169
|
+
issue(
|
|
1170
|
+
"gs_choice_source_conflict",
|
|
1171
|
+
["gaps"],
|
|
1172
|
+
`Gap ${name} has both its own choices and a word bank. Use one or the other.`
|
|
1173
|
+
)
|
|
1174
|
+
);
|
|
1175
|
+
} else if (!hasOwn && !hasBank) {
|
|
1176
|
+
atGaps.push(
|
|
1177
|
+
issue(
|
|
1178
|
+
"gs_choice_source_required",
|
|
1179
|
+
["gaps"],
|
|
1180
|
+
`Give gap ${name} a list of choices, or point it at a word bank.`
|
|
1181
|
+
)
|
|
1182
|
+
);
|
|
1183
|
+
}
|
|
1184
|
+
if (hasBank && typeof gap.bankId === "string" && !bankIds.has(gap.bankId)) {
|
|
1185
|
+
atGaps.push(
|
|
1186
|
+
issue(
|
|
1187
|
+
"gs_bank_unknown",
|
|
1188
|
+
["gaps"],
|
|
1189
|
+
`Gap ${name} uses the word bank "${gap.bankId}", which does not exist.`
|
|
1190
|
+
)
|
|
1191
|
+
);
|
|
1192
|
+
}
|
|
1193
|
+
if (hasOwn) {
|
|
1194
|
+
issues.push(...checkChoiceList(gap.choices, ["gaps", index, "choices"], `gap ${name}`));
|
|
1195
|
+
}
|
|
1196
|
+
issues.push(...checkAnswerKey(gap, index, name, banks, bankIds));
|
|
1197
|
+
}
|
|
1198
|
+
for (const id of repeated) {
|
|
1199
|
+
atGaps.push(issue("gs_gap_id_duplicate", ["gaps"], `More than one gap has the id "${id}".`));
|
|
1200
|
+
}
|
|
1201
|
+
return [...issues, ...atGaps];
|
|
1202
|
+
}
|
|
1203
|
+
function checkAnswerKey(gap, index, name, banks, bankIds) {
|
|
1204
|
+
const key = gap.correctChoiceId;
|
|
1205
|
+
if (isUnwritten(key)) {
|
|
1206
|
+
return [
|
|
1207
|
+
issue(
|
|
1208
|
+
"gs_correct_choice_required",
|
|
1209
|
+
["gaps", index, "correctChoiceId"],
|
|
1210
|
+
`Mark the correct choice for gap ${name}.`
|
|
1211
|
+
)
|
|
1212
|
+
];
|
|
1213
|
+
}
|
|
1214
|
+
if (typeof key !== "string") {
|
|
1215
|
+
return [];
|
|
1216
|
+
}
|
|
1217
|
+
const choices = resolveChoices2(gap, banks, bankIds);
|
|
1218
|
+
if (choices === void 0) {
|
|
1219
|
+
return [];
|
|
1220
|
+
}
|
|
1221
|
+
const offered = choices.some((choice) => isRecord(choice) && choice.id === key);
|
|
1222
|
+
return offered ? [] : [
|
|
1223
|
+
issue(
|
|
1224
|
+
"gs_correct_choice_unknown",
|
|
1225
|
+
["gaps"],
|
|
1226
|
+
`Gap ${name} is marked correct on "${key}", which is not one of its choices.`
|
|
1227
|
+
)
|
|
1228
|
+
];
|
|
1229
|
+
}
|
|
1230
|
+
function resolveChoices2(gap, banks, bankIds) {
|
|
1231
|
+
const hasOwn = !isUnset(gap.choices);
|
|
1232
|
+
const hasBank = !isUnwritten(gap.bankId);
|
|
1233
|
+
if (hasOwn === hasBank) {
|
|
1234
|
+
return void 0;
|
|
1235
|
+
}
|
|
1236
|
+
if (hasOwn) {
|
|
1237
|
+
return Array.isArray(gap.choices) ? gap.choices : void 0;
|
|
1238
|
+
}
|
|
1239
|
+
if (typeof gap.bankId !== "string" || !bankIds.has(gap.bankId) || !Array.isArray(banks)) {
|
|
1240
|
+
return void 0;
|
|
1241
|
+
}
|
|
1242
|
+
const bank = banks.find((entry) => isRecord(entry) && entry.id === gap.bankId);
|
|
1243
|
+
return isRecord(bank) && Array.isArray(bank.choices) ? bank.choices : void 0;
|
|
1244
|
+
}
|
|
1245
|
+
function checkChoiceList(choices, path, owner) {
|
|
1246
|
+
const issues = [];
|
|
1247
|
+
const list = isUnset(choices) ? [] : choices;
|
|
1248
|
+
if (!Array.isArray(list)) {
|
|
1249
|
+
return issues;
|
|
1250
|
+
}
|
|
1251
|
+
if (list.length < MIN_CHOICES) {
|
|
1252
|
+
issues.push(
|
|
1253
|
+
issue(
|
|
1254
|
+
"gs_choices_too_few",
|
|
1255
|
+
path,
|
|
1256
|
+
`Give ${owner} at least ${MIN_CHOICES} choices to pick from.`
|
|
1257
|
+
)
|
|
1258
|
+
);
|
|
1259
|
+
}
|
|
1260
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1261
|
+
const repeated = /* @__PURE__ */ new Set();
|
|
1262
|
+
for (const [index, choice] of list.entries()) {
|
|
1263
|
+
if (!isRecord(choice)) {
|
|
1264
|
+
continue;
|
|
1265
|
+
}
|
|
1266
|
+
if (isMissingId(choice.id)) {
|
|
1267
|
+
issues.push(
|
|
1268
|
+
issue("gs_choice_id_required", [...path, index, "id"], `Choice ${index + 1} has no id.`)
|
|
1269
|
+
);
|
|
1270
|
+
} else if (typeof choice.id === "string") {
|
|
1271
|
+
if (seen.has(choice.id)) {
|
|
1272
|
+
repeated.add(choice.id);
|
|
1273
|
+
}
|
|
1274
|
+
seen.add(choice.id);
|
|
1275
|
+
}
|
|
1276
|
+
if (isUnwritten(choice.text)) {
|
|
1277
|
+
issues.push(
|
|
1278
|
+
issue(
|
|
1279
|
+
"gs_choice_text_required",
|
|
1280
|
+
[...path, index, "text"],
|
|
1281
|
+
`Write the text of choice ${index + 1}.`
|
|
1282
|
+
)
|
|
1283
|
+
);
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
for (const id of repeated) {
|
|
1287
|
+
issues.push(issue("gs_choice_id_duplicate", path, `More than one choice has the id "${id}".`));
|
|
1288
|
+
}
|
|
1289
|
+
return issues;
|
|
1290
|
+
}
|
|
1291
|
+
function checkPairing2(passage, gaps, gapIds) {
|
|
1292
|
+
const issues = [];
|
|
1293
|
+
const placeholders = /* @__PURE__ */ new Map();
|
|
1294
|
+
for (const match of passage.matchAll(PLACEHOLDER_RE)) {
|
|
1295
|
+
const id = match[1];
|
|
1296
|
+
placeholders.set(id, (_nullishCoalesce(placeholders.get(id), () => ( 0))) + 1);
|
|
1297
|
+
}
|
|
1298
|
+
const ids = new Set(gapIds);
|
|
1299
|
+
for (const [id, count] of placeholders) {
|
|
1300
|
+
if (count > 1) {
|
|
1301
|
+
issues.push(
|
|
1302
|
+
issue(
|
|
1303
|
+
"gs_placeholder_duplicate",
|
|
1304
|
+
["passage"],
|
|
1305
|
+
`{{${id}}} appears more than once in the passage. Each gap goes in one place.`
|
|
1306
|
+
)
|
|
1307
|
+
);
|
|
1308
|
+
}
|
|
1309
|
+
if (!ids.has(id)) {
|
|
1310
|
+
issues.push(
|
|
1311
|
+
issue(
|
|
1312
|
+
"gs_gap_missing",
|
|
1313
|
+
["passage"],
|
|
1314
|
+
`The passage has {{${id}}}, but there is no gap with that id.`
|
|
1315
|
+
)
|
|
1316
|
+
);
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
for (const id of ids) {
|
|
1320
|
+
if (!placeholders.has(id)) {
|
|
1321
|
+
issues.push(
|
|
1322
|
+
issue(
|
|
1323
|
+
"gs_placeholder_missing",
|
|
1324
|
+
["passage"],
|
|
1325
|
+
`Gap "${id}" is not in the passage. Put {{${id}}} where it goes.`
|
|
1326
|
+
)
|
|
1327
|
+
);
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
if (issues.length === 0 && !pairsOneToOne2(placeholders, gaps)) {
|
|
1331
|
+
issues.push(
|
|
1332
|
+
issue(
|
|
1333
|
+
"gs_gaps_mismatch",
|
|
1334
|
+
["passage"],
|
|
1335
|
+
"The gaps and the {{id}} placeholders in the passage do not pair up one to one."
|
|
1336
|
+
)
|
|
1337
|
+
);
|
|
1338
|
+
}
|
|
1339
|
+
return issues;
|
|
1340
|
+
}
|
|
1341
|
+
function pairsOneToOne2(placeholders, gaps) {
|
|
1342
|
+
const ids = gaps.filter(isRecord).map((gap) => gap.id);
|
|
1343
|
+
if (new Set(ids).size !== ids.length || placeholders.size !== ids.length) {
|
|
1344
|
+
return false;
|
|
1345
|
+
}
|
|
1346
|
+
return ids.every((id) => typeof id === "string" && placeholders.get(id) === 1);
|
|
1347
|
+
}
|
|
1348
|
+
|
|
891
1349
|
// src/authoring/multiple-choice.ts
|
|
892
1350
|
var MIN_OPTIONS = 2;
|
|
893
1351
|
var MAX_OPTIONS = 26;
|
|
@@ -967,6 +1425,9 @@ function checkMultipleChoiceDraft(draft) {
|
|
|
967
1425
|
if (option.isCorrect === true) {
|
|
968
1426
|
correct += 1;
|
|
969
1427
|
}
|
|
1428
|
+
if (isRecord(option.media)) {
|
|
1429
|
+
issues.push(...checkOptionMedia(option.media, index, ordinal));
|
|
1430
|
+
}
|
|
970
1431
|
}
|
|
971
1432
|
for (const id of repeated) {
|
|
972
1433
|
issues.push(
|
|
@@ -994,6 +1455,20 @@ function checkMultipleChoiceDraft(draft) {
|
|
|
994
1455
|
issues.push(...checkSharedOptional(draft));
|
|
995
1456
|
return issues;
|
|
996
1457
|
}
|
|
1458
|
+
function checkOptionMedia(media, index, ordinal) {
|
|
1459
|
+
const path = ["options", index, "media"];
|
|
1460
|
+
const kind = media.type;
|
|
1461
|
+
if (kind === "video" || kind === "embed") {
|
|
1462
|
+
return [
|
|
1463
|
+
issue(
|
|
1464
|
+
"mc_option_media_kind",
|
|
1465
|
+
[...path, "type"],
|
|
1466
|
+
`Option ${ordinal} carries ${kind === "embed" ? "an embedded player" : "a video"}, which cannot be an option: its controls swallow the click that selects the answer. Use a picture or a recording.`
|
|
1467
|
+
)
|
|
1468
|
+
];
|
|
1469
|
+
}
|
|
1470
|
+
return checkMedia(media, path, MultipleChoiceOptionMediaSchema);
|
|
1471
|
+
}
|
|
997
1472
|
|
|
998
1473
|
// src/authoring/written-response.ts
|
|
999
1474
|
var writtenResponseAuthoring = {
|
|
@@ -1181,6 +1656,38 @@ function scoreFillInTheBlanks(data, response) {
|
|
|
1181
1656
|
return { score: scoreValue, maxScore: 1, feedback: null, details };
|
|
1182
1657
|
}
|
|
1183
1658
|
|
|
1659
|
+
// src/scoring/activity-scorers/gap-select.ts
|
|
1660
|
+
function choicesFor(data, gap) {
|
|
1661
|
+
if (gap.choices !== void 0) {
|
|
1662
|
+
return gap.choices;
|
|
1663
|
+
}
|
|
1664
|
+
return _nullishCoalesce(_optionalChain([data, 'access', _13 => _13.banks, 'optionalAccess', _14 => _14.find, 'call', _15 => _15((bank) => bank.id === gap.bankId), 'optionalAccess', _16 => _16.choices]), () => ( []));
|
|
1665
|
+
}
|
|
1666
|
+
function scoreGapSelect(data, response) {
|
|
1667
|
+
const details = [];
|
|
1668
|
+
const perGapCorrect = [];
|
|
1669
|
+
for (const gap of data.gaps) {
|
|
1670
|
+
const raw = response.selections[gap.id];
|
|
1671
|
+
const selected = typeof raw === "string" ? raw : "";
|
|
1672
|
+
const choices = choicesFor(data, gap);
|
|
1673
|
+
const offered = choices.some((choice) => choice.id === selected);
|
|
1674
|
+
const answered = selected !== "" && offered;
|
|
1675
|
+
const correct = answered && selected === gap.correctChoiceId;
|
|
1676
|
+
perGapCorrect.push(correct);
|
|
1677
|
+
details.push({
|
|
1678
|
+
itemId: gap.id,
|
|
1679
|
+
correct,
|
|
1680
|
+
outcome: correct ? "correct" : answered ? "incorrect" : "incorrect-omission",
|
|
1681
|
+
learnerResponse: [selected],
|
|
1682
|
+
correctResponse: [gap.correctChoiceId],
|
|
1683
|
+
weight: 1
|
|
1684
|
+
});
|
|
1685
|
+
}
|
|
1686
|
+
const correctGaps = perGapCorrect.filter(Boolean).length;
|
|
1687
|
+
const scoreValue = data.scoringStrategy === "all-or-nothing" ? allOrNothingStrategy(perGapCorrect) : partialBlankStrategy(correctGaps, data.gaps.length);
|
|
1688
|
+
return { score: scoreValue, maxScore: 1, feedback: null, details };
|
|
1689
|
+
}
|
|
1690
|
+
|
|
1184
1691
|
// src/scoring/activity-scorers/multiple-choice.ts
|
|
1185
1692
|
function scoreMultipleChoice(data, response) {
|
|
1186
1693
|
const optionById = new Map(data.options.map((option) => [option.id, option]));
|
|
@@ -1190,7 +1697,7 @@ function scoreMultipleChoice(data, response) {
|
|
|
1190
1697
|
let scoreValue;
|
|
1191
1698
|
if (data.scoringStrategy === "all-or-nothing") {
|
|
1192
1699
|
if (data.mode === "single") {
|
|
1193
|
-
scoreValue = response.selectedOptionIds.length === 1 && _optionalChain([optionById, 'access',
|
|
1700
|
+
scoreValue = response.selectedOptionIds.length === 1 && _optionalChain([optionById, 'access', _17 => _17.get, 'call', _18 => _18(response.selectedOptionIds[0]), 'optionalAccess', _19 => _19.isCorrect]) === true ? 1 : 0;
|
|
1194
1701
|
} else {
|
|
1195
1702
|
const correctIds = data.options.filter((o) => o.isCorrect).map((o) => o.id);
|
|
1196
1703
|
const allCorrectSelected = correctIds.every((id) => selected.has(id));
|
|
@@ -1201,7 +1708,7 @@ function scoreMultipleChoice(data, response) {
|
|
|
1201
1708
|
let incorrectSelected = 0;
|
|
1202
1709
|
for (const id of selected) {
|
|
1203
1710
|
const option = optionById.get(id);
|
|
1204
|
-
if (_optionalChain([option, 'optionalAccess',
|
|
1711
|
+
if (_optionalChain([option, 'optionalAccess', _20 => _20.isCorrect])) {
|
|
1205
1712
|
correctSelected += 1;
|
|
1206
1713
|
} else {
|
|
1207
1714
|
incorrectSelected += 1;
|
|
@@ -1280,6 +1787,38 @@ var MULTIPLE_CHOICE_FIELD_POLICY = {
|
|
|
1280
1787
|
id: "public",
|
|
1281
1788
|
text: "public",
|
|
1282
1789
|
isCorrect: "answer-key",
|
|
1790
|
+
feedback: "answer-key",
|
|
1791
|
+
// Public, and classified key by key rather than as one leaf — the lesson
|
|
1792
|
+
// `media`, `rubric` and `feedback` each taught: a scalar classification
|
|
1793
|
+
// assigns the author's object by reference without recursing, so an
|
|
1794
|
+
// unclassified key parked inside it survives redact() AND assertRedacted().
|
|
1795
|
+
// An option's picture or recording IS the thing the learner picks, so
|
|
1796
|
+
// stripping it would ship a row of blank options.
|
|
1797
|
+
media: {
|
|
1798
|
+
type: "public",
|
|
1799
|
+
url: "public",
|
|
1800
|
+
alt: "public",
|
|
1801
|
+
captionsUrl: "public"
|
|
1802
|
+
}
|
|
1803
|
+
}
|
|
1804
|
+
};
|
|
1805
|
+
var GAP_SELECT_FIELD_POLICY = {
|
|
1806
|
+
...SHARED_PUBLIC_FIELDS,
|
|
1807
|
+
passage: "public",
|
|
1808
|
+
passageHtml: "public",
|
|
1809
|
+
presentation: "public",
|
|
1810
|
+
shuffleChoices: "public",
|
|
1811
|
+
scoringStrategy: "answer-key",
|
|
1812
|
+
feedback: FEEDBACK_FIELD_POLICY,
|
|
1813
|
+
banks: {
|
|
1814
|
+
id: "public",
|
|
1815
|
+
choices: { id: "public", text: "public" }
|
|
1816
|
+
},
|
|
1817
|
+
gaps: {
|
|
1818
|
+
id: "public",
|
|
1819
|
+
bankId: "public",
|
|
1820
|
+
choices: { id: "public", text: "public" },
|
|
1821
|
+
correctChoiceId: "answer-key",
|
|
1283
1822
|
feedback: "answer-key"
|
|
1284
1823
|
}
|
|
1285
1824
|
};
|
|
@@ -1331,7 +1870,7 @@ var multipleChoiceType = defineActivityType({
|
|
|
1331
1870
|
// exact optionals. Structurally identical at runtime — cast is type-level only.
|
|
1332
1871
|
schema: MultipleChoiceDataSchema,
|
|
1333
1872
|
scoring: { kind: "sync", score: scoreMultipleChoice },
|
|
1334
|
-
isAnswered: (response) => (_nullishCoalesce(_optionalChain([response, 'optionalAccess',
|
|
1873
|
+
isAnswered: (response) => (_nullishCoalesce(_optionalChain([response, 'optionalAccess', _21 => _21.selectedOptionIds, 'access', _22 => _22.length]), () => ( 0))) > 0,
|
|
1335
1874
|
fieldPolicy: MULTIPLE_CHOICE_FIELD_POLICY,
|
|
1336
1875
|
redactedSchema: RedactedMultipleChoiceDataSchema,
|
|
1337
1876
|
interop: {
|
|
@@ -1348,7 +1887,7 @@ var fillInTheBlanksType = defineActivityType({
|
|
|
1348
1887
|
type: "fill-in-the-blanks",
|
|
1349
1888
|
schema: FillInTheBlanksDataSchema,
|
|
1350
1889
|
scoring: { kind: "sync", score: scoreFillInTheBlanks },
|
|
1351
|
-
isAnswered: (response) => Object.values(_nullishCoalesce(_optionalChain([response, 'optionalAccess',
|
|
1890
|
+
isAnswered: (response) => Object.values(_nullishCoalesce(_optionalChain([response, 'optionalAccess', _23 => _23.answers]), () => ( {}))).some((answer) => answer.trim().length > 0),
|
|
1352
1891
|
fieldPolicy: FILL_IN_THE_BLANKS_FIELD_POLICY,
|
|
1353
1892
|
redactedSchema: RedactedFillInTheBlanksDataSchema,
|
|
1354
1893
|
interop: {
|
|
@@ -1371,14 +1910,14 @@ var writtenResponseType = defineActivityType({
|
|
|
1371
1910
|
kind: "deferred",
|
|
1372
1911
|
reason: "requires_async_grading",
|
|
1373
1912
|
partial: (data, response) => {
|
|
1374
|
-
const wordCount = countWords(_nullishCoalesce(_optionalChain([response, 'optionalAccess',
|
|
1913
|
+
const wordCount = countWords(_nullishCoalesce(_optionalChain([response, 'optionalAccess', _24 => _24.text]), () => ( "")));
|
|
1375
1914
|
return {
|
|
1376
1915
|
withinWordBounds: response !== void 0 && wordCount >= data.minWords && wordCount <= data.maxWords,
|
|
1377
1916
|
wordCount
|
|
1378
1917
|
};
|
|
1379
1918
|
}
|
|
1380
1919
|
},
|
|
1381
|
-
isAnswered: (response) => (_nullishCoalesce(_optionalChain([response, 'optionalAccess',
|
|
1920
|
+
isAnswered: (response) => (_nullishCoalesce(_optionalChain([response, 'optionalAccess', _25 => _25.text, 'access', _26 => _26.trim, 'call', _27 => _27(), 'access', _28 => _28.length]), () => ( 0))) > 0,
|
|
1382
1921
|
fieldPolicy: WRITTEN_RESPONSE_FIELD_POLICY,
|
|
1383
1922
|
redactedSchema: RedactedWrittenResponseDataSchema,
|
|
1384
1923
|
interop: {
|
|
@@ -1389,9 +1928,43 @@ var writtenResponseType = defineActivityType({
|
|
|
1389
1928
|
interactions: ["text-changed", "submitted"],
|
|
1390
1929
|
authoring: writtenResponseAuthoring
|
|
1391
1930
|
});
|
|
1931
|
+
var gapSelectType = defineActivityType({
|
|
1932
|
+
type: "gap-select",
|
|
1933
|
+
schema: GapSelectDataSchema,
|
|
1934
|
+
scoring: { kind: "sync", score: scoreGapSelect },
|
|
1935
|
+
isAnswered: (response) => Object.values(_nullishCoalesce(_optionalChain([response, 'optionalAccess', _29 => _29.selections]), () => ( {}))).some((choiceId) => choiceId !== ""),
|
|
1936
|
+
fieldPolicy: GAP_SELECT_FIELD_POLICY,
|
|
1937
|
+
redactedSchema: RedactedGapSelectDataSchema,
|
|
1938
|
+
interop: {
|
|
1939
|
+
xapiActivityTypeIri: "http://adlnet.gov/expapi/activities/cmi.interaction",
|
|
1940
|
+
// `matching`, not `choice` or `fill-in`. The learner pairs a set of sources
|
|
1941
|
+
// (the gaps) with a set of targets (the choices), which is exactly what the
|
|
1942
|
+
// xAPI matching interaction describes, and it is the only built-in type
|
|
1943
|
+
// whose pattern can name WHICH gap took which answer. `fill-in` — what the
|
|
1944
|
+
// Fill-in-the-Blanks descriptor uses — would flatten the gaps into an
|
|
1945
|
+
// ordered list of strings and lose that.
|
|
1946
|
+
xapiInteractionType: "matching",
|
|
1947
|
+
correctResponsesPattern: (data) => [
|
|
1948
|
+
data.gaps.map((gap) => `${gap.id}[.]${gap.correctChoiceId}`).join("[,]")
|
|
1949
|
+
]
|
|
1950
|
+
},
|
|
1951
|
+
interactions: ["gap-selected", "submitted"],
|
|
1952
|
+
authoring: gapSelectAuthoring
|
|
1953
|
+
});
|
|
1392
1954
|
registerActivityType(multipleChoiceType);
|
|
1393
1955
|
registerActivityType(fillInTheBlanksType);
|
|
1394
1956
|
registerActivityType(writtenResponseType);
|
|
1957
|
+
registerActivityType(gapSelectType);
|
|
1958
|
+
|
|
1959
|
+
|
|
1960
|
+
|
|
1961
|
+
|
|
1962
|
+
|
|
1963
|
+
|
|
1964
|
+
|
|
1965
|
+
|
|
1966
|
+
|
|
1967
|
+
|
|
1395
1968
|
|
|
1396
1969
|
|
|
1397
1970
|
|
|
@@ -1432,5 +2005,5 @@ registerActivityType(writtenResponseType);
|
|
|
1432
2005
|
|
|
1433
2006
|
|
|
1434
2007
|
|
|
1435
|
-
exports.ActivitySchemaError = ActivitySchemaError; exports.UnknownActivityTypeError = UnknownActivityTypeError; exports.RedactedScoringError = RedactedScoringError; exports.DeferredScoringError = DeferredScoringError; exports.FeedbackSchema = FeedbackSchema; exports.MediaUrlSchema = MediaUrlSchema; exports.NativeControlHintSchema = NativeControlHintSchema; exports.MediaPlaybackSchema = MediaPlaybackSchema; exports.MediaSchema = MediaSchema; exports.RedactedMediaSchema = RedactedMediaSchema; exports.TextMatchPolicySchema = TextMatchPolicySchema; exports.BlankConfigSchema = BlankConfigSchema; exports.FillInTheBlanksDataSchema = FillInTheBlanksDataSchema; exports.DRAFT_ISSUE_SEVERITY = DRAFT_ISSUE_SEVERITY; exports.issue = issue; exports.isRecord = isRecord; exports.refusesEmpty = refusesEmpty; exports.
|
|
1436
|
-
//# sourceMappingURL=chunk-
|
|
2008
|
+
exports.ActivitySchemaError = ActivitySchemaError; exports.UnknownActivityTypeError = UnknownActivityTypeError; exports.RedactedScoringError = RedactedScoringError; exports.DeferredScoringError = DeferredScoringError; exports.FeedbackSchema = FeedbackSchema; exports.MediaUrlSchema = MediaUrlSchema; exports.NativeControlHintSchema = NativeControlHintSchema; exports.MediaPlaybackSchema = MediaPlaybackSchema; exports.MediaSchema = MediaSchema; exports.RedactedMediaSchema = RedactedMediaSchema; exports.TextMatchPolicySchema = TextMatchPolicySchema; exports.BlankConfigSchema = BlankConfigSchema; exports.FillInTheBlanksDataSchema = FillInTheBlanksDataSchema; exports.DRAFT_ISSUE_SEVERITY = DRAFT_ISSUE_SEVERITY; exports.issue = issue; exports.isRecord = isRecord; exports.refusesEmpty = refusesEmpty; exports.MultipleChoiceOptionMediaSchema = MultipleChoiceOptionMediaSchema; exports.MultipleChoiceOptionSchema = MultipleChoiceOptionSchema; exports.MultipleChoiceDataSchema = MultipleChoiceDataSchema; exports.countWords = countWords; exports.GapSelectChoiceSchema = GapSelectChoiceSchema; exports.GapSelectBankSchema = GapSelectBankSchema; exports.GapSelectGapSchema = GapSelectGapSchema; exports.GapSelectDataSchema = GapSelectDataSchema; exports.WrittenResponseRubricCriterionSchema = WrittenResponseRubricCriterionSchema; exports.WrittenResponseRubricSchema = WrittenResponseRubricSchema; exports.WrittenResponseDataSchema = WrittenResponseDataSchema; exports.RedactedMultipleChoiceOptionMediaSchema = RedactedMultipleChoiceOptionMediaSchema; exports.RedactedMultipleChoiceOptionSchema = RedactedMultipleChoiceOptionSchema; exports.RedactedMultipleChoiceDataSchema = RedactedMultipleChoiceDataSchema; exports.RedactedBlankConfigSchema = RedactedBlankConfigSchema; exports.RedactedFillInTheBlanksDataSchema = RedactedFillInTheBlanksDataSchema; exports.RedactedGapSelectChoiceSchema = RedactedGapSelectChoiceSchema; exports.RedactedGapSelectBankSchema = RedactedGapSelectBankSchema; exports.RedactedGapSelectGapSchema = RedactedGapSelectGapSchema; exports.RedactedGapSelectDataSchema = RedactedGapSelectDataSchema; exports.RedactedWrittenResponseDataSchema = RedactedWrittenResponseDataSchema; exports.levenshteinDistance = levenshteinDistance; exports.matchText = matchText; exports.defineActivityType = defineActivityType; exports.registerActivityType = registerActivityType; exports.getActivityTypeDescriptor = getActivityTypeDescriptor; exports.registeredActivityTypes = registeredActivityTypes; exports.MEDIA_FIELD_POLICY = MEDIA_FIELD_POLICY; exports.multipleChoiceType = multipleChoiceType; exports.fillInTheBlanksType = fillInTheBlanksType; exports.writtenResponseType = writtenResponseType;
|
|
2009
|
+
//# sourceMappingURL=chunk-7ODQRNR4.cjs.map
|