@intellectif/lk-core 0.22.0 → 1.0.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 +15 -0
- package/README.md +5 -5
- package/dist/{activity-CYc7SD3d.d.cts → activity-DgpBJeBc.d.cts} +11 -15
- package/dist/{activity-CYc7SD3d.d.ts → activity-DgpBJeBc.d.ts} +11 -15
- package/dist/ai-check.cjs +8 -8
- package/dist/ai-check.d.cts +1 -1
- package/dist/ai-check.d.ts +1 -1
- package/dist/ai-check.js +3 -3
- package/dist/{chunk-2CV6NU32.cjs → chunk-2JT2WUQ2.cjs} +77 -39
- package/dist/chunk-2JT2WUQ2.cjs.map +1 -0
- package/dist/{chunk-D4TOPSHV.cjs → chunk-DSDXL3JO.cjs} +1216 -1105
- package/dist/chunk-DSDXL3JO.cjs.map +1 -0
- package/dist/{chunk-D3GP5MET.js → chunk-FBKTION6.js} +1093 -982
- package/dist/chunk-FBKTION6.js.map +1 -0
- package/dist/{chunk-Q7MV3IXJ.js → chunk-IARLJ5MW.js} +15 -16
- package/dist/chunk-IARLJ5MW.js.map +1 -0
- package/dist/{chunk-5UCR4UNR.js → chunk-RCZCP5OE.js} +12 -7
- package/dist/chunk-RCZCP5OE.js.map +1 -0
- package/dist/{chunk-X5DAXBJI.js → chunk-SJVRUPJT.js} +2 -2
- package/dist/{chunk-EOFWSGLU.cjs → chunk-TJJFKKH2.cjs} +4 -4
- package/dist/{chunk-EOFWSGLU.cjs.map → chunk-TJJFKKH2.cjs.map} +1 -1
- package/dist/{chunk-WQVZPIN7.cjs → chunk-VSH7ECQL.cjs} +44 -45
- package/dist/chunk-VSH7ECQL.cjs.map +1 -0
- package/dist/{chunk-OPMY2MNS.cjs → chunk-W5KB3DNT.cjs} +19 -14
- package/dist/chunk-W5KB3DNT.cjs.map +1 -0
- package/dist/{chunk-Z7QQZ53L.js → chunk-XS6U2ARZ.js} +70 -32
- package/dist/chunk-XS6U2ARZ.js.map +1 -0
- package/dist/index-BVNeSCWC.d.ts +548 -0
- package/dist/index-BlOLlwOi.d.cts +548 -0
- package/dist/index.cjs +129 -206
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +115 -15
- package/dist/index.d.ts +115 -15
- package/dist/index.js +26 -103
- package/dist/index.js.map +1 -1
- package/dist/schemas.cjs +3 -94
- package/dist/schemas.cjs.map +1 -1
- package/dist/schemas.d.cts +2 -3
- package/dist/schemas.d.ts +2 -3
- package/dist/schemas.js +6 -97
- package/dist/scoring.cjs +3 -3
- package/dist/scoring.d.cts +2 -2
- package/dist/scoring.d.ts +2 -2
- package/dist/scoring.js +2 -2
- package/dist/xapi.cjs +3 -3
- package/dist/xapi.d.cts +1 -1
- package/dist/xapi.d.ts +1 -1
- package/dist/xapi.js +2 -2
- package/package.json +4 -4
- package/vectors/README.md +32 -2
- package/vectors/scoring.json +1 -498
- package/vectors/validation.json +6742 -0
- package/vectors/validation.mjs +195 -0
- package/dist/chunk-2CV6NU32.cjs.map +0 -1
- package/dist/chunk-5UCR4UNR.js.map +0 -1
- package/dist/chunk-D3GP5MET.js.map +0 -1
- package/dist/chunk-D4TOPSHV.cjs.map +0 -1
- package/dist/chunk-OPMY2MNS.cjs.map +0 -1
- package/dist/chunk-Q7MV3IXJ.js.map +0 -1
- package/dist/chunk-WQVZPIN7.cjs.map +0 -1
- package/dist/chunk-Z7QQZ53L.js.map +0 -1
- package/dist/index-0Z3-nnAP.d.ts +0 -2303
- package/dist/index-CxxiNSSH.d.cts +0 -2303
- /package/dist/{chunk-X5DAXBJI.js.map → chunk-SJVRUPJT.js.map} +0 -0
|
@@ -1,19 +1,22 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }// src/schemas/
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }// src/schemas/media.ts
|
|
2
2
|
var _v4 = require('zod/v4');
|
|
3
|
-
var AiPermissionsSchema = _v4.z.looseObject({
|
|
4
|
-
explanations: _v4.z.boolean().optional(),
|
|
5
|
-
hints: _v4.z.boolean().optional()
|
|
6
|
-
});
|
|
7
3
|
|
|
8
|
-
// src/schemas/
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
4
|
+
// src/schemas/text-length.ts
|
|
5
|
+
function maxUnits(schema, max) {
|
|
6
|
+
return schema.check((payload) => {
|
|
7
|
+
if (payload.value.length > max) {
|
|
8
|
+
payload.issues.push({
|
|
9
|
+
code: "too_big",
|
|
10
|
+
origin: "string",
|
|
11
|
+
maximum: max,
|
|
12
|
+
inclusive: true,
|
|
13
|
+
input: payload.value
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
}).meta({ maxLength: max });
|
|
17
|
+
}
|
|
14
18
|
|
|
15
19
|
// src/schemas/media.ts
|
|
16
|
-
|
|
17
20
|
var MediaUrlSchema = _v4.z.union([
|
|
18
21
|
_v4.z.url().refine((value) => /^(https?|data|blob):/i.test(value), {
|
|
19
22
|
error: "Absolute media URLs must use the https:, http:, data:, or blob: scheme."
|
|
@@ -52,7 +55,9 @@ var MediaTrackSchema = _v4.z.strictObject({
|
|
|
52
55
|
srclang: _v4.z.string().regex(/^[A-Za-z]{2,3}(-[A-Za-z0-9]{2,8})*$/, {
|
|
53
56
|
error: 'srclang must be a BCP 47 language tag, such as "en", "es" or "pt-BR".'
|
|
54
57
|
}),
|
|
55
|
-
label: _v4.z.string()
|
|
58
|
+
label: maxUnits(_v4.z.string(), 60).refine((label) => label.trim().length > 0, {
|
|
59
|
+
error: "A track label must not be empty."
|
|
60
|
+
}),
|
|
56
61
|
default: _v4.z.boolean().optional()
|
|
57
62
|
});
|
|
58
63
|
var MAX_TRACKS = 12;
|
|
@@ -137,70 +142,6 @@ var RedactedMediaSchema = _v4.z.strictObject({
|
|
|
137
142
|
path: ["url"]
|
|
138
143
|
}).refine(posterFits, POSTER_FITS).refine(tracksFit, TRACKS_FIT).refine(tracksAreDistinct, TRACKS_DISTINCT);
|
|
139
144
|
|
|
140
|
-
// src/schemas/fill-in-the-blanks.ts
|
|
141
|
-
|
|
142
|
-
var PLACEHOLDER_RE = /\{\{\s*([^{}]+?)\s*\}\}/g;
|
|
143
|
-
var TextMatchPolicySchema = _v4.z.looseObject({
|
|
144
|
-
caseSensitive: _v4.z.boolean().optional(),
|
|
145
|
-
trim: _v4.z.boolean().optional(),
|
|
146
|
-
normalize: _v4.z.enum(["none", "NFC", "NFKC"]).optional(),
|
|
147
|
-
foldDiacritics: _v4.z.boolean().optional(),
|
|
148
|
-
collapseInnerWhitespace: _v4.z.boolean().optional(),
|
|
149
|
-
ignorePunctuation: _v4.z.boolean().optional(),
|
|
150
|
-
levenshtein: _v4.z.number().int().min(0).optional(),
|
|
151
|
-
locale: _v4.z.string().optional()
|
|
152
|
-
});
|
|
153
|
-
var BlankConfigSchema = _v4.z.looseObject({
|
|
154
|
-
id: _v4.z.string().min(1),
|
|
155
|
-
acceptedAnswers: _v4.z.array(
|
|
156
|
-
_v4.z.string().min(1).refine((answer) => answer.trim().length > 0, {
|
|
157
|
-
error: "Accepted answers must contain non-whitespace characters."
|
|
158
|
-
})
|
|
159
|
-
).min(1),
|
|
160
|
-
caseSensitive: _v4.z.boolean().optional(),
|
|
161
|
-
trimWhitespace: _v4.z.boolean().optional(),
|
|
162
|
-
match: TextMatchPolicySchema.optional(),
|
|
163
|
-
hint: _v4.z.string().optional(),
|
|
164
|
-
feedback: _v4.z.string().optional()
|
|
165
|
-
});
|
|
166
|
-
var FillInTheBlanksDataSchema = _v4.z.looseObject({
|
|
167
|
-
schemaVersion: _v4.z.literal("1.0"),
|
|
168
|
-
type: _v4.z.literal("fill-in-the-blanks"),
|
|
169
|
-
id: _v4.z.string().min(1),
|
|
170
|
-
title: _v4.z.string().min(1),
|
|
171
|
-
passage: _v4.z.string().min(1),
|
|
172
|
-
passageHtml: _v4.z.string().optional(),
|
|
173
|
-
blanks: _v4.z.array(BlankConfigSchema).min(1),
|
|
174
|
-
scoringStrategy: _v4.z.enum(["all-or-nothing", "partial"]),
|
|
175
|
-
media: MediaSchema.optional(),
|
|
176
|
-
feedback: FeedbackSchema.optional(),
|
|
177
|
-
passThreshold: _v4.z.number().min(0).max(1).optional(),
|
|
178
|
-
locale: _v4.z.string().optional(),
|
|
179
|
-
learningObjectives: _v4.z.array(_v4.z.string()).optional(),
|
|
180
|
-
difficultyLevel: _v4.z.literal([1, 2, 3, 4, 5]).optional(),
|
|
181
|
-
ai: AiPermissionsSchema.optional()
|
|
182
|
-
}).refine(
|
|
183
|
-
(data) => {
|
|
184
|
-
const placeholderCounts2 = /* @__PURE__ */ new Map();
|
|
185
|
-
for (const match of data.passage.matchAll(PLACEHOLDER_RE)) {
|
|
186
|
-
const id = match[1];
|
|
187
|
-
placeholderCounts2.set(id, (_nullishCoalesce(placeholderCounts2.get(id), () => ( 0))) + 1);
|
|
188
|
-
}
|
|
189
|
-
const blankIds = data.blanks.map((blank) => blank.id);
|
|
190
|
-
if (new Set(blankIds).size !== blankIds.length) {
|
|
191
|
-
return false;
|
|
192
|
-
}
|
|
193
|
-
if (placeholderCounts2.size !== blankIds.length) {
|
|
194
|
-
return false;
|
|
195
|
-
}
|
|
196
|
-
return blankIds.every((id) => placeholderCounts2.get(id) === 1);
|
|
197
|
-
},
|
|
198
|
-
{
|
|
199
|
-
error: "Each blank id must appear exactly once in blanks[] and have exactly one matching {{id}} placeholder in the passage, and vice versa.",
|
|
200
|
-
path: ["passage"]
|
|
201
|
-
}
|
|
202
|
-
);
|
|
203
|
-
|
|
204
145
|
// src/validation-scope.ts
|
|
205
146
|
var active = null;
|
|
206
147
|
function withValidationScope(run) {
|
|
@@ -1306,112 +1247,428 @@ var DeferredScoringError = class extends Error {
|
|
|
1306
1247
|
|
|
1307
1248
|
};
|
|
1308
1249
|
|
|
1309
|
-
// src/schemas/
|
|
1250
|
+
// src/schemas/multiple-choice.ts
|
|
1310
1251
|
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1252
|
+
|
|
1253
|
+
// src/schemas/ai.ts
|
|
1254
|
+
|
|
1255
|
+
var AiPermissionsSchema = _v4.z.looseObject({
|
|
1256
|
+
explanations: _v4.z.boolean().optional(),
|
|
1257
|
+
hints: _v4.z.boolean().optional()
|
|
1315
1258
|
});
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
error: `A rule rewrites at most ${DICTATION_MAX_EQUIVALENCE_LENGTH} characters.`,
|
|
1323
|
-
path: ["from"]
|
|
1324
|
-
}).refine((rule) => codePointLength(rule.to) <= DICTATION_MAX_EQUIVALENCE_LENGTH, {
|
|
1325
|
-
error: `A rewrite is at most ${DICTATION_MAX_EQUIVALENCE_LENGTH} characters.`,
|
|
1326
|
-
path: ["to"]
|
|
1259
|
+
|
|
1260
|
+
// src/schemas/feedback.ts
|
|
1261
|
+
|
|
1262
|
+
var FeedbackSchema = _v4.z.looseObject({
|
|
1263
|
+
correct: _v4.z.string().min(1).optional(),
|
|
1264
|
+
incorrect: _v4.z.string().min(1).optional()
|
|
1327
1265
|
});
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1266
|
+
|
|
1267
|
+
// src/schemas/multiple-choice.ts
|
|
1268
|
+
var MultipleChoiceOptionMediaSchema = _v4.z.looseObject({
|
|
1269
|
+
// No `embed`: an iframe swallows the click that selects the option, so the
|
|
1270
|
+
// learner could not choose it. No `video`: a native control bar inside the
|
|
1271
|
+
// option's label eats the same click.
|
|
1272
|
+
type: _v4.z.enum(["image", "audio"]),
|
|
1273
|
+
url: MediaUrlSchema,
|
|
1274
|
+
alt: _v4.z.string().min(1).optional(),
|
|
1275
|
+
captionsUrl: MediaUrlSchema.optional()
|
|
1276
|
+
}).refine((media) => media.type !== "image" || media.alt !== void 0 && media.alt !== "", {
|
|
1277
|
+
error: "An image option requires a non-empty `alt`.",
|
|
1278
|
+
path: ["alt"]
|
|
1279
|
+
}).refine((media) => !("playback" in media), {
|
|
1280
|
+
error: "An option carries no playback policy. maxPlays, seek and rate are enforced only on the media above the question.",
|
|
1281
|
+
path: ["playback"]
|
|
1332
1282
|
});
|
|
1333
|
-
var
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
}
|
|
1342
|
-
function measure(data, text) {
|
|
1343
|
-
return normalizerFor(data).measure(text);
|
|
1344
|
-
}
|
|
1345
|
-
function candidatesOf(data) {
|
|
1346
|
-
return [data.transcript, ..._nullishCoalesce(data.acceptedTranscripts, () => ( []))];
|
|
1347
|
-
}
|
|
1348
|
-
function isEmpty(measured) {
|
|
1349
|
-
return measured.normalized === "";
|
|
1350
|
-
}
|
|
1351
|
-
function checkEvenAfterIssues(check) {
|
|
1352
|
-
const guard = new _v4.z.core.$ZodCheck({ check: "custom", when: () => true });
|
|
1353
|
-
guard._zod.check = check;
|
|
1354
|
-
return guard;
|
|
1355
|
-
}
|
|
1356
|
-
var DictationDataShape = _v4.z.looseObject({
|
|
1283
|
+
var MultipleChoiceOptionSchema = _v4.z.looseObject({
|
|
1284
|
+
id: _v4.z.string().min(1),
|
|
1285
|
+
text: _v4.z.string().min(1),
|
|
1286
|
+
isCorrect: _v4.z.boolean(),
|
|
1287
|
+
feedback: _v4.z.string().optional(),
|
|
1288
|
+
media: MultipleChoiceOptionMediaSchema.optional()
|
|
1289
|
+
});
|
|
1290
|
+
var MultipleChoiceDataSchema = _v4.z.looseObject({
|
|
1357
1291
|
schemaVersion: _v4.z.literal("1.0"),
|
|
1358
|
-
type: _v4.z.literal("
|
|
1292
|
+
type: _v4.z.literal("multiple-choice"),
|
|
1359
1293
|
id: _v4.z.string().min(1),
|
|
1360
1294
|
title: _v4.z.string().min(1),
|
|
1361
|
-
|
|
1362
|
-
|
|
1295
|
+
question: _v4.z.string().min(1),
|
|
1296
|
+
questionHtml: _v4.z.string().optional(),
|
|
1297
|
+
mode: _v4.z.enum(["single", "multi"]),
|
|
1298
|
+
options: _v4.z.array(MultipleChoiceOptionSchema).min(2).max(26),
|
|
1299
|
+
scoringStrategy: _v4.z.enum(["all-or-nothing", "partial"]),
|
|
1363
1300
|
media: MediaSchema.optional(),
|
|
1364
|
-
slowMedia: DictationSlowMediaSchema.optional(),
|
|
1365
|
-
hints: _v4.z.looseObject({ mode: _v4.z.literal("progressive-words") }).optional(),
|
|
1366
|
-
tolerance: DictationToleranceSchema.optional(),
|
|
1367
1301
|
feedback: FeedbackSchema.optional(),
|
|
1368
1302
|
passThreshold: _v4.z.number().min(0).max(1).optional(),
|
|
1303
|
+
shuffle: _v4.z.boolean().optional(),
|
|
1369
1304
|
locale: _v4.z.string().optional(),
|
|
1370
1305
|
learningObjectives: _v4.z.array(_v4.z.string()).optional(),
|
|
1371
1306
|
difficultyLevel: _v4.z.literal([1, 2, 3, 4, 5]).optional(),
|
|
1372
1307
|
ai: AiPermissionsSchema.optional()
|
|
1308
|
+
}).refine((data) => data.options.some((option) => option.isCorrect), {
|
|
1309
|
+
error: "At least one option must be marked correct.",
|
|
1310
|
+
path: ["options"]
|
|
1311
|
+
}).refine(
|
|
1312
|
+
(data) => data.mode !== "single" || data.options.filter((option) => option.isCorrect).length === 1,
|
|
1313
|
+
{
|
|
1314
|
+
error: 'Single-select activities (mode: "single") must have exactly one correct option.',
|
|
1315
|
+
path: ["options"]
|
|
1316
|
+
}
|
|
1317
|
+
).refine((data) => new Set(data.options.map((option) => option.id)).size === data.options.length, {
|
|
1318
|
+
error: "Option ids must be unique within the activity.",
|
|
1319
|
+
path: ["options"]
|
|
1373
1320
|
});
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1321
|
+
|
|
1322
|
+
// src/scoring/speech/limits.ts
|
|
1323
|
+
var READ_ALOUD_MAX_REFERENCE_LENGTH = DICTATION_MAX_TRANSCRIPT_LENGTH;
|
|
1324
|
+
var READ_ALOUD_MAX_SECONDS = 300;
|
|
1325
|
+
var READ_ALOUD_MAX_TAKES = 20;
|
|
1326
|
+
var READ_ALOUD_MAX_DIMENSION_WEIGHT = 1e3;
|
|
1327
|
+
var SPEECH_ASSESSMENT_MAX_WORDS = 1e3;
|
|
1328
|
+
var SPEECH_ASSESSMENT_MAX_TEXT_LENGTH = DICTATION_MAX_TEXT_LENGTH;
|
|
1329
|
+
|
|
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
|
+
// src/schemas/read-schema.ts
|
|
1343
|
+
var OWN_SCHEMAS = /* @__PURE__ */ new WeakSet();
|
|
1344
|
+
function ownSchemas(...schemas) {
|
|
1345
|
+
for (const schema of schemas) {
|
|
1346
|
+
OWN_SCHEMAS.add(schema);
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
function isOwnSchema(schema) {
|
|
1350
|
+
return OWN_SCHEMAS.has(schema);
|
|
1351
|
+
}
|
|
1352
|
+
var GENERIC_CODE = "invalid";
|
|
1353
|
+
function valueAt(root, path) {
|
|
1354
|
+
let node = root;
|
|
1355
|
+
for (const segment of path) {
|
|
1356
|
+
if (node === null || typeof node !== "object") {
|
|
1357
|
+
return void 0;
|
|
1378
1358
|
}
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1359
|
+
node = node[segment];
|
|
1360
|
+
}
|
|
1361
|
+
return node;
|
|
1362
|
+
}
|
|
1363
|
+
var isThenable = (value) => typeof _optionalChain([value, 'optionalAccess', _33 => _33.then]) === "function";
|
|
1364
|
+
function isZodSchema(schema) {
|
|
1365
|
+
return schema["~standard"].vendor === "zod" && typeof schema.safeParse === "function";
|
|
1366
|
+
}
|
|
1367
|
+
function readSchema(schema, value, subject, options) {
|
|
1368
|
+
if (isZodSchema(schema)) {
|
|
1369
|
+
const parsed = schema.safeParse(
|
|
1370
|
+
value,
|
|
1371
|
+
_optionalChain([options, 'optionalAccess', _34 => _34.reportInput]) === true ? { reportInput: true } : void 0
|
|
1372
|
+
);
|
|
1373
|
+
if (parsed.success) {
|
|
1374
|
+
return { success: true, data: parsed.data };
|
|
1375
|
+
}
|
|
1376
|
+
return {
|
|
1377
|
+
success: false,
|
|
1378
|
+
issues: parsed.error.issues.map((issue2) => ({
|
|
1379
|
+
path: issue2.path,
|
|
1380
|
+
message: issue2.message,
|
|
1381
|
+
code: issue2.code,
|
|
1382
|
+
..."input" in issue2 ? { input: issue2.input } : {}
|
|
1383
|
+
}))
|
|
1383
1384
|
};
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1385
|
+
}
|
|
1386
|
+
const result = schema["~standard"].validate(value);
|
|
1387
|
+
if (isThenable(result)) {
|
|
1388
|
+
result.then(void 0, () => {
|
|
1389
|
+
});
|
|
1390
|
+
throw new TypeError(
|
|
1391
|
+
`${subject} has a schema whose validation returned a Promise: it validates asynchronously, or its library reported an error that way. lk-core validates synchronously: register a schema whose validation returns its result.`
|
|
1392
|
+
);
|
|
1393
|
+
}
|
|
1394
|
+
const settled = result;
|
|
1395
|
+
if (settled.issues === void 0) {
|
|
1396
|
+
return { success: true, data: settled.value };
|
|
1397
|
+
}
|
|
1398
|
+
return {
|
|
1399
|
+
success: false,
|
|
1400
|
+
issues: settled.issues.map((issue2) => {
|
|
1401
|
+
const path = (_nullishCoalesce(issue2.path, () => ( []))).map(
|
|
1402
|
+
(segment) => typeof segment === "object" && segment !== null ? segment.key : segment
|
|
1403
|
+
);
|
|
1404
|
+
const code = issue2.code;
|
|
1405
|
+
return {
|
|
1406
|
+
path,
|
|
1407
|
+
message: issue2.message,
|
|
1408
|
+
code: typeof code === "string" && code !== "" ? code : GENERIC_CODE,
|
|
1409
|
+
input: valueAt(value, path)
|
|
1410
|
+
};
|
|
1411
|
+
})
|
|
1412
|
+
};
|
|
1413
|
+
}
|
|
1414
|
+
function isStandardSchema(value) {
|
|
1415
|
+
const props = _optionalChain([value, 'optionalAccess', _35 => _35["~standard"]]);
|
|
1416
|
+
return _optionalChain([props, 'optionalAccess', _36 => _36.version]) === 1 && typeof props.validate === "function";
|
|
1417
|
+
}
|
|
1418
|
+
function standardJsonSchemaOf(schema) {
|
|
1419
|
+
const converter = schema["~standard"].jsonSchema;
|
|
1420
|
+
if (typeof _optionalChain([converter, 'optionalAccess', _37 => _37.output]) !== "function") {
|
|
1421
|
+
return void 0;
|
|
1422
|
+
}
|
|
1423
|
+
const generated = converter.output({ target: "draft-07" });
|
|
1424
|
+
return generated !== null && typeof generated === "object" && !Array.isArray(generated) ? generated : void 0;
|
|
1425
|
+
}
|
|
1426
|
+
|
|
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
|
+
// src/registry/registry.ts
|
|
1511
|
+
var registry = /* @__PURE__ */ new Map();
|
|
1512
|
+
function defineActivityType(descriptor) {
|
|
1513
|
+
return descriptor;
|
|
1514
|
+
}
|
|
1515
|
+
function registerActivityType(descriptor) {
|
|
1516
|
+
if (descriptor.type === "item-group") {
|
|
1517
|
+
throw new Error(
|
|
1518
|
+
`"item-group" is reserved for the SDK's item-group container (see ItemGroup) and cannot be registered as an activity type.`
|
|
1519
|
+
);
|
|
1520
|
+
}
|
|
1521
|
+
if (!isStandardSchema(descriptor.schema)) {
|
|
1522
|
+
throw new TypeError(
|
|
1523
|
+
`Activity type "${descriptor.type}": \`schema\` must implement Standard Schema v1 (https://standardschema.dev), as a zod 4, valibot or ArkType schema does.`
|
|
1524
|
+
);
|
|
1525
|
+
}
|
|
1526
|
+
if (descriptor.redactedSchema !== void 0 && !isStandardSchema(descriptor.redactedSchema)) {
|
|
1527
|
+
throw new TypeError(
|
|
1528
|
+
`Activity type "${descriptor.type}": \`redactedSchema\` must implement Standard Schema v1 (https://standardschema.dev), as a zod 4, valibot or ArkType schema does.`
|
|
1529
|
+
);
|
|
1530
|
+
}
|
|
1531
|
+
if (descriptor.jsonSchema !== void 0 && (descriptor.jsonSchema === null || typeof descriptor.jsonSchema !== "object" || Array.isArray(descriptor.jsonSchema))) {
|
|
1532
|
+
throw new TypeError(
|
|
1533
|
+
`Activity type "${descriptor.type}": \`jsonSchema\` must be a JSON Schema object.`
|
|
1534
|
+
);
|
|
1535
|
+
}
|
|
1536
|
+
const existing = registry.get(descriptor.type);
|
|
1537
|
+
if (existing !== void 0) {
|
|
1538
|
+
if (existing === descriptor) {
|
|
1539
|
+
return;
|
|
1540
|
+
}
|
|
1541
|
+
throw new Error(
|
|
1542
|
+
`Activity type "${descriptor.type}" is already registered. Registering a different descriptor for an existing type is not allowed.`
|
|
1543
|
+
);
|
|
1544
|
+
}
|
|
1545
|
+
registry.set(descriptor.type, descriptor);
|
|
1546
|
+
}
|
|
1547
|
+
function getActivityTypeDescriptor(type) {
|
|
1548
|
+
return registry.get(type);
|
|
1549
|
+
}
|
|
1550
|
+
function registeredActivityTypes() {
|
|
1551
|
+
return [...registry.keys()];
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
// src/schemas/dictation.ts
|
|
1555
|
+
|
|
1556
|
+
|
|
1557
|
+
// src/schemas/after-issues.ts
|
|
1558
|
+
|
|
1559
|
+
function checkEvenAfterIssues(check) {
|
|
1560
|
+
const guard = new _v4.z.core.$ZodCheck({ check: "custom", when: () => true });
|
|
1561
|
+
guard._zod.check = check;
|
|
1562
|
+
return guard;
|
|
1563
|
+
}
|
|
1564
|
+
function parsedFields(payload) {
|
|
1565
|
+
const refused = new Set(payload.issues.map((issue2) => String(_optionalChain([issue2, 'access', _38 => _38.path, 'optionalAccess', _39 => _39[0]]))));
|
|
1566
|
+
const data = { ...payload.value };
|
|
1567
|
+
for (const key of refused) {
|
|
1568
|
+
delete data[key];
|
|
1569
|
+
}
|
|
1570
|
+
return { data, refused };
|
|
1571
|
+
}
|
|
1572
|
+
|
|
1573
|
+
// src/schemas/dictation.ts
|
|
1574
|
+
var DictationSlowMediaSchema = _v4.z.looseObject({
|
|
1575
|
+
type: _v4.z.literal("audio"),
|
|
1576
|
+
url: MediaUrlSchema,
|
|
1577
|
+
alt: _v4.z.string().min(1).optional()
|
|
1578
|
+
});
|
|
1579
|
+
var RuleTextSchema = _v4.z.string().min(1).meta({ maxLength: DICTATION_MAX_EQUIVALENCE_LENGTH });
|
|
1580
|
+
var TranscriptTextSchema = _v4.z.string().min(1).meta({ maxLength: DICTATION_MAX_TRANSCRIPT_LENGTH });
|
|
1581
|
+
var DictationEquivalenceSchema = _v4.z.looseObject({
|
|
1582
|
+
from: RuleTextSchema,
|
|
1583
|
+
to: RuleTextSchema
|
|
1584
|
+
}).refine((rule) => codePointLength(rule.from) <= DICTATION_MAX_EQUIVALENCE_LENGTH, {
|
|
1585
|
+
error: `A rule rewrites at most ${DICTATION_MAX_EQUIVALENCE_LENGTH} characters.`,
|
|
1586
|
+
path: ["from"]
|
|
1587
|
+
}).refine((rule) => codePointLength(rule.to) <= DICTATION_MAX_EQUIVALENCE_LENGTH, {
|
|
1588
|
+
error: `A rewrite is at most ${DICTATION_MAX_EQUIVALENCE_LENGTH} characters.`,
|
|
1589
|
+
path: ["to"]
|
|
1590
|
+
});
|
|
1591
|
+
var DictationToleranceSchema = _v4.z.looseObject({
|
|
1592
|
+
equivalences: _v4.z.array(DictationEquivalenceSchema).max(DICTATION_MAX_EQUIVALENCES, {
|
|
1593
|
+
error: `A dictation carries at most ${DICTATION_MAX_EQUIVALENCES} equivalence rules.`
|
|
1594
|
+
}).optional()
|
|
1595
|
+
});
|
|
1596
|
+
var normalizers = /* @__PURE__ */ new WeakMap();
|
|
1597
|
+
function normalizerFor(data) {
|
|
1598
|
+
let normalizer = normalizers.get(data);
|
|
1599
|
+
if (normalizer === void 0) {
|
|
1600
|
+
normalizer = dictationNormalizer(data.tolerance);
|
|
1601
|
+
normalizers.set(data, normalizer);
|
|
1602
|
+
}
|
|
1603
|
+
return normalizer;
|
|
1604
|
+
}
|
|
1605
|
+
function measure(data, text) {
|
|
1606
|
+
return normalizerFor(data).measure(text);
|
|
1607
|
+
}
|
|
1608
|
+
function candidatesOf(data) {
|
|
1609
|
+
return [data.transcript, ..._nullishCoalesce(data.acceptedTranscripts, () => ( []))];
|
|
1610
|
+
}
|
|
1611
|
+
function isEmpty(measured) {
|
|
1612
|
+
return measured.normalized === "";
|
|
1613
|
+
}
|
|
1614
|
+
var DictationDataShape = _v4.z.looseObject({
|
|
1615
|
+
schemaVersion: _v4.z.literal("1.0"),
|
|
1616
|
+
type: _v4.z.literal("dictation"),
|
|
1617
|
+
id: _v4.z.string().min(1),
|
|
1618
|
+
title: _v4.z.string().min(1),
|
|
1619
|
+
transcript: TranscriptTextSchema,
|
|
1620
|
+
acceptedTranscripts: _v4.z.array(TranscriptTextSchema).max(DICTATION_MAX_ACCEPTED_TRANSCRIPTS).optional(),
|
|
1621
|
+
media: MediaSchema.optional(),
|
|
1622
|
+
slowMedia: DictationSlowMediaSchema.optional(),
|
|
1623
|
+
hints: _v4.z.looseObject({ mode: _v4.z.literal("progressive-words") }).optional(),
|
|
1624
|
+
tolerance: DictationToleranceSchema.optional(),
|
|
1625
|
+
feedback: FeedbackSchema.optional(),
|
|
1626
|
+
passThreshold: _v4.z.number().min(0).max(1).optional(),
|
|
1627
|
+
locale: _v4.z.string().optional(),
|
|
1628
|
+
learningObjectives: _v4.z.array(_v4.z.string()).optional(),
|
|
1629
|
+
difficultyLevel: _v4.z.literal([1, 2, 3, 4, 5]).optional(),
|
|
1630
|
+
ai: AiPermissionsSchema.optional()
|
|
1631
|
+
});
|
|
1632
|
+
var DictationDataSchema = DictationDataShape.check(
|
|
1633
|
+
checkEvenAfterIssues((ctx) => {
|
|
1634
|
+
if (ctx.issues.some((issue2) => (_nullishCoalesce(_optionalChain([issue2, 'access', _40 => _40.path, 'optionalAccess', _41 => _41.length]), () => ( 0))) === 0)) {
|
|
1635
|
+
return;
|
|
1636
|
+
}
|
|
1637
|
+
const { data, refused } = parsedFields(ctx);
|
|
1638
|
+
const report = (path, input, message) => {
|
|
1639
|
+
ctx.issues.push({ code: "custom", input, message, path: [...path] });
|
|
1640
|
+
};
|
|
1641
|
+
const rulesParse = !refused.has("tolerance");
|
|
1642
|
+
const transcriptParses = !refused.has("transcript");
|
|
1643
|
+
if (rulesParse && transcriptParses) {
|
|
1644
|
+
const transcript = measure(data, data.transcript);
|
|
1645
|
+
if (isEmpty(transcript)) {
|
|
1646
|
+
report(
|
|
1647
|
+
["transcript"],
|
|
1648
|
+
data.transcript,
|
|
1649
|
+
"The transcript must contain something to type: after ignoring case, punctuation and spacing nothing is left."
|
|
1650
|
+
);
|
|
1651
|
+
}
|
|
1652
|
+
if (transcript.tooLong) {
|
|
1653
|
+
report(
|
|
1654
|
+
["transcript"],
|
|
1655
|
+
data.transcript,
|
|
1656
|
+
`A transcript is at most ${DICTATION_MAX_TRANSCRIPT_LENGTH} characters, before and after its equivalences are applied, and its equivalences may not grow it past ${WORKING_LENGTH} characters on the way.`
|
|
1657
|
+
);
|
|
1658
|
+
}
|
|
1659
|
+
if (!refused.has("acceptedTranscripts")) {
|
|
1660
|
+
const accepted = _nullishCoalesce(data.acceptedTranscripts, () => ( []));
|
|
1661
|
+
if (accepted.some((entry) => {
|
|
1662
|
+
const measured = measure(data, entry);
|
|
1663
|
+
return measured.tooLong || isEmpty(measured);
|
|
1664
|
+
})) {
|
|
1665
|
+
report(
|
|
1666
|
+
["acceptedTranscripts"],
|
|
1667
|
+
data.acceptedTranscripts,
|
|
1668
|
+
`An accepted transcript must contain something to type and be at most ${DICTATION_MAX_TRANSCRIPT_LENGTH} characters, before and after its equivalences are applied, and its equivalences may not grow it past ${WORKING_LENGTH} characters on the way.`
|
|
1669
|
+
);
|
|
1670
|
+
}
|
|
1671
|
+
const normalized = candidatesOf(data).map((entry) => measure(data, entry).normalized).filter((entry) => entry !== null);
|
|
1415
1672
|
if (new Set(normalized).size !== normalized.length) {
|
|
1416
1673
|
report(
|
|
1417
1674
|
["acceptedTranscripts"],
|
|
@@ -1430,7 +1687,7 @@ var DictationDataSchema = DictationDataShape.check(
|
|
|
1430
1687
|
"A dictation is dictated: its recording must be audio."
|
|
1431
1688
|
);
|
|
1432
1689
|
}
|
|
1433
|
-
if (slowParses && _optionalChain([data, 'access',
|
|
1690
|
+
if (slowParses && _optionalChain([data, 'access', _42 => _42.slowMedia, 'optionalAccess', _43 => _43.playback]) !== void 0) {
|
|
1434
1691
|
report(
|
|
1435
1692
|
["slowMedia", "playback"],
|
|
1436
1693
|
data.slowMedia.playback,
|
|
@@ -1438,14 +1695,14 @@ var DictationDataSchema = DictationDataShape.check(
|
|
|
1438
1695
|
);
|
|
1439
1696
|
}
|
|
1440
1697
|
if (mediaParses && slowParses && data.slowMedia !== void 0) {
|
|
1441
|
-
if (_optionalChain([data, 'access',
|
|
1698
|
+
if (_optionalChain([data, 'access', _44 => _44.media, 'optionalAccess', _45 => _45.playback, 'optionalAccess', _46 => _46.maxPlays]) !== void 0) {
|
|
1442
1699
|
report(
|
|
1443
1700
|
["slowMedia"],
|
|
1444
1701
|
data.slowMedia,
|
|
1445
1702
|
"A play budget on `media` cannot coexist with an unbudgeted slow recording of the same content. Drop `slowMedia`, or drop `maxPlays` (a budgeted recording can still offer the transport's 0.75\xD7 speed when `rate` is `allow`)."
|
|
1446
1703
|
);
|
|
1447
1704
|
}
|
|
1448
|
-
if (typeof data.slowMedia.url === "string" && typeof _optionalChain([data, 'access',
|
|
1705
|
+
if (typeof data.slowMedia.url === "string" && typeof _optionalChain([data, 'access', _47 => _47.media, 'optionalAccess', _48 => _48.url]) === "string" && data.slowMedia.url === data.media.url) {
|
|
1449
1706
|
report(
|
|
1450
1707
|
["slowMedia", "url"],
|
|
1451
1708
|
data.slowMedia.url,
|
|
@@ -1460,12 +1717,12 @@ var DictationDataSchema = DictationDataShape.check(
|
|
|
1460
1717
|
);
|
|
1461
1718
|
}
|
|
1462
1719
|
}
|
|
1463
|
-
if (rulesParse && !(_nullishCoalesce(_optionalChain([data, 'access',
|
|
1720
|
+
if (rulesParse && !(_nullishCoalesce(_optionalChain([data, 'access', _49 => _49.tolerance, 'optionalAccess', _50 => _50.equivalences]), () => ( []))).every(
|
|
1464
1721
|
(rule) => preStripNormalize(rule.from) !== "" && normalizeDictationText(rule.to) !== ""
|
|
1465
1722
|
)) {
|
|
1466
1723
|
report(
|
|
1467
1724
|
["tolerance", "equivalences"],
|
|
1468
|
-
_optionalChain([data, 'access',
|
|
1725
|
+
_optionalChain([data, 'access', _51 => _51.tolerance, 'optionalAccess', _52 => _52.equivalences]),
|
|
1469
1726
|
"An equivalence must name a word, phrase or symbol to rewrite, and rewrite it to something that survives normalisation (not only punctuation or spaces)."
|
|
1470
1727
|
);
|
|
1471
1728
|
}
|
|
@@ -1474,7 +1731,7 @@ var DictationDataSchema = DictationDataShape.check(
|
|
|
1474
1731
|
for (const captions of captionFieldsOf(recording)) {
|
|
1475
1732
|
report(
|
|
1476
1733
|
[field, captions],
|
|
1477
|
-
_optionalChain([recording, 'optionalAccess',
|
|
1734
|
+
_optionalChain([recording, 'optionalAccess', _53 => _53[captions]]),
|
|
1478
1735
|
"A dictation recording cannot carry captions: the captions are the answer. Offer an accessible alternative as a different item."
|
|
1479
1736
|
);
|
|
1480
1737
|
}
|
|
@@ -1486,8 +1743,8 @@ var DictationDataSchema = DictationDataShape.check(
|
|
|
1486
1743
|
const candidates = candidatesOf(data).map((entry) => normalizer.measure(entry));
|
|
1487
1744
|
const shown = [
|
|
1488
1745
|
[["title"], refused.has("title") ? void 0 : data.title],
|
|
1489
|
-
[["media", "alt"], _optionalChain([data, 'access',
|
|
1490
|
-
[["slowMedia", "alt"], _optionalChain([data, 'access',
|
|
1746
|
+
[["media", "alt"], _optionalChain([data, 'access', _54 => _54.media, 'optionalAccess', _55 => _55.alt])],
|
|
1747
|
+
[["slowMedia", "alt"], _optionalChain([data, 'access', _56 => _56.slowMedia, 'optionalAccess', _57 => _57.alt])]
|
|
1491
1748
|
];
|
|
1492
1749
|
for (const [path, value] of shown) {
|
|
1493
1750
|
if (value === void 0) {
|
|
@@ -1539,671 +1796,6 @@ function groupCaptionsField(group, written) {
|
|
|
1539
1796
|
}
|
|
1540
1797
|
var GROUP_CAPTIONS_REVEAL_DICTATION = "The stimulus recording carries captions, and a dictation in this group plays that recording: the captions are the answer. Remove the captions, or give the dictation its own recording.";
|
|
1541
1798
|
|
|
1542
|
-
// src/schemas/multiple-choice.ts
|
|
1543
|
-
|
|
1544
|
-
var MultipleChoiceOptionMediaSchema = _v4.z.looseObject({
|
|
1545
|
-
// No `embed`: an iframe swallows the click that selects the option, so the
|
|
1546
|
-
// learner could not choose it. No `video`: a native control bar inside the
|
|
1547
|
-
// option's label eats the same click.
|
|
1548
|
-
type: _v4.z.enum(["image", "audio"]),
|
|
1549
|
-
url: MediaUrlSchema,
|
|
1550
|
-
alt: _v4.z.string().min(1).optional(),
|
|
1551
|
-
captionsUrl: MediaUrlSchema.optional()
|
|
1552
|
-
}).refine((media) => media.type !== "image" || media.alt !== void 0 && media.alt !== "", {
|
|
1553
|
-
error: "An image option requires a non-empty `alt`.",
|
|
1554
|
-
path: ["alt"]
|
|
1555
|
-
}).refine((media) => !("playback" in media), {
|
|
1556
|
-
error: "An option carries no playback policy. maxPlays, seek and rate are enforced only on the media above the question.",
|
|
1557
|
-
path: ["playback"]
|
|
1558
|
-
});
|
|
1559
|
-
var MultipleChoiceOptionSchema = _v4.z.looseObject({
|
|
1560
|
-
id: _v4.z.string().min(1),
|
|
1561
|
-
text: _v4.z.string().min(1),
|
|
1562
|
-
isCorrect: _v4.z.boolean(),
|
|
1563
|
-
feedback: _v4.z.string().optional(),
|
|
1564
|
-
media: MultipleChoiceOptionMediaSchema.optional()
|
|
1565
|
-
});
|
|
1566
|
-
var MultipleChoiceDataSchema = _v4.z.looseObject({
|
|
1567
|
-
schemaVersion: _v4.z.literal("1.0"),
|
|
1568
|
-
type: _v4.z.literal("multiple-choice"),
|
|
1569
|
-
id: _v4.z.string().min(1),
|
|
1570
|
-
title: _v4.z.string().min(1),
|
|
1571
|
-
question: _v4.z.string().min(1),
|
|
1572
|
-
questionHtml: _v4.z.string().optional(),
|
|
1573
|
-
mode: _v4.z.enum(["single", "multi"]),
|
|
1574
|
-
options: _v4.z.array(MultipleChoiceOptionSchema).min(2).max(26),
|
|
1575
|
-
scoringStrategy: _v4.z.enum(["all-or-nothing", "partial"]),
|
|
1576
|
-
media: MediaSchema.optional(),
|
|
1577
|
-
feedback: FeedbackSchema.optional(),
|
|
1578
|
-
passThreshold: _v4.z.number().min(0).max(1).optional(),
|
|
1579
|
-
shuffle: _v4.z.boolean().optional(),
|
|
1580
|
-
locale: _v4.z.string().optional(),
|
|
1581
|
-
learningObjectives: _v4.z.array(_v4.z.string()).optional(),
|
|
1582
|
-
difficultyLevel: _v4.z.literal([1, 2, 3, 4, 5]).optional(),
|
|
1583
|
-
ai: AiPermissionsSchema.optional()
|
|
1584
|
-
}).refine((data) => data.options.some((option) => option.isCorrect), {
|
|
1585
|
-
error: "At least one option must be marked correct.",
|
|
1586
|
-
path: ["options"]
|
|
1587
|
-
}).refine(
|
|
1588
|
-
(data) => data.mode !== "single" || data.options.filter((option) => option.isCorrect).length === 1,
|
|
1589
|
-
{
|
|
1590
|
-
error: 'Single-select activities (mode: "single") must have exactly one correct option.',
|
|
1591
|
-
path: ["options"]
|
|
1592
|
-
}
|
|
1593
|
-
).refine((data) => new Set(data.options.map((option) => option.id)).size === data.options.length, {
|
|
1594
|
-
error: "Option ids must be unique within the activity.",
|
|
1595
|
-
path: ["options"]
|
|
1596
|
-
});
|
|
1597
|
-
|
|
1598
|
-
// src/scoring/speech/limits.ts
|
|
1599
|
-
var READ_ALOUD_MAX_REFERENCE_LENGTH = DICTATION_MAX_TRANSCRIPT_LENGTH;
|
|
1600
|
-
var READ_ALOUD_MAX_SECONDS = 300;
|
|
1601
|
-
var READ_ALOUD_MAX_TAKES = 20;
|
|
1602
|
-
var READ_ALOUD_MAX_DIMENSION_WEIGHT = 1e3;
|
|
1603
|
-
var SPEECH_ASSESSMENT_MAX_WORDS = 1e3;
|
|
1604
|
-
var SPEECH_ASSESSMENT_MAX_TEXT_LENGTH = DICTATION_MAX_TEXT_LENGTH;
|
|
1605
|
-
|
|
1606
|
-
// src/schemas/read-aloud.ts
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
// src/scoring/speech/locale.ts
|
|
1610
|
-
var CANONICAL_LOCALE_RE = /^[a-z]{2,3}(?:-[A-Z][a-z]{3})?-(?:[A-Z]{2}|[0-9]{3})$/;
|
|
1611
|
-
|
|
1612
|
-
// src/schemas/read-aloud.ts
|
|
1613
|
-
var READ_ALOUD_DIMENSIONS = ["accuracy", "fluency", "completeness", "prosody"];
|
|
1614
|
-
var READ_ALOUD_MAX_DIMENSIONS = READ_ALOUD_DIMENSIONS.length;
|
|
1615
|
-
var ReadAloudSlowMediaSchema = _v4.z.strictObject({
|
|
1616
|
-
type: _v4.z.literal("audio"),
|
|
1617
|
-
url: MediaUrlSchema,
|
|
1618
|
-
alt: _v4.z.string().min(1).optional()
|
|
1619
|
-
});
|
|
1620
|
-
var ReferenceTextSchema = _v4.z.string().min(1).refine((text) => text.trim() !== "", {
|
|
1621
|
-
error: "The text to read aloud must contain something to read."
|
|
1622
|
-
}).meta({ maxLength: READ_ALOUD_MAX_REFERENCE_LENGTH });
|
|
1623
|
-
var RecordingBoundsSchema = _v4.z.looseObject({
|
|
1624
|
-
maxSeconds: _v4.z.number().gt(0).max(READ_ALOUD_MAX_SECONDS),
|
|
1625
|
-
minSeconds: _v4.z.number().min(0).optional(),
|
|
1626
|
-
maxTakes: _v4.z.number().int().min(1).max(READ_ALOUD_MAX_TAKES).optional()
|
|
1627
|
-
});
|
|
1628
|
-
var ReadAloudDimensionWeightSchema = _v4.z.looseObject({
|
|
1629
|
-
name: _v4.z.enum(READ_ALOUD_DIMENSIONS),
|
|
1630
|
-
weight: _v4.z.number().min(0).max(READ_ALOUD_MAX_DIMENSION_WEIGHT)
|
|
1631
|
-
});
|
|
1632
|
-
var ReadAloudScoringSchema = _v4.z.looseObject({
|
|
1633
|
-
dimensions: _v4.z.array(ReadAloudDimensionWeightSchema).min(1).max(READ_ALOUD_MAX_DIMENSIONS)
|
|
1634
|
-
});
|
|
1635
|
-
function checkEvenAfterIssues2(check) {
|
|
1636
|
-
const guard = new _v4.z.core.$ZodCheck({ check: "custom", when: () => true });
|
|
1637
|
-
guard._zod.check = check;
|
|
1638
|
-
return guard;
|
|
1639
|
-
}
|
|
1640
|
-
var ReadAloudDataShape = _v4.z.looseObject({
|
|
1641
|
-
schemaVersion: _v4.z.literal("1.0"),
|
|
1642
|
-
type: _v4.z.literal("read-aloud"),
|
|
1643
|
-
id: _v4.z.string().min(1),
|
|
1644
|
-
title: _v4.z.string().min(1),
|
|
1645
|
-
instructions: _v4.z.string().optional(),
|
|
1646
|
-
referenceText: ReferenceTextSchema,
|
|
1647
|
-
locale: _v4.z.string().regex(CANONICAL_LOCALE_RE, {
|
|
1648
|
-
error: `The locale must be a BCP 47 tag in canonical form, with a region: "en-US", "es-419", "zh-Hant-TW". Pronunciation is assessed against one locale's speech, and "en" does not say whose.`
|
|
1649
|
-
}),
|
|
1650
|
-
media: MediaSchema.optional(),
|
|
1651
|
-
slowMedia: ReadAloudSlowMediaSchema.optional(),
|
|
1652
|
-
recording: RecordingBoundsSchema,
|
|
1653
|
-
scoring: ReadAloudScoringSchema,
|
|
1654
|
-
passThreshold: _v4.z.number().min(0).max(1).optional(),
|
|
1655
|
-
feedback: FeedbackSchema.optional(),
|
|
1656
|
-
learningObjectives: _v4.z.array(_v4.z.string()).optional(),
|
|
1657
|
-
difficultyLevel: _v4.z.literal([1, 2, 3, 4, 5]).optional(),
|
|
1658
|
-
ai: AiPermissionsSchema.optional()
|
|
1659
|
-
});
|
|
1660
|
-
var ReadAloudDataSchema = ReadAloudDataShape.check(
|
|
1661
|
-
checkEvenAfterIssues2((ctx) => {
|
|
1662
|
-
if (ctx.issues.some((issue2) => (_nullishCoalesce(_optionalChain([issue2, 'access', _53 => _53.path, 'optionalAccess', _54 => _54.length]), () => ( 0))) === 0)) {
|
|
1663
|
-
return;
|
|
1664
|
-
}
|
|
1665
|
-
const data = ctx.value;
|
|
1666
|
-
const refused = new Set(ctx.issues.map((issue2) => String(_optionalChain([issue2, 'access', _55 => _55.path, 'optionalAccess', _56 => _56[0]]))));
|
|
1667
|
-
const report = (path, input, message) => {
|
|
1668
|
-
ctx.issues.push({ code: "custom", input, message, path: [...path] });
|
|
1669
|
-
};
|
|
1670
|
-
if (!refused.has("referenceText")) {
|
|
1671
|
-
const text = data.referenceText;
|
|
1672
|
-
const measured = dictationNormalizer(void 0).measure(text);
|
|
1673
|
-
if (measured.tooLong) {
|
|
1674
|
-
report(
|
|
1675
|
-
["referenceText"],
|
|
1676
|
-
text,
|
|
1677
|
-
`The text to read aloud is at most ${READ_ALOUD_MAX_REFERENCE_LENGTH} characters, before and after normalisation.`
|
|
1678
|
-
);
|
|
1679
|
-
}
|
|
1680
|
-
if (measured.normalized === "") {
|
|
1681
|
-
report(
|
|
1682
|
-
["referenceText"],
|
|
1683
|
-
text,
|
|
1684
|
-
"The text to read aloud must contain a word: after ignoring punctuation and spacing nothing is left, so no word would be marked and every reading would align as one insertion after another."
|
|
1685
|
-
);
|
|
1686
|
-
}
|
|
1687
|
-
if (containsUnspacedScript(text)) {
|
|
1688
|
-
report(
|
|
1689
|
-
["referenceText"],
|
|
1690
|
-
text,
|
|
1691
|
-
"A read-aloud item needs a script written with spaces between its words. The text is compared word by word, and a run of Han, kana, Thai, Lao, Khmer or Myanmar letters is one word however many it holds."
|
|
1692
|
-
);
|
|
1693
|
-
}
|
|
1694
|
-
}
|
|
1695
|
-
const mediaParses = !refused.has("media");
|
|
1696
|
-
const slowParses = !refused.has("slowMedia");
|
|
1697
|
-
if (mediaParses && data.media !== void 0 && data.media.type !== "audio") {
|
|
1698
|
-
report(
|
|
1699
|
-
["media", "type"],
|
|
1700
|
-
data.media.type,
|
|
1701
|
-
"A read-aloud model is read aloud: its recording must be audio."
|
|
1702
|
-
);
|
|
1703
|
-
}
|
|
1704
|
-
if (mediaParses && slowParses && data.slowMedia !== void 0) {
|
|
1705
|
-
if (data.media === void 0) {
|
|
1706
|
-
report(
|
|
1707
|
-
["media"],
|
|
1708
|
-
data.media,
|
|
1709
|
-
"A slow model recording accompanies a recording: add `media` first."
|
|
1710
|
-
);
|
|
1711
|
-
}
|
|
1712
|
-
if (_optionalChain([data, 'access', _57 => _57.media, 'optionalAccess', _58 => _58.playback, 'optionalAccess', _59 => _59.maxPlays]) !== void 0) {
|
|
1713
|
-
report(
|
|
1714
|
-
["slowMedia"],
|
|
1715
|
-
data.slowMedia,
|
|
1716
|
-
"A play budget on `media` cannot coexist with an unbudgeted slow recording of the same content. Drop `slowMedia`, or drop `maxPlays`."
|
|
1717
|
-
);
|
|
1718
|
-
}
|
|
1719
|
-
if (typeof data.slowMedia.url === "string" && typeof _optionalChain([data, 'access', _60 => _60.media, 'optionalAccess', _61 => _61.url]) === "string" && data.slowMedia.url === data.media.url) {
|
|
1720
|
-
report(
|
|
1721
|
-
["slowMedia", "url"],
|
|
1722
|
-
data.slowMedia.url,
|
|
1723
|
-
"The slow model recording must be a different file from the recording."
|
|
1724
|
-
);
|
|
1725
|
-
}
|
|
1726
|
-
}
|
|
1727
|
-
if (!refused.has("scoring")) {
|
|
1728
|
-
const dimensions = data.scoring.dimensions;
|
|
1729
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1730
|
-
for (const [index, dimension] of dimensions.entries()) {
|
|
1731
|
-
if (seen.has(dimension.name)) {
|
|
1732
|
-
report(
|
|
1733
|
-
["scoring", "dimensions", index, "name"],
|
|
1734
|
-
dimension.name,
|
|
1735
|
-
`"${dimension.name}" is already weighed: a dimension counted twice hides which weight the grade used.`
|
|
1736
|
-
);
|
|
1737
|
-
}
|
|
1738
|
-
seen.add(dimension.name);
|
|
1739
|
-
}
|
|
1740
|
-
if (dimensions.every((dimension) => dimension.weight === 0)) {
|
|
1741
|
-
report(
|
|
1742
|
-
["scoring", "dimensions"],
|
|
1743
|
-
dimensions,
|
|
1744
|
-
"At least one dimension must carry a weight above 0: with every weight at 0 there is no weighted total, and every take would be unscorable."
|
|
1745
|
-
);
|
|
1746
|
-
}
|
|
1747
|
-
}
|
|
1748
|
-
if (!refused.has("recording")) {
|
|
1749
|
-
const { minSeconds, maxSeconds } = data.recording;
|
|
1750
|
-
if (minSeconds !== void 0 && minSeconds >= maxSeconds) {
|
|
1751
|
-
report(
|
|
1752
|
-
["recording", "minSeconds"],
|
|
1753
|
-
minSeconds,
|
|
1754
|
-
"The shortest take must be shorter than the longest."
|
|
1755
|
-
);
|
|
1756
|
-
}
|
|
1757
|
-
}
|
|
1758
|
-
})
|
|
1759
|
-
);
|
|
1760
|
-
|
|
1761
|
-
// src/count-words.ts
|
|
1762
|
-
function countWords(text) {
|
|
1763
|
-
if (typeof text !== "string") {
|
|
1764
|
-
return 0;
|
|
1765
|
-
}
|
|
1766
|
-
const trimmed = text.trim();
|
|
1767
|
-
if (trimmed === "") {
|
|
1768
|
-
return 0;
|
|
1769
|
-
}
|
|
1770
|
-
return trimmed.split(/\s+/).length;
|
|
1771
|
-
}
|
|
1772
|
-
|
|
1773
|
-
// src/schemas/gap-select.ts
|
|
1774
|
-
|
|
1775
|
-
var GapSelectChoiceSchema = _v4.z.looseObject({
|
|
1776
|
-
id: _v4.z.string().min(1),
|
|
1777
|
-
text: _v4.z.string().min(1)
|
|
1778
|
-
});
|
|
1779
|
-
var GapSelectBankSchema = _v4.z.looseObject({
|
|
1780
|
-
id: _v4.z.string().min(1),
|
|
1781
|
-
choices: _v4.z.array(GapSelectChoiceSchema).min(2)
|
|
1782
|
-
});
|
|
1783
|
-
var GapSelectGapSchema = _v4.z.looseObject({
|
|
1784
|
-
id: _v4.z.string().min(1),
|
|
1785
|
-
choices: _v4.z.array(GapSelectChoiceSchema).min(2).optional(),
|
|
1786
|
-
bankId: _v4.z.string().min(1).optional(),
|
|
1787
|
-
correctChoiceId: _v4.z.string().min(1),
|
|
1788
|
-
feedback: _v4.z.string().optional()
|
|
1789
|
-
});
|
|
1790
|
-
function resolveChoices(gap, banks) {
|
|
1791
|
-
if (gap.choices !== void 0) {
|
|
1792
|
-
return gap.bankId === void 0 ? gap.choices : void 0;
|
|
1793
|
-
}
|
|
1794
|
-
if (gap.bankId === void 0) {
|
|
1795
|
-
return void 0;
|
|
1796
|
-
}
|
|
1797
|
-
return _optionalChain([banks, 'access', _62 => _62.find, 'call', _63 => _63((bank) => bank.id === gap.bankId), 'optionalAccess', _64 => _64.choices]);
|
|
1798
|
-
}
|
|
1799
|
-
function placeholderCounts(passage) {
|
|
1800
|
-
const counts = /* @__PURE__ */ new Map();
|
|
1801
|
-
for (const match of passage.matchAll(PLACEHOLDER_RE)) {
|
|
1802
|
-
const id = match[1];
|
|
1803
|
-
counts.set(id, (_nullishCoalesce(counts.get(id), () => ( 0))) + 1);
|
|
1804
|
-
}
|
|
1805
|
-
return counts;
|
|
1806
|
-
}
|
|
1807
|
-
var GapSelectDataSchema = _v4.z.looseObject({
|
|
1808
|
-
schemaVersion: _v4.z.literal("1.0"),
|
|
1809
|
-
type: _v4.z.literal("gap-select"),
|
|
1810
|
-
id: _v4.z.string().min(1),
|
|
1811
|
-
title: _v4.z.string().min(1),
|
|
1812
|
-
passage: _v4.z.string().min(1),
|
|
1813
|
-
passageHtml: _v4.z.string().optional(),
|
|
1814
|
-
gaps: _v4.z.array(GapSelectGapSchema).min(1),
|
|
1815
|
-
banks: _v4.z.array(GapSelectBankSchema).optional(),
|
|
1816
|
-
scoringStrategy: _v4.z.enum(["all-or-nothing", "partial"]),
|
|
1817
|
-
presentation: _v4.z.literal("dropdown").optional(),
|
|
1818
|
-
shuffleChoices: _v4.z.boolean().optional(),
|
|
1819
|
-
media: MediaSchema.optional(),
|
|
1820
|
-
feedback: FeedbackSchema.optional(),
|
|
1821
|
-
passThreshold: _v4.z.number().min(0).max(1).optional(),
|
|
1822
|
-
locale: _v4.z.string().optional(),
|
|
1823
|
-
learningObjectives: _v4.z.array(_v4.z.string()).optional(),
|
|
1824
|
-
difficultyLevel: _v4.z.literal([1, 2, 3, 4, 5]).optional(),
|
|
1825
|
-
ai: AiPermissionsSchema.optional()
|
|
1826
|
-
}).refine(
|
|
1827
|
-
(data) => {
|
|
1828
|
-
const ids = (_nullishCoalesce(data.banks, () => ( []))).map((bank) => bank.id);
|
|
1829
|
-
return new Set(ids).size === ids.length;
|
|
1830
|
-
},
|
|
1831
|
-
{ error: "Word bank ids must be unique.", path: ["banks"] }
|
|
1832
|
-
).refine(
|
|
1833
|
-
(data) => {
|
|
1834
|
-
const ids = data.gaps.map((gap) => gap.id);
|
|
1835
|
-
return new Set(ids).size === ids.length;
|
|
1836
|
-
},
|
|
1837
|
-
{ error: "Gap ids must be unique.", path: ["gaps"] }
|
|
1838
|
-
).refine(
|
|
1839
|
-
(data) => {
|
|
1840
|
-
const counts = placeholderCounts(data.passage);
|
|
1841
|
-
const ids = data.gaps.map((gap) => gap.id);
|
|
1842
|
-
return counts.size === new Set(ids).size && ids.every((id) => counts.get(id) === 1) && [...counts.values()].every((count) => count === 1);
|
|
1843
|
-
},
|
|
1844
|
-
{
|
|
1845
|
-
error: "Every gap must appear exactly once in the passage as {{gap_id}}, and every {{gap_id}} must have a gap.",
|
|
1846
|
-
path: ["passage"]
|
|
1847
|
-
}
|
|
1848
|
-
).refine(
|
|
1849
|
-
(data) => data.gaps.every((gap) => gap.choices === void 0 !== (gap.bankId === void 0)),
|
|
1850
|
-
{
|
|
1851
|
-
error: "Each gap needs exactly one choice source: its own `choices`, or a `bankId`.",
|
|
1852
|
-
path: ["gaps"]
|
|
1853
|
-
}
|
|
1854
|
-
).refine(
|
|
1855
|
-
(data) => {
|
|
1856
|
-
const bankIds = new Set((_nullishCoalesce(data.banks, () => ( []))).map((bank) => bank.id));
|
|
1857
|
-
return data.gaps.every((gap) => gap.bankId === void 0 || bankIds.has(gap.bankId));
|
|
1858
|
-
},
|
|
1859
|
-
{ error: "A gap references a `bankId` that no word bank defines.", path: ["gaps"] }
|
|
1860
|
-
).refine(
|
|
1861
|
-
(data) => {
|
|
1862
|
-
const banks = _nullishCoalesce(data.banks, () => ( []));
|
|
1863
|
-
return data.gaps.every((gap) => {
|
|
1864
|
-
const choices = resolveChoices(gap, banks);
|
|
1865
|
-
if (choices === void 0) {
|
|
1866
|
-
return true;
|
|
1867
|
-
}
|
|
1868
|
-
const ids = choices.map((choice) => choice.id);
|
|
1869
|
-
return new Set(ids).size === ids.length && ids.includes(gap.correctChoiceId);
|
|
1870
|
-
});
|
|
1871
|
-
},
|
|
1872
|
-
{
|
|
1873
|
-
error: "Each gap needs unique choice ids and a `correctChoiceId` that is one of the choices it offers.",
|
|
1874
|
-
path: ["gaps"]
|
|
1875
|
-
}
|
|
1876
|
-
);
|
|
1877
|
-
|
|
1878
|
-
// src/schemas/written-response.ts
|
|
1879
|
-
|
|
1880
|
-
var WrittenResponseRubricCriterionSchema = _v4.z.looseObject({
|
|
1881
|
-
name: _v4.z.string().min(1),
|
|
1882
|
-
description: _v4.z.string().optional(),
|
|
1883
|
-
weight: _v4.z.number().min(0)
|
|
1884
|
-
});
|
|
1885
|
-
var WrittenResponseRubricSchema = _v4.z.looseObject({
|
|
1886
|
-
label: _v4.z.string().optional(),
|
|
1887
|
-
criteria: _v4.z.array(WrittenResponseRubricCriterionSchema).min(1)
|
|
1888
|
-
});
|
|
1889
|
-
var RedactedWrittenResponseRubricCriterionSchema = _v4.z.strictObject({
|
|
1890
|
-
name: _v4.z.string().min(1),
|
|
1891
|
-
description: _v4.z.string().optional(),
|
|
1892
|
-
weight: _v4.z.number().min(0)
|
|
1893
|
-
});
|
|
1894
|
-
var RedactedWrittenResponseRubricSchema = _v4.z.strictObject({
|
|
1895
|
-
label: _v4.z.string().optional(),
|
|
1896
|
-
criteria: _v4.z.array(RedactedWrittenResponseRubricCriterionSchema).min(1)
|
|
1897
|
-
});
|
|
1898
|
-
var WrittenResponseDataSchema = _v4.z.looseObject({
|
|
1899
|
-
schemaVersion: _v4.z.literal("1.0"),
|
|
1900
|
-
type: _v4.z.literal("written-response"),
|
|
1901
|
-
id: _v4.z.string().min(1),
|
|
1902
|
-
title: _v4.z.string().min(1),
|
|
1903
|
-
prompt: _v4.z.string(),
|
|
1904
|
-
promptHtml: _v4.z.string().optional(),
|
|
1905
|
-
minWords: _v4.z.number().int().min(0),
|
|
1906
|
-
maxWords: _v4.z.number().int().min(1),
|
|
1907
|
-
rubric: WrittenResponseRubricSchema.optional(),
|
|
1908
|
-
languageTarget: _v4.z.string().optional(),
|
|
1909
|
-
media: MediaSchema.optional(),
|
|
1910
|
-
feedback: FeedbackSchema.optional(),
|
|
1911
|
-
passThreshold: _v4.z.number().min(0).max(1).optional(),
|
|
1912
|
-
locale: _v4.z.string().optional(),
|
|
1913
|
-
learningObjectives: _v4.z.array(_v4.z.string()).optional(),
|
|
1914
|
-
difficultyLevel: _v4.z.literal([1, 2, 3, 4, 5]).optional(),
|
|
1915
|
-
ai: AiPermissionsSchema.optional()
|
|
1916
|
-
}).refine((data) => data.maxWords >= data.minWords, {
|
|
1917
|
-
error: "maxWords must be greater than or equal to minWords.",
|
|
1918
|
-
path: ["maxWords"]
|
|
1919
|
-
});
|
|
1920
|
-
|
|
1921
|
-
// src/schemas/redacted.ts
|
|
1922
|
-
|
|
1923
|
-
var redactedBase = {
|
|
1924
|
-
/** Marker distinguishing a redacted projection from full activity data. */
|
|
1925
|
-
redacted: _v4.z.literal(true),
|
|
1926
|
-
/** Slot identity, carried through redaction so the client and the plan agree. */
|
|
1927
|
-
slotKey: _v4.z.string().min(1).optional(),
|
|
1928
|
-
schemaVersion: _v4.z.literal("1.0"),
|
|
1929
|
-
id: _v4.z.string().min(1),
|
|
1930
|
-
title: _v4.z.string().min(1),
|
|
1931
|
-
media: RedactedMediaSchema.optional(),
|
|
1932
|
-
passThreshold: _v4.z.number().min(0).max(1).optional(),
|
|
1933
|
-
locale: _v4.z.string().optional(),
|
|
1934
|
-
learningObjectives: _v4.z.array(_v4.z.string()).optional(),
|
|
1935
|
-
difficultyLevel: _v4.z.literal([1, 2, 3, 4, 5]).optional(),
|
|
1936
|
-
ai: _v4.z.strictObject({ explanations: _v4.z.boolean().optional(), hints: _v4.z.boolean().optional() }).optional()
|
|
1937
|
-
};
|
|
1938
|
-
var RedactedMultipleChoiceOptionMediaSchema = _v4.z.strictObject({
|
|
1939
|
-
type: _v4.z.enum(["image", "audio"]),
|
|
1940
|
-
url: _v4.z.string().min(1),
|
|
1941
|
-
alt: _v4.z.string().min(1).optional(),
|
|
1942
|
-
captionsUrl: _v4.z.string().min(1).optional()
|
|
1943
|
-
});
|
|
1944
|
-
var RedactedMultipleChoiceOptionSchema = _v4.z.strictObject({
|
|
1945
|
-
id: _v4.z.string().min(1),
|
|
1946
|
-
text: _v4.z.string().min(1),
|
|
1947
|
-
media: RedactedMultipleChoiceOptionMediaSchema.optional()
|
|
1948
|
-
});
|
|
1949
|
-
var RedactedMultipleChoiceDataSchema = _v4.z.strictObject({
|
|
1950
|
-
...redactedBase,
|
|
1951
|
-
type: _v4.z.literal("multiple-choice"),
|
|
1952
|
-
question: _v4.z.string().min(1),
|
|
1953
|
-
questionHtml: _v4.z.string().optional(),
|
|
1954
|
-
mode: _v4.z.enum(["single", "multi"]),
|
|
1955
|
-
options: _v4.z.array(RedactedMultipleChoiceOptionSchema).min(2).max(26),
|
|
1956
|
-
shuffle: _v4.z.boolean().optional()
|
|
1957
|
-
});
|
|
1958
|
-
var RedactedBlankConfigSchema = _v4.z.strictObject({
|
|
1959
|
-
id: _v4.z.string().min(1),
|
|
1960
|
-
hint: _v4.z.string().optional()
|
|
1961
|
-
});
|
|
1962
|
-
var RedactedFillInTheBlanksDataSchema = _v4.z.strictObject({
|
|
1963
|
-
...redactedBase,
|
|
1964
|
-
type: _v4.z.literal("fill-in-the-blanks"),
|
|
1965
|
-
passage: _v4.z.string().min(1),
|
|
1966
|
-
passageHtml: _v4.z.string().optional(),
|
|
1967
|
-
blanks: _v4.z.array(RedactedBlankConfigSchema).min(1)
|
|
1968
|
-
});
|
|
1969
|
-
var RedactedGapSelectChoiceSchema = _v4.z.strictObject({
|
|
1970
|
-
id: _v4.z.string().min(1),
|
|
1971
|
-
text: _v4.z.string().min(1)
|
|
1972
|
-
});
|
|
1973
|
-
var RedactedGapSelectBankSchema = _v4.z.strictObject({
|
|
1974
|
-
id: _v4.z.string().min(1),
|
|
1975
|
-
choices: _v4.z.array(RedactedGapSelectChoiceSchema).min(2)
|
|
1976
|
-
});
|
|
1977
|
-
var RedactedGapSelectGapSchema = _v4.z.strictObject({
|
|
1978
|
-
id: _v4.z.string().min(1),
|
|
1979
|
-
choices: _v4.z.array(RedactedGapSelectChoiceSchema).min(2).optional(),
|
|
1980
|
-
bankId: _v4.z.string().min(1).optional()
|
|
1981
|
-
});
|
|
1982
|
-
var RedactedGapSelectDataSchema = _v4.z.strictObject({
|
|
1983
|
-
...redactedBase,
|
|
1984
|
-
type: _v4.z.literal("gap-select"),
|
|
1985
|
-
passage: _v4.z.string().min(1),
|
|
1986
|
-
passageHtml: _v4.z.string().optional(),
|
|
1987
|
-
gaps: _v4.z.array(RedactedGapSelectGapSchema).min(1),
|
|
1988
|
-
banks: _v4.z.array(RedactedGapSelectBankSchema).optional(),
|
|
1989
|
-
presentation: _v4.z.literal("dropdown").optional(),
|
|
1990
|
-
shuffleChoices: _v4.z.boolean().optional()
|
|
1991
|
-
});
|
|
1992
|
-
var RedactedWrittenResponseDataSchema = _v4.z.strictObject({
|
|
1993
|
-
...redactedBase,
|
|
1994
|
-
type: _v4.z.literal("written-response"),
|
|
1995
|
-
prompt: _v4.z.string(),
|
|
1996
|
-
promptHtml: _v4.z.string().optional(),
|
|
1997
|
-
minWords: _v4.z.number().int().min(0),
|
|
1998
|
-
maxWords: _v4.z.number().int().min(1),
|
|
1999
|
-
rubric: RedactedWrittenResponseRubricSchema.optional(),
|
|
2000
|
-
languageTarget: _v4.z.string().optional()
|
|
2001
|
-
});
|
|
2002
|
-
var RedactedDictationSlowMediaSchema = _v4.z.strictObject({
|
|
2003
|
-
type: _v4.z.literal("audio"),
|
|
2004
|
-
url: MediaUrlSchema,
|
|
2005
|
-
alt: _v4.z.string().min(1).optional()
|
|
2006
|
-
});
|
|
2007
|
-
var RedactedDictationDataSchema = _v4.z.strictObject({
|
|
2008
|
-
...redactedBase,
|
|
2009
|
-
type: _v4.z.literal("dictation"),
|
|
2010
|
-
slowMedia: RedactedDictationSlowMediaSchema.optional(),
|
|
2011
|
-
hints: _v4.z.strictObject({ mode: _v4.z.literal("progressive-words") }).optional()
|
|
2012
|
-
}).check((ctx) => {
|
|
2013
|
-
const data = ctx.value;
|
|
2014
|
-
for (const captions of captionFieldsOf(data.media)) {
|
|
2015
|
-
ctx.issues.push({
|
|
2016
|
-
code: "custom",
|
|
2017
|
-
input: _optionalChain([data, 'access', _65 => _65.media, 'optionalAccess', _66 => _66[captions]]),
|
|
2018
|
-
message: "A dictation recording cannot carry captions: the captions are the answer, so this payload is not learner-safe.",
|
|
2019
|
-
path: ["media", captions]
|
|
2020
|
-
});
|
|
2021
|
-
}
|
|
2022
|
-
if (data.slowMedia !== void 0 && _optionalChain([data, 'access', _67 => _67.media, 'optionalAccess', _68 => _68.playback, 'optionalAccess', _69 => _69.maxPlays]) !== void 0) {
|
|
2023
|
-
ctx.issues.push({
|
|
2024
|
-
code: "custom",
|
|
2025
|
-
input: data.slowMedia,
|
|
2026
|
-
message: "A play budget on `media` cannot coexist with an unbudgeted slow recording of the same content.",
|
|
2027
|
-
path: ["slowMedia"]
|
|
2028
|
-
});
|
|
2029
|
-
}
|
|
2030
|
-
if (data.slowMedia !== void 0 && data.media === void 0) {
|
|
2031
|
-
ctx.issues.push({
|
|
2032
|
-
code: "custom",
|
|
2033
|
-
input: data.slowMedia,
|
|
2034
|
-
message: "A slow recording accompanies a recording: this payload has no `media`.",
|
|
2035
|
-
path: ["media"]
|
|
2036
|
-
});
|
|
2037
|
-
}
|
|
2038
|
-
});
|
|
2039
|
-
var RedactedReadAloudDataSchema = _v4.z.strictObject({
|
|
2040
|
-
...redactedBase,
|
|
2041
|
-
type: _v4.z.literal("read-aloud"),
|
|
2042
|
-
locale: _v4.z.string().regex(CANONICAL_LOCALE_RE),
|
|
2043
|
-
instructions: _v4.z.string().optional(),
|
|
2044
|
-
referenceText: _v4.z.string().min(1),
|
|
2045
|
-
slowMedia: ReadAloudSlowMediaSchema.optional(),
|
|
2046
|
-
recording: _v4.z.strictObject({
|
|
2047
|
-
maxSeconds: _v4.z.number().gt(0).max(READ_ALOUD_MAX_SECONDS),
|
|
2048
|
-
minSeconds: _v4.z.number().min(0).optional(),
|
|
2049
|
-
maxTakes: _v4.z.number().int().min(1).max(READ_ALOUD_MAX_TAKES).optional()
|
|
2050
|
-
}),
|
|
2051
|
-
scoring: _v4.z.strictObject({
|
|
2052
|
-
dimensions: _v4.z.array(
|
|
2053
|
-
_v4.z.strictObject({
|
|
2054
|
-
name: _v4.z.enum(READ_ALOUD_DIMENSIONS),
|
|
2055
|
-
weight: _v4.z.number().min(0).max(READ_ALOUD_MAX_DIMENSION_WEIGHT)
|
|
2056
|
-
})
|
|
2057
|
-
).min(1).max(READ_ALOUD_DIMENSIONS.length)
|
|
2058
|
-
})
|
|
2059
|
-
}).check((ctx) => {
|
|
2060
|
-
const data = ctx.value;
|
|
2061
|
-
if (data.media !== void 0 && data.media.type !== "audio") {
|
|
2062
|
-
ctx.issues.push({
|
|
2063
|
-
code: "custom",
|
|
2064
|
-
input: data.media.type,
|
|
2065
|
-
message: "A read-aloud model recording must be audio, so this payload is not renderable.",
|
|
2066
|
-
path: ["media", "type"]
|
|
2067
|
-
});
|
|
2068
|
-
}
|
|
2069
|
-
if (data.slowMedia !== void 0 && data.media === void 0) {
|
|
2070
|
-
ctx.issues.push({
|
|
2071
|
-
code: "custom",
|
|
2072
|
-
input: data.slowMedia,
|
|
2073
|
-
message: "A slow model recording accompanies a recording: this payload has no `media`.",
|
|
2074
|
-
path: ["media"]
|
|
2075
|
-
});
|
|
2076
|
-
}
|
|
2077
|
-
if (data.slowMedia !== void 0 && _optionalChain([data, 'access', _70 => _70.media, 'optionalAccess', _71 => _71.playback, 'optionalAccess', _72 => _72.maxPlays]) !== void 0) {
|
|
2078
|
-
ctx.issues.push({
|
|
2079
|
-
code: "custom",
|
|
2080
|
-
input: data.slowMedia,
|
|
2081
|
-
message: "A play budget on `media` cannot coexist with an unbudgeted slow recording of the same content.",
|
|
2082
|
-
path: ["slowMedia"]
|
|
2083
|
-
});
|
|
2084
|
-
}
|
|
2085
|
-
if (data.slowMedia !== void 0 && data.slowMedia.url === _optionalChain([data, 'access', _73 => _73.media, 'optionalAccess', _74 => _74.url])) {
|
|
2086
|
-
ctx.issues.push({
|
|
2087
|
-
code: "custom",
|
|
2088
|
-
input: data.slowMedia.url,
|
|
2089
|
-
message: "The slow model recording must be a different file from the recording.",
|
|
2090
|
-
path: ["slowMedia", "url"]
|
|
2091
|
-
});
|
|
2092
|
-
}
|
|
2093
|
-
});
|
|
2094
|
-
|
|
2095
|
-
// src/scoring/text-match.ts
|
|
2096
|
-
var COMBINING_MARKS_RE = /\p{M}/gu;
|
|
2097
|
-
var PUNCTUATION_RE2 = /\p{P}/gu;
|
|
2098
|
-
function applyBaseline(value, policy) {
|
|
2099
|
-
let result = value;
|
|
2100
|
-
if (policy.trim !== false) {
|
|
2101
|
-
result = result.trim();
|
|
2102
|
-
}
|
|
2103
|
-
if (policy.caseSensitive !== true) {
|
|
2104
|
-
result = policy.locale ? result.toLocaleLowerCase(policy.locale) : result.toLowerCase();
|
|
2105
|
-
}
|
|
2106
|
-
return result;
|
|
2107
|
-
}
|
|
2108
|
-
function applyNormalization(value, policy) {
|
|
2109
|
-
let result = value;
|
|
2110
|
-
if (policy.normalize === "NFC" || policy.normalize === "NFKC") {
|
|
2111
|
-
result = result.normalize(policy.normalize);
|
|
2112
|
-
}
|
|
2113
|
-
if (policy.ignorePunctuation === true) {
|
|
2114
|
-
result = result.replace(PUNCTUATION_RE2, "");
|
|
2115
|
-
}
|
|
2116
|
-
if (policy.collapseInnerWhitespace === true) {
|
|
2117
|
-
result = result.replace(/(?<=\S)\s+(?=\S)/g, " ");
|
|
2118
|
-
if (policy.trim !== false) {
|
|
2119
|
-
result = result.trim();
|
|
2120
|
-
}
|
|
2121
|
-
}
|
|
2122
|
-
return result;
|
|
2123
|
-
}
|
|
2124
|
-
function applyDiacriticFold(value) {
|
|
2125
|
-
return value.normalize("NFD").replace(COMBINING_MARKS_RE, "").normalize("NFC");
|
|
2126
|
-
}
|
|
2127
|
-
function levenshteinDistance(a, b, max) {
|
|
2128
|
-
if (a === b) return 0;
|
|
2129
|
-
if (Math.abs(a.length - b.length) > max) return max + 1;
|
|
2130
|
-
if (a.length === 0) return b.length;
|
|
2131
|
-
if (b.length === 0) return a.length;
|
|
2132
|
-
let previous = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
2133
|
-
for (let i = 1; i <= a.length; i += 1) {
|
|
2134
|
-
const current = [i];
|
|
2135
|
-
let rowMin = i;
|
|
2136
|
-
for (let j = 1; j <= b.length; j += 1) {
|
|
2137
|
-
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
2138
|
-
const value = Math.min(
|
|
2139
|
-
previous[j] + 1,
|
|
2140
|
-
current[j - 1] + 1,
|
|
2141
|
-
previous[j - 1] + cost
|
|
2142
|
-
);
|
|
2143
|
-
current.push(value);
|
|
2144
|
-
if (value < rowMin) rowMin = value;
|
|
2145
|
-
}
|
|
2146
|
-
if (rowMin > max) return max + 1;
|
|
2147
|
-
previous = current;
|
|
2148
|
-
}
|
|
2149
|
-
return previous[b.length];
|
|
2150
|
-
}
|
|
2151
|
-
function matchText(input, accepted, policy = {}) {
|
|
2152
|
-
const acceptedList = typeof accepted === "string" ? [accepted] : accepted;
|
|
2153
|
-
const baselineInput = applyBaseline(input, policy);
|
|
2154
|
-
const baselineAccepted = acceptedList.map((answer) => applyBaseline(answer, policy));
|
|
2155
|
-
if (baselineAccepted.some((answer) => answer === baselineInput)) {
|
|
2156
|
-
return { matched: true, via: "exact" };
|
|
2157
|
-
}
|
|
2158
|
-
const usesNormalization = policy.normalize === "NFC" || policy.normalize === "NFKC" || policy.ignorePunctuation === true || policy.collapseInnerWhitespace === true;
|
|
2159
|
-
const normalizedInput = usesNormalization ? applyNormalization(baselineInput, policy) : baselineInput;
|
|
2160
|
-
const normalizedAccepted = usesNormalization ? baselineAccepted.map((answer) => applyNormalization(answer, policy)) : baselineAccepted;
|
|
2161
|
-
if (usesNormalization && normalizedAccepted.some((answer) => answer === normalizedInput)) {
|
|
2162
|
-
return { matched: true, via: "normalized" };
|
|
2163
|
-
}
|
|
2164
|
-
const foldedInput = policy.foldDiacritics === true ? applyDiacriticFold(normalizedInput) : normalizedInput;
|
|
2165
|
-
const foldedAccepted = policy.foldDiacritics === true ? normalizedAccepted.map((answer) => applyDiacriticFold(answer)) : normalizedAccepted;
|
|
2166
|
-
if (policy.foldDiacritics === true && foldedAccepted.some((answer) => answer === foldedInput)) {
|
|
2167
|
-
return { matched: true, via: "folded" };
|
|
2168
|
-
}
|
|
2169
|
-
const maxDistance = _nullishCoalesce(policy.levenshtein, () => ( 0));
|
|
2170
|
-
if (maxDistance > 0 && foldedInput.trim().length > 0 && foldedAccepted.some(
|
|
2171
|
-
(answer) => levenshteinDistance(foldedInput, answer, maxDistance) <= maxDistance
|
|
2172
|
-
)) {
|
|
2173
|
-
return { matched: true, via: "fuzzy" };
|
|
2174
|
-
}
|
|
2175
|
-
return { matched: false, via: "none" };
|
|
2176
|
-
}
|
|
2177
|
-
|
|
2178
|
-
// src/registry/registry.ts
|
|
2179
|
-
var registry = /* @__PURE__ */ new Map();
|
|
2180
|
-
function defineActivityType(descriptor) {
|
|
2181
|
-
return descriptor;
|
|
2182
|
-
}
|
|
2183
|
-
function registerActivityType(descriptor) {
|
|
2184
|
-
if (descriptor.type === "item-group") {
|
|
2185
|
-
throw new Error(
|
|
2186
|
-
`"item-group" is reserved for the SDK's item-group container (see ItemGroup) and cannot be registered as an activity type.`
|
|
2187
|
-
);
|
|
2188
|
-
}
|
|
2189
|
-
const existing = registry.get(descriptor.type);
|
|
2190
|
-
if (existing !== void 0) {
|
|
2191
|
-
if (existing === descriptor) {
|
|
2192
|
-
return;
|
|
2193
|
-
}
|
|
2194
|
-
throw new Error(
|
|
2195
|
-
`Activity type "${descriptor.type}" is already registered. Registering a different descriptor for an existing type is not allowed.`
|
|
2196
|
-
);
|
|
2197
|
-
}
|
|
2198
|
-
registry.set(descriptor.type, descriptor);
|
|
2199
|
-
}
|
|
2200
|
-
function getActivityTypeDescriptor(type) {
|
|
2201
|
-
return registry.get(type);
|
|
2202
|
-
}
|
|
2203
|
-
function registeredActivityTypes() {
|
|
2204
|
-
return [...registry.keys()];
|
|
2205
|
-
}
|
|
2206
|
-
|
|
2207
1799
|
// src/authoring/issues.ts
|
|
2208
1800
|
var DRAFT_ISSUE_SEVERITY = {
|
|
2209
1801
|
// Every registered type — reported by validateDraft itself
|
|
@@ -2365,7 +1957,7 @@ function issue(code, path, message) {
|
|
|
2365
1957
|
function isRecord(value) {
|
|
2366
1958
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2367
1959
|
}
|
|
2368
|
-
function
|
|
1960
|
+
function valueAt2(root, path) {
|
|
2369
1961
|
let node = root;
|
|
2370
1962
|
for (const segment of path) {
|
|
2371
1963
|
if (typeof node !== "object" || node === null) {
|
|
@@ -2377,9 +1969,9 @@ function valueAt(root, path) {
|
|
|
2377
1969
|
}
|
|
2378
1970
|
function refusesEmpty(root, schemaIssue) {
|
|
2379
1971
|
const { path } = schemaIssue;
|
|
2380
|
-
const value =
|
|
1972
|
+
const value = valueAt2(root, path);
|
|
2381
1973
|
const index = path.at(-1);
|
|
2382
|
-
const list =
|
|
1974
|
+
const list = valueAt2(root, path.slice(0, -1));
|
|
2383
1975
|
const empty = value === null || value === void 0 && typeof index === "number" && Array.isArray(list) && index < list.length;
|
|
2384
1976
|
return empty && (schemaIssue.input === value || schemaIssue.code === "invalid_union");
|
|
2385
1977
|
}
|
|
@@ -2906,6 +2498,70 @@ function checkTolerance(tolerance) {
|
|
|
2906
2498
|
return issues;
|
|
2907
2499
|
}
|
|
2908
2500
|
|
|
2501
|
+
// src/schemas/fill-in-the-blanks.ts
|
|
2502
|
+
|
|
2503
|
+
var PLACEHOLDER_RE = /\{\{\s*([^{}]+?)\s*\}\}/g;
|
|
2504
|
+
var TextMatchPolicySchema = _v4.z.looseObject({
|
|
2505
|
+
caseSensitive: _v4.z.boolean().optional(),
|
|
2506
|
+
trim: _v4.z.boolean().optional(),
|
|
2507
|
+
normalize: _v4.z.enum(["none", "NFC", "NFKC"]).optional(),
|
|
2508
|
+
foldDiacritics: _v4.z.boolean().optional(),
|
|
2509
|
+
collapseInnerWhitespace: _v4.z.boolean().optional(),
|
|
2510
|
+
ignorePunctuation: _v4.z.boolean().optional(),
|
|
2511
|
+
levenshtein: _v4.z.number().int().min(0).optional(),
|
|
2512
|
+
locale: _v4.z.string().optional()
|
|
2513
|
+
});
|
|
2514
|
+
var BlankConfigSchema = _v4.z.looseObject({
|
|
2515
|
+
id: _v4.z.string().min(1),
|
|
2516
|
+
acceptedAnswers: _v4.z.array(
|
|
2517
|
+
_v4.z.string().min(1).refine((answer) => answer.trim().length > 0, {
|
|
2518
|
+
error: "Accepted answers must contain non-whitespace characters."
|
|
2519
|
+
})
|
|
2520
|
+
).min(1),
|
|
2521
|
+
caseSensitive: _v4.z.boolean().optional(),
|
|
2522
|
+
trimWhitespace: _v4.z.boolean().optional(),
|
|
2523
|
+
match: TextMatchPolicySchema.optional(),
|
|
2524
|
+
hint: _v4.z.string().optional(),
|
|
2525
|
+
feedback: _v4.z.string().optional()
|
|
2526
|
+
});
|
|
2527
|
+
var FillInTheBlanksDataSchema = _v4.z.looseObject({
|
|
2528
|
+
schemaVersion: _v4.z.literal("1.0"),
|
|
2529
|
+
type: _v4.z.literal("fill-in-the-blanks"),
|
|
2530
|
+
id: _v4.z.string().min(1),
|
|
2531
|
+
title: _v4.z.string().min(1),
|
|
2532
|
+
passage: _v4.z.string().min(1),
|
|
2533
|
+
passageHtml: _v4.z.string().optional(),
|
|
2534
|
+
blanks: _v4.z.array(BlankConfigSchema).min(1),
|
|
2535
|
+
scoringStrategy: _v4.z.enum(["all-or-nothing", "partial"]),
|
|
2536
|
+
media: MediaSchema.optional(),
|
|
2537
|
+
feedback: FeedbackSchema.optional(),
|
|
2538
|
+
passThreshold: _v4.z.number().min(0).max(1).optional(),
|
|
2539
|
+
locale: _v4.z.string().optional(),
|
|
2540
|
+
learningObjectives: _v4.z.array(_v4.z.string()).optional(),
|
|
2541
|
+
difficultyLevel: _v4.z.literal([1, 2, 3, 4, 5]).optional(),
|
|
2542
|
+
ai: AiPermissionsSchema.optional()
|
|
2543
|
+
}).refine(
|
|
2544
|
+
(data) => {
|
|
2545
|
+
const placeholderCounts2 = /* @__PURE__ */ new Map();
|
|
2546
|
+
for (const match of data.passage.matchAll(PLACEHOLDER_RE)) {
|
|
2547
|
+
const id = match[1];
|
|
2548
|
+
placeholderCounts2.set(id, (_nullishCoalesce(placeholderCounts2.get(id), () => ( 0))) + 1);
|
|
2549
|
+
}
|
|
2550
|
+
const blankIds = data.blanks.map((blank) => blank.id);
|
|
2551
|
+
if (new Set(blankIds).size !== blankIds.length) {
|
|
2552
|
+
return false;
|
|
2553
|
+
}
|
|
2554
|
+
if (placeholderCounts2.size !== blankIds.length) {
|
|
2555
|
+
return false;
|
|
2556
|
+
}
|
|
2557
|
+
return blankIds.every((id) => placeholderCounts2.get(id) === 1);
|
|
2558
|
+
},
|
|
2559
|
+
{
|
|
2560
|
+
error: "Each blank id must appear exactly once in blanks[] and have exactly one matching {{id}} placeholder in the passage, and vice versa.",
|
|
2561
|
+
path: ["passage"]
|
|
2562
|
+
}
|
|
2563
|
+
);
|
|
2564
|
+
|
|
2909
2565
|
// src/authoring/fill-in-the-blanks.ts
|
|
2910
2566
|
var fillInTheBlanksAuthoring = {
|
|
2911
2567
|
createDraft: ({ newId }) => ({
|
|
@@ -3262,7 +2918,7 @@ function checkAnswerKey(gap, index, name, banks, bankIds) {
|
|
|
3262
2918
|
if (typeof key !== "string") {
|
|
3263
2919
|
return [];
|
|
3264
2920
|
}
|
|
3265
|
-
const choices =
|
|
2921
|
+
const choices = resolveChoices(gap, banks, bankIds);
|
|
3266
2922
|
if (choices === void 0) {
|
|
3267
2923
|
return [];
|
|
3268
2924
|
}
|
|
@@ -3275,7 +2931,7 @@ function checkAnswerKey(gap, index, name, banks, bankIds) {
|
|
|
3275
2931
|
)
|
|
3276
2932
|
];
|
|
3277
2933
|
}
|
|
3278
|
-
function
|
|
2934
|
+
function resolveChoices(gap, banks, bankIds) {
|
|
3279
2935
|
const hasOwn = !isUnset(gap.choices);
|
|
3280
2936
|
const hasBank = !isUnwritten(gap.bankId);
|
|
3281
2937
|
if (hasOwn === hasBank) {
|
|
@@ -3499,24 +3155,173 @@ function checkMultipleChoiceDraft(draft) {
|
|
|
3499
3155
|
)
|
|
3500
3156
|
);
|
|
3501
3157
|
}
|
|
3502
|
-
}
|
|
3503
|
-
issues.push(...checkSharedOptional(draft));
|
|
3504
|
-
return issues;
|
|
3505
|
-
}
|
|
3506
|
-
function checkOptionMedia(media, index, ordinal) {
|
|
3507
|
-
const path = ["options", index, "media"];
|
|
3508
|
-
const kind = media.type;
|
|
3509
|
-
if (kind === "video" || kind === "embed") {
|
|
3510
|
-
return [
|
|
3511
|
-
issue(
|
|
3512
|
-
"mc_option_media_kind",
|
|
3513
|
-
[...path, "type"],
|
|
3514
|
-
`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.`
|
|
3515
|
-
)
|
|
3516
|
-
];
|
|
3517
|
-
}
|
|
3518
|
-
return checkMedia(media, path, MultipleChoiceOptionMediaSchema);
|
|
3519
|
-
}
|
|
3158
|
+
}
|
|
3159
|
+
issues.push(...checkSharedOptional(draft));
|
|
3160
|
+
return issues;
|
|
3161
|
+
}
|
|
3162
|
+
function checkOptionMedia(media, index, ordinal) {
|
|
3163
|
+
const path = ["options", index, "media"];
|
|
3164
|
+
const kind = media.type;
|
|
3165
|
+
if (kind === "video" || kind === "embed") {
|
|
3166
|
+
return [
|
|
3167
|
+
issue(
|
|
3168
|
+
"mc_option_media_kind",
|
|
3169
|
+
[...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.`
|
|
3171
|
+
)
|
|
3172
|
+
];
|
|
3173
|
+
}
|
|
3174
|
+
return checkMedia(media, path, MultipleChoiceOptionMediaSchema);
|
|
3175
|
+
}
|
|
3176
|
+
|
|
3177
|
+
// src/schemas/read-aloud.ts
|
|
3178
|
+
|
|
3179
|
+
|
|
3180
|
+
// src/scoring/speech/locale.ts
|
|
3181
|
+
var CANONICAL_LOCALE_RE = /^[a-z]{2,3}(?:-[A-Z][a-z]{3})?-(?:[A-Z]{2}|[0-9]{3})$/;
|
|
3182
|
+
|
|
3183
|
+
// src/schemas/read-aloud.ts
|
|
3184
|
+
var READ_ALOUD_DIMENSIONS = ["accuracy", "fluency", "completeness", "prosody"];
|
|
3185
|
+
var READ_ALOUD_MAX_DIMENSIONS = READ_ALOUD_DIMENSIONS.length;
|
|
3186
|
+
var ReadAloudSlowMediaSchema = _v4.z.strictObject({
|
|
3187
|
+
type: _v4.z.literal("audio"),
|
|
3188
|
+
url: MediaUrlSchema,
|
|
3189
|
+
alt: _v4.z.string().min(1).optional()
|
|
3190
|
+
});
|
|
3191
|
+
var ReferenceTextSchema = _v4.z.string().min(1).refine((text) => text.trim() !== "", {
|
|
3192
|
+
error: "The text to read aloud must contain something to read."
|
|
3193
|
+
}).meta({ maxLength: READ_ALOUD_MAX_REFERENCE_LENGTH });
|
|
3194
|
+
var RecordingBoundsSchema = _v4.z.looseObject({
|
|
3195
|
+
maxSeconds: _v4.z.number().gt(0).max(READ_ALOUD_MAX_SECONDS),
|
|
3196
|
+
minSeconds: _v4.z.number().min(0).optional(),
|
|
3197
|
+
maxTakes: _v4.z.number().int().min(1).max(READ_ALOUD_MAX_TAKES).optional()
|
|
3198
|
+
});
|
|
3199
|
+
var ReadAloudDimensionWeightSchema = _v4.z.looseObject({
|
|
3200
|
+
name: _v4.z.enum(READ_ALOUD_DIMENSIONS),
|
|
3201
|
+
weight: _v4.z.number().min(0).max(READ_ALOUD_MAX_DIMENSION_WEIGHT)
|
|
3202
|
+
});
|
|
3203
|
+
var ReadAloudScoringSchema = _v4.z.looseObject({
|
|
3204
|
+
dimensions: _v4.z.array(ReadAloudDimensionWeightSchema).min(1).max(READ_ALOUD_MAX_DIMENSIONS)
|
|
3205
|
+
});
|
|
3206
|
+
var ReadAloudDataShape = _v4.z.looseObject({
|
|
3207
|
+
schemaVersion: _v4.z.literal("1.0"),
|
|
3208
|
+
type: _v4.z.literal("read-aloud"),
|
|
3209
|
+
id: _v4.z.string().min(1),
|
|
3210
|
+
title: _v4.z.string().min(1),
|
|
3211
|
+
instructions: _v4.z.string().optional(),
|
|
3212
|
+
referenceText: ReferenceTextSchema,
|
|
3213
|
+
locale: _v4.z.string().regex(CANONICAL_LOCALE_RE, {
|
|
3214
|
+
error: `The locale must be a BCP 47 tag in canonical form, with a region: "en-US", "es-419", "zh-Hant-TW". Pronunciation is assessed against one locale's speech, and "en" does not say whose.`
|
|
3215
|
+
}),
|
|
3216
|
+
media: MediaSchema.optional(),
|
|
3217
|
+
slowMedia: ReadAloudSlowMediaSchema.optional(),
|
|
3218
|
+
recording: RecordingBoundsSchema,
|
|
3219
|
+
scoring: ReadAloudScoringSchema,
|
|
3220
|
+
passThreshold: _v4.z.number().min(0).max(1).optional(),
|
|
3221
|
+
feedback: FeedbackSchema.optional(),
|
|
3222
|
+
learningObjectives: _v4.z.array(_v4.z.string()).optional(),
|
|
3223
|
+
difficultyLevel: _v4.z.literal([1, 2, 3, 4, 5]).optional(),
|
|
3224
|
+
ai: AiPermissionsSchema.optional()
|
|
3225
|
+
});
|
|
3226
|
+
var ReadAloudDataSchema = ReadAloudDataShape.check(
|
|
3227
|
+
checkEvenAfterIssues((ctx) => {
|
|
3228
|
+
if (ctx.issues.some((issue2) => (_nullishCoalesce(_optionalChain([issue2, 'access', _58 => _58.path, 'optionalAccess', _59 => _59.length]), () => ( 0))) === 0)) {
|
|
3229
|
+
return;
|
|
3230
|
+
}
|
|
3231
|
+
const { data, refused } = parsedFields(ctx);
|
|
3232
|
+
const report = (path, input, message) => {
|
|
3233
|
+
ctx.issues.push({ code: "custom", input, message, path: [...path] });
|
|
3234
|
+
};
|
|
3235
|
+
if (!refused.has("referenceText")) {
|
|
3236
|
+
const text = data.referenceText;
|
|
3237
|
+
const measured = dictationNormalizer(void 0).measure(text);
|
|
3238
|
+
if (measured.tooLong) {
|
|
3239
|
+
report(
|
|
3240
|
+
["referenceText"],
|
|
3241
|
+
text,
|
|
3242
|
+
`The text to read aloud is at most ${READ_ALOUD_MAX_REFERENCE_LENGTH} characters, before and after normalisation.`
|
|
3243
|
+
);
|
|
3244
|
+
}
|
|
3245
|
+
if (measured.normalized === "") {
|
|
3246
|
+
report(
|
|
3247
|
+
["referenceText"],
|
|
3248
|
+
text,
|
|
3249
|
+
"The text to read aloud must contain a word: after ignoring punctuation and spacing nothing is left, so no word would be marked and every reading would align as one insertion after another."
|
|
3250
|
+
);
|
|
3251
|
+
}
|
|
3252
|
+
if (containsUnspacedScript(text)) {
|
|
3253
|
+
report(
|
|
3254
|
+
["referenceText"],
|
|
3255
|
+
text,
|
|
3256
|
+
"A read-aloud item needs a script written with spaces between its words. The text is compared word by word, and a run of Han, kana, Thai, Lao, Khmer or Myanmar letters is one word however many it holds."
|
|
3257
|
+
);
|
|
3258
|
+
}
|
|
3259
|
+
}
|
|
3260
|
+
const mediaParses = !refused.has("media");
|
|
3261
|
+
const slowParses = !refused.has("slowMedia");
|
|
3262
|
+
if (mediaParses && data.media !== void 0 && data.media.type !== "audio") {
|
|
3263
|
+
report(
|
|
3264
|
+
["media", "type"],
|
|
3265
|
+
data.media.type,
|
|
3266
|
+
"A read-aloud model is read aloud: its recording must be audio."
|
|
3267
|
+
);
|
|
3268
|
+
}
|
|
3269
|
+
if (mediaParses && slowParses && data.slowMedia !== void 0) {
|
|
3270
|
+
if (data.media === void 0) {
|
|
3271
|
+
report(
|
|
3272
|
+
["media"],
|
|
3273
|
+
data.media,
|
|
3274
|
+
"A slow model recording accompanies a recording: add `media` first."
|
|
3275
|
+
);
|
|
3276
|
+
}
|
|
3277
|
+
if (_optionalChain([data, 'access', _60 => _60.media, 'optionalAccess', _61 => _61.playback, 'optionalAccess', _62 => _62.maxPlays]) !== void 0) {
|
|
3278
|
+
report(
|
|
3279
|
+
["slowMedia"],
|
|
3280
|
+
data.slowMedia,
|
|
3281
|
+
"A play budget on `media` cannot coexist with an unbudgeted slow recording of the same content. Drop `slowMedia`, or drop `maxPlays`."
|
|
3282
|
+
);
|
|
3283
|
+
}
|
|
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) {
|
|
3285
|
+
report(
|
|
3286
|
+
["slowMedia", "url"],
|
|
3287
|
+
data.slowMedia.url,
|
|
3288
|
+
"The slow model recording must be a different file from the recording."
|
|
3289
|
+
);
|
|
3290
|
+
}
|
|
3291
|
+
}
|
|
3292
|
+
if (!refused.has("scoring")) {
|
|
3293
|
+
const dimensions = data.scoring.dimensions;
|
|
3294
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3295
|
+
for (const [index, dimension] of dimensions.entries()) {
|
|
3296
|
+
if (seen.has(dimension.name)) {
|
|
3297
|
+
report(
|
|
3298
|
+
["scoring", "dimensions", index, "name"],
|
|
3299
|
+
dimension.name,
|
|
3300
|
+
`"${dimension.name}" is already weighed: a dimension counted twice hides which weight the grade used.`
|
|
3301
|
+
);
|
|
3302
|
+
}
|
|
3303
|
+
seen.add(dimension.name);
|
|
3304
|
+
}
|
|
3305
|
+
if (dimensions.every((dimension) => dimension.weight === 0)) {
|
|
3306
|
+
report(
|
|
3307
|
+
["scoring", "dimensions"],
|
|
3308
|
+
dimensions,
|
|
3309
|
+
"At least one dimension must carry a weight above 0: with every weight at 0 there is no weighted total, and every take would be unscorable."
|
|
3310
|
+
);
|
|
3311
|
+
}
|
|
3312
|
+
}
|
|
3313
|
+
if (!refused.has("recording")) {
|
|
3314
|
+
const { minSeconds, maxSeconds } = data.recording;
|
|
3315
|
+
if (minSeconds !== void 0 && minSeconds >= maxSeconds) {
|
|
3316
|
+
report(
|
|
3317
|
+
["recording", "minSeconds"],
|
|
3318
|
+
minSeconds,
|
|
3319
|
+
"The shortest take must be shorter than the longest."
|
|
3320
|
+
);
|
|
3321
|
+
}
|
|
3322
|
+
}
|
|
3323
|
+
})
|
|
3324
|
+
);
|
|
3520
3325
|
|
|
3521
3326
|
// src/authoring/read-aloud.ts
|
|
3522
3327
|
var readAloudAuthoring = {
|
|
@@ -3723,222 +3528,546 @@ function checkDimensions(scoring) {
|
|
|
3723
3528
|
)
|
|
3724
3529
|
);
|
|
3725
3530
|
}
|
|
3726
|
-
return issues;
|
|
3727
|
-
}
|
|
3728
|
-
function dimensionsRequired() {
|
|
3729
|
-
return issue(
|
|
3730
|
-
"ra_dimensions_required",
|
|
3731
|
-
["scoring", "dimensions"],
|
|
3732
|
-
"Choose what the grade measures: at least one of accuracy, fluency, completeness or prosody, with a weight."
|
|
3733
|
-
);
|
|
3531
|
+
return issues;
|
|
3532
|
+
}
|
|
3533
|
+
function dimensionsRequired() {
|
|
3534
|
+
return issue(
|
|
3535
|
+
"ra_dimensions_required",
|
|
3536
|
+
["scoring", "dimensions"],
|
|
3537
|
+
"Choose what the grade measures: at least one of accuracy, fluency, completeness or prosody, with a weight."
|
|
3538
|
+
);
|
|
3539
|
+
}
|
|
3540
|
+
function checkModelRecording(media) {
|
|
3541
|
+
if (!isRecord(media)) {
|
|
3542
|
+
return [];
|
|
3543
|
+
}
|
|
3544
|
+
const kind = media.type;
|
|
3545
|
+
return isUnwritten(kind) || kind === "audio" ? [] : [
|
|
3546
|
+
issue(
|
|
3547
|
+
"ra_media_kind",
|
|
3548
|
+
["media", "type"],
|
|
3549
|
+
"A read-aloud model is read aloud: its recording must be audio."
|
|
3550
|
+
)
|
|
3551
|
+
];
|
|
3552
|
+
}
|
|
3553
|
+
function checkSlowRecording2(draft) {
|
|
3554
|
+
const slow = draft.slowMedia;
|
|
3555
|
+
if (!isRecord(slow)) {
|
|
3556
|
+
return [];
|
|
3557
|
+
}
|
|
3558
|
+
const kind = slow.type;
|
|
3559
|
+
const wrongKind = !isUnwritten(kind) && kind !== "audio";
|
|
3560
|
+
const issues = wrongKind ? [issue("media_invalid", ["slowMedia", "type"], "The slow model recording must be audio.")] : [];
|
|
3561
|
+
issues.push(
|
|
3562
|
+
...checkMedia(
|
|
3563
|
+
wrongKind ? { ...slow, type: "audio" } : slow,
|
|
3564
|
+
["slowMedia"],
|
|
3565
|
+
ReadAloudSlowMediaSchema
|
|
3566
|
+
)
|
|
3567
|
+
);
|
|
3568
|
+
const media = draft.media;
|
|
3569
|
+
if (isUnset(media)) {
|
|
3570
|
+
issues.push(
|
|
3571
|
+
issue(
|
|
3572
|
+
"ra_slow_media_without_media",
|
|
3573
|
+
["media"],
|
|
3574
|
+
"A slow model recording accompanies a recording. Add the recording first."
|
|
3575
|
+
)
|
|
3576
|
+
);
|
|
3577
|
+
return issues;
|
|
3578
|
+
}
|
|
3579
|
+
if (!isRecord(media)) {
|
|
3580
|
+
return issues;
|
|
3581
|
+
}
|
|
3582
|
+
if (isRecord(media.playback) && media.playback.maxPlays !== void 0) {
|
|
3583
|
+
issues.push(
|
|
3584
|
+
issue(
|
|
3585
|
+
"ra_slow_media_beside_play_limit",
|
|
3586
|
+
["slowMedia"],
|
|
3587
|
+
"A play budget on the recording cannot coexist with a slow recording that has none. Remove the slow recording, or remove the play limit."
|
|
3588
|
+
)
|
|
3589
|
+
);
|
|
3590
|
+
}
|
|
3591
|
+
if (typeof slow.url === "string" && slow.url !== "" && slow.url === media.url) {
|
|
3592
|
+
issues.push(
|
|
3593
|
+
issue(
|
|
3594
|
+
"ra_slow_media_same_recording",
|
|
3595
|
+
["slowMedia", "url"],
|
|
3596
|
+
"The slow model recording is the same file as the recording. Point it at the slower reading."
|
|
3597
|
+
)
|
|
3598
|
+
);
|
|
3599
|
+
}
|
|
3600
|
+
return issues;
|
|
3601
|
+
}
|
|
3602
|
+
|
|
3603
|
+
// src/authoring/written-response.ts
|
|
3604
|
+
var writtenResponseAuthoring = {
|
|
3605
|
+
createDraft: ({ newId }) => ({
|
|
3606
|
+
schemaVersion: "1.0",
|
|
3607
|
+
type: "written-response",
|
|
3608
|
+
id: newId(),
|
|
3609
|
+
title: "",
|
|
3610
|
+
prompt: "",
|
|
3611
|
+
minWords: 0,
|
|
3612
|
+
maxWords: 0
|
|
3613
|
+
}),
|
|
3614
|
+
checkDraft: checkWrittenResponseDraft
|
|
3615
|
+
};
|
|
3616
|
+
function checkWrittenResponseDraft(draft) {
|
|
3617
|
+
const issues = checkIdentity(draft, "written-response");
|
|
3618
|
+
if (isUnwritten(draft.prompt)) {
|
|
3619
|
+
issues.push(
|
|
3620
|
+
issue(
|
|
3621
|
+
"wr_prompt_required",
|
|
3622
|
+
["prompt"],
|
|
3623
|
+
isUnwritten(draft.promptHtml) ? "Write the prompt." : "Write the prompt as plain text too. The rich-text prompt is shown only where a sanitiser is supplied; the plain text is shown everywhere else."
|
|
3624
|
+
)
|
|
3625
|
+
);
|
|
3626
|
+
}
|
|
3627
|
+
const min = draft.minWords;
|
|
3628
|
+
const max = draft.maxWords;
|
|
3629
|
+
if (isUnset(min)) {
|
|
3630
|
+
issues.push(
|
|
3631
|
+
issue(
|
|
3632
|
+
"wr_min_words_required",
|
|
3633
|
+
["minWords"],
|
|
3634
|
+
"Set the minimum number of words. Use 0 for no minimum."
|
|
3635
|
+
)
|
|
3636
|
+
);
|
|
3637
|
+
} else if (!(isWholeNumber(min) && min >= 0)) {
|
|
3638
|
+
issues.push(
|
|
3639
|
+
issue(
|
|
3640
|
+
"wr_min_words_invalid",
|
|
3641
|
+
["minWords"],
|
|
3642
|
+
isTooLarge(min) ? "The minimum word count is too large." : "The minimum word count must be a whole number, 0 or more."
|
|
3643
|
+
)
|
|
3644
|
+
);
|
|
3645
|
+
}
|
|
3646
|
+
if (isUnset(max) || max === 0) {
|
|
3647
|
+
issues.push(issue("wr_max_words_required", ["maxWords"], "Set the maximum number of words."));
|
|
3648
|
+
} else if (!(isWholeNumber(max) && max >= 1)) {
|
|
3649
|
+
issues.push(
|
|
3650
|
+
issue(
|
|
3651
|
+
"wr_max_words_invalid",
|
|
3652
|
+
["maxWords"],
|
|
3653
|
+
isTooLarge(max) ? "The maximum word count is too large." : "The maximum word count must be a whole number, 1 or more."
|
|
3654
|
+
)
|
|
3655
|
+
);
|
|
3656
|
+
} else if (typeof min === "number" && max < min) {
|
|
3657
|
+
issues.push(
|
|
3658
|
+
issue(
|
|
3659
|
+
"wr_word_bounds_order",
|
|
3660
|
+
["maxWords"],
|
|
3661
|
+
"The maximum word count cannot be lower than the minimum."
|
|
3662
|
+
)
|
|
3663
|
+
);
|
|
3664
|
+
}
|
|
3665
|
+
const rubric = draft.rubric;
|
|
3666
|
+
if (isRecord(rubric)) {
|
|
3667
|
+
const criteria = isUnset(rubric.criteria) ? [] : rubric.criteria;
|
|
3668
|
+
if (Array.isArray(criteria)) {
|
|
3669
|
+
issues.push(...checkCriteria(criteria));
|
|
3670
|
+
}
|
|
3671
|
+
}
|
|
3672
|
+
issues.push(...checkSharedOptional(draft));
|
|
3673
|
+
return issues;
|
|
3674
|
+
}
|
|
3675
|
+
function checkCriteria(criteria) {
|
|
3676
|
+
const issues = [];
|
|
3677
|
+
if (criteria.length === 0) {
|
|
3678
|
+
issues.push(
|
|
3679
|
+
issue(
|
|
3680
|
+
"wr_rubric_criteria_required",
|
|
3681
|
+
["rubric", "criteria"],
|
|
3682
|
+
"Add at least one rubric criterion, or remove the rubric."
|
|
3683
|
+
)
|
|
3684
|
+
);
|
|
3685
|
+
return issues;
|
|
3686
|
+
}
|
|
3687
|
+
let totalWeight = 0;
|
|
3688
|
+
let everyWeightUsable = true;
|
|
3689
|
+
for (const [index, criterion] of criteria.entries()) {
|
|
3690
|
+
if (!isRecord(criterion)) {
|
|
3691
|
+
everyWeightUsable = false;
|
|
3692
|
+
continue;
|
|
3693
|
+
}
|
|
3694
|
+
const ordinal = index + 1;
|
|
3695
|
+
const path = ["rubric", "criteria", index];
|
|
3696
|
+
if (isUnwritten(criterion.name)) {
|
|
3697
|
+
issues.push(
|
|
3698
|
+
issue("wr_criterion_name_required", [...path, "name"], `Name rubric criterion ${ordinal}.`)
|
|
3699
|
+
);
|
|
3700
|
+
}
|
|
3701
|
+
const weight = criterion.weight;
|
|
3702
|
+
if (isUnset(weight)) {
|
|
3703
|
+
everyWeightUsable = false;
|
|
3704
|
+
issues.push(
|
|
3705
|
+
issue(
|
|
3706
|
+
"wr_criterion_weight_required",
|
|
3707
|
+
[...path, "weight"],
|
|
3708
|
+
`Give rubric criterion ${ordinal} a weight.`
|
|
3709
|
+
)
|
|
3710
|
+
);
|
|
3711
|
+
} else if (!(typeof weight === "number" && Number.isFinite(weight) && weight >= 0)) {
|
|
3712
|
+
everyWeightUsable = false;
|
|
3713
|
+
issues.push(
|
|
3714
|
+
issue(
|
|
3715
|
+
"wr_criterion_weight_invalid",
|
|
3716
|
+
[...path, "weight"],
|
|
3717
|
+
"A rubric weight must be a number, 0 or more."
|
|
3718
|
+
)
|
|
3719
|
+
);
|
|
3720
|
+
} else {
|
|
3721
|
+
totalWeight += weight;
|
|
3722
|
+
}
|
|
3723
|
+
}
|
|
3724
|
+
if (everyWeightUsable && totalWeight === 0) {
|
|
3725
|
+
issues.push(
|
|
3726
|
+
issue(
|
|
3727
|
+
"wr_rubric_weights_zero",
|
|
3728
|
+
["rubric", "criteria"],
|
|
3729
|
+
"Give at least one rubric criterion a weight above 0. With every weight at 0, no weighted total can be computed."
|
|
3730
|
+
)
|
|
3731
|
+
);
|
|
3732
|
+
} else if (totalWeight === Number.POSITIVE_INFINITY) {
|
|
3733
|
+
issues.push(
|
|
3734
|
+
issue(
|
|
3735
|
+
"wr_rubric_weights_too_large",
|
|
3736
|
+
["rubric", "criteria"],
|
|
3737
|
+
"The rubric weights add up to more than can be calculated with. Use smaller weights."
|
|
3738
|
+
)
|
|
3739
|
+
);
|
|
3740
|
+
}
|
|
3741
|
+
return issues;
|
|
3742
|
+
}
|
|
3743
|
+
|
|
3744
|
+
// src/schemas/gap-select.ts
|
|
3745
|
+
|
|
3746
|
+
var GapSelectChoiceSchema = _v4.z.looseObject({
|
|
3747
|
+
id: _v4.z.string().min(1),
|
|
3748
|
+
text: _v4.z.string().min(1)
|
|
3749
|
+
});
|
|
3750
|
+
var GapSelectBankSchema = _v4.z.looseObject({
|
|
3751
|
+
id: _v4.z.string().min(1),
|
|
3752
|
+
choices: _v4.z.array(GapSelectChoiceSchema).min(2)
|
|
3753
|
+
});
|
|
3754
|
+
var GapSelectGapSchema = _v4.z.looseObject({
|
|
3755
|
+
id: _v4.z.string().min(1),
|
|
3756
|
+
choices: _v4.z.array(GapSelectChoiceSchema).min(2).optional(),
|
|
3757
|
+
bankId: _v4.z.string().min(1).optional(),
|
|
3758
|
+
correctChoiceId: _v4.z.string().min(1),
|
|
3759
|
+
feedback: _v4.z.string().optional()
|
|
3760
|
+
});
|
|
3761
|
+
function resolveChoices2(gap, banks) {
|
|
3762
|
+
if (gap.choices !== void 0) {
|
|
3763
|
+
return gap.bankId === void 0 ? gap.choices : void 0;
|
|
3764
|
+
}
|
|
3765
|
+
if (gap.bankId === void 0) {
|
|
3766
|
+
return void 0;
|
|
3767
|
+
}
|
|
3768
|
+
return _optionalChain([banks, 'access', _65 => _65.find, 'call', _66 => _66((bank) => bank.id === gap.bankId), 'optionalAccess', _67 => _67.choices]);
|
|
3734
3769
|
}
|
|
3735
|
-
function
|
|
3736
|
-
|
|
3737
|
-
|
|
3770
|
+
function placeholderCounts(passage) {
|
|
3771
|
+
const counts = /* @__PURE__ */ new Map();
|
|
3772
|
+
for (const match of passage.matchAll(PLACEHOLDER_RE)) {
|
|
3773
|
+
const id = match[1];
|
|
3774
|
+
counts.set(id, (_nullishCoalesce(counts.get(id), () => ( 0))) + 1);
|
|
3738
3775
|
}
|
|
3739
|
-
|
|
3740
|
-
return isUnwritten(kind) || kind === "audio" ? [] : [
|
|
3741
|
-
issue(
|
|
3742
|
-
"ra_media_kind",
|
|
3743
|
-
["media", "type"],
|
|
3744
|
-
"A read-aloud model is read aloud: its recording must be audio."
|
|
3745
|
-
)
|
|
3746
|
-
];
|
|
3776
|
+
return counts;
|
|
3747
3777
|
}
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
3778
|
+
var GapSelectDataSchema = _v4.z.looseObject({
|
|
3779
|
+
schemaVersion: _v4.z.literal("1.0"),
|
|
3780
|
+
type: _v4.z.literal("gap-select"),
|
|
3781
|
+
id: _v4.z.string().min(1),
|
|
3782
|
+
title: _v4.z.string().min(1),
|
|
3783
|
+
passage: _v4.z.string().min(1),
|
|
3784
|
+
passageHtml: _v4.z.string().optional(),
|
|
3785
|
+
gaps: _v4.z.array(GapSelectGapSchema).min(1),
|
|
3786
|
+
banks: _v4.z.array(GapSelectBankSchema).optional(),
|
|
3787
|
+
scoringStrategy: _v4.z.enum(["all-or-nothing", "partial"]),
|
|
3788
|
+
presentation: _v4.z.literal("dropdown").optional(),
|
|
3789
|
+
shuffleChoices: _v4.z.boolean().optional(),
|
|
3790
|
+
media: MediaSchema.optional(),
|
|
3791
|
+
feedback: FeedbackSchema.optional(),
|
|
3792
|
+
passThreshold: _v4.z.number().min(0).max(1).optional(),
|
|
3793
|
+
locale: _v4.z.string().optional(),
|
|
3794
|
+
learningObjectives: _v4.z.array(_v4.z.string()).optional(),
|
|
3795
|
+
difficultyLevel: _v4.z.literal([1, 2, 3, 4, 5]).optional(),
|
|
3796
|
+
ai: AiPermissionsSchema.optional()
|
|
3797
|
+
}).refine(
|
|
3798
|
+
(data) => {
|
|
3799
|
+
const ids = (_nullishCoalesce(data.banks, () => ( []))).map((bank) => bank.id);
|
|
3800
|
+
return new Set(ids).size === ids.length;
|
|
3801
|
+
},
|
|
3802
|
+
{ error: "Word bank ids must be unique.", path: ["banks"] }
|
|
3803
|
+
).refine(
|
|
3804
|
+
(data) => {
|
|
3805
|
+
const ids = data.gaps.map((gap) => gap.id);
|
|
3806
|
+
return new Set(ids).size === ids.length;
|
|
3807
|
+
},
|
|
3808
|
+
{ error: "Gap ids must be unique.", path: ["gaps"] }
|
|
3809
|
+
).refine(
|
|
3810
|
+
(data) => {
|
|
3811
|
+
const counts = placeholderCounts(data.passage);
|
|
3812
|
+
const ids = data.gaps.map((gap) => gap.id);
|
|
3813
|
+
return counts.size === new Set(ids).size && ids.every((id) => counts.get(id) === 1) && [...counts.values()].every((count) => count === 1);
|
|
3814
|
+
},
|
|
3815
|
+
{
|
|
3816
|
+
error: "Every gap must appear exactly once in the passage as {{gap_id}}, and every {{gap_id}} must have a gap.",
|
|
3817
|
+
path: ["passage"]
|
|
3752
3818
|
}
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
wrongKind ? { ...slow, type: "audio" } : slow,
|
|
3759
|
-
["slowMedia"],
|
|
3760
|
-
ReadAloudSlowMediaSchema
|
|
3761
|
-
)
|
|
3762
|
-
);
|
|
3763
|
-
const media = draft.media;
|
|
3764
|
-
if (isUnset(media)) {
|
|
3765
|
-
issues.push(
|
|
3766
|
-
issue(
|
|
3767
|
-
"ra_slow_media_without_media",
|
|
3768
|
-
["media"],
|
|
3769
|
-
"A slow model recording accompanies a recording. Add the recording first."
|
|
3770
|
-
)
|
|
3771
|
-
);
|
|
3772
|
-
return issues;
|
|
3819
|
+
).refine(
|
|
3820
|
+
(data) => data.gaps.every((gap) => gap.choices === void 0 !== (gap.bankId === void 0)),
|
|
3821
|
+
{
|
|
3822
|
+
error: "Each gap needs exactly one choice source: its own `choices`, or a `bankId`.",
|
|
3823
|
+
path: ["gaps"]
|
|
3773
3824
|
}
|
|
3774
|
-
|
|
3775
|
-
|
|
3825
|
+
).refine(
|
|
3826
|
+
(data) => {
|
|
3827
|
+
const bankIds = new Set((_nullishCoalesce(data.banks, () => ( []))).map((bank) => bank.id));
|
|
3828
|
+
return data.gaps.every((gap) => gap.bankId === void 0 || bankIds.has(gap.bankId));
|
|
3829
|
+
},
|
|
3830
|
+
{ error: "A gap references a `bankId` that no word bank defines.", path: ["gaps"] }
|
|
3831
|
+
).refine(
|
|
3832
|
+
(data) => {
|
|
3833
|
+
const banks = _nullishCoalesce(data.banks, () => ( []));
|
|
3834
|
+
return data.gaps.every((gap) => {
|
|
3835
|
+
const choices = resolveChoices2(gap, banks);
|
|
3836
|
+
if (choices === void 0) {
|
|
3837
|
+
return true;
|
|
3838
|
+
}
|
|
3839
|
+
const ids = choices.map((choice) => choice.id);
|
|
3840
|
+
return new Set(ids).size === ids.length && ids.includes(gap.correctChoiceId);
|
|
3841
|
+
});
|
|
3842
|
+
},
|
|
3843
|
+
{
|
|
3844
|
+
error: "Each gap needs unique choice ids and a `correctChoiceId` that is one of the choices it offers.",
|
|
3845
|
+
path: ["gaps"]
|
|
3846
|
+
}
|
|
3847
|
+
);
|
|
3848
|
+
|
|
3849
|
+
// src/schemas/redacted.ts
|
|
3850
|
+
|
|
3851
|
+
|
|
3852
|
+
// src/schemas/written-response.ts
|
|
3853
|
+
|
|
3854
|
+
var WrittenResponseRubricCriterionSchema = _v4.z.looseObject({
|
|
3855
|
+
name: _v4.z.string().min(1),
|
|
3856
|
+
description: _v4.z.string().optional(),
|
|
3857
|
+
weight: _v4.z.number().min(0)
|
|
3858
|
+
});
|
|
3859
|
+
var WrittenResponseRubricSchema = _v4.z.looseObject({
|
|
3860
|
+
label: _v4.z.string().optional(),
|
|
3861
|
+
criteria: _v4.z.array(WrittenResponseRubricCriterionSchema).min(1)
|
|
3862
|
+
});
|
|
3863
|
+
var RedactedWrittenResponseRubricCriterionSchema = _v4.z.strictObject({
|
|
3864
|
+
name: _v4.z.string().min(1),
|
|
3865
|
+
description: _v4.z.string().optional(),
|
|
3866
|
+
weight: _v4.z.number().min(0)
|
|
3867
|
+
});
|
|
3868
|
+
var RedactedWrittenResponseRubricSchema = _v4.z.strictObject({
|
|
3869
|
+
label: _v4.z.string().optional(),
|
|
3870
|
+
criteria: _v4.z.array(RedactedWrittenResponseRubricCriterionSchema).min(1)
|
|
3871
|
+
});
|
|
3872
|
+
var WrittenResponseDataSchema = _v4.z.looseObject({
|
|
3873
|
+
schemaVersion: _v4.z.literal("1.0"),
|
|
3874
|
+
type: _v4.z.literal("written-response"),
|
|
3875
|
+
id: _v4.z.string().min(1),
|
|
3876
|
+
title: _v4.z.string().min(1),
|
|
3877
|
+
prompt: _v4.z.string(),
|
|
3878
|
+
promptHtml: _v4.z.string().optional(),
|
|
3879
|
+
minWords: _v4.z.number().int().min(0),
|
|
3880
|
+
maxWords: _v4.z.number().int().min(1),
|
|
3881
|
+
rubric: WrittenResponseRubricSchema.optional(),
|
|
3882
|
+
languageTarget: _v4.z.string().optional(),
|
|
3883
|
+
media: MediaSchema.optional(),
|
|
3884
|
+
feedback: FeedbackSchema.optional(),
|
|
3885
|
+
passThreshold: _v4.z.number().min(0).max(1).optional(),
|
|
3886
|
+
locale: _v4.z.string().optional(),
|
|
3887
|
+
learningObjectives: _v4.z.array(_v4.z.string()).optional(),
|
|
3888
|
+
difficultyLevel: _v4.z.literal([1, 2, 3, 4, 5]).optional(),
|
|
3889
|
+
ai: AiPermissionsSchema.optional()
|
|
3890
|
+
}).refine((data) => data.maxWords >= data.minWords, {
|
|
3891
|
+
error: "maxWords must be greater than or equal to minWords.",
|
|
3892
|
+
path: ["maxWords"]
|
|
3893
|
+
});
|
|
3894
|
+
|
|
3895
|
+
// src/schemas/redacted.ts
|
|
3896
|
+
var redactedBase = {
|
|
3897
|
+
/** Marker distinguishing a redacted projection from full activity data. */
|
|
3898
|
+
redacted: _v4.z.literal(true),
|
|
3899
|
+
/** Slot identity, carried through redaction so the client and the plan agree. */
|
|
3900
|
+
slotKey: _v4.z.string().min(1).optional(),
|
|
3901
|
+
schemaVersion: _v4.z.literal("1.0"),
|
|
3902
|
+
id: _v4.z.string().min(1),
|
|
3903
|
+
title: _v4.z.string().min(1),
|
|
3904
|
+
media: RedactedMediaSchema.optional(),
|
|
3905
|
+
passThreshold: _v4.z.number().min(0).max(1).optional(),
|
|
3906
|
+
locale: _v4.z.string().optional(),
|
|
3907
|
+
learningObjectives: _v4.z.array(_v4.z.string()).optional(),
|
|
3908
|
+
difficultyLevel: _v4.z.literal([1, 2, 3, 4, 5]).optional(),
|
|
3909
|
+
ai: _v4.z.strictObject({ explanations: _v4.z.boolean().optional(), hints: _v4.z.boolean().optional() }).optional()
|
|
3910
|
+
};
|
|
3911
|
+
var RedactedMultipleChoiceOptionMediaSchema = _v4.z.strictObject({
|
|
3912
|
+
type: _v4.z.enum(["image", "audio"]),
|
|
3913
|
+
url: _v4.z.string().min(1),
|
|
3914
|
+
alt: _v4.z.string().min(1).optional(),
|
|
3915
|
+
captionsUrl: _v4.z.string().min(1).optional()
|
|
3916
|
+
});
|
|
3917
|
+
var RedactedMultipleChoiceOptionSchema = _v4.z.strictObject({
|
|
3918
|
+
id: _v4.z.string().min(1),
|
|
3919
|
+
text: _v4.z.string().min(1),
|
|
3920
|
+
media: RedactedMultipleChoiceOptionMediaSchema.optional()
|
|
3921
|
+
});
|
|
3922
|
+
var RedactedMultipleChoiceDataSchema = _v4.z.strictObject({
|
|
3923
|
+
...redactedBase,
|
|
3924
|
+
type: _v4.z.literal("multiple-choice"),
|
|
3925
|
+
question: _v4.z.string().min(1),
|
|
3926
|
+
questionHtml: _v4.z.string().optional(),
|
|
3927
|
+
mode: _v4.z.enum(["single", "multi"]),
|
|
3928
|
+
options: _v4.z.array(RedactedMultipleChoiceOptionSchema).min(2).max(26),
|
|
3929
|
+
shuffle: _v4.z.boolean().optional()
|
|
3930
|
+
});
|
|
3931
|
+
var RedactedBlankConfigSchema = _v4.z.strictObject({
|
|
3932
|
+
id: _v4.z.string().min(1),
|
|
3933
|
+
hint: _v4.z.string().optional()
|
|
3934
|
+
});
|
|
3935
|
+
var RedactedFillInTheBlanksDataSchema = _v4.z.strictObject({
|
|
3936
|
+
...redactedBase,
|
|
3937
|
+
type: _v4.z.literal("fill-in-the-blanks"),
|
|
3938
|
+
passage: _v4.z.string().min(1),
|
|
3939
|
+
passageHtml: _v4.z.string().optional(),
|
|
3940
|
+
blanks: _v4.z.array(RedactedBlankConfigSchema).min(1)
|
|
3941
|
+
});
|
|
3942
|
+
var RedactedGapSelectChoiceSchema = _v4.z.strictObject({
|
|
3943
|
+
id: _v4.z.string().min(1),
|
|
3944
|
+
text: _v4.z.string().min(1)
|
|
3945
|
+
});
|
|
3946
|
+
var RedactedGapSelectBankSchema = _v4.z.strictObject({
|
|
3947
|
+
id: _v4.z.string().min(1),
|
|
3948
|
+
choices: _v4.z.array(RedactedGapSelectChoiceSchema).min(2)
|
|
3949
|
+
});
|
|
3950
|
+
var RedactedGapSelectGapSchema = _v4.z.strictObject({
|
|
3951
|
+
id: _v4.z.string().min(1),
|
|
3952
|
+
choices: _v4.z.array(RedactedGapSelectChoiceSchema).min(2).optional(),
|
|
3953
|
+
bankId: _v4.z.string().min(1).optional()
|
|
3954
|
+
});
|
|
3955
|
+
var RedactedGapSelectDataSchema = _v4.z.strictObject({
|
|
3956
|
+
...redactedBase,
|
|
3957
|
+
type: _v4.z.literal("gap-select"),
|
|
3958
|
+
passage: _v4.z.string().min(1),
|
|
3959
|
+
passageHtml: _v4.z.string().optional(),
|
|
3960
|
+
gaps: _v4.z.array(RedactedGapSelectGapSchema).min(1),
|
|
3961
|
+
banks: _v4.z.array(RedactedGapSelectBankSchema).optional(),
|
|
3962
|
+
presentation: _v4.z.literal("dropdown").optional(),
|
|
3963
|
+
shuffleChoices: _v4.z.boolean().optional()
|
|
3964
|
+
});
|
|
3965
|
+
var RedactedWrittenResponseDataSchema = _v4.z.strictObject({
|
|
3966
|
+
...redactedBase,
|
|
3967
|
+
type: _v4.z.literal("written-response"),
|
|
3968
|
+
prompt: _v4.z.string(),
|
|
3969
|
+
promptHtml: _v4.z.string().optional(),
|
|
3970
|
+
minWords: _v4.z.number().int().min(0),
|
|
3971
|
+
maxWords: _v4.z.number().int().min(1),
|
|
3972
|
+
rubric: RedactedWrittenResponseRubricSchema.optional(),
|
|
3973
|
+
languageTarget: _v4.z.string().optional()
|
|
3974
|
+
});
|
|
3975
|
+
var RedactedDictationSlowMediaSchema = _v4.z.strictObject({
|
|
3976
|
+
type: _v4.z.literal("audio"),
|
|
3977
|
+
url: MediaUrlSchema,
|
|
3978
|
+
alt: _v4.z.string().min(1).optional()
|
|
3979
|
+
});
|
|
3980
|
+
var RedactedDictationDataSchema = _v4.z.strictObject({
|
|
3981
|
+
...redactedBase,
|
|
3982
|
+
type: _v4.z.literal("dictation"),
|
|
3983
|
+
slowMedia: RedactedDictationSlowMediaSchema.optional(),
|
|
3984
|
+
hints: _v4.z.strictObject({ mode: _v4.z.literal("progressive-words") }).optional()
|
|
3985
|
+
}).check((ctx) => {
|
|
3986
|
+
const data = ctx.value;
|
|
3987
|
+
for (const captions of captionFieldsOf(data.media)) {
|
|
3988
|
+
ctx.issues.push({
|
|
3989
|
+
code: "custom",
|
|
3990
|
+
input: _optionalChain([data, 'access', _68 => _68.media, 'optionalAccess', _69 => _69[captions]]),
|
|
3991
|
+
message: "A dictation recording cannot carry captions: the captions are the answer, so this payload is not learner-safe.",
|
|
3992
|
+
path: ["media", captions]
|
|
3993
|
+
});
|
|
3776
3994
|
}
|
|
3777
|
-
if (
|
|
3778
|
-
issues.push(
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
);
|
|
3995
|
+
if (data.slowMedia !== void 0 && _optionalChain([data, 'access', _70 => _70.media, 'optionalAccess', _71 => _71.playback, 'optionalAccess', _72 => _72.maxPlays]) !== void 0) {
|
|
3996
|
+
ctx.issues.push({
|
|
3997
|
+
code: "custom",
|
|
3998
|
+
input: data.slowMedia,
|
|
3999
|
+
message: "A play budget on `media` cannot coexist with an unbudgeted slow recording of the same content.",
|
|
4000
|
+
path: ["slowMedia"]
|
|
4001
|
+
});
|
|
3785
4002
|
}
|
|
3786
|
-
if (
|
|
3787
|
-
issues.push(
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
);
|
|
4003
|
+
if (data.slowMedia !== void 0 && data.media === void 0) {
|
|
4004
|
+
ctx.issues.push({
|
|
4005
|
+
code: "custom",
|
|
4006
|
+
input: data.slowMedia,
|
|
4007
|
+
message: "A slow recording accompanies a recording: this payload has no `media`.",
|
|
4008
|
+
path: ["media"]
|
|
4009
|
+
});
|
|
3794
4010
|
}
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
maxWords: 0
|
|
4011
|
+
});
|
|
4012
|
+
var RedactedReadAloudDataSchema = _v4.z.strictObject({
|
|
4013
|
+
...redactedBase,
|
|
4014
|
+
type: _v4.z.literal("read-aloud"),
|
|
4015
|
+
locale: _v4.z.string().regex(CANONICAL_LOCALE_RE),
|
|
4016
|
+
instructions: _v4.z.string().optional(),
|
|
4017
|
+
referenceText: _v4.z.string().min(1),
|
|
4018
|
+
slowMedia: ReadAloudSlowMediaSchema.optional(),
|
|
4019
|
+
recording: _v4.z.strictObject({
|
|
4020
|
+
maxSeconds: _v4.z.number().gt(0).max(READ_ALOUD_MAX_SECONDS),
|
|
4021
|
+
minSeconds: _v4.z.number().min(0).optional(),
|
|
4022
|
+
maxTakes: _v4.z.number().int().min(1).max(READ_ALOUD_MAX_TAKES).optional()
|
|
3808
4023
|
}),
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
issue(
|
|
3827
|
-
"wr_min_words_required",
|
|
3828
|
-
["minWords"],
|
|
3829
|
-
"Set the minimum number of words. Use 0 for no minimum."
|
|
3830
|
-
)
|
|
3831
|
-
);
|
|
3832
|
-
} else if (!(isWholeNumber(min) && min >= 0)) {
|
|
3833
|
-
issues.push(
|
|
3834
|
-
issue(
|
|
3835
|
-
"wr_min_words_invalid",
|
|
3836
|
-
["minWords"],
|
|
3837
|
-
isTooLarge(min) ? "The minimum word count is too large." : "The minimum word count must be a whole number, 0 or more."
|
|
3838
|
-
)
|
|
3839
|
-
);
|
|
3840
|
-
}
|
|
3841
|
-
if (isUnset(max) || max === 0) {
|
|
3842
|
-
issues.push(issue("wr_max_words_required", ["maxWords"], "Set the maximum number of words."));
|
|
3843
|
-
} else if (!(isWholeNumber(max) && max >= 1)) {
|
|
3844
|
-
issues.push(
|
|
3845
|
-
issue(
|
|
3846
|
-
"wr_max_words_invalid",
|
|
3847
|
-
["maxWords"],
|
|
3848
|
-
isTooLarge(max) ? "The maximum word count is too large." : "The maximum word count must be a whole number, 1 or more."
|
|
3849
|
-
)
|
|
3850
|
-
);
|
|
3851
|
-
} else if (typeof min === "number" && max < min) {
|
|
3852
|
-
issues.push(
|
|
3853
|
-
issue(
|
|
3854
|
-
"wr_word_bounds_order",
|
|
3855
|
-
["maxWords"],
|
|
3856
|
-
"The maximum word count cannot be lower than the minimum."
|
|
3857
|
-
)
|
|
3858
|
-
);
|
|
3859
|
-
}
|
|
3860
|
-
const rubric = draft.rubric;
|
|
3861
|
-
if (isRecord(rubric)) {
|
|
3862
|
-
const criteria = isUnset(rubric.criteria) ? [] : rubric.criteria;
|
|
3863
|
-
if (Array.isArray(criteria)) {
|
|
3864
|
-
issues.push(...checkCriteria(criteria));
|
|
3865
|
-
}
|
|
4024
|
+
scoring: _v4.z.strictObject({
|
|
4025
|
+
dimensions: _v4.z.array(
|
|
4026
|
+
_v4.z.strictObject({
|
|
4027
|
+
name: _v4.z.enum(READ_ALOUD_DIMENSIONS),
|
|
4028
|
+
weight: _v4.z.number().min(0).max(READ_ALOUD_MAX_DIMENSION_WEIGHT)
|
|
4029
|
+
})
|
|
4030
|
+
).min(1).max(READ_ALOUD_DIMENSIONS.length)
|
|
4031
|
+
})
|
|
4032
|
+
}).check((ctx) => {
|
|
4033
|
+
const data = ctx.value;
|
|
4034
|
+
if (data.media !== void 0 && data.media.type !== "audio") {
|
|
4035
|
+
ctx.issues.push({
|
|
4036
|
+
code: "custom",
|
|
4037
|
+
input: data.media.type,
|
|
4038
|
+
message: "A read-aloud model recording must be audio, so this payload is not renderable.",
|
|
4039
|
+
path: ["media", "type"]
|
|
4040
|
+
});
|
|
3866
4041
|
}
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
|
|
3871
|
-
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
issue(
|
|
3875
|
-
"wr_rubric_criteria_required",
|
|
3876
|
-
["rubric", "criteria"],
|
|
3877
|
-
"Add at least one rubric criterion, or remove the rubric."
|
|
3878
|
-
)
|
|
3879
|
-
);
|
|
3880
|
-
return issues;
|
|
4042
|
+
if (data.slowMedia !== void 0 && data.media === void 0) {
|
|
4043
|
+
ctx.issues.push({
|
|
4044
|
+
code: "custom",
|
|
4045
|
+
input: data.slowMedia,
|
|
4046
|
+
message: "A slow model recording accompanies a recording: this payload has no `media`.",
|
|
4047
|
+
path: ["media"]
|
|
4048
|
+
});
|
|
3881
4049
|
}
|
|
3882
|
-
|
|
3883
|
-
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
}
|
|
3889
|
-
const ordinal = index + 1;
|
|
3890
|
-
const path = ["rubric", "criteria", index];
|
|
3891
|
-
if (isUnwritten(criterion.name)) {
|
|
3892
|
-
issues.push(
|
|
3893
|
-
issue("wr_criterion_name_required", [...path, "name"], `Name rubric criterion ${ordinal}.`)
|
|
3894
|
-
);
|
|
3895
|
-
}
|
|
3896
|
-
const weight = criterion.weight;
|
|
3897
|
-
if (isUnset(weight)) {
|
|
3898
|
-
everyWeightUsable = false;
|
|
3899
|
-
issues.push(
|
|
3900
|
-
issue(
|
|
3901
|
-
"wr_criterion_weight_required",
|
|
3902
|
-
[...path, "weight"],
|
|
3903
|
-
`Give rubric criterion ${ordinal} a weight.`
|
|
3904
|
-
)
|
|
3905
|
-
);
|
|
3906
|
-
} else if (!(typeof weight === "number" && Number.isFinite(weight) && weight >= 0)) {
|
|
3907
|
-
everyWeightUsable = false;
|
|
3908
|
-
issues.push(
|
|
3909
|
-
issue(
|
|
3910
|
-
"wr_criterion_weight_invalid",
|
|
3911
|
-
[...path, "weight"],
|
|
3912
|
-
"A rubric weight must be a number, 0 or more."
|
|
3913
|
-
)
|
|
3914
|
-
);
|
|
3915
|
-
} else {
|
|
3916
|
-
totalWeight += weight;
|
|
3917
|
-
}
|
|
4050
|
+
if (data.slowMedia !== void 0 && _optionalChain([data, 'access', _73 => _73.media, 'optionalAccess', _74 => _74.playback, 'optionalAccess', _75 => _75.maxPlays]) !== void 0) {
|
|
4051
|
+
ctx.issues.push({
|
|
4052
|
+
code: "custom",
|
|
4053
|
+
input: data.slowMedia,
|
|
4054
|
+
message: "A play budget on `media` cannot coexist with an unbudgeted slow recording of the same content.",
|
|
4055
|
+
path: ["slowMedia"]
|
|
4056
|
+
});
|
|
3918
4057
|
}
|
|
3919
|
-
if (
|
|
3920
|
-
issues.push(
|
|
3921
|
-
|
|
3922
|
-
|
|
3923
|
-
|
|
3924
|
-
|
|
3925
|
-
|
|
3926
|
-
);
|
|
3927
|
-
} else if (totalWeight === Number.POSITIVE_INFINITY) {
|
|
3928
|
-
issues.push(
|
|
3929
|
-
issue(
|
|
3930
|
-
"wr_rubric_weights_too_large",
|
|
3931
|
-
["rubric", "criteria"],
|
|
3932
|
-
"The rubric weights add up to more than can be calculated with. Use smaller weights."
|
|
3933
|
-
)
|
|
3934
|
-
);
|
|
4058
|
+
if (data.slowMedia !== void 0 && data.slowMedia.url === _optionalChain([data, 'access', _76 => _76.media, 'optionalAccess', _77 => _77.url])) {
|
|
4059
|
+
ctx.issues.push({
|
|
4060
|
+
code: "custom",
|
|
4061
|
+
input: data.slowMedia.url,
|
|
4062
|
+
message: "The slow model recording must be a different file from the recording.",
|
|
4063
|
+
path: ["slowMedia", "url"]
|
|
4064
|
+
});
|
|
3935
4065
|
}
|
|
3936
|
-
|
|
3937
|
-
}
|
|
4066
|
+
});
|
|
3938
4067
|
|
|
3939
4068
|
// src/scoring/activity-scorers/dictation.ts
|
|
3940
4069
|
function scoreDictation(data, response) {
|
|
3941
|
-
const alignment = alignDictation(data, typeof _optionalChain([response, 'optionalAccess',
|
|
4070
|
+
const alignment = alignDictation(data, typeof _optionalChain([response, 'optionalAccess', _78 => _78.text]) === "string" ? response.text : "");
|
|
3942
4071
|
const details = [];
|
|
3943
4072
|
for (const word of alignment.words) {
|
|
3944
4073
|
if (word.status === "extra") {
|
|
@@ -3946,7 +4075,6 @@ function scoreDictation(data, response) {
|
|
|
3946
4075
|
}
|
|
3947
4076
|
details.push({
|
|
3948
4077
|
itemId: word.itemId,
|
|
3949
|
-
correct: word.status === "correct",
|
|
3950
4078
|
outcome: word.status === "correct" ? "correct" : word.status === "incorrect" ? "incorrect" : "incorrect-omission",
|
|
3951
4079
|
learnerResponse: word.attempt,
|
|
3952
4080
|
correctResponse: word.reference,
|
|
@@ -3990,7 +4118,6 @@ function scoreFillInTheBlanks(data, response) {
|
|
|
3990
4118
|
perBlankCorrect.push(matched);
|
|
3991
4119
|
details.push({
|
|
3992
4120
|
itemId: blank.id,
|
|
3993
|
-
correct: matched,
|
|
3994
4121
|
outcome: matched ? "correct" : "incorrect",
|
|
3995
4122
|
learnerResponse: [input],
|
|
3996
4123
|
correctResponse: [...blank.acceptedAnswers],
|
|
@@ -4007,7 +4134,7 @@ function choicesFor(data, gap) {
|
|
|
4007
4134
|
if (gap.choices !== void 0) {
|
|
4008
4135
|
return gap.choices;
|
|
4009
4136
|
}
|
|
4010
|
-
return _nullishCoalesce(_optionalChain([data, 'access',
|
|
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]), () => ( []));
|
|
4011
4138
|
}
|
|
4012
4139
|
function scoreGapSelect(data, response) {
|
|
4013
4140
|
const details = [];
|
|
@@ -4022,7 +4149,6 @@ function scoreGapSelect(data, response) {
|
|
|
4022
4149
|
perGapCorrect.push(correct);
|
|
4023
4150
|
details.push({
|
|
4024
4151
|
itemId: gap.id,
|
|
4025
|
-
correct,
|
|
4026
4152
|
outcome: correct ? "correct" : answered ? "incorrect" : "incorrect-omission",
|
|
4027
4153
|
learnerResponse: [selected],
|
|
4028
4154
|
correctResponse: [gap.correctChoiceId],
|
|
@@ -4043,7 +4169,7 @@ function scoreMultipleChoice(data, response) {
|
|
|
4043
4169
|
let scoreValue;
|
|
4044
4170
|
if (data.scoringStrategy === "all-or-nothing") {
|
|
4045
4171
|
if (data.mode === "single") {
|
|
4046
|
-
scoreValue = response.selectedOptionIds.length === 1 && _optionalChain([optionById, 'access',
|
|
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;
|
|
4047
4173
|
} else {
|
|
4048
4174
|
const correctIds = data.options.filter((o) => o.isCorrect).map((o) => o.id);
|
|
4049
4175
|
const allCorrectSelected = correctIds.every((id) => selected.has(id));
|
|
@@ -4054,7 +4180,7 @@ function scoreMultipleChoice(data, response) {
|
|
|
4054
4180
|
let incorrectSelected = 0;
|
|
4055
4181
|
for (const id of selected) {
|
|
4056
4182
|
const option = optionById.get(id);
|
|
4057
|
-
if (_optionalChain([option, 'optionalAccess',
|
|
4183
|
+
if (_optionalChain([option, 'optionalAccess', _86 => _86.isCorrect])) {
|
|
4058
4184
|
correctSelected += 1;
|
|
4059
4185
|
} else {
|
|
4060
4186
|
incorrectSelected += 1;
|
|
@@ -4067,7 +4193,6 @@ function scoreMultipleChoice(data, response) {
|
|
|
4067
4193
|
const outcome = wasSelected ? option.isCorrect ? "correct" : "incorrect" : option.isCorrect ? "incorrect-omission" : "correct-omission";
|
|
4068
4194
|
return {
|
|
4069
4195
|
itemId: option.id,
|
|
4070
|
-
correct: wasSelected === option.isCorrect,
|
|
4071
4196
|
outcome,
|
|
4072
4197
|
learnerResponse: [wasSelected ? "selected" : "not-selected"],
|
|
4073
4198
|
correctResponse: [option.isCorrect ? "selected" : "not-selected"],
|
|
@@ -4238,7 +4363,7 @@ var multipleChoiceType = defineActivityType({
|
|
|
4238
4363
|
// exact optionals. Structurally identical at runtime — cast is type-level only.
|
|
4239
4364
|
schema: MultipleChoiceDataSchema,
|
|
4240
4365
|
scoring: { kind: "sync", score: scoreMultipleChoice },
|
|
4241
|
-
isAnswered: (response) => (_nullishCoalesce(_optionalChain([response, 'optionalAccess',
|
|
4366
|
+
isAnswered: (response) => (_nullishCoalesce(_optionalChain([response, 'optionalAccess', _87 => _87.selectedOptionIds, 'access', _88 => _88.length]), () => ( 0))) > 0,
|
|
4242
4367
|
fieldPolicy: MULTIPLE_CHOICE_FIELD_POLICY,
|
|
4243
4368
|
redactedSchema: RedactedMultipleChoiceDataSchema,
|
|
4244
4369
|
interop: {
|
|
@@ -4255,7 +4380,7 @@ var fillInTheBlanksType = defineActivityType({
|
|
|
4255
4380
|
type: "fill-in-the-blanks",
|
|
4256
4381
|
schema: FillInTheBlanksDataSchema,
|
|
4257
4382
|
scoring: { kind: "sync", score: scoreFillInTheBlanks },
|
|
4258
|
-
isAnswered: (response) => Object.values(_nullishCoalesce(_optionalChain([response, 'optionalAccess',
|
|
4383
|
+
isAnswered: (response) => Object.values(_nullishCoalesce(_optionalChain([response, 'optionalAccess', _89 => _89.answers]), () => ( {}))).some((answer) => answer.trim().length > 0),
|
|
4259
4384
|
fieldPolicy: FILL_IN_THE_BLANKS_FIELD_POLICY,
|
|
4260
4385
|
redactedSchema: RedactedFillInTheBlanksDataSchema,
|
|
4261
4386
|
interop: {
|
|
@@ -4278,14 +4403,14 @@ var writtenResponseType = defineActivityType({
|
|
|
4278
4403
|
kind: "deferred",
|
|
4279
4404
|
reason: "requires_async_grading",
|
|
4280
4405
|
partial: (data, response) => {
|
|
4281
|
-
const wordCount = countWords(_nullishCoalesce(_optionalChain([response, 'optionalAccess',
|
|
4406
|
+
const wordCount = countWords(_nullishCoalesce(_optionalChain([response, 'optionalAccess', _90 => _90.text]), () => ( "")));
|
|
4282
4407
|
return {
|
|
4283
4408
|
withinWordBounds: response !== void 0 && wordCount >= data.minWords && wordCount <= data.maxWords,
|
|
4284
4409
|
wordCount
|
|
4285
4410
|
};
|
|
4286
4411
|
}
|
|
4287
4412
|
},
|
|
4288
|
-
isAnswered: (response) => (_nullishCoalesce(_optionalChain([response, 'optionalAccess',
|
|
4413
|
+
isAnswered: (response) => (_nullishCoalesce(_optionalChain([response, 'optionalAccess', _91 => _91.text, 'access', _92 => _92.trim, 'call', _93 => _93(), 'access', _94 => _94.length]), () => ( 0))) > 0,
|
|
4289
4414
|
fieldPolicy: WRITTEN_RESPONSE_FIELD_POLICY,
|
|
4290
4415
|
redactedSchema: RedactedWrittenResponseDataSchema,
|
|
4291
4416
|
interop: {
|
|
@@ -4300,7 +4425,7 @@ var gapSelectType = defineActivityType({
|
|
|
4300
4425
|
type: "gap-select",
|
|
4301
4426
|
schema: GapSelectDataSchema,
|
|
4302
4427
|
scoring: { kind: "sync", score: scoreGapSelect },
|
|
4303
|
-
isAnswered: (response) => Object.values(_nullishCoalesce(_optionalChain([response, 'optionalAccess',
|
|
4428
|
+
isAnswered: (response) => Object.values(_nullishCoalesce(_optionalChain([response, 'optionalAccess', _95 => _95.selections]), () => ( {}))).some((choiceId) => choiceId !== ""),
|
|
4304
4429
|
fieldPolicy: GAP_SELECT_FIELD_POLICY,
|
|
4305
4430
|
redactedSchema: RedactedGapSelectDataSchema,
|
|
4306
4431
|
interop: {
|
|
@@ -4331,7 +4456,7 @@ var dictationType = defineActivityType({
|
|
|
4331
4456
|
// cut where the scorer cuts, so text past the cap never counts as an answer.
|
|
4332
4457
|
isAnswered: (response) => normalizeDictationText(
|
|
4333
4458
|
truncateCodePoints(
|
|
4334
|
-
typeof _optionalChain([response, 'optionalAccess',
|
|
4459
|
+
typeof _optionalChain([response, 'optionalAccess', _96 => _96.text]) === "string" ? response.text : "",
|
|
4335
4460
|
DICTATION_MAX_TEXT_LENGTH
|
|
4336
4461
|
)
|
|
4337
4462
|
) !== "",
|
|
@@ -4357,7 +4482,7 @@ var READ_ALOUD_FIELD_POLICY = {
|
|
|
4357
4482
|
feedback: FEEDBACK_FIELD_POLICY
|
|
4358
4483
|
};
|
|
4359
4484
|
function hasRecording(response) {
|
|
4360
|
-
const recording = _optionalChain([response, 'optionalAccess',
|
|
4485
|
+
const recording = _optionalChain([response, 'optionalAccess', _97 => _97.recording]);
|
|
4361
4486
|
return typeof recording === "object" && recording !== null && typeof recording.key === "string" && recording.key !== "";
|
|
4362
4487
|
}
|
|
4363
4488
|
var readAloudType = defineActivityType({
|
|
@@ -4383,6 +4508,19 @@ var readAloudType = defineActivityType({
|
|
|
4383
4508
|
interactions: ["recording-started", "recording-stopped", "recording-uploaded", "submitted"],
|
|
4384
4509
|
authoring: readAloudAuthoring
|
|
4385
4510
|
});
|
|
4511
|
+
for (const descriptor of [
|
|
4512
|
+
multipleChoiceType,
|
|
4513
|
+
fillInTheBlanksType,
|
|
4514
|
+
writtenResponseType,
|
|
4515
|
+
gapSelectType,
|
|
4516
|
+
dictationType,
|
|
4517
|
+
readAloudType
|
|
4518
|
+
]) {
|
|
4519
|
+
ownSchemas(
|
|
4520
|
+
descriptor.schema,
|
|
4521
|
+
...descriptor.redactedSchema !== void 0 ? [descriptor.redactedSchema] : []
|
|
4522
|
+
);
|
|
4523
|
+
}
|
|
4386
4524
|
registerActivityType(multipleChoiceType);
|
|
4387
4525
|
registerActivityType(fillInTheBlanksType);
|
|
4388
4526
|
registerActivityType(writtenResponseType);
|
|
@@ -4455,32 +4593,5 @@ registerActivityType(readAloudType);
|
|
|
4455
4593
|
|
|
4456
4594
|
|
|
4457
4595
|
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
|
|
4477
|
-
|
|
4478
|
-
|
|
4479
|
-
|
|
4480
|
-
|
|
4481
|
-
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
exports.AiPermissionsSchema = AiPermissionsSchema; exports.FeedbackSchema = FeedbackSchema; exports.MediaUrlSchema = MediaUrlSchema; exports.NativeControlHintSchema = NativeControlHintSchema; exports.MediaPlaybackSchema = MediaPlaybackSchema; exports.MediaTrackSchema = MediaTrackSchema; exports.MediaSchema = MediaSchema; exports.RedactedMediaSchema = RedactedMediaSchema; exports.PLACEHOLDER_RE = PLACEHOLDER_RE; exports.TextMatchPolicySchema = TextMatchPolicySchema; exports.BlankConfigSchema = BlankConfigSchema; 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.DictationSlowMediaSchema = DictationSlowMediaSchema; exports.DictationEquivalenceSchema = DictationEquivalenceSchema; exports.DictationToleranceSchema = DictationToleranceSchema; 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.MultipleChoiceOptionSchema = MultipleChoiceOptionSchema; 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.ReadAloudSlowMediaSchema = ReadAloudSlowMediaSchema; exports.ReadAloudDataSchema = ReadAloudDataSchema; exports.countWords = countWords; exports.GapSelectChoiceSchema = GapSelectChoiceSchema; exports.GapSelectBankSchema = GapSelectBankSchema; exports.GapSelectGapSchema = GapSelectGapSchema; exports.GapSelectDataSchema = GapSelectDataSchema; exports.WrittenResponseRubricCriterionSchema = WrittenResponseRubricCriterionSchema; exports.WrittenResponseRubricSchema = WrittenResponseRubricSchema; exports.WrittenResponseDataSchema = WrittenResponseDataSchema; exports.RedactedMultipleChoiceOptionMediaSchema = RedactedMultipleChoiceOptionMediaSchema; exports.RedactedMultipleChoiceOptionSchema = RedactedMultipleChoiceOptionSchema; exports.RedactedMultipleChoiceDataSchema = RedactedMultipleChoiceDataSchema; exports.RedactedBlankConfigSchema = RedactedBlankConfigSchema; exports.RedactedFillInTheBlanksDataSchema = RedactedFillInTheBlanksDataSchema; exports.RedactedGapSelectChoiceSchema = RedactedGapSelectChoiceSchema; exports.RedactedGapSelectBankSchema = RedactedGapSelectBankSchema; exports.RedactedGapSelectGapSchema = RedactedGapSelectGapSchema; exports.RedactedGapSelectDataSchema = RedactedGapSelectDataSchema; exports.RedactedWrittenResponseDataSchema = RedactedWrittenResponseDataSchema; exports.RedactedDictationSlowMediaSchema = RedactedDictationSlowMediaSchema; exports.RedactedDictationDataSchema = RedactedDictationDataSchema; exports.RedactedReadAloudDataSchema = RedactedReadAloudDataSchema; 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;
|
|
4486
|
-
//# sourceMappingURL=chunk-D4TOPSHV.cjs.map
|
|
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
|