@intellectif/lk-core 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +2 -1
  3. package/dist/{activity-MBOYLBjR.d.cts → activity-D705CXng.d.cts} +324 -2
  4. package/dist/{activity-MBOYLBjR.d.ts → activity-D705CXng.d.ts} +324 -2
  5. package/dist/ai-check.cjs +174 -12
  6. package/dist/ai-check.cjs.map +1 -1
  7. package/dist/ai-check.d.cts +15 -3
  8. package/dist/ai-check.d.ts +15 -3
  9. package/dist/ai-check.js +166 -4
  10. package/dist/ai-check.js.map +1 -1
  11. package/dist/{chunk-TJJFKKH2.cjs → chunk-46BXIGLG.cjs} +4 -4
  12. package/dist/{chunk-TJJFKKH2.cjs.map → chunk-46BXIGLG.cjs.map} +1 -1
  13. package/dist/{chunk-VSH7ECQL.cjs → chunk-4YANYTFN.cjs} +43 -43
  14. package/dist/{chunk-VSH7ECQL.cjs.map → chunk-4YANYTFN.cjs.map} +1 -1
  15. package/dist/chunk-7KOWU4RO.js +2207 -0
  16. package/dist/chunk-7KOWU4RO.js.map +1 -0
  17. package/dist/{chunk-DSDXL3JO.cjs → chunk-7THXD7AN.cjs} +657 -197
  18. package/dist/chunk-7THXD7AN.cjs.map +1 -0
  19. package/dist/chunk-CYCTIXUN.cjs +118 -0
  20. package/dist/chunk-CYCTIXUN.cjs.map +1 -0
  21. package/dist/chunk-CYPA5BW5.js +118 -0
  22. package/dist/chunk-CYPA5BW5.js.map +1 -0
  23. package/dist/{chunk-SJVRUPJT.js → chunk-JTNDIBZO.js} +2 -2
  24. package/dist/{chunk-XS6U2ARZ.js → chunk-MV2N2WNS.js} +6 -112
  25. package/dist/chunk-MV2N2WNS.js.map +1 -0
  26. package/dist/chunk-MXI26H62.cjs +2207 -0
  27. package/dist/chunk-MXI26H62.cjs.map +1 -0
  28. package/dist/{chunk-FBKTION6.js → chunk-QS7B42ZR.js} +640 -180
  29. package/dist/chunk-QS7B42ZR.js.map +1 -0
  30. package/dist/{chunk-IARLJ5MW.js → chunk-QX2ERL72.js} +2 -2
  31. package/dist/{chunk-2JT2WUQ2.cjs → chunk-ZWXQSKCX.cjs} +14 -120
  32. package/dist/chunk-ZWXQSKCX.cjs.map +1 -0
  33. package/dist/{index-DlcqkM_L.d.ts → index-BChYtrEe.d.ts} +1 -1
  34. package/dist/{index-DlCW0XaQ.d.cts → index-VXt_v3YI.d.cts} +1 -1
  35. package/dist/index.cjs +68 -593
  36. package/dist/index.cjs.map +1 -1
  37. package/dist/index.d.cts +245 -60
  38. package/dist/index.d.ts +245 -60
  39. package/dist/index.js +68 -593
  40. package/dist/index.js.map +1 -1
  41. package/dist/schemas.cjs +5 -3
  42. package/dist/schemas.cjs.map +1 -1
  43. package/dist/schemas.d.cts +2 -2
  44. package/dist/schemas.d.ts +2 -2
  45. package/dist/schemas.js +5 -3
  46. package/dist/scoring.cjs +3 -3
  47. package/dist/scoring.d.cts +2 -2
  48. package/dist/scoring.d.ts +2 -2
  49. package/dist/scoring.js +2 -2
  50. package/dist/xapi.cjs +3 -3
  51. package/dist/xapi.d.cts +1 -1
  52. package/dist/xapi.d.ts +1 -1
  53. package/dist/xapi.js +2 -2
  54. package/package.json +1 -1
  55. package/dist/chunk-2JT2WUQ2.cjs.map +0 -1
  56. package/dist/chunk-DSDXL3JO.cjs.map +0 -1
  57. package/dist/chunk-FBKTION6.js.map +0 -1
  58. package/dist/chunk-LDFQUZII.cjs +0 -950
  59. package/dist/chunk-LDFQUZII.cjs.map +0 -1
  60. package/dist/chunk-NQHETFDA.js +0 -950
  61. package/dist/chunk-NQHETFDA.js.map +0 -1
  62. package/dist/chunk-XS6U2ARZ.js.map +0 -1
  63. /package/dist/{chunk-SJVRUPJT.js.map → chunk-JTNDIBZO.js.map} +0 -0
  64. /package/dist/{chunk-IARLJ5MW.js.map → chunk-QX2ERL72.js.map} +0 -0
@@ -289,7 +289,7 @@ var FOLDED = new Map(
289
289
  // TIBETAN MARK DELIMITER TSHEG BSTAR
290
290
  [65374, cp(12316)]
291
291
  // FULLWIDTH TILDE, which a Japanese keyboard types for the WAVE DASH
292
- ].map(([point, folded]) => [cp(point), folded])
292
+ ].map(([point, folded2]) => [cp(point), folded2])
293
293
  );
294
294
  var FOLDED_RE = new RegExp(`[${[...FOLDED.keys()].join("")}]`, "gu");
295
295
  var DRAWN_FORMAT_CHARACTERS = `${cp(1536)}-${cp(1541)}${cp(1757, 1807, 2192, 2193, 2274, 69821, 69837)}`;
@@ -660,10 +660,10 @@ var CARRIED_MARKS_RE = new RegExp(`^[ ${cp(1600)}]?(\\p{M}+)$`, "u");
660
660
  var SPACING_CHARACTER_RE = new RegExp(`[\\s${NEXT_LINE}]`, "u");
661
661
  var PUNCTUATION_CHARACTER_RE = /^\p{P}$/u;
662
662
  function foldCompatibility(character, offset, text) {
663
- const folded = character.normalize("NFKC");
664
- const carried = CARRIED_MARKS_RE.exec(folded);
663
+ const folded2 = character.normalize("NFKC");
664
+ const carried = CARRIED_MARKS_RE.exec(folded2);
665
665
  if (carried === null) {
666
- return _nullishCoalesce(COMPATIBILITY_JAMO.get(folded), () => ( folded));
666
+ return _nullishCoalesce(COMPATIBILITY_JAMO.get(folded2), () => ( folded2));
667
667
  }
668
668
  return PUNCTUATION_CHARACTER_RE.test(character) || offset === 0 || SPACING_CHARACTER_RE.test(text.charAt(offset - 1)) ? character : carried[1];
669
669
  }
