@intellectif/lk-core 0.2.1 → 0.3.1
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/dist/activity-zdcAMtFB.d.cts +626 -0
- package/dist/activity-zdcAMtFB.d.ts +626 -0
- package/dist/chunk-3YAVDV5F.js +47 -0
- package/dist/chunk-3YAVDV5F.js.map +1 -0
- package/dist/chunk-55O4M45K.js +57 -0
- package/dist/chunk-55O4M45K.js.map +1 -0
- package/dist/chunk-5GJJHGY5.js +576 -0
- package/dist/chunk-5GJJHGY5.js.map +1 -0
- package/dist/{chunk-ZHOX6TO4.js → chunk-HS7BYCGE.js} +65 -7
- package/dist/chunk-HS7BYCGE.js.map +1 -0
- package/dist/chunk-PIMX4B4D.cjs +47 -0
- package/dist/chunk-PIMX4B4D.cjs.map +1 -0
- package/dist/chunk-QOAORTA4.cjs +576 -0
- package/dist/chunk-QOAORTA4.cjs.map +1 -0
- package/dist/chunk-QSVBYTNM.js +94 -0
- package/dist/chunk-QSVBYTNM.js.map +1 -0
- package/dist/{chunk-PJFDAP54.cjs → chunk-RUGIOQZY.cjs} +66 -8
- package/dist/chunk-RUGIOQZY.cjs.map +1 -0
- package/dist/chunk-THSZMZND.cjs +94 -0
- package/dist/chunk-THSZMZND.cjs.map +1 -0
- package/dist/chunk-YNTYWHZI.cjs +57 -0
- package/dist/chunk-YNTYWHZI.cjs.map +1 -0
- package/dist/index.cjs +185 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +253 -7
- package/dist/index.d.ts +253 -7
- package/dist/index.js +188 -9
- package/dist/index.js.map +1 -1
- package/dist/schemas.cjs +29 -2
- package/dist/schemas.cjs.map +1 -1
- package/dist/schemas.d.cts +310 -29
- package/dist/schemas.d.ts +310 -29
- package/dist/schemas.js +32 -5
- package/dist/scoring.cjs +13 -3
- package/dist/scoring.cjs.map +1 -1
- package/dist/scoring.d.cts +29 -5
- package/dist/scoring.d.ts +29 -5
- package/dist/scoring.js +12 -2
- package/dist/xapi.cjs +3 -3
- package/dist/xapi.d.cts +40 -2
- package/dist/xapi.d.ts +40 -2
- package/dist/xapi.js +2 -2
- package/package.json +7 -2
- package/dist/activity-D5tqgx8A.d.cts +0 -330
- package/dist/activity-D5tqgx8A.d.ts +0 -330
- package/dist/chunk-2T3IL7VL.js +0 -147
- package/dist/chunk-2T3IL7VL.js.map +0 -1
- package/dist/chunk-3JMLUUFS.cjs +0 -124
- package/dist/chunk-3JMLUUFS.cjs.map +0 -1
- package/dist/chunk-6VOCV4EX.js +0 -25
- package/dist/chunk-6VOCV4EX.js.map +0 -1
- package/dist/chunk-DCDOTQNH.cjs +0 -147
- package/dist/chunk-DCDOTQNH.cjs.map +0 -1
- package/dist/chunk-DV466SBJ.cjs +0 -25
- package/dist/chunk-DV466SBJ.cjs.map +0 -1
- package/dist/chunk-GZ73G2TS.js +0 -124
- package/dist/chunk-GZ73G2TS.js.map +0 -1
- package/dist/chunk-PJFDAP54.cjs.map +0 -1
- package/dist/chunk-ZHOX6TO4.js.map +0 -1
|
@@ -0,0 +1,626 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configurable free-text answer matching (R3.3).
|
|
3
|
+
*
|
|
4
|
+
* Grade-stability contract: the DEFAULTS reproduce the v1 fill-in-the-blanks
|
|
5
|
+
* semantics exactly (trim, then locale-insensitive lowercase, strict `===`).
|
|
6
|
+
* Every tolerance below is opt-in, because changing a default here changes
|
|
7
|
+
* historical grades — and this SDK powers real summative exams.
|
|
8
|
+
*/
|
|
9
|
+
interface TextMatchPolicy {
|
|
10
|
+
/** Compare case-sensitively. Default `false` (v1 behaviour). */
|
|
11
|
+
caseSensitive?: boolean;
|
|
12
|
+
/** Strip leading/trailing whitespace before comparing. Default `true` (v1 behaviour). */
|
|
13
|
+
trim?: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Unicode normalization applied to both sides before comparing. Default
|
|
16
|
+
* `'none'` (v1 behaviour). `'NFC'` makes a decomposed `está` (combining
|
|
17
|
+
* U+0301) equal its composed form — the fix for accent-input mismatches in
|
|
18
|
+
* EN/ES/PT content.
|
|
19
|
+
*/
|
|
20
|
+
normalize?: 'none' | 'NFC' | 'NFKC';
|
|
21
|
+
/** Treat diacritics as equal to their base letters (`está` ≡ `esta`). Default `false`. */
|
|
22
|
+
foldDiacritics?: boolean;
|
|
23
|
+
/** Collapse runs of inner whitespace to a single space. Default `false`. */
|
|
24
|
+
collapseInnerWhitespace?: boolean;
|
|
25
|
+
/** Ignore Unicode punctuation on both sides. Default `false`. */
|
|
26
|
+
ignorePunctuation?: boolean;
|
|
27
|
+
/** Maximum Levenshtein edit distance still accepted as a match. Default `0`. */
|
|
28
|
+
levenshtein?: number;
|
|
29
|
+
/**
|
|
30
|
+
* BCP 47 tag for locale-aware case folding (e.g. `'tr'` for Turkish dotted /
|
|
31
|
+
* dotless I). Default: locale-insensitive `toLowerCase()` (v1 behaviour).
|
|
32
|
+
*/
|
|
33
|
+
locale?: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* How a match was achieved. Lets a consumer award full credit for an `exact`
|
|
37
|
+
* match and partial credit for a `folded` or `fuzzy` one (e.g. diacritic
|
|
38
|
+
* tolerance in a listening gap-fill vs. a spelling test).
|
|
39
|
+
* - `exact` — equal under the baseline trim/case rules alone.
|
|
40
|
+
* - `normalized` — required Unicode normalization, whitespace collapse, or punctuation tolerance.
|
|
41
|
+
* - `folded` — required diacritic folding.
|
|
42
|
+
* - `fuzzy` — required Levenshtein tolerance.
|
|
43
|
+
* - `none` — no accepted answer matched.
|
|
44
|
+
*/
|
|
45
|
+
interface TextMatchResult {
|
|
46
|
+
matched: boolean;
|
|
47
|
+
via: 'exact' | 'normalized' | 'folded' | 'fuzzy' | 'none';
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Levenshtein distance with an early-exit bound: returns `max + 1` as soon as
|
|
51
|
+
* the distance provably exceeds `max`.
|
|
52
|
+
*/
|
|
53
|
+
declare function levenshteinDistance(a: string, b: string, max: number): number;
|
|
54
|
+
/**
|
|
55
|
+
* Matches a learner's input against one or more accepted answers under a
|
|
56
|
+
* {@link TextMatchPolicy}. Tolerances are evaluated in stages — baseline
|
|
57
|
+
* (trim/case), then normalization, then diacritic folding, then Levenshtein —
|
|
58
|
+
* and {@link TextMatchResult.via} reports the first stage that produced the
|
|
59
|
+
* match, so graders can award credit by match quality.
|
|
60
|
+
*
|
|
61
|
+
* With no policy (or an empty one) this is byte-for-byte the v1 matching
|
|
62
|
+
* semantics: `trim` + locale-insensitive `toLowerCase` + strict equality.
|
|
63
|
+
*/
|
|
64
|
+
declare function matchText(input: string, accepted: string | readonly string[], policy?: TextMatchPolicy): TextMatchResult;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* An xAPI Activity object — the thing a statement is about.
|
|
68
|
+
* This SDK only builds Activity-type objects (xAPI 1.0.3 §4.1.4.1).
|
|
69
|
+
*/
|
|
70
|
+
interface XAPIObject {
|
|
71
|
+
objectType: 'Activity';
|
|
72
|
+
/** IRI uniquely identifying the activity. */
|
|
73
|
+
id: string;
|
|
74
|
+
definition?: {
|
|
75
|
+
/** Language-map of human-readable names for the activity. */
|
|
76
|
+
name?: Record<string, string>;
|
|
77
|
+
/** Language-map of human-readable descriptions. */
|
|
78
|
+
description?: Record<string, string>;
|
|
79
|
+
/** IRI identifying the activity type. */
|
|
80
|
+
type?: string;
|
|
81
|
+
/** xAPI 1.0.3 cmi.interaction type (e.g. `choice`, `fill-in`, `long-fill-in`). */
|
|
82
|
+
interactionType?: string;
|
|
83
|
+
/** Patterns describing the correct response(s), per the xAPI CRP format. */
|
|
84
|
+
correctResponsesPattern?: string[];
|
|
85
|
+
/** For `choice`-family interactions: the available components. */
|
|
86
|
+
choices?: Array<{
|
|
87
|
+
id: string;
|
|
88
|
+
description?: Record<string, string>;
|
|
89
|
+
}>;
|
|
90
|
+
extensions?: Record<string, unknown>;
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* An xAPI Agent (person or system) that performed the statement's verb.
|
|
95
|
+
* Exactly one of `mbox` or `account` must be present.
|
|
96
|
+
*/
|
|
97
|
+
interface XAPIActor {
|
|
98
|
+
objectType: 'Agent';
|
|
99
|
+
/** Display name for the actor. */
|
|
100
|
+
name?: string;
|
|
101
|
+
/** Mailto IRI uniquely identifying the actor (e.g. `mailto:user@example.com`). */
|
|
102
|
+
mbox?: string;
|
|
103
|
+
/** Account-based actor identifier. */
|
|
104
|
+
account?: {
|
|
105
|
+
/** IRI of the home page of the account's service provider. */
|
|
106
|
+
homePage: string;
|
|
107
|
+
/** The actor's unique name on that service. */
|
|
108
|
+
name: string;
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
/** An xAPI Verb — the action performed in the statement. */
|
|
112
|
+
interface XAPIVerbObject {
|
|
113
|
+
/** IRI uniquely identifying this verb (e.g. from adlnet.gov/expapi/verbs). */
|
|
114
|
+
id: string;
|
|
115
|
+
/** Language-map of human-readable verb labels. */
|
|
116
|
+
display: Record<string, string>;
|
|
117
|
+
}
|
|
118
|
+
/** Score sub-object within an xAPI Result. */
|
|
119
|
+
interface XAPIScore {
|
|
120
|
+
/** Normalized score in the range [0, 1]. */
|
|
121
|
+
scaled: number;
|
|
122
|
+
/** Raw score in the instrument's native scale. */
|
|
123
|
+
raw?: number;
|
|
124
|
+
/** Minimum possible raw score. */
|
|
125
|
+
min?: number;
|
|
126
|
+
/** Maximum possible raw score. */
|
|
127
|
+
max?: number;
|
|
128
|
+
}
|
|
129
|
+
/** Result information attached to an xAPI Statement. */
|
|
130
|
+
interface XAPIResult {
|
|
131
|
+
score?: XAPIScore;
|
|
132
|
+
/** Whether the actor's performance was successful. */
|
|
133
|
+
success?: boolean;
|
|
134
|
+
/** Whether the activity was completed. */
|
|
135
|
+
completion?: boolean;
|
|
136
|
+
/** ISO 8601 duration string representing time spent (e.g. `PT5M30S`). */
|
|
137
|
+
duration?: string;
|
|
138
|
+
/** A string representation of the learner's response. */
|
|
139
|
+
response?: string;
|
|
140
|
+
extensions?: Record<string, unknown>;
|
|
141
|
+
}
|
|
142
|
+
/** Related-activity references attached to a statement's context. */
|
|
143
|
+
interface XAPIContextActivities {
|
|
144
|
+
/** Activities this statement's activity is directly part of (e.g. the exam section). */
|
|
145
|
+
parent?: XAPIObject[];
|
|
146
|
+
/** Broader groupings (e.g. the course). */
|
|
147
|
+
grouping?: XAPIObject[];
|
|
148
|
+
/** Categorisations (e.g. a profile IRI). */
|
|
149
|
+
category?: XAPIObject[];
|
|
150
|
+
/** Other contextually related activities. */
|
|
151
|
+
other?: XAPIObject[];
|
|
152
|
+
}
|
|
153
|
+
/** Context information attached to an xAPI Statement. */
|
|
154
|
+
interface XAPIContext {
|
|
155
|
+
/** Name of the software used to record the statement. */
|
|
156
|
+
platform?: string;
|
|
157
|
+
/** BCP 47 language tag for the activity content. */
|
|
158
|
+
language?: string;
|
|
159
|
+
/** Related activities (parent/grouping/category/other). */
|
|
160
|
+
contextActivities?: XAPIContextActivities;
|
|
161
|
+
extensions?: Record<string, unknown>;
|
|
162
|
+
}
|
|
163
|
+
/** A complete xAPI 1.0.3 Statement. */
|
|
164
|
+
interface XAPIStatement {
|
|
165
|
+
/** UUID v4 uniquely identifying this statement. */
|
|
166
|
+
id: string;
|
|
167
|
+
actor: XAPIActor;
|
|
168
|
+
verb: XAPIVerbObject;
|
|
169
|
+
object: XAPIObject;
|
|
170
|
+
result?: XAPIResult;
|
|
171
|
+
context?: XAPIContext;
|
|
172
|
+
/** ISO 8601 timestamp of when the statement occurred. */
|
|
173
|
+
timestamp: string;
|
|
174
|
+
version: '1.0.3';
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Configuration passed to `useXAPI` to connect to an LRS endpoint.
|
|
178
|
+
* The `auth` field supports both HTTP Basic and Bearer token authentication.
|
|
179
|
+
*/
|
|
180
|
+
interface XAPIConfig {
|
|
181
|
+
/** Full URL of the LRS statements endpoint. */
|
|
182
|
+
endpoint: string;
|
|
183
|
+
/** Authentication credentials for the LRS. */
|
|
184
|
+
auth: {
|
|
185
|
+
type: 'basic';
|
|
186
|
+
username: string;
|
|
187
|
+
password: string;
|
|
188
|
+
} | {
|
|
189
|
+
type: 'bearer';
|
|
190
|
+
token: string;
|
|
191
|
+
};
|
|
192
|
+
/**
|
|
193
|
+
* Replaces the SDK's placeholder object id (`urn:learning-kit:activity:*`)
|
|
194
|
+
* before a statement is sent.
|
|
195
|
+
*
|
|
196
|
+
* - `string` — the IRI for THE activity this hook instance serves. A hook
|
|
197
|
+
* instance is per-activity by contract; with several activities sharing
|
|
198
|
+
* one hook, a plain string would collapse their statements onto one IRI —
|
|
199
|
+
* use the function form instead.
|
|
200
|
+
* - `(sdkObjectId) => string` — maps each placeholder id (which embeds the
|
|
201
|
+
* activity's `data.id`) to its deployment IRI, for pages that send many
|
|
202
|
+
* activities through one hook.
|
|
203
|
+
*
|
|
204
|
+
* Object ids the consumer already rewrote (non-`urn:learning-kit:` ids) are
|
|
205
|
+
* never touched.
|
|
206
|
+
*/
|
|
207
|
+
activityId: string | ((sdkObjectId: string) => string);
|
|
208
|
+
actor: XAPIActor;
|
|
209
|
+
/** Called after all retry attempts are exhausted. */
|
|
210
|
+
onError?: (err: XAPIError) => void;
|
|
211
|
+
}
|
|
212
|
+
/** Error payload delivered to `XAPIConfig.onError` after retries are exhausted. */
|
|
213
|
+
interface XAPIError {
|
|
214
|
+
/** The statement that failed to send. */
|
|
215
|
+
statement: XAPIStatement;
|
|
216
|
+
/** The 1-based attempt number on which the final failure occurred. */
|
|
217
|
+
attempt: number;
|
|
218
|
+
/** HTTP status code from the LRS, if a response was received. */
|
|
219
|
+
statusCode?: number;
|
|
220
|
+
/** Human-readable error description. */
|
|
221
|
+
message: string;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Maps each ActivityType string to its corresponding data shape.
|
|
226
|
+
*
|
|
227
|
+
* Open for extension via TypeScript module augmentation: a consumer that
|
|
228
|
+
* registers a custom activity type (`registerActivityType`) can augment this
|
|
229
|
+
* interface so `ActivityType`, `ActivityData`, `validateActivity`, and
|
|
230
|
+
* `evaluate` pick the new type up without an SDK release:
|
|
231
|
+
*
|
|
232
|
+
* ```ts
|
|
233
|
+
* declare module '@intellectif/lk-core' {
|
|
234
|
+
* interface ActivityDataMap { 'my-type': MyTypeData }
|
|
235
|
+
* interface LearnerResponseMap { 'my-type': MyTypeLearnerResponse }
|
|
236
|
+
* }
|
|
237
|
+
* ```
|
|
238
|
+
*/
|
|
239
|
+
interface ActivityDataMap {
|
|
240
|
+
'multiple-choice': MultipleChoiceData;
|
|
241
|
+
'fill-in-the-blanks': FillInTheBlanksData;
|
|
242
|
+
'written-response': WrittenResponseData;
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* The set of activity types supported by learning-kit. Derived from
|
|
246
|
+
* {@link ActivityDataMap}, so module augmentation widens it automatically.
|
|
247
|
+
*/
|
|
248
|
+
type ActivityType = keyof ActivityDataMap;
|
|
249
|
+
/** Union of all valid activity data shapes. */
|
|
250
|
+
type ActivityData = ActivityDataMap[ActivityType];
|
|
251
|
+
/**
|
|
252
|
+
* Optional media (image/audio/video) shown above a question or passage.
|
|
253
|
+
* URL-only: hosting/delivery is the consuming application's responsibility.
|
|
254
|
+
*/
|
|
255
|
+
interface ActivityMedia {
|
|
256
|
+
/** The kind of media; selects the rendered element. `embed` → sandboxed iframe. */
|
|
257
|
+
type: 'image' | 'audio' | 'video' | 'embed';
|
|
258
|
+
/**
|
|
259
|
+
* Source URL. For `embed` this MUST be the provider's embeddable URL
|
|
260
|
+
* (e.g. `https://www.youtube.com/embed/<id>`), not the watch page.
|
|
261
|
+
*/
|
|
262
|
+
url: string;
|
|
263
|
+
/** Alternative text. Required for `image` and `embed`; optional label for audio/video. */
|
|
264
|
+
alt?: string;
|
|
265
|
+
/** Optional WebVTT captions track URL for `audio`/`video`. */
|
|
266
|
+
captionsUrl?: string;
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Optional authored "overall feedback" shown after submission, chosen by
|
|
270
|
+
* whether the learner passed (h5p-style overall feedback). Distinct from
|
|
271
|
+
* per-option feedback; either field may be omitted.
|
|
272
|
+
*/
|
|
273
|
+
interface ActivityFeedback {
|
|
274
|
+
/** Shown when the learner passes (score ≥ pass threshold). */
|
|
275
|
+
correct?: string;
|
|
276
|
+
/** Shown when the learner does not pass. */
|
|
277
|
+
incorrect?: string;
|
|
278
|
+
}
|
|
279
|
+
/** A single selectable option within a Multiple Choice activity. */
|
|
280
|
+
interface MultipleChoiceOption {
|
|
281
|
+
/** Unique identifier for this option within the activity. */
|
|
282
|
+
id: string;
|
|
283
|
+
/** Display text shown to the learner. */
|
|
284
|
+
text: string;
|
|
285
|
+
/** Whether this option is part of the correct answer. */
|
|
286
|
+
isCorrect: boolean;
|
|
287
|
+
/** Optional per-option feedback shown after submission. */
|
|
288
|
+
feedback?: string;
|
|
289
|
+
}
|
|
290
|
+
/** Data contract for a Multiple Choice activity. */
|
|
291
|
+
interface MultipleChoiceData {
|
|
292
|
+
schemaVersion: '1.0';
|
|
293
|
+
type: 'multiple-choice';
|
|
294
|
+
/** Unique identifier for this activity. */
|
|
295
|
+
id: string;
|
|
296
|
+
/** Human-readable title used in xAPI statements and error boundaries. */
|
|
297
|
+
title: string;
|
|
298
|
+
/** The question stem presented to the learner, as plain text. */
|
|
299
|
+
question: string;
|
|
300
|
+
/**
|
|
301
|
+
* Optional sanitised rich-HTML rendering of the question, parallel to
|
|
302
|
+
* `WrittenResponseData.promptHtml`. The SDK carries and redacts it as
|
|
303
|
+
* learner-visible content but does not render it yet (components escape
|
|
304
|
+
* text); sanitisation is the application's responsibility.
|
|
305
|
+
*/
|
|
306
|
+
questionHtml?: string;
|
|
307
|
+
/** `single` allows one selection; `multi` allows multiple. */
|
|
308
|
+
mode: 'single' | 'multi';
|
|
309
|
+
/** Ordered list of answer options. */
|
|
310
|
+
options: MultipleChoiceOption[];
|
|
311
|
+
/** Scoring algorithm applied when the learner submits. */
|
|
312
|
+
scoringStrategy: 'all-or-nothing' | 'partial';
|
|
313
|
+
/** Optional media shown above the question. */
|
|
314
|
+
media?: ActivityMedia;
|
|
315
|
+
/** Optional authored overall feedback shown after submission. */
|
|
316
|
+
feedback?: ActivityFeedback;
|
|
317
|
+
/** Minimum scaled score [0–1] required to pass. Defaults to {@link DEFAULT_PASS_THRESHOLD} (0.7) when absent. */
|
|
318
|
+
passThreshold?: number;
|
|
319
|
+
/** When true, options are shuffled deterministically per session. */
|
|
320
|
+
shuffle?: boolean;
|
|
321
|
+
/** BCP 47 language tag for the activity content. */
|
|
322
|
+
locale?: string;
|
|
323
|
+
/** IRI references to learning objectives addressed by this activity. */
|
|
324
|
+
learningObjectives?: string[];
|
|
325
|
+
/** Subjective difficulty on a 1–5 scale. */
|
|
326
|
+
difficultyLevel?: 1 | 2 | 3 | 4 | 5;
|
|
327
|
+
}
|
|
328
|
+
/** Configuration for a single fill-in-the-blank slot. */
|
|
329
|
+
interface BlankConfig {
|
|
330
|
+
/** Unique identifier matching the `{{blank_id}}` placeholder in the passage. */
|
|
331
|
+
id: string;
|
|
332
|
+
/** List of strings accepted as correct answers for this blank. */
|
|
333
|
+
acceptedAnswers: string[];
|
|
334
|
+
/** Whether answer matching is case-sensitive. Defaults to false. */
|
|
335
|
+
caseSensitive?: boolean;
|
|
336
|
+
/** Whether leading/trailing whitespace is stripped before matching. Defaults to true. */
|
|
337
|
+
trimWhitespace?: boolean;
|
|
338
|
+
/**
|
|
339
|
+
* Optional matching-tolerance policy for this blank (Unicode normalization,
|
|
340
|
+
* diacritic folding, whitespace collapse, punctuation tolerance, typo
|
|
341
|
+
* tolerance). Every tolerance is opt-in; when absent, matching reproduces
|
|
342
|
+
* the v1 trim + case-fold semantics exactly. Fields set here take
|
|
343
|
+
* precedence over the legacy `caseSensitive` / `trimWhitespace` flags.
|
|
344
|
+
*/
|
|
345
|
+
match?: TextMatchPolicy;
|
|
346
|
+
/** Optional hint text revealed on learner request. */
|
|
347
|
+
hint?: string;
|
|
348
|
+
/** Optional feedback shown inline next to this blank after submission. */
|
|
349
|
+
feedback?: string;
|
|
350
|
+
}
|
|
351
|
+
/** Data contract for a Fill-in-the-Blanks activity. */
|
|
352
|
+
interface FillInTheBlanksData {
|
|
353
|
+
schemaVersion: '1.0';
|
|
354
|
+
type: 'fill-in-the-blanks';
|
|
355
|
+
/** Unique identifier for this activity. */
|
|
356
|
+
id: string;
|
|
357
|
+
/** Human-readable title used in xAPI statements and error boundaries. */
|
|
358
|
+
title: string;
|
|
359
|
+
/** Passage text containing `{{blank_id}}` placeholders. */
|
|
360
|
+
passage: string;
|
|
361
|
+
/**
|
|
362
|
+
* Optional sanitised rich-HTML rendering of the passage, parallel to
|
|
363
|
+
* `WrittenResponseData.promptHtml`. Carried and redacted as learner-visible
|
|
364
|
+
* content; not rendered by the SDK yet. Sanitisation is the application's.
|
|
365
|
+
*/
|
|
366
|
+
passageHtml?: string;
|
|
367
|
+
/** Configuration for each blank in the passage. */
|
|
368
|
+
blanks: BlankConfig[];
|
|
369
|
+
/** Scoring algorithm applied when the learner submits. */
|
|
370
|
+
scoringStrategy: 'all-or-nothing' | 'partial';
|
|
371
|
+
/** Optional media shown above the passage. */
|
|
372
|
+
media?: ActivityMedia;
|
|
373
|
+
/** Optional authored overall feedback shown after submission. */
|
|
374
|
+
feedback?: ActivityFeedback;
|
|
375
|
+
/** Minimum scaled score [0–1] required to pass. Defaults to {@link DEFAULT_PASS_THRESHOLD} (0.7) when absent. */
|
|
376
|
+
passThreshold?: number;
|
|
377
|
+
/** BCP 47 language tag for the activity content. */
|
|
378
|
+
locale?: string;
|
|
379
|
+
/** IRI references to learning objectives addressed by this activity. */
|
|
380
|
+
learningObjectives?: string[];
|
|
381
|
+
/** Subjective difficulty on a 1–5 scale. */
|
|
382
|
+
difficultyLevel?: 1 | 2 | 3 | 4 | 5;
|
|
383
|
+
}
|
|
384
|
+
/** A single criterion within a written-response grading rubric. */
|
|
385
|
+
interface WrittenResponseRubricCriterion {
|
|
386
|
+
/** Criterion name (e.g. "Task achievement", "Grammar range"). */
|
|
387
|
+
name: string;
|
|
388
|
+
/** Optional longer description of what the criterion assesses. */
|
|
389
|
+
description?: string;
|
|
390
|
+
/** Non-negative weight of this criterion in the overall grade. */
|
|
391
|
+
weight: number;
|
|
392
|
+
}
|
|
393
|
+
/** Grading rubric attached to a written-response activity. */
|
|
394
|
+
interface WrittenResponseRubric {
|
|
395
|
+
/** Optional display label for the rubric as a whole. */
|
|
396
|
+
label?: string;
|
|
397
|
+
/** The criteria the response is graded against. Non-empty when present. */
|
|
398
|
+
criteria: WrittenResponseRubricCriterion[];
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Data contract for a Written Response activity (free-text writing graded
|
|
402
|
+
* asynchronously — by an AI or human grader — after submission).
|
|
403
|
+
*
|
|
404
|
+
* Wire-format note (Req 22.9): field names and casing are locked for
|
|
405
|
+
* byte-compatibility with consumer-stored JSONB rows. `feedback` and
|
|
406
|
+
* `passThreshold` are SDK-side optional additions for component parity
|
|
407
|
+
* (Req 22.8 / Req 3.9) — being optional, their absence keeps stored payloads
|
|
408
|
+
* byte-identical.
|
|
409
|
+
*/
|
|
410
|
+
interface WrittenResponseData {
|
|
411
|
+
schemaVersion: '1.0';
|
|
412
|
+
type: 'written-response';
|
|
413
|
+
/** Unique identifier for this activity. */
|
|
414
|
+
id: string;
|
|
415
|
+
/** Human-readable title used in xAPI statements and error boundaries. */
|
|
416
|
+
title: string;
|
|
417
|
+
/** The writing prompt, as plain text. */
|
|
418
|
+
prompt: string;
|
|
419
|
+
/** Optional sanitised rich-HTML sidecar of the prompt (not rendered by the SDK yet). */
|
|
420
|
+
promptHtml?: string;
|
|
421
|
+
/** Minimum acceptable word count (≥ 0). */
|
|
422
|
+
minWords: number;
|
|
423
|
+
/** Maximum acceptable word count (≥ 1, and ≥ `minWords`). */
|
|
424
|
+
maxWords: number;
|
|
425
|
+
/** Optional grading rubric consumed by the asynchronous grader. */
|
|
426
|
+
rubric?: WrittenResponseRubric;
|
|
427
|
+
/** Optional target language/level for the response (e.g. `"en-A2"`, `"es-B1"`). */
|
|
428
|
+
languageTarget?: string;
|
|
429
|
+
/** Optional media shown above the prompt. */
|
|
430
|
+
media?: ActivityMedia;
|
|
431
|
+
/** Optional authored overall feedback (surfaced once the deferred grade exists). */
|
|
432
|
+
feedback?: ActivityFeedback;
|
|
433
|
+
/** Minimum scaled score [0–1] required to pass once graded. Defaults to {@link DEFAULT_PASS_THRESHOLD} (0.7) when absent. */
|
|
434
|
+
passThreshold?: number;
|
|
435
|
+
/** BCP 47 language tag for the activity content. */
|
|
436
|
+
locale?: string;
|
|
437
|
+
/** IRI references to learning objectives addressed by this activity. */
|
|
438
|
+
learningObjectives?: string[];
|
|
439
|
+
/** Subjective difficulty on a 1–5 scale. */
|
|
440
|
+
difficultyLevel?: 1 | 2 | 3 | 4 | 5;
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* Maps each ActivityType string to its learner-response shape. Open for
|
|
444
|
+
* extension via module augmentation, mirroring {@link ActivityDataMap}.
|
|
445
|
+
*/
|
|
446
|
+
interface LearnerResponseMap {
|
|
447
|
+
'multiple-choice': MultipleChoiceLearnerResponse;
|
|
448
|
+
'fill-in-the-blanks': FillInTheBlanksLearnerResponse;
|
|
449
|
+
'written-response': WrittenResponseLearnerResponse;
|
|
450
|
+
}
|
|
451
|
+
/** Union of all learner response shapes. */
|
|
452
|
+
type LearnerResponse = LearnerResponseMap[keyof LearnerResponseMap];
|
|
453
|
+
/** Learner response for a Multiple Choice activity. */
|
|
454
|
+
interface MultipleChoiceLearnerResponse {
|
|
455
|
+
type: 'multiple-choice';
|
|
456
|
+
/** IDs of the options the learner selected. */
|
|
457
|
+
selectedOptionIds: string[];
|
|
458
|
+
}
|
|
459
|
+
/** Learner response for a Fill-in-the-Blanks activity. */
|
|
460
|
+
interface FillInTheBlanksLearnerResponse {
|
|
461
|
+
type: 'fill-in-the-blanks';
|
|
462
|
+
/** Map of blank ID to the learner's typed answer. */
|
|
463
|
+
answers: Record<string, string>;
|
|
464
|
+
}
|
|
465
|
+
/** Learner response for a Written Response activity. */
|
|
466
|
+
interface WrittenResponseLearnerResponse {
|
|
467
|
+
type: 'written-response';
|
|
468
|
+
/** The learner's free-text response. */
|
|
469
|
+
text: string;
|
|
470
|
+
/** Word count of `text`, computed with the canonical `countWords()` helper. */
|
|
471
|
+
wordCount: number;
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Fine-grained outcome of the learner's action on a single item, replacing the
|
|
475
|
+
* ambiguous {@link ScoringDetail.correct}:
|
|
476
|
+
* - `correct` — the learner selected/entered the right answer.
|
|
477
|
+
* - `incorrect` — the learner selected/entered a wrong answer.
|
|
478
|
+
* - `correct-omission` — the learner correctly left a non-answer unselected (multiple-choice only).
|
|
479
|
+
* - `incorrect-omission` — the learner failed to select a correct answer (multiple-choice only).
|
|
480
|
+
*/
|
|
481
|
+
type ScoringOutcome = 'correct' | 'incorrect' | 'correct-omission' | 'incorrect-omission';
|
|
482
|
+
/** Per-item scoring breakdown returned by the scoring engine. */
|
|
483
|
+
interface ScoringDetail {
|
|
484
|
+
/** ID of the option or blank this detail refers to. */
|
|
485
|
+
itemId: string;
|
|
486
|
+
/**
|
|
487
|
+
* @deprecated Ambiguous: for multiple-choice this means "the learner acted
|
|
488
|
+
* correctly on this option" (`wasSelected === option.isCorrect`), NOT "this
|
|
489
|
+
* option is the answer" — an unselected wrong option reads `correct: true`.
|
|
490
|
+
* Read {@link ScoringDetail.outcome} instead; `correct` remains written for
|
|
491
|
+
* backward compatibility and will be removed in v1.0.
|
|
492
|
+
*/
|
|
493
|
+
correct: boolean;
|
|
494
|
+
/**
|
|
495
|
+
* Unambiguous outcome of the learner's action on this item. Optional in the
|
|
496
|
+
* type so 0.2-era consumer-constructed literals keep compiling, but ALWAYS
|
|
497
|
+
* written by both built-in scorers since 0.3.0; becomes required in v1.0
|
|
498
|
+
* when the deprecated `correct` is removed.
|
|
499
|
+
*/
|
|
500
|
+
outcome?: ScoringOutcome;
|
|
501
|
+
/** The learner's actual response for this item. */
|
|
502
|
+
learnerResponse: string | string[];
|
|
503
|
+
/** The expected correct response(s) for this item. */
|
|
504
|
+
correctResponse: string | string[];
|
|
505
|
+
/**
|
|
506
|
+
* Weight applied to this item's contribution to the overall score. Both
|
|
507
|
+
* built-in scorers currently weight every item equally and write `1`.
|
|
508
|
+
*/
|
|
509
|
+
weight?: number;
|
|
510
|
+
}
|
|
511
|
+
/** Full scoring result returned by the scoring engine. */
|
|
512
|
+
interface ScoringResult {
|
|
513
|
+
/** Scaled score in the range [0, 1]. */
|
|
514
|
+
score: number;
|
|
515
|
+
/** Maximum possible scaled score (always 1). */
|
|
516
|
+
maxScore: number;
|
|
517
|
+
/** Whether the score meets or exceeds the activity's pass threshold. */
|
|
518
|
+
passed: boolean;
|
|
519
|
+
/**
|
|
520
|
+
* The authored overall feedback selected for this result: `feedback.correct`
|
|
521
|
+
* when the learner passed, `feedback.incorrect` otherwise; `null` when the
|
|
522
|
+
* activity authored no matching feedback.
|
|
523
|
+
*/
|
|
524
|
+
feedback: string | null;
|
|
525
|
+
/** Per-item scoring breakdown. */
|
|
526
|
+
details: ScoringDetail[];
|
|
527
|
+
}
|
|
528
|
+
/**
|
|
529
|
+
* Progress facts about a deferred (asynchronously graded) submission that are
|
|
530
|
+
* computable synchronously at submit time. For written-response, both fields
|
|
531
|
+
* are always present and `wordCount` is recomputed from the submitted text
|
|
532
|
+
* with the canonical `countWords()` (the client-supplied count is not trusted).
|
|
533
|
+
*/
|
|
534
|
+
interface DeferredScoringPartial {
|
|
535
|
+
/** Whether the recomputed word count falls within `[minWords, maxWords]`. */
|
|
536
|
+
withinWordBounds?: boolean;
|
|
537
|
+
/** Recomputed word count of the submitted text. */
|
|
538
|
+
wordCount?: number;
|
|
539
|
+
[key: string]: unknown;
|
|
540
|
+
}
|
|
541
|
+
/**
|
|
542
|
+
* The outcome of evaluating a learner response against an activity — the
|
|
543
|
+
* union `evaluate()` returns. Unlike {@link ScoringResult}, it can express
|
|
544
|
+
* "not gradable yet" (`deferred`) and "not gradable at all" (`unscorable`),
|
|
545
|
+
* so an ungraded written response is never conflated with a score of 0.
|
|
546
|
+
*/
|
|
547
|
+
type ItemOutcome = {
|
|
548
|
+
status: 'scored';
|
|
549
|
+
/** Scaled score in the range [0, 1]. */
|
|
550
|
+
score: number;
|
|
551
|
+
/** Maximum possible scaled score. */
|
|
552
|
+
maxScore: number;
|
|
553
|
+
/** Whether the score meets or exceeds the activity's pass threshold. */
|
|
554
|
+
passed: boolean;
|
|
555
|
+
/** Authored overall feedback selected by pass state, or null. */
|
|
556
|
+
feedback: string | null;
|
|
557
|
+
/** Per-item scoring breakdown. */
|
|
558
|
+
details: ScoringDetail[];
|
|
559
|
+
} | {
|
|
560
|
+
status: 'deferred';
|
|
561
|
+
/** Why the grade is deferred (asynchronous AI/human grading). */
|
|
562
|
+
reason: 'requires_async_grading';
|
|
563
|
+
/** Maximum possible scaled score once graded. */
|
|
564
|
+
maxScore: number;
|
|
565
|
+
/** Synchronously computable progress facts (word bounds, counts). */
|
|
566
|
+
partial?: DeferredScoringPartial;
|
|
567
|
+
} | {
|
|
568
|
+
status: 'unscorable';
|
|
569
|
+
/** Why no grade can be produced (e.g. unregistered activity type). */
|
|
570
|
+
reason: string;
|
|
571
|
+
/** Maximum possible scaled score, when known. */
|
|
572
|
+
maxScore: number;
|
|
573
|
+
};
|
|
574
|
+
/** A single validation error produced by `validateActivity`. */
|
|
575
|
+
interface ValidationError {
|
|
576
|
+
/** JSON-path-style location of the invalid field. */
|
|
577
|
+
path: string[];
|
|
578
|
+
/** Human-readable description of the validation failure. */
|
|
579
|
+
message: string;
|
|
580
|
+
/** Machine-readable error code. */
|
|
581
|
+
code: string;
|
|
582
|
+
}
|
|
583
|
+
/**
|
|
584
|
+
* Result of a `validateActivity` call.
|
|
585
|
+
* On success, `data` is the validated and typed activity data.
|
|
586
|
+
* On failure, `errors` contains one entry per violated constraint.
|
|
587
|
+
*/
|
|
588
|
+
type ValidationResult<T> = {
|
|
589
|
+
success: true;
|
|
590
|
+
data: T;
|
|
591
|
+
} | {
|
|
592
|
+
success: false;
|
|
593
|
+
errors: ValidationError[];
|
|
594
|
+
};
|
|
595
|
+
/** The payload delivered to an activity's `onComplete` callback. */
|
|
596
|
+
interface ActivityResult {
|
|
597
|
+
/** Scaled score in the range [0, 1]. */
|
|
598
|
+
score: number;
|
|
599
|
+
/** Maximum possible scaled score (always 1). */
|
|
600
|
+
maxScore: number;
|
|
601
|
+
/** Whether the learner passed based on the activity's pass threshold. */
|
|
602
|
+
passed: boolean;
|
|
603
|
+
/** Time in milliseconds from first interaction to submission. */
|
|
604
|
+
timeSpent: number;
|
|
605
|
+
/** The xAPI statement built and (optionally) sent for this attempt. */
|
|
606
|
+
xapiStatement: XAPIStatement;
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* The kinds of interaction the built-in components emit. Custom activity types
|
|
610
|
+
* registered by consumers may emit their own kinds, so any string is accepted;
|
|
611
|
+
* the named literals are kept for autocompletion.
|
|
612
|
+
*/
|
|
613
|
+
type InteractionKind = 'option-selected' | 'option-deselected' | 'blank-filled' | 'hint-requested' | 'text-changed' | 'submitted' | (string & {});
|
|
614
|
+
/** Fired by activity components on every discrete learner interaction. */
|
|
615
|
+
interface InteractionEvent {
|
|
616
|
+
/** The kind of interaction that occurred. */
|
|
617
|
+
type: InteractionKind;
|
|
618
|
+
/** ID of the activity that produced the event. */
|
|
619
|
+
activityId: string;
|
|
620
|
+
/** Unix timestamp (ms) of when the interaction occurred. */
|
|
621
|
+
timestamp: number;
|
|
622
|
+
/** Additional event-specific data. */
|
|
623
|
+
payload: Record<string, unknown>;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
export { type ActivityData as A, type BlankConfig as B, type XAPIVerbObject as C, type DeferredScoringPartial as D, levenshteinDistance as E, type FillInTheBlanksData as F, matchText as G, type InteractionEvent as I, type LearnerResponse as L, type MultipleChoiceData as M, type ScoringDetail as S, type TextMatchPolicy as T, type ValidationError as V, type WrittenResponseData as W, type XAPIActor as X, type ActivityDataMap as a, type ActivityFeedback as b, type ActivityMedia as c, type ActivityResult as d, type ActivityType as e, type FillInTheBlanksLearnerResponse as f, type InteractionKind as g, type ItemOutcome as h, type LearnerResponseMap as i, type MultipleChoiceLearnerResponse as j, type MultipleChoiceOption as k, type ScoringOutcome as l, type ScoringResult as m, type TextMatchResult as n, type ValidationResult as o, type WrittenResponseLearnerResponse as p, type WrittenResponseRubric as q, type WrittenResponseRubricCriterion as r, type XAPIConfig as s, type XAPIContext as t, type XAPIContextActivities as u, type XAPIError as v, type XAPIObject as w, type XAPIResult as x, type XAPIScore as y, type XAPIStatement as z };
|