@intellectif/lk-core 0.13.1 → 0.14.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 +23 -0
- package/README.md +1 -1
- package/dist/{activity-DfAmJ1sl.d.cts → activity-BVkg9VDo.d.cts} +153 -4
- package/dist/{activity-DfAmJ1sl.d.ts → activity-BVkg9VDo.d.ts} +153 -4
- package/dist/{chunk-DQKJUMUW.cjs → chunk-2NIALQEH.cjs} +24 -19
- package/dist/chunk-2NIALQEH.cjs.map +1 -0
- package/dist/{chunk-XKKKWKRH.js → chunk-6RND47QE.js} +694 -126
- package/dist/chunk-6RND47QE.js.map +1 -0
- package/dist/{chunk-FT7SAC3X.cjs → chunk-CZRUMHKW.cjs} +4 -4
- package/dist/{chunk-FT7SAC3X.cjs.map → chunk-CZRUMHKW.cjs.map} +1 -1
- package/dist/chunk-J5OIRIOW.cjs +1034 -0
- package/dist/chunk-J5OIRIOW.cjs.map +1 -0
- package/dist/{chunk-Y36S3X3L.js → chunk-NBADQ3JU.js} +7 -2
- package/dist/chunk-NBADQ3JU.js.map +1 -0
- package/dist/{chunk-PYP3HCRQ.js → chunk-R7PV3XIC.js} +2 -2
- package/dist/chunk-RTIR6R2U.js +1034 -0
- package/dist/chunk-RTIR6R2U.js.map +1 -0
- package/dist/{chunk-XONKWL6D.cjs → chunk-VCXIPLEW.cjs} +585 -17
- package/dist/chunk-VCXIPLEW.cjs.map +1 -0
- package/dist/{index-BefQ8FAS.d.cts → index-BE1bM2Wi.d.cts} +238 -16
- package/dist/{index-BiIvdASA.d.ts → index-v2_-Fyn3.d.ts} +238 -16
- package/dist/index.cjs +110 -80
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +45 -17
- package/dist/index.d.ts +45 -17
- package/dist/index.js +35 -5
- package/dist/index.js.map +1 -1
- package/dist/schemas.cjs +11 -3
- package/dist/schemas.cjs.map +1 -1
- package/dist/schemas.d.cts +2 -2
- package/dist/schemas.d.ts +2 -2
- package/dist/schemas.js +10 -2
- package/dist/scoring.cjs +23 -3
- package/dist/scoring.cjs.map +1 -1
- package/dist/scoring.d.cts +471 -16
- package/dist/scoring.d.ts +471 -16
- package/dist/scoring.js +24 -4
- package/dist/xapi.cjs +3 -3
- package/dist/xapi.d.cts +1 -1
- package/dist/xapi.d.ts +1 -1
- package/dist/xapi.js +2 -2
- package/package.json +1 -1
- package/vectors/README.md +63 -3
- package/vectors/replay.d.mts +7 -2
- package/vectors/replay.mjs +83 -1
- package/vectors/scoring.json +10875 -2
- package/dist/chunk-DQKJUMUW.cjs.map +0 -1
- package/dist/chunk-FGN4LLSP.js +0 -387
- package/dist/chunk-FGN4LLSP.js.map +0 -1
- package/dist/chunk-UHQVIC3P.cjs +0 -387
- package/dist/chunk-UHQVIC3P.cjs.map +0 -1
- package/dist/chunk-XKKKWKRH.js.map +0 -1
- package/dist/chunk-XONKWL6D.cjs.map +0 -1
- package/dist/chunk-Y36S3X3L.js.map +0 -1
- /package/dist/{chunk-PYP3HCRQ.js.map → chunk-R7PV3XIC.js.map} +0 -0
|
@@ -20,7 +20,7 @@ var UnknownActivityTypeError = class extends Error {
|
|
|
20
20
|
var RedactedScoringError = class extends Error {
|
|
21
21
|
constructor(activityType) {
|
|
22
22
|
super(
|
|
23
|
-
`Activity data for "${activityType}" carries no answer key (it looks redacted), so it cannot be scored. Score against the full activity data server-side
|
|
23
|
+
`Activity data for "${activityType}" carries no answer key (it looks redacted), so it cannot be scored. Score against the full activity data server-side. evaluate() answers this input without throwing, but it never grades it.`
|
|
24
24
|
);
|
|
25
25
|
this.activityType = activityType;
|
|
26
26
|
this.name = "RedactedScoringError";
|
|
@@ -220,6 +220,10 @@ var SPACE_BESIDE_UNSPACED_RE = new RegExp(
|
|
|
220
220
|
);
|
|
221
221
|
var WORD_RE = new RegExp(`^[\\p{L}\\p{M}\\p{N}${JOINERS}]$`, "u");
|
|
222
222
|
var UNSPACED_CHARACTER_RE = new RegExp(`^${UNSPACED_CHARACTER}$`, "u");
|
|
223
|
+
var UNSPACED_LETTER_RE = new RegExp(`(?=\\p{L})${UNSPACED_CHARACTER}`, "u");
|
|
224
|
+
function containsUnspacedScript(text) {
|
|
225
|
+
return UNSPACED_LETTER_RE.test(text);
|
|
226
|
+
}
|
|
223
227
|
var EXTENDING_RE = new RegExp(`^[\\p{M}${JOINERS}${TAGS}]$`, "u");
|
|
224
228
|
var WORD = 1;
|
|
225
229
|
var UNSPACED = 2;
|
|
@@ -1341,6 +1345,168 @@ var MultipleChoiceDataSchema = z5.looseObject({
|
|
|
1341
1345
|
path: ["options"]
|
|
1342
1346
|
});
|
|
1343
1347
|
|
|
1348
|
+
// src/scoring/speech/limits.ts
|
|
1349
|
+
var READ_ALOUD_MAX_REFERENCE_LENGTH = DICTATION_MAX_TRANSCRIPT_LENGTH;
|
|
1350
|
+
var READ_ALOUD_MAX_SECONDS = 300;
|
|
1351
|
+
var READ_ALOUD_MAX_TAKES = 20;
|
|
1352
|
+
var READ_ALOUD_MAX_DIMENSION_WEIGHT = 1e3;
|
|
1353
|
+
var SPEECH_ASSESSMENT_MAX_WORDS = 1e3;
|
|
1354
|
+
var SPEECH_ASSESSMENT_MAX_TEXT_LENGTH = DICTATION_MAX_TEXT_LENGTH;
|
|
1355
|
+
|
|
1356
|
+
// src/schemas/read-aloud.ts
|
|
1357
|
+
import { z as z6 } from "zod/v4";
|
|
1358
|
+
|
|
1359
|
+
// src/scoring/speech/locale.ts
|
|
1360
|
+
var CANONICAL_LOCALE_RE = /^[a-z]{2,3}(?:-[A-Z][a-z]{3})?-(?:[A-Z]{2}|[0-9]{3})$/;
|
|
1361
|
+
|
|
1362
|
+
// src/schemas/read-aloud.ts
|
|
1363
|
+
var READ_ALOUD_DIMENSIONS = ["accuracy", "fluency", "completeness", "prosody"];
|
|
1364
|
+
var READ_ALOUD_MAX_DIMENSIONS = READ_ALOUD_DIMENSIONS.length;
|
|
1365
|
+
var ReadAloudSlowMediaSchema = z6.strictObject({
|
|
1366
|
+
type: z6.literal("audio"),
|
|
1367
|
+
url: MediaUrlSchema,
|
|
1368
|
+
alt: z6.string().min(1).optional()
|
|
1369
|
+
});
|
|
1370
|
+
var ReferenceTextSchema = z6.string().min(1).refine((text) => text.trim() !== "", {
|
|
1371
|
+
error: "The text to read aloud must contain something to read."
|
|
1372
|
+
}).meta({ maxLength: READ_ALOUD_MAX_REFERENCE_LENGTH });
|
|
1373
|
+
var RecordingBoundsSchema = z6.looseObject({
|
|
1374
|
+
maxSeconds: z6.number().gt(0).max(READ_ALOUD_MAX_SECONDS),
|
|
1375
|
+
minSeconds: z6.number().min(0).optional(),
|
|
1376
|
+
maxTakes: z6.number().int().min(1).max(READ_ALOUD_MAX_TAKES).optional()
|
|
1377
|
+
});
|
|
1378
|
+
var ReadAloudDimensionWeightSchema = z6.looseObject({
|
|
1379
|
+
name: z6.enum(READ_ALOUD_DIMENSIONS),
|
|
1380
|
+
weight: z6.number().min(0).max(READ_ALOUD_MAX_DIMENSION_WEIGHT)
|
|
1381
|
+
});
|
|
1382
|
+
var ReadAloudScoringSchema = z6.looseObject({
|
|
1383
|
+
dimensions: z6.array(ReadAloudDimensionWeightSchema).min(1).max(READ_ALOUD_MAX_DIMENSIONS)
|
|
1384
|
+
});
|
|
1385
|
+
function checkEvenAfterIssues2(check) {
|
|
1386
|
+
const guard = new z6.core.$ZodCheck({ check: "custom", when: () => true });
|
|
1387
|
+
guard._zod.check = check;
|
|
1388
|
+
return guard;
|
|
1389
|
+
}
|
|
1390
|
+
var ReadAloudDataShape = z6.looseObject({
|
|
1391
|
+
schemaVersion: z6.literal("1.0"),
|
|
1392
|
+
type: z6.literal("read-aloud"),
|
|
1393
|
+
id: z6.string().min(1),
|
|
1394
|
+
title: z6.string().min(1),
|
|
1395
|
+
instructions: z6.string().optional(),
|
|
1396
|
+
referenceText: ReferenceTextSchema,
|
|
1397
|
+
locale: z6.string().regex(CANONICAL_LOCALE_RE, {
|
|
1398
|
+
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.`
|
|
1399
|
+
}),
|
|
1400
|
+
media: MediaSchema.optional(),
|
|
1401
|
+
slowMedia: ReadAloudSlowMediaSchema.optional(),
|
|
1402
|
+
recording: RecordingBoundsSchema,
|
|
1403
|
+
scoring: ReadAloudScoringSchema,
|
|
1404
|
+
passThreshold: z6.number().min(0).max(1).optional(),
|
|
1405
|
+
feedback: FeedbackSchema.optional(),
|
|
1406
|
+
learningObjectives: z6.array(z6.string()).optional(),
|
|
1407
|
+
difficultyLevel: z6.literal([1, 2, 3, 4, 5]).optional()
|
|
1408
|
+
});
|
|
1409
|
+
var ReadAloudDataSchema = ReadAloudDataShape.check(
|
|
1410
|
+
checkEvenAfterIssues2((ctx) => {
|
|
1411
|
+
if (ctx.issues.some((issue2) => (issue2.path?.length ?? 0) === 0)) {
|
|
1412
|
+
return;
|
|
1413
|
+
}
|
|
1414
|
+
const data = ctx.value;
|
|
1415
|
+
const refused = new Set(ctx.issues.map((issue2) => String(issue2.path?.[0])));
|
|
1416
|
+
const report = (path, input, message) => {
|
|
1417
|
+
ctx.issues.push({ code: "custom", input, message, path: [...path] });
|
|
1418
|
+
};
|
|
1419
|
+
if (!refused.has("referenceText")) {
|
|
1420
|
+
const text = data.referenceText;
|
|
1421
|
+
const measured = dictationNormalizer(void 0).measure(text);
|
|
1422
|
+
if (measured.tooLong) {
|
|
1423
|
+
report(
|
|
1424
|
+
["referenceText"],
|
|
1425
|
+
text,
|
|
1426
|
+
`The text to read aloud is at most ${READ_ALOUD_MAX_REFERENCE_LENGTH} characters, before and after normalisation.`
|
|
1427
|
+
);
|
|
1428
|
+
}
|
|
1429
|
+
if (measured.normalized === "") {
|
|
1430
|
+
report(
|
|
1431
|
+
["referenceText"],
|
|
1432
|
+
text,
|
|
1433
|
+
"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."
|
|
1434
|
+
);
|
|
1435
|
+
}
|
|
1436
|
+
if (containsUnspacedScript(text)) {
|
|
1437
|
+
report(
|
|
1438
|
+
["referenceText"],
|
|
1439
|
+
text,
|
|
1440
|
+
"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."
|
|
1441
|
+
);
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
const mediaParses = !refused.has("media");
|
|
1445
|
+
const slowParses = !refused.has("slowMedia");
|
|
1446
|
+
if (mediaParses && data.media !== void 0 && data.media.type !== "audio") {
|
|
1447
|
+
report(
|
|
1448
|
+
["media", "type"],
|
|
1449
|
+
data.media.type,
|
|
1450
|
+
"A read-aloud model is read aloud: its recording must be audio."
|
|
1451
|
+
);
|
|
1452
|
+
}
|
|
1453
|
+
if (mediaParses && slowParses && data.slowMedia !== void 0) {
|
|
1454
|
+
if (data.media === void 0) {
|
|
1455
|
+
report(
|
|
1456
|
+
["media"],
|
|
1457
|
+
data.media,
|
|
1458
|
+
"A slow model recording accompanies a recording: add `media` first."
|
|
1459
|
+
);
|
|
1460
|
+
}
|
|
1461
|
+
if (data.media?.playback?.maxPlays !== void 0) {
|
|
1462
|
+
report(
|
|
1463
|
+
["slowMedia"],
|
|
1464
|
+
data.slowMedia,
|
|
1465
|
+
"A play budget on `media` cannot coexist with an unbudgeted slow recording of the same content. Drop `slowMedia`, or drop `maxPlays`."
|
|
1466
|
+
);
|
|
1467
|
+
}
|
|
1468
|
+
if (typeof data.slowMedia.url === "string" && typeof data.media?.url === "string" && data.slowMedia.url === data.media.url) {
|
|
1469
|
+
report(
|
|
1470
|
+
["slowMedia", "url"],
|
|
1471
|
+
data.slowMedia.url,
|
|
1472
|
+
"The slow model recording must be a different file from the recording."
|
|
1473
|
+
);
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
if (!refused.has("scoring")) {
|
|
1477
|
+
const dimensions = data.scoring.dimensions;
|
|
1478
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1479
|
+
for (const [index, dimension] of dimensions.entries()) {
|
|
1480
|
+
if (seen.has(dimension.name)) {
|
|
1481
|
+
report(
|
|
1482
|
+
["scoring", "dimensions", index, "name"],
|
|
1483
|
+
dimension.name,
|
|
1484
|
+
`"${dimension.name}" is already weighed: a dimension counted twice hides which weight the grade used.`
|
|
1485
|
+
);
|
|
1486
|
+
}
|
|
1487
|
+
seen.add(dimension.name);
|
|
1488
|
+
}
|
|
1489
|
+
if (dimensions.every((dimension) => dimension.weight === 0)) {
|
|
1490
|
+
report(
|
|
1491
|
+
["scoring", "dimensions"],
|
|
1492
|
+
dimensions,
|
|
1493
|
+
"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."
|
|
1494
|
+
);
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
if (!refused.has("recording")) {
|
|
1498
|
+
const { minSeconds, maxSeconds } = data.recording;
|
|
1499
|
+
if (minSeconds !== void 0 && minSeconds >= maxSeconds) {
|
|
1500
|
+
report(
|
|
1501
|
+
["recording", "minSeconds"],
|
|
1502
|
+
minSeconds,
|
|
1503
|
+
"The shortest take must be shorter than the longest."
|
|
1504
|
+
);
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
})
|
|
1508
|
+
);
|
|
1509
|
+
|
|
1344
1510
|
// src/count-words.ts
|
|
1345
1511
|
function countWords(text) {
|
|
1346
1512
|
if (typeof text !== "string") {
|
|
@@ -1354,21 +1520,21 @@ function countWords(text) {
|
|
|
1354
1520
|
}
|
|
1355
1521
|
|
|
1356
1522
|
// src/schemas/gap-select.ts
|
|
1357
|
-
import { z as
|
|
1358
|
-
var GapSelectChoiceSchema =
|
|
1359
|
-
id:
|
|
1360
|
-
text:
|
|
1523
|
+
import { z as z7 } from "zod/v4";
|
|
1524
|
+
var GapSelectChoiceSchema = z7.looseObject({
|
|
1525
|
+
id: z7.string().min(1),
|
|
1526
|
+
text: z7.string().min(1)
|
|
1361
1527
|
});
|
|
1362
|
-
var GapSelectBankSchema =
|
|
1363
|
-
id:
|
|
1364
|
-
choices:
|
|
1528
|
+
var GapSelectBankSchema = z7.looseObject({
|
|
1529
|
+
id: z7.string().min(1),
|
|
1530
|
+
choices: z7.array(GapSelectChoiceSchema).min(2)
|
|
1365
1531
|
});
|
|
1366
|
-
var GapSelectGapSchema =
|
|
1367
|
-
id:
|
|
1368
|
-
choices:
|
|
1369
|
-
bankId:
|
|
1370
|
-
correctChoiceId:
|
|
1371
|
-
feedback:
|
|
1532
|
+
var GapSelectGapSchema = z7.looseObject({
|
|
1533
|
+
id: z7.string().min(1),
|
|
1534
|
+
choices: z7.array(GapSelectChoiceSchema).min(2).optional(),
|
|
1535
|
+
bankId: z7.string().min(1).optional(),
|
|
1536
|
+
correctChoiceId: z7.string().min(1),
|
|
1537
|
+
feedback: z7.string().optional()
|
|
1372
1538
|
});
|
|
1373
1539
|
function resolveChoices(gap, banks) {
|
|
1374
1540
|
if (gap.choices !== void 0) {
|
|
@@ -1387,24 +1553,24 @@ function placeholderCounts(passage) {
|
|
|
1387
1553
|
}
|
|
1388
1554
|
return counts;
|
|
1389
1555
|
}
|
|
1390
|
-
var GapSelectDataSchema =
|
|
1391
|
-
schemaVersion:
|
|
1392
|
-
type:
|
|
1393
|
-
id:
|
|
1394
|
-
title:
|
|
1395
|
-
passage:
|
|
1396
|
-
passageHtml:
|
|
1397
|
-
gaps:
|
|
1398
|
-
banks:
|
|
1399
|
-
scoringStrategy:
|
|
1400
|
-
presentation:
|
|
1401
|
-
shuffleChoices:
|
|
1556
|
+
var GapSelectDataSchema = z7.looseObject({
|
|
1557
|
+
schemaVersion: z7.literal("1.0"),
|
|
1558
|
+
type: z7.literal("gap-select"),
|
|
1559
|
+
id: z7.string().min(1),
|
|
1560
|
+
title: z7.string().min(1),
|
|
1561
|
+
passage: z7.string().min(1),
|
|
1562
|
+
passageHtml: z7.string().optional(),
|
|
1563
|
+
gaps: z7.array(GapSelectGapSchema).min(1),
|
|
1564
|
+
banks: z7.array(GapSelectBankSchema).optional(),
|
|
1565
|
+
scoringStrategy: z7.enum(["all-or-nothing", "partial"]),
|
|
1566
|
+
presentation: z7.literal("dropdown").optional(),
|
|
1567
|
+
shuffleChoices: z7.boolean().optional(),
|
|
1402
1568
|
media: MediaSchema.optional(),
|
|
1403
1569
|
feedback: FeedbackSchema.optional(),
|
|
1404
|
-
passThreshold:
|
|
1405
|
-
locale:
|
|
1406
|
-
learningObjectives:
|
|
1407
|
-
difficultyLevel:
|
|
1570
|
+
passThreshold: z7.number().min(0).max(1).optional(),
|
|
1571
|
+
locale: z7.string().optional(),
|
|
1572
|
+
learningObjectives: z7.array(z7.string()).optional(),
|
|
1573
|
+
difficultyLevel: z7.literal([1, 2, 3, 4, 5]).optional()
|
|
1408
1574
|
}).refine(
|
|
1409
1575
|
(data) => {
|
|
1410
1576
|
const ids = (data.banks ?? []).map((bank) => bank.id);
|
|
@@ -1458,137 +1624,137 @@ var GapSelectDataSchema = z6.looseObject({
|
|
|
1458
1624
|
);
|
|
1459
1625
|
|
|
1460
1626
|
// src/schemas/written-response.ts
|
|
1461
|
-
import { z as
|
|
1462
|
-
var WrittenResponseRubricCriterionSchema =
|
|
1463
|
-
name:
|
|
1464
|
-
description:
|
|
1465
|
-
weight:
|
|
1627
|
+
import { z as z8 } from "zod/v4";
|
|
1628
|
+
var WrittenResponseRubricCriterionSchema = z8.looseObject({
|
|
1629
|
+
name: z8.string().min(1),
|
|
1630
|
+
description: z8.string().optional(),
|
|
1631
|
+
weight: z8.number().min(0)
|
|
1466
1632
|
});
|
|
1467
|
-
var WrittenResponseRubricSchema =
|
|
1468
|
-
label:
|
|
1469
|
-
criteria:
|
|
1633
|
+
var WrittenResponseRubricSchema = z8.looseObject({
|
|
1634
|
+
label: z8.string().optional(),
|
|
1635
|
+
criteria: z8.array(WrittenResponseRubricCriterionSchema).min(1)
|
|
1470
1636
|
});
|
|
1471
|
-
var RedactedWrittenResponseRubricCriterionSchema =
|
|
1472
|
-
name:
|
|
1473
|
-
description:
|
|
1474
|
-
weight:
|
|
1637
|
+
var RedactedWrittenResponseRubricCriterionSchema = z8.strictObject({
|
|
1638
|
+
name: z8.string().min(1),
|
|
1639
|
+
description: z8.string().optional(),
|
|
1640
|
+
weight: z8.number().min(0)
|
|
1475
1641
|
});
|
|
1476
|
-
var RedactedWrittenResponseRubricSchema =
|
|
1477
|
-
label:
|
|
1478
|
-
criteria:
|
|
1642
|
+
var RedactedWrittenResponseRubricSchema = z8.strictObject({
|
|
1643
|
+
label: z8.string().optional(),
|
|
1644
|
+
criteria: z8.array(RedactedWrittenResponseRubricCriterionSchema).min(1)
|
|
1479
1645
|
});
|
|
1480
|
-
var WrittenResponseDataSchema =
|
|
1481
|
-
schemaVersion:
|
|
1482
|
-
type:
|
|
1483
|
-
id:
|
|
1484
|
-
title:
|
|
1485
|
-
prompt:
|
|
1486
|
-
promptHtml:
|
|
1487
|
-
minWords:
|
|
1488
|
-
maxWords:
|
|
1646
|
+
var WrittenResponseDataSchema = z8.looseObject({
|
|
1647
|
+
schemaVersion: z8.literal("1.0"),
|
|
1648
|
+
type: z8.literal("written-response"),
|
|
1649
|
+
id: z8.string().min(1),
|
|
1650
|
+
title: z8.string().min(1),
|
|
1651
|
+
prompt: z8.string(),
|
|
1652
|
+
promptHtml: z8.string().optional(),
|
|
1653
|
+
minWords: z8.number().int().min(0),
|
|
1654
|
+
maxWords: z8.number().int().min(1),
|
|
1489
1655
|
rubric: WrittenResponseRubricSchema.optional(),
|
|
1490
|
-
languageTarget:
|
|
1656
|
+
languageTarget: z8.string().optional(),
|
|
1491
1657
|
media: MediaSchema.optional(),
|
|
1492
1658
|
feedback: FeedbackSchema.optional(),
|
|
1493
|
-
passThreshold:
|
|
1494
|
-
locale:
|
|
1495
|
-
learningObjectives:
|
|
1496
|
-
difficultyLevel:
|
|
1659
|
+
passThreshold: z8.number().min(0).max(1).optional(),
|
|
1660
|
+
locale: z8.string().optional(),
|
|
1661
|
+
learningObjectives: z8.array(z8.string()).optional(),
|
|
1662
|
+
difficultyLevel: z8.literal([1, 2, 3, 4, 5]).optional()
|
|
1497
1663
|
}).refine((data) => data.maxWords >= data.minWords, {
|
|
1498
1664
|
error: "maxWords must be greater than or equal to minWords.",
|
|
1499
1665
|
path: ["maxWords"]
|
|
1500
1666
|
});
|
|
1501
1667
|
|
|
1502
1668
|
// src/schemas/redacted.ts
|
|
1503
|
-
import { z as
|
|
1669
|
+
import { z as z9 } from "zod/v4";
|
|
1504
1670
|
var redactedBase = {
|
|
1505
1671
|
/** Marker distinguishing a redacted projection from full activity data. */
|
|
1506
|
-
redacted:
|
|
1672
|
+
redacted: z9.literal(true),
|
|
1507
1673
|
/** Slot identity, carried through redaction so the client and the plan agree. */
|
|
1508
|
-
slotKey:
|
|
1509
|
-
schemaVersion:
|
|
1510
|
-
id:
|
|
1511
|
-
title:
|
|
1674
|
+
slotKey: z9.string().min(1).optional(),
|
|
1675
|
+
schemaVersion: z9.literal("1.0"),
|
|
1676
|
+
id: z9.string().min(1),
|
|
1677
|
+
title: z9.string().min(1),
|
|
1512
1678
|
media: RedactedMediaSchema.optional(),
|
|
1513
|
-
passThreshold:
|
|
1514
|
-
locale:
|
|
1515
|
-
learningObjectives:
|
|
1516
|
-
difficultyLevel:
|
|
1679
|
+
passThreshold: z9.number().min(0).max(1).optional(),
|
|
1680
|
+
locale: z9.string().optional(),
|
|
1681
|
+
learningObjectives: z9.array(z9.string()).optional(),
|
|
1682
|
+
difficultyLevel: z9.literal([1, 2, 3, 4, 5]).optional()
|
|
1517
1683
|
};
|
|
1518
|
-
var RedactedMultipleChoiceOptionMediaSchema =
|
|
1519
|
-
type:
|
|
1520
|
-
url:
|
|
1521
|
-
alt:
|
|
1522
|
-
captionsUrl:
|
|
1684
|
+
var RedactedMultipleChoiceOptionMediaSchema = z9.strictObject({
|
|
1685
|
+
type: z9.enum(["image", "audio"]),
|
|
1686
|
+
url: z9.string().min(1),
|
|
1687
|
+
alt: z9.string().min(1).optional(),
|
|
1688
|
+
captionsUrl: z9.string().min(1).optional()
|
|
1523
1689
|
});
|
|
1524
|
-
var RedactedMultipleChoiceOptionSchema =
|
|
1525
|
-
id:
|
|
1526
|
-
text:
|
|
1690
|
+
var RedactedMultipleChoiceOptionSchema = z9.strictObject({
|
|
1691
|
+
id: z9.string().min(1),
|
|
1692
|
+
text: z9.string().min(1),
|
|
1527
1693
|
media: RedactedMultipleChoiceOptionMediaSchema.optional()
|
|
1528
1694
|
});
|
|
1529
|
-
var RedactedMultipleChoiceDataSchema =
|
|
1695
|
+
var RedactedMultipleChoiceDataSchema = z9.strictObject({
|
|
1530
1696
|
...redactedBase,
|
|
1531
|
-
type:
|
|
1532
|
-
question:
|
|
1533
|
-
questionHtml:
|
|
1534
|
-
mode:
|
|
1535
|
-
options:
|
|
1536
|
-
shuffle:
|
|
1697
|
+
type: z9.literal("multiple-choice"),
|
|
1698
|
+
question: z9.string().min(1),
|
|
1699
|
+
questionHtml: z9.string().optional(),
|
|
1700
|
+
mode: z9.enum(["single", "multi"]),
|
|
1701
|
+
options: z9.array(RedactedMultipleChoiceOptionSchema).min(2).max(26),
|
|
1702
|
+
shuffle: z9.boolean().optional()
|
|
1537
1703
|
});
|
|
1538
|
-
var RedactedBlankConfigSchema =
|
|
1539
|
-
id:
|
|
1540
|
-
hint:
|
|
1704
|
+
var RedactedBlankConfigSchema = z9.strictObject({
|
|
1705
|
+
id: z9.string().min(1),
|
|
1706
|
+
hint: z9.string().optional()
|
|
1541
1707
|
});
|
|
1542
|
-
var RedactedFillInTheBlanksDataSchema =
|
|
1708
|
+
var RedactedFillInTheBlanksDataSchema = z9.strictObject({
|
|
1543
1709
|
...redactedBase,
|
|
1544
|
-
type:
|
|
1545
|
-
passage:
|
|
1546
|
-
passageHtml:
|
|
1547
|
-
blanks:
|
|
1710
|
+
type: z9.literal("fill-in-the-blanks"),
|
|
1711
|
+
passage: z9.string().min(1),
|
|
1712
|
+
passageHtml: z9.string().optional(),
|
|
1713
|
+
blanks: z9.array(RedactedBlankConfigSchema).min(1)
|
|
1548
1714
|
});
|
|
1549
|
-
var RedactedGapSelectChoiceSchema =
|
|
1550
|
-
id:
|
|
1551
|
-
text:
|
|
1715
|
+
var RedactedGapSelectChoiceSchema = z9.strictObject({
|
|
1716
|
+
id: z9.string().min(1),
|
|
1717
|
+
text: z9.string().min(1)
|
|
1552
1718
|
});
|
|
1553
|
-
var RedactedGapSelectBankSchema =
|
|
1554
|
-
id:
|
|
1555
|
-
choices:
|
|
1719
|
+
var RedactedGapSelectBankSchema = z9.strictObject({
|
|
1720
|
+
id: z9.string().min(1),
|
|
1721
|
+
choices: z9.array(RedactedGapSelectChoiceSchema).min(2)
|
|
1556
1722
|
});
|
|
1557
|
-
var RedactedGapSelectGapSchema =
|
|
1558
|
-
id:
|
|
1559
|
-
choices:
|
|
1560
|
-
bankId:
|
|
1723
|
+
var RedactedGapSelectGapSchema = z9.strictObject({
|
|
1724
|
+
id: z9.string().min(1),
|
|
1725
|
+
choices: z9.array(RedactedGapSelectChoiceSchema).min(2).optional(),
|
|
1726
|
+
bankId: z9.string().min(1).optional()
|
|
1561
1727
|
});
|
|
1562
|
-
var RedactedGapSelectDataSchema =
|
|
1728
|
+
var RedactedGapSelectDataSchema = z9.strictObject({
|
|
1563
1729
|
...redactedBase,
|
|
1564
|
-
type:
|
|
1565
|
-
passage:
|
|
1566
|
-
passageHtml:
|
|
1567
|
-
gaps:
|
|
1568
|
-
banks:
|
|
1569
|
-
presentation:
|
|
1570
|
-
shuffleChoices:
|
|
1730
|
+
type: z9.literal("gap-select"),
|
|
1731
|
+
passage: z9.string().min(1),
|
|
1732
|
+
passageHtml: z9.string().optional(),
|
|
1733
|
+
gaps: z9.array(RedactedGapSelectGapSchema).min(1),
|
|
1734
|
+
banks: z9.array(RedactedGapSelectBankSchema).optional(),
|
|
1735
|
+
presentation: z9.literal("dropdown").optional(),
|
|
1736
|
+
shuffleChoices: z9.boolean().optional()
|
|
1571
1737
|
});
|
|
1572
|
-
var RedactedWrittenResponseDataSchema =
|
|
1738
|
+
var RedactedWrittenResponseDataSchema = z9.strictObject({
|
|
1573
1739
|
...redactedBase,
|
|
1574
|
-
type:
|
|
1575
|
-
prompt:
|
|
1576
|
-
promptHtml:
|
|
1577
|
-
minWords:
|
|
1578
|
-
maxWords:
|
|
1740
|
+
type: z9.literal("written-response"),
|
|
1741
|
+
prompt: z9.string(),
|
|
1742
|
+
promptHtml: z9.string().optional(),
|
|
1743
|
+
minWords: z9.number().int().min(0),
|
|
1744
|
+
maxWords: z9.number().int().min(1),
|
|
1579
1745
|
rubric: RedactedWrittenResponseRubricSchema.optional(),
|
|
1580
|
-
languageTarget:
|
|
1746
|
+
languageTarget: z9.string().optional()
|
|
1581
1747
|
});
|
|
1582
|
-
var RedactedDictationSlowMediaSchema =
|
|
1583
|
-
type:
|
|
1748
|
+
var RedactedDictationSlowMediaSchema = z9.strictObject({
|
|
1749
|
+
type: z9.literal("audio"),
|
|
1584
1750
|
url: MediaUrlSchema,
|
|
1585
|
-
alt:
|
|
1751
|
+
alt: z9.string().min(1).optional()
|
|
1586
1752
|
});
|
|
1587
|
-
var RedactedDictationDataSchema =
|
|
1753
|
+
var RedactedDictationDataSchema = z9.strictObject({
|
|
1588
1754
|
...redactedBase,
|
|
1589
|
-
type:
|
|
1755
|
+
type: z9.literal("dictation"),
|
|
1590
1756
|
slowMedia: RedactedDictationSlowMediaSchema.optional(),
|
|
1591
|
-
hints:
|
|
1757
|
+
hints: z9.strictObject({ mode: z9.literal("progressive-words") }).optional()
|
|
1592
1758
|
}).check((ctx) => {
|
|
1593
1759
|
const data = ctx.value;
|
|
1594
1760
|
if (data.media?.captionsUrl !== void 0) {
|
|
@@ -1616,6 +1782,61 @@ var RedactedDictationDataSchema = z8.strictObject({
|
|
|
1616
1782
|
});
|
|
1617
1783
|
}
|
|
1618
1784
|
});
|
|
1785
|
+
var RedactedReadAloudDataSchema = z9.strictObject({
|
|
1786
|
+
...redactedBase,
|
|
1787
|
+
type: z9.literal("read-aloud"),
|
|
1788
|
+
locale: z9.string().regex(CANONICAL_LOCALE_RE),
|
|
1789
|
+
instructions: z9.string().optional(),
|
|
1790
|
+
referenceText: z9.string().min(1),
|
|
1791
|
+
slowMedia: ReadAloudSlowMediaSchema.optional(),
|
|
1792
|
+
recording: z9.strictObject({
|
|
1793
|
+
maxSeconds: z9.number().gt(0).max(READ_ALOUD_MAX_SECONDS),
|
|
1794
|
+
minSeconds: z9.number().min(0).optional(),
|
|
1795
|
+
maxTakes: z9.number().int().min(1).max(READ_ALOUD_MAX_TAKES).optional()
|
|
1796
|
+
}),
|
|
1797
|
+
scoring: z9.strictObject({
|
|
1798
|
+
dimensions: z9.array(
|
|
1799
|
+
z9.strictObject({
|
|
1800
|
+
name: z9.enum(READ_ALOUD_DIMENSIONS),
|
|
1801
|
+
weight: z9.number().min(0).max(READ_ALOUD_MAX_DIMENSION_WEIGHT)
|
|
1802
|
+
})
|
|
1803
|
+
).min(1).max(READ_ALOUD_DIMENSIONS.length)
|
|
1804
|
+
})
|
|
1805
|
+
}).check((ctx) => {
|
|
1806
|
+
const data = ctx.value;
|
|
1807
|
+
if (data.media !== void 0 && data.media.type !== "audio") {
|
|
1808
|
+
ctx.issues.push({
|
|
1809
|
+
code: "custom",
|
|
1810
|
+
input: data.media.type,
|
|
1811
|
+
message: "A read-aloud model recording must be audio, so this payload is not renderable.",
|
|
1812
|
+
path: ["media", "type"]
|
|
1813
|
+
});
|
|
1814
|
+
}
|
|
1815
|
+
if (data.slowMedia !== void 0 && data.media === void 0) {
|
|
1816
|
+
ctx.issues.push({
|
|
1817
|
+
code: "custom",
|
|
1818
|
+
input: data.slowMedia,
|
|
1819
|
+
message: "A slow model recording accompanies a recording: this payload has no `media`.",
|
|
1820
|
+
path: ["media"]
|
|
1821
|
+
});
|
|
1822
|
+
}
|
|
1823
|
+
if (data.slowMedia !== void 0 && data.media?.playback?.maxPlays !== void 0) {
|
|
1824
|
+
ctx.issues.push({
|
|
1825
|
+
code: "custom",
|
|
1826
|
+
input: data.slowMedia,
|
|
1827
|
+
message: "A play budget on `media` cannot coexist with an unbudgeted slow recording of the same content.",
|
|
1828
|
+
path: ["slowMedia"]
|
|
1829
|
+
});
|
|
1830
|
+
}
|
|
1831
|
+
if (data.slowMedia !== void 0 && data.slowMedia.url === data.media?.url) {
|
|
1832
|
+
ctx.issues.push({
|
|
1833
|
+
code: "custom",
|
|
1834
|
+
input: data.slowMedia.url,
|
|
1835
|
+
message: "The slow model recording must be a different file from the recording.",
|
|
1836
|
+
path: ["slowMedia", "url"]
|
|
1837
|
+
});
|
|
1838
|
+
}
|
|
1839
|
+
});
|
|
1619
1840
|
|
|
1620
1841
|
// src/scoring/dictation/edit-distance.ts
|
|
1621
1842
|
function codePoints(text) {
|
|
@@ -2037,7 +2258,27 @@ var DRAFT_ISSUE_SEVERITY = {
|
|
|
2037
2258
|
dc_hints_mode_invalid: "invalid",
|
|
2038
2259
|
dc_equivalence_from_required: "incomplete",
|
|
2039
2260
|
dc_equivalence_to_required: "incomplete",
|
|
2040
|
-
dc_tolerance_invalid: "invalid"
|
|
2261
|
+
dc_tolerance_invalid: "invalid",
|
|
2262
|
+
// read-aloud
|
|
2263
|
+
ra_reference_text_required: "incomplete",
|
|
2264
|
+
ra_reference_text_too_long: "invalid",
|
|
2265
|
+
ra_reference_text_unreadable: "invalid",
|
|
2266
|
+
ra_reference_text_unspaced_script: "invalid",
|
|
2267
|
+
ra_locale_required: "incomplete",
|
|
2268
|
+
ra_locale_invalid: "invalid",
|
|
2269
|
+
ra_max_seconds_required: "incomplete",
|
|
2270
|
+
ra_max_seconds_out_of_range: "invalid",
|
|
2271
|
+
ra_min_seconds_out_of_range: "invalid",
|
|
2272
|
+
ra_max_takes_out_of_range: "invalid",
|
|
2273
|
+
ra_dimensions_required: "incomplete",
|
|
2274
|
+
ra_dimension_name_invalid: "invalid",
|
|
2275
|
+
ra_dimension_duplicate: "invalid",
|
|
2276
|
+
ra_dimension_weight_invalid: "invalid",
|
|
2277
|
+
ra_dimension_weights_zero: "invalid",
|
|
2278
|
+
ra_media_kind: "invalid",
|
|
2279
|
+
ra_slow_media_without_media: "incomplete",
|
|
2280
|
+
ra_slow_media_same_recording: "invalid",
|
|
2281
|
+
ra_slow_media_beside_play_limit: "invalid"
|
|
2041
2282
|
};
|
|
2042
2283
|
function issue(code, path, message) {
|
|
2043
2284
|
return { path: path.map(String), message, code, severity: DRAFT_ISSUE_SEVERITY[code] };
|
|
@@ -3178,6 +3419,283 @@ function checkOptionMedia(media, index, ordinal) {
|
|
|
3178
3419
|
return checkMedia(media, path, MultipleChoiceOptionMediaSchema);
|
|
3179
3420
|
}
|
|
3180
3421
|
|
|
3422
|
+
// src/authoring/read-aloud.ts
|
|
3423
|
+
var readAloudAuthoring = {
|
|
3424
|
+
createDraft: ({ newId }) => ({
|
|
3425
|
+
schemaVersion: "1.0",
|
|
3426
|
+
type: "read-aloud",
|
|
3427
|
+
id: newId(),
|
|
3428
|
+
title: "",
|
|
3429
|
+
referenceText: "",
|
|
3430
|
+
locale: "",
|
|
3431
|
+
recording: { maxSeconds: 0 },
|
|
3432
|
+
scoring: { dimensions: [] }
|
|
3433
|
+
}),
|
|
3434
|
+
checkDraft: checkReadAloudDraft
|
|
3435
|
+
};
|
|
3436
|
+
function checkReadAloudDraft(draft) {
|
|
3437
|
+
const issues = checkIdentity(draft, "read-aloud");
|
|
3438
|
+
issues.push(...checkReferenceText(draft.referenceText));
|
|
3439
|
+
issues.push(...checkLocale(draft.locale));
|
|
3440
|
+
issues.push(...checkRecording2(draft.recording));
|
|
3441
|
+
issues.push(...checkDimensions(draft.scoring));
|
|
3442
|
+
issues.push(...checkModelRecording(draft.media));
|
|
3443
|
+
issues.push(...checkSlowRecording2(draft));
|
|
3444
|
+
issues.push(...checkSharedOptional(draft));
|
|
3445
|
+
return issues;
|
|
3446
|
+
}
|
|
3447
|
+
function checkReferenceText(referenceText) {
|
|
3448
|
+
if (isUnwritten(referenceText)) {
|
|
3449
|
+
return [
|
|
3450
|
+
issue("ra_reference_text_required", ["referenceText"], "Write the text to be read aloud.")
|
|
3451
|
+
];
|
|
3452
|
+
}
|
|
3453
|
+
if (typeof referenceText !== "string") {
|
|
3454
|
+
return [];
|
|
3455
|
+
}
|
|
3456
|
+
const issues = [];
|
|
3457
|
+
const measured = dictationNormalizer(void 0).measure(referenceText);
|
|
3458
|
+
if (measured.tooLong) {
|
|
3459
|
+
issues.push(
|
|
3460
|
+
issue(
|
|
3461
|
+
"ra_reference_text_too_long",
|
|
3462
|
+
["referenceText"],
|
|
3463
|
+
`The text is longer than ${READ_ALOUD_MAX_REFERENCE_LENGTH} characters, before or after normalisation. A read-aloud item is a sentence or a short paragraph.`
|
|
3464
|
+
)
|
|
3465
|
+
);
|
|
3466
|
+
}
|
|
3467
|
+
if (measured.normalized === "") {
|
|
3468
|
+
issues.push(
|
|
3469
|
+
issue(
|
|
3470
|
+
"ra_reference_text_unreadable",
|
|
3471
|
+
["referenceText"],
|
|
3472
|
+
"The text has no word to read: once punctuation and spacing are ignored, nothing is left. A reading of it would be marked against no word at all."
|
|
3473
|
+
)
|
|
3474
|
+
);
|
|
3475
|
+
}
|
|
3476
|
+
if (containsUnspacedScript(referenceText)) {
|
|
3477
|
+
issues.push(
|
|
3478
|
+
issue(
|
|
3479
|
+
"ra_reference_text_unspaced_script",
|
|
3480
|
+
["referenceText"],
|
|
3481
|
+
"The text is written in a script that does not separate its words with spaces. Each word is marked on its own, and a run of Han, kana, Thai, Lao, Khmer or Myanmar letters would be marked as one word."
|
|
3482
|
+
)
|
|
3483
|
+
);
|
|
3484
|
+
}
|
|
3485
|
+
return issues;
|
|
3486
|
+
}
|
|
3487
|
+
function checkLocale(locale) {
|
|
3488
|
+
if (isUnwritten(locale)) {
|
|
3489
|
+
return [
|
|
3490
|
+
issue(
|
|
3491
|
+
"ra_locale_required",
|
|
3492
|
+
["locale"],
|
|
3493
|
+
'Choose the language the learner reads in, with its region \u2014 "en-US", not "en".'
|
|
3494
|
+
)
|
|
3495
|
+
];
|
|
3496
|
+
}
|
|
3497
|
+
if (typeof locale !== "string" || CANONICAL_LOCALE_RE.test(locale)) {
|
|
3498
|
+
return [];
|
|
3499
|
+
}
|
|
3500
|
+
return [
|
|
3501
|
+
issue(
|
|
3502
|
+
"ra_locale_invalid",
|
|
3503
|
+
["locale"],
|
|
3504
|
+
`The language 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.`
|
|
3505
|
+
)
|
|
3506
|
+
];
|
|
3507
|
+
}
|
|
3508
|
+
function checkRecording2(recording) {
|
|
3509
|
+
if (isUnset(recording)) {
|
|
3510
|
+
return [maxSecondsRequired()];
|
|
3511
|
+
}
|
|
3512
|
+
if (!isRecord(recording)) {
|
|
3513
|
+
return [];
|
|
3514
|
+
}
|
|
3515
|
+
const issues = [];
|
|
3516
|
+
const max = recording.maxSeconds;
|
|
3517
|
+
if (isUnset(max) || max === 0) {
|
|
3518
|
+
issues.push(maxSecondsRequired());
|
|
3519
|
+
} else if (typeof max === "number" && !(Number.isFinite(max) && max > 0 && max <= READ_ALOUD_MAX_SECONDS)) {
|
|
3520
|
+
issues.push(
|
|
3521
|
+
issue(
|
|
3522
|
+
"ra_max_seconds_out_of_range",
|
|
3523
|
+
["recording", "maxSeconds"],
|
|
3524
|
+
`A take is at most ${READ_ALOUD_MAX_SECONDS} seconds long.`
|
|
3525
|
+
)
|
|
3526
|
+
);
|
|
3527
|
+
}
|
|
3528
|
+
const min = recording.minSeconds;
|
|
3529
|
+
if (typeof min === "number" && !(Number.isFinite(min) && min >= 0)) {
|
|
3530
|
+
issues.push(minSecondsOutOfRange("The shortest take is a number of seconds, 0 or more."));
|
|
3531
|
+
} else if (typeof min === "number" && typeof max === "number" && max > 0 && min >= max) {
|
|
3532
|
+
issues.push(minSecondsOutOfRange("The shortest take must be shorter than the longest."));
|
|
3533
|
+
}
|
|
3534
|
+
const takes = recording.maxTakes;
|
|
3535
|
+
if (typeof takes === "number" && !(isWholeNumber(takes) && takes >= 1 && takes <= READ_ALOUD_MAX_TAKES)) {
|
|
3536
|
+
issues.push(
|
|
3537
|
+
issue(
|
|
3538
|
+
"ra_max_takes_out_of_range",
|
|
3539
|
+
["recording", "maxTakes"],
|
|
3540
|
+
`How many takes a learner may record is a whole number from 1 to ${READ_ALOUD_MAX_TAKES}.`
|
|
3541
|
+
)
|
|
3542
|
+
);
|
|
3543
|
+
}
|
|
3544
|
+
return issues;
|
|
3545
|
+
}
|
|
3546
|
+
function maxSecondsRequired() {
|
|
3547
|
+
return issue(
|
|
3548
|
+
"ra_max_seconds_required",
|
|
3549
|
+
["recording", "maxSeconds"],
|
|
3550
|
+
"Set how long a take may be, in seconds."
|
|
3551
|
+
);
|
|
3552
|
+
}
|
|
3553
|
+
function minSecondsOutOfRange(message) {
|
|
3554
|
+
return issue("ra_min_seconds_out_of_range", ["recording", "minSeconds"], message);
|
|
3555
|
+
}
|
|
3556
|
+
var DIMENSION_NAMES = READ_ALOUD_DIMENSIONS;
|
|
3557
|
+
function checkDimensions(scoring) {
|
|
3558
|
+
if (isUnset(scoring)) {
|
|
3559
|
+
return [dimensionsRequired()];
|
|
3560
|
+
}
|
|
3561
|
+
if (!isRecord(scoring)) {
|
|
3562
|
+
return [];
|
|
3563
|
+
}
|
|
3564
|
+
const dimensions = scoring.dimensions;
|
|
3565
|
+
if (isUnset(dimensions)) {
|
|
3566
|
+
return [dimensionsRequired()];
|
|
3567
|
+
}
|
|
3568
|
+
if (!Array.isArray(dimensions)) {
|
|
3569
|
+
return [];
|
|
3570
|
+
}
|
|
3571
|
+
if (dimensions.length === 0) {
|
|
3572
|
+
return [dimensionsRequired()];
|
|
3573
|
+
}
|
|
3574
|
+
const issues = [];
|
|
3575
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3576
|
+
let everyWeightUsable = true;
|
|
3577
|
+
let totalWeight = 0;
|
|
3578
|
+
for (const [index, dimension] of dimensions.entries()) {
|
|
3579
|
+
if (!isRecord(dimension)) {
|
|
3580
|
+
everyWeightUsable = false;
|
|
3581
|
+
continue;
|
|
3582
|
+
}
|
|
3583
|
+
const path = ["scoring", "dimensions", index];
|
|
3584
|
+
const name = dimension.name;
|
|
3585
|
+
if (!DIMENSION_NAMES.includes(name)) {
|
|
3586
|
+
issues.push(
|
|
3587
|
+
issue(
|
|
3588
|
+
"ra_dimension_name_invalid",
|
|
3589
|
+
[...path, "name"],
|
|
3590
|
+
`Choose what dimension ${index + 1} measures: ${READ_ALOUD_DIMENSIONS.join(", ")}.`
|
|
3591
|
+
)
|
|
3592
|
+
);
|
|
3593
|
+
} else if (seen.has(name)) {
|
|
3594
|
+
issues.push(
|
|
3595
|
+
issue(
|
|
3596
|
+
"ra_dimension_duplicate",
|
|
3597
|
+
[...path, "name"],
|
|
3598
|
+
`"${String(name)}" is already weighed. Remove this one, or measure something else.`
|
|
3599
|
+
)
|
|
3600
|
+
);
|
|
3601
|
+
} else {
|
|
3602
|
+
seen.add(name);
|
|
3603
|
+
}
|
|
3604
|
+
const weight = dimension.weight;
|
|
3605
|
+
if (typeof weight === "number" && Number.isFinite(weight) && weight >= 0 && weight <= READ_ALOUD_MAX_DIMENSION_WEIGHT) {
|
|
3606
|
+
totalWeight += weight;
|
|
3607
|
+
} else {
|
|
3608
|
+
everyWeightUsable = false;
|
|
3609
|
+
issues.push(
|
|
3610
|
+
issue(
|
|
3611
|
+
"ra_dimension_weight_invalid",
|
|
3612
|
+
[...path, "weight"],
|
|
3613
|
+
`Give dimension ${index + 1} a weight: a number from 0 to ${READ_ALOUD_MAX_DIMENSION_WEIGHT}.`
|
|
3614
|
+
)
|
|
3615
|
+
);
|
|
3616
|
+
}
|
|
3617
|
+
}
|
|
3618
|
+
if (everyWeightUsable && totalWeight === 0) {
|
|
3619
|
+
issues.push(
|
|
3620
|
+
issue(
|
|
3621
|
+
"ra_dimension_weights_zero",
|
|
3622
|
+
["scoring", "dimensions"],
|
|
3623
|
+
"Give at least one dimension a weight above 0. With every weight at 0 there is no weighted total, and every take would be unscorable."
|
|
3624
|
+
)
|
|
3625
|
+
);
|
|
3626
|
+
}
|
|
3627
|
+
return issues;
|
|
3628
|
+
}
|
|
3629
|
+
function dimensionsRequired() {
|
|
3630
|
+
return issue(
|
|
3631
|
+
"ra_dimensions_required",
|
|
3632
|
+
["scoring", "dimensions"],
|
|
3633
|
+
"Choose what the grade measures: at least one of accuracy, fluency, completeness or prosody, with a weight."
|
|
3634
|
+
);
|
|
3635
|
+
}
|
|
3636
|
+
function checkModelRecording(media) {
|
|
3637
|
+
if (!isRecord(media)) {
|
|
3638
|
+
return [];
|
|
3639
|
+
}
|
|
3640
|
+
const kind = media.type;
|
|
3641
|
+
return isUnwritten(kind) || kind === "audio" ? [] : [
|
|
3642
|
+
issue(
|
|
3643
|
+
"ra_media_kind",
|
|
3644
|
+
["media", "type"],
|
|
3645
|
+
"A read-aloud model is read aloud: its recording must be audio."
|
|
3646
|
+
)
|
|
3647
|
+
];
|
|
3648
|
+
}
|
|
3649
|
+
function checkSlowRecording2(draft) {
|
|
3650
|
+
const slow = draft.slowMedia;
|
|
3651
|
+
if (!isRecord(slow)) {
|
|
3652
|
+
return [];
|
|
3653
|
+
}
|
|
3654
|
+
const kind = slow.type;
|
|
3655
|
+
const wrongKind = !isUnwritten(kind) && kind !== "audio";
|
|
3656
|
+
const issues = wrongKind ? [issue("media_invalid", ["slowMedia", "type"], "The slow model recording must be audio.")] : [];
|
|
3657
|
+
issues.push(
|
|
3658
|
+
...checkMedia(
|
|
3659
|
+
wrongKind ? { ...slow, type: "audio" } : slow,
|
|
3660
|
+
["slowMedia"],
|
|
3661
|
+
ReadAloudSlowMediaSchema
|
|
3662
|
+
)
|
|
3663
|
+
);
|
|
3664
|
+
const media = draft.media;
|
|
3665
|
+
if (isUnset(media)) {
|
|
3666
|
+
issues.push(
|
|
3667
|
+
issue(
|
|
3668
|
+
"ra_slow_media_without_media",
|
|
3669
|
+
["media"],
|
|
3670
|
+
"A slow model recording accompanies a recording. Add the recording first."
|
|
3671
|
+
)
|
|
3672
|
+
);
|
|
3673
|
+
return issues;
|
|
3674
|
+
}
|
|
3675
|
+
if (!isRecord(media)) {
|
|
3676
|
+
return issues;
|
|
3677
|
+
}
|
|
3678
|
+
if (isRecord(media.playback) && media.playback.maxPlays !== void 0) {
|
|
3679
|
+
issues.push(
|
|
3680
|
+
issue(
|
|
3681
|
+
"ra_slow_media_beside_play_limit",
|
|
3682
|
+
["slowMedia"],
|
|
3683
|
+
"A play budget on the recording cannot coexist with a slow recording that has none. Remove the slow recording, or remove the play limit."
|
|
3684
|
+
)
|
|
3685
|
+
);
|
|
3686
|
+
}
|
|
3687
|
+
if (typeof slow.url === "string" && slow.url !== "" && slow.url === media.url) {
|
|
3688
|
+
issues.push(
|
|
3689
|
+
issue(
|
|
3690
|
+
"ra_slow_media_same_recording",
|
|
3691
|
+
["slowMedia", "url"],
|
|
3692
|
+
"The slow model recording is the same file as the recording. Point it at the slower reading."
|
|
3693
|
+
)
|
|
3694
|
+
);
|
|
3695
|
+
}
|
|
3696
|
+
return issues;
|
|
3697
|
+
}
|
|
3698
|
+
|
|
3181
3699
|
// src/authoring/written-response.ts
|
|
3182
3700
|
var writtenResponseAuthoring = {
|
|
3183
3701
|
createDraft: ({ newId }) => ({
|
|
@@ -3717,11 +4235,48 @@ var dictationType = defineActivityType({
|
|
|
3717
4235
|
interactions: ["text-changed", "hint-requested", "submitted"],
|
|
3718
4236
|
authoring: dictationAuthoring
|
|
3719
4237
|
});
|
|
4238
|
+
var READ_ALOUD_FIELD_POLICY = {
|
|
4239
|
+
...SHARED_PUBLIC_FIELDS,
|
|
4240
|
+
instructions: "public",
|
|
4241
|
+
referenceText: "public",
|
|
4242
|
+
recording: { maxSeconds: "public", minSeconds: "public", maxTakes: "public" },
|
|
4243
|
+
slowMedia: { type: "public", url: "public", alt: "public" },
|
|
4244
|
+
scoring: { dimensions: { name: "public", weight: "public" } },
|
|
4245
|
+
feedback: FEEDBACK_FIELD_POLICY
|
|
4246
|
+
};
|
|
4247
|
+
function hasRecording(response) {
|
|
4248
|
+
const recording = response?.recording;
|
|
4249
|
+
return typeof recording === "object" && recording !== null && typeof recording.key === "string" && recording.key !== "";
|
|
4250
|
+
}
|
|
4251
|
+
var readAloudType = defineActivityType({
|
|
4252
|
+
type: "read-aloud",
|
|
4253
|
+
schema: ReadAloudDataSchema,
|
|
4254
|
+
scoring: {
|
|
4255
|
+
kind: "deferred",
|
|
4256
|
+
reason: "requires_async_grading",
|
|
4257
|
+
partial: (_data, response) => ({ hasRecording: hasRecording(response) })
|
|
4258
|
+
},
|
|
4259
|
+
isAnswered: (response) => hasRecording(response),
|
|
4260
|
+
fieldPolicy: READ_ALOUD_FIELD_POLICY,
|
|
4261
|
+
redactedSchema: RedactedReadAloudDataSchema,
|
|
4262
|
+
interop: {
|
|
4263
|
+
xapiActivityTypeIri: "http://adlnet.gov/expapi/activities/cmi.interaction",
|
|
4264
|
+
// `other`, not `performance`: xAPI's performance interaction describes a set
|
|
4265
|
+
// of steps with their own responses, and a reading has one. There is no
|
|
4266
|
+
// pattern to publish either — the "correct response" is a pronunciation, not
|
|
4267
|
+
// a string a statement can carry.
|
|
4268
|
+
xapiInteractionType: "other",
|
|
4269
|
+
correctResponsesPattern: () => []
|
|
4270
|
+
},
|
|
4271
|
+
interactions: ["recording-started", "recording-stopped", "recording-uploaded", "submitted"],
|
|
4272
|
+
authoring: readAloudAuthoring
|
|
4273
|
+
});
|
|
3720
4274
|
registerActivityType(multipleChoiceType);
|
|
3721
4275
|
registerActivityType(fillInTheBlanksType);
|
|
3722
4276
|
registerActivityType(writtenResponseType);
|
|
3723
4277
|
registerActivityType(gapSelectType);
|
|
3724
4278
|
registerActivityType(dictationType);
|
|
4279
|
+
registerActivityType(readAloudType);
|
|
3725
4280
|
|
|
3726
4281
|
export {
|
|
3727
4282
|
ActivitySchemaError,
|
|
@@ -3734,6 +4289,8 @@ export {
|
|
|
3734
4289
|
DICTATION_MAX_EQUIVALENCE_LENGTH,
|
|
3735
4290
|
DICTATION_MAX_EQUIVALENCES,
|
|
3736
4291
|
DICTATION_MAX_ACCEPTED_TRANSCRIPTS,
|
|
4292
|
+
codePointLength,
|
|
4293
|
+
normalizeDictationText,
|
|
3737
4294
|
FeedbackSchema,
|
|
3738
4295
|
MediaUrlSchema,
|
|
3739
4296
|
NativeControlHintSchema,
|
|
@@ -3763,6 +4320,15 @@ export {
|
|
|
3763
4320
|
MultipleChoiceOptionMediaSchema,
|
|
3764
4321
|
MultipleChoiceOptionSchema,
|
|
3765
4322
|
MultipleChoiceDataSchema,
|
|
4323
|
+
READ_ALOUD_MAX_REFERENCE_LENGTH,
|
|
4324
|
+
READ_ALOUD_MAX_SECONDS,
|
|
4325
|
+
READ_ALOUD_MAX_TAKES,
|
|
4326
|
+
READ_ALOUD_MAX_DIMENSION_WEIGHT,
|
|
4327
|
+
SPEECH_ASSESSMENT_MAX_WORDS,
|
|
4328
|
+
SPEECH_ASSESSMENT_MAX_TEXT_LENGTH,
|
|
4329
|
+
CANONICAL_LOCALE_RE,
|
|
4330
|
+
ReadAloudSlowMediaSchema,
|
|
4331
|
+
ReadAloudDataSchema,
|
|
3766
4332
|
countWords,
|
|
3767
4333
|
GapSelectChoiceSchema,
|
|
3768
4334
|
GapSelectBankSchema,
|
|
@@ -3783,6 +4349,7 @@ export {
|
|
|
3783
4349
|
RedactedWrittenResponseDataSchema,
|
|
3784
4350
|
RedactedDictationSlowMediaSchema,
|
|
3785
4351
|
RedactedDictationDataSchema,
|
|
4352
|
+
RedactedReadAloudDataSchema,
|
|
3786
4353
|
dictationReferenceWords,
|
|
3787
4354
|
alignDictation,
|
|
3788
4355
|
STEP,
|
|
@@ -3798,6 +4365,7 @@ export {
|
|
|
3798
4365
|
fillInTheBlanksType,
|
|
3799
4366
|
writtenResponseType,
|
|
3800
4367
|
gapSelectType,
|
|
3801
|
-
dictationType
|
|
4368
|
+
dictationType,
|
|
4369
|
+
readAloudType
|
|
3802
4370
|
};
|
|
3803
|
-
//# sourceMappingURL=chunk-
|
|
4371
|
+
//# sourceMappingURL=chunk-6RND47QE.js.map
|