@@ -917,8 +917,8 @@ var NOTHING_TO_REVEAL = { forms: [], long: false, before: "none", after: "none"
917
917
  function stripForReveal(text) {
918
918
  return stripPunctuation(text.replace(DIGIT_SEPARATOR_RE, ""));
919
919
  }
920
- function readingOf(written, working) {
921
- const writtenStripped = stripForReveal(written);
920
+ function readingOf(written2, working) {
921
+ const writtenStripped = stripForReveal(written2);
922
922
  const workingStripped = stripForReveal(working);
923
923
  const source = writtenStripped === "" ? workingStripped : writtenStripped;
924
924
  if (source === "") {
@@ -928,7 +928,7 @@ function readingOf(written, working) {
928
928
  const { before, after } = phraseOf(source, long ? "none" : "visible");
929
929
  const forms = [
930
930
  writtenStripped,
931
- spacePunctuation(written),
931
+ spacePunctuation(written2),
932
932
  workingStripped,
933
933
  spacePunctuation(working)
934
934
  ].map((form) => long ? form.replace(SPACE_BESIDE_UNSPACED_RE, "") : form);
@@ -945,13 +945,13 @@ function dictationNormalizer(tolerance) {
945
945
  if (isLongerThan(text, DICTATION_MAX_TRANSCRIPT_LENGTH)) {
946
946
  result = { tooLong: true, normalized: null, reveal: NOTHING_TO_REVEAL };
947
947
  } else {
948
- const written = boundedPreStrip(text);
949
- const working = workingText(text, compiled, written);
948
+ const written2 = boundedPreStrip(text);
949
+ const working = workingText(text, compiled, written2);
950
950
  const normalized = stripPunctuation(working.text);
951
951
  result = {
952
952
  tooLong: working.cut || isLongerThan(normalized, DICTATION_MAX_TRANSCRIPT_LENGTH),
953
953
  normalized,
954
- reveal: readingOf(written.text, working.text)
954
+ reveal: readingOf(written2.text, working.text)
955
955
  };
956
956
  }
957
957
  measured.set(text, result);
@@ -1172,7 +1172,7 @@ function alignSequences(reference, attempt) {
1172
1172
  }
1173
1173
  function alignWords(reference, attempt) {
1174
1174
  const words = [];
1175
- let ordinal = 0;
1175
+ let ordinal2 = 0;
1176
1176
  for (const { step, referenceIndex, attemptIndex } of alignSequences(reference, attempt)) {
1177
1177
  if (step === STEP.extra) {
1178
1178
  words.push({
@@ -1183,11 +1183,11 @@ function alignWords(reference, attempt) {
1183
1183
  });
1184
1184
  continue;
1185
1185
  }
1186
- ordinal += 1;
1186
+ ordinal2 += 1;
1187
1187
  const referenceWord = reference[referenceIndex];
1188
1188
  if (step === STEP.missing) {
1189
1189
  words.push({
1190
- itemId: `w${ordinal}`,
1190
+ itemId: `w${ordinal2}`,
1191
1191
  reference: referenceWord,
1192
1192
  attempt: "",
1193
1193
  similarity: 0,
@@ -1197,7 +1197,7 @@ function alignWords(reference, attempt) {
1197
1197
  }
1198
1198
  const attemptWord = attempt[attemptIndex];
1199
1199
  words.push({
1200
- itemId: `w${ordinal}`,
1200
+ itemId: `w${ordinal2}`,
1201
1201
  reference: referenceWord,
1202
1202
  attempt: attemptWord,
1203
1203
  similarity: similarity(referenceWord, attemptWord),
@@ -1247,6 +1247,101 @@ var DeferredScoringError = class extends Error {
1247
1247
 
1248
1248
  };
1249
1249
 
1250
+ // src/count-words.ts
1251
+ function countWords(text) {
1252
+ if (typeof text !== "string") {
1253
+ return 0;
1254
+ }
1255
+ const trimmed = text.trim();
1256
+ if (trimmed === "") {
1257
+ return 0;
1258
+ }
1259
+ return trimmed.split(/\s+/).length;
1260
+ }
1261
+
1262
+ // src/scoring/text-match.ts
1263
+ var COMBINING_MARKS_RE = /\p{M}/gu;
1264
+ var PUNCTUATION_RE2 = /\p{P}/gu;
1265
+ function applyBaseline(value, policy) {
1266
+ let result = value;
1267
+ if (policy.trim !== false) {
1268
+ result = result.trim();
1269
+ }
1270
+ if (policy.caseSensitive !== true) {
1271
+ result = policy.locale ? result.toLocaleLowerCase(policy.locale) : result.toLowerCase();
1272
+ }
1273
+ return result;
1274
+ }
1275
+ function applyNormalization(value, policy) {
1276
+ let result = value;
1277
+ if (policy.normalize === "NFC" || policy.normalize === "NFKC") {
1278
+ result = result.normalize(policy.normalize);
1279
+ }
1280
+ if (policy.ignorePunctuation === true) {
1281
+ result = result.replace(PUNCTUATION_RE2, "");
1282
+ }
1283
+ if (policy.collapseInnerWhitespace === true) {
1284
+ result = result.replace(/(?<=\S)\s+(?=\S)/g, " ");
1285
+ if (policy.trim !== false) {
1286
+ result = result.trim();
1287
+ }
1288
+ }
1289
+ return result;
1290
+ }
1291
+ function applyDiacriticFold(value) {
1292
+ return value.normalize("NFD").replace(COMBINING_MARKS_RE, "").normalize("NFC");
1293
+ }
1294
+ function levenshteinDistance(a, b, max) {
1295
+ if (a === b) return 0;
1296
+ if (Math.abs(a.length - b.length) > max) return max + 1;
1297
+ if (a.length === 0) return b.length;
1298
+ if (b.length === 0) return a.length;
1299
+ let previous = Array.from({ length: b.length + 1 }, (_, i) => i);
1300
+ for (let i = 1; i <= a.length; i += 1) {
1301
+ const current = [i];
1302
+ let rowMin = i;
1303
+ for (let j = 1; j <= b.length; j += 1) {
1304
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
1305
+ const value = Math.min(
1306
+ previous[j] + 1,
1307
+ current[j - 1] + 1,
1308
+ previous[j - 1] + cost
1309
+ );
1310
+ current.push(value);
1311
+ if (value < rowMin) rowMin = value;
1312
+ }
1313
+ if (rowMin > max) return max + 1;
1314
+ previous = current;
1315
+ }
1316
+ return previous[b.length];
1317
+ }
1318
+ function matchText(input, accepted, policy = {}) {
1319
+ const acceptedList = typeof accepted === "string" ? [accepted] : accepted;
1320
+ const baselineInput = applyBaseline(input, policy);
1321
+ const baselineAccepted = acceptedList.map((answer) => applyBaseline(answer, policy));
1322
+ if (baselineAccepted.some((answer) => answer === baselineInput)) {
1323
+ return { matched: true, via: "exact" };
1324
+ }
1325
+ const usesNormalization = policy.normalize === "NFC" || policy.normalize === "NFKC" || policy.ignorePunctuation === true || policy.collapseInnerWhitespace === true;
1326
+ const normalizedInput = usesNormalization ? applyNormalization(baselineInput, policy) : baselineInput;
1327
+ const normalizedAccepted = usesNormalization ? baselineAccepted.map((answer) => applyNormalization(answer, policy)) : baselineAccepted;
1328
+ if (usesNormalization && normalizedAccepted.some((answer) => answer === normalizedInput)) {
1329
+ return { matched: true, via: "normalized" };
1330
+ }
1331
+ const foldedInput = policy.foldDiacritics === true ? applyDiacriticFold(normalizedInput) : normalizedInput;
1332
+ const foldedAccepted = policy.foldDiacritics === true ? normalizedAccepted.map((answer) => applyDiacriticFold(answer)) : normalizedAccepted;
1333
+ if (policy.foldDiacritics === true && foldedAccepted.some((answer) => answer === foldedInput)) {
1334
+ return { matched: true, via: "folded" };
1335
+ }
1336
+ const maxDistance = _nullishCoalesce(policy.levenshtein, () => ( 0));
1337
+ if (maxDistance > 0 && foldedInput.trim().length > 0 && foldedAccepted.some(
1338
+ (answer) => levenshteinDistance(foldedInput, answer, maxDistance) <= maxDistance
1339
+ )) {
1340
+ return { matched: true, via: "fuzzy" };
1341
+ }
1342
+ return { matched: false, via: "none" };
1343
+ }
1344
+
1250
1345
  // src/schemas/multiple-choice.ts
1251
1346
 
1252
1347
 
@@ -1327,18 +1422,6 @@ var READ_ALOUD_MAX_DIMENSION_WEIGHT = 1e3;
1327
1422
  var SPEECH_ASSESSMENT_MAX_WORDS = 1e3;
1328
1423
  var SPEECH_ASSESSMENT_MAX_TEXT_LENGTH = DICTATION_MAX_TEXT_LENGTH;
1329
1424
 
1330
- // src/count-words.ts
1331
- function countWords(text) {
1332
- if (typeof text !== "string") {
1333
- return 0;
1334
- }
1335
- const trimmed = text.trim();
1336
- if (trimmed === "") {
1337
- return 0;
1338
- }
1339
- return trimmed.split(/\s+/).length;
1340
- }
1341
-
1342
1425
  // src/schemas/read-schema.ts
1343
1426
  var OWN_SCHEMAS = /* @__PURE__ */ new WeakSet();
1344
1427
  function ownSchemas(...schemas) {
@@ -1424,89 +1507,6 @@ function standardJsonSchemaOf(schema) {
1424
1507
  return generated !== null && typeof generated === "object" && !Array.isArray(generated) ? generated : void 0;
1425
1508
  }
1426
1509
 
1427
- // src/scoring/text-match.ts
1428
- var COMBINING_MARKS_RE = /\p{M}/gu;
1429
- var PUNCTUATION_RE2 = /\p{P}/gu;
1430
- function applyBaseline(value, policy) {
1431
- let result = value;
1432
- if (policy.trim !== false) {
1433
- result = result.trim();
1434
- }
1435
- if (policy.caseSensitive !== true) {
1436
- result = policy.locale ? result.toLocaleLowerCase(policy.locale) : result.toLowerCase();
1437
- }
1438
- return result;
1439
- }
1440
- function applyNormalization(value, policy) {
1441
- let result = value;
1442
- if (policy.normalize === "NFC" || policy.normalize === "NFKC") {
1443
- result = result.normalize(policy.normalize);
1444
- }
1445
- if (policy.ignorePunctuation === true) {
1446
- result = result.replace(PUNCTUATION_RE2, "");
1447
- }
1448
- if (policy.collapseInnerWhitespace === true) {
1449
- result = result.replace(/(?<=\S)\s+(?=\S)/g, " ");
1450
- if (policy.trim !== false) {
1451
- result = result.trim();
1452
- }
1453
- }
1454
- return result;
1455
- }
1456
- function applyDiacriticFold(value) {
1457
- return value.normalize("NFD").replace(COMBINING_MARKS_RE, "").normalize("NFC");
1458
- }
1459
- function levenshteinDistance(a, b, max) {
1460
- if (a === b) return 0;
1461
- if (Math.abs(a.length - b.length) > max) return max + 1;
1462
- if (a.length === 0) return b.length;
1463
- if (b.length === 0) return a.length;
1464
- let previous = Array.from({ length: b.length + 1 }, (_, i) => i);
1465
- for (let i = 1; i <= a.length; i += 1) {
1466
- const current = [i];
1467
- let rowMin = i;
1468
- for (let j = 1; j <= b.length; j += 1) {
1469
- const cost = a[i - 1] === b[j - 1] ? 0 : 1;
1470
- const value = Math.min(
1471
- previous[j] + 1,
1472
- current[j - 1] + 1,
1473
- previous[j - 1] + cost
1474
- );
1475
- current.push(value);
1476
- if (value < rowMin) rowMin = value;
1477
- }
1478
- if (rowMin > max) return max + 1;
1479
- previous = current;
1480
- }
1481
- return previous[b.length];
1482
- }
1483
- function matchText(input, accepted, policy = {}) {
1484
- const acceptedList = typeof accepted === "string" ? [accepted] : accepted;
1485
- const baselineInput = applyBaseline(input, policy);
1486
- const baselineAccepted = acceptedList.map((answer) => applyBaseline(answer, policy));
1487
- if (baselineAccepted.some((answer) => answer === baselineInput)) {
1488
- return { matched: true, via: "exact" };
1489
- }
1490
- const usesNormalization = policy.normalize === "NFC" || policy.normalize === "NFKC" || policy.ignorePunctuation === true || policy.collapseInnerWhitespace === true;
1491
- const normalizedInput = usesNormalization ? applyNormalization(baselineInput, policy) : baselineInput;
1492
- const normalizedAccepted = usesNormalization ? baselineAccepted.map((answer) => applyNormalization(answer, policy)) : baselineAccepted;
1493
- if (usesNormalization && normalizedAccepted.some((answer) => answer === normalizedInput)) {
1494
- return { matched: true, via: "normalized" };
1495
- }
1496
- const foldedInput = policy.foldDiacritics === true ? applyDiacriticFold(normalizedInput) : normalizedInput;
1497
- const foldedAccepted = policy.foldDiacritics === true ? normalizedAccepted.map((answer) => applyDiacriticFold(answer)) : normalizedAccepted;
1498
- if (policy.foldDiacritics === true && foldedAccepted.some((answer) => answer === foldedInput)) {
1499
- return { matched: true, via: "folded" };
1500
- }
1501
- const maxDistance = _nullishCoalesce(policy.levenshtein, () => ( 0));
1502
- if (maxDistance > 0 && foldedInput.trim().length > 0 && foldedAccepted.some(
1503
- (answer) => levenshteinDistance(foldedInput, answer, maxDistance) <= maxDistance
1504
- )) {
1505
- return { matched: true, via: "fuzzy" };
1506
- }
1507
- return { matched: false, via: "none" };
1508
- }
1509
-
1510
1510
  // src/registry/registry.ts
1511
1511
  var registry = /* @__PURE__ */ new Map();
1512
1512
  function defineActivityType(descriptor) {
@@ -1768,12 +1768,12 @@ var DictationDataSchema = DictationDataShape.check(
1768
1768
  })
1769
1769
  );
1770
1770
  var isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1771
- function captionFieldsOf(media, written = (captionsUrl) => captionsUrl !== void 0) {
1771
+ function captionFieldsOf(media, written2 = (captionsUrl) => captionsUrl !== void 0) {
1772
1772
  if (!isObject(media)) {
1773
1773
  return [];
1774
1774
  }
1775
1775
  const fields = [];
1776
- if (written(media.captionsUrl)) {
1776
+ if (written2(media.captionsUrl)) {
1777
1777
  fields.push("captionsUrl");
1778
1778
  }
1779
1779
  if (Array.isArray(media.tracks) && media.tracks.length > 0) {
@@ -1781,11 +1781,11 @@ function captionFieldsOf(media, written = (captionsUrl) => captionsUrl !== void
1781
1781
  }
1782
1782
  return fields;
1783
1783
  }
1784
- function groupCaptionsField(group, written) {
1784
+ function groupCaptionsField(group, written2) {
1785
1785
  if (!isObject(group) || !isObject(group.stimulus) || !Array.isArray(group.items)) {
1786
1786
  return void 0;
1787
1787
  }
1788
- const [field] = captionFieldsOf(group.stimulus.media, written);
1788
+ const [field] = captionFieldsOf(group.stimulus.media, written2);
1789
1789
  if (field === void 0) {
1790
1790
  return void 0;
1791
1791
  }
@@ -2562,6 +2562,499 @@ var FillInTheBlanksDataSchema = _v4.z.looseObject({
2562
2562
  }
2563
2563
  );
2564
2564
 
2565
+ // src/answer-leak.ts
2566
+ function placeholderOrder(passage) {
2567
+ return [...passage.matchAll(PLACEHOLDER_RE)].map((match) => match[1]);
2568
+ }
2569
+ function numberedPassage(passage) {
2570
+ let position = 0;
2571
+ return passage.replace(PLACEHOLDER_RE, () => {
2572
+ position += 1;
2573
+ return `[${position}]`;
2574
+ });
2575
+ }
2576
+ function passageWithoutPlaceholders(passage) {
2577
+ return passage.replace(PLACEHOLDER_RE, " ");
2578
+ }
2579
+ function fold(text) {
2580
+ const folded2 = text.normalize("NFKD").replace(/\p{M}/gu, "").toLowerCase().replace(/[^\p{L}\p{N}]+/gu, " ").trim();
2581
+ return folded2 === "" ? [] : folded2.split(" ");
2582
+ }
2583
+ function containsWords(haystack, needle) {
2584
+ if (needle.length === 0 || needle.length > haystack.length) {
2585
+ return false;
2586
+ }
2587
+ for (let start = 0; start + needle.length <= haystack.length; start += 1) {
2588
+ let match = true;
2589
+ for (let offset = 0; offset < needle.length; offset += 1) {
2590
+ if (haystack[start + offset] !== needle[offset]) {
2591
+ match = false;
2592
+ break;
2593
+ }
2594
+ }
2595
+ if (match) {
2596
+ return true;
2597
+ }
2598
+ }
2599
+ return false;
2600
+ }
2601
+ var isShort = (words) => words.length === 1 && [...words[0]].length < 4;
2602
+ function neighbours(passage, position) {
2603
+ const marker = `[${position}]`;
2604
+ const at = passage.indexOf(marker);
2605
+ if (at === -1) {
2606
+ return {};
2607
+ }
2608
+ const strip = (text) => text.replace(/\[\d+\]/g, " ");
2609
+ const left = fold(strip(passage.slice(0, at)));
2610
+ const right = fold(strip(passage.slice(at + marker.length)));
2611
+ const before = left[left.length - 1];
2612
+ const after = right[0];
2613
+ return {
2614
+ ...before !== void 0 ? { before } : {},
2615
+ ...after !== void 0 ? { after } : {}
2616
+ };
2617
+ }
2618
+ function revealsAnswer(text, answer, passage, position) {
2619
+ const words = fold(text);
2620
+ const needle = fold(answer);
2621
+ if (needle.length === 0) {
2622
+ return false;
2623
+ }
2624
+ if (!isShort(needle) || passage === void 0 || position === void 0) {
2625
+ return containsWords(words, needle);
2626
+ }
2627
+ const { before, after } = neighbours(passage, position);
2628
+ if (before === void 0 && after === void 0) {
2629
+ return containsWords(words, needle);
2630
+ }
2631
+ return before !== void 0 && containsWords(words, [before, ...needle]) || after !== void 0 && containsWords(words, [...needle, after]);
2632
+ }
2633
+
2634
+ // src/scoring/strategies/all-or-nothing.ts
2635
+ function allOrNothingStrategy(correctItems) {
2636
+ return correctItems.every((isCorrect) => isCorrect) ? 1 : 0;
2637
+ }
2638
+
2639
+ // src/scoring/strategies/partial.ts
2640
+ function partialStrategy(correctSelected, incorrectSelected, totalCorrect, totalIncorrect) {
2641
+ const reward = correctSelected / totalCorrect;
2642
+ const penalty = totalIncorrect === 0 ? 0 : incorrectSelected / totalIncorrect;
2643
+ return Math.max(0, reward - penalty);
2644
+ }
2645
+ function partialBlankStrategy(correctBlanks, totalBlanks) {
2646
+ return correctBlanks / totalBlanks;
2647
+ }
2648
+
2649
+ // src/scoring/activity-scorers/fill-in-the-blanks.ts
2650
+ function policyFor(blank) {
2651
+ return {
2652
+ ...blank.caseSensitive !== void 0 ? { caseSensitive: blank.caseSensitive } : {},
2653
+ ...blank.trimWhitespace !== void 0 ? { trim: blank.trimWhitespace } : {},
2654
+ ...blank.match
2655
+ };
2656
+ }
2657
+ function scoreFillInTheBlanks(data, response) {
2658
+ const details = [];
2659
+ const perBlankCorrect = [];
2660
+ for (const blank of data.blanks) {
2661
+ const rawInput = response.answers[blank.id];
2662
+ const input = typeof rawInput === "string" ? rawInput : "";
2663
+ const matched = matchText(input, blank.acceptedAnswers, policyFor(blank)).matched;
2664
+ perBlankCorrect.push(matched);
2665
+ details.push({
2666
+ itemId: blank.id,
2667
+ outcome: matched ? "correct" : "incorrect",
2668
+ learnerResponse: [input],
2669
+ correctResponse: [...blank.acceptedAnswers],
2670
+ weight: 1
2671
+ });
2672
+ }
2673
+ const correctBlanks = perBlankCorrect.filter(Boolean).length;
2674
+ const scoreValue = data.scoringStrategy === "all-or-nothing" ? allOrNothingStrategy(perBlankCorrect) : partialBlankStrategy(correctBlanks, data.blanks.length);
2675
+ return { score: scoreValue, maxScore: 1, feedback: null, details };
2676
+ }
2677
+
2678
+ // src/authoring/findings.ts
2679
+ var ITEM_FINDING_SEVERITY = {
2680
+ // multiple-choice
2681
+ mc_title_reveals_answer: "warning",
2682
+ mc_options_duplicate: "warning",
2683
+ mc_key_longest: "warning",
2684
+ mc_every_option_correct: "warning",
2685
+ mc_above_option_shuffled: "warning",
2686
+ mc_above_option: "advice",
2687
+ // fill-in-the-blanks
2688
+ fib_title_reveals_answer: "warning",
2689
+ fib_hint_reveals_answer: "warning",
2690
+ fib_answer_in_passage: "warning",
2691
+ fib_accepted_answer_redundant: "advice",
2692
+ // gap-select
2693
+ gs_title_reveals_answer: "warning",
2694
+ gs_choices_duplicate: "warning",
2695
+ gs_bank_no_distractor: "warning",
2696
+ gs_answer_in_passage: "warning",
2697
+ // written-response
2698
+ wr_criterion_name_duplicate: "warning",
2699
+ // read-aloud
2700
+ ra_text_long_for_time: "warning",
2701
+ // a set of items: a quiz, or an item group's items
2702
+ set_key_position_same: "warning"
2703
+ };
2704
+ function finding(code, path, message) {
2705
+ return { path: path.map(String), message, code, severity: ITEM_FINDING_SEVERITY[code] };
2706
+ }
2707
+ function normaliseFinding(found) {
2708
+ const documented = Object.hasOwn(ITEM_FINDING_SEVERITY, found.code) ? ITEM_FINDING_SEVERITY[found.code] : void 0;
2709
+ return {
2710
+ ...found,
2711
+ path: found.path.map(String),
2712
+ severity: _nullishCoalesce(documented, () => ( (found.severity === "advice" ? "advice" : "warning")))
2713
+ };
2714
+ }
2715
+
2716
+ // src/authoring/critique.ts
2717
+ var written = (value) => typeof value === "string" && !isUnwritten(value) ? value : void 0;
2718
+ var folded = (text) => fold(text).join(" ");
2719
+ var listOf = (value) => Array.isArray(value) ? value : [];
2720
+ var lengthOf = (text) => [...text.trim()].length;
2721
+ var ordinal = (index) => index + 1;
2722
+ function duplicates(entries, textOf) {
2723
+ const seen = /* @__PURE__ */ new Map();
2724
+ const pairs = [];
2725
+ entries.forEach((entry, index) => {
2726
+ const text = textOf(entry);
2727
+ if (text === void 0) {
2728
+ return;
2729
+ }
2730
+ const key = folded(text);
2731
+ if (key === "") {
2732
+ return;
2733
+ }
2734
+ const first = seen.get(key);
2735
+ if (first === void 0) {
2736
+ seen.set(key, index);
2737
+ } else {
2738
+ pairs.push({ first, second: index });
2739
+ }
2740
+ });
2741
+ return pairs;
2742
+ }
2743
+ function printedIn(passage, answer) {
2744
+ const needle = fold(answer);
2745
+ return needle.length > 0 && !isShort(needle) && containsWords(fold(passageWithoutPlaceholders(passage)), needle);
2746
+ }
2747
+ var POSITIONAL = /(^| )(the above|las anteriores|los anteriores|as anteriores|os anteriores|[a-h] (and|or|y|o|e|ou) [a-h])( |$)/;
2748
+ var AGGREGATE = /(^| )(all of the above|none of the above|todas las anteriores|ninguna de las anteriores|todas as anteriores|nenhuma das anteriores)( |$)/;
2749
+ var KEY_LONGEST_RATIO = 1.5;
2750
+ var KEY_LONGEST_MARGIN = 10;
2751
+ function critiqueMultipleChoice(draft) {
2752
+ const findings = [];
2753
+ const options = listOf(draft.options);
2754
+ const texts = options.map((option) => isRecord(option) ? written(option.text) : void 0);
2755
+ const correct = options.map((option) => isRecord(option) && option.isCorrect === true);
2756
+ const hasMedia = options.map((option) => isRecord(option) && option.media !== void 0);
2757
+ const title = written(draft.title);
2758
+ const revealed = options.findIndex(
2759
+ (_, index) => correct[index] === true && title !== void 0 && texts[index] !== void 0 && revealsAnswer(title, texts[index])
2760
+ );
2761
+ if (revealed !== -1) {
2762
+ findings.push(
2763
+ finding(
2764
+ "mc_title_reveals_answer",
2765
+ ["title"],
2766
+ `The title, which the learner sees, contains the right answer ("${texts[revealed]}").`
2767
+ )
2768
+ );
2769
+ }
2770
+ for (const { first, second } of duplicates(
2771
+ options,
2772
+ (option) => isRecord(option) && option.media === void 0 ? written(option.text) : void 0
2773
+ )) {
2774
+ findings.push(
2775
+ finding(
2776
+ "mc_options_duplicate",
2777
+ ["options", second, "text"],
2778
+ `Options ${ordinal(first)} and ${ordinal(second)} read the same, so a learner cannot tell them apart.`
2779
+ )
2780
+ );
2781
+ }
2782
+ const keys = correct.flatMap((isKey, index) => isKey ? [index] : []);
2783
+ if (draft.mode === "single" && options.length >= 3 && keys.length === 1 && texts.every((text) => text !== void 0) && !hasMedia.some(Boolean)) {
2784
+ const key = keys[0];
2785
+ const keyLength = lengthOf(texts[key]);
2786
+ const longestOther = Math.max(
2787
+ ...texts.flatMap((text, index) => index === key ? [] : [lengthOf(text)])
2788
+ );
2789
+ if (keyLength >= longestOther * KEY_LONGEST_RATIO && keyLength - longestOther >= KEY_LONGEST_MARGIN) {
2790
+ findings.push(
2791
+ finding(
2792
+ "mc_key_longest",
2793
+ ["options", key, "text"],
2794
+ "The right option is much longer than every other, and a test-wise learner picks the longest. Bring the options to a similar length."
2795
+ )
2796
+ );
2797
+ }
2798
+ }
2799
+ if (draft.mode === "multi" && options.length >= 2 && correct.every(Boolean)) {
2800
+ findings.push(
2801
+ finding(
2802
+ "mc_every_option_correct",
2803
+ ["options"],
2804
+ "Every option is correct, so a learner who selects them all scores full marks without reading them."
2805
+ )
2806
+ );
2807
+ }
2808
+ texts.forEach((text, index) => {
2809
+ if (text === void 0) {
2810
+ return;
2811
+ }
2812
+ const words = folded(text);
2813
+ if (draft.shuffle === true && POSITIONAL.test(words)) {
2814
+ findings.push(
2815
+ finding(
2816
+ "mc_above_option_shuffled",
2817
+ ["options", index, "text"],
2818
+ `Option ${ordinal(index)} points at other options by position, and this question shuffles them: after shuffling it points at the wrong ones.`
2819
+ )
2820
+ );
2821
+ } else if (AGGREGATE.test(words)) {
2822
+ findings.push(
2823
+ finding(
2824
+ "mc_above_option",
2825
+ ["options", index, "text"],
2826
+ `Option ${ordinal(index)} is "all" or "none of the above". A learner who spots two right options can choose "all" without knowing the rest; "none" tests only what is wrong.`
2827
+ )
2828
+ );
2829
+ }
2830
+ });
2831
+ return findings;
2832
+ }
2833
+ function redundantUnder(blank, earlier, later) {
2834
+ try {
2835
+ const { levenshtein: _typos, ...policy } = policyFor(blank);
2836
+ return matchText(later, earlier, policy).matched;
2837
+ } catch (e) {
2838
+ return false;
2839
+ }
2840
+ }
2841
+ function critiqueFillInTheBlanks(draft) {
2842
+ const findings = [];
2843
+ const passage = typeof draft.passage === "string" ? draft.passage : "";
2844
+ const numbered = numberedPassage(passage);
2845
+ const order = placeholderOrder(passage);
2846
+ const blanks = listOf(draft.blanks);
2847
+ const answersOf = (blank) => isRecord(blank) ? listOf(blank.acceptedAnswers).flatMap((answer) => _nullishCoalesce(written(answer), () => ( []))) : [];
2848
+ const positionOf = (blank) => {
2849
+ const at = isRecord(blank) && typeof blank.id === "string" ? order.indexOf(blank.id) : -1;
2850
+ return at === -1 ? void 0 : at + 1;
2851
+ };
2852
+ const title = written(draft.title);
2853
+ if (title !== void 0 && blanks.some(
2854
+ (blank) => answersOf(blank).some((answer) => revealsAnswer(title, answer, numbered, positionOf(blank)))
2855
+ )) {
2856
+ findings.push(
2857
+ finding(
2858
+ "fib_title_reveals_answer",
2859
+ ["title"],
2860
+ "The title, which the learner sees, contains an answer to a blank."
2861
+ )
2862
+ );
2863
+ }
2864
+ blanks.forEach((blank, index) => {
2865
+ if (!isRecord(blank)) {
2866
+ return;
2867
+ }
2868
+ const answers = answersOf(blank);
2869
+ const hint = written(blank.hint);
2870
+ if (hint !== void 0 && answers.some((answer) => revealsAnswer(hint, answer, numbered, positionOf(blank)))) {
2871
+ findings.push(
2872
+ finding(
2873
+ "fib_hint_reveals_answer",
2874
+ ["blanks", index, "hint"],
2875
+ `The hint for blank ${ordinal(index)} contains its answer.`
2876
+ )
2877
+ );
2878
+ }
2879
+ const accepted = listOf(blank.acceptedAnswers);
2880
+ const printed = accepted.findIndex((answer) => {
2881
+ const text = written(answer);
2882
+ return text !== void 0 && printedIn(passage, text);
2883
+ });
2884
+ if (printed !== -1) {
2885
+ findings.push(
2886
+ finding(
2887
+ "fib_answer_in_passage",
2888
+ ["blanks", index, "acceptedAnswers", printed],
2889
+ `The passage prints "${accepted[printed]}", an answer to blank ${ordinal(index)}, where the learner can copy it.`
2890
+ )
2891
+ );
2892
+ }
2893
+ accepted.forEach((later, laterIndex) => {
2894
+ const laterText = written(later);
2895
+ if (laterText === void 0) {
2896
+ return;
2897
+ }
2898
+ const earlier = accepted.slice(0, laterIndex).findIndex((candidate) => {
2899
+ const text = written(candidate);
2900
+ return text !== void 0 && redundantUnder(blank, text, laterText);
2901
+ });
2902
+ if (earlier !== -1) {
2903
+ findings.push(
2904
+ finding(
2905
+ "fib_accepted_answer_redundant",
2906
+ ["blanks", index, "acceptedAnswers", laterIndex],
2907
+ `"${laterText}" is already accepted as "${accepted[earlier]}" under this blank's matching.`
2908
+ )
2909
+ );
2910
+ }
2911
+ });
2912
+ });
2913
+ return findings;
2914
+ }
2915
+ function critiqueGapSelect(draft) {
2916
+ const findings = [];
2917
+ const passage = typeof draft.passage === "string" ? draft.passage : "";
2918
+ const numbered = numberedPassage(passage);
2919
+ const order = placeholderOrder(passage);
2920
+ const gaps = listOf(draft.gaps);
2921
+ const banks = listOf(draft.banks);
2922
+ const choiceText = (choice) => isRecord(choice) ? written(choice.text) : void 0;
2923
+ const choicesOf = (gap) => {
2924
+ if (Array.isArray(gap.choices)) {
2925
+ return gap.choices;
2926
+ }
2927
+ const bank = banks.find((candidate) => isRecord(candidate) && candidate.id === gap.bankId);
2928
+ return isRecord(bank) ? listOf(bank.choices) : [];
2929
+ };
2930
+ const answerOf = (gap) => choiceText(
2931
+ choicesOf(gap).find((choice) => isRecord(choice) && choice.id === gap.correctChoiceId)
2932
+ );
2933
+ const positionOf = (gap) => {
2934
+ const at = typeof gap.id === "string" ? order.indexOf(gap.id) : -1;
2935
+ return at === -1 ? void 0 : at + 1;
2936
+ };
2937
+ const title = written(draft.title);
2938
+ if (title !== void 0 && gaps.some((gap) => {
2939
+ if (!isRecord(gap)) {
2940
+ return false;
2941
+ }
2942
+ const answer = answerOf(gap);
2943
+ return answer !== void 0 && revealsAnswer(title, answer, numbered, positionOf(gap));
2944
+ })) {
2945
+ findings.push(
2946
+ finding(
2947
+ "gs_title_reveals_answer",
2948
+ ["title"],
2949
+ "The title, which the learner sees, contains the answer to a gap."
2950
+ )
2951
+ );
2952
+ }
2953
+ const reportDuplicates = (choices, path) => {
2954
+ for (const { first, second } of duplicates(choices, choiceText)) {
2955
+ findings.push(
2956
+ finding(
2957
+ "gs_choices_duplicate",
2958
+ [...path, "choices", second, "text"],
2959
+ `Choices ${ordinal(first)} and ${ordinal(second)} read the same, so a learner cannot tell them apart.`
2960
+ )
2961
+ );
2962
+ }
2963
+ };
2964
+ gaps.forEach((gap, index) => {
2965
+ if (!isRecord(gap)) {
2966
+ return;
2967
+ }
2968
+ if (Array.isArray(gap.choices)) {
2969
+ reportDuplicates(gap.choices, ["gaps", index]);
2970
+ }
2971
+ const answer = answerOf(gap);
2972
+ if (answer !== void 0 && printedIn(passage, answer)) {
2973
+ findings.push(
2974
+ finding(
2975
+ "gs_answer_in_passage",
2976
+ ["gaps", index, "correctChoiceId"],
2977
+ `The passage prints "${answer}", the answer to gap ${ordinal(index)}.`
2978
+ )
2979
+ );
2980
+ }
2981
+ });
2982
+ banks.forEach((bank, index) => {
2983
+ if (!isRecord(bank)) {
2984
+ return;
2985
+ }
2986
+ const choices = listOf(bank.choices);
2987
+ reportDuplicates(choices, ["banks", index]);
2988
+ const drawnOn = gaps.filter((gap) => isRecord(gap) && gap.bankId === bank.id).length;
2989
+ if (drawnOn >= 2 && choices.length <= drawnOn) {
2990
+ findings.push(
2991
+ finding(
2992
+ "gs_bank_no_distractor",
2993
+ ["banks", index, "choices"],
2994
+ `This bank offers ${choices.length} choices to ${drawnOn} gaps, so the last gap is answered by elimination. Add a choice that answers no gap.`
2995
+ )
2996
+ );
2997
+ }
2998
+ });
2999
+ return findings;
3000
+ }
3001
+ function critiqueWrittenResponse(draft) {
3002
+ const rubric = isRecord(draft.rubric) ? draft.rubric : void 0;
3003
+ const criteria = rubric === void 0 ? [] : listOf(rubric.criteria);
3004
+ return duplicates(
3005
+ criteria,
3006
+ (criterion) => isRecord(criterion) ? written(criterion.name) : void 0
3007
+ ).map(
3008
+ ({ first, second }) => finding(
3009
+ "wr_criterion_name_duplicate",
3010
+ ["rubric", "criteria", second, "name"],
3011
+ `Criteria ${ordinal(first)} and ${ordinal(second)} have the same name. Feedback and grades name a criterion, so one of them cannot be told apart.`
3012
+ )
3013
+ );
3014
+ }
3015
+ var WORDS_PER_SECOND = 2.5;
3016
+ function critiqueReadAloud(draft) {
3017
+ const text = written(draft.referenceText);
3018
+ const recording = isRecord(draft.recording) ? draft.recording : void 0;
3019
+ const maxSeconds = _optionalChain([recording, 'optionalAccess', _58 => _58.maxSeconds]);
3020
+ if (text === void 0 || typeof maxSeconds !== "number" || !(maxSeconds > 0)) {
3021
+ return [];
3022
+ }
3023
+ const words = countWords(text);
3024
+ if (words / maxSeconds <= WORDS_PER_SECOND) {
3025
+ return [];
3026
+ }
3027
+ return [
3028
+ finding(
3029
+ "ra_text_long_for_time",
3030
+ ["recording", "maxSeconds"],
3031
+ `${words} words in ${maxSeconds} seconds is faster than a fluent reader reads aloud (${WORDS_PER_SECOND} words a second). Allow at least ${Math.ceil(words / WORDS_PER_SECOND)} seconds, or shorten the text.`
3032
+ )
3033
+ ];
3034
+ }
3035
+ var KEY_POSITION_MINIMUM = 4;
3036
+ function keyPositionFinding(items, path) {
3037
+ const positions = items.flatMap((item) => {
3038
+ if (!isRecord(item) || item.type !== "multiple-choice" || item.mode !== "single" || item.shuffle === true) {
3039
+ return [];
3040
+ }
3041
+ const keys = listOf(item.options).flatMap(
3042
+ (option, index) => isRecord(option) && option.isCorrect === true ? [index] : []
3043
+ );
3044
+ return keys.length === 1 ? keys : [];
3045
+ });
3046
+ if (positions.length < KEY_POSITION_MINIMUM || !positions.every((at) => at === positions[0])) {
3047
+ return [];
3048
+ }
3049
+ return [
3050
+ finding(
3051
+ "set_key_position_same",
3052
+ path,
3053
+ `In all ${positions.length} single-answer questions the right option is option ${ordinal(positions[0])}. Vary its position, or shuffle the options.`
3054
+ )
3055
+ ];
3056
+ }
3057
+
2565
3058
  // src/authoring/fill-in-the-blanks.ts
2566
3059
  var fillInTheBlanksAuthoring = {
2567
3060
  createDraft: ({ newId }) => ({
@@ -2573,7 +3066,8 @@ var fillInTheBlanksAuthoring = {
2573
3066
  blanks: [],
2574
3067
  scoringStrategy: "all-or-nothing"
2575
3068
  }),
2576
- checkDraft: checkFillInTheBlanksDraft
3069
+ checkDraft: checkFillInTheBlanksDraft,
3070
+ critique: critiqueFillInTheBlanks
2577
3071
  };
2578
3072
  function checkFillInTheBlanksDraft(draft) {
2579
3073
  const issues = checkIdentity(draft, "fill-in-the-blanks");
@@ -2680,7 +3174,7 @@ function isLanguageTag(tag) {
2680
3174
  try {
2681
3175
  Intl.getCanonicalLocales(tag);
2682
3176
  return true;
2683
- } catch (e) {
3177
+ } catch (e2) {
2684
3178
  return false;
2685
3179
  }
2686
3180
  }
@@ -2767,7 +3261,8 @@ var gapSelectAuthoring = {
2767
3261
  gaps: [],
2768
3262
  scoringStrategy: "all-or-nothing"
2769
3263
  }),
2770
- checkDraft: checkGapSelectDraft
3264
+ checkDraft: checkGapSelectDraft,
3265
+ critique: critiqueGapSelect
2771
3266
  };
2772
3267
  function checkGapSelectDraft(draft) {
2773
3268
  const issues = checkIdentity(draft, "gap-select");
@@ -2846,10 +3341,10 @@ function checkGaps(gaps, banks, bankIds, gapIds) {
2846
3341
  if (!isRecord(gap)) {
2847
3342
  continue;
2848
3343
  }
2849
- const ordinal = index + 1;
2850
- const name = typeof gap.id === "string" && gap.id !== "" ? `"${gap.id}"` : ordinal;
3344
+ const ordinal2 = index + 1;
3345
+ const name = typeof gap.id === "string" && gap.id !== "" ? `"${gap.id}"` : ordinal2;
2851
3346
  if (isMissingId(gap.id)) {
2852
- issues.push(issue("gs_gap_id_required", ["gaps", index, "id"], `Gap ${ordinal} has no id.`));
3347
+ issues.push(issue("gs_gap_id_required", ["gaps", index, "id"], `Gap ${ordinal2} has no id.`));
2853
3348
  } else if (typeof gap.id === "string") {
2854
3349
  if (seen.has(gap.id)) {
2855
3350
  repeated.add(gap.id);
@@ -3067,7 +3562,8 @@ var multipleChoiceAuthoring = {
3067
3562
  { id: newId(), text: "", isCorrect: false }
3068
3563
  ]
3069
3564
  }),
3070
- checkDraft: checkMultipleChoiceDraft
3565
+ checkDraft: checkMultipleChoiceDraft,
3566
+ critique: critiqueMultipleChoice
3071
3567
  };
3072
3568
  function checkMultipleChoiceDraft(draft) {
3073
3569
  const issues = checkIdentity(draft, "multiple-choice");
@@ -3097,10 +3593,10 @@ function checkMultipleChoiceDraft(draft) {
3097
3593
  if (!isRecord(option)) {
3098
3594
  continue;
3099
3595
  }
3100
- const ordinal = index + 1;
3596
+ const ordinal2 = index + 1;
3101
3597
  if (isMissingId(option.id)) {
3102
3598
  issues.push(
3103
- issue("mc_option_id_required", ["options", index, "id"], `Option ${ordinal} has no id.`)
3599
+ issue("mc_option_id_required", ["options", index, "id"], `Option ${ordinal2} has no id.`)
3104
3600
  );
3105
3601
  } else if (typeof option.id === "string") {
3106
3602
  if (seen.has(option.id)) {
@@ -3113,7 +3609,7 @@ function checkMultipleChoiceDraft(draft) {
3113
3609
  issue(
3114
3610
  "mc_option_text_required",
3115
3611
  ["options", index, "text"],
3116
- `Write the text of option ${ordinal}.`
3612
+ `Write the text of option ${ordinal2}.`
3117
3613
  )
3118
3614
  );
3119
3615
  }
@@ -3122,7 +3618,7 @@ function checkMultipleChoiceDraft(draft) {
3122
3618
  issue(
3123
3619
  "mc_option_correctness_required",
3124
3620
  ["options", index, "isCorrect"],
3125
- `Say whether option ${ordinal} is correct.`
3621
+ `Say whether option ${ordinal2} is correct.`
3126
3622
  )
3127
3623
  );
3128
3624
  }
@@ -3130,7 +3626,7 @@ function checkMultipleChoiceDraft(draft) {
3130
3626
  correct += 1;
3131
3627
  }
3132
3628
  if (isRecord(option.media)) {
3133
- issues.push(...checkOptionMedia(option.media, index, ordinal));
3629
+ issues.push(...checkOptionMedia(option.media, index, ordinal2));
3134
3630
  }
3135
3631
  }
3136
3632
  for (const id of repeated) {
@@ -3159,7 +3655,7 @@ function checkMultipleChoiceDraft(draft) {
3159
3655
  issues.push(...checkSharedOptional(draft));
3160
3656
  return issues;
3161
3657
  }
3162
- function checkOptionMedia(media, index, ordinal) {
3658
+ function checkOptionMedia(media, index, ordinal2) {
3163
3659
  const path = ["options", index, "media"];
3164
3660
  const kind = media.type;
3165
3661
  if (kind === "video" || kind === "embed") {
@@ -3167,7 +3663,7 @@ function checkOptionMedia(media, index, ordinal) {
3167
3663
  issue(
3168
3664
  "mc_option_media_kind",
3169
3665
  [...path, "type"],
3170
- `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.`
3666
+ `Option ${ordinal2} 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.`
3171
3667
  )
3172
3668
  ];
3173
3669
  }
@@ -3225,7 +3721,7 @@ var ReadAloudDataShape = _v4.z.looseObject({
3225
3721
  });
3226
3722
  var ReadAloudDataSchema = ReadAloudDataShape.check(
3227
3723
  checkEvenAfterIssues((ctx) => {
3228
- if (ctx.issues.some((issue2) => (_nullishCoalesce(_optionalChain([issue2, 'access', _58 => _58.path, 'optionalAccess', _59 => _59.length]), () => ( 0))) === 0)) {
3724
+ if (ctx.issues.some((issue2) => (_nullishCoalesce(_optionalChain([issue2, 'access', _59 => _59.path, 'optionalAccess', _60 => _60.length]), () => ( 0))) === 0)) {
3229
3725
  return;
3230
3726
  }
3231
3727
  const { data, refused } = parsedFields(ctx);
@@ -3274,14 +3770,14 @@ var ReadAloudDataSchema = ReadAloudDataShape.check(
3274
3770
  "A slow model recording accompanies a recording: add `media` first."
3275
3771
  );
3276
3772
  }
3277
- if (_optionalChain([data, 'access', _60 => _60.media, 'optionalAccess', _61 => _61.playback, 'optionalAccess', _62 => _62.maxPlays]) !== void 0) {
3773
+ if (_optionalChain([data, 'access', _61 => _61.media, 'optionalAccess', _62 => _62.playback, 'optionalAccess', _63 => _63.maxPlays]) !== void 0) {
3278
3774
  report(
3279
3775
  ["slowMedia"],
3280
3776
  data.slowMedia,
3281
3777
  "A play budget on `media` cannot coexist with an unbudgeted slow recording of the same content. Drop `slowMedia`, or drop `maxPlays`."
3282
3778
  );
3283
3779
  }
3284
- if (typeof data.slowMedia.url === "string" && typeof _optionalChain([data, 'access', _63 => _63.media, 'optionalAccess', _64 => _64.url]) === "string" && data.slowMedia.url === data.media.url) {
3780
+ if (typeof data.slowMedia.url === "string" && typeof _optionalChain([data, 'access', _64 => _64.media, 'optionalAccess', _65 => _65.url]) === "string" && data.slowMedia.url === data.media.url) {
3285
3781
  report(
3286
3782
  ["slowMedia", "url"],
3287
3783
  data.slowMedia.url,
@@ -3335,7 +3831,8 @@ var readAloudAuthoring = {
3335
3831
  recording: { maxSeconds: 0 },
3336
3832
  scoring: { dimensions: [] }
3337
3833
  }),
3338
- checkDraft: checkReadAloudDraft
3834
+ checkDraft: checkReadAloudDraft,
3835
+ critique: critiqueReadAloud
3339
3836
  };
3340
3837
  function checkReadAloudDraft(draft) {
3341
3838
  const issues = checkIdentity(draft, "read-aloud");
@@ -3611,7 +4108,8 @@ var writtenResponseAuthoring = {
3611
4108
  minWords: 0,
3612
4109
  maxWords: 0
3613
4110
  }),
3614
- checkDraft: checkWrittenResponseDraft
4111
+ checkDraft: checkWrittenResponseDraft,
4112
+ critique: critiqueWrittenResponse
3615
4113
  };
3616
4114
  function checkWrittenResponseDraft(draft) {
3617
4115
  const issues = checkIdentity(draft, "written-response");
@@ -3691,11 +4189,11 @@ function checkCriteria(criteria) {
3691
4189
  everyWeightUsable = false;
3692
4190
  continue;
3693
4191
  }
3694
- const ordinal = index + 1;
4192
+ const ordinal2 = index + 1;
3695
4193
  const path = ["rubric", "criteria", index];
3696
4194
  if (isUnwritten(criterion.name)) {
3697
4195
  issues.push(
3698
- issue("wr_criterion_name_required", [...path, "name"], `Name rubric criterion ${ordinal}.`)
4196
+ issue("wr_criterion_name_required", [...path, "name"], `Name rubric criterion ${ordinal2}.`)
3699
4197
  );
3700
4198
  }
3701
4199
  const weight = criterion.weight;
@@ -3705,7 +4203,7 @@ function checkCriteria(criteria) {
3705
4203
  issue(
3706
4204
  "wr_criterion_weight_required",
3707
4205
  [...path, "weight"],
3708
- `Give rubric criterion ${ordinal} a weight.`
4206
+ `Give rubric criterion ${ordinal2} a weight.`
3709
4207
  )
3710
4208
  );
3711
4209
  } else if (!(typeof weight === "number" && Number.isFinite(weight) && weight >= 0)) {
@@ -3765,7 +4263,7 @@ function resolveChoices2(gap, banks) {
3765
4263
  if (gap.bankId === void 0) {
3766
4264
  return void 0;
3767
4265
  }
3768
- return _optionalChain([banks, 'access', _65 => _65.find, 'call', _66 => _66((bank) => bank.id === gap.bankId), 'optionalAccess', _67 => _67.choices]);
4266
+ return _optionalChain([banks, 'access', _66 => _66.find, 'call', _67 => _67((bank) => bank.id === gap.bankId), 'optionalAccess', _68 => _68.choices]);
3769
4267
  }
3770
4268
  function placeholderCounts(passage) {
3771
4269
  const counts = /* @__PURE__ */ new Map();
@@ -3987,12 +4485,12 @@ var RedactedDictationDataSchema = _v4.z.strictObject({
3987
4485
  for (const captions of captionFieldsOf(data.media)) {
3988
4486
  ctx.issues.push({
3989
4487
  code: "custom",
3990
- input: _optionalChain([data, 'access', _68 => _68.media, 'optionalAccess', _69 => _69[captions]]),
4488
+ input: _optionalChain([data, 'access', _69 => _69.media, 'optionalAccess', _70 => _70[captions]]),
3991
4489
  message: "A dictation recording cannot carry captions: the captions are the answer, so this payload is not learner-safe.",
3992
4490
  path: ["media", captions]
3993
4491
  });
3994
4492
  }
3995
- if (data.slowMedia !== void 0 && _optionalChain([data, 'access', _70 => _70.media, 'optionalAccess', _71 => _71.playback, 'optionalAccess', _72 => _72.maxPlays]) !== void 0) {
4493
+ if (data.slowMedia !== void 0 && _optionalChain([data, 'access', _71 => _71.media, 'optionalAccess', _72 => _72.playback, 'optionalAccess', _73 => _73.maxPlays]) !== void 0) {
3996
4494
  ctx.issues.push({
3997
4495
  code: "custom",
3998
4496
  input: data.slowMedia,
@@ -4047,7 +4545,7 @@ var RedactedReadAloudDataSchema = _v4.z.strictObject({
4047
4545
  path: ["media"]
4048
4546
  });
4049
4547
  }
4050
- if (data.slowMedia !== void 0 && _optionalChain([data, 'access', _73 => _73.media, 'optionalAccess', _74 => _74.playback, 'optionalAccess', _75 => _75.maxPlays]) !== void 0) {
4548
+ if (data.slowMedia !== void 0 && _optionalChain([data, 'access', _74 => _74.media, 'optionalAccess', _75 => _75.playback, 'optionalAccess', _76 => _76.maxPlays]) !== void 0) {
4051
4549
  ctx.issues.push({
4052
4550
  code: "custom",
4053
4551
  input: data.slowMedia,
@@ -4055,7 +4553,7 @@ var RedactedReadAloudDataSchema = _v4.z.strictObject({
4055
4553
  path: ["slowMedia"]
4056
4554
  });
4057
4555
  }
4058
- if (data.slowMedia !== void 0 && data.slowMedia.url === _optionalChain([data, 'access', _76 => _76.media, 'optionalAccess', _77 => _77.url])) {
4556
+ if (data.slowMedia !== void 0 && data.slowMedia.url === _optionalChain([data, 'access', _77 => _77.media, 'optionalAccess', _78 => _78.url])) {
4059
4557
  ctx.issues.push({
4060
4558
  code: "custom",
4061
4559
  input: data.slowMedia.url,
@@ -4067,7 +4565,7 @@ var RedactedReadAloudDataSchema = _v4.z.strictObject({
4067
4565
 
4068
4566
  // src/scoring/activity-scorers/dictation.ts
4069
4567
  function scoreDictation(data, response) {
4070
- const alignment = alignDictation(data, typeof _optionalChain([response, 'optionalAccess', _78 => _78.text]) === "string" ? response.text : "");
4568
+ const alignment = alignDictation(data, typeof _optionalChain([response, 'optionalAccess', _79 => _79.text]) === "string" ? response.text : "");
4071
4569
  const details = [];
4072
4570
  for (const word of alignment.words) {
4073
4571
  if (word.status === "extra") {
@@ -4085,56 +4583,12 @@ function scoreDictation(data, response) {
4085
4583
  return { score: alignment.similarity, maxScore: 1, feedback: null, details };
4086
4584
  }
4087
4585
 
4088
- // src/scoring/strategies/all-or-nothing.ts
4089
- function allOrNothingStrategy(correctItems) {
4090
- return correctItems.every((isCorrect) => isCorrect) ? 1 : 0;
4091
- }
4092
-
4093
- // src/scoring/strategies/partial.ts
4094
- function partialStrategy(correctSelected, incorrectSelected, totalCorrect, totalIncorrect) {
4095
- const reward = correctSelected / totalCorrect;
4096
- const penalty = totalIncorrect === 0 ? 0 : incorrectSelected / totalIncorrect;
4097
- return Math.max(0, reward - penalty);
4098
- }
4099
- function partialBlankStrategy(correctBlanks, totalBlanks) {
4100
- return correctBlanks / totalBlanks;
4101
- }
4102
-
4103
- // src/scoring/activity-scorers/fill-in-the-blanks.ts
4104
- function policyFor(blank) {
4105
- return {
4106
- ...blank.caseSensitive !== void 0 ? { caseSensitive: blank.caseSensitive } : {},
4107
- ...blank.trimWhitespace !== void 0 ? { trim: blank.trimWhitespace } : {},
4108
- ...blank.match
4109
- };
4110
- }
4111
- function scoreFillInTheBlanks(data, response) {
4112
- const details = [];
4113
- const perBlankCorrect = [];
4114
- for (const blank of data.blanks) {
4115
- const rawInput = response.answers[blank.id];
4116
- const input = typeof rawInput === "string" ? rawInput : "";
4117
- const matched = matchText(input, blank.acceptedAnswers, policyFor(blank)).matched;
4118
- perBlankCorrect.push(matched);
4119
- details.push({
4120
- itemId: blank.id,
4121
- outcome: matched ? "correct" : "incorrect",
4122
- learnerResponse: [input],
4123
- correctResponse: [...blank.acceptedAnswers],
4124
- weight: 1
4125
- });
4126
- }
4127
- const correctBlanks = perBlankCorrect.filter(Boolean).length;
4128
- const scoreValue = data.scoringStrategy === "all-or-nothing" ? allOrNothingStrategy(perBlankCorrect) : partialBlankStrategy(correctBlanks, data.blanks.length);
4129
- return { score: scoreValue, maxScore: 1, feedback: null, details };
4130
- }
4131
-
4132
4586
  // src/scoring/activity-scorers/gap-select.ts
4133
4587
  function choicesFor(data, gap) {
4134
4588
  if (gap.choices !== void 0) {
4135
4589
  return gap.choices;
4136
4590
  }
4137
- return _nullishCoalesce(_optionalChain([data, 'access', _79 => _79.banks, 'optionalAccess', _80 => _80.find, 'call', _81 => _81((bank) => bank.id === gap.bankId), 'optionalAccess', _82 => _82.choices]), () => ( []));
4591
+ return _nullishCoalesce(_optionalChain([data, 'access', _80 => _80.banks, 'optionalAccess', _81 => _81.find, 'call', _82 => _82((bank) => bank.id === gap.bankId), 'optionalAccess', _83 => _83.choices]), () => ( []));
4138
4592
  }
4139
4593
  function scoreGapSelect(data, response) {
4140
4594
  const details = [];
@@ -4169,7 +4623,7 @@ function scoreMultipleChoice(data, response) {
4169
4623
  let scoreValue;
4170
4624
  if (data.scoringStrategy === "all-or-nothing") {
4171
4625
  if (data.mode === "single") {
4172
- scoreValue = response.selectedOptionIds.length === 1 && _optionalChain([optionById, 'access', _83 => _83.get, 'call', _84 => _84(response.selectedOptionIds[0]), 'optionalAccess', _85 => _85.isCorrect]) === true ? 1 : 0;
4626
+ scoreValue = response.selectedOptionIds.length === 1 && _optionalChain([optionById, 'access', _84 => _84.get, 'call', _85 => _85(response.selectedOptionIds[0]), 'optionalAccess', _86 => _86.isCorrect]) === true ? 1 : 0;
4173
4627
  } else {
4174
4628
  const correctIds = data.options.filter((o) => o.isCorrect).map((o) => o.id);
4175
4629
  const allCorrectSelected = correctIds.every((id) => selected.has(id));
@@ -4180,7 +4634,7 @@ function scoreMultipleChoice(data, response) {
4180
4634
  let incorrectSelected = 0;
4181
4635
  for (const id of selected) {
4182
4636
  const option = optionById.get(id);
4183
- if (_optionalChain([option, 'optionalAccess', _86 => _86.isCorrect])) {
4637
+ if (_optionalChain([option, 'optionalAccess', _87 => _87.isCorrect])) {
4184
4638
  correctSelected += 1;
4185
4639
  } else {
4186
4640
  incorrectSelected += 1;
@@ -4363,7 +4817,7 @@ var multipleChoiceType = defineActivityType({
4363
4817
  // exact optionals. Structurally identical at runtime — cast is type-level only.
4364
4818
  schema: MultipleChoiceDataSchema,
4365
4819
  scoring: { kind: "sync", score: scoreMultipleChoice },
4366
- isAnswered: (response) => (_nullishCoalesce(_optionalChain([response, 'optionalAccess', _87 => _87.selectedOptionIds, 'access', _88 => _88.length]), () => ( 0))) > 0,
4820
+ isAnswered: (response) => (_nullishCoalesce(_optionalChain([response, 'optionalAccess', _88 => _88.selectedOptionIds, 'access', _89 => _89.length]), () => ( 0))) > 0,
4367
4821
  fieldPolicy: MULTIPLE_CHOICE_FIELD_POLICY,
4368
4822
  redactedSchema: RedactedMultipleChoiceDataSchema,
4369
4823
  interop: {
@@ -4380,7 +4834,7 @@ var fillInTheBlanksType = defineActivityType({
4380
4834
  type: "fill-in-the-blanks",
4381
4835
  schema: FillInTheBlanksDataSchema,
4382
4836
  scoring: { kind: "sync", score: scoreFillInTheBlanks },
4383
- isAnswered: (response) => Object.values(_nullishCoalesce(_optionalChain([response, 'optionalAccess', _89 => _89.answers]), () => ( {}))).some((answer) => answer.trim().length > 0),
4837
+ isAnswered: (response) => Object.values(_nullishCoalesce(_optionalChain([response, 'optionalAccess', _90 => _90.answers]), () => ( {}))).some((answer) => answer.trim().length > 0),
4384
4838
  fieldPolicy: FILL_IN_THE_BLANKS_FIELD_POLICY,
4385
4839
  redactedSchema: RedactedFillInTheBlanksDataSchema,
4386
4840
  interop: {
@@ -4403,14 +4857,14 @@ var writtenResponseType = defineActivityType({
4403
4857
  kind: "deferred",
4404
4858
  reason: "requires_async_grading",
4405
4859
  partial: (data, response) => {
4406
- const wordCount = countWords(_nullishCoalesce(_optionalChain([response, 'optionalAccess', _90 => _90.text]), () => ( "")));
4860
+ const wordCount = countWords(_nullishCoalesce(_optionalChain([response, 'optionalAccess', _91 => _91.text]), () => ( "")));
4407
4861
  return {
4408
4862
  withinWordBounds: response !== void 0 && wordCount >= data.minWords && wordCount <= data.maxWords,
4409
4863
  wordCount
4410
4864
  };
4411
4865
  }
4412
4866
  },
4413
- isAnswered: (response) => (_nullishCoalesce(_optionalChain([response, 'optionalAccess', _91 => _91.text, 'access', _92 => _92.trim, 'call', _93 => _93(), 'access', _94 => _94.length]), () => ( 0))) > 0,
4867
+ isAnswered: (response) => (_nullishCoalesce(_optionalChain([response, 'optionalAccess', _92 => _92.text, 'access', _93 => _93.trim, 'call', _94 => _94(), 'access', _95 => _95.length]), () => ( 0))) > 0,
4414
4868
  fieldPolicy: WRITTEN_RESPONSE_FIELD_POLICY,
4415
4869
  redactedSchema: RedactedWrittenResponseDataSchema,
4416
4870
  interop: {
@@ -4425,7 +4879,7 @@ var gapSelectType = defineActivityType({
4425
4879
  type: "gap-select",
4426
4880
  schema: GapSelectDataSchema,
4427
4881
  scoring: { kind: "sync", score: scoreGapSelect },
4428
- isAnswered: (response) => Object.values(_nullishCoalesce(_optionalChain([response, 'optionalAccess', _95 => _95.selections]), () => ( {}))).some((choiceId) => choiceId !== ""),
4882
+ isAnswered: (response) => Object.values(_nullishCoalesce(_optionalChain([response, 'optionalAccess', _96 => _96.selections]), () => ( {}))).some((choiceId) => choiceId !== ""),
4429
4883
  fieldPolicy: GAP_SELECT_FIELD_POLICY,
4430
4884
  redactedSchema: RedactedGapSelectDataSchema,
4431
4885
  interop: {
@@ -4456,7 +4910,7 @@ var dictationType = defineActivityType({
4456
4910
  // cut where the scorer cuts, so text past the cap never counts as an answer.
4457
4911
  isAnswered: (response) => normalizeDictationText(
4458
4912
  truncateCodePoints(
4459
- typeof _optionalChain([response, 'optionalAccess', _96 => _96.text]) === "string" ? response.text : "",
4913
+ typeof _optionalChain([response, 'optionalAccess', _97 => _97.text]) === "string" ? response.text : "",
4460
4914
  DICTATION_MAX_TEXT_LENGTH
4461
4915
  )
4462
4916
  ) !== "",
@@ -4482,7 +4936,7 @@ var READ_ALOUD_FIELD_POLICY = {
4482
4936
  feedback: FEEDBACK_FIELD_POLICY
4483
4937
  };
4484
4938
  function hasRecording(response) {
4485
- const recording = _optionalChain([response, 'optionalAccess', _97 => _97.recording]);
4939
+ const recording = _optionalChain([response, 'optionalAccess', _98 => _98.recording]);
4486
4940
  return typeof recording === "object" && recording !== null && typeof recording.key === "string" && recording.key !== "";
4487
4941
  }
4488
4942
  var readAloudType = defineActivityType({
@@ -4593,5 +5047,11 @@ registerActivityType(readAloudType);
4593
5047
 
4594
5048
 
4595
5049
 
4596
- exports.maxUnits = maxUnits; exports.MediaSchema = MediaSchema; exports.RedactedMediaSchema = RedactedMediaSchema; exports.PLACEHOLDER_RE = PLACEHOLDER_RE; exports.FillInTheBlanksDataSchema = FillInTheBlanksDataSchema; exports.withValidationScope = withValidationScope; exports.DICTATION_MAX_TRANSCRIPT_LENGTH = DICTATION_MAX_TRANSCRIPT_LENGTH; exports.DICTATION_MAX_TEXT_LENGTH = DICTATION_MAX_TEXT_LENGTH; exports.DICTATION_MAX_EQUIVALENCE_LENGTH = DICTATION_MAX_EQUIVALENCE_LENGTH; exports.DICTATION_MAX_EQUIVALENCES = DICTATION_MAX_EQUIVALENCES; exports.DICTATION_MAX_ACCEPTED_TRANSCRIPTS = DICTATION_MAX_ACCEPTED_TRANSCRIPTS; exports.codePointLength = codePointLength; exports.normalizeDictationText = normalizeDictationText; exports.dictationReferenceWords = dictationReferenceWords; exports.alignDictation = alignDictation; exports.STEP = STEP; exports.alignSequences = alignSequences; exports.ActivitySchemaError = ActivitySchemaError; exports.UnknownActivityTypeError = UnknownActivityTypeError; exports.RedactedScoringError = RedactedScoringError; exports.DeferredScoringError = DeferredScoringError; exports.DictationDataSchema = DictationDataSchema; exports.groupCaptionsField = groupCaptionsField; exports.GROUP_CAPTIONS_REVEAL_DICTATION = GROUP_CAPTIONS_REVEAL_DICTATION; exports.DRAFT_ISSUE_SEVERITY = DRAFT_ISSUE_SEVERITY; exports.issue = issue; exports.isRecord = isRecord; exports.refusesEmpty = refusesEmpty; exports.isUnset = isUnset; exports.isUnwritten = isUnwritten; exports.isMissingId = isMissingId; exports.checkMedia = checkMedia; exports.covers = covers; exports.pathKey = pathKey; exports.coveredPathsOf = coveredPathsOf; exports.MultipleChoiceOptionMediaSchema = MultipleChoiceOptionMediaSchema; exports.MultipleChoiceDataSchema = MultipleChoiceDataSchema; exports.READ_ALOUD_MAX_REFERENCE_LENGTH = READ_ALOUD_MAX_REFERENCE_LENGTH; exports.READ_ALOUD_MAX_SECONDS = READ_ALOUD_MAX_SECONDS; exports.READ_ALOUD_MAX_TAKES = READ_ALOUD_MAX_TAKES; exports.READ_ALOUD_MAX_DIMENSION_WEIGHT = READ_ALOUD_MAX_DIMENSION_WEIGHT; exports.SPEECH_ASSESSMENT_MAX_WORDS = SPEECH_ASSESSMENT_MAX_WORDS; exports.SPEECH_ASSESSMENT_MAX_TEXT_LENGTH = SPEECH_ASSESSMENT_MAX_TEXT_LENGTH; exports.CANONICAL_LOCALE_RE = CANONICAL_LOCALE_RE; exports.ReadAloudDataSchema = ReadAloudDataSchema; exports.countWords = countWords; exports.GapSelectDataSchema = GapSelectDataSchema; exports.isOwnSchema = isOwnSchema; exports.readSchema = readSchema; exports.standardJsonSchemaOf = standardJsonSchemaOf; exports.WrittenResponseDataSchema = WrittenResponseDataSchema; 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; exports.gapSelectType = gapSelectType; exports.dictationType = dictationType; exports.readAloudType = readAloudType;
4597
- //# sourceMappingURL=chunk-DSDXL3JO.cjs.map
5050
+
5051
+
5052
+
5053
+
5054
+
5055
+
5056
+ exports.maxUnits = maxUnits; exports.MediaSchema = MediaSchema; exports.RedactedMediaSchema = RedactedMediaSchema; exports.FillInTheBlanksDataSchema = FillInTheBlanksDataSchema; exports.placeholderOrder = placeholderOrder; exports.numberedPassage = numberedPassage; exports.fold = fold; exports.containsWords = containsWords; exports.revealsAnswer = revealsAnswer; exports.withValidationScope = withValidationScope; exports.DICTATION_MAX_TRANSCRIPT_LENGTH = DICTATION_MAX_TRANSCRIPT_LENGTH; exports.DICTATION_MAX_TEXT_LENGTH = DICTATION_MAX_TEXT_LENGTH; exports.DICTATION_MAX_EQUIVALENCE_LENGTH = DICTATION_MAX_EQUIVALENCE_LENGTH; exports.DICTATION_MAX_EQUIVALENCES = DICTATION_MAX_EQUIVALENCES; exports.DICTATION_MAX_ACCEPTED_TRANSCRIPTS = DICTATION_MAX_ACCEPTED_TRANSCRIPTS; exports.codePointLength = codePointLength; exports.normalizeDictationText = normalizeDictationText; exports.dictationReferenceWords = dictationReferenceWords; exports.alignDictation = alignDictation; exports.STEP = STEP; exports.alignSequences = alignSequences; exports.ActivitySchemaError = ActivitySchemaError; exports.UnknownActivityTypeError = UnknownActivityTypeError; exports.RedactedScoringError = RedactedScoringError; exports.DeferredScoringError = DeferredScoringError; exports.DictationDataSchema = DictationDataSchema; exports.groupCaptionsField = groupCaptionsField; exports.GROUP_CAPTIONS_REVEAL_DICTATION = GROUP_CAPTIONS_REVEAL_DICTATION; exports.DRAFT_ISSUE_SEVERITY = DRAFT_ISSUE_SEVERITY; exports.issue = issue; exports.isRecord = isRecord; exports.refusesEmpty = refusesEmpty; exports.isUnset = isUnset; exports.isUnwritten = isUnwritten; exports.isMissingId = isMissingId; exports.checkMedia = checkMedia; exports.covers = covers; exports.pathKey = pathKey; exports.coveredPathsOf = coveredPathsOf; exports.countWords = countWords; exports.levenshteinDistance = levenshteinDistance; exports.matchText = matchText; exports.normaliseFinding = normaliseFinding; exports.keyPositionFinding = keyPositionFinding; exports.MultipleChoiceOptionMediaSchema = MultipleChoiceOptionMediaSchema; exports.MultipleChoiceDataSchema = MultipleChoiceDataSchema; exports.READ_ALOUD_MAX_REFERENCE_LENGTH = READ_ALOUD_MAX_REFERENCE_LENGTH; exports.READ_ALOUD_MAX_SECONDS = READ_ALOUD_MAX_SECONDS; exports.READ_ALOUD_MAX_TAKES = READ_ALOUD_MAX_TAKES; exports.READ_ALOUD_MAX_DIMENSION_WEIGHT = READ_ALOUD_MAX_DIMENSION_WEIGHT; exports.SPEECH_ASSESSMENT_MAX_WORDS = SPEECH_ASSESSMENT_MAX_WORDS; exports.SPEECH_ASSESSMENT_MAX_TEXT_LENGTH = SPEECH_ASSESSMENT_MAX_TEXT_LENGTH; exports.CANONICAL_LOCALE_RE = CANONICAL_LOCALE_RE; exports.ReadAloudDataSchema = ReadAloudDataSchema; exports.GapSelectDataSchema = GapSelectDataSchema; exports.isOwnSchema = isOwnSchema; exports.readSchema = readSchema; exports.standardJsonSchemaOf = standardJsonSchemaOf; exports.WrittenResponseDataSchema = WrittenResponseDataSchema; 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; exports.gapSelectType = gapSelectType; exports.dictationType = dictationType; exports.readAloudType = readAloudType;
5057
+ //# sourceMappingURL=chunk-7THXD7AN.cjs.map