@intellectif/lk-core 0.22.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/README.md +5 -5
- package/dist/{activity-CYc7SD3d.d.cts → activity-DgpBJeBc.d.cts} +11 -15
- package/dist/{activity-CYc7SD3d.d.ts → activity-DgpBJeBc.d.ts} +11 -15
- package/dist/ai-check.cjs +8 -8
- package/dist/ai-check.d.cts +1 -1
- package/dist/ai-check.d.ts +1 -1
- package/dist/ai-check.js +3 -3
- package/dist/{chunk-2CV6NU32.cjs → chunk-2JT2WUQ2.cjs} +77 -39
- package/dist/chunk-2JT2WUQ2.cjs.map +1 -0
- package/dist/{chunk-D4TOPSHV.cjs → chunk-DSDXL3JO.cjs} +1216 -1105
- package/dist/chunk-DSDXL3JO.cjs.map +1 -0
- package/dist/{chunk-D3GP5MET.js → chunk-FBKTION6.js} +1093 -982
- package/dist/chunk-FBKTION6.js.map +1 -0
- package/dist/{chunk-Q7MV3IXJ.js → chunk-IARLJ5MW.js} +15 -16
- package/dist/chunk-IARLJ5MW.js.map +1 -0
- package/dist/{chunk-5UCR4UNR.js → chunk-RCZCP5OE.js} +12 -7
- package/dist/chunk-RCZCP5OE.js.map +1 -0
- package/dist/{chunk-X5DAXBJI.js → chunk-SJVRUPJT.js} +2 -2
- package/dist/{chunk-EOFWSGLU.cjs → chunk-TJJFKKH2.cjs} +4 -4
- package/dist/{chunk-EOFWSGLU.cjs.map → chunk-TJJFKKH2.cjs.map} +1 -1
- package/dist/{chunk-WQVZPIN7.cjs → chunk-VSH7ECQL.cjs} +44 -45
- package/dist/chunk-VSH7ECQL.cjs.map +1 -0
- package/dist/{chunk-OPMY2MNS.cjs → chunk-W5KB3DNT.cjs} +19 -14
- package/dist/chunk-W5KB3DNT.cjs.map +1 -0
- package/dist/{chunk-Z7QQZ53L.js → chunk-XS6U2ARZ.js} +70 -32
- package/dist/chunk-XS6U2ARZ.js.map +1 -0
- package/dist/index-BVNeSCWC.d.ts +548 -0
- package/dist/index-BlOLlwOi.d.cts +548 -0
- package/dist/index.cjs +129 -206
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +115 -15
- package/dist/index.d.ts +115 -15
- package/dist/index.js +26 -103
- package/dist/index.js.map +1 -1
- package/dist/schemas.cjs +3 -94
- package/dist/schemas.cjs.map +1 -1
- package/dist/schemas.d.cts +2 -3
- package/dist/schemas.d.ts +2 -3
- package/dist/schemas.js +6 -97
- package/dist/scoring.cjs +3 -3
- package/dist/scoring.d.cts +2 -2
- package/dist/scoring.d.ts +2 -2
- package/dist/scoring.js +2 -2
- package/dist/xapi.cjs +3 -3
- package/dist/xapi.d.cts +1 -1
- package/dist/xapi.d.ts +1 -1
- package/dist/xapi.js +2 -2
- package/package.json +4 -4
- package/vectors/README.md +32 -2
- package/vectors/scoring.json +1 -498
- package/vectors/validation.json +6742 -0
- package/vectors/validation.mjs +195 -0
- package/dist/chunk-2CV6NU32.cjs.map +0 -1
- package/dist/chunk-5UCR4UNR.js.map +0 -1
- package/dist/chunk-D3GP5MET.js.map +0 -1
- package/dist/chunk-D4TOPSHV.cjs.map +0 -1
- package/dist/chunk-OPMY2MNS.cjs.map +0 -1
- package/dist/chunk-Q7MV3IXJ.js.map +0 -1
- package/dist/chunk-WQVZPIN7.cjs.map +0 -1
- package/dist/chunk-Z7QQZ53L.js.map +0 -1
- package/dist/index-0Z3-nnAP.d.ts +0 -2303
- package/dist/index-CxxiNSSH.d.cts +0 -2303
- /package/dist/{chunk-X5DAXBJI.js.map → chunk-SJVRUPJT.js.map} +0 -0
|
@@ -0,0 +1,548 @@
|
|
|
1
|
+
import { a as ActivityData, d as ActivityMedia, aq as ValidationResult, f as ActivityType, b as ActivityDataMap, ab as MultipleChoiceOptionMedia } from './activity-DgpBJeBc.cjs';
|
|
2
|
+
|
|
3
|
+
/** What a stimulus primarily is. Drives validation and layout, never scoring. */
|
|
4
|
+
type StimulusKind = 'text' | 'audio' | 'video' | 'image' | 'mixed';
|
|
5
|
+
/**
|
|
6
|
+
* Shared material that several items refer to: a reading passage, a
|
|
7
|
+
* recording, a chart. Modelled as CONTENT — it lives inside the item group
|
|
8
|
+
* that carries it — so it works in a lesson quiz, a sequence and an exam
|
|
9
|
+
* section alike, and needs no blueprint to exist.
|
|
10
|
+
*
|
|
11
|
+
* Learner-visible by definition: the point of a stimulus is that the learner
|
|
12
|
+
* reads or hears it. The one exception is `transcript`, an author asset that
|
|
13
|
+
* `redactItemGroup` removes.
|
|
14
|
+
*/
|
|
15
|
+
interface Stimulus {
|
|
16
|
+
id: string;
|
|
17
|
+
kind: StimulusKind;
|
|
18
|
+
/** Optional heading (a passage title). */
|
|
19
|
+
title?: string;
|
|
20
|
+
/**
|
|
21
|
+
* Plain-text body: the passage itself, or short instructions above media.
|
|
22
|
+
* Required for `text` and `mixed`.
|
|
23
|
+
*/
|
|
24
|
+
body?: string;
|
|
25
|
+
/**
|
|
26
|
+
* Optional sanitised rich-HTML sidecar of `body`. Requires `body`, which is
|
|
27
|
+
* the accessible fallback rendered when no sanitiser is supplied.
|
|
28
|
+
*/
|
|
29
|
+
bodyHtml?: string;
|
|
30
|
+
/** The recording, video or image. Required for `audio`, `video`, `image` and `mixed`. */
|
|
31
|
+
media?: ActivityMedia;
|
|
32
|
+
/**
|
|
33
|
+
* Author-only transcript of `media` — for item generation and grading,
|
|
34
|
+
* never shown to the learner. Removed by `redactItemGroup`.
|
|
35
|
+
*/
|
|
36
|
+
transcript?: string;
|
|
37
|
+
/** BCP 47 language tag of the material, when it differs from the items'. */
|
|
38
|
+
locale?: string;
|
|
39
|
+
/** Source credit, shown to the learner after the material. */
|
|
40
|
+
attribution?: string;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* A quiz inside an interactive video: one moment of the video, and the
|
|
44
|
+
* questions shown there. The video pauses when playback reaches `at`, and the
|
|
45
|
+
* questions are shown one at a time, in `itemIds` order.
|
|
46
|
+
*/
|
|
47
|
+
interface TimelineCue {
|
|
48
|
+
/** Unique within the timeline. Names the quiz in events, the contents panel and drafts. */
|
|
49
|
+
id: string;
|
|
50
|
+
/**
|
|
51
|
+
* Seconds from the start of the video: finite and 0 or more. The SDK never
|
|
52
|
+
* reads the video, so it cannot check this against the duration: a quiz at
|
|
53
|
+
* or past the end opens when the video ends.
|
|
54
|
+
*/
|
|
55
|
+
at: number;
|
|
56
|
+
/**
|
|
57
|
+
* `id`s of items in this group, in the order the quiz shows them. Non-empty.
|
|
58
|
+
* Every item of the group belongs to exactly one quiz.
|
|
59
|
+
*/
|
|
60
|
+
itemIds: string[];
|
|
61
|
+
/** Optional heading shown above the questions. At most 120 characters. */
|
|
62
|
+
title?: string;
|
|
63
|
+
/**
|
|
64
|
+
* Every question must be answered (practice) or submitted (exam) before
|
|
65
|
+
* playback may pass `at`. Default false.
|
|
66
|
+
*/
|
|
67
|
+
required?: boolean;
|
|
68
|
+
}
|
|
69
|
+
/** A titled section of the video. */
|
|
70
|
+
interface TimelineChapter {
|
|
71
|
+
/** Seconds from the start: finite, 0 or more, strictly increasing across the list. */
|
|
72
|
+
at: number;
|
|
73
|
+
/** Non-empty after trimming; at most 120 characters. */
|
|
74
|
+
title: string;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* The arrangement that turns an item group with a video stimulus into an
|
|
78
|
+
* interactive video: when each quiz opens, the video's chapters, and how far
|
|
79
|
+
* ahead a learner may seek.
|
|
80
|
+
*
|
|
81
|
+
* It lives on the container rather than inside `stimulus` because it describes
|
|
82
|
+
* how THESE questions are arranged over the material, not the material itself.
|
|
83
|
+
*/
|
|
84
|
+
interface MediaTimeline {
|
|
85
|
+
cues: TimelineCue[];
|
|
86
|
+
chapters?: TimelineChapter[];
|
|
87
|
+
/**
|
|
88
|
+
* `free` (default): seek anywhere. `no-skip-ahead`: rewind freely, never past
|
|
89
|
+
* the furthest point reached. Either way, playback never passes a `required`
|
|
90
|
+
* quiz that is not finished.
|
|
91
|
+
*/
|
|
92
|
+
navigation?: 'free' | 'no-skip-ahead';
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Where a learner stands in an interactive video, stored by the host so the
|
|
96
|
+
* video can resume. Kept apart from `AttemptState`, which holds answers by slot
|
|
97
|
+
* and drops fields it does not know. Read it back through
|
|
98
|
+
* `readMediaProgress`, never directly.
|
|
99
|
+
*/
|
|
100
|
+
interface MediaProgress {
|
|
101
|
+
progressVersion: '1.0';
|
|
102
|
+
/** Seconds: where playback last stood. */
|
|
103
|
+
at: number;
|
|
104
|
+
/**
|
|
105
|
+
* Seconds: the furthest point reached. Decides how far `no-skip-ahead` lets
|
|
106
|
+
* the learner seek, and how much of the transcript it shows.
|
|
107
|
+
*/
|
|
108
|
+
furthest: number;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* One stimulus serving several items. Two rules, both taken from observed
|
|
112
|
+
* failures: a group is SHUFFLE-ATOMIC (shuffling a sequence moves the group
|
|
113
|
+
* as one block — interleaving two passages' questions is the defect), and its
|
|
114
|
+
* stimulus is presented persistently alongside every item, never behind a
|
|
115
|
+
* toggle the learner must reopen per question.
|
|
116
|
+
*
|
|
117
|
+
* Generic over the item type so one shape describes a group of full activity
|
|
118
|
+
* data (authoring, server) and a group of `redact()` projections (client)
|
|
119
|
+
* without a cast at every boundary.
|
|
120
|
+
*/
|
|
121
|
+
interface ItemGroup<TItem = ActivityData> {
|
|
122
|
+
schemaVersion: '1.0';
|
|
123
|
+
type: 'item-group';
|
|
124
|
+
id: string;
|
|
125
|
+
title?: string;
|
|
126
|
+
/**
|
|
127
|
+
* Stable identity for this entry's slots, independent of where it sits in
|
|
128
|
+
* the array. See {@link SequenceSlot.slotId}: without one, inserting a
|
|
129
|
+
* question above this group re-maps every slot id beneath it, and stored
|
|
130
|
+
* grades quietly start naming different questions.
|
|
131
|
+
*/
|
|
132
|
+
slotKey?: string;
|
|
133
|
+
stimulus: Stimulus;
|
|
134
|
+
/**
|
|
135
|
+
* The items, in authored order. Non-empty; ids unique within the group; no
|
|
136
|
+
* nested groups.
|
|
137
|
+
*
|
|
138
|
+
* An item may declare its own `slotKey`, for the same reason an entry can:
|
|
139
|
+
* it pins the item's identity within the group (`"reading.q1"` rather than
|
|
140
|
+
* `"reading.0"`), so inserting a question into a published group does not
|
|
141
|
+
* re-map the ones after it.
|
|
142
|
+
*/
|
|
143
|
+
items: (TItem & {
|
|
144
|
+
slotKey?: string;
|
|
145
|
+
})[];
|
|
146
|
+
/**
|
|
147
|
+
* `none` (default) keeps authored order; `within-group` shuffles the items
|
|
148
|
+
* among themselves under the sequence seed. Either way the group stays one
|
|
149
|
+
* contiguous block.
|
|
150
|
+
*/
|
|
151
|
+
shuffle?: 'none' | 'within-group';
|
|
152
|
+
/**
|
|
153
|
+
* Makes the group an interactive video: the stimulus must be a video, every
|
|
154
|
+
* item must be one of `INTERACTIVE_VIDEO_ITEM_TYPES`, and every item belongs
|
|
155
|
+
* to exactly one quiz. Items are then presented in quiz order, never
|
|
156
|
+
* shuffled. An older lk-core that does not know this field presents the same
|
|
157
|
+
* items as an ordinary video testlet, with the same slots and grades.
|
|
158
|
+
*/
|
|
159
|
+
timeline?: MediaTimeline;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* What a sequence is made of: loose activities and item groups, in authored
|
|
163
|
+
* order.
|
|
164
|
+
*
|
|
165
|
+
* A plain activity may carry `slotKey` here even though no activity SCHEMA
|
|
166
|
+
* declares one, because a slot key describes an item's PLACE in a paper, not
|
|
167
|
+
* its content — the same question keeps its own id in every paper it appears
|
|
168
|
+
* in. Putting it on the entry rather than on the activity is what lets it be
|
|
169
|
+
* authored without every activity type having to know about assessment
|
|
170
|
+
* assembly. See {@link SequenceSlot.slotId}.
|
|
171
|
+
*/
|
|
172
|
+
type SequenceEntry<TItem = ActivityData> = (TItem & {
|
|
173
|
+
slotKey?: string;
|
|
174
|
+
}) | ItemGroup<TItem>;
|
|
175
|
+
/** The group a presented slot belongs to. */
|
|
176
|
+
interface SequenceSlotGroup {
|
|
177
|
+
id: string;
|
|
178
|
+
title?: string;
|
|
179
|
+
stimulus: Stimulus;
|
|
180
|
+
/** 0-based position of this slot within the group's PRESENTED order. */
|
|
181
|
+
position: number;
|
|
182
|
+
/** Number of slots in the group. */
|
|
183
|
+
size: number;
|
|
184
|
+
/** The group's timeline, when the group is an interactive video. */
|
|
185
|
+
timeline?: MediaTimeline;
|
|
186
|
+
/** The quiz this slot's item sits in, when the group is an interactive video. */
|
|
187
|
+
cue?: TimelineCue;
|
|
188
|
+
}
|
|
189
|
+
/** One presented position in a flattened sequence. */
|
|
190
|
+
interface SequenceSlot<TItem = ActivityData> {
|
|
191
|
+
/**
|
|
192
|
+
* Identity of the slot within the sequence definition — unique, and stable
|
|
193
|
+
* under shuffling. Feed it to `composeAssessmentScore` as `slotId`.
|
|
194
|
+
*
|
|
195
|
+
* By default it is derived from the AUTHORED position (`"2"` for the third
|
|
196
|
+
* top-level entry; `"2.1"` for the second item of that entry when it is a
|
|
197
|
+
* group), so the same activity can appear in two entries and still be two
|
|
198
|
+
* slots.
|
|
199
|
+
*
|
|
200
|
+
* **A positional id is only valid against one version of the entries
|
|
201
|
+
* array.** Insert a question at the top of a published paper and every id
|
|
202
|
+
* below it shifts: rows stored as `"3"` now name what used to be entry 2,
|
|
203
|
+
* and a re-grade or a review render pairs each response with the wrong
|
|
204
|
+
* question — silently, because the ids still look valid. For any content
|
|
205
|
+
* whose slot ids you persist, give the entries an explicit `slotKey`; it is
|
|
206
|
+
* used verbatim here and survives insertion, deletion and re-ordering.
|
|
207
|
+
*/
|
|
208
|
+
slotId: string;
|
|
209
|
+
/** 0-based PRESENTED position, after shuffling. */
|
|
210
|
+
index: number;
|
|
211
|
+
activity: TItem;
|
|
212
|
+
/** Present when the slot comes from an item group. */
|
|
213
|
+
group?: SequenceSlotGroup;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* The learner-safe SHAPE of each built-in type.
|
|
218
|
+
*
|
|
219
|
+
* `redact()` returns `RedactedActivityData`, which proves a payload is
|
|
220
|
+
* learner-safe but is index-signature typed — it deliberately says nothing
|
|
221
|
+
* about what the payload still CONTAINS. That is right for the assertion and
|
|
222
|
+
* useless for anything that has to render or transport the result, so every
|
|
223
|
+
* integrator would end up re-declaring these interfaces by hand, and they would
|
|
224
|
+
* drift the moment a schema changed.
|
|
225
|
+
*
|
|
226
|
+
* Use them for the payload a server sends an exam client, and for the props of
|
|
227
|
+
* a renderer that must never see an answer key.
|
|
228
|
+
*
|
|
229
|
+
* Each is pinned to the strict schema `assertRedacted` checks against by a
|
|
230
|
+
* compile-time equality test, so the type and the validator cannot disagree.
|
|
231
|
+
* They are written out rather than inferred from those schemas so that the
|
|
232
|
+
* published types name no validation library.
|
|
233
|
+
*/
|
|
234
|
+
/** A caption or subtitle track, unchanged by redaction. */
|
|
235
|
+
interface Track {
|
|
236
|
+
kind: 'captions' | 'subtitles';
|
|
237
|
+
src: string;
|
|
238
|
+
srclang: string;
|
|
239
|
+
label: string;
|
|
240
|
+
default?: boolean | undefined;
|
|
241
|
+
}
|
|
242
|
+
/** A recording's playback policy, unchanged by redaction. */
|
|
243
|
+
interface Playback {
|
|
244
|
+
controls?: 'native' | 'minimal' | undefined;
|
|
245
|
+
maxPlays?: number | undefined;
|
|
246
|
+
seek?: 'allow' | 'none' | undefined;
|
|
247
|
+
rate?: 'allow' | 'fixed' | undefined;
|
|
248
|
+
nativeControlHints?: ('hide-download' | 'hide-rate')[] | undefined;
|
|
249
|
+
}
|
|
250
|
+
/** An item's or a stimulus's media, unchanged by redaction. */
|
|
251
|
+
interface Media {
|
|
252
|
+
type: 'image' | 'audio' | 'video' | 'embed';
|
|
253
|
+
url: string;
|
|
254
|
+
alt?: string | undefined;
|
|
255
|
+
captionsUrl?: string | undefined;
|
|
256
|
+
tracks?: Track[] | undefined;
|
|
257
|
+
poster?: string | undefined;
|
|
258
|
+
playback?: Playback | undefined;
|
|
259
|
+
}
|
|
260
|
+
/** The author's switches for AI help, unchanged by redaction. */
|
|
261
|
+
interface AiSwitches {
|
|
262
|
+
explanations?: boolean | undefined;
|
|
263
|
+
hints?: boolean | undefined;
|
|
264
|
+
}
|
|
265
|
+
/** What every redacted item carries, whatever its type. */
|
|
266
|
+
interface RedactedItem {
|
|
267
|
+
redacted: true;
|
|
268
|
+
schemaVersion: '1.0';
|
|
269
|
+
id: string;
|
|
270
|
+
title: string;
|
|
271
|
+
slotKey?: string | undefined;
|
|
272
|
+
media?: Media | undefined;
|
|
273
|
+
passThreshold?: number | undefined;
|
|
274
|
+
learningObjectives?: string[] | undefined;
|
|
275
|
+
difficultyLevel?: 1 | 2 | 3 | 4 | 5 | undefined;
|
|
276
|
+
ai?: AiSwitches | undefined;
|
|
277
|
+
}
|
|
278
|
+
/** An option's picture or recording, unchanged by redaction — it is what the learner picks. */
|
|
279
|
+
interface RedactedMultipleChoiceOptionMedia {
|
|
280
|
+
type: 'image' | 'audio';
|
|
281
|
+
url: string;
|
|
282
|
+
alt?: string | undefined;
|
|
283
|
+
captionsUrl?: string | undefined;
|
|
284
|
+
}
|
|
285
|
+
/** An option with `isCorrect` and its feedback removed. */
|
|
286
|
+
interface RedactedMultipleChoiceOption {
|
|
287
|
+
id: string;
|
|
288
|
+
text: string;
|
|
289
|
+
media?: RedactedMultipleChoiceOptionMedia | undefined;
|
|
290
|
+
}
|
|
291
|
+
/** A Multiple Choice item with the answer key, feedback and strategy removed. */
|
|
292
|
+
interface RedactedMultipleChoiceData extends RedactedItem {
|
|
293
|
+
type: 'multiple-choice';
|
|
294
|
+
question: string;
|
|
295
|
+
questionHtml?: string | undefined;
|
|
296
|
+
mode: 'single' | 'multi';
|
|
297
|
+
options: RedactedMultipleChoiceOption[];
|
|
298
|
+
shuffle?: boolean | undefined;
|
|
299
|
+
locale?: string | undefined;
|
|
300
|
+
}
|
|
301
|
+
/** A blank with its accepted answers and matching rules removed; the hint survives. */
|
|
302
|
+
interface RedactedBlankConfig {
|
|
303
|
+
id: string;
|
|
304
|
+
hint?: string | undefined;
|
|
305
|
+
}
|
|
306
|
+
/** A Fill-in-the-Blanks item with every accepted answer removed. */
|
|
307
|
+
interface RedactedFillInTheBlanksData extends RedactedItem {
|
|
308
|
+
type: 'fill-in-the-blanks';
|
|
309
|
+
passage: string;
|
|
310
|
+
passageHtml?: string | undefined;
|
|
311
|
+
blanks: RedactedBlankConfig[];
|
|
312
|
+
locale?: string | undefined;
|
|
313
|
+
}
|
|
314
|
+
/** A Written Response item; the rubric survives, because it tells the learner what is assessed. */
|
|
315
|
+
interface RedactedWrittenResponseData extends RedactedItem {
|
|
316
|
+
type: 'written-response';
|
|
317
|
+
prompt: string;
|
|
318
|
+
promptHtml?: string | undefined;
|
|
319
|
+
minWords: number;
|
|
320
|
+
maxWords: number;
|
|
321
|
+
rubric?: {
|
|
322
|
+
criteria: {
|
|
323
|
+
name: string;
|
|
324
|
+
weight: number;
|
|
325
|
+
description?: string | undefined;
|
|
326
|
+
}[];
|
|
327
|
+
label?: string | undefined;
|
|
328
|
+
} | undefined;
|
|
329
|
+
languageTarget?: string | undefined;
|
|
330
|
+
locale?: string | undefined;
|
|
331
|
+
}
|
|
332
|
+
/** A selectable choice, unchanged by redaction. */
|
|
333
|
+
interface RedactedGapSelectChoice {
|
|
334
|
+
id: string;
|
|
335
|
+
text: string;
|
|
336
|
+
}
|
|
337
|
+
/** A word bank, unchanged by redaction — the learner picks from it. */
|
|
338
|
+
interface RedactedGapSelectBank {
|
|
339
|
+
id: string;
|
|
340
|
+
choices: RedactedGapSelectChoice[];
|
|
341
|
+
}
|
|
342
|
+
/** A gap with its `correctChoiceId` removed; every choice it offers survives. */
|
|
343
|
+
interface RedactedGapSelectGap {
|
|
344
|
+
id: string;
|
|
345
|
+
choices?: RedactedGapSelectChoice[] | undefined;
|
|
346
|
+
bankId?: string | undefined;
|
|
347
|
+
}
|
|
348
|
+
/** A Gap Select item the learner can still answer: choices intact, answer key gone. */
|
|
349
|
+
interface RedactedGapSelectData extends RedactedItem {
|
|
350
|
+
type: 'gap-select';
|
|
351
|
+
passage: string;
|
|
352
|
+
passageHtml?: string | undefined;
|
|
353
|
+
gaps: RedactedGapSelectGap[];
|
|
354
|
+
banks?: RedactedGapSelectBank[] | undefined;
|
|
355
|
+
presentation?: 'dropdown' | undefined;
|
|
356
|
+
shuffleChoices?: boolean | undefined;
|
|
357
|
+
locale?: string | undefined;
|
|
358
|
+
}
|
|
359
|
+
/** The slower recording of a dictation, unchanged by redaction. */
|
|
360
|
+
interface RedactedDictationSlowMedia {
|
|
361
|
+
type: 'audio';
|
|
362
|
+
url: string;
|
|
363
|
+
alt?: string | undefined;
|
|
364
|
+
}
|
|
365
|
+
/** A Dictation item with its transcript, accepted alternatives and tolerances removed. */
|
|
366
|
+
interface RedactedDictationData extends RedactedItem {
|
|
367
|
+
type: 'dictation';
|
|
368
|
+
slowMedia?: RedactedDictationSlowMedia | undefined;
|
|
369
|
+
hints?: {
|
|
370
|
+
mode: 'progressive-words';
|
|
371
|
+
} | undefined;
|
|
372
|
+
locale?: string | undefined;
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* A Read Aloud item, which keeps everything but the authored feedback: the text
|
|
376
|
+
* to read is what the learner is asked to read. The slow recording needs no
|
|
377
|
+
* redacted counterpart of its own — the content schema for it is already strict.
|
|
378
|
+
*/
|
|
379
|
+
interface RedactedReadAloudData extends RedactedItem {
|
|
380
|
+
type: 'read-aloud';
|
|
381
|
+
instructions?: string | undefined;
|
|
382
|
+
referenceText: string;
|
|
383
|
+
locale: string;
|
|
384
|
+
slowMedia?: RedactedDictationSlowMedia | undefined;
|
|
385
|
+
recording: {
|
|
386
|
+
maxSeconds: number;
|
|
387
|
+
minSeconds?: number | undefined;
|
|
388
|
+
maxTakes?: number | undefined;
|
|
389
|
+
};
|
|
390
|
+
scoring: {
|
|
391
|
+
dimensions: {
|
|
392
|
+
name: 'accuracy' | 'fluency' | 'completeness' | 'prosody';
|
|
393
|
+
weight: number;
|
|
394
|
+
}[];
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
/** A stimulus as a learner receives it: everything but the author-only `transcript`. */
|
|
398
|
+
interface RedactedStimulus {
|
|
399
|
+
id: string;
|
|
400
|
+
kind: 'text' | 'image' | 'audio' | 'video' | 'mixed';
|
|
401
|
+
title?: string | undefined;
|
|
402
|
+
body?: string | undefined;
|
|
403
|
+
bodyHtml?: string | undefined;
|
|
404
|
+
media?: Media | undefined;
|
|
405
|
+
locale?: string | undefined;
|
|
406
|
+
attribution?: string | undefined;
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Discriminated union of every built-in redacted activity. Narrow it on
|
|
410
|
+
* `type`, exactly as you would `ActivityData`:
|
|
411
|
+
*
|
|
412
|
+
* ```ts
|
|
413
|
+
* function render(item: RedactedActivity) {
|
|
414
|
+
* if (item.type === 'multiple-choice') {
|
|
415
|
+
* return item.options.map((option) => option.text); // no `isCorrect` to leak
|
|
416
|
+
* }
|
|
417
|
+
* }
|
|
418
|
+
* ```
|
|
419
|
+
*/
|
|
420
|
+
type RedactedActivity = RedactedMultipleChoiceData | RedactedFillInTheBlanksData | RedactedWrittenResponseData | RedactedGapSelectData | RedactedDictationData | RedactedReadAloudData;
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* Validates an item group in full: the container and stimulus against
|
|
424
|
+
* {@link ItemGroupSchema}, then every item against the schema registered for
|
|
425
|
+
* its `type`. Errors from items are reported at `items.<index>.…`.
|
|
426
|
+
*
|
|
427
|
+
* Unlike `validateActivity`, an item whose type is not registered is REPORTED
|
|
428
|
+
* (code `unknown_activity_type`) rather than thrown: a group is validated as
|
|
429
|
+
* a whole, and an author fixing a six-item group wants every problem listed,
|
|
430
|
+
* not the first one that happened to throw.
|
|
431
|
+
*/
|
|
432
|
+
declare function validateItemGroup(data: unknown): ValidationResult<ItemGroup>;
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* JSON Schema (Draft 7) for a `Stimulus`. Structural contract only — the
|
|
436
|
+
* kind/media/body consistency guards are Zod-only.
|
|
437
|
+
*/
|
|
438
|
+
declare const stimulusJsonSchema: Record<string, unknown>;
|
|
439
|
+
/**
|
|
440
|
+
* JSON Schema (Draft 7) for an `ItemGroup` CONTAINER. Items appear as objects
|
|
441
|
+
* with `type` and `id` only; each item's own contract is `jsonSchemaFor(type)`.
|
|
442
|
+
* For an AI generation pipeline, ask for the group and each item separately
|
|
443
|
+
* rather than a single nested schema — that keeps the per-type schema the
|
|
444
|
+
* registry's, not a copy.
|
|
445
|
+
*/
|
|
446
|
+
declare const itemGroupJsonSchema: Record<string, unknown>;
|
|
447
|
+
/**
|
|
448
|
+
* JSON Schema (Draft 7) representation of the Multiple Choice activity data
|
|
449
|
+
* contract, generated natively by Zod 4. Draft 7 is mandated by Requirement
|
|
450
|
+
* 2.2 for the widest AI-prompt / OpenAPI tooling compatibility. The semantic
|
|
451
|
+
* `.refine()` guards are not representable in JSON Schema and are
|
|
452
|
+
* intentionally omitted — the export captures the *structural* contract only.
|
|
453
|
+
* Since v0.3 the source schemas are loose, so these no longer emit
|
|
454
|
+
* `additionalProperties: false` — the JSON Schema and `validateActivity` now
|
|
455
|
+
* agree on unknown-key handling.
|
|
456
|
+
*/
|
|
457
|
+
/**
|
|
458
|
+
* JSON Schema (Draft 7) representation of the Gap Select activity data
|
|
459
|
+
* contract. Structural contract only: the six semantic guards — the
|
|
460
|
+
* passage/gap bijection, id uniqueness, the one-choice-source rule, and an
|
|
461
|
+
* answer key that names a choice the gap offers — are Zod-only, as they are
|
|
462
|
+
* for the other types.
|
|
463
|
+
*/
|
|
464
|
+
declare const gapSelectJsonSchema: Record<string, unknown>;
|
|
465
|
+
/**
|
|
466
|
+
* JSON Schema (Draft 7) representation of the Dictation activity data
|
|
467
|
+
* contract. Structural contract, plus the raw length caps JSON Schema can
|
|
468
|
+
* state in code points as zod counts them — `maxLength` 2000 on a transcript
|
|
469
|
+
* and an accepted transcript, 200 on a rule's `from` and `to`. The twelve
|
|
470
|
+
* semantic guards — a transcript that survives normalisation, the caps after
|
|
471
|
+
* equivalences are applied, distinct candidates, audio only, the
|
|
472
|
+
* slow-recording rules, no captions, no transcript in the title — are
|
|
473
|
+
* Zod-only, as they are for the other types.
|
|
474
|
+
*/
|
|
475
|
+
declare const dictationJsonSchema: Record<string, unknown>;
|
|
476
|
+
/**
|
|
477
|
+
* JSON Schema (Draft 7) representation of the Read Aloud activity data
|
|
478
|
+
* contract. Structural contract, plus the raw cap JSON Schema can state in code
|
|
479
|
+
* points as zod counts them — `maxLength` 2000 on the reference text — and the
|
|
480
|
+
* canonical locale pattern. The ten semantic guards — the cap after
|
|
481
|
+
* normalisation, a text that survives it, the spaced-script rule, the
|
|
482
|
+
* model-recording rules, unique dimensions, a weight above 0, and the take
|
|
483
|
+
* bounds — are Zod-only, as they are for the other types.
|
|
484
|
+
*/
|
|
485
|
+
declare const readAloudJsonSchema: Record<string, unknown>;
|
|
486
|
+
declare const multipleChoiceJsonSchema: Record<string, unknown>;
|
|
487
|
+
/**
|
|
488
|
+
* JSON Schema (Draft 7) representation of the Fill-in-the-Blanks activity data
|
|
489
|
+
* contract, generated natively by Zod 4. Structural contract only (semantic
|
|
490
|
+
* `.refine()` guards are Zod-only and not representable in JSON Schema).
|
|
491
|
+
*/
|
|
492
|
+
declare const fillInTheBlanksJsonSchema: Record<string, unknown>;
|
|
493
|
+
/**
|
|
494
|
+
* JSON Schema (Draft 7) representation of the Written Response activity data
|
|
495
|
+
* contract. Structural contract only.
|
|
496
|
+
*/
|
|
497
|
+
declare const writtenResponseJsonSchema: Record<string, unknown>;
|
|
498
|
+
/**
|
|
499
|
+
* Derives the JSON Schema (Draft 7) for any REGISTERED activity type — the
|
|
500
|
+
* live, registry-backed replacement for the static per-type exports above,
|
|
501
|
+
* and the building block for AI generation pipelines (R6.1): pass the result
|
|
502
|
+
* as a structured-output schema so a model can only emit valid items.
|
|
503
|
+
*
|
|
504
|
+
* For a type you registered, it is the descriptor's `jsonSchema` when you gave
|
|
505
|
+
* one, or else what the schema produces through the Standard JSON Schema
|
|
506
|
+
* converter (`'~standard'.jsonSchema`) — a zod 4.6 schema has one. A fresh copy
|
|
507
|
+
* every call.
|
|
508
|
+
*
|
|
509
|
+
* @throws UnknownActivityTypeError when `type` has no registered descriptor.
|
|
510
|
+
* @throws Error when a registered type has neither a `jsonSchema` nor a schema
|
|
511
|
+
* that can produce one.
|
|
512
|
+
*/
|
|
513
|
+
declare function jsonSchemaFor(type: string): Record<string, unknown>;
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* Checks a piece of activity media — an item's `media`, a stimulus's, a
|
|
517
|
+
* dictation's `slowMedia` — on its own, the way `validateActivity` checks it
|
|
518
|
+
* inside an item: its kind, an address in the allowed schemes, a playback
|
|
519
|
+
* policy only on audio, captions and tracks only where they can play. For an
|
|
520
|
+
* editor's media picker, which holds the media before the item is whole.
|
|
521
|
+
*
|
|
522
|
+
* @returns `{ success: true, data }`, or `{ success: false, errors }` with the
|
|
523
|
+
* same codes, paths and messages `validateActivity` reports.
|
|
524
|
+
*/
|
|
525
|
+
declare function validateMedia(value: unknown): ValidationResult<ActivityMedia>;
|
|
526
|
+
/**
|
|
527
|
+
* Checks a multiple-choice option's `media` on its own: a picture or a
|
|
528
|
+
* recording, at an address in the allowed schemes, with no playback policy.
|
|
529
|
+
*
|
|
530
|
+
* @returns `{ success: true, data }`, or `{ success: false, errors }` with the
|
|
531
|
+
* same codes, paths and messages `validateActivity` reports.
|
|
532
|
+
*/
|
|
533
|
+
declare function validateOptionMedia(value: unknown): ValidationResult<MultipleChoiceOptionMedia>;
|
|
534
|
+
/**
|
|
535
|
+
* Validates raw activity data against the schema registered for the given
|
|
536
|
+
* activity type (built-in or consumer-registered via `registerActivityType`).
|
|
537
|
+
*
|
|
538
|
+
* Unknown keys are PRESERVED, not stripped: every built-in schema is loose,
|
|
539
|
+
* so consumer sidecar fields and forward-version fields survive validation
|
|
540
|
+
* verbatim in the returned `data`.
|
|
541
|
+
*
|
|
542
|
+
* @returns `{ success: true, data }` with the typed, validated data, or
|
|
543
|
+
* `{ success: false, errors }` with one entry per failed constraint.
|
|
544
|
+
* @throws UnknownActivityTypeError when `type` has no registered descriptor.
|
|
545
|
+
*/
|
|
546
|
+
declare function validateActivity<T extends ActivityType>(type: T, data: unknown): ValidationResult<ActivityDataMap[T]>;
|
|
547
|
+
|
|
548
|
+
export { readAloudJsonSchema as A, stimulusJsonSchema as B, validateActivity as C, validateItemGroup as D, validateMedia as E, validateOptionMedia as F, writtenResponseJsonSchema as G, type ItemGroup as I, type MediaProgress as M, type RedactedActivity as R, type SequenceEntry as S, type TimelineChapter as T, type MediaTimeline as a, type RedactedBlankConfig as b, type RedactedDictationData as c, type RedactedDictationSlowMedia as d, type RedactedFillInTheBlanksData as e, type RedactedGapSelectBank as f, type RedactedGapSelectChoice as g, type RedactedGapSelectData as h, type RedactedGapSelectGap as i, type RedactedMultipleChoiceData as j, type RedactedMultipleChoiceOption as k, type RedactedMultipleChoiceOptionMedia as l, type RedactedReadAloudData as m, type RedactedStimulus as n, type RedactedWrittenResponseData as o, type SequenceSlot as p, type SequenceSlotGroup as q, type Stimulus as r, type StimulusKind as s, type TimelineCue as t, dictationJsonSchema as u, fillInTheBlanksJsonSchema as v, gapSelectJsonSchema as w, itemGroupJsonSchema as x, jsonSchemaFor as y, multipleChoiceJsonSchema as z };
|