@intellectif/lk-core 0.8.2 → 0.10.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 +40 -0
- package/README.md +5 -4
- package/dist/{activity-CPtJUBek.d.cts → activity-B0zbvu18.d.cts} +177 -2
- package/dist/{activity-CPtJUBek.d.ts → activity-B0zbvu18.d.ts} +177 -2
- package/dist/{chunk-FVLKIL6W.cjs → chunk-3FEAEO3D.cjs} +18 -13
- package/dist/chunk-3FEAEO3D.cjs.map +1 -0
- package/dist/chunk-7ODQRNR4.cjs +2009 -0
- package/dist/chunk-7ODQRNR4.cjs.map +1 -0
- package/dist/chunk-FIS5KBCE.js +2009 -0
- package/dist/chunk-FIS5KBCE.js.map +1 -0
- package/dist/{chunk-NHGXOOZ2.js → chunk-IXXMYJI7.js} +2 -2
- package/dist/{chunk-YJZY5TPY.cjs → chunk-VBM5H6P5.cjs} +8 -8
- package/dist/{chunk-YJZY5TPY.cjs.map → chunk-VBM5H6P5.cjs.map} +1 -1
- package/dist/{chunk-7ACNBDSF.cjs → chunk-VTTRXGDD.cjs} +4 -4
- package/dist/{chunk-7ACNBDSF.cjs.map → chunk-VTTRXGDD.cjs.map} +1 -1
- package/dist/{chunk-2M76F32Y.js → chunk-XGCTAQSS.js} +7 -2
- package/dist/chunk-XGCTAQSS.js.map +1 -0
- package/dist/{chunk-LNA33IK7.js → chunk-ZTY4UIUD.js} +2 -2
- package/dist/{index-BvrA8nIV.d.ts → index-DSfETm4b.d.ts} +321 -3
- package/dist/{index-gIN564mV.d.cts → index-u_rBLkuc.d.cts} +321 -3
- package/dist/index.cjs +156 -42
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +159 -8
- package/dist/index.d.ts +159 -8
- package/dist/index.js +137 -23
- package/dist/index.js.map +1 -1
- package/dist/schemas.cjs +25 -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 +24 -2
- 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 +1 -1
- package/dist/chunk-2M76F32Y.js.map +0 -1
- package/dist/chunk-FKH4YT5X.cjs +0 -748
- package/dist/chunk-FKH4YT5X.cjs.map +0 -1
- package/dist/chunk-FVLKIL6W.cjs.map +0 -1
- package/dist/chunk-HCOWSGEZ.js +0 -748
- package/dist/chunk-HCOWSGEZ.js.map +0 -1
- /package/dist/{chunk-NHGXOOZ2.js.map → chunk-IXXMYJI7.js.map} +0 -0
- /package/dist/{chunk-LNA33IK7.js.map → chunk-ZTY4UIUD.js.map} +0 -0
package/dist/chunk-HCOWSGEZ.js
DELETED
|
@@ -1,748 +0,0 @@
|
|
|
1
|
-
// src/count-words.ts
|
|
2
|
-
function countWords(text) {
|
|
3
|
-
if (typeof text !== "string") {
|
|
4
|
-
return 0;
|
|
5
|
-
}
|
|
6
|
-
const trimmed = text.trim();
|
|
7
|
-
if (trimmed === "") {
|
|
8
|
-
return 0;
|
|
9
|
-
}
|
|
10
|
-
return trimmed.split(/\s+/).length;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
// src/errors.ts
|
|
14
|
-
var ActivitySchemaError = class extends Error {
|
|
15
|
-
constructor(activityType, errors) {
|
|
16
|
-
super(`Invalid activity data for type "${activityType}"`);
|
|
17
|
-
this.activityType = activityType;
|
|
18
|
-
this.errors = errors;
|
|
19
|
-
this.name = "ActivitySchemaError";
|
|
20
|
-
}
|
|
21
|
-
activityType;
|
|
22
|
-
errors;
|
|
23
|
-
};
|
|
24
|
-
var UnknownActivityTypeError = class extends Error {
|
|
25
|
-
constructor(activityType) {
|
|
26
|
-
super(`Activity type "${activityType}" is not registered`);
|
|
27
|
-
this.activityType = activityType;
|
|
28
|
-
this.name = "UnknownActivityTypeError";
|
|
29
|
-
}
|
|
30
|
-
activityType;
|
|
31
|
-
};
|
|
32
|
-
var RedactedScoringError = class extends Error {
|
|
33
|
-
constructor(activityType) {
|
|
34
|
-
super(
|
|
35
|
-
`Activity data for "${activityType}" carries no answer key (it looks redacted), so it cannot be scored. Score against the full activity data server-side, or use evaluate() which returns { status: "unscorable" }.`
|
|
36
|
-
);
|
|
37
|
-
this.activityType = activityType;
|
|
38
|
-
this.name = "RedactedScoringError";
|
|
39
|
-
}
|
|
40
|
-
activityType;
|
|
41
|
-
};
|
|
42
|
-
var DeferredScoringError = class extends Error {
|
|
43
|
-
constructor(activityType) {
|
|
44
|
-
super(
|
|
45
|
-
`Activity type "${activityType}" is graded asynchronously and has no synchronous score. Use evaluate() \u2014 it returns { status: 'deferred' } for this type.`
|
|
46
|
-
);
|
|
47
|
-
this.activityType = activityType;
|
|
48
|
-
this.name = "DeferredScoringError";
|
|
49
|
-
}
|
|
50
|
-
activityType;
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
// src/schemas/feedback.ts
|
|
54
|
-
import { z } from "zod/v4";
|
|
55
|
-
var FeedbackSchema = z.looseObject({
|
|
56
|
-
correct: z.string().min(1).optional(),
|
|
57
|
-
incorrect: z.string().min(1).optional()
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
// src/schemas/media.ts
|
|
61
|
-
import { z as z2 } from "zod/v4";
|
|
62
|
-
var MediaUrlSchema = z2.union([
|
|
63
|
-
z2.url().refine((value) => /^(https?|data|blob):/i.test(value), {
|
|
64
|
-
error: "Absolute media URLs must use the https:, http:, data:, or blob: scheme."
|
|
65
|
-
}),
|
|
66
|
-
z2.string().regex(/^\/(?!\/)\S*$/, {
|
|
67
|
-
error: 'Relative media URLs must be root-relative (a single leading "/").'
|
|
68
|
-
})
|
|
69
|
-
]);
|
|
70
|
-
var NativeControlHintSchema = z2.enum(["hide-download", "hide-rate"]);
|
|
71
|
-
var MediaPlaybackSchema = z2.strictObject({
|
|
72
|
-
/**
|
|
73
|
-
* `native` (the resolved default) renders today's `<audio controls>`
|
|
74
|
-
* unchanged. `minimal` — resolved automatically whenever any enforcement
|
|
75
|
-
* field is set — replaces the browser bar with the SDK transport:
|
|
76
|
-
* play/pause, elapsed/total, mute, volume, optional speed, optional
|
|
77
|
-
* scrubber, and a live plays-remaining status.
|
|
78
|
-
*/
|
|
79
|
-
controls: z2.enum(["native", "minimal"]).optional(),
|
|
80
|
-
/**
|
|
81
|
-
* How many times the recording may be STARTED. A play is consumed when
|
|
82
|
-
* playback begins from anywhere other than where it last stopped, so
|
|
83
|
-
* pausing, resuming, and paging between the questions of one listening
|
|
84
|
-
* group are all free. Enforced in `practice` and `exam`; never in `review`.
|
|
85
|
-
*/
|
|
86
|
-
maxPlays: z2.number().int().min(1).max(20).optional(),
|
|
87
|
-
/** `none` renders no scrubber and reverts an out-of-band seek to the high-water mark. */
|
|
88
|
-
seek: z2.enum(["allow", "none"]).optional(),
|
|
89
|
-
/** `fixed` renders no speed control and snaps `playbackRate` back to 1. */
|
|
90
|
-
rate: z2.enum(["allow", "fixed"]).optional(),
|
|
91
|
-
/** Advisory only. See {@link NativeControlHintSchema}. */
|
|
92
|
-
nativeControlHints: z2.array(NativeControlHintSchema).min(1).max(2).optional()
|
|
93
|
-
});
|
|
94
|
-
var MediaSchema = z2.looseObject({
|
|
95
|
-
type: z2.enum(["image", "audio", "video", "embed"]),
|
|
96
|
-
url: MediaUrlSchema,
|
|
97
|
-
alt: z2.string().min(1).optional(),
|
|
98
|
-
captionsUrl: MediaUrlSchema.optional(),
|
|
99
|
-
playback: MediaPlaybackSchema.optional()
|
|
100
|
-
}).refine(
|
|
101
|
-
(m) => m.type !== "image" && m.type !== "embed" || typeof m.alt === "string" && m.alt.length > 0,
|
|
102
|
-
{
|
|
103
|
-
error: "image and embed media require non-empty alt text (WCAG 1.1.1 / 4.1.2).",
|
|
104
|
-
path: ["alt"]
|
|
105
|
-
}
|
|
106
|
-
).refine((m) => m.type !== "embed" || /^https?:\/\//i.test(m.url), {
|
|
107
|
-
error: "embed media requires an absolute http(s) provider URL. data:, blob:, and relative URLs are not allowed for embeds \u2014 the embed iframe runs with allow-scripts, and a data:/same-origin document there is an XSS vector.",
|
|
108
|
-
path: ["url"]
|
|
109
|
-
}).refine((m) => m.playback === void 0 || m.type === "audio", {
|
|
110
|
-
error: "playback policy is supported on audio media only. An embed is a provider iframe the SDK cannot control at all (it has no reliable JS API for a third-party player); an image has nothing to play; video is deliberately deferred, because a video transport must also own fullscreen and Picture-in-Picture and the SDK will not pretend to govern those yet.",
|
|
111
|
-
path: ["playback"]
|
|
112
|
-
}).refine(
|
|
113
|
-
(m) => m.playback?.controls !== "native" || m.playback.maxPlays === void 0 && m.playback.seek !== "none" && m.playback.rate !== "fixed",
|
|
114
|
-
{
|
|
115
|
-
error: 'controls: "native" cannot carry maxPlays, seek: "none" or rate: "fixed". The browser\'s own bar keeps its play button enabled after a budget is spent, and keeps a scrubber that would move and then silently snap back \u2014 a control that looks operable and does nothing (WCAG 3.2.2 / 4.1.3). Omit `controls` and the SDK transport is used automatically, or use nativeControlHints for a cosmetic hint.',
|
|
116
|
-
path: ["playback", "controls"]
|
|
117
|
-
}
|
|
118
|
-
).refine((m) => m.playback?.maxPlays === void 0 || m.playback.seek !== "allow", {
|
|
119
|
-
error: 'maxPlays cannot be combined with seek: "allow". A play is consumed when playback starts from somewhere other than where it stopped, so scrubbing back mid-play would replay the whole recording without spending anything. Omit `seek` \u2014 it resolves to "none" under a budget.',
|
|
120
|
-
path: ["playback", "seek"]
|
|
121
|
-
}).refine(
|
|
122
|
-
(m) => m.playback?.controls !== "minimal" || m.playback.maxPlays !== void 0 || m.playback.seek === "none" || m.playback.rate === "fixed",
|
|
123
|
-
{
|
|
124
|
-
error: 'controls: "minimal" with nothing to enforce trades the browser\'s localized, familiar control bar for the SDK\'s, and buys nothing. Set maxPlays, seek: "none" or rate: "fixed", or omit `controls`.',
|
|
125
|
-
path: ["playback", "controls"]
|
|
126
|
-
}
|
|
127
|
-
).refine(
|
|
128
|
-
(m) => m.playback?.nativeControlHints === void 0 || m.playback.controls !== "minimal" && m.playback.maxPlays === void 0 && m.playback.seek !== "none" && m.playback.rate !== "fixed",
|
|
129
|
-
{
|
|
130
|
-
error: "nativeControlHints only affects the browser's own control bar, and an enforcing policy replaces that bar with the SDK transport \u2014 so the hints would be silently inert. Use them on an otherwise unrestricted recording, or drop them.",
|
|
131
|
-
path: ["playback", "nativeControlHints"]
|
|
132
|
-
}
|
|
133
|
-
);
|
|
134
|
-
var RedactedMediaSchema = z2.strictObject({
|
|
135
|
-
type: z2.enum(["image", "audio", "video", "embed"]),
|
|
136
|
-
url: MediaUrlSchema,
|
|
137
|
-
alt: z2.string().min(1).optional(),
|
|
138
|
-
captionsUrl: MediaUrlSchema.optional(),
|
|
139
|
-
playback: MediaPlaybackSchema.optional()
|
|
140
|
-
}).refine(
|
|
141
|
-
(m) => m.type !== "image" && m.type !== "embed" || typeof m.alt === "string" && m.alt.length > 0,
|
|
142
|
-
{
|
|
143
|
-
error: "image and embed media require non-empty alt text (WCAG 1.1.1 / 4.1.2).",
|
|
144
|
-
path: ["alt"]
|
|
145
|
-
}
|
|
146
|
-
).refine((m) => m.type !== "embed" || /^https?:\/\//i.test(m.url), {
|
|
147
|
-
error: "embed media requires an absolute http(s) provider URL. data:, blob:, and relative URLs are not allowed for embeds \u2014 the embed iframe runs with allow-scripts, and a data:/same-origin document there is an XSS vector.",
|
|
148
|
-
path: ["url"]
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
// src/schemas/fill-in-the-blanks.ts
|
|
152
|
-
import { z as z3 } from "zod/v4";
|
|
153
|
-
var PLACEHOLDER_RE = /\{\{\s*([^{}]+?)\s*\}\}/g;
|
|
154
|
-
var TextMatchPolicySchema = z3.looseObject({
|
|
155
|
-
caseSensitive: z3.boolean().optional(),
|
|
156
|
-
trim: z3.boolean().optional(),
|
|
157
|
-
normalize: z3.enum(["none", "NFC", "NFKC"]).optional(),
|
|
158
|
-
foldDiacritics: z3.boolean().optional(),
|
|
159
|
-
collapseInnerWhitespace: z3.boolean().optional(),
|
|
160
|
-
ignorePunctuation: z3.boolean().optional(),
|
|
161
|
-
levenshtein: z3.number().int().min(0).optional(),
|
|
162
|
-
locale: z3.string().optional()
|
|
163
|
-
});
|
|
164
|
-
var BlankConfigSchema = z3.looseObject({
|
|
165
|
-
id: z3.string().min(1),
|
|
166
|
-
acceptedAnswers: z3.array(
|
|
167
|
-
z3.string().min(1).refine((answer) => answer.trim().length > 0, {
|
|
168
|
-
error: "Accepted answers must contain non-whitespace characters."
|
|
169
|
-
})
|
|
170
|
-
).min(1),
|
|
171
|
-
caseSensitive: z3.boolean().optional(),
|
|
172
|
-
trimWhitespace: z3.boolean().optional(),
|
|
173
|
-
match: TextMatchPolicySchema.optional(),
|
|
174
|
-
hint: z3.string().optional(),
|
|
175
|
-
feedback: z3.string().optional()
|
|
176
|
-
});
|
|
177
|
-
var FillInTheBlanksDataSchema = z3.looseObject({
|
|
178
|
-
schemaVersion: z3.literal("1.0"),
|
|
179
|
-
type: z3.literal("fill-in-the-blanks"),
|
|
180
|
-
id: z3.string().min(1),
|
|
181
|
-
title: z3.string().min(1),
|
|
182
|
-
passage: z3.string().min(1),
|
|
183
|
-
passageHtml: z3.string().optional(),
|
|
184
|
-
blanks: z3.array(BlankConfigSchema).min(1),
|
|
185
|
-
scoringStrategy: z3.enum(["all-or-nothing", "partial"]),
|
|
186
|
-
media: MediaSchema.optional(),
|
|
187
|
-
feedback: FeedbackSchema.optional(),
|
|
188
|
-
passThreshold: z3.number().min(0).max(1).optional(),
|
|
189
|
-
locale: z3.string().optional(),
|
|
190
|
-
learningObjectives: z3.array(z3.string()).optional(),
|
|
191
|
-
difficultyLevel: z3.literal([1, 2, 3, 4, 5]).optional()
|
|
192
|
-
}).refine(
|
|
193
|
-
(data) => {
|
|
194
|
-
const placeholderCounts = /* @__PURE__ */ new Map();
|
|
195
|
-
for (const match of data.passage.matchAll(PLACEHOLDER_RE)) {
|
|
196
|
-
const id = match[1];
|
|
197
|
-
placeholderCounts.set(id, (placeholderCounts.get(id) ?? 0) + 1);
|
|
198
|
-
}
|
|
199
|
-
const blankIds = data.blanks.map((blank) => blank.id);
|
|
200
|
-
if (new Set(blankIds).size !== blankIds.length) {
|
|
201
|
-
return false;
|
|
202
|
-
}
|
|
203
|
-
if (placeholderCounts.size !== blankIds.length) {
|
|
204
|
-
return false;
|
|
205
|
-
}
|
|
206
|
-
return blankIds.every((id) => placeholderCounts.get(id) === 1);
|
|
207
|
-
},
|
|
208
|
-
{
|
|
209
|
-
error: "Each blank id must appear exactly once in blanks[] and have exactly one matching {{id}} placeholder in the passage, and vice versa.",
|
|
210
|
-
path: ["passage"]
|
|
211
|
-
}
|
|
212
|
-
);
|
|
213
|
-
|
|
214
|
-
// src/schemas/multiple-choice.ts
|
|
215
|
-
import { z as z4 } from "zod/v4";
|
|
216
|
-
var MultipleChoiceOptionSchema = z4.looseObject({
|
|
217
|
-
id: z4.string().min(1),
|
|
218
|
-
text: z4.string().min(1),
|
|
219
|
-
isCorrect: z4.boolean(),
|
|
220
|
-
feedback: z4.string().optional()
|
|
221
|
-
});
|
|
222
|
-
var MultipleChoiceDataSchema = z4.looseObject({
|
|
223
|
-
schemaVersion: z4.literal("1.0"),
|
|
224
|
-
type: z4.literal("multiple-choice"),
|
|
225
|
-
id: z4.string().min(1),
|
|
226
|
-
title: z4.string().min(1),
|
|
227
|
-
question: z4.string().min(1),
|
|
228
|
-
questionHtml: z4.string().optional(),
|
|
229
|
-
mode: z4.enum(["single", "multi"]),
|
|
230
|
-
options: z4.array(MultipleChoiceOptionSchema).min(2).max(26),
|
|
231
|
-
scoringStrategy: z4.enum(["all-or-nothing", "partial"]),
|
|
232
|
-
media: MediaSchema.optional(),
|
|
233
|
-
feedback: FeedbackSchema.optional(),
|
|
234
|
-
passThreshold: z4.number().min(0).max(1).optional(),
|
|
235
|
-
shuffle: z4.boolean().optional(),
|
|
236
|
-
locale: z4.string().optional(),
|
|
237
|
-
learningObjectives: z4.array(z4.string()).optional(),
|
|
238
|
-
difficultyLevel: z4.literal([1, 2, 3, 4, 5]).optional()
|
|
239
|
-
}).refine((data) => data.options.some((option) => option.isCorrect), {
|
|
240
|
-
error: "At least one option must be marked correct.",
|
|
241
|
-
path: ["options"]
|
|
242
|
-
}).refine(
|
|
243
|
-
(data) => data.mode !== "single" || data.options.filter((option) => option.isCorrect).length === 1,
|
|
244
|
-
{
|
|
245
|
-
error: 'Single-select activities (mode: "single") must have exactly one correct option.',
|
|
246
|
-
path: ["options"]
|
|
247
|
-
}
|
|
248
|
-
).refine((data) => new Set(data.options.map((option) => option.id)).size === data.options.length, {
|
|
249
|
-
error: "Option ids must be unique within the activity.",
|
|
250
|
-
path: ["options"]
|
|
251
|
-
});
|
|
252
|
-
|
|
253
|
-
// src/schemas/written-response.ts
|
|
254
|
-
import { z as z5 } from "zod/v4";
|
|
255
|
-
var WrittenResponseRubricCriterionSchema = z5.looseObject({
|
|
256
|
-
name: z5.string().min(1),
|
|
257
|
-
description: z5.string().optional(),
|
|
258
|
-
weight: z5.number().min(0)
|
|
259
|
-
});
|
|
260
|
-
var WrittenResponseRubricSchema = z5.looseObject({
|
|
261
|
-
label: z5.string().optional(),
|
|
262
|
-
criteria: z5.array(WrittenResponseRubricCriterionSchema).min(1)
|
|
263
|
-
});
|
|
264
|
-
var RedactedWrittenResponseRubricCriterionSchema = z5.strictObject({
|
|
265
|
-
name: z5.string().min(1),
|
|
266
|
-
description: z5.string().optional(),
|
|
267
|
-
weight: z5.number().min(0)
|
|
268
|
-
});
|
|
269
|
-
var RedactedWrittenResponseRubricSchema = z5.strictObject({
|
|
270
|
-
label: z5.string().optional(),
|
|
271
|
-
criteria: z5.array(RedactedWrittenResponseRubricCriterionSchema).min(1)
|
|
272
|
-
});
|
|
273
|
-
var WrittenResponseDataSchema = z5.looseObject({
|
|
274
|
-
schemaVersion: z5.literal("1.0"),
|
|
275
|
-
type: z5.literal("written-response"),
|
|
276
|
-
id: z5.string().min(1),
|
|
277
|
-
title: z5.string().min(1),
|
|
278
|
-
prompt: z5.string(),
|
|
279
|
-
promptHtml: z5.string().optional(),
|
|
280
|
-
minWords: z5.number().int().min(0),
|
|
281
|
-
maxWords: z5.number().int().min(1),
|
|
282
|
-
rubric: WrittenResponseRubricSchema.optional(),
|
|
283
|
-
languageTarget: z5.string().optional(),
|
|
284
|
-
media: MediaSchema.optional(),
|
|
285
|
-
feedback: FeedbackSchema.optional(),
|
|
286
|
-
passThreshold: z5.number().min(0).max(1).optional(),
|
|
287
|
-
locale: z5.string().optional(),
|
|
288
|
-
learningObjectives: z5.array(z5.string()).optional(),
|
|
289
|
-
difficultyLevel: z5.literal([1, 2, 3, 4, 5]).optional()
|
|
290
|
-
}).refine((data) => data.maxWords >= data.minWords, {
|
|
291
|
-
error: "maxWords must be greater than or equal to minWords.",
|
|
292
|
-
path: ["maxWords"]
|
|
293
|
-
});
|
|
294
|
-
|
|
295
|
-
// src/schemas/redacted.ts
|
|
296
|
-
import { z as z6 } from "zod/v4";
|
|
297
|
-
var redactedBase = {
|
|
298
|
-
/** Marker distinguishing a redacted projection from full activity data. */
|
|
299
|
-
redacted: z6.literal(true),
|
|
300
|
-
/** Slot identity, carried through redaction so the client and the plan agree. */
|
|
301
|
-
slotKey: z6.string().min(1).optional(),
|
|
302
|
-
schemaVersion: z6.literal("1.0"),
|
|
303
|
-
id: z6.string().min(1),
|
|
304
|
-
title: z6.string().min(1),
|
|
305
|
-
media: RedactedMediaSchema.optional(),
|
|
306
|
-
passThreshold: z6.number().min(0).max(1).optional(),
|
|
307
|
-
locale: z6.string().optional(),
|
|
308
|
-
learningObjectives: z6.array(z6.string()).optional(),
|
|
309
|
-
difficultyLevel: z6.literal([1, 2, 3, 4, 5]).optional()
|
|
310
|
-
};
|
|
311
|
-
var RedactedMultipleChoiceOptionSchema = z6.strictObject({
|
|
312
|
-
id: z6.string().min(1),
|
|
313
|
-
text: z6.string().min(1)
|
|
314
|
-
});
|
|
315
|
-
var RedactedMultipleChoiceDataSchema = z6.strictObject({
|
|
316
|
-
...redactedBase,
|
|
317
|
-
type: z6.literal("multiple-choice"),
|
|
318
|
-
question: z6.string().min(1),
|
|
319
|
-
questionHtml: z6.string().optional(),
|
|
320
|
-
mode: z6.enum(["single", "multi"]),
|
|
321
|
-
options: z6.array(RedactedMultipleChoiceOptionSchema).min(2).max(26),
|
|
322
|
-
shuffle: z6.boolean().optional()
|
|
323
|
-
});
|
|
324
|
-
var RedactedBlankConfigSchema = z6.strictObject({
|
|
325
|
-
id: z6.string().min(1),
|
|
326
|
-
hint: z6.string().optional()
|
|
327
|
-
});
|
|
328
|
-
var RedactedFillInTheBlanksDataSchema = z6.strictObject({
|
|
329
|
-
...redactedBase,
|
|
330
|
-
type: z6.literal("fill-in-the-blanks"),
|
|
331
|
-
passage: z6.string().min(1),
|
|
332
|
-
passageHtml: z6.string().optional(),
|
|
333
|
-
blanks: z6.array(RedactedBlankConfigSchema).min(1)
|
|
334
|
-
});
|
|
335
|
-
var RedactedWrittenResponseDataSchema = z6.strictObject({
|
|
336
|
-
...redactedBase,
|
|
337
|
-
type: z6.literal("written-response"),
|
|
338
|
-
prompt: z6.string(),
|
|
339
|
-
promptHtml: z6.string().optional(),
|
|
340
|
-
minWords: z6.number().int().min(0),
|
|
341
|
-
maxWords: z6.number().int().min(1),
|
|
342
|
-
rubric: RedactedWrittenResponseRubricSchema.optional(),
|
|
343
|
-
languageTarget: z6.string().optional()
|
|
344
|
-
});
|
|
345
|
-
|
|
346
|
-
// src/scoring/text-match.ts
|
|
347
|
-
var COMBINING_MARKS_RE = /\p{M}/gu;
|
|
348
|
-
var PUNCTUATION_RE = /\p{P}/gu;
|
|
349
|
-
function applyBaseline(value, policy) {
|
|
350
|
-
let result = value;
|
|
351
|
-
if (policy.trim !== false) {
|
|
352
|
-
result = result.trim();
|
|
353
|
-
}
|
|
354
|
-
if (policy.caseSensitive !== true) {
|
|
355
|
-
result = policy.locale ? result.toLocaleLowerCase(policy.locale) : result.toLowerCase();
|
|
356
|
-
}
|
|
357
|
-
return result;
|
|
358
|
-
}
|
|
359
|
-
function applyNormalization(value, policy) {
|
|
360
|
-
let result = value;
|
|
361
|
-
if (policy.normalize === "NFC" || policy.normalize === "NFKC") {
|
|
362
|
-
result = result.normalize(policy.normalize);
|
|
363
|
-
}
|
|
364
|
-
if (policy.ignorePunctuation === true) {
|
|
365
|
-
result = result.replace(PUNCTUATION_RE, "");
|
|
366
|
-
}
|
|
367
|
-
if (policy.collapseInnerWhitespace === true) {
|
|
368
|
-
result = result.replace(/(?<=\S)\s+(?=\S)/g, " ");
|
|
369
|
-
if (policy.trim !== false) {
|
|
370
|
-
result = result.trim();
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
return result;
|
|
374
|
-
}
|
|
375
|
-
function applyDiacriticFold(value) {
|
|
376
|
-
return value.normalize("NFD").replace(COMBINING_MARKS_RE, "").normalize("NFC");
|
|
377
|
-
}
|
|
378
|
-
function levenshteinDistance(a, b, max) {
|
|
379
|
-
if (a === b) return 0;
|
|
380
|
-
if (Math.abs(a.length - b.length) > max) return max + 1;
|
|
381
|
-
if (a.length === 0) return b.length;
|
|
382
|
-
if (b.length === 0) return a.length;
|
|
383
|
-
let previous = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
384
|
-
for (let i = 1; i <= a.length; i += 1) {
|
|
385
|
-
const current = [i];
|
|
386
|
-
let rowMin = i;
|
|
387
|
-
for (let j = 1; j <= b.length; j += 1) {
|
|
388
|
-
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
389
|
-
const value = Math.min(
|
|
390
|
-
previous[j] + 1,
|
|
391
|
-
current[j - 1] + 1,
|
|
392
|
-
previous[j - 1] + cost
|
|
393
|
-
);
|
|
394
|
-
current.push(value);
|
|
395
|
-
if (value < rowMin) rowMin = value;
|
|
396
|
-
}
|
|
397
|
-
if (rowMin > max) return max + 1;
|
|
398
|
-
previous = current;
|
|
399
|
-
}
|
|
400
|
-
return previous[b.length];
|
|
401
|
-
}
|
|
402
|
-
function matchText(input, accepted, policy = {}) {
|
|
403
|
-
const acceptedList = typeof accepted === "string" ? [accepted] : accepted;
|
|
404
|
-
const baselineInput = applyBaseline(input, policy);
|
|
405
|
-
const baselineAccepted = acceptedList.map((answer) => applyBaseline(answer, policy));
|
|
406
|
-
if (baselineAccepted.some((answer) => answer === baselineInput)) {
|
|
407
|
-
return { matched: true, via: "exact" };
|
|
408
|
-
}
|
|
409
|
-
const usesNormalization = policy.normalize === "NFC" || policy.normalize === "NFKC" || policy.ignorePunctuation === true || policy.collapseInnerWhitespace === true;
|
|
410
|
-
const normalizedInput = usesNormalization ? applyNormalization(baselineInput, policy) : baselineInput;
|
|
411
|
-
const normalizedAccepted = usesNormalization ? baselineAccepted.map((answer) => applyNormalization(answer, policy)) : baselineAccepted;
|
|
412
|
-
if (usesNormalization && normalizedAccepted.some((answer) => answer === normalizedInput)) {
|
|
413
|
-
return { matched: true, via: "normalized" };
|
|
414
|
-
}
|
|
415
|
-
const foldedInput = policy.foldDiacritics === true ? applyDiacriticFold(normalizedInput) : normalizedInput;
|
|
416
|
-
const foldedAccepted = policy.foldDiacritics === true ? normalizedAccepted.map((answer) => applyDiacriticFold(answer)) : normalizedAccepted;
|
|
417
|
-
if (policy.foldDiacritics === true && foldedAccepted.some((answer) => answer === foldedInput)) {
|
|
418
|
-
return { matched: true, via: "folded" };
|
|
419
|
-
}
|
|
420
|
-
const maxDistance = policy.levenshtein ?? 0;
|
|
421
|
-
if (maxDistance > 0 && foldedInput.trim().length > 0 && foldedAccepted.some(
|
|
422
|
-
(answer) => levenshteinDistance(foldedInput, answer, maxDistance) <= maxDistance
|
|
423
|
-
)) {
|
|
424
|
-
return { matched: true, via: "fuzzy" };
|
|
425
|
-
}
|
|
426
|
-
return { matched: false, via: "none" };
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
// src/registry/registry.ts
|
|
430
|
-
var registry = /* @__PURE__ */ new Map();
|
|
431
|
-
function defineActivityType(descriptor) {
|
|
432
|
-
return descriptor;
|
|
433
|
-
}
|
|
434
|
-
function registerActivityType(descriptor) {
|
|
435
|
-
if (descriptor.type === "item-group") {
|
|
436
|
-
throw new Error(
|
|
437
|
-
`"item-group" is reserved for the SDK's item-group container (see ItemGroup) and cannot be registered as an activity type.`
|
|
438
|
-
);
|
|
439
|
-
}
|
|
440
|
-
const existing = registry.get(descriptor.type);
|
|
441
|
-
if (existing !== void 0) {
|
|
442
|
-
if (existing === descriptor) {
|
|
443
|
-
return;
|
|
444
|
-
}
|
|
445
|
-
throw new Error(
|
|
446
|
-
`Activity type "${descriptor.type}" is already registered. Registering a different descriptor for an existing type is not allowed.`
|
|
447
|
-
);
|
|
448
|
-
}
|
|
449
|
-
registry.set(descriptor.type, descriptor);
|
|
450
|
-
}
|
|
451
|
-
function getActivityTypeDescriptor(type) {
|
|
452
|
-
return registry.get(type);
|
|
453
|
-
}
|
|
454
|
-
function registeredActivityTypes() {
|
|
455
|
-
return [...registry.keys()];
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
// src/scoring/strategies/all-or-nothing.ts
|
|
459
|
-
function allOrNothingStrategy(correctItems) {
|
|
460
|
-
return correctItems.every((isCorrect) => isCorrect) ? 1 : 0;
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
// src/scoring/strategies/partial.ts
|
|
464
|
-
function partialStrategy(correctSelected, incorrectSelected, totalCorrect, totalIncorrect) {
|
|
465
|
-
const reward = correctSelected / totalCorrect;
|
|
466
|
-
const penalty = totalIncorrect === 0 ? 0 : incorrectSelected / totalIncorrect;
|
|
467
|
-
return Math.max(0, reward - penalty);
|
|
468
|
-
}
|
|
469
|
-
function partialBlankStrategy(correctBlanks, totalBlanks) {
|
|
470
|
-
return correctBlanks / totalBlanks;
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
// src/scoring/activity-scorers/fill-in-the-blanks.ts
|
|
474
|
-
function policyFor(blank) {
|
|
475
|
-
return {
|
|
476
|
-
...blank.caseSensitive !== void 0 ? { caseSensitive: blank.caseSensitive } : {},
|
|
477
|
-
...blank.trimWhitespace !== void 0 ? { trim: blank.trimWhitespace } : {},
|
|
478
|
-
...blank.match
|
|
479
|
-
};
|
|
480
|
-
}
|
|
481
|
-
function scoreFillInTheBlanks(data, response) {
|
|
482
|
-
const details = [];
|
|
483
|
-
const perBlankCorrect = [];
|
|
484
|
-
for (const blank of data.blanks) {
|
|
485
|
-
const rawInput = response.answers[blank.id];
|
|
486
|
-
const input = typeof rawInput === "string" ? rawInput : "";
|
|
487
|
-
const matched = matchText(input, blank.acceptedAnswers, policyFor(blank)).matched;
|
|
488
|
-
perBlankCorrect.push(matched);
|
|
489
|
-
details.push({
|
|
490
|
-
itemId: blank.id,
|
|
491
|
-
correct: matched,
|
|
492
|
-
outcome: matched ? "correct" : "incorrect",
|
|
493
|
-
learnerResponse: [input],
|
|
494
|
-
correctResponse: [...blank.acceptedAnswers],
|
|
495
|
-
weight: 1
|
|
496
|
-
});
|
|
497
|
-
}
|
|
498
|
-
const correctBlanks = perBlankCorrect.filter(Boolean).length;
|
|
499
|
-
const scoreValue = data.scoringStrategy === "all-or-nothing" ? allOrNothingStrategy(perBlankCorrect) : partialBlankStrategy(correctBlanks, data.blanks.length);
|
|
500
|
-
return { score: scoreValue, maxScore: 1, feedback: null, details };
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
// src/scoring/activity-scorers/multiple-choice.ts
|
|
504
|
-
function scoreMultipleChoice(data, response) {
|
|
505
|
-
const optionById = new Map(data.options.map((option) => [option.id, option]));
|
|
506
|
-
const selected = new Set(response.selectedOptionIds);
|
|
507
|
-
const totalCorrect = data.options.filter((option) => option.isCorrect).length;
|
|
508
|
-
const totalIncorrect = data.options.length - totalCorrect;
|
|
509
|
-
let scoreValue;
|
|
510
|
-
if (data.scoringStrategy === "all-or-nothing") {
|
|
511
|
-
if (data.mode === "single") {
|
|
512
|
-
scoreValue = response.selectedOptionIds.length === 1 && optionById.get(response.selectedOptionIds[0])?.isCorrect === true ? 1 : 0;
|
|
513
|
-
} else {
|
|
514
|
-
const correctIds = data.options.filter((o) => o.isCorrect).map((o) => o.id);
|
|
515
|
-
const allCorrectSelected = correctIds.every((id) => selected.has(id));
|
|
516
|
-
scoreValue = selected.size === correctIds.length && allCorrectSelected ? 1 : 0;
|
|
517
|
-
}
|
|
518
|
-
} else {
|
|
519
|
-
let correctSelected = 0;
|
|
520
|
-
let incorrectSelected = 0;
|
|
521
|
-
for (const id of selected) {
|
|
522
|
-
const option = optionById.get(id);
|
|
523
|
-
if (option?.isCorrect) {
|
|
524
|
-
correctSelected += 1;
|
|
525
|
-
} else {
|
|
526
|
-
incorrectSelected += 1;
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
scoreValue = partialStrategy(correctSelected, incorrectSelected, totalCorrect, totalIncorrect);
|
|
530
|
-
}
|
|
531
|
-
const details = data.options.map((option) => {
|
|
532
|
-
const wasSelected = selected.has(option.id);
|
|
533
|
-
const outcome = wasSelected ? option.isCorrect ? "correct" : "incorrect" : option.isCorrect ? "incorrect-omission" : "correct-omission";
|
|
534
|
-
return {
|
|
535
|
-
itemId: option.id,
|
|
536
|
-
correct: wasSelected === option.isCorrect,
|
|
537
|
-
outcome,
|
|
538
|
-
learnerResponse: [wasSelected ? "selected" : "not-selected"],
|
|
539
|
-
correctResponse: [option.isCorrect ? "selected" : "not-selected"],
|
|
540
|
-
weight: 1
|
|
541
|
-
};
|
|
542
|
-
});
|
|
543
|
-
return { score: scoreValue, maxScore: 1, feedback: null, details };
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
// src/registry/builtins.ts
|
|
547
|
-
var MEDIA_FIELD_POLICY = {
|
|
548
|
-
type: "public",
|
|
549
|
-
url: "public",
|
|
550
|
-
alt: "public",
|
|
551
|
-
captionsUrl: "public",
|
|
552
|
-
playback: {
|
|
553
|
-
controls: "public",
|
|
554
|
-
maxPlays: "public",
|
|
555
|
-
seek: "public",
|
|
556
|
-
rate: "public",
|
|
557
|
-
nativeControlHints: "public"
|
|
558
|
-
}
|
|
559
|
-
};
|
|
560
|
-
var SHARED_PUBLIC_FIELDS = {
|
|
561
|
-
schemaVersion: "public",
|
|
562
|
-
type: "public",
|
|
563
|
-
id: "public",
|
|
564
|
-
// Assembly metadata, not content: it names the slot this item occupies in a
|
|
565
|
-
// paper. It has to survive redaction, or the exam client derives positional
|
|
566
|
-
// slot ids while the server's stored plan holds keyed ones, and the
|
|
567
|
-
// responses cannot be matched back to the attempt.
|
|
568
|
-
slotKey: "public",
|
|
569
|
-
title: "public",
|
|
570
|
-
media: MEDIA_FIELD_POLICY,
|
|
571
|
-
passThreshold: "public",
|
|
572
|
-
locale: "public",
|
|
573
|
-
learningObjectives: "public",
|
|
574
|
-
difficultyLevel: "public"
|
|
575
|
-
};
|
|
576
|
-
var FEEDBACK_FIELD_POLICY = {
|
|
577
|
-
correct: "answer-key",
|
|
578
|
-
incorrect: "answer-key"
|
|
579
|
-
};
|
|
580
|
-
var TEXT_MATCH_FIELD_POLICY = {
|
|
581
|
-
caseSensitive: "answer-key",
|
|
582
|
-
trim: "answer-key",
|
|
583
|
-
normalize: "answer-key",
|
|
584
|
-
foldDiacritics: "answer-key",
|
|
585
|
-
collapseInnerWhitespace: "answer-key",
|
|
586
|
-
ignorePunctuation: "answer-key",
|
|
587
|
-
levenshtein: "answer-key",
|
|
588
|
-
locale: "answer-key"
|
|
589
|
-
};
|
|
590
|
-
var MULTIPLE_CHOICE_FIELD_POLICY = {
|
|
591
|
-
...SHARED_PUBLIC_FIELDS,
|
|
592
|
-
question: "public",
|
|
593
|
-
questionHtml: "public",
|
|
594
|
-
mode: "public",
|
|
595
|
-
shuffle: "public",
|
|
596
|
-
scoringStrategy: "answer-key",
|
|
597
|
-
feedback: FEEDBACK_FIELD_POLICY,
|
|
598
|
-
options: {
|
|
599
|
-
id: "public",
|
|
600
|
-
text: "public",
|
|
601
|
-
isCorrect: "answer-key",
|
|
602
|
-
feedback: "answer-key"
|
|
603
|
-
}
|
|
604
|
-
};
|
|
605
|
-
var FILL_IN_THE_BLANKS_FIELD_POLICY = {
|
|
606
|
-
...SHARED_PUBLIC_FIELDS,
|
|
607
|
-
passage: "public",
|
|
608
|
-
passageHtml: "public",
|
|
609
|
-
scoringStrategy: "answer-key",
|
|
610
|
-
feedback: FEEDBACK_FIELD_POLICY,
|
|
611
|
-
blanks: {
|
|
612
|
-
id: "public",
|
|
613
|
-
hint: "public",
|
|
614
|
-
acceptedAnswers: "answer-key",
|
|
615
|
-
caseSensitive: "answer-key",
|
|
616
|
-
trimWhitespace: "answer-key",
|
|
617
|
-
match: TEXT_MATCH_FIELD_POLICY,
|
|
618
|
-
feedback: "answer-key"
|
|
619
|
-
}
|
|
620
|
-
};
|
|
621
|
-
var RUBRIC_FIELD_POLICY = {
|
|
622
|
-
label: "public",
|
|
623
|
-
// Applies to every element of the array.
|
|
624
|
-
criteria: {
|
|
625
|
-
name: "public",
|
|
626
|
-
description: "public",
|
|
627
|
-
weight: "public"
|
|
628
|
-
}
|
|
629
|
-
};
|
|
630
|
-
var WRITTEN_RESPONSE_FIELD_POLICY = {
|
|
631
|
-
...SHARED_PUBLIC_FIELDS,
|
|
632
|
-
prompt: "public",
|
|
633
|
-
promptHtml: "public",
|
|
634
|
-
minWords: "public",
|
|
635
|
-
maxWords: "public",
|
|
636
|
-
languageTarget: "public",
|
|
637
|
-
feedback: FEEDBACK_FIELD_POLICY,
|
|
638
|
-
// A rubric is a LEARNER affordance, not a grader secret: it tells the
|
|
639
|
-
// learner what they are being graded on, which is pedagogically the point
|
|
640
|
-
// of publishing one. (Classifying it author-only broke real deployments
|
|
641
|
-
// that render a rubric panel during the attempt.) A deployment that wants
|
|
642
|
-
// it hidden can tighten this per call via `redact(data, { policy })`.
|
|
643
|
-
// Classified field by field so that stays true of the rubric's DOCUMENTED
|
|
644
|
-
// fields only — see {@link RUBRIC_FIELD_POLICY}.
|
|
645
|
-
rubric: RUBRIC_FIELD_POLICY
|
|
646
|
-
};
|
|
647
|
-
var multipleChoiceType = defineActivityType({
|
|
648
|
-
type: "multiple-choice",
|
|
649
|
-
// zod4 optional outputs are `T | undefined`; the hand-written wire types use
|
|
650
|
-
// exact optionals. Structurally identical at runtime — cast is type-level only.
|
|
651
|
-
schema: MultipleChoiceDataSchema,
|
|
652
|
-
scoring: { kind: "sync", score: scoreMultipleChoice },
|
|
653
|
-
isAnswered: (response) => (response?.selectedOptionIds.length ?? 0) > 0,
|
|
654
|
-
fieldPolicy: MULTIPLE_CHOICE_FIELD_POLICY,
|
|
655
|
-
redactedSchema: RedactedMultipleChoiceDataSchema,
|
|
656
|
-
interop: {
|
|
657
|
-
xapiActivityTypeIri: "http://adlnet.gov/expapi/activities/cmi.interaction",
|
|
658
|
-
xapiInteractionType: "choice",
|
|
659
|
-
correctResponsesPattern: (data) => [
|
|
660
|
-
data.options.filter((option) => option.isCorrect).map((option) => option.id).join("[,]")
|
|
661
|
-
]
|
|
662
|
-
},
|
|
663
|
-
interactions: ["option-selected", "option-deselected", "submitted"]
|
|
664
|
-
});
|
|
665
|
-
var fillInTheBlanksType = defineActivityType({
|
|
666
|
-
type: "fill-in-the-blanks",
|
|
667
|
-
schema: FillInTheBlanksDataSchema,
|
|
668
|
-
scoring: { kind: "sync", score: scoreFillInTheBlanks },
|
|
669
|
-
isAnswered: (response) => Object.values(response?.answers ?? {}).some((answer) => answer.trim().length > 0),
|
|
670
|
-
fieldPolicy: FILL_IN_THE_BLANKS_FIELD_POLICY,
|
|
671
|
-
redactedSchema: RedactedFillInTheBlanksDataSchema,
|
|
672
|
-
interop: {
|
|
673
|
-
xapiActivityTypeIri: "http://adlnet.gov/expapi/activities/cmi.interaction",
|
|
674
|
-
xapiInteractionType: "fill-in",
|
|
675
|
-
// xAPI fill-in pattern: blank answers joined with "[,]". Only the first
|
|
676
|
-
// accepted answer per blank is emitted (full alternates would explode
|
|
677
|
-
// combinatorially); the complete key lives in the activity data.
|
|
678
|
-
correctResponsesPattern: (data) => [
|
|
679
|
-
data.blanks.map((blank) => blank.acceptedAnswers[0] ?? "").join("[,]")
|
|
680
|
-
]
|
|
681
|
-
},
|
|
682
|
-
interactions: ["blank-filled", "hint-requested", "submitted"]
|
|
683
|
-
});
|
|
684
|
-
var writtenResponseType = defineActivityType({
|
|
685
|
-
type: "written-response",
|
|
686
|
-
schema: WrittenResponseDataSchema,
|
|
687
|
-
scoring: {
|
|
688
|
-
kind: "deferred",
|
|
689
|
-
reason: "requires_async_grading",
|
|
690
|
-
partial: (data, response) => {
|
|
691
|
-
const wordCount = countWords(response?.text ?? "");
|
|
692
|
-
return {
|
|
693
|
-
withinWordBounds: response !== void 0 && wordCount >= data.minWords && wordCount <= data.maxWords,
|
|
694
|
-
wordCount
|
|
695
|
-
};
|
|
696
|
-
}
|
|
697
|
-
},
|
|
698
|
-
isAnswered: (response) => (response?.text.trim().length ?? 0) > 0,
|
|
699
|
-
fieldPolicy: WRITTEN_RESPONSE_FIELD_POLICY,
|
|
700
|
-
redactedSchema: RedactedWrittenResponseDataSchema,
|
|
701
|
-
interop: {
|
|
702
|
-
xapiActivityTypeIri: "http://adlnet.gov/expapi/activities/cmi.interaction",
|
|
703
|
-
xapiInteractionType: "long-fill-in",
|
|
704
|
-
correctResponsesPattern: () => []
|
|
705
|
-
},
|
|
706
|
-
interactions: ["text-changed", "submitted"]
|
|
707
|
-
});
|
|
708
|
-
registerActivityType(multipleChoiceType);
|
|
709
|
-
registerActivityType(fillInTheBlanksType);
|
|
710
|
-
registerActivityType(writtenResponseType);
|
|
711
|
-
|
|
712
|
-
export {
|
|
713
|
-
countWords,
|
|
714
|
-
ActivitySchemaError,
|
|
715
|
-
UnknownActivityTypeError,
|
|
716
|
-
RedactedScoringError,
|
|
717
|
-
DeferredScoringError,
|
|
718
|
-
FeedbackSchema,
|
|
719
|
-
MediaUrlSchema,
|
|
720
|
-
NativeControlHintSchema,
|
|
721
|
-
MediaPlaybackSchema,
|
|
722
|
-
MediaSchema,
|
|
723
|
-
RedactedMediaSchema,
|
|
724
|
-
TextMatchPolicySchema,
|
|
725
|
-
BlankConfigSchema,
|
|
726
|
-
FillInTheBlanksDataSchema,
|
|
727
|
-
MultipleChoiceOptionSchema,
|
|
728
|
-
MultipleChoiceDataSchema,
|
|
729
|
-
WrittenResponseRubricCriterionSchema,
|
|
730
|
-
WrittenResponseRubricSchema,
|
|
731
|
-
WrittenResponseDataSchema,
|
|
732
|
-
RedactedMultipleChoiceOptionSchema,
|
|
733
|
-
RedactedMultipleChoiceDataSchema,
|
|
734
|
-
RedactedBlankConfigSchema,
|
|
735
|
-
RedactedFillInTheBlanksDataSchema,
|
|
736
|
-
RedactedWrittenResponseDataSchema,
|
|
737
|
-
levenshteinDistance,
|
|
738
|
-
matchText,
|
|
739
|
-
defineActivityType,
|
|
740
|
-
registerActivityType,
|
|
741
|
-
getActivityTypeDescriptor,
|
|
742
|
-
registeredActivityTypes,
|
|
743
|
-
MEDIA_FIELD_POLICY,
|
|
744
|
-
multipleChoiceType,
|
|
745
|
-
fillInTheBlanksType,
|
|
746
|
-
writtenResponseType
|
|
747
|
-
};
|
|
748
|
-
//# sourceMappingURL=chunk-HCOWSGEZ.js.map
|