@intellectif/lk-core 1.0.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.
- package/CHANGELOG.md +30 -0
- package/README.md +2 -1
- package/dist/{activity-DgpBJeBc.d.cts → activity-D705CXng.d.cts} +439 -7
- package/dist/{activity-DgpBJeBc.d.ts → activity-D705CXng.d.ts} +439 -7
- package/dist/ai-check.cjs +300 -9
- package/dist/ai-check.cjs.map +1 -1
- package/dist/ai-check.d.cts +18 -3
- package/dist/ai-check.d.ts +18 -3
- package/dist/ai-check.js +295 -4
- package/dist/ai-check.js.map +1 -1
- package/dist/{chunk-TJJFKKH2.cjs → chunk-46BXIGLG.cjs} +4 -4
- package/dist/{chunk-TJJFKKH2.cjs.map → chunk-46BXIGLG.cjs.map} +1 -1
- package/dist/{chunk-VSH7ECQL.cjs → chunk-4YANYTFN.cjs} +43 -43
- package/dist/{chunk-VSH7ECQL.cjs.map → chunk-4YANYTFN.cjs.map} +1 -1
- package/dist/chunk-7KOWU4RO.js +2207 -0
- package/dist/chunk-7KOWU4RO.js.map +1 -0
- package/dist/{chunk-DSDXL3JO.cjs → chunk-7THXD7AN.cjs} +657 -197
- package/dist/chunk-7THXD7AN.cjs.map +1 -0
- package/dist/chunk-CYCTIXUN.cjs +118 -0
- package/dist/chunk-CYCTIXUN.cjs.map +1 -0
- package/dist/chunk-CYPA5BW5.js +118 -0
- package/dist/chunk-CYPA5BW5.js.map +1 -0
- package/dist/{chunk-SJVRUPJT.js → chunk-JTNDIBZO.js} +2 -2
- package/dist/{chunk-XS6U2ARZ.js → chunk-MV2N2WNS.js} +6 -112
- package/dist/chunk-MV2N2WNS.js.map +1 -0
- package/dist/chunk-MXI26H62.cjs +2207 -0
- package/dist/chunk-MXI26H62.cjs.map +1 -0
- package/dist/{chunk-FBKTION6.js → chunk-QS7B42ZR.js} +640 -180
- package/dist/chunk-QS7B42ZR.js.map +1 -0
- package/dist/{chunk-IARLJ5MW.js → chunk-QX2ERL72.js} +2 -2
- package/dist/{chunk-2JT2WUQ2.cjs → chunk-ZWXQSKCX.cjs} +14 -120
- package/dist/chunk-ZWXQSKCX.cjs.map +1 -0
- package/dist/{index-BVNeSCWC.d.ts → index-BChYtrEe.d.ts} +1 -1
- package/dist/{index-BlOLlwOi.d.cts → index-VXt_v3YI.d.cts} +1 -1
- package/dist/index.cjs +76 -593
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +308 -66
- package/dist/index.d.ts +308 -66
- package/dist/index.js +76 -593
- package/dist/index.js.map +1 -1
- package/dist/schemas.cjs +5 -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 +5 -3
- package/dist/scoring.cjs +3 -3
- package/dist/scoring.d.cts +242 -242
- package/dist/scoring.d.ts +242 -242
- 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-2JT2WUQ2.cjs.map +0 -1
- package/dist/chunk-DSDXL3JO.cjs.map +0 -1
- package/dist/chunk-FBKTION6.js.map +0 -1
- package/dist/chunk-RCZCP5OE.js +0 -729
- package/dist/chunk-RCZCP5OE.js.map +0 -1
- package/dist/chunk-W5KB3DNT.cjs +0 -729
- package/dist/chunk-W5KB3DNT.cjs.map +0 -1
- package/dist/chunk-XS6U2ARZ.js.map +0 -1
- /package/dist/{chunk-SJVRUPJT.js.map → chunk-JTNDIBZO.js.map} +0 -0
- /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,
|
|
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
|
|
664
|
-
const carried = CARRIED_MARKS_RE.exec(
|
|
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(
|
|
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(
|
|
921
|
-
const writtenStripped = stripForReveal(
|
|
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(
|
|
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
|
|
949
|
-
const working = workingText(text, compiled,
|
|
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(
|
|
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
|
|
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
|
-
|
|
1186
|
+
ordinal2 += 1;
|
|
1187
1187
|
const referenceWord = reference[referenceIndex];
|
|
1188
1188
|
if (step === STEP.missing) {
|
|
1189
1189
|
words.push({
|
|
1190
|
-
itemId: `w${
|
|
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${
|
|
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,
|
|
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 (
|
|
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,
|
|
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,
|
|
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 (
|
|
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
|
|
2850
|
-
const name = typeof gap.id === "string" && gap.id !== "" ? `"${gap.id}"` :
|
|
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 ${
|
|
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
|
|
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 ${
|
|
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 ${
|
|
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 ${
|
|
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,
|
|
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,
|
|
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 ${
|
|
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',
|
|
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',
|
|
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',
|
|
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
|
|
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 ${
|
|
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 ${
|
|
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',
|
|
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',
|
|
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',
|
|
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',
|
|
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',
|
|
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',
|
|
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',
|
|
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',
|
|
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',
|
|
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',
|
|
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',
|
|
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',
|
|
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',
|
|
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',
|
|
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',
|
|
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',
|
|
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
|
-
|
|
4597
|
-
|
|
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
|