@intellectif/lk-core 1.13.0 → 1.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 +12 -0
- package/README.md +1 -1
- package/dist/ai-check.cjs +2 -2
- package/dist/ai-check.d.cts +1 -1
- package/dist/ai-check.d.ts +1 -1
- package/dist/ai-check.js +2 -2
- package/dist/{ai-hC5pGYeu.d.cts → ai-mC-5XhBc.d.cts} +92 -2
- package/dist/ai-mC-5XhBc.d.cts.map +1 -0
- package/dist/{ai-hC5pGYeu.d.ts → ai-mC-5XhBc.d.ts} +92 -2
- package/dist/ai-mC-5XhBc.d.ts.map +1 -0
- package/dist/{ai-writing-BDSYuifR.cjs → ai-writing-Bm2TUWQ-.cjs} +10 -4
- package/dist/ai-writing-Bm2TUWQ-.cjs.map +1 -0
- package/dist/{ai-writing-Dsmvv4T-.js → ai-writing-D3oREPsp.js} +10 -4
- package/dist/ai-writing-D3oREPsp.js.map +1 -0
- package/dist/{index-Dlei-7PX.d.ts → index-2UQ5v6NZ.d.ts} +2 -2
- package/dist/{index-Dlei-7PX.d.ts.map → index-2UQ5v6NZ.d.ts.map} +1 -1
- package/dist/{index-Du9Kx_H6.d.ts → index-BudO4ES2.d.ts} +2 -2
- package/dist/{index-Du9Kx_H6.d.ts.map → index-BudO4ES2.d.ts.map} +1 -1
- package/dist/{index-BlJU10O4.d.cts → index-CFKxHXAY.d.ts} +11 -4
- package/dist/{index-DVW_N4pJ.d.ts.map → index-CFKxHXAY.d.ts.map} +1 -1
- package/dist/{index-DVW_N4pJ.d.ts → index-CagMlF4b.d.cts} +11 -4
- package/dist/{index-BlJU10O4.d.cts.map → index-CagMlF4b.d.cts.map} +1 -1
- package/dist/{index-4x0auNOv.d.cts → index-CqmNcMFM.d.cts} +2 -2
- package/dist/{index-4x0auNOv.d.cts.map → index-CqmNcMFM.d.cts.map} +1 -1
- package/dist/{index-DohEHH10.d.cts → index-MgPVCiSJ.d.cts} +2 -2
- package/dist/{index-DohEHH10.d.cts.map → index-MgPVCiSJ.d.cts.map} +1 -1
- package/dist/index.cjs +12 -6
- package/dist/index.d.cts +86 -10
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +86 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -7
- package/dist/{item-group-vTcNy2JF.js → item-group-BIl1ZJHg.js} +7 -6
- package/dist/item-group-BIl1ZJHg.js.map +1 -0
- package/dist/{item-group-CqdByaLR.cjs → item-group-DOk562oI.cjs} +7 -6
- package/dist/item-group-DOk562oI.cjs.map +1 -0
- package/dist/{registry-Cn2it-y7.js → registry-CroAI5nc.js} +517 -11
- package/dist/registry-CroAI5nc.js.map +1 -0
- package/dist/{registry-DUTE1vtq.cjs → registry-Dw19lNo2.cjs} +552 -10
- package/dist/registry-Dw19lNo2.cjs.map +1 -0
- package/dist/{schemas-BrA6CFWz.cjs → schemas-CEv2otNw.cjs} +3 -3
- package/dist/{schemas-BrA6CFWz.cjs.map → schemas-CEv2otNw.cjs.map} +1 -1
- package/dist/{schemas-DlxeS7Lu.js → schemas-COw7muXH.js} +3 -3
- package/dist/{schemas-DlxeS7Lu.js.map → schemas-COw7muXH.js.map} +1 -1
- package/dist/schemas.cjs +3 -3
- package/dist/schemas.d.cts +2 -2
- package/dist/schemas.d.ts +2 -2
- package/dist/schemas.js +3 -3
- package/dist/{scoring-weQ4SzwB.js → scoring-BxvOhHE0.js} +2 -2
- package/dist/{scoring-weQ4SzwB.js.map → scoring-BxvOhHE0.js.map} +1 -1
- package/dist/{scoring-IjLi2fYF.cjs → scoring-EpNBJ8Fs.cjs} +2 -2
- package/dist/{scoring-IjLi2fYF.cjs.map → scoring-EpNBJ8Fs.cjs.map} +1 -1
- package/dist/scoring.cjs +2 -2
- package/dist/scoring.d.cts +2 -2
- package/dist/scoring.d.ts +2 -2
- package/dist/scoring.js +2 -2
- package/dist/{xapi-BqEbpWag.cjs → xapi-C-cQgWRX.cjs} +2 -2
- package/dist/{xapi-BqEbpWag.cjs.map → xapi-C-cQgWRX.cjs.map} +1 -1
- package/dist/{xapi-DbRVH_RB.js → xapi-DHHm7Os9.js} +2 -2
- package/dist/{xapi-DbRVH_RB.js.map → xapi-DHHm7Os9.js.map} +1 -1
- package/dist/xapi.cjs +1 -1
- package/dist/xapi.d.cts +1 -1
- package/dist/xapi.d.ts +1 -1
- package/dist/xapi.js +1 -1
- package/package.json +1 -1
- package/vectors/scoring.json +833 -0
- package/vectors/validation.json +631 -0
- package/dist/ai-hC5pGYeu.d.cts.map +0 -1
- package/dist/ai-hC5pGYeu.d.ts.map +0 -1
- package/dist/ai-writing-BDSYuifR.cjs.map +0 -1
- package/dist/ai-writing-Dsmvv4T-.js.map +0 -1
- package/dist/item-group-CqdByaLR.cjs.map +0 -1
- package/dist/item-group-vTcNy2JF.js.map +0 -1
- package/dist/registry-Cn2it-y7.js.map +0 -1
- package/dist/registry-DUTE1vtq.cjs.map +0 -1
|
@@ -443,7 +443,8 @@ const BUILT_IN_ACTIVITY_TYPES = [
|
|
|
443
443
|
"dictation",
|
|
444
444
|
"read-aloud",
|
|
445
445
|
"speaking-response",
|
|
446
|
-
"matching"
|
|
446
|
+
"matching",
|
|
447
|
+
"mark-the-words"
|
|
447
448
|
];
|
|
448
449
|
/** Whether `type` names an activity type the SDK ships. */
|
|
449
450
|
function isBuiltInActivityType(type) {
|
|
@@ -1054,7 +1055,7 @@ function containsUnspacedScript(text) {
|
|
|
1054
1055
|
}
|
|
1055
1056
|
/** What is drawn onto the character before it: a match never ends just before one. */
|
|
1056
1057
|
const EXTENDING_RE = new RegExp(`^[\\p{M}${JOINERS}${TAGS}]$`, "u");
|
|
1057
|
-
const WORD = 1;
|
|
1058
|
+
const WORD$1 = 1;
|
|
1058
1059
|
const UNSPACED = 2;
|
|
1059
1060
|
const EXTENDING = 4;
|
|
1060
1061
|
const KNOWN = 8;
|
|
@@ -1068,7 +1069,7 @@ const KNOWN = 8;
|
|
|
1068
1069
|
*/
|
|
1069
1070
|
const FLAGS = [];
|
|
1070
1071
|
function flagsOfCharacter(character) {
|
|
1071
|
-
return KNOWN | (WORD_RE.test(character) ? WORD : 0) | (UNSPACED_CHARACTER_RE.test(character) ? UNSPACED : 0) | (EXTENDING_RE.test(character) ? EXTENDING : 0);
|
|
1072
|
+
return KNOWN | (WORD_RE.test(character) ? WORD$1 : 0) | (UNSPACED_CHARACTER_RE.test(character) ? UNSPACED : 0) | (EXTENDING_RE.test(character) ? EXTENDING : 0);
|
|
1072
1073
|
}
|
|
1073
1074
|
/** The flags of the code point that starts at UTF-16 index `index` of `text`. */
|
|
1074
1075
|
function flagsAt(text, index) {
|
|
@@ -1298,12 +1299,12 @@ function isAsciiWordUnit(unit) {
|
|
|
1298
1299
|
}
|
|
1299
1300
|
function isWordAt(text, index) {
|
|
1300
1301
|
const unit = text.charCodeAt(index);
|
|
1301
|
-
return unit < 128 ? isAsciiWordUnit(unit) : (flagsAt(text, index) & WORD) !== 0;
|
|
1302
|
+
return unit < 128 ? isAsciiWordUnit(unit) : (flagsAt(text, index) & WORD$1) !== 0;
|
|
1302
1303
|
}
|
|
1303
1304
|
/** A word character of a script that separates its words with spaces. */
|
|
1304
1305
|
function isSpacedWordAt(text, index) {
|
|
1305
1306
|
const unit = text.charCodeAt(index);
|
|
1306
|
-
return unit < 128 ? isAsciiWordUnit(unit) : (flagsAt(text, index) & 3) === WORD;
|
|
1307
|
+
return unit < 128 ? isAsciiWordUnit(unit) : (flagsAt(text, index) & 3) === WORD$1;
|
|
1307
1308
|
}
|
|
1308
1309
|
function isExtendingAt(text, index) {
|
|
1309
1310
|
return text.charCodeAt(index) >= 128 && (flagsAt(text, index) & EXTENDING) !== 0;
|
|
@@ -2342,6 +2343,15 @@ const DRAFT_ISSUE_SEVERITY = {
|
|
|
2342
2343
|
mt_correct_option_required: "incomplete",
|
|
2343
2344
|
mt_correct_option_unknown: "invalid",
|
|
2344
2345
|
mt_option_reused: "invalid",
|
|
2346
|
+
mw_text_required: "incomplete",
|
|
2347
|
+
mw_text_too_long: "invalid",
|
|
2348
|
+
mw_targets_required: "incomplete",
|
|
2349
|
+
mw_target_text_required: "incomplete",
|
|
2350
|
+
mw_target_no_words: "invalid",
|
|
2351
|
+
mw_target_not_found: "invalid",
|
|
2352
|
+
mw_target_ambiguous: "incomplete",
|
|
2353
|
+
mw_target_occurrence_invalid: "invalid",
|
|
2354
|
+
mw_target_overlap: "invalid",
|
|
2345
2355
|
wr_prompt_required: "incomplete",
|
|
2346
2356
|
wr_min_words_required: "incomplete",
|
|
2347
2357
|
wr_min_words_invalid: "invalid",
|
|
@@ -3105,6 +3115,9 @@ const ITEM_FINDING_SEVERITY = {
|
|
|
3105
3115
|
mt_options_duplicate: "warning",
|
|
3106
3116
|
mt_answer_in_prompt: "warning",
|
|
3107
3117
|
mt_no_distractor: "warning",
|
|
3118
|
+
mw_title_reveals_answer: "warning",
|
|
3119
|
+
mw_targets_dense: "warning",
|
|
3120
|
+
mw_target_elsewhere: "advice",
|
|
3108
3121
|
wr_criterion_name_duplicate: "warning",
|
|
3109
3122
|
wr_levels_unreadable: "warning",
|
|
3110
3123
|
ra_text_long_for_time: "warning",
|
|
@@ -3376,7 +3389,7 @@ function critiqueGapSelect(draft) {
|
|
|
3376
3389
|
return findings;
|
|
3377
3390
|
}
|
|
3378
3391
|
/** Whether `text` writes out `answer` as whole words — an answer of one short word never counts. */
|
|
3379
|
-
function writesOut(text, answer) {
|
|
3392
|
+
function writesOut$1(text, answer) {
|
|
3380
3393
|
const needle = fold(answer);
|
|
3381
3394
|
return needle.length > 0 && !isShort(needle) && containsWords(fold(text), needle);
|
|
3382
3395
|
}
|
|
@@ -3389,7 +3402,7 @@ function critiqueMatching(draft) {
|
|
|
3389
3402
|
const title = written(draft.title);
|
|
3390
3403
|
if (title !== void 0 && prompts.some((prompt) => {
|
|
3391
3404
|
const answer = answerOf(prompt);
|
|
3392
|
-
return answer !== void 0 && writesOut(title, answer);
|
|
3405
|
+
return answer !== void 0 && writesOut$1(title, answer);
|
|
3393
3406
|
})) findings.push(finding("mt_title_reveals_answer", ["title"], "The title, which the learner sees, contains the answer to a prompt."));
|
|
3394
3407
|
for (const { first, second } of duplicates(options, optionText)) findings.push(finding("mt_options_duplicate", [
|
|
3395
3408
|
"options",
|
|
@@ -3399,7 +3412,7 @@ function critiqueMatching(draft) {
|
|
|
3399
3412
|
prompts.forEach((prompt, index) => {
|
|
3400
3413
|
const text = isRecord(prompt) ? written(prompt.text) : void 0;
|
|
3401
3414
|
const answer = answerOf(prompt);
|
|
3402
|
-
if (text !== void 0 && answer !== void 0 && writesOut(text, answer)) findings.push(finding("mt_answer_in_prompt", [
|
|
3415
|
+
if (text !== void 0 && answer !== void 0 && writesOut$1(text, answer)) findings.push(finding("mt_answer_in_prompt", [
|
|
3403
3416
|
"prompts",
|
|
3404
3417
|
index,
|
|
3405
3418
|
"text"
|
|
@@ -3798,6 +3811,301 @@ function pairsOneToOne(placeholders, gaps) {
|
|
|
3798
3811
|
if (new Set(ids).size !== ids.length || placeholders.size !== ids.length) return false;
|
|
3799
3812
|
return ids.every((id) => typeof id === "string" && placeholders.get(id) === 1);
|
|
3800
3813
|
}
|
|
3814
|
+
// src/mark-the-words.ts
|
|
3815
|
+
/**
|
|
3816
|
+
* The words of a mark-the-words text, and where an author's targets fall
|
|
3817
|
+
* among them. A leaf module: the schema, the scorer, the draft checks and the
|
|
3818
|
+
* component all read it, and it imports nothing.
|
|
3819
|
+
*
|
|
3820
|
+
* A learner marks words, never characters, and a response records a word by
|
|
3821
|
+
* its position — so how a text is cut into words is part of every stored
|
|
3822
|
+
* answer. The cut is the SDK's own (`lk-words@1`), not `Intl.Segmenter`,
|
|
3823
|
+
* whose word boundaries change with the ICU a runtime ships: the same text
|
|
3824
|
+
* must be the same words in every browser and on every server, now and when
|
|
3825
|
+
* the answer is graded again later.
|
|
3826
|
+
*/
|
|
3827
|
+
/** The name of the word cut. A change to it is a major: stored answers name words by position. */
|
|
3828
|
+
const MARK_THE_WORDS_TOKENIZER = "lk-words@1";
|
|
3829
|
+
/** The most words a mark-the-words text may have. */
|
|
3830
|
+
const MARK_THE_WORDS_MAX_WORDS = 1500;
|
|
3831
|
+
/**
|
|
3832
|
+
* A word: a run of letters, marks and digits, which may hold an apostrophe or
|
|
3833
|
+
* a hyphen between two such runs — "don't", "well-known", "1960s". Punctuation
|
|
3834
|
+
* and spaces are never part of one and can never be marked.
|
|
3835
|
+
*/
|
|
3836
|
+
const WORD = /[\p{L}\p{M}\p{N}]+(?:['’ʼ\-‐][\p{L}\p{M}\p{N}]+)*/gu;
|
|
3837
|
+
/** Every markable word of `text`, in order, cut by `lk-words@1`. */
|
|
3838
|
+
function markableWords(text) {
|
|
3839
|
+
const words = [];
|
|
3840
|
+
for (const match of text.matchAll(WORD)) words.push({
|
|
3841
|
+
index: words.length,
|
|
3842
|
+
text: match[0],
|
|
3843
|
+
start: match.index,
|
|
3844
|
+
end: match.index + match[0].length
|
|
3845
|
+
});
|
|
3846
|
+
return words;
|
|
3847
|
+
}
|
|
3848
|
+
/** A word as compared with a target's: composed, and with one apostrophe and one hyphen. */
|
|
3849
|
+
function wordKey(word) {
|
|
3850
|
+
return word.normalize("NFC").replace(/[’ʼ]/g, "'").replace(/‐/g, "-");
|
|
3851
|
+
}
|
|
3852
|
+
/** Every place `needle` appears in `words`, as the position of its first word. */
|
|
3853
|
+
function placesOf(words, needle) {
|
|
3854
|
+
const places = [];
|
|
3855
|
+
for (let at = 0; at + needle.length <= words.length; at += 1) if (needle.every((word, offset) => words[at + offset] === word)) places.push(at);
|
|
3856
|
+
return places;
|
|
3857
|
+
}
|
|
3858
|
+
/**
|
|
3859
|
+
* Where a target falls among `words`. Its text must be whole words of the text,
|
|
3860
|
+
* in order — case, accents and punctuation as written, apart from the kind of
|
|
3861
|
+
* apostrophe — and must say which appearance it means when there are several.
|
|
3862
|
+
*/
|
|
3863
|
+
function resolveMarkTarget(words, target) {
|
|
3864
|
+
const needle = markableWords(typeof target.text === "string" ? target.text : "").map((word) => wordKey(word.text));
|
|
3865
|
+
if (needle.length === 0) return {
|
|
3866
|
+
ok: false,
|
|
3867
|
+
problem: "no-words",
|
|
3868
|
+
found: 0
|
|
3869
|
+
};
|
|
3870
|
+
const places = placesOf(words.map((word) => wordKey(word.text)), needle);
|
|
3871
|
+
if (places.length === 0) return {
|
|
3872
|
+
ok: false,
|
|
3873
|
+
problem: "not-found",
|
|
3874
|
+
found: 0
|
|
3875
|
+
};
|
|
3876
|
+
const occurrence = target.occurrence;
|
|
3877
|
+
let at;
|
|
3878
|
+
if (occurrence === void 0) {
|
|
3879
|
+
if (places.length > 1) return {
|
|
3880
|
+
ok: false,
|
|
3881
|
+
problem: "ambiguous",
|
|
3882
|
+
found: places.length
|
|
3883
|
+
};
|
|
3884
|
+
at = places[0];
|
|
3885
|
+
} else {
|
|
3886
|
+
at = typeof occurrence === "number" && Number.isInteger(occurrence) && occurrence >= 1 ? places[occurrence - 1] : void 0;
|
|
3887
|
+
if (at === void 0) return {
|
|
3888
|
+
ok: false,
|
|
3889
|
+
problem: "no-such-occurrence",
|
|
3890
|
+
found: places.length
|
|
3891
|
+
};
|
|
3892
|
+
}
|
|
3893
|
+
return {
|
|
3894
|
+
ok: true,
|
|
3895
|
+
indices: needle.map((_, offset) => at + offset)
|
|
3896
|
+
};
|
|
3897
|
+
}
|
|
3898
|
+
/**
|
|
3899
|
+
* The word positions of each target of an item, in target order; a target
|
|
3900
|
+
* that falls nowhere has none. Read defensively: the scorer is reachable with
|
|
3901
|
+
* content an older build stored.
|
|
3902
|
+
*/
|
|
3903
|
+
function markTargetIndices(data) {
|
|
3904
|
+
const words = markableWords(typeof data.text === "string" ? data.text : "");
|
|
3905
|
+
return (Array.isArray(data.targets) ? data.targets : []).map((target) => {
|
|
3906
|
+
if (typeof target !== "object" || target === null) return [];
|
|
3907
|
+
const found = resolveMarkTarget(words, target);
|
|
3908
|
+
return found.ok ? found.indices : [];
|
|
3909
|
+
});
|
|
3910
|
+
}
|
|
3911
|
+
/**
|
|
3912
|
+
* A text and its targets from markup that stars each target, as an H5P "Mark
|
|
3913
|
+
* the Words" text does: `I *went* home and *had gone* to bed.` A `\*` is a
|
|
3914
|
+
* literal star. Each target names its appearance only where its words appear
|
|
3915
|
+
* more than once in the text.
|
|
3916
|
+
*
|
|
3917
|
+
* @throws Error when a star is left open.
|
|
3918
|
+
*/
|
|
3919
|
+
function markTheWordsFromMarkup(markup) {
|
|
3920
|
+
let text = "";
|
|
3921
|
+
const spans = [];
|
|
3922
|
+
let open = null;
|
|
3923
|
+
for (let at = 0; at < markup.length; at += 1) {
|
|
3924
|
+
const char = markup[at];
|
|
3925
|
+
if (char === "\\" && markup[at + 1] === "*") {
|
|
3926
|
+
text += "*";
|
|
3927
|
+
at += 1;
|
|
3928
|
+
} else if (char === "*") {
|
|
3929
|
+
if (open === null) open = text.length;
|
|
3930
|
+
else {
|
|
3931
|
+
spans.push({
|
|
3932
|
+
start: open,
|
|
3933
|
+
end: text.length
|
|
3934
|
+
});
|
|
3935
|
+
open = null;
|
|
3936
|
+
}
|
|
3937
|
+
} else text += char;
|
|
3938
|
+
}
|
|
3939
|
+
if (open !== null) throw new Error("markTheWordsFromMarkup: a * is opened and never closed.");
|
|
3940
|
+
const words = markableWords(text);
|
|
3941
|
+
const keys = words.map((word) => wordKey(word.text));
|
|
3942
|
+
const targets = [];
|
|
3943
|
+
for (const span of spans) {
|
|
3944
|
+
const inside = words.filter((word) => word.start >= span.start && word.end <= span.end);
|
|
3945
|
+
const first = inside[0];
|
|
3946
|
+
if (first === void 0) continue;
|
|
3947
|
+
const quote = text.slice(first.start, inside.at(-1).end);
|
|
3948
|
+
const places = placesOf(keys, inside.map((word) => wordKey(word.text)));
|
|
3949
|
+
targets.push(places.length > 1 ? {
|
|
3950
|
+
text: quote,
|
|
3951
|
+
occurrence: places.indexOf(first.index) + 1
|
|
3952
|
+
} : { text: quote });
|
|
3953
|
+
}
|
|
3954
|
+
return {
|
|
3955
|
+
text,
|
|
3956
|
+
targets
|
|
3957
|
+
};
|
|
3958
|
+
}
|
|
3959
|
+
// src/authoring/mark-the-words.ts
|
|
3960
|
+
/**
|
|
3961
|
+
* Draft support for `mark-the-words`.
|
|
3962
|
+
*
|
|
3963
|
+
* A new draft has an empty text and no targets, and `scoringStrategy` starts
|
|
3964
|
+
* at `all-or-nothing`, as on every built-in. An editor usually lets the author
|
|
3965
|
+
* click the words to mark; `markTheWordsFromMarkup` turns starred text into
|
|
3966
|
+
* the same `text` and `targets`.
|
|
3967
|
+
*/
|
|
3968
|
+
const markTheWordsAuthoring = {
|
|
3969
|
+
createDraft: ({ newId }) => ({
|
|
3970
|
+
schemaVersion: "1.0",
|
|
3971
|
+
type: "mark-the-words",
|
|
3972
|
+
id: newId(),
|
|
3973
|
+
title: "",
|
|
3974
|
+
text: "",
|
|
3975
|
+
targets: [],
|
|
3976
|
+
scoringStrategy: "all-or-nothing"
|
|
3977
|
+
}),
|
|
3978
|
+
checkDraft: checkMarkTheWordsDraft,
|
|
3979
|
+
critique: critiqueMarkTheWords
|
|
3980
|
+
};
|
|
3981
|
+
/** The words of a draft's text, or `null` when there is no usable text to resolve targets in. */
|
|
3982
|
+
function wordsOf(draft) {
|
|
3983
|
+
if (typeof draft.text !== "string") return null;
|
|
3984
|
+
const words = markableWords(draft.text);
|
|
3985
|
+
return words.length === 0 || words.length > 1500 ? null : words;
|
|
3986
|
+
}
|
|
3987
|
+
function checkMarkTheWordsDraft(draft) {
|
|
3988
|
+
const issues = checkIdentity(draft, "mark-the-words");
|
|
3989
|
+
issues.push(...checkScoringStrategy(draft));
|
|
3990
|
+
const text = draft.text;
|
|
3991
|
+
if (isUnwritten(text)) issues.push(issue("mw_text_required", ["text"], "Write the text the learner marks words in."));
|
|
3992
|
+
else if (typeof text === "string") {
|
|
3993
|
+
const count = markableWords(text).length;
|
|
3994
|
+
if (count === 0) issues.push(issue("mw_text_required", ["text"], "The text has no word to mark yet. Write it out."));
|
|
3995
|
+
else if (count > 1500) issues.push(issue("mw_text_too_long", ["text"], `The text has ${count} words; at most ${MARK_THE_WORDS_MAX_WORDS} can be marked in. Split it.`));
|
|
3996
|
+
}
|
|
3997
|
+
const targets = isUnset(draft.targets) ? [] : draft.targets;
|
|
3998
|
+
if (Array.isArray(targets)) {
|
|
3999
|
+
if (targets.length === 0) issues.push(issue("mw_targets_required", ["targets"], "Choose at least one word to mark."));
|
|
4000
|
+
issues.push(...checkTargets(targets, wordsOf(draft)));
|
|
4001
|
+
}
|
|
4002
|
+
issues.push(...checkSharedOptional(draft));
|
|
4003
|
+
return issues;
|
|
4004
|
+
}
|
|
4005
|
+
/**
|
|
4006
|
+
* Every target, each at its own path, where the schema reports it. Against
|
|
4007
|
+
* the text only when the text is usable: an unwritten one is already
|
|
4008
|
+
* reported, and every target would only restate that it is not found.
|
|
4009
|
+
*/
|
|
4010
|
+
function checkTargets(targets, words) {
|
|
4011
|
+
const issues = [];
|
|
4012
|
+
const owner = /* @__PURE__ */ new Map();
|
|
4013
|
+
for (const [index, target] of targets.entries()) {
|
|
4014
|
+
if (!isRecord(target)) continue;
|
|
4015
|
+
const ordinal = index + 1;
|
|
4016
|
+
const occurrence = target.occurrence;
|
|
4017
|
+
const occurrenceWellFormed = isUnset(occurrence) || typeof occurrence === "number" && Number.isInteger(occurrence) && occurrence >= 1;
|
|
4018
|
+
if (!occurrenceWellFormed) issues.push(issue("mw_target_occurrence_invalid", [
|
|
4019
|
+
"targets",
|
|
4020
|
+
index,
|
|
4021
|
+
"occurrence"
|
|
4022
|
+
], `Target ${ordinal}'s appearance must be a whole number from 1, or left out.`));
|
|
4023
|
+
if (isUnwritten(target.text)) {
|
|
4024
|
+
issues.push(issue("mw_target_text_required", [
|
|
4025
|
+
"targets",
|
|
4026
|
+
index,
|
|
4027
|
+
"text"
|
|
4028
|
+
], `Write the word or words target ${ordinal} marks.`));
|
|
4029
|
+
continue;
|
|
4030
|
+
}
|
|
4031
|
+
if (typeof target.text !== "string" || words === null || !occurrenceWellFormed) continue;
|
|
4032
|
+
const found = resolveMarkTarget(words, {
|
|
4033
|
+
text: target.text,
|
|
4034
|
+
...isUnset(occurrence) ? {} : { occurrence }
|
|
4035
|
+
});
|
|
4036
|
+
if (!found.ok) {
|
|
4037
|
+
issues.push(targetProblem(found.problem, found.found, index, target.text));
|
|
4038
|
+
continue;
|
|
4039
|
+
}
|
|
4040
|
+
if (found.indices.some((word) => owner.has(word))) {
|
|
4041
|
+
issues.push(issue("mw_target_overlap", ["targets", index], `Target ${ordinal} shares a word with target ${owner.get(found.indices.find((word) => owner.has(word))) + 1}. A word belongs to one target at most.`));
|
|
4042
|
+
continue;
|
|
4043
|
+
}
|
|
4044
|
+
for (const word of found.indices) owner.set(word, index);
|
|
4045
|
+
}
|
|
4046
|
+
return issues;
|
|
4047
|
+
}
|
|
4048
|
+
function targetProblem(problem, count, index, text) {
|
|
4049
|
+
const ordinal = index + 1;
|
|
4050
|
+
switch (problem) {
|
|
4051
|
+
case "no-words": return issue("mw_target_no_words", [
|
|
4052
|
+
"targets",
|
|
4053
|
+
index,
|
|
4054
|
+
"text"
|
|
4055
|
+
], `Target ${ordinal} has no word in it: punctuation alone cannot be marked.`);
|
|
4056
|
+
case "not-found": return issue("mw_target_not_found", [
|
|
4057
|
+
"targets",
|
|
4058
|
+
index,
|
|
4059
|
+
"text"
|
|
4060
|
+
], `"${text}" is not in the text as whole words. Copy it from the text, with the same capitals and accents.`);
|
|
4061
|
+
case "ambiguous": return issue("mw_target_ambiguous", [
|
|
4062
|
+
"targets",
|
|
4063
|
+
index,
|
|
4064
|
+
"text"
|
|
4065
|
+
], `"${text}" appears ${count} times in the text. Say which one target ${ordinal} is, with its appearance (1 to ${count}).`);
|
|
4066
|
+
case "no-such-occurrence": return issue("mw_target_occurrence_invalid", [
|
|
4067
|
+
"targets",
|
|
4068
|
+
index,
|
|
4069
|
+
"occurrence"
|
|
4070
|
+
], `"${text}" appears ${count === 1 ? "once" : `${count} times`} in the text, so it has no such appearance.`);
|
|
4071
|
+
}
|
|
4072
|
+
}
|
|
4073
|
+
/** Whether `text` writes out `answer` as whole words — a short answer never counts. */
|
|
4074
|
+
function writesOut(text, answer) {
|
|
4075
|
+
const needle = fold(answer);
|
|
4076
|
+
return needle.length > 0 && !isShort(needle) && containsWords(fold(text), needle);
|
|
4077
|
+
}
|
|
4078
|
+
function critiqueMarkTheWords(draft) {
|
|
4079
|
+
const findings = [];
|
|
4080
|
+
const words = wordsOf(draft);
|
|
4081
|
+
const targets = Array.isArray(draft.targets) ? draft.targets : [];
|
|
4082
|
+
if (words === null) return findings;
|
|
4083
|
+
const keys = words.map((word) => wordKey(word.text));
|
|
4084
|
+
const resolved = targets.map((target) => {
|
|
4085
|
+
if (!isRecord(target) || typeof target.text !== "string") return null;
|
|
4086
|
+
const found = resolveMarkTarget(words, {
|
|
4087
|
+
text: target.text,
|
|
4088
|
+
...typeof target.occurrence === "number" ? { occurrence: target.occurrence } : {}
|
|
4089
|
+
});
|
|
4090
|
+
return found.ok ? found.indices : null;
|
|
4091
|
+
});
|
|
4092
|
+
const marked = new Set(resolved.flatMap((indices) => indices ?? []));
|
|
4093
|
+
const title = typeof draft.title === "string" ? draft.title : "";
|
|
4094
|
+
if (resolved.some((indices) => indices !== null && writesOut(title, indices.map((word) => words[word].text).join(" ")))) findings.push(finding("mw_title_reveals_answer", ["title"], "The title, which the learner sees, contains a word to mark."));
|
|
4095
|
+
if (marked.size > 0 && marked.size * 2 > words.length) {
|
|
4096
|
+
const everything = Math.round((2 * marked.size - words.length) / marked.size * 100);
|
|
4097
|
+
findings.push(finding("mw_targets_dense", ["targets"], `${marked.size} of the text's ${words.length} words are to be marked, so a learner who marks every word scores ${everything}%. Mark fewer, or use a longer text.`));
|
|
4098
|
+
}
|
|
4099
|
+
resolved.forEach((indices, index) => {
|
|
4100
|
+
if (indices === null) return;
|
|
4101
|
+
const needle = indices.map((word) => keys[word]);
|
|
4102
|
+
for (let at = 0; at + needle.length <= keys.length; at += 1) if (needle.every((key, offset) => keys[at + offset] === key) && !indices.includes(at) && !marked.has(at)) {
|
|
4103
|
+
findings.push(finding("mw_target_elsewhere", ["targets", index], `"${needle.length === 1 ? words[indices[0]].text : indices.map((word) => words[word].text).join(" ")}" also appears where it is not to be marked. A learner who marks it there loses a mark: check that is meant.`));
|
|
4104
|
+
return;
|
|
4105
|
+
}
|
|
4106
|
+
});
|
|
4107
|
+
return findings;
|
|
4108
|
+
}
|
|
3801
4109
|
// src/schemas/matching.ts
|
|
3802
4110
|
/** Zod schema for one option of the shared list. Loose: a consumer sidecar survives validation. */
|
|
3803
4111
|
const MatchingOptionSchema = z.looseObject({
|
|
@@ -5014,6 +5322,97 @@ const GapSelectDataSchema = z.looseObject({
|
|
|
5014
5322
|
error: "Each gap needs unique choice ids and a `correctChoiceId` that is one of the choices it offers.",
|
|
5015
5323
|
path: ["gaps"]
|
|
5016
5324
|
});
|
|
5325
|
+
// src/schemas/mark-the-words.ts
|
|
5326
|
+
/** Zod schema for one target. Loose: a consumer sidecar survives validation. */
|
|
5327
|
+
const MarkTheWordsTargetSchema = z.looseObject({
|
|
5328
|
+
text: z.string().min(1),
|
|
5329
|
+
occurrence: z.number().int().min(1).optional(),
|
|
5330
|
+
feedback: z.string().optional()
|
|
5331
|
+
});
|
|
5332
|
+
/** What the schema says when a target falls on no words of the text. */
|
|
5333
|
+
const MARK_TARGET_MESSAGES = {
|
|
5334
|
+
"no-words": "A target must contain a word: punctuation alone cannot be marked.",
|
|
5335
|
+
"not-found": "A target must be whole words of the text, in order, written as the text writes them.",
|
|
5336
|
+
ambiguous: "These words appear more than once in the text: say which appearance with `occurrence`.",
|
|
5337
|
+
"no-such-occurrence": "The text does not have that many appearances of these words."
|
|
5338
|
+
};
|
|
5339
|
+
/**
|
|
5340
|
+
* Zod schema validating the full Mark the Words data contract.
|
|
5341
|
+
*
|
|
5342
|
+
* Beyond the shape, three guards, each reported at the field it is about:
|
|
5343
|
+
*
|
|
5344
|
+
* 1. **The text has a word to mark**, and at most 1500 of them.
|
|
5345
|
+
* 2. **Every target falls on whole words of the text** — once, or at the
|
|
5346
|
+
* appearance its `occurrence` names. A target that could be either of two
|
|
5347
|
+
* places is refused: which one is marked right would be a guess.
|
|
5348
|
+
* 3. **No two targets share a word.** A word marked once cannot count twice.
|
|
5349
|
+
*/
|
|
5350
|
+
const MarkTheWordsDataSchema = z.looseObject({
|
|
5351
|
+
schemaVersion: z.literal("1.0"),
|
|
5352
|
+
type: z.literal("mark-the-words"),
|
|
5353
|
+
id: z.string().min(1),
|
|
5354
|
+
title: z.string().min(1),
|
|
5355
|
+
instructions: z.string().optional(),
|
|
5356
|
+
text: z.string().min(1),
|
|
5357
|
+
targets: z.array(MarkTheWordsTargetSchema).min(1),
|
|
5358
|
+
scoringStrategy: z.enum(["all-or-nothing", "partial"]),
|
|
5359
|
+
media: MediaSchema.optional(),
|
|
5360
|
+
feedback: FeedbackSchema.optional(),
|
|
5361
|
+
passThreshold: z.number().min(0).max(1).optional(),
|
|
5362
|
+
locale: z.string().optional(),
|
|
5363
|
+
learningObjectives: z.array(z.string()).optional(),
|
|
5364
|
+
difficultyLevel: z.literal([
|
|
5365
|
+
1,
|
|
5366
|
+
2,
|
|
5367
|
+
3,
|
|
5368
|
+
4,
|
|
5369
|
+
5
|
|
5370
|
+
]).optional(),
|
|
5371
|
+
ai: AiPermissionsSchema.optional()
|
|
5372
|
+
}).superRefine((data, ctx) => {
|
|
5373
|
+
const words = markableWords(data.text);
|
|
5374
|
+
if (words.length === 0) {
|
|
5375
|
+
ctx.addIssue({
|
|
5376
|
+
code: "custom",
|
|
5377
|
+
path: ["text"],
|
|
5378
|
+
message: "The text has no word to mark."
|
|
5379
|
+
});
|
|
5380
|
+
return;
|
|
5381
|
+
}
|
|
5382
|
+
if (words.length > 1500) {
|
|
5383
|
+
ctx.addIssue({
|
|
5384
|
+
code: "custom",
|
|
5385
|
+
path: ["text"],
|
|
5386
|
+
message: `The text may have at most ${MARK_THE_WORDS_MAX_WORDS} words; it has ${words.length}.`
|
|
5387
|
+
});
|
|
5388
|
+
return;
|
|
5389
|
+
}
|
|
5390
|
+
const owner = /* @__PURE__ */ new Map();
|
|
5391
|
+
data.targets.forEach((target, index) => {
|
|
5392
|
+
const found = resolveMarkTarget(words, target);
|
|
5393
|
+
if (!found.ok) {
|
|
5394
|
+
ctx.addIssue({
|
|
5395
|
+
code: "custom",
|
|
5396
|
+
path: [
|
|
5397
|
+
"targets",
|
|
5398
|
+
index,
|
|
5399
|
+
found.problem === "no-such-occurrence" ? "occurrence" : "text"
|
|
5400
|
+
],
|
|
5401
|
+
message: MARK_TARGET_MESSAGES[found.problem]
|
|
5402
|
+
});
|
|
5403
|
+
return;
|
|
5404
|
+
}
|
|
5405
|
+
if (found.indices.some((word) => owner.has(word))) {
|
|
5406
|
+
ctx.addIssue({
|
|
5407
|
+
code: "custom",
|
|
5408
|
+
path: ["targets", index],
|
|
5409
|
+
message: "Two targets share a word: each word belongs to one target at most."
|
|
5410
|
+
});
|
|
5411
|
+
return;
|
|
5412
|
+
}
|
|
5413
|
+
for (const word of found.indices) owner.set(word, index);
|
|
5414
|
+
});
|
|
5415
|
+
});
|
|
5017
5416
|
// src/schemas/written-response.ts
|
|
5018
5417
|
/** Zod schema for a single rubric criterion. Loose: unknown keys preserved. */
|
|
5019
5418
|
const WrittenResponseRubricCriterionSchema = z.looseObject({
|
|
@@ -5242,6 +5641,19 @@ const RedactedMatchingDataSchema = z.strictObject({
|
|
|
5242
5641
|
shuffleOptions: z.boolean().optional()
|
|
5243
5642
|
});
|
|
5244
5643
|
/**
|
|
5644
|
+
* Redacted Mark the Words data: the text, and nothing about which words are
|
|
5645
|
+
* right — not the targets, not even how many there are, since a count tells a
|
|
5646
|
+
* learner when to stop. `scoringStrategy` stays answer-key, as on every type:
|
|
5647
|
+
* whether a wrong mark costs anything is the difference between marking
|
|
5648
|
+
* carefully and marking everything.
|
|
5649
|
+
*/
|
|
5650
|
+
const RedactedMarkTheWordsDataSchema = z.strictObject({
|
|
5651
|
+
...redactedBase,
|
|
5652
|
+
type: z.literal("mark-the-words"),
|
|
5653
|
+
instructions: z.string().optional(),
|
|
5654
|
+
text: z.string().min(1)
|
|
5655
|
+
});
|
|
5656
|
+
/**
|
|
5245
5657
|
* Redacted Written Response data: the prompt, word bounds and rubric are
|
|
5246
5658
|
* learner-visible (a rubric tells the learner what they are graded on);
|
|
5247
5659
|
* authored pass/fail feedback is removed until the grade exists.
|
|
@@ -5480,6 +5892,62 @@ function scoreGapSelect(data, response) {
|
|
|
5480
5892
|
details
|
|
5481
5893
|
};
|
|
5482
5894
|
}
|
|
5895
|
+
// src/scoring/activity-scorers/mark-the-words.ts
|
|
5896
|
+
/**
|
|
5897
|
+
* Scores a Mark the Words response.
|
|
5898
|
+
*
|
|
5899
|
+
* Each target is found when every one of its words is marked. Every marked
|
|
5900
|
+
* word that belongs to no target is a wrong mark, and costs one target's
|
|
5901
|
+
* worth: `partial` is (targets found − wrong marks) / targets, never below 0,
|
|
5902
|
+
* so marking every word is not a strategy. `all-or-nothing` is 1 only with
|
|
5903
|
+
* every target found and no wrong mark.
|
|
5904
|
+
*
|
|
5905
|
+
* A position that names no word, repeats one, or is not a whole number (a
|
|
5906
|
+
* tampered payload, or a text edited after the attempt) is ignored: it marks
|
|
5907
|
+
* nothing, right or wrong.
|
|
5908
|
+
*
|
|
5909
|
+
* Details: one per target (`t1`, `t2`… in target order) — `correct`, or
|
|
5910
|
+
* `incorrect` when only some of its words are marked, or `incorrect-omission`
|
|
5911
|
+
* when none are — with the target's word positions as `correctResponse`; then
|
|
5912
|
+
* one per wrong mark (`w` and the word's position), `incorrect`, with no
|
|
5913
|
+
* correct response. A review reads both to mark the text.
|
|
5914
|
+
*/
|
|
5915
|
+
function scoreMarkTheWords(data, response) {
|
|
5916
|
+
const count = markableWords(data.text).length;
|
|
5917
|
+
const raw = Array.isArray(response.marked) ? response.marked : [];
|
|
5918
|
+
const marked = new Set(raw.filter((word) => typeof word === "number" && Number.isInteger(word) && word >= 0).filter((word) => word < count));
|
|
5919
|
+
const targets = markTargetIndices(data);
|
|
5920
|
+
const inTargets = new Set(targets.flat());
|
|
5921
|
+
const details = [];
|
|
5922
|
+
let found = 0;
|
|
5923
|
+
targets.forEach((words, index) => {
|
|
5924
|
+
const hit = words.filter((word) => marked.has(word));
|
|
5925
|
+
const correct = words.length > 0 && hit.length === words.length;
|
|
5926
|
+
if (correct) found += 1;
|
|
5927
|
+
details.push({
|
|
5928
|
+
itemId: `t${index + 1}`,
|
|
5929
|
+
outcome: correct ? "correct" : hit.length > 0 ? "incorrect" : "incorrect-omission",
|
|
5930
|
+
learnerResponse: hit.map(String),
|
|
5931
|
+
correctResponse: words.map(String),
|
|
5932
|
+
weight: 1
|
|
5933
|
+
});
|
|
5934
|
+
});
|
|
5935
|
+
const wrong = [...marked].filter((word) => !inTargets.has(word)).sort((a, b) => a - b);
|
|
5936
|
+
for (const word of wrong) details.push({
|
|
5937
|
+
itemId: `w${word}`,
|
|
5938
|
+
outcome: "incorrect",
|
|
5939
|
+
learnerResponse: [String(word)],
|
|
5940
|
+
correctResponse: [],
|
|
5941
|
+
weight: 1
|
|
5942
|
+
});
|
|
5943
|
+
const total = targets.length;
|
|
5944
|
+
return {
|
|
5945
|
+
score: total === 0 ? 0 : data.scoringStrategy === "all-or-nothing" ? found === total && wrong.length === 0 ? 1 : 0 : Math.max(0, found - wrong.length) / total,
|
|
5946
|
+
maxScore: 1,
|
|
5947
|
+
feedback: null,
|
|
5948
|
+
details
|
|
5949
|
+
};
|
|
5950
|
+
}
|
|
5483
5951
|
// src/scoring/activity-scorers/matching.ts
|
|
5484
5952
|
/**
|
|
5485
5953
|
* Scores a Matching response.
|
|
@@ -5810,6 +6278,19 @@ const MATCHING_FIELD_POLICY = {
|
|
|
5810
6278
|
feedback: "answer-key"
|
|
5811
6279
|
}
|
|
5812
6280
|
};
|
|
6281
|
+
/**
|
|
6282
|
+
* Mark the Words: the text is what the learner marks in, so it is public; the
|
|
6283
|
+
* targets are the key, as one leaf — their number included, since a count
|
|
6284
|
+
* tells a learner when to stop marking.
|
|
6285
|
+
*/
|
|
6286
|
+
const MARK_THE_WORDS_FIELD_POLICY = {
|
|
6287
|
+
...SHARED_PUBLIC_FIELDS,
|
|
6288
|
+
instructions: "public",
|
|
6289
|
+
text: "public",
|
|
6290
|
+
targets: "answer-key",
|
|
6291
|
+
scoringStrategy: "answer-key",
|
|
6292
|
+
feedback: FEEDBACK_FIELD_POLICY
|
|
6293
|
+
};
|
|
5813
6294
|
const FILL_IN_THE_BLANKS_FIELD_POLICY = {
|
|
5814
6295
|
...SHARED_PUBLIC_FIELDS,
|
|
5815
6296
|
instructions: "public",
|
|
@@ -6010,6 +6491,30 @@ const matchingType = defineActivityType({
|
|
|
6010
6491
|
authoring: matchingAuthoring,
|
|
6011
6492
|
mediaSlots: () => MAIN_MEDIA
|
|
6012
6493
|
});
|
|
6494
|
+
/** Built-in Mark the Words descriptor. */
|
|
6495
|
+
const markTheWordsType = defineActivityType({
|
|
6496
|
+
type: "mark-the-words",
|
|
6497
|
+
schema: MarkTheWordsDataSchema,
|
|
6498
|
+
scoring: {
|
|
6499
|
+
kind: "sync",
|
|
6500
|
+
score: scoreMarkTheWords
|
|
6501
|
+
},
|
|
6502
|
+
isAnswered: (response) => Array.isArray(response?.marked) && response.marked.length > 0,
|
|
6503
|
+
fieldPolicy: MARK_THE_WORDS_FIELD_POLICY,
|
|
6504
|
+
redactedSchema: RedactedMarkTheWordsDataSchema,
|
|
6505
|
+
interop: {
|
|
6506
|
+
xapiActivityTypeIri: "http://adlnet.gov/expapi/activities/cmi.interaction",
|
|
6507
|
+
xapiInteractionType: "choice",
|
|
6508
|
+
correctResponsesPattern: (data) => [markTargetIndices(data).flat().sort((a, b) => a - b).join("[,]")]
|
|
6509
|
+
},
|
|
6510
|
+
interactions: [
|
|
6511
|
+
"word-marked",
|
|
6512
|
+
"word-unmarked",
|
|
6513
|
+
"submitted"
|
|
6514
|
+
],
|
|
6515
|
+
authoring: markTheWordsAuthoring,
|
|
6516
|
+
mediaSlots: () => MAIN_MEDIA
|
|
6517
|
+
});
|
|
6013
6518
|
/**
|
|
6014
6519
|
* Built-in Dictation descriptor. Graded synchronously: the score is the
|
|
6015
6520
|
* character-level similarity of the typed text to the transcript, a pure
|
|
@@ -6187,7 +6692,8 @@ const BUILT_IN_DESCRIPTORS = {
|
|
|
6187
6692
|
authoring: speakingResponseAuthoring,
|
|
6188
6693
|
mediaSlots: () => MAIN_MEDIA
|
|
6189
6694
|
}),
|
|
6190
|
-
matching: matchingType
|
|
6695
|
+
matching: matchingType,
|
|
6696
|
+
"mark-the-words": markTheWordsType
|
|
6191
6697
|
};
|
|
6192
6698
|
// src/schemas/read-schema.ts
|
|
6193
6699
|
/**
|
|
@@ -6351,6 +6857,6 @@ function getActivityTypeDescriptor(type) {
|
|
|
6351
6857
|
function registeredActivityTypes() {
|
|
6352
6858
|
return [...registry.keys()];
|
|
6353
6859
|
}
|
|
6354
|
-
export {
|
|
6860
|
+
export { isUnset as $, MARK_THE_WORDS_MAX_WORDS as A, placeholderOrder as At, levenshteinDistance as B, READ_ALOUD_MAX_REFERENCE_LENGTH as C, BUILT_IN_ACTIVITY_TYPES as Ct, SPEECH_ASSESSMENT_MAX_WORDS as D, fold as Dt, SPEECH_ASSESSMENT_MAX_TEXT_LENGTH as E, containsWords as Et, resolveMarkTarget as F, RedactedMediaSchema as Ft, readRubricLevels as G, LEVEL_PROBABILITY_TOLERANCE as H, keyPositionFinding as I, SCRIPT_MAX_TURNS as It, checkMedia as J, countWords as K, finding as L, ScriptSchema as Lt, markTargetIndices as M, FillInTheBlanksDataSchema as Mt, markTheWordsFromMarkup as N, PLACEHOLDER_RE as Nt, MultipleChoiceDataSchema as O, numberedPassage as Ot, markableWords as P, MediaSchema as Pt, isRecord as Q, normaliseFinding as R, maxUnits as Rt, READ_ALOUD_MAX_DIMENSION_WEIGHT as S, withValidationScope as St, READ_ALOUD_MAX_TAKES as T, isBuiltInActivityType as Tt, levelDistribution as U, matchText as V, levelKey as W, covers as X, coveredPathsOf as Y, isMissingId as Z, GapSelectDataSchema as _, DICTATION_MAX_EQUIVALENCE_LENGTH as _t, readSchema as a, GROUP_CAPTIONS_REVEAL_DICTATION as at, ReadAloudDataSchema as b, codePointLength as bt, dictationType as c, DeferredScoringError as ct, multipleChoiceType as d, STEP as dt, isUnwritten as et, readAloudType as f, alignDictation as ft, WrittenResponseDataSchema as g, DICTATION_MAX_EQUIVALENCES as gt, defineActivityType as h, DICTATION_MAX_ACCEPTED_TRANSCRIPTS as ht, isOwnSchema as i, DictationDataSchema as it, MARK_THE_WORDS_TOKENIZER as j, revealsAnswer as jt, MultipleChoiceOptionMediaSchema as k, passageWithoutPlaceholders as kt, fillInTheBlanksType as l, RedactedScoringError as lt, guardOf as m, dictationReferenceWords as mt, registerActivityType as n, pathKey as nt, standardJsonSchemaOf as o, groupCaptionsField as ot, writtenResponseType as p, alignSequences as pt, DRAFT_ISSUE_SEVERITY as q, registeredActivityTypes as r, refusesEmpty as rt, MEDIA_FIELD_POLICY as s, ActivitySchemaError as st, getActivityTypeDescriptor as t, issue as tt, gapSelectType as u, UnknownActivityTypeError as ut, SPEAKING_MEASURES as v, DICTATION_MAX_TEXT_LENGTH as vt, READ_ALOUD_MAX_SECONDS as w, everyBuiltInTypeHandled as wt, CANONICAL_LOCALE_RE as x, normalizeDictationText as xt, SpeakingResponseDataSchema as y, DICTATION_MAX_TRANSCRIPT_LENGTH as yt, policyFor as z };
|
|
6355
6861
|
|
|
6356
|
-
//# sourceMappingURL=registry-
|
|
6862
|
+
//# sourceMappingURL=registry-CroAI5nc.js.map
|