@intellectif/lk-core 1.1.0 → 1.3.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 +30 -0
- package/README.md +4 -3
- package/dist/ai-B222dtHH.d.cts +2154 -0
- package/dist/ai-B222dtHH.d.cts.map +1 -0
- package/dist/ai-B222dtHH.d.ts +2154 -0
- package/dist/ai-B222dtHH.d.ts.map +1 -0
- package/dist/ai-check.cjs +758 -464
- package/dist/ai-check.cjs.map +1 -1
- package/dist/ai-check.d.cts +82 -68
- package/dist/ai-check.d.cts.map +1 -0
- package/dist/ai-check.d.ts +82 -68
- package/dist/ai-check.d.ts.map +1 -0
- package/dist/ai-check.js +756 -465
- package/dist/ai-check.js.map +1 -1
- package/dist/ai-writing-CCBjTOM3.js +2496 -0
- package/dist/ai-writing-CCBjTOM3.js.map +1 -0
- package/dist/ai-writing-DCVkv4Zj.cjs +2735 -0
- package/dist/ai-writing-DCVkv4Zj.cjs.map +1 -0
- package/dist/{index-DlcqkM_L.d.ts → index-BWPRPIXL.d.ts} +263 -258
- package/dist/index-BWPRPIXL.d.ts.map +1 -0
- package/dist/index-CXyOV8sm.d.cts +153 -0
- package/dist/index-CXyOV8sm.d.cts.map +1 -0
- package/dist/index-CegM8o15.d.ts +153 -0
- package/dist/index-CegM8o15.d.ts.map +1 -0
- package/dist/index-Dyu66R4-.d.cts +1071 -0
- package/dist/index-Dyu66R4-.d.cts.map +1 -0
- package/dist/{index-DlCW0XaQ.d.cts → index-O4_ExlAD.d.cts} +263 -258
- package/dist/index-O4_ExlAD.d.cts.map +1 -0
- package/dist/index-UGxRSopE.d.ts +1071 -0
- package/dist/index-UGxRSopE.d.ts.map +1 -0
- package/dist/index.cjs +1210 -1671
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1090 -853
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.ts +1090 -853
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1063 -1672
- package/dist/index.js.map +1 -1
- package/dist/item-group-DXiR8nOr.js +445 -0
- package/dist/item-group-DXiR8nOr.js.map +1 -0
- package/dist/item-group-DroFXgLb.cjs +505 -0
- package/dist/item-group-DroFXgLb.cjs.map +1 -0
- package/dist/registry-DVi0LR_i.cjs +5915 -0
- package/dist/registry-DVi0LR_i.cjs.map +1 -0
- package/dist/registry-Dsx9mNsS.js +5449 -0
- package/dist/registry-Dsx9mNsS.js.map +1 -0
- package/dist/schemas-Bdw0nGPo.js +152 -0
- package/dist/schemas-Bdw0nGPo.js.map +1 -0
- package/dist/schemas-DVbAOrtd.cjs +224 -0
- package/dist/schemas-DVbAOrtd.cjs.map +1 -0
- package/dist/schemas.cjs +16 -32
- package/dist/schemas.d.cts +2 -2
- package/dist/schemas.d.ts +2 -2
- package/dist/schemas.js +3 -32
- package/dist/scoring-B2_HEhMT.cjs +1748 -0
- package/dist/scoring-B2_HEhMT.cjs.map +1 -0
- package/dist/scoring-BZqUkuDF.js +1604 -0
- package/dist/scoring-BZqUkuDF.js.map +1 -0
- package/dist/scoring.cjs +37 -75
- package/dist/scoring.d.cts +3 -1103
- package/dist/scoring.d.ts +3 -1103
- package/dist/scoring.js +3 -75
- package/dist/xapi-CMTph7wG.js +332 -0
- package/dist/xapi-CMTph7wG.js.map +1 -0
- package/dist/xapi-DNzkSJ0S.cjs +362 -0
- package/dist/xapi-DNzkSJ0S.cjs.map +1 -0
- package/dist/xapi.cjs +7 -16
- package/dist/xapi.d.cts +2 -150
- package/dist/xapi.d.ts +2 -150
- package/dist/xapi.js +2 -16
- package/package.json +4 -4
- package/dist/activity-MBOYLBjR.d.cts +0 -1840
- package/dist/activity-MBOYLBjR.d.ts +0 -1840
- package/dist/chunk-2JT2WUQ2.cjs +0 -458
- package/dist/chunk-2JT2WUQ2.cjs.map +0 -1
- package/dist/chunk-DSDXL3JO.cjs +0 -4597
- package/dist/chunk-DSDXL3JO.cjs.map +0 -1
- package/dist/chunk-FBKTION6.js +0 -4597
- package/dist/chunk-FBKTION6.js.map +0 -1
- package/dist/chunk-IARLJ5MW.js +0 -1256
- package/dist/chunk-IARLJ5MW.js.map +0 -1
- package/dist/chunk-LDFQUZII.cjs +0 -950
- package/dist/chunk-LDFQUZII.cjs.map +0 -1
- package/dist/chunk-NQHETFDA.js +0 -950
- package/dist/chunk-NQHETFDA.js.map +0 -1
- package/dist/chunk-SJVRUPJT.js +0 -272
- package/dist/chunk-SJVRUPJT.js.map +0 -1
- package/dist/chunk-TJJFKKH2.cjs +0 -272
- package/dist/chunk-TJJFKKH2.cjs.map +0 -1
- package/dist/chunk-VSH7ECQL.cjs +0 -1256
- package/dist/chunk-VSH7ECQL.cjs.map +0 -1
- package/dist/chunk-XS6U2ARZ.js +0 -458
- package/dist/chunk-XS6U2ARZ.js.map +0 -1
- package/dist/schemas.cjs.map +0 -1
- package/dist/schemas.js.map +0 -1
- package/dist/scoring.cjs.map +0 -1
- package/dist/scoring.js.map +0 -1
- package/dist/xapi.cjs.map +0 -1
- package/dist/xapi.js.map +0 -1
|
@@ -0,0 +1,2154 @@
|
|
|
1
|
+
//#region src/scoring/text-match.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Configurable free-text answer matching (R3.3).
|
|
4
|
+
*
|
|
5
|
+
* Grade-stability contract: the DEFAULTS reproduce the v1 fill-in-the-blanks
|
|
6
|
+
* semantics exactly (trim, then locale-insensitive lowercase, strict `===`).
|
|
7
|
+
* Every tolerance below is opt-in, because changing a default here changes
|
|
8
|
+
* historical grades — and this SDK powers real summative exams.
|
|
9
|
+
*/
|
|
10
|
+
interface TextMatchPolicy {
|
|
11
|
+
/** Compare case-sensitively. Default `false` (v1 behaviour). */
|
|
12
|
+
caseSensitive?: boolean;
|
|
13
|
+
/** Strip leading/trailing whitespace before comparing. Default `true` (v1 behaviour). */
|
|
14
|
+
trim?: boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Unicode normalization applied to both sides before comparing. Default
|
|
17
|
+
* `'none'` (v1 behaviour). `'NFC'` makes a decomposed `está` (combining
|
|
18
|
+
* U+0301) equal its composed form — the fix for accent-input mismatches in
|
|
19
|
+
* EN/ES/PT content.
|
|
20
|
+
*/
|
|
21
|
+
normalize?: 'none' | 'NFC' | 'NFKC';
|
|
22
|
+
/** Treat diacritics as equal to their base letters (`está` ≡ `esta`). Default `false`. */
|
|
23
|
+
foldDiacritics?: boolean;
|
|
24
|
+
/** Collapse runs of inner whitespace to a single space. Default `false`. */
|
|
25
|
+
collapseInnerWhitespace?: boolean;
|
|
26
|
+
/** Ignore Unicode punctuation on both sides. Default `false`. */
|
|
27
|
+
ignorePunctuation?: boolean;
|
|
28
|
+
/** Maximum Levenshtein edit distance still accepted as a match. Default `0`. */
|
|
29
|
+
levenshtein?: number;
|
|
30
|
+
/**
|
|
31
|
+
* BCP 47 tag for locale-aware case folding (e.g. `'tr'` for Turkish dotted /
|
|
32
|
+
* dotless I). Default: locale-insensitive `toLowerCase()` (v1 behaviour).
|
|
33
|
+
*/
|
|
34
|
+
locale?: string;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* How a match was achieved. Lets a consumer award full credit for an `exact`
|
|
38
|
+
* match and partial credit for a `folded` or `fuzzy` one (e.g. diacritic
|
|
39
|
+
* tolerance in a listening gap-fill vs. a spelling test).
|
|
40
|
+
* - `exact` — equal under the baseline trim/case rules alone.
|
|
41
|
+
* - `normalized` — required Unicode normalization, whitespace collapse, or punctuation tolerance.
|
|
42
|
+
* - `folded` — required diacritic folding.
|
|
43
|
+
* - `fuzzy` — required Levenshtein tolerance.
|
|
44
|
+
* - `none` — no accepted answer matched.
|
|
45
|
+
*/
|
|
46
|
+
interface TextMatchResult {
|
|
47
|
+
matched: boolean;
|
|
48
|
+
via: 'exact' | 'normalized' | 'folded' | 'fuzzy' | 'none';
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Levenshtein distance with an early-exit bound: returns `max + 1` as soon as
|
|
52
|
+
* the distance provably exceeds `max`.
|
|
53
|
+
*/
|
|
54
|
+
declare function levenshteinDistance(a: string, b: string, max: number): number;
|
|
55
|
+
/**
|
|
56
|
+
* Matches a learner's input against one or more accepted answers under a
|
|
57
|
+
* {@link TextMatchPolicy}. Tolerances are evaluated in stages — baseline
|
|
58
|
+
* (trim/case), then normalization, then diacritic folding, then Levenshtein —
|
|
59
|
+
* and {@link TextMatchResult.via} reports the first stage that produced the
|
|
60
|
+
* match, so graders can award credit by match quality.
|
|
61
|
+
*
|
|
62
|
+
* With no policy (or an empty one) this is byte-for-byte the v1 matching
|
|
63
|
+
* semantics: `trim` + locale-insensitive `toLowerCase` + strict equality.
|
|
64
|
+
*/
|
|
65
|
+
declare function matchText(input: string, accepted: string | readonly string[], policy?: TextMatchPolicy): TextMatchResult;
|
|
66
|
+
//#endregion
|
|
67
|
+
//#region src/types/grading.d.ts
|
|
68
|
+
/**
|
|
69
|
+
* Lifecycle of a deferred grade. A grade exists only in the `graded` state;
|
|
70
|
+
* every other state means "no grade, and here is why" — which is precisely the
|
|
71
|
+
* distinction that stops an ungraded submission being rendered as a zero.
|
|
72
|
+
*/
|
|
73
|
+
type GradingState = 'queued' | 'running' | 'graded' | 'failed' | 'skipped';
|
|
74
|
+
/**
|
|
75
|
+
* Who produced a grade.
|
|
76
|
+
*
|
|
77
|
+
* - `'auto'` — a non-generative measurement engine, for example a
|
|
78
|
+
* pronunciation-assessment service.
|
|
79
|
+
* - `'ai'` — a generative model.
|
|
80
|
+
* - `'human'` — a person.
|
|
81
|
+
*/
|
|
82
|
+
type GraderKind = 'ai' | 'human' | 'auto';
|
|
83
|
+
/** Provenance of a grade, so a re-grade two years later is explicable. */
|
|
84
|
+
interface Grader {
|
|
85
|
+
kind: GraderKind;
|
|
86
|
+
/** Identifier of the human grader, when `kind` is `'human'`. */
|
|
87
|
+
id?: string;
|
|
88
|
+
/** Model identifier, when `kind` is `'ai'` (e.g. a model name and version). */
|
|
89
|
+
model?: string;
|
|
90
|
+
/** Hash or id of the prompt/rubric revision used, for auditability. */
|
|
91
|
+
promptHash?: string;
|
|
92
|
+
}
|
|
93
|
+
/** Optional cost/usage telemetry emitted by an AI grader. */
|
|
94
|
+
interface GraderUsage {
|
|
95
|
+
promptTokens?: number;
|
|
96
|
+
completionTokens?: number;
|
|
97
|
+
costUsd?: number;
|
|
98
|
+
}
|
|
99
|
+
/** A grader's verdict on one rubric criterion. */
|
|
100
|
+
interface CriterionScore {
|
|
101
|
+
/** Matches a `WrittenResponseRubricCriterion.name` when a rubric is known. */
|
|
102
|
+
name: string;
|
|
103
|
+
/**
|
|
104
|
+
* The grader's score for this criterion, out of {@link CriterionScore.maxScore}
|
|
105
|
+
* (which defaults to 1, the SDK's scaled convention). Absent when the
|
|
106
|
+
* criterion is judged on an ordinal `band`.
|
|
107
|
+
*/
|
|
108
|
+
score?: number;
|
|
109
|
+
/**
|
|
110
|
+
* What `score` is out of. Defaults to `1`.
|
|
111
|
+
*
|
|
112
|
+
* Real graders rarely work in [0,1]: rubrics come back out of 100, out of 9
|
|
113
|
+
* (CEFR/IELTS bands), or out of a points total that differs between
|
|
114
|
+
* criteria. Before this field the only way to use {@link gradeFromRubric}
|
|
115
|
+
* was to normalise by hand first — and a grader that skipped that step got
|
|
116
|
+
* its scores REJECTED, which pushed integrators back to letting the model
|
|
117
|
+
* compute the weighted total itself. That is precisely the arithmetic this
|
|
118
|
+
* SDK exists to take away from a language model.
|
|
119
|
+
*
|
|
120
|
+
* Set it per criterion, so a rubric mixing a /100 criterion with a /9 band
|
|
121
|
+
* score still composes. `gradeFromRubric` normalises `score / maxScore`
|
|
122
|
+
* before weighting; the resulting ratio must land in [0,1].
|
|
123
|
+
*/
|
|
124
|
+
maxScore?: number;
|
|
125
|
+
/** Ordinal verdict when the rubric is banded rather than numeric (e.g. `"B1"`). */
|
|
126
|
+
band?: string;
|
|
127
|
+
/** The grader's comment for this criterion, addressed to the learner. */
|
|
128
|
+
comment?: string;
|
|
129
|
+
/**
|
|
130
|
+
* Weight actually applied, echoed from the rubric so the total is checkable.
|
|
131
|
+
* Defaults to `1`. Zero or more: `gradeFromRubric` refuses a negative weight
|
|
132
|
+
* rather than let it carry the total outside [0,1].
|
|
133
|
+
*/
|
|
134
|
+
weight?: number;
|
|
135
|
+
/** Not applicable to this submission (e.g. interaction on a monologue task). */
|
|
136
|
+
notApplicable?: boolean;
|
|
137
|
+
}
|
|
138
|
+
/** A suggested correction anchored in the learner's own text. */
|
|
139
|
+
interface InlineCorrection {
|
|
140
|
+
/** Verbatim span from the submission. */
|
|
141
|
+
original: string;
|
|
142
|
+
/** Suggested replacement. */
|
|
143
|
+
corrected: string;
|
|
144
|
+
explanation?: string;
|
|
145
|
+
/** Character offsets into the submitted text, so a UI can anchor it inline. */
|
|
146
|
+
range?: {
|
|
147
|
+
start: number;
|
|
148
|
+
end: number;
|
|
149
|
+
};
|
|
150
|
+
/** Error-type tag (`"article"`, `"tense"`, `"register"`). */
|
|
151
|
+
category?: string;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* A grade that came back from an asynchronous grader. `score` is scaled
|
|
155
|
+
* [0,1] against `maxScore`, matching every other score in the SDK. A grader
|
|
156
|
+
* that works in points does not have to normalise by hand: declare
|
|
157
|
+
* `maxScore` on each {@link CriterionScore} and let `gradeFromRubric` do the
|
|
158
|
+
* arithmetic, which is the whole point of handing it the judgements.
|
|
159
|
+
*
|
|
160
|
+
* `maxScore` is a positive, finite number and `score` a finite number from 0
|
|
161
|
+
* to it. `outcomeFromGrade` refuses a record that is not, rather than mirror
|
|
162
|
+
* numbers nobody can defend onto an outcome.
|
|
163
|
+
*/
|
|
164
|
+
interface GradeRecord {
|
|
165
|
+
score: number;
|
|
166
|
+
maxScore: number;
|
|
167
|
+
passed: boolean;
|
|
168
|
+
/** Narrative feedback addressed to the learner. */
|
|
169
|
+
feedback: string | null;
|
|
170
|
+
/** Per-criterion breakdown, when the grader worked against a rubric. */
|
|
171
|
+
criteria?: CriterionScore[];
|
|
172
|
+
/**
|
|
173
|
+
* Per-item marks carried by a grade — for example the per-word marks of a
|
|
174
|
+
* read-aloud grade. `outcomeFromGrade` does not mirror them onto the
|
|
175
|
+
* outcome; read `outcome.grade.details`.
|
|
176
|
+
*/
|
|
177
|
+
details?: ScoringDetail[];
|
|
178
|
+
/** Corrections anchored in the learner's text. */
|
|
179
|
+
corrections?: InlineCorrection[];
|
|
180
|
+
/** Supporting observations the grader cited. */
|
|
181
|
+
evidence?: string[];
|
|
182
|
+
/** How the grader reached this verdict. */
|
|
183
|
+
rationale?: string;
|
|
184
|
+
/** The grader's own confidence — a signal for routing to human review. */
|
|
185
|
+
confidence?: 'high' | 'medium' | 'low';
|
|
186
|
+
/** The grader is unsure or the submission is atypical; route to a human. */
|
|
187
|
+
requiresHumanReview?: boolean;
|
|
188
|
+
grader?: Grader;
|
|
189
|
+
usage?: GraderUsage;
|
|
190
|
+
/** ISO 8601 timestamp of when the grade was produced. */
|
|
191
|
+
gradedAt?: string;
|
|
192
|
+
}
|
|
193
|
+
//#endregion
|
|
194
|
+
//#region src/types/xapi.d.ts
|
|
195
|
+
/**
|
|
196
|
+
* An xAPI Activity object — the thing a statement is about.
|
|
197
|
+
* This SDK only builds Activity-type objects (xAPI 1.0.3 §4.1.4.1).
|
|
198
|
+
*/
|
|
199
|
+
interface XAPIObject {
|
|
200
|
+
objectType: 'Activity';
|
|
201
|
+
/** IRI uniquely identifying the activity. */
|
|
202
|
+
id: string;
|
|
203
|
+
definition?: {
|
|
204
|
+
/** Language-map of human-readable names for the activity. */
|
|
205
|
+
name?: Record<string, string>;
|
|
206
|
+
/** Language-map of human-readable descriptions. */
|
|
207
|
+
description?: Record<string, string>;
|
|
208
|
+
/** IRI identifying the activity type. */
|
|
209
|
+
type?: string;
|
|
210
|
+
/** xAPI 1.0.3 cmi.interaction type (e.g. `choice`, `fill-in`, `long-fill-in`). */
|
|
211
|
+
interactionType?: string;
|
|
212
|
+
/** Patterns describing the correct response(s), per the xAPI CRP format. */
|
|
213
|
+
correctResponsesPattern?: string[];
|
|
214
|
+
/** For `choice`-family interactions: the available components. */
|
|
215
|
+
choices?: Array<{
|
|
216
|
+
id: string;
|
|
217
|
+
description?: Record<string, string>;
|
|
218
|
+
}>;
|
|
219
|
+
extensions?: Record<string, unknown>;
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* An xAPI Agent (person or system) that performed the statement's verb.
|
|
224
|
+
* Exactly one of `mbox` or `account` must be present.
|
|
225
|
+
*/
|
|
226
|
+
interface XAPIActor {
|
|
227
|
+
objectType: 'Agent';
|
|
228
|
+
/** Display name for the actor. */
|
|
229
|
+
name?: string;
|
|
230
|
+
/** Mailto IRI uniquely identifying the actor (e.g. `mailto:user@example.com`). */
|
|
231
|
+
mbox?: string;
|
|
232
|
+
/** Account-based actor identifier. */
|
|
233
|
+
account?: {
|
|
234
|
+
/** IRI of the home page of the account's service provider. */
|
|
235
|
+
homePage: string;
|
|
236
|
+
/** The actor's unique name on that service. */
|
|
237
|
+
name: string;
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
/** An xAPI Verb — the action performed in the statement. */
|
|
241
|
+
interface XAPIVerbObject {
|
|
242
|
+
/** IRI uniquely identifying this verb (e.g. from adlnet.gov/expapi/verbs). */
|
|
243
|
+
id: string;
|
|
244
|
+
/** Language-map of human-readable verb labels. */
|
|
245
|
+
display: Record<string, string>;
|
|
246
|
+
}
|
|
247
|
+
/** Score sub-object within an xAPI Result. */
|
|
248
|
+
interface XAPIScore {
|
|
249
|
+
/** Normalized score in the range [0, 1]. */
|
|
250
|
+
scaled: number;
|
|
251
|
+
/** Raw score in the instrument's native scale. */
|
|
252
|
+
raw?: number;
|
|
253
|
+
/** Minimum possible raw score. */
|
|
254
|
+
min?: number;
|
|
255
|
+
/** Maximum possible raw score. */
|
|
256
|
+
max?: number;
|
|
257
|
+
}
|
|
258
|
+
/** Result information attached to an xAPI Statement. */
|
|
259
|
+
interface XAPIResult {
|
|
260
|
+
score?: XAPIScore;
|
|
261
|
+
/** Whether the actor's performance was successful. */
|
|
262
|
+
success?: boolean;
|
|
263
|
+
/** Whether the activity was completed. */
|
|
264
|
+
completion?: boolean;
|
|
265
|
+
/** ISO 8601 duration string representing time spent (e.g. `PT5M30S`). */
|
|
266
|
+
duration?: string;
|
|
267
|
+
/** A string representation of the learner's response. */
|
|
268
|
+
response?: string;
|
|
269
|
+
extensions?: Record<string, unknown>;
|
|
270
|
+
}
|
|
271
|
+
/** Related-activity references attached to a statement's context. */
|
|
272
|
+
interface XAPIContextActivities {
|
|
273
|
+
/** Activities this statement's activity is directly part of (e.g. the exam section). */
|
|
274
|
+
parent?: XAPIObject[];
|
|
275
|
+
/** Broader groupings (e.g. the course). */
|
|
276
|
+
grouping?: XAPIObject[];
|
|
277
|
+
/** Categorisations (e.g. a profile IRI). */
|
|
278
|
+
category?: XAPIObject[];
|
|
279
|
+
/** Other contextually related activities. */
|
|
280
|
+
other?: XAPIObject[];
|
|
281
|
+
}
|
|
282
|
+
/** Context information attached to an xAPI Statement. */
|
|
283
|
+
interface XAPIContext {
|
|
284
|
+
/** Name of the software used to record the statement. */
|
|
285
|
+
platform?: string;
|
|
286
|
+
/** BCP 47 language tag for the activity content. */
|
|
287
|
+
language?: string;
|
|
288
|
+
/** Related activities (parent/grouping/category/other). */
|
|
289
|
+
contextActivities?: XAPIContextActivities;
|
|
290
|
+
extensions?: Record<string, unknown>;
|
|
291
|
+
}
|
|
292
|
+
/** A complete xAPI 1.0.3 Statement. */
|
|
293
|
+
interface XAPIStatement {
|
|
294
|
+
/** UUID v4 uniquely identifying this statement. */
|
|
295
|
+
id: string;
|
|
296
|
+
actor: XAPIActor;
|
|
297
|
+
verb: XAPIVerbObject;
|
|
298
|
+
object: XAPIObject;
|
|
299
|
+
result?: XAPIResult;
|
|
300
|
+
context?: XAPIContext;
|
|
301
|
+
/** ISO 8601 timestamp of when the statement occurred. */
|
|
302
|
+
timestamp: string;
|
|
303
|
+
version: '1.0.3';
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Configuration passed to `useXAPI` to connect to an LRS endpoint.
|
|
307
|
+
* The `auth` field supports both HTTP Basic and Bearer token authentication.
|
|
308
|
+
*/
|
|
309
|
+
interface XAPIConfig {
|
|
310
|
+
/** Full URL of the LRS statements endpoint. */
|
|
311
|
+
endpoint: string;
|
|
312
|
+
/** Authentication credentials for the LRS. */
|
|
313
|
+
auth: {
|
|
314
|
+
type: 'basic';
|
|
315
|
+
username: string;
|
|
316
|
+
password: string;
|
|
317
|
+
} | {
|
|
318
|
+
type: 'bearer';
|
|
319
|
+
token: string;
|
|
320
|
+
};
|
|
321
|
+
/**
|
|
322
|
+
* Replaces the SDK's placeholder object id (`urn:learning-kit:activity:*`)
|
|
323
|
+
* before a statement is sent.
|
|
324
|
+
*
|
|
325
|
+
* - `string` — the IRI for THE activity this hook instance serves. A hook
|
|
326
|
+
* instance is per-activity by contract; with several activities sharing
|
|
327
|
+
* one hook, a plain string would collapse their statements onto one IRI —
|
|
328
|
+
* use the function form instead.
|
|
329
|
+
* - `(sdkObjectId) => string` — maps each placeholder id (which embeds the
|
|
330
|
+
* activity's `data.id`) to its deployment IRI, for pages that send many
|
|
331
|
+
* activities through one hook.
|
|
332
|
+
*
|
|
333
|
+
* Object ids the consumer already rewrote (non-`urn:learning-kit:` ids) are
|
|
334
|
+
* never touched.
|
|
335
|
+
*/
|
|
336
|
+
activityId: string | ((sdkObjectId: string) => string);
|
|
337
|
+
actor: XAPIActor;
|
|
338
|
+
/** Called after all retry attempts are exhausted. */
|
|
339
|
+
onError?: (err: XAPIError) => void;
|
|
340
|
+
}
|
|
341
|
+
/** Error payload delivered to `XAPIConfig.onError` after retries are exhausted. */
|
|
342
|
+
interface XAPIError {
|
|
343
|
+
/** The statement that failed to send. */
|
|
344
|
+
statement: XAPIStatement;
|
|
345
|
+
/** The 1-based attempt number on which the final failure occurred. */
|
|
346
|
+
attempt: number;
|
|
347
|
+
/** HTTP status code from the LRS, if a response was received. */
|
|
348
|
+
statusCode?: number;
|
|
349
|
+
/** Human-readable error description. */
|
|
350
|
+
message: string;
|
|
351
|
+
}
|
|
352
|
+
//#endregion
|
|
353
|
+
//#region src/types/activity.d.ts
|
|
354
|
+
/**
|
|
355
|
+
* Maps each ActivityType string to its corresponding data shape.
|
|
356
|
+
*
|
|
357
|
+
* Open for extension via TypeScript module augmentation: a consumer that
|
|
358
|
+
* registers a custom activity type (`registerActivityType`) can augment this
|
|
359
|
+
* interface so `ActivityType`, `ActivityData`, `validateActivity`, and
|
|
360
|
+
* `evaluate` pick the new type up without an SDK release:
|
|
361
|
+
*
|
|
362
|
+
* ```ts
|
|
363
|
+
* declare module '@intellectif/lk-core' {
|
|
364
|
+
* interface ActivityDataMap { 'my-type': MyTypeData }
|
|
365
|
+
* interface LearnerResponseMap { 'my-type': MyTypeLearnerResponse }
|
|
366
|
+
* }
|
|
367
|
+
* ```
|
|
368
|
+
*/
|
|
369
|
+
interface ActivityDataMap {
|
|
370
|
+
'multiple-choice': MultipleChoiceData;
|
|
371
|
+
'fill-in-the-blanks': FillInTheBlanksData;
|
|
372
|
+
'written-response': WrittenResponseData;
|
|
373
|
+
'gap-select': GapSelectData;
|
|
374
|
+
dictation: DictationData;
|
|
375
|
+
'read-aloud': ReadAloudData;
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* The set of activity types supported by learning-kit. Derived from
|
|
379
|
+
* {@link ActivityDataMap}, so module augmentation widens it automatically.
|
|
380
|
+
*/
|
|
381
|
+
type ActivityType = keyof ActivityDataMap;
|
|
382
|
+
/** Union of all valid activity data shapes. */
|
|
383
|
+
type ActivityData = ActivityDataMap[ActivityType];
|
|
384
|
+
/** A text track for an `audio` or `video` recording. */
|
|
385
|
+
interface MediaTrack {
|
|
386
|
+
kind: 'captions' | 'subtitles';
|
|
387
|
+
/** WebVTT URL. A player may fetch it through a loader the host supplies, with the host's credentials. */
|
|
388
|
+
src: string;
|
|
389
|
+
/** BCP 47 language tag of the text (`en`, `es`, `pt-BR`). */
|
|
390
|
+
srclang: string;
|
|
391
|
+
/** Shown in the captions menu. Non-empty, at most 60 characters. */
|
|
392
|
+
label: string;
|
|
393
|
+
/** Selected when the learner has chosen no language yet. At most one track may be the default. */
|
|
394
|
+
default?: boolean;
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Optional media (image/audio/video) shown above a question or passage.
|
|
398
|
+
* URL-only: hosting/delivery is the consuming application's responsibility.
|
|
399
|
+
*/
|
|
400
|
+
interface ActivityMedia {
|
|
401
|
+
/** The kind of media; selects the rendered element. `embed` → sandboxed iframe. */
|
|
402
|
+
type: 'image' | 'audio' | 'video' | 'embed';
|
|
403
|
+
/**
|
|
404
|
+
* Source URL. For `embed` this MUST be the provider's embeddable URL
|
|
405
|
+
* (e.g. `https://www.youtube.com/embed/<id>`), not the watch page.
|
|
406
|
+
*/
|
|
407
|
+
url: string;
|
|
408
|
+
/** Alternative text. Required for `image` and `embed`; optional label for audio/video. */
|
|
409
|
+
alt?: string;
|
|
410
|
+
/** Optional WebVTT captions track URL for `audio`/`video`. */
|
|
411
|
+
captionsUrl?: string;
|
|
412
|
+
/**
|
|
413
|
+
* Text tracks for `audio`/`video`, one per language: what `captionsUrl`
|
|
414
|
+
* cannot say — a language, a label, more than one. When present, a player
|
|
415
|
+
* that reads it ignores `captionsUrl`. At most 12, one `default`, and no two
|
|
416
|
+
* sharing a `kind` and a `srclang`.
|
|
417
|
+
*/
|
|
418
|
+
tracks?: MediaTrack[];
|
|
419
|
+
/** An image shown before the first play. `video` only. */
|
|
420
|
+
poster?: string;
|
|
421
|
+
/**
|
|
422
|
+
* How the recording may be played. `audio` only — see
|
|
423
|
+
* {@link MediaPlaybackPolicy}. Absent means today's behaviour exactly: the
|
|
424
|
+
* browser's own control bar, unlimited plays, free seeking and free speed.
|
|
425
|
+
*/
|
|
426
|
+
playback?: MediaPlaybackPolicy;
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* A hint to the browser's own control bar, emitted as a `controlsList` token.
|
|
430
|
+
*
|
|
431
|
+
* Advisory, and engine-dependent. `hide-download` removes a menu item where
|
|
432
|
+
* `controlsList` is implemented; it never prevents a download, because the URL
|
|
433
|
+
* is in the page and the bytes are in the network panel. The real control is a
|
|
434
|
+
* short-lived signed URL, which belongs to the consuming application.
|
|
435
|
+
*/
|
|
436
|
+
type NativeControlHint = 'hide-download' | 'hide-rate';
|
|
437
|
+
/**
|
|
438
|
+
* How an audio recording may be played.
|
|
439
|
+
*
|
|
440
|
+
* Every field is optional and every default reproduces the SDK's pre-0.8.0
|
|
441
|
+
* behaviour, so adding this key is the only thing that changes anything.
|
|
442
|
+
*
|
|
443
|
+
* Setting any *enforcement* field (`maxPlays`, `seek: 'none'`, `rate: 'fixed'`)
|
|
444
|
+
* resolves `controls` to `'minimal'`: the browser's bar cannot express a spent
|
|
445
|
+
* budget — its play button stays enabled — so the SDK renders its own
|
|
446
|
+
* transport rather than leave a control that looks operable and does nothing.
|
|
447
|
+
*/
|
|
448
|
+
interface MediaPlaybackPolicy {
|
|
449
|
+
/**
|
|
450
|
+
* `native` renders the browser's control bar. `minimal` renders the SDK
|
|
451
|
+
* transport. Resolved automatically; set it explicitly only to keep the
|
|
452
|
+
* native bar on a recording that has nothing to enforce.
|
|
453
|
+
*/
|
|
454
|
+
controls?: 'native' | 'minimal';
|
|
455
|
+
/**
|
|
456
|
+
* How many times the recording may be STARTED (1–20). A play is consumed
|
|
457
|
+
* when playback begins from anywhere other than where it last stopped, so
|
|
458
|
+
* pausing, resuming, and paging between the questions of one listening group
|
|
459
|
+
* are free.
|
|
460
|
+
*
|
|
461
|
+
* The SDK refuses the play; it does not remember it. The count is durable
|
|
462
|
+
* only if the consuming application persists it — see `mediaBudget` on
|
|
463
|
+
* `<ActivitySequence>` in `@intellectif/lk-react`.
|
|
464
|
+
*/
|
|
465
|
+
maxPlays?: number;
|
|
466
|
+
/** `none` renders no scrubber and reverts an out-of-band seek. Resolves to `none` under a budget. */
|
|
467
|
+
seek?: 'allow' | 'none';
|
|
468
|
+
/** `fixed` renders no speed control and snaps `playbackRate` back to 1. */
|
|
469
|
+
rate?: 'allow' | 'fixed';
|
|
470
|
+
/** Advisory hints to the native bar. See {@link NativeControlHint}. */
|
|
471
|
+
nativeControlHints?: NativeControlHint[];
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Optional authored "overall feedback" shown after submission, chosen by
|
|
475
|
+
* whether the learner passed (h5p-style overall feedback). Distinct from
|
|
476
|
+
* per-option feedback; either field may be omitted.
|
|
477
|
+
*/
|
|
478
|
+
interface ActivityFeedback {
|
|
479
|
+
/** Shown when the learner passes (score ≥ pass threshold). */
|
|
480
|
+
correct?: string;
|
|
481
|
+
/** Shown when the learner does not pass. */
|
|
482
|
+
incorrect?: string;
|
|
483
|
+
}
|
|
484
|
+
/** A single selectable option within a Multiple Choice activity. */
|
|
485
|
+
/**
|
|
486
|
+
* A picture or a recording carried by ONE multiple-choice option — the
|
|
487
|
+
* A1/A2 picture-choice item, and the minimal-pair listening item.
|
|
488
|
+
*
|
|
489
|
+
* Narrower than {@link ActivityMedia}, which sits above the question, and
|
|
490
|
+
* deliberately so. An option is a click target, and two of the activity-level
|
|
491
|
+
* kinds cannot be one:
|
|
492
|
+
*
|
|
493
|
+
* - **`embed` is refused.** A provider iframe swallows pointer events, so
|
|
494
|
+
* clicking the option would play the video instead of selecting the answer.
|
|
495
|
+
* The learner could not choose it at all.
|
|
496
|
+
* - **`video` is refused** for the same reason one step down: a native control
|
|
497
|
+
* bar inside the option's label eats the clicks meant for the radio.
|
|
498
|
+
* - **No `playback` policy.** `maxPlays` binds per slot through a
|
|
499
|
+
* `MediaBudgetBinding`, and four recordings in one question raise a question
|
|
500
|
+
* nothing has answered yet — whether the budget belongs to the option or to
|
|
501
|
+
* the item. Until something asks, an option plays through the browser's own
|
|
502
|
+
* bar and counts nothing.
|
|
503
|
+
*
|
|
504
|
+
* `alt` follows the same rule {@link ActivityMedia} uses, so there is only one
|
|
505
|
+
* rule to learn: required and non-empty for an image, optional for audio, where
|
|
506
|
+
* it becomes the player's accessible label.
|
|
507
|
+
*
|
|
508
|
+
* **On an image option, `alt` is part of the item.** It joins the option's
|
|
509
|
+
* `text` in the radio's accessible name, so on "which picture shows a cat?" an
|
|
510
|
+
* `alt` of "a cat" hands a screen-reader user the answer that a sighted learner
|
|
511
|
+
* has to work out. That is a property of picture-choice items, not something a
|
|
512
|
+
* schema can fix — the SDK requires `alt` so an option is never SILENTLY
|
|
513
|
+
* inaccessible, and leaves the wording, and the item's validity, to the author.
|
|
514
|
+
*/
|
|
515
|
+
interface MultipleChoiceOptionMedia {
|
|
516
|
+
/** `image` for a picture-choice option, `audio` for a listening option. */
|
|
517
|
+
type: 'image' | 'audio';
|
|
518
|
+
/** Address of the file. Same scheme allow-list as {@link ActivityMedia}. */
|
|
519
|
+
url: string;
|
|
520
|
+
/** Required and non-empty for `image`; an optional accessible label for `audio`. */
|
|
521
|
+
alt?: string;
|
|
522
|
+
/** WebVTT captions for an audio option, rendered as a `<track>`. */
|
|
523
|
+
captionsUrl?: string;
|
|
524
|
+
}
|
|
525
|
+
interface MultipleChoiceOption {
|
|
526
|
+
/** Unique identifier for this option within the activity. */
|
|
527
|
+
id: string;
|
|
528
|
+
/**
|
|
529
|
+
* Display text shown to the learner.
|
|
530
|
+
*
|
|
531
|
+
* Required even when the option carries {@link media}: it names the option in
|
|
532
|
+
* the radio's accessible name and in the xAPI statement, and it is what the
|
|
533
|
+
* learner sees if a picture fails to load. For a pure picture-choice option,
|
|
534
|
+
* a neutral label ("Picture 1") keeps the naming out of the answer.
|
|
535
|
+
*/
|
|
536
|
+
text: string;
|
|
537
|
+
/** Whether this option is part of the correct answer. */
|
|
538
|
+
isCorrect: boolean;
|
|
539
|
+
/** Optional per-option feedback shown after submission. */
|
|
540
|
+
feedback?: string;
|
|
541
|
+
/** An optional picture or recording shown as part of this option. */
|
|
542
|
+
media?: MultipleChoiceOptionMedia;
|
|
543
|
+
}
|
|
544
|
+
/** Data contract for a Multiple Choice activity. */
|
|
545
|
+
interface MultipleChoiceData {
|
|
546
|
+
schemaVersion: '1.0';
|
|
547
|
+
type: 'multiple-choice';
|
|
548
|
+
/** Unique identifier for this activity. */
|
|
549
|
+
id: string;
|
|
550
|
+
/** Human-readable title used in xAPI statements and error boundaries. */
|
|
551
|
+
title: string;
|
|
552
|
+
/** The question stem presented to the learner, as plain text. */
|
|
553
|
+
question: string;
|
|
554
|
+
/**
|
|
555
|
+
* Optional sanitised rich-HTML rendering of the question, parallel to
|
|
556
|
+
* `WrittenResponseData.promptHtml`. `<MultipleChoice>` renders it in place
|
|
557
|
+
* of `question` when — and only when — a `sanitizeHtml` function is
|
|
558
|
+
* supplied; without one the plain `question` is rendered and escaped.
|
|
559
|
+
* Sanitisation is the application's responsibility: the SDK ships none.
|
|
560
|
+
*/
|
|
561
|
+
questionHtml?: string;
|
|
562
|
+
/** `single` allows one selection; `multi` allows multiple. */
|
|
563
|
+
mode: 'single' | 'multi';
|
|
564
|
+
/** Ordered list of answer options. */
|
|
565
|
+
options: MultipleChoiceOption[];
|
|
566
|
+
/** Scoring algorithm applied when the learner submits. */
|
|
567
|
+
scoringStrategy: 'all-or-nothing' | 'partial';
|
|
568
|
+
/** Optional media shown above the question. */
|
|
569
|
+
media?: ActivityMedia;
|
|
570
|
+
/** Optional authored overall feedback shown after submission. */
|
|
571
|
+
feedback?: ActivityFeedback;
|
|
572
|
+
/** Minimum scaled score [0–1] required to pass. Defaults to {@link DEFAULT_PASS_THRESHOLD} (0.7) when absent. */
|
|
573
|
+
passThreshold?: number;
|
|
574
|
+
/**
|
|
575
|
+
* When true, option order is shuffled deterministically.
|
|
576
|
+
*
|
|
577
|
+
* The order is derived from the renderer's `shuffleSeed` when one is given —
|
|
578
|
+
* pass the attempt id, and a review render reproduces exactly the order the
|
|
579
|
+
* learner sat. Without a seed the order is stable for the life of the mount
|
|
580
|
+
* only, and is NOT reproducible afterwards, so never grade or appeal against
|
|
581
|
+
* a remembered position.
|
|
582
|
+
*/
|
|
583
|
+
shuffle?: boolean;
|
|
584
|
+
/** BCP 47 language tag for the activity content. */
|
|
585
|
+
locale?: string;
|
|
586
|
+
/** IRI references to learning objectives addressed by this activity. */
|
|
587
|
+
learningObjectives?: string[];
|
|
588
|
+
/** Subjective difficulty on a 1–5 scale. */
|
|
589
|
+
difficultyLevel?: 1 | 2 | 3 | 4 | 5;
|
|
590
|
+
/** What an author allows AI to do for this item: see {@link ActivityAiPermissions}. */
|
|
591
|
+
ai?: ActivityAiPermissions;
|
|
592
|
+
}
|
|
593
|
+
/** Configuration for a single fill-in-the-blank slot. */
|
|
594
|
+
interface BlankConfig {
|
|
595
|
+
/** Unique identifier matching the `{{blank_id}}` placeholder in the passage. */
|
|
596
|
+
id: string;
|
|
597
|
+
/** List of strings accepted as correct answers for this blank. */
|
|
598
|
+
acceptedAnswers: string[];
|
|
599
|
+
/**
|
|
600
|
+
* Whether answer matching is case-sensitive. Defaults to false.
|
|
601
|
+
*
|
|
602
|
+
* @deprecated Since lk-core 1.3: write `match: { caseSensitive }`, which
|
|
603
|
+
* means the same. Still read throughout 1.x; a 2.0 may remove it.
|
|
604
|
+
*/
|
|
605
|
+
caseSensitive?: boolean;
|
|
606
|
+
/**
|
|
607
|
+
* Whether leading/trailing whitespace is stripped before matching. Defaults to true.
|
|
608
|
+
*
|
|
609
|
+
* @deprecated Since lk-core 1.3: write `match: { trim }`, which means the
|
|
610
|
+
* same. Still read throughout 1.x; a 2.0 may remove it.
|
|
611
|
+
*/
|
|
612
|
+
trimWhitespace?: boolean;
|
|
613
|
+
/**
|
|
614
|
+
* Optional matching-tolerance policy for this blank (Unicode normalization,
|
|
615
|
+
* diacritic folding, whitespace collapse, punctuation tolerance, typo
|
|
616
|
+
* tolerance). Every tolerance is opt-in; when absent, matching reproduces
|
|
617
|
+
* the v1 trim + case-fold semantics exactly. Fields set here take
|
|
618
|
+
* precedence over the legacy `caseSensitive` / `trimWhitespace` flags.
|
|
619
|
+
*/
|
|
620
|
+
match?: TextMatchPolicy;
|
|
621
|
+
/** Optional hint text revealed on learner request. */
|
|
622
|
+
hint?: string;
|
|
623
|
+
/** Optional feedback shown inline next to this blank after submission. */
|
|
624
|
+
feedback?: string;
|
|
625
|
+
}
|
|
626
|
+
/** Data contract for a Fill-in-the-Blanks activity. */
|
|
627
|
+
/** One selectable option in a Gap Select activity. */
|
|
628
|
+
interface GapSelectChoice {
|
|
629
|
+
/** Unique identifier within the choice set the gap resolves to. */
|
|
630
|
+
id: string;
|
|
631
|
+
/** The text the learner reads in the selector. */
|
|
632
|
+
text: string;
|
|
633
|
+
}
|
|
634
|
+
/**
|
|
635
|
+
* A named set of choices several gaps can draw from — a word bank.
|
|
636
|
+
*
|
|
637
|
+
* A bank is what makes distractors possible: three gaps sharing a bank of five
|
|
638
|
+
* choices means every selector offers all five, and two of them answer no gap
|
|
639
|
+
* at all. That is the difference between a reading-comprehension item and a
|
|
640
|
+
* sequence of three-way guesses, so it is modelled once here rather than
|
|
641
|
+
* retrofitted after per-gap lists ship.
|
|
642
|
+
*/
|
|
643
|
+
interface GapSelectBank {
|
|
644
|
+
/** Unique identifier referenced by {@link GapSelectGap.bankId}. */
|
|
645
|
+
id: string;
|
|
646
|
+
/** The choices every gap on this bank offers. */
|
|
647
|
+
choices: GapSelectChoice[];
|
|
648
|
+
}
|
|
649
|
+
/**
|
|
650
|
+
* One gap in the passage, matching a `{{gap_id}}` placeholder.
|
|
651
|
+
*
|
|
652
|
+
* Its choices come from exactly one place: `choices` for a list of its own, or
|
|
653
|
+
* `bankId` for a shared word bank. Both, or neither, is an authoring error the
|
|
654
|
+
* schema rejects — with two sources a reader cannot tell which list the learner
|
|
655
|
+
* is offered, and the answer key means nothing without one.
|
|
656
|
+
*/
|
|
657
|
+
interface GapSelectGap {
|
|
658
|
+
/** Unique identifier matching the `{{gap_id}}` placeholder in the passage. */
|
|
659
|
+
id: string;
|
|
660
|
+
/** This gap's own choices. Mutually exclusive with {@link bankId}. */
|
|
661
|
+
choices?: GapSelectChoice[];
|
|
662
|
+
/** The {@link GapSelectBank} this gap draws from. Mutually exclusive with {@link choices}. */
|
|
663
|
+
bankId?: string;
|
|
664
|
+
/** The `id` of the one choice that is correct. Must exist in the resolved choice set. */
|
|
665
|
+
correctChoiceId: string;
|
|
666
|
+
/** Optional feedback shown inline next to this gap after submission. */
|
|
667
|
+
feedback?: string;
|
|
668
|
+
}
|
|
669
|
+
/**
|
|
670
|
+
* Data contract for a Gap Select activity: a passage whose gaps the learner
|
|
671
|
+
* fills by choosing from a list rather than typing.
|
|
672
|
+
*
|
|
673
|
+
* It is deliberately **not** a mode of {@link FillInTheBlanksData}, though the
|
|
674
|
+
* passage and its `{{id}}` placeholders are authored the same way. A learner
|
|
675
|
+
* picking from a selector cannot mistype, so the whole `TextMatchPolicy`
|
|
676
|
+
* surface — diacritic folding, typo tolerance, locale-aware case — is not just
|
|
677
|
+
* unused but misleading. Scoring is identity comparison of a choice id.
|
|
678
|
+
* Redaction inverts too: in Fill-in-the-Blanks the candidate answers ARE the
|
|
679
|
+
* key, while here the learner must be shown every choice and only
|
|
680
|
+
* `correctChoiceId` is withheld.
|
|
681
|
+
*/
|
|
682
|
+
interface GapSelectData {
|
|
683
|
+
schemaVersion: '1.0';
|
|
684
|
+
type: 'gap-select';
|
|
685
|
+
/** Unique identifier for this activity. */
|
|
686
|
+
id: string;
|
|
687
|
+
/** Human-readable title used in xAPI statements and error boundaries. */
|
|
688
|
+
title: string;
|
|
689
|
+
/** Passage text containing `{{gap_id}}` placeholders. */
|
|
690
|
+
passage: string;
|
|
691
|
+
/**
|
|
692
|
+
* Optional sanitised rich-HTML rendering of the passage. Carried and redacted
|
|
693
|
+
* as learner-visible content; not rendered by the SDK, for the same reason
|
|
694
|
+
* `FillInTheBlanksData.passageHtml` is not — slicing sanitised HTML at the
|
|
695
|
+
* placeholders to host the selectors is both lossy and unsafe.
|
|
696
|
+
*/
|
|
697
|
+
passageHtml?: string;
|
|
698
|
+
/** Every gap in the passage, one per distinct `{{id}}` placeholder. */
|
|
699
|
+
gaps: GapSelectGap[];
|
|
700
|
+
/** Shared word banks. Only needed by gaps that set `bankId`. */
|
|
701
|
+
banks?: GapSelectBank[];
|
|
702
|
+
/** Scoring algorithm applied when the learner submits. */
|
|
703
|
+
scoringStrategy: 'all-or-nothing' | 'partial';
|
|
704
|
+
/**
|
|
705
|
+
* How the choices are presented. Only `'dropdown'` exists today, and it is
|
|
706
|
+
* the default.
|
|
707
|
+
*
|
|
708
|
+
* The field is here rather than assumed because a drag-and-drop presentation
|
|
709
|
+
* is the obvious next request, and WCAG 2.5.7 requires that a drag interface
|
|
710
|
+
* always keep a non-drag path — so the choice has to be expressible in the
|
|
711
|
+
* content, not decided by a component prop. `'drag'` is NOT accepted yet:
|
|
712
|
+
* shipping a value nothing renders would freeze an API this repository has
|
|
713
|
+
* not validated. Widening the union later is additive.
|
|
714
|
+
*/
|
|
715
|
+
presentation?: 'dropdown';
|
|
716
|
+
/**
|
|
717
|
+
* Whether the SDK shuffles each selector's choices. Off by default.
|
|
718
|
+
*
|
|
719
|
+
* Like `MultipleChoiceData.shuffle`, this needs a seed to be reproducible at
|
|
720
|
+
* a remark — `<ActivitySequence>` refuses to shuffle without one outside
|
|
721
|
+
* `practice` mode.
|
|
722
|
+
*/
|
|
723
|
+
shuffleChoices?: boolean;
|
|
724
|
+
/** Optional media shown above the passage. */
|
|
725
|
+
media?: ActivityMedia;
|
|
726
|
+
/** Optional authored overall feedback shown after submission. */
|
|
727
|
+
feedback?: ActivityFeedback;
|
|
728
|
+
/** Minimum scaled score [0–1] required to pass. Defaults to {@link DEFAULT_PASS_THRESHOLD} (0.7) when absent. */
|
|
729
|
+
passThreshold?: number;
|
|
730
|
+
/** BCP 47 language tag for the activity content. */
|
|
731
|
+
locale?: string;
|
|
732
|
+
/** IRI references to learning objectives addressed by this activity. */
|
|
733
|
+
learningObjectives?: string[];
|
|
734
|
+
/** Subjective difficulty on a 1–5 scale. */
|
|
735
|
+
difficultyLevel?: 1 | 2 | 3 | 4 | 5;
|
|
736
|
+
/** What an author allows AI to do for this item: see {@link ActivityAiPermissions}. */
|
|
737
|
+
ai?: ActivityAiPermissions;
|
|
738
|
+
}
|
|
739
|
+
interface FillInTheBlanksData {
|
|
740
|
+
schemaVersion: '1.0';
|
|
741
|
+
type: 'fill-in-the-blanks';
|
|
742
|
+
/** Unique identifier for this activity. */
|
|
743
|
+
id: string;
|
|
744
|
+
/** Human-readable title used in xAPI statements and error boundaries. */
|
|
745
|
+
title: string;
|
|
746
|
+
/** Passage text containing `{{blank_id}}` placeholders. */
|
|
747
|
+
passage: string;
|
|
748
|
+
/**
|
|
749
|
+
* Optional sanitised rich-HTML rendering of the passage, parallel to
|
|
750
|
+
* `WrittenResponseData.promptHtml`. Carried and redacted as learner-visible
|
|
751
|
+
* content; not rendered by the SDK yet. Sanitisation is the application's.
|
|
752
|
+
*/
|
|
753
|
+
passageHtml?: string;
|
|
754
|
+
/** Configuration for each blank in the passage. */
|
|
755
|
+
blanks: BlankConfig[];
|
|
756
|
+
/** Scoring algorithm applied when the learner submits. */
|
|
757
|
+
scoringStrategy: 'all-or-nothing' | 'partial';
|
|
758
|
+
/** Optional media shown above the passage. */
|
|
759
|
+
media?: ActivityMedia;
|
|
760
|
+
/** Optional authored overall feedback shown after submission. */
|
|
761
|
+
feedback?: ActivityFeedback;
|
|
762
|
+
/** Minimum scaled score [0–1] required to pass. Defaults to {@link DEFAULT_PASS_THRESHOLD} (0.7) when absent. */
|
|
763
|
+
passThreshold?: number;
|
|
764
|
+
/** BCP 47 language tag for the activity content. */
|
|
765
|
+
locale?: string;
|
|
766
|
+
/** IRI references to learning objectives addressed by this activity. */
|
|
767
|
+
learningObjectives?: string[];
|
|
768
|
+
/** Subjective difficulty on a 1–5 scale. */
|
|
769
|
+
difficultyLevel?: 1 | 2 | 3 | 4 | 5;
|
|
770
|
+
/** What an author allows AI to do for this item: see {@link ActivityAiPermissions}. */
|
|
771
|
+
ai?: ActivityAiPermissions;
|
|
772
|
+
}
|
|
773
|
+
/** A single criterion within a written-response grading rubric. */
|
|
774
|
+
interface WrittenResponseRubricCriterion {
|
|
775
|
+
/** Criterion name (e.g. "Task achievement", "Grammar range"). */
|
|
776
|
+
name: string;
|
|
777
|
+
/** Optional longer description of what the criterion assesses. */
|
|
778
|
+
description?: string;
|
|
779
|
+
/** Non-negative weight of this criterion in the overall grade. */
|
|
780
|
+
weight: number;
|
|
781
|
+
}
|
|
782
|
+
/** Grading rubric attached to a written-response activity. */
|
|
783
|
+
interface WrittenResponseRubric {
|
|
784
|
+
/** Optional display label for the rubric as a whole. */
|
|
785
|
+
label?: string;
|
|
786
|
+
/** The criteria the response is graded against. Non-empty when present. */
|
|
787
|
+
criteria: WrittenResponseRubricCriterion[];
|
|
788
|
+
}
|
|
789
|
+
/**
|
|
790
|
+
* Data contract for a Written Response activity (free-text writing graded
|
|
791
|
+
* asynchronously — by an AI or human grader — after submission).
|
|
792
|
+
*
|
|
793
|
+
* Wire-format note: field names and casing are locked for
|
|
794
|
+
* byte-compatibility with consumer-stored JSONB rows. `feedback` and
|
|
795
|
+
* `passThreshold` are SDK-side optional additions for component parity —
|
|
796
|
+
* being optional, their absence keeps stored payloads
|
|
797
|
+
* byte-identical.
|
|
798
|
+
*/
|
|
799
|
+
interface WrittenResponseData {
|
|
800
|
+
schemaVersion: '1.0';
|
|
801
|
+
type: 'written-response';
|
|
802
|
+
/** Unique identifier for this activity. */
|
|
803
|
+
id: string;
|
|
804
|
+
/** Human-readable title used in xAPI statements and error boundaries. */
|
|
805
|
+
title: string;
|
|
806
|
+
/** The writing prompt, as plain text. */
|
|
807
|
+
prompt: string;
|
|
808
|
+
/**
|
|
809
|
+
* Optional sanitised rich-HTML sidecar of the prompt. `<WrittenResponse>`
|
|
810
|
+
* renders it in place of `prompt` when a `sanitizeHtml` function is
|
|
811
|
+
* supplied; without one the plain `prompt` is rendered and escaped.
|
|
812
|
+
*/
|
|
813
|
+
promptHtml?: string;
|
|
814
|
+
/** Minimum acceptable word count (≥ 0). */
|
|
815
|
+
minWords: number;
|
|
816
|
+
/** Maximum acceptable word count (≥ 1, and ≥ `minWords`). */
|
|
817
|
+
maxWords: number;
|
|
818
|
+
/** Optional grading rubric consumed by the asynchronous grader. */
|
|
819
|
+
rubric?: WrittenResponseRubric;
|
|
820
|
+
/** Optional target language/level for the response (e.g. `"en-A2"`, `"es-B1"`). */
|
|
821
|
+
languageTarget?: string;
|
|
822
|
+
/** Optional media shown above the prompt. */
|
|
823
|
+
media?: ActivityMedia;
|
|
824
|
+
/** Optional authored overall feedback (surfaced once the deferred grade exists). */
|
|
825
|
+
feedback?: ActivityFeedback;
|
|
826
|
+
/** Minimum scaled score [0–1] required to pass once graded. Defaults to {@link DEFAULT_PASS_THRESHOLD} (0.7) when absent. */
|
|
827
|
+
passThreshold?: number;
|
|
828
|
+
/** BCP 47 language tag for the activity content. */
|
|
829
|
+
locale?: string;
|
|
830
|
+
/** IRI references to learning objectives addressed by this activity. */
|
|
831
|
+
learningObjectives?: string[];
|
|
832
|
+
/** Subjective difficulty on a 1–5 scale. */
|
|
833
|
+
difficultyLevel?: 1 | 2 | 3 | 4 | 5;
|
|
834
|
+
/** What an author allows AI to do for this item: see {@link ActivityAiPermissions}. */
|
|
835
|
+
ai?: ActivityAiPermissions;
|
|
836
|
+
}
|
|
837
|
+
/**
|
|
838
|
+
* A second, slower recording of a dictation — a separate file (a text-to-speech
|
|
839
|
+
* render at reduced speed, or a slower human reading), not a `playbackRate`
|
|
840
|
+
* change. It carries no `captionsUrl` (a caption track of a dictation is the
|
|
841
|
+
* transcript on screen) and no `playback` policy of its own: it follows the
|
|
842
|
+
* policy on `media`, except the play budget, which it never has.
|
|
843
|
+
*/
|
|
844
|
+
interface DictationSlowMedia {
|
|
845
|
+
type: 'audio';
|
|
846
|
+
/** Source URL, under the same policy as {@link ActivityMedia.url}. */
|
|
847
|
+
url: string;
|
|
848
|
+
/** Accessible label — a description ("Recording, slow"), never a transcription. */
|
|
849
|
+
alt?: string;
|
|
850
|
+
}
|
|
851
|
+
/**
|
|
852
|
+
* A whole-word rewrite applied to BOTH the transcript and the attempt before
|
|
853
|
+
* they are compared. `from` and `to` are literal text: a rule is applied to
|
|
854
|
+
* every whole-word occurrence, exactly once, in the order the rules are listed,
|
|
855
|
+
* after spacing is collapsed and before punctuation is ignored — so a rule may
|
|
856
|
+
* name a symbol (`{ from: '&', to: 'and' }`) and a multi-word `from` matches
|
|
857
|
+
* across any whitespace.
|
|
858
|
+
*/
|
|
859
|
+
interface DictationEquivalence {
|
|
860
|
+
/** What to rewrite: at least one code point that survives case, quote and whitespace folding. */
|
|
861
|
+
from: string;
|
|
862
|
+
/** What it becomes: 1..200 code points, something of which survives normalisation. A rule cannot delete a word. */
|
|
863
|
+
to: string;
|
|
864
|
+
}
|
|
865
|
+
/** Grading tolerances for a dictation. Part of the answer key. */
|
|
866
|
+
interface DictationTolerance {
|
|
867
|
+
/** Whole-word rewrites, applied in listed order, each exactly once. */
|
|
868
|
+
equivalences?: DictationEquivalence[];
|
|
869
|
+
}
|
|
870
|
+
/**
|
|
871
|
+
* Data contract for a Dictation activity: the learner listens to a recording
|
|
872
|
+
* and types what they hear; the grade is how close their text is to the
|
|
873
|
+
* transcript.
|
|
874
|
+
*
|
|
875
|
+
* Scoring is character-level similarity over the whole sentence —
|
|
876
|
+
* `(length − edit distance) / length` over NFC code points after case,
|
|
877
|
+
* punctuation and spacing are ignored — with one {@link ScoringDetail} per
|
|
878
|
+
* transcript word carrying that word's own {@link ScoringDetail.score}. It is
|
|
879
|
+
* deliberately not a mode of {@link FillInTheBlanksData}: a blank is right or
|
|
880
|
+
* wrong, a dictation is graded on a continuum, and the whole `TextMatchPolicy`
|
|
881
|
+
* surface (typo tolerance, diacritic folding) would be a second, competing
|
|
882
|
+
* notion of "close enough".
|
|
883
|
+
*/
|
|
884
|
+
interface DictationData {
|
|
885
|
+
schemaVersion: '1.0';
|
|
886
|
+
type: 'dictation';
|
|
887
|
+
/** Unique identifier for this activity. */
|
|
888
|
+
id: string;
|
|
889
|
+
/**
|
|
890
|
+
* Human-readable title used in xAPI statements and error boundaries. It is
|
|
891
|
+
* the stem the learner sees, so it must not contain the transcript.
|
|
892
|
+
*/
|
|
893
|
+
title: string;
|
|
894
|
+
/**
|
|
895
|
+
* What the learner is expected to type. ANSWER KEY. 1..2000 code points,
|
|
896
|
+
* before and after its equivalences are applied, and something must survive
|
|
897
|
+
* normalisation.
|
|
898
|
+
*/
|
|
899
|
+
transcript: string;
|
|
900
|
+
/**
|
|
901
|
+
* Alternative transcriptions accepted at full credit — a regional spelling, a
|
|
902
|
+
* numeral, a whole-sentence contraction variant. ANSWER KEY. At most 10, each
|
|
903
|
+
* 1..2000 code points, none normalising equal to the transcript or to each
|
|
904
|
+
* other. The attempt is scored against every candidate and the best
|
|
905
|
+
* similarity wins; a tie goes to `transcript`.
|
|
906
|
+
*/
|
|
907
|
+
acceptedTranscripts?: string[];
|
|
908
|
+
/**
|
|
909
|
+
* The recording. Audio only, never with captions. Optional in the schema — a
|
|
910
|
+
* dictation inside an item group may draw on the group's stimulus recording —
|
|
911
|
+
* but required whenever `slowMedia` is present. Budgeted per slot like any
|
|
912
|
+
* other activity media, with the usual playback policy.
|
|
913
|
+
*/
|
|
914
|
+
media?: ActivityMedia;
|
|
915
|
+
/** A second, slower recording. See {@link DictationSlowMedia}. */
|
|
916
|
+
slowMedia?: DictationSlowMedia;
|
|
917
|
+
/**
|
|
918
|
+
* Progressive word hints, in `practice` only (the transcript is absent in
|
|
919
|
+
* `exam` mode, so there is nothing to reveal). Public: it reveals nothing by
|
|
920
|
+
* itself. `progressive-words` reveals the transcript's words left to right,
|
|
921
|
+
* one per request; a revealed hint never changes the score.
|
|
922
|
+
*/
|
|
923
|
+
hints?: {
|
|
924
|
+
mode: 'progressive-words';
|
|
925
|
+
};
|
|
926
|
+
/** Grading tolerances. ANSWER KEY. */
|
|
927
|
+
tolerance?: DictationTolerance;
|
|
928
|
+
/** Optional authored overall feedback shown after submission. */
|
|
929
|
+
feedback?: ActivityFeedback;
|
|
930
|
+
/** Minimum scaled score [0–1] required to pass. Defaults to {@link DEFAULT_PASS_THRESHOLD} (0.7) when absent. */
|
|
931
|
+
passThreshold?: number;
|
|
932
|
+
/** BCP 47 language tag for the activity content. Presentation only: scoring never reads it. */
|
|
933
|
+
locale?: string;
|
|
934
|
+
/** IRI references to learning objectives addressed by this activity. */
|
|
935
|
+
learningObjectives?: string[];
|
|
936
|
+
/** Subjective difficulty on a 1–5 scale. */
|
|
937
|
+
difficultyLevel?: 1 | 2 | 3 | 4 | 5;
|
|
938
|
+
/** What an author allows AI to do for this item: see {@link ActivityAiPermissions}. */
|
|
939
|
+
ai?: ActivityAiPermissions;
|
|
940
|
+
}
|
|
941
|
+
/** A dimension of speech a pronunciation assessor scores, on a 0–100 scale. */
|
|
942
|
+
type ReadAloudDimension = 'accuracy' | 'fluency' | 'completeness' | 'prosody';
|
|
943
|
+
/** One assessor dimension counted towards a read-aloud grade, and its weight. */
|
|
944
|
+
interface ReadAloudDimensionWeight {
|
|
945
|
+
name: ReadAloudDimension;
|
|
946
|
+
/**
|
|
947
|
+
* Relative weight, 0..1000 (`READ_ALOUD_MAX_DIMENSION_WEIGHT`). Weights need
|
|
948
|
+
* not add up to anything; a weight of 0 leaves the dimension out of the grade.
|
|
949
|
+
*/
|
|
950
|
+
weight: number;
|
|
951
|
+
}
|
|
952
|
+
/**
|
|
953
|
+
* The limits on a read-aloud take, applied where the learner records. No grade
|
|
954
|
+
* reads them: whether a take is plausible speech is decided from the server's
|
|
955
|
+
* own measurement of the recording, never from a bound the client enforced.
|
|
956
|
+
*/
|
|
957
|
+
interface RecordingBounds {
|
|
958
|
+
/** The longest take, in seconds: above 0, and at most `READ_ALOUD_MAX_SECONDS` (300). */
|
|
959
|
+
maxSeconds: number;
|
|
960
|
+
/** The shortest take, in seconds: at least 0, and below `maxSeconds`. */
|
|
961
|
+
minSeconds?: number;
|
|
962
|
+
/** How many takes the learner may record: a whole number from 1 to `READ_ALOUD_MAX_TAKES` (20). */
|
|
963
|
+
maxTakes?: number;
|
|
964
|
+
}
|
|
965
|
+
/**
|
|
966
|
+
* A second, slower model recording of the reference text — a separate file,
|
|
967
|
+
* not a `playbackRate` change. It carries no `captionsUrl` and no `playback`
|
|
968
|
+
* policy of its own, requires `media`, must be a different file from it, and
|
|
969
|
+
* cannot accompany a play limit on `media`.
|
|
970
|
+
*/
|
|
971
|
+
interface ReadAloudSlowMedia {
|
|
972
|
+
type: 'audio';
|
|
973
|
+
/** Source URL, under the same policy as {@link ActivityMedia.url}. */
|
|
974
|
+
url: string;
|
|
975
|
+
/** Accessible label — a description ("Model recording, slow"). */
|
|
976
|
+
alt?: string;
|
|
977
|
+
}
|
|
978
|
+
/**
|
|
979
|
+
* Data contract for a Read Aloud activity: the learner reads a text aloud, an
|
|
980
|
+
* assessor measures the recording, and the grade is the weighted total of the
|
|
981
|
+
* assessor's dimension scores.
|
|
982
|
+
*
|
|
983
|
+
* Grading is deferred — `evaluate()` returns `deferred` until the application
|
|
984
|
+
* holds an assessment — and nothing here is an answer key: the learner is shown
|
|
985
|
+
* the text they are asked to read.
|
|
986
|
+
*/
|
|
987
|
+
interface ReadAloudData {
|
|
988
|
+
schemaVersion: '1.0';
|
|
989
|
+
type: 'read-aloud';
|
|
990
|
+
/** Unique identifier for this activity. */
|
|
991
|
+
id: string;
|
|
992
|
+
/** Human-readable title used in xAPI statements and error boundaries. */
|
|
993
|
+
title: string;
|
|
994
|
+
/** Optional instructions shown to the learner, as plain text. */
|
|
995
|
+
instructions?: string;
|
|
996
|
+
/**
|
|
997
|
+
* The text the learner reads aloud. Shown to the learner (public). At most
|
|
998
|
+
* `READ_ALOUD_MAX_REFERENCE_LENGTH` code points before and after
|
|
999
|
+
* normalisation, in a script written with spaces between words.
|
|
1000
|
+
*/
|
|
1001
|
+
referenceText: string;
|
|
1002
|
+
/**
|
|
1003
|
+
* BCP 47 tag in canonical form with a region (e.g. `en-US`). GRADE-DECIDING:
|
|
1004
|
+
* an assessment made for another locale is unscorable.
|
|
1005
|
+
*/
|
|
1006
|
+
locale: string;
|
|
1007
|
+
/** A model recording of the text. Audio only; a playback policy is allowed. */
|
|
1008
|
+
media?: ActivityMedia;
|
|
1009
|
+
/** A second, slower model recording. See {@link ReadAloudSlowMedia}. */
|
|
1010
|
+
slowMedia?: ReadAloudSlowMedia;
|
|
1011
|
+
/** The limits on a take. See {@link RecordingBounds}. */
|
|
1012
|
+
recording: RecordingBounds;
|
|
1013
|
+
/**
|
|
1014
|
+
* The assessor dimensions the grade is made of, with their weights: 1 to 4
|
|
1015
|
+
* entries, each name at most once, at least one weight above 0. A weighted
|
|
1016
|
+
* dimension the assessment has no score for makes the take unscorable, never
|
|
1017
|
+
* a zero. Public.
|
|
1018
|
+
*/
|
|
1019
|
+
scoring: {
|
|
1020
|
+
dimensions: ReadAloudDimensionWeight[];
|
|
1021
|
+
};
|
|
1022
|
+
/** Minimum scaled score [0–1] required to pass once graded. Defaults to {@link DEFAULT_PASS_THRESHOLD} (0.7) when absent. */
|
|
1023
|
+
passThreshold?: number;
|
|
1024
|
+
/** Optional authored overall feedback, selected once the grade exists. */
|
|
1025
|
+
feedback?: ActivityFeedback;
|
|
1026
|
+
/** IRI references to learning objectives addressed by this activity. */
|
|
1027
|
+
learningObjectives?: string[];
|
|
1028
|
+
/** Subjective difficulty on a 1–5 scale. */
|
|
1029
|
+
difficultyLevel?: 1 | 2 | 3 | 4 | 5;
|
|
1030
|
+
/** What an author allows AI to do for this item: see {@link ActivityAiPermissions}. */
|
|
1031
|
+
ai?: ActivityAiPermissions;
|
|
1032
|
+
}
|
|
1033
|
+
/**
|
|
1034
|
+
* Maps each ActivityType string to its learner-response shape. Open for
|
|
1035
|
+
* extension via module augmentation, mirroring {@link ActivityDataMap}.
|
|
1036
|
+
*/
|
|
1037
|
+
interface LearnerResponseMap {
|
|
1038
|
+
'multiple-choice': MultipleChoiceLearnerResponse;
|
|
1039
|
+
'fill-in-the-blanks': FillInTheBlanksLearnerResponse;
|
|
1040
|
+
'written-response': WrittenResponseLearnerResponse;
|
|
1041
|
+
'gap-select': GapSelectLearnerResponse;
|
|
1042
|
+
dictation: DictationLearnerResponse;
|
|
1043
|
+
'read-aloud': ReadAloudLearnerResponse;
|
|
1044
|
+
}
|
|
1045
|
+
/** Union of all learner response shapes. */
|
|
1046
|
+
type LearnerResponse = LearnerResponseMap[keyof LearnerResponseMap];
|
|
1047
|
+
/** Learner response for a Multiple Choice activity. */
|
|
1048
|
+
interface MultipleChoiceLearnerResponse {
|
|
1049
|
+
type: 'multiple-choice';
|
|
1050
|
+
/** IDs of the options the learner selected. */
|
|
1051
|
+
selectedOptionIds: string[];
|
|
1052
|
+
/**
|
|
1053
|
+
* Hints the learner was shown before submitting this answer, counted from
|
|
1054
|
+
* the start of the question — the author's and AI hints alike. Written by
|
|
1055
|
+
* the SDK's components in `practice`; absent or 0 means none. A scoring
|
|
1056
|
+
* policy's `hintPenalty` charges for them. Client-reported, like any count
|
|
1057
|
+
* a browser makes.
|
|
1058
|
+
*/
|
|
1059
|
+
hintsRevealed?: number;
|
|
1060
|
+
}
|
|
1061
|
+
/** Learner response for a Fill-in-the-Blanks activity. */
|
|
1062
|
+
interface FillInTheBlanksLearnerResponse {
|
|
1063
|
+
type: 'fill-in-the-blanks';
|
|
1064
|
+
/** Map of blank ID to the learner's typed answer. */
|
|
1065
|
+
answers: Record<string, string>;
|
|
1066
|
+
/**
|
|
1067
|
+
* Hints the learner was shown before submitting this answer, counted from
|
|
1068
|
+
* the start of the question — the author's and AI hints alike. Written by
|
|
1069
|
+
* the SDK's components in `practice`; absent or 0 means none. A scoring
|
|
1070
|
+
* policy's `hintPenalty` charges for them. Client-reported, like any count
|
|
1071
|
+
* a browser makes.
|
|
1072
|
+
*/
|
|
1073
|
+
hintsRevealed?: number;
|
|
1074
|
+
}
|
|
1075
|
+
/**
|
|
1076
|
+
* Learner response for a Gap Select activity.
|
|
1077
|
+
*
|
|
1078
|
+
* A gap the learner has not answered is **absent from the map, or holds an
|
|
1079
|
+
* empty string** — the two are equivalent, because a `<select>` whose
|
|
1080
|
+
* placeholder is still showing submits `''`. Neither is a wrong answer: the
|
|
1081
|
+
* scorer reports it as `incorrect-omission`, which is how a blank the learner
|
|
1082
|
+
* never reached is told apart from one they got wrong.
|
|
1083
|
+
*/
|
|
1084
|
+
interface GapSelectLearnerResponse {
|
|
1085
|
+
type: 'gap-select';
|
|
1086
|
+
/** Map of gap ID to the ID of the choice the learner selected. */
|
|
1087
|
+
selections: Record<string, string>;
|
|
1088
|
+
/**
|
|
1089
|
+
* Hints the learner was shown before submitting this answer, counted from
|
|
1090
|
+
* the start of the question — the author's and AI hints alike. Written by
|
|
1091
|
+
* the SDK's components in `practice`; absent or 0 means none. A scoring
|
|
1092
|
+
* policy's `hintPenalty` charges for them. Client-reported, like any count
|
|
1093
|
+
* a browser makes.
|
|
1094
|
+
*/
|
|
1095
|
+
hintsRevealed?: number;
|
|
1096
|
+
}
|
|
1097
|
+
/** Learner response for a Written Response activity. */
|
|
1098
|
+
interface WrittenResponseLearnerResponse {
|
|
1099
|
+
type: 'written-response';
|
|
1100
|
+
/** The learner's free-text response. */
|
|
1101
|
+
text: string;
|
|
1102
|
+
/** Word count of `text`, computed with the canonical `countWords()` helper. */
|
|
1103
|
+
wordCount: number;
|
|
1104
|
+
}
|
|
1105
|
+
/**
|
|
1106
|
+
* Learner response for a Dictation activity.
|
|
1107
|
+
*
|
|
1108
|
+
* `text` is exactly what the learner typed; every normalisation happens in the
|
|
1109
|
+
* scorer. `hintsRevealed` is client-reported — how many hint words were shown
|
|
1110
|
+
* before submitting. The scorer ignores it; a scoring policy's `hintPenalty`
|
|
1111
|
+
* charges for it, knowing it cannot be verified.
|
|
1112
|
+
*/
|
|
1113
|
+
interface DictationLearnerResponse {
|
|
1114
|
+
type: 'dictation';
|
|
1115
|
+
/** The learner's text, as typed. */
|
|
1116
|
+
text: string;
|
|
1117
|
+
/**
|
|
1118
|
+
* Hint words shown before submitting (practice only), counted from the start
|
|
1119
|
+
* of the question. Absent or 0 means none.
|
|
1120
|
+
*/
|
|
1121
|
+
hintsRevealed?: number;
|
|
1122
|
+
}
|
|
1123
|
+
/**
|
|
1124
|
+
* A learner recording held by the application. `key` is opaque,
|
|
1125
|
+
* application-issued storage identity: never a URL or bytes. `durationMs` is a
|
|
1126
|
+
* client claim no grade reads.
|
|
1127
|
+
*/
|
|
1128
|
+
interface RecordingRef {
|
|
1129
|
+
key: string;
|
|
1130
|
+
/** The media type the recording was stored as, e.g. `audio/wav`. */
|
|
1131
|
+
mimeType: string;
|
|
1132
|
+
durationMs?: number;
|
|
1133
|
+
}
|
|
1134
|
+
/**
|
|
1135
|
+
* Learner response for a Read Aloud activity: a reference to the recording the
|
|
1136
|
+
* application stored, never the audio itself.
|
|
1137
|
+
*/
|
|
1138
|
+
interface ReadAloudLearnerResponse {
|
|
1139
|
+
type: 'read-aloud';
|
|
1140
|
+
/** `null`: the learner submitted without recording (a blank). A take that failed to upload is never `null`. */
|
|
1141
|
+
recording: RecordingRef | null;
|
|
1142
|
+
/** Telemetry: takes used. No grade reads it. */
|
|
1143
|
+
takes?: number;
|
|
1144
|
+
}
|
|
1145
|
+
/**
|
|
1146
|
+
* Fine-grained outcome of the learner's action on a single item:
|
|
1147
|
+
* - `correct` — the learner selected/entered the right answer.
|
|
1148
|
+
* - `incorrect` — the learner selected/entered a wrong answer.
|
|
1149
|
+
* - `correct-omission` — the learner correctly left a non-answer unselected (multiple-choice only).
|
|
1150
|
+
* - `incorrect-omission` — a correct answer the learner did not give: an unselected
|
|
1151
|
+
* correct option, an unanswered gap, a missing dictation word.
|
|
1152
|
+
*/
|
|
1153
|
+
type ScoringOutcome = 'correct' | 'incorrect' | 'correct-omission' | 'incorrect-omission';
|
|
1154
|
+
/** Per-item scoring breakdown returned by the scoring engine. */
|
|
1155
|
+
interface ScoringDetail {
|
|
1156
|
+
/** ID of the option, blank, gap or dictation word (`w1`…`wN`) this detail refers to. */
|
|
1157
|
+
itemId: string;
|
|
1158
|
+
/**
|
|
1159
|
+
* The outcome of the learner's action on this item. On a multiple-choice
|
|
1160
|
+
* option it says both whether the option was chosen and whether it is part
|
|
1161
|
+
* of the answer.
|
|
1162
|
+
*
|
|
1163
|
+
* Until 1.0 a detail also carried `correct`, which on a multiple-choice
|
|
1164
|
+
* option meant "the learner acted correctly on it" — an unselected wrong
|
|
1165
|
+
* option read `correct: true` — and a detail stored before 0.3 carries only
|
|
1166
|
+
* that. The SDK's components still read such a stored detail; a type written
|
|
1167
|
+
* against 1.0 does not describe it.
|
|
1168
|
+
*/
|
|
1169
|
+
outcome: ScoringOutcome;
|
|
1170
|
+
/** The learner's actual response for this item. */
|
|
1171
|
+
learnerResponse: string | string[];
|
|
1172
|
+
/** The expected correct response(s) for this item. */
|
|
1173
|
+
correctResponse: string | string[];
|
|
1174
|
+
/**
|
|
1175
|
+
* Weight applied to this item's contribution to the overall score. Every
|
|
1176
|
+
* built-in scorer currently weights every item equally and writes `1`.
|
|
1177
|
+
*/
|
|
1178
|
+
weight?: number;
|
|
1179
|
+
/**
|
|
1180
|
+
* The item's own scaled score in [0, 1], for a type that grades its items on
|
|
1181
|
+
* a continuum rather than right or wrong. Written by the dictation scorer
|
|
1182
|
+
* (the similarity of each transcript word to what was typed for it) and by
|
|
1183
|
+
* `gradeReadAloud` (the assessor's accuracy for each reference word as a
|
|
1184
|
+
* fraction, 0 for an omitted word, and absent where the assessor gave none).
|
|
1185
|
+
* Absent, an item is right or wrong: read `outcome`.
|
|
1186
|
+
*/
|
|
1187
|
+
score?: number;
|
|
1188
|
+
}
|
|
1189
|
+
/** Full scoring result returned by the scoring engine. */
|
|
1190
|
+
interface ScoringResult {
|
|
1191
|
+
/** Scaled score in the range [0, 1]. */
|
|
1192
|
+
score: number;
|
|
1193
|
+
/** Maximum possible scaled score (always 1). */
|
|
1194
|
+
maxScore: number;
|
|
1195
|
+
/** Whether the score meets or exceeds the activity's pass threshold. */
|
|
1196
|
+
passed: boolean;
|
|
1197
|
+
/**
|
|
1198
|
+
* The authored overall feedback selected for this result: `feedback.correct`
|
|
1199
|
+
* when the learner passed, `feedback.incorrect` otherwise; `null` when the
|
|
1200
|
+
* activity authored no matching feedback.
|
|
1201
|
+
*/
|
|
1202
|
+
feedback: string | null;
|
|
1203
|
+
/** Per-item scoring breakdown. */
|
|
1204
|
+
details: ScoringDetail[];
|
|
1205
|
+
}
|
|
1206
|
+
/**
|
|
1207
|
+
* Progress facts about a deferred (asynchronously graded) submission that are
|
|
1208
|
+
* computable synchronously at submit time. For written-response, both fields
|
|
1209
|
+
* are always present and `wordCount` is recomputed from the submitted text
|
|
1210
|
+
* with the canonical `countWords()` (the client-supplied count is not trusted).
|
|
1211
|
+
*/
|
|
1212
|
+
interface DeferredScoringPartial {
|
|
1213
|
+
/** Whether the recomputed word count falls within `[minWords, maxWords]`. */
|
|
1214
|
+
withinWordBounds?: boolean;
|
|
1215
|
+
/** Recomputed word count of the submitted text. */
|
|
1216
|
+
wordCount?: number;
|
|
1217
|
+
[key: string]: unknown;
|
|
1218
|
+
}
|
|
1219
|
+
/**
|
|
1220
|
+
* Why an item has no grade *yet* — a state that is explicitly not final, and
|
|
1221
|
+
* so keeps a composed assessment `provisional`.
|
|
1222
|
+
*
|
|
1223
|
+
* - `requires_async_grading` — the type is graded later by an AI or a human.
|
|
1224
|
+
* - `no_response_recorded` — nothing was stored against this slot at all. It
|
|
1225
|
+
* is NOT the same as a zero: a learner who left a question blank on a
|
|
1226
|
+
* submitted paper has earned zero, but a slot missing because a save failed,
|
|
1227
|
+
* a grade has not landed, or the attempt is still open has earned nothing
|
|
1228
|
+
* yet, and recording it as zero is how an incomplete attempt becomes a
|
|
1229
|
+
* plausible-looking fail.
|
|
1230
|
+
* - `grade_rejected` — a grade came back, and `outcomeFromGrade` refused it
|
|
1231
|
+
* because its numbers cannot be a grade (a `score` or `maxScore` that is not
|
|
1232
|
+
* a finite number, a `maxScore` of 0 or less, or a score outside 0 to
|
|
1233
|
+
* `maxScore`). The record is kept on `rejectedGrade`. A real grade is still
|
|
1234
|
+
* owed, so re-grade the slot: waiting will not bring one.
|
|
1235
|
+
*/
|
|
1236
|
+
type DeferredReason = 'requires_async_grading' | 'no_response_recorded' | 'grade_rejected';
|
|
1237
|
+
/**
|
|
1238
|
+
* The outcome of evaluating a learner response against an activity — the
|
|
1239
|
+
* union `evaluate()` returns. Unlike {@link ScoringResult}, it can express
|
|
1240
|
+
* "not gradable yet" (`deferred`) and "not gradable at all" (`unscorable`),
|
|
1241
|
+
* so an ungraded written response is never conflated with a score of 0.
|
|
1242
|
+
*/
|
|
1243
|
+
type ItemOutcome = {
|
|
1244
|
+
status: 'scored';
|
|
1245
|
+
/** Scaled score in the range [0, 1]. */
|
|
1246
|
+
score: number;
|
|
1247
|
+
/** Maximum possible scaled score. */
|
|
1248
|
+
maxScore: number;
|
|
1249
|
+
/** Whether the score meets or exceeds the activity's pass threshold. */
|
|
1250
|
+
passed: boolean;
|
|
1251
|
+
/** Authored overall feedback selected by pass state, or null. */
|
|
1252
|
+
feedback: string | null;
|
|
1253
|
+
/** Per-item scoring breakdown. */
|
|
1254
|
+
details: ScoringDetail[];
|
|
1255
|
+
} | {
|
|
1256
|
+
status: 'deferred';
|
|
1257
|
+
/** Why no grade exists yet. See {@link DeferredReason}. */
|
|
1258
|
+
reason: DeferredReason;
|
|
1259
|
+
/** Maximum possible scaled score once graded. */
|
|
1260
|
+
maxScore: number;
|
|
1261
|
+
/** Synchronously computable progress facts (word bounds, counts). */
|
|
1262
|
+
partial?: DeferredScoringPartial;
|
|
1263
|
+
/**
|
|
1264
|
+
* The record `outcomeFromGrade` refused, verbatim, when `reason` is
|
|
1265
|
+
* `grade_rejected` — kept for audit and for the re-grade. Nothing on it
|
|
1266
|
+
* is mirrored onto the outcome, and nothing reads it as a grade.
|
|
1267
|
+
*/
|
|
1268
|
+
rejectedGrade?: GradeRecord;
|
|
1269
|
+
} | {
|
|
1270
|
+
status: 'graded';
|
|
1271
|
+
/**
|
|
1272
|
+
* A grade produced by an asynchronous grader (AI or human) and handed
|
|
1273
|
+
* back to the SDK. This is the state a `deferred` outcome transitions
|
|
1274
|
+
* to once grading completes; nothing in the SDK ever manufactures it.
|
|
1275
|
+
*/
|
|
1276
|
+
grade: GradeRecord;
|
|
1277
|
+
/** Scaled score in the range [0, 1], mirrored from `grade` for uniform reads. */
|
|
1278
|
+
score: number;
|
|
1279
|
+
maxScore: number;
|
|
1280
|
+
passed: boolean;
|
|
1281
|
+
/** Narrative feedback from the grader, mirrored from `grade`. */
|
|
1282
|
+
feedback: string | null;
|
|
1283
|
+
} | {
|
|
1284
|
+
status: 'unscorable';
|
|
1285
|
+
/** Why no grade can be produced (e.g. unregistered activity type). */
|
|
1286
|
+
reason: string;
|
|
1287
|
+
/** Maximum possible scaled score, when known. */
|
|
1288
|
+
maxScore: number;
|
|
1289
|
+
/**
|
|
1290
|
+
* A machine-readable reason, written by `outcomeFromUnscorable` — for
|
|
1291
|
+
* example a read-aloud `SpeechUnscorableCode`. `evaluate` never writes it.
|
|
1292
|
+
*/
|
|
1293
|
+
code?: string;
|
|
1294
|
+
};
|
|
1295
|
+
/** A single validation error produced by `validateActivity`. */
|
|
1296
|
+
interface ValidationError {
|
|
1297
|
+
/** JSON-path-style location of the invalid field. */
|
|
1298
|
+
path: string[];
|
|
1299
|
+
/** Human-readable description of the validation failure. */
|
|
1300
|
+
message: string;
|
|
1301
|
+
/** Machine-readable error code. */
|
|
1302
|
+
code: string;
|
|
1303
|
+
}
|
|
1304
|
+
/**
|
|
1305
|
+
* Result of a `validateActivity` call.
|
|
1306
|
+
* On success, `data` is the validated and typed activity data.
|
|
1307
|
+
* On failure, `errors` contains one entry per violated constraint.
|
|
1308
|
+
*/
|
|
1309
|
+
type ValidationResult<T> = {
|
|
1310
|
+
success: true;
|
|
1311
|
+
data: T;
|
|
1312
|
+
} | {
|
|
1313
|
+
success: false;
|
|
1314
|
+
errors: ValidationError[];
|
|
1315
|
+
};
|
|
1316
|
+
/** The payload delivered to an activity's `onComplete` callback. */
|
|
1317
|
+
interface ActivityResult {
|
|
1318
|
+
/** Scaled score in the range [0, 1]. */
|
|
1319
|
+
score: number;
|
|
1320
|
+
/** Maximum possible scaled score (always 1). */
|
|
1321
|
+
maxScore: number;
|
|
1322
|
+
/** Whether the learner passed based on the activity's pass threshold. */
|
|
1323
|
+
passed: boolean;
|
|
1324
|
+
/** Time in milliseconds from first interaction to submission. */
|
|
1325
|
+
timeSpent: number;
|
|
1326
|
+
/** The xAPI statement built and (optionally) sent for this attempt. */
|
|
1327
|
+
xapiStatement: XAPIStatement;
|
|
1328
|
+
}
|
|
1329
|
+
/**
|
|
1330
|
+
* The kinds of interaction the built-in components emit. Custom activity types
|
|
1331
|
+
* registered by consumers may emit their own kinds, so any string is accepted;
|
|
1332
|
+
* the named literals are kept for autocompletion.
|
|
1333
|
+
*/
|
|
1334
|
+
type InteractionKind = 'option-selected' | 'option-deselected' | 'blank-filled' | 'hint-requested' | 'text-changed' | 'submitted' |
|
|
1335
|
+
/**
|
|
1336
|
+
* A play was charged against a recording's budget, before any audio was
|
|
1337
|
+
* audible. Emitted only when a budget is in force, so a `review`-mode replay
|
|
1338
|
+
* never pollutes the record an appeal reads.
|
|
1339
|
+
*/
|
|
1340
|
+
'media-play-consumed' |
|
|
1341
|
+
/** An exhausted budget refused a play. The artifact an appeal asks for. */
|
|
1342
|
+
'media-play-refused' |
|
|
1343
|
+
/** A charged play produced no audio — a failed or expired media URL. */
|
|
1344
|
+
'media-play-errored' |
|
|
1345
|
+
/** A charged play was returned, because the consumer accepted the refund. */
|
|
1346
|
+
'media-play-refunded' |
|
|
1347
|
+
/** The microphone began capturing a take. */
|
|
1348
|
+
'recording-started' |
|
|
1349
|
+
/** A take ended, by the learner or at its time limit. */
|
|
1350
|
+
'recording-stopped' |
|
|
1351
|
+
/** A take was thrown away, to record another. */
|
|
1352
|
+
'recording-discarded' |
|
|
1353
|
+
/** The application stored a take and returned its reference. */
|
|
1354
|
+
'recording-uploaded' |
|
|
1355
|
+
/** Storing a take failed. A take that failed to upload is never a blank answer. */
|
|
1356
|
+
'recording-upload-failed' |
|
|
1357
|
+
/** A stored take was sent for assessment. */
|
|
1358
|
+
'assessment-requested' |
|
|
1359
|
+
/** An assessment request failed: the take has no grade. */
|
|
1360
|
+
'assessment-failed' |
|
|
1361
|
+
/** Interactive video: the learner started playback. */
|
|
1362
|
+
'video-played' |
|
|
1363
|
+
/** Interactive video: playback paused, by the learner or by a quiz. */
|
|
1364
|
+
'video-paused' |
|
|
1365
|
+
/** Interactive video: the playhead moved (`from`, `to`, and whether navigation `clamped` it). */
|
|
1366
|
+
'video-seeked' | 'video-rate-changed' | 'video-ended' |
|
|
1367
|
+
/** Interactive video: a quiz opened (`cueId`, `at`). */
|
|
1368
|
+
'video-quiz-opened' |
|
|
1369
|
+
/** Interactive video: one question of an open quiz was shown (`cueId`, `slotId`). */
|
|
1370
|
+
'video-quiz-question-shown' | 'video-quiz-skipped' | 'video-quiz-closed' |
|
|
1371
|
+
/**
|
|
1372
|
+
* Interactive video: the captions a learner chose. `payload.srclang` is the
|
|
1373
|
+
* primary line's language, or `null` with captions off; `payload.secondary` is
|
|
1374
|
+
* the second line's, or `null` with none showing. Fires when either changes.
|
|
1375
|
+
*/
|
|
1376
|
+
'video-captions-changed' | 'video-fullscreen-changed' | 'video-pip-changed' |
|
|
1377
|
+
/**
|
|
1378
|
+
* An AI hint was shown to the learner (`hintNumber`, and the port's
|
|
1379
|
+
* `provenance` when it sent one). Only a hint the SDK accepted: a refused one
|
|
1380
|
+
* is never shown and never reported here.
|
|
1381
|
+
*/
|
|
1382
|
+
'ai-hint-shown' |
|
|
1383
|
+
/** An AI explanation of the graded answer was shown (`provenance`, `usage` when sent). */
|
|
1384
|
+
'ai-explanation-shown' |
|
|
1385
|
+
/**
|
|
1386
|
+
* An AI answer was refused, and the learner was told help was not available
|
|
1387
|
+
* (`feature`, `reason` — an {@link AiRefusal} — and `hintNumber` for a hint).
|
|
1388
|
+
*
|
|
1389
|
+
* **The text itself is never carried**: a hint refused for revealing the
|
|
1390
|
+
* answer contains the answer, and this event is logged. It is how a host
|
|
1391
|
+
* watches its own model in production, where the development console warning
|
|
1392
|
+
* it accompanies is silent.
|
|
1393
|
+
*/
|
|
1394
|
+
'ai-help-refused' |
|
|
1395
|
+
/**
|
|
1396
|
+
* Feedback on a draft of a written response was shown (`draftNumber`, the
|
|
1397
|
+
* number of `corrections`, the `indicativeScore` when there was one, and the
|
|
1398
|
+
* port's `provenance` and `usage` when it sent them). Never the text.
|
|
1399
|
+
*/
|
|
1400
|
+
'ai-writing-feedback-shown' |
|
|
1401
|
+
/**
|
|
1402
|
+
* Coaching on a read-aloud was shown (the number of `words` it coached, and
|
|
1403
|
+
* the port's `provenance` and `usage` when it sent them). Never the text.
|
|
1404
|
+
*/
|
|
1405
|
+
'ai-coaching-shown' | (string & {});
|
|
1406
|
+
/** Fired by activity components on every discrete learner interaction. */
|
|
1407
|
+
interface InteractionEvent {
|
|
1408
|
+
/** The kind of interaction that occurred. */
|
|
1409
|
+
type: InteractionKind;
|
|
1410
|
+
/** ID of the activity that produced the event. */
|
|
1411
|
+
activityId: string;
|
|
1412
|
+
/** Unix timestamp (ms) of when the interaction occurred. */
|
|
1413
|
+
timestamp: number;
|
|
1414
|
+
/** Additional event-specific data. */
|
|
1415
|
+
payload: Record<string, unknown>;
|
|
1416
|
+
}
|
|
1417
|
+
//#endregion
|
|
1418
|
+
//#region src/types/authoring.d.ts
|
|
1419
|
+
/**
|
|
1420
|
+
* Why a draft is not an activity yet.
|
|
1421
|
+
*
|
|
1422
|
+
* - `incomplete` — something the author has not written yet: an empty title, an
|
|
1423
|
+
* option with no text, no option marked correct. The fix is to add it.
|
|
1424
|
+
* - `invalid` — something is wrong in a way that writing more cannot fix: two
|
|
1425
|
+
* correct options on a single-selection question, a maximum word count below
|
|
1426
|
+
* the minimum, a media address with a scheme the SDK refuses, a `null` in an
|
|
1427
|
+
* optional field that accepts none. The fix is to change it.
|
|
1428
|
+
*/
|
|
1429
|
+
type DraftSeverity = 'incomplete' | 'invalid';
|
|
1430
|
+
/**
|
|
1431
|
+
* One problem with a draft. A {@link ValidationError} with a severity, so code
|
|
1432
|
+
* that already lists `validateActivity` errors can list these unchanged.
|
|
1433
|
+
*/
|
|
1434
|
+
interface DraftIssue extends ValidationError {
|
|
1435
|
+
severity: DraftSeverity;
|
|
1436
|
+
}
|
|
1437
|
+
/** A draft that is a valid activity and has nothing left to write. */
|
|
1438
|
+
interface DraftComplete<T> {
|
|
1439
|
+
status: 'complete';
|
|
1440
|
+
/** The validated activity, exactly as `validateActivity` returns it. */
|
|
1441
|
+
data: T;
|
|
1442
|
+
/** Always empty. Present so that every result can be read the same way. */
|
|
1443
|
+
issues: DraftIssue[];
|
|
1444
|
+
}
|
|
1445
|
+
/**
|
|
1446
|
+
* A draft that is not an activity yet: `invalid` when any issue is invalid,
|
|
1447
|
+
* otherwise `incomplete`. It carries no `data`, because nothing here may be
|
|
1448
|
+
* stored or rendered as an activity.
|
|
1449
|
+
*/
|
|
1450
|
+
interface DraftNotComplete {
|
|
1451
|
+
status: 'incomplete' | 'invalid';
|
|
1452
|
+
issues: DraftIssue[];
|
|
1453
|
+
}
|
|
1454
|
+
/**
|
|
1455
|
+
* The result of `validateDraft`.
|
|
1456
|
+
*
|
|
1457
|
+
* There is deliberately no `success` boolean. A boolean has to call an
|
|
1458
|
+
* incomplete draft either a success, which lets a half-written answer key
|
|
1459
|
+
* through anything that checks it, or a failure, which is the conflation this
|
|
1460
|
+
* result exists to remove. Branch on `status`.
|
|
1461
|
+
*/
|
|
1462
|
+
type DraftValidationResult<T> = DraftComplete<T> | DraftNotComplete;
|
|
1463
|
+
/**
|
|
1464
|
+
* How much a finding of the item critic matters. Neither makes a draft
|
|
1465
|
+
* incomplete or invalid: an item with findings is a valid item, stored and
|
|
1466
|
+
* served like any other, and whether to change it is the author's call.
|
|
1467
|
+
*
|
|
1468
|
+
* - `warning` — a known flaw a learner can exploit or trip over: the right
|
|
1469
|
+
* option is the longest, an answer printed in the passage, a hint that gives
|
|
1470
|
+
* the answer away, two options that read the same.
|
|
1471
|
+
* - `advice` — an item-writing guideline the item departs from: "all of the
|
|
1472
|
+
* above", two accepted answers the matcher already treats as one.
|
|
1473
|
+
*/
|
|
1474
|
+
type ItemFindingSeverity = 'warning' | 'advice';
|
|
1475
|
+
/**
|
|
1476
|
+
* One thing the item critic found. A {@link ValidationError} with a severity,
|
|
1477
|
+
* as a {@link DraftIssue} is, so an editor lists both in one list — sorted by
|
|
1478
|
+
* severity: `invalid`, `incomplete`, `warning`, `advice`.
|
|
1479
|
+
*/
|
|
1480
|
+
interface ItemFinding extends ValidationError {
|
|
1481
|
+
severity: ItemFindingSeverity;
|
|
1482
|
+
}
|
|
1483
|
+
/** What `createDraft` hands an activity type's `authoring.createDraft`. */
|
|
1484
|
+
interface DraftContext {
|
|
1485
|
+
/**
|
|
1486
|
+
* Returns a new id on every call. The SDK invents none: an id made here would
|
|
1487
|
+
* need `crypto`, which plain-http origins do not have, or would be positional —
|
|
1488
|
+
* and option ids reach the learner, so ids like `"a"` and `"b"` would tell them
|
|
1489
|
+
* which option was written first.
|
|
1490
|
+
*/
|
|
1491
|
+
newId: () => string;
|
|
1492
|
+
}
|
|
1493
|
+
//#endregion
|
|
1494
|
+
//#region src/types/ai.d.ts
|
|
1495
|
+
/**
|
|
1496
|
+
* What an author allows AI to do for one item. Absent, or a feature left out,
|
|
1497
|
+
* leaves the choice to the deployment. `false` switches a feature off for this
|
|
1498
|
+
* item wherever it is delivered; an author can only switch a feature off,
|
|
1499
|
+
* never force one on.
|
|
1500
|
+
*
|
|
1501
|
+
* Switch hints off where any hint would give the answer away — a
|
|
1502
|
+
* one-word vocabulary item, say.
|
|
1503
|
+
*/
|
|
1504
|
+
interface ActivityAiPermissions {
|
|
1505
|
+
/**
|
|
1506
|
+
* An explanation of the learner's graded answer — and, on a written
|
|
1507
|
+
* response, feedback on a draft, and on a read-aloud, coaching on the
|
|
1508
|
+
* marks: each is a model's words about the learner's own answer.
|
|
1509
|
+
*/
|
|
1510
|
+
explanations?: boolean;
|
|
1511
|
+
/** Hints before the learner submits. */
|
|
1512
|
+
hints?: boolean;
|
|
1513
|
+
}
|
|
1514
|
+
/**
|
|
1515
|
+
* An activity as a renderer holds it: full data or a `redact()` projection.
|
|
1516
|
+
* Structural rather than the data union, so a renderer's own view of an item —
|
|
1517
|
+
* whose answer-key fields may be absent — is accepted as it is. The AI
|
|
1518
|
+
* functions read every field defensively.
|
|
1519
|
+
*/
|
|
1520
|
+
interface AiActivityInput {
|
|
1521
|
+
readonly type: string;
|
|
1522
|
+
readonly id: string;
|
|
1523
|
+
readonly title: string;
|
|
1524
|
+
readonly locale?: unknown;
|
|
1525
|
+
readonly redacted?: unknown;
|
|
1526
|
+
readonly ai?: unknown;
|
|
1527
|
+
}
|
|
1528
|
+
/** The kinds of help a learner can be given. */
|
|
1529
|
+
type AiFeature = 'explanation' | 'hint' | 'writing-feedback' | 'pronunciation-coaching';
|
|
1530
|
+
/** The activity types the SDK builds facts for. */
|
|
1531
|
+
type AiSupportedActivityType = 'multiple-choice' | 'fill-in-the-blanks' | 'gap-select' | 'dictation';
|
|
1532
|
+
/**
|
|
1533
|
+
* A whole answer's grade, from the SDK's scorer. `category` is the verdict an
|
|
1534
|
+
* explanation must agree with: all marks, some, or none.
|
|
1535
|
+
*/
|
|
1536
|
+
interface AiGrade {
|
|
1537
|
+
score: number;
|
|
1538
|
+
maxScore: number;
|
|
1539
|
+
passed: boolean;
|
|
1540
|
+
category: AiVerdict;
|
|
1541
|
+
}
|
|
1542
|
+
/** The three verdicts an explanation can speak to. */
|
|
1543
|
+
type AiVerdict = 'correct' | 'partly-correct' | 'incorrect';
|
|
1544
|
+
interface AiFactsBase {
|
|
1545
|
+
activityId: string;
|
|
1546
|
+
title: string;
|
|
1547
|
+
/** The language the item is written in, when the author gave one. */
|
|
1548
|
+
locale?: string;
|
|
1549
|
+
}
|
|
1550
|
+
/** One option of a multiple-choice question, and what the learner did with it. */
|
|
1551
|
+
interface AiOptionFact {
|
|
1552
|
+
id: string;
|
|
1553
|
+
text: string;
|
|
1554
|
+
chosen: boolean;
|
|
1555
|
+
/** Whether this option is part of the answer; `null` when the key is not available. */
|
|
1556
|
+
correct: boolean | null;
|
|
1557
|
+
/** The author's feedback on this option. */
|
|
1558
|
+
feedback?: string;
|
|
1559
|
+
}
|
|
1560
|
+
interface AiMultipleChoiceFacts extends AiFactsBase {
|
|
1561
|
+
activityType: 'multiple-choice';
|
|
1562
|
+
question: string;
|
|
1563
|
+
mode: 'single' | 'multi';
|
|
1564
|
+
options: AiOptionFact[];
|
|
1565
|
+
}
|
|
1566
|
+
/** One blank, in the order it appears in the passage. */
|
|
1567
|
+
interface AiBlankFact {
|
|
1568
|
+
id: string;
|
|
1569
|
+
/** 1-based, in passage order. */
|
|
1570
|
+
position: number;
|
|
1571
|
+
/** What the learner typed, or `''`. */
|
|
1572
|
+
typed: string;
|
|
1573
|
+
/** The answers that count; `null` when the key is not available. */
|
|
1574
|
+
accepted: string[] | null;
|
|
1575
|
+
/** The SDK's verdict on this blank; `null` before it is graded or without a key. */
|
|
1576
|
+
correct: boolean | null;
|
|
1577
|
+
/** The author's hint for this blank. */
|
|
1578
|
+
hint?: string;
|
|
1579
|
+
/** The author's feedback for this blank. */
|
|
1580
|
+
feedback?: string;
|
|
1581
|
+
}
|
|
1582
|
+
interface AiFillInTheBlanksFacts extends AiFactsBase {
|
|
1583
|
+
activityType: 'fill-in-the-blanks';
|
|
1584
|
+
/** The passage, each blank written as `[1]`, `[2]`… in passage order. */
|
|
1585
|
+
passage: string;
|
|
1586
|
+
blanks: AiBlankFact[];
|
|
1587
|
+
}
|
|
1588
|
+
/** One gap, in the order it appears in the passage. */
|
|
1589
|
+
interface AiGapFact {
|
|
1590
|
+
id: string;
|
|
1591
|
+
/** 1-based, in passage order. */
|
|
1592
|
+
position: number;
|
|
1593
|
+
/** The words offered at this gap. */
|
|
1594
|
+
choices: string[];
|
|
1595
|
+
/** The word the learner chose, or `null`. */
|
|
1596
|
+
chosen: string | null;
|
|
1597
|
+
/** The right word; `null` when the key is not available. */
|
|
1598
|
+
answer: string | null;
|
|
1599
|
+
/** The SDK's verdict on this gap; `null` before it is graded or without a key. */
|
|
1600
|
+
correct: boolean | null;
|
|
1601
|
+
/** The author's feedback for this gap. */
|
|
1602
|
+
feedback?: string;
|
|
1603
|
+
}
|
|
1604
|
+
interface AiGapSelectFacts extends AiFactsBase {
|
|
1605
|
+
activityType: 'gap-select';
|
|
1606
|
+
/** The passage, each gap written as `[1]`, `[2]`… in passage order. */
|
|
1607
|
+
passage: string;
|
|
1608
|
+
gaps: AiGapFact[];
|
|
1609
|
+
}
|
|
1610
|
+
/** One word of a dictation, as the SDK's alignment paired it. */
|
|
1611
|
+
interface AiWordFact {
|
|
1612
|
+
/** The word the recording says; `''` for an extra word the learner added. */
|
|
1613
|
+
expected: string;
|
|
1614
|
+
/** The word the learner typed; `''` for a word they left out. */
|
|
1615
|
+
typed: string;
|
|
1616
|
+
status: 'correct' | 'incorrect' | 'missing' | 'extra';
|
|
1617
|
+
}
|
|
1618
|
+
interface AiDictationFacts extends AiFactsBase {
|
|
1619
|
+
activityType: 'dictation';
|
|
1620
|
+
/** What the recording says; `null` when the key is not available. */
|
|
1621
|
+
transcript: string | null;
|
|
1622
|
+
/** What the learner typed. */
|
|
1623
|
+
typed: string;
|
|
1624
|
+
/** The alignment word by word; `null` when the key is not available. */
|
|
1625
|
+
words: AiWordFact[] | null;
|
|
1626
|
+
}
|
|
1627
|
+
/**
|
|
1628
|
+
* What a model is told about one item and one answer: the question as the
|
|
1629
|
+
* learner saw it, what they answered, and — part by part — the SDK's verdict.
|
|
1630
|
+
*/
|
|
1631
|
+
type AiItemFacts = AiMultipleChoiceFacts | AiFillInTheBlanksFacts | AiGapSelectFacts | AiDictationFacts;
|
|
1632
|
+
/**
|
|
1633
|
+
* Asks for an explanation of a graded answer. Sent after the SDK has graded
|
|
1634
|
+
* it, so `grade` is always present, and an explanation that contradicts
|
|
1635
|
+
* `grade.category` is refused.
|
|
1636
|
+
*/
|
|
1637
|
+
interface AiExplanationRequest {
|
|
1638
|
+
feature: 'explanation';
|
|
1639
|
+
facts: AiItemFacts;
|
|
1640
|
+
grade: AiGrade;
|
|
1641
|
+
/** The language to answer in: the learner's interface language, when known. */
|
|
1642
|
+
learnerLocale?: string;
|
|
1643
|
+
}
|
|
1644
|
+
/**
|
|
1645
|
+
* Asks for the next hint before the learner submits. `facts` carries the key
|
|
1646
|
+
* so a model can hint toward the answer; the SDK refuses any hint that
|
|
1647
|
+
* contains it.
|
|
1648
|
+
*/
|
|
1649
|
+
interface AiHintRequest {
|
|
1650
|
+
feature: 'hint';
|
|
1651
|
+
facts: AiItemFacts;
|
|
1652
|
+
/** 1-based: the hint being asked for. */
|
|
1653
|
+
hintNumber: number;
|
|
1654
|
+
/** The hints already shown for this item, oldest first. */
|
|
1655
|
+
previousHints: string[];
|
|
1656
|
+
learnerLocale?: string;
|
|
1657
|
+
}
|
|
1658
|
+
/** What produced an AI text, for the record a host keeps. */
|
|
1659
|
+
interface AiProvenance {
|
|
1660
|
+
/** The model's identifier, e.g. a name and version. */
|
|
1661
|
+
model?: string;
|
|
1662
|
+
/** A hash or id of the prompt revision. */
|
|
1663
|
+
promptHash?: string;
|
|
1664
|
+
/** ISO 8601. */
|
|
1665
|
+
generatedAt?: string;
|
|
1666
|
+
}
|
|
1667
|
+
/**
|
|
1668
|
+
* What a host's port returns. Plain text: the SDK renders it as text, never
|
|
1669
|
+
* as HTML. An explanation may state the verdict it assumed; one that
|
|
1670
|
+
* disagrees with the SDK's is refused.
|
|
1671
|
+
*/
|
|
1672
|
+
interface AiTextResult {
|
|
1673
|
+
text: string;
|
|
1674
|
+
/** For an explanation: the verdict the model explained. */
|
|
1675
|
+
verdict?: AiVerdict;
|
|
1676
|
+
provenance?: AiProvenance;
|
|
1677
|
+
/**
|
|
1678
|
+
* What the call cost, as the provider reported it to your port: the same
|
|
1679
|
+
* {@link GraderUsage} a returned grade carries, so one shape covers help for
|
|
1680
|
+
* a learner and marking by a grader.
|
|
1681
|
+
*
|
|
1682
|
+
* The SDK never estimates it and never adds it up. It carries what a port
|
|
1683
|
+
* sends through to `onInteraction`, beside the provenance, so the record of
|
|
1684
|
+
* what a learner was shown and the record of what it cost are the same
|
|
1685
|
+
* record. Numbers that are not finite and zero or more are dropped.
|
|
1686
|
+
*/
|
|
1687
|
+
usage?: GraderUsage;
|
|
1688
|
+
}
|
|
1689
|
+
/**
|
|
1690
|
+
* Why a result was not shown. `misquotes-answer` is writing feedback that
|
|
1691
|
+
* corrects words the learner did not write: a correction whose quote is not in
|
|
1692
|
+
* the draft, or not where it claims to be. `contradicts-marks` is coaching on
|
|
1693
|
+
* a word the engine did not mark, or on a sound it did not report.
|
|
1694
|
+
* `contradicts-item` is a critique of an item that points at a field the item
|
|
1695
|
+
* does not have, or quotes words that field does not contain.
|
|
1696
|
+
*/
|
|
1697
|
+
type AiRefusal = 'malformed' | 'empty' | 'too-long' | 'contradicts-grade' | 'reveals-answer' | 'misquotes-answer' | 'contradicts-marks' | 'contradicts-item';
|
|
1698
|
+
/** One criterion of a written response's rubric, as the author wrote it. */
|
|
1699
|
+
interface AiRubricCriterionFact {
|
|
1700
|
+
name: string;
|
|
1701
|
+
description?: string;
|
|
1702
|
+
weight: number;
|
|
1703
|
+
}
|
|
1704
|
+
/**
|
|
1705
|
+
* What a model is told about a draft of a written response: the task as the
|
|
1706
|
+
* learner saw it, the draft verbatim, and the rubric it will be graded on.
|
|
1707
|
+
*/
|
|
1708
|
+
interface AiWritingFacts {
|
|
1709
|
+
activityType: 'written-response';
|
|
1710
|
+
activityId: string;
|
|
1711
|
+
title: string;
|
|
1712
|
+
/** The language the item is written in, when the author gave one. */
|
|
1713
|
+
locale?: string;
|
|
1714
|
+
prompt: string;
|
|
1715
|
+
/** The draft, exactly as the learner wrote it. Every correction quotes it. */
|
|
1716
|
+
text: string;
|
|
1717
|
+
/** Recomputed with `countWords`, as the grader will count it. */
|
|
1718
|
+
wordCount: number;
|
|
1719
|
+
minWords: number;
|
|
1720
|
+
maxWords: number;
|
|
1721
|
+
withinWordBounds: boolean;
|
|
1722
|
+
/** The rubric the answer will be graded on; `null` when the item has none. */
|
|
1723
|
+
rubric: AiRubricCriterionFact[] | null;
|
|
1724
|
+
/** The target language and level, when the author gave one (e.g. `"en-A2"`). */
|
|
1725
|
+
languageTarget?: string;
|
|
1726
|
+
}
|
|
1727
|
+
/**
|
|
1728
|
+
* Asks for feedback on a draft of a written response, before the learner
|
|
1729
|
+
* submits it. Practice only.
|
|
1730
|
+
*/
|
|
1731
|
+
interface AiWritingFeedbackRequest {
|
|
1732
|
+
feature: 'writing-feedback';
|
|
1733
|
+
facts: AiWritingFacts;
|
|
1734
|
+
/** 1-based: which draft this is. */
|
|
1735
|
+
draftNumber: number;
|
|
1736
|
+
/** The text of the feedback already shown on earlier drafts, oldest first. */
|
|
1737
|
+
previousFeedback: string[];
|
|
1738
|
+
/** The language to write in: the learner's interface language, when known. */
|
|
1739
|
+
learnerLocale?: string;
|
|
1740
|
+
}
|
|
1741
|
+
/**
|
|
1742
|
+
* One correction a model proposes. `original` must be words the learner
|
|
1743
|
+
* wrote; the SDK finds them in the draft itself, so a model need not count
|
|
1744
|
+
* characters — leave `range` out unless you are sure of it, because a range
|
|
1745
|
+
* that does not hold the quote is refused.
|
|
1746
|
+
*/
|
|
1747
|
+
interface AiWritingCorrection {
|
|
1748
|
+
original: string;
|
|
1749
|
+
corrected: string;
|
|
1750
|
+
explanation?: string;
|
|
1751
|
+
/** An error-type tag, e.g. `"tense"`, `"article"`. */
|
|
1752
|
+
category?: string;
|
|
1753
|
+
range?: {
|
|
1754
|
+
start: number;
|
|
1755
|
+
end: number;
|
|
1756
|
+
};
|
|
1757
|
+
}
|
|
1758
|
+
/**
|
|
1759
|
+
* A model's judgement of one rubric criterion. The SDK attaches the author's
|
|
1760
|
+
* weight; a weight sent here is ignored.
|
|
1761
|
+
*/
|
|
1762
|
+
type AiCriterionJudgement = Omit<CriterionScore, 'weight'> & {
|
|
1763
|
+
weight?: number;
|
|
1764
|
+
};
|
|
1765
|
+
/**
|
|
1766
|
+
* What a host's `writingFeedback` port returns: the overall feedback as plain
|
|
1767
|
+
* text, and optionally corrections and a judgement per rubric criterion.
|
|
1768
|
+
*/
|
|
1769
|
+
interface AiWritingFeedbackResult {
|
|
1770
|
+
/** The overall feedback, addressed to the learner. Plain text, never HTML. */
|
|
1771
|
+
text: string;
|
|
1772
|
+
corrections?: AiWritingCorrection[];
|
|
1773
|
+
criteria?: AiCriterionJudgement[];
|
|
1774
|
+
provenance?: AiProvenance;
|
|
1775
|
+
usage?: GraderUsage;
|
|
1776
|
+
}
|
|
1777
|
+
/**
|
|
1778
|
+
* Feedback on a draft, as the SDK accepted it: what a learner is shown.
|
|
1779
|
+
*/
|
|
1780
|
+
interface AiWritingFeedback {
|
|
1781
|
+
text: string;
|
|
1782
|
+
/**
|
|
1783
|
+
* Every correction, anchored in the draft: `range` is where the SDK found
|
|
1784
|
+
* the quote, and `original` is the draft's own text at that range.
|
|
1785
|
+
*/
|
|
1786
|
+
corrections: InlineCorrection[];
|
|
1787
|
+
/** The model's judgements, with the rubric's weights. */
|
|
1788
|
+
criteria: CriterionScore[];
|
|
1789
|
+
/**
|
|
1790
|
+
* The rubric's weighted total of the judgements, from 0 to 1, computed by
|
|
1791
|
+
* the SDK as `gradeFromRubric` computes a grade — when every criterion of
|
|
1792
|
+
* the rubric was judged; `null` otherwise, and on an item with no rubric.
|
|
1793
|
+
*
|
|
1794
|
+
* **An indication, never a grade:** it is a model's reading of a draft the
|
|
1795
|
+
* learner has not submitted, and nothing in the SDK scores an answer with
|
|
1796
|
+
* it. `ai-writing-feedback-shown` reports it, so a record can say what the
|
|
1797
|
+
* learner was shown.
|
|
1798
|
+
*/
|
|
1799
|
+
indicativeScore: number | null;
|
|
1800
|
+
provenance?: AiProvenance;
|
|
1801
|
+
usage?: GraderUsage;
|
|
1802
|
+
}
|
|
1803
|
+
/** One sound of a word, as the engine reported it. */
|
|
1804
|
+
interface AiSoundFact {
|
|
1805
|
+
/** The sound expected, in the assessment's phoneme alphabet. */
|
|
1806
|
+
symbol: string;
|
|
1807
|
+
/** How close it came, 0..100, when the engine scored it. */
|
|
1808
|
+
accuracy?: number;
|
|
1809
|
+
/** What the engine heard instead, best first, each with its score 0..100. */
|
|
1810
|
+
heardAs?: {
|
|
1811
|
+
symbol: string;
|
|
1812
|
+
score: number;
|
|
1813
|
+
}[];
|
|
1814
|
+
}
|
|
1815
|
+
/** One word of a reading, as the engine marked it. */
|
|
1816
|
+
interface AiReadingWordFact {
|
|
1817
|
+
/** `w<n>` for a word of the text; absent for a word the learner added. */
|
|
1818
|
+
itemId?: string;
|
|
1819
|
+
/** The word of the text, normalised; `''` for a word the learner added. */
|
|
1820
|
+
word: string;
|
|
1821
|
+
/** What was heard, normalised; `''` for an omitted word. */
|
|
1822
|
+
heard: string;
|
|
1823
|
+
state: 'correct' | 'mispronounced' | 'omitted' | 'inserted';
|
|
1824
|
+
/** The engine's accuracy for the word, 0..100, when it has one. */
|
|
1825
|
+
accuracy?: number;
|
|
1826
|
+
/** The word's sounds, from the full assessment only. */
|
|
1827
|
+
sounds?: AiSoundFact[];
|
|
1828
|
+
}
|
|
1829
|
+
/**
|
|
1830
|
+
* What a model is told about a read-aloud: the text, the engine's marks word
|
|
1831
|
+
* by word, and its scores. The marks are the engine's; a model explains them
|
|
1832
|
+
* and never makes its own.
|
|
1833
|
+
*/
|
|
1834
|
+
interface AiReadingFacts {
|
|
1835
|
+
activityType: 'read-aloud';
|
|
1836
|
+
activityId: string;
|
|
1837
|
+
title: string;
|
|
1838
|
+
/** The language the text is in. */
|
|
1839
|
+
locale: string;
|
|
1840
|
+
referenceText: string;
|
|
1841
|
+
instructions?: string;
|
|
1842
|
+
/** Who made the marks: a measurement engine, a model, or a person. */
|
|
1843
|
+
assessor: 'auto' | 'ai' | 'human' | 'unknown';
|
|
1844
|
+
/** The alphabet `sounds` are written in, when any are. */
|
|
1845
|
+
phonemeAlphabet?: 'ipa' | 'sapi';
|
|
1846
|
+
/** The engine's dimension scores, each 0..100, where it measured them. */
|
|
1847
|
+
scores: {
|
|
1848
|
+
accuracy?: number;
|
|
1849
|
+
fluency?: number;
|
|
1850
|
+
completeness?: number;
|
|
1851
|
+
prosody?: number;
|
|
1852
|
+
};
|
|
1853
|
+
words: AiReadingWordFact[];
|
|
1854
|
+
}
|
|
1855
|
+
/**
|
|
1856
|
+
* Asks for coaching on a graded reading: what to work on, word by word, and
|
|
1857
|
+
* how. Never in an exam.
|
|
1858
|
+
*/
|
|
1859
|
+
interface AiCoachingRequest {
|
|
1860
|
+
feature: 'pronunciation-coaching';
|
|
1861
|
+
facts: AiReadingFacts;
|
|
1862
|
+
/** The grade of the reading, when there is one. */
|
|
1863
|
+
grade?: {
|
|
1864
|
+
score: number;
|
|
1865
|
+
maxScore: number;
|
|
1866
|
+
passed: boolean;
|
|
1867
|
+
};
|
|
1868
|
+
/** The language to write in: the learner's interface language, when known. */
|
|
1869
|
+
learnerLocale?: string;
|
|
1870
|
+
}
|
|
1871
|
+
/** One word a model coaches, as it returns it. */
|
|
1872
|
+
interface AiCoachingWord {
|
|
1873
|
+
/** The `itemId` of a word the engine marked mispronounced or omitted. */
|
|
1874
|
+
itemId: string;
|
|
1875
|
+
/** What to do about it, addressed to the learner. */
|
|
1876
|
+
tip: string;
|
|
1877
|
+
/**
|
|
1878
|
+
* A sound the engine reported for this word — `expected` — and, if the
|
|
1879
|
+
* model names one, what the engine heard instead.
|
|
1880
|
+
*/
|
|
1881
|
+
sound?: {
|
|
1882
|
+
expected: string;
|
|
1883
|
+
heard?: string;
|
|
1884
|
+
};
|
|
1885
|
+
}
|
|
1886
|
+
/** What a host's `pronunciationCoaching` port returns. */
|
|
1887
|
+
interface AiCoachingResult {
|
|
1888
|
+
/** The coaching as a whole, addressed to the learner. Plain text, never HTML. */
|
|
1889
|
+
text: string;
|
|
1890
|
+
words?: AiCoachingWord[];
|
|
1891
|
+
provenance?: AiProvenance;
|
|
1892
|
+
usage?: GraderUsage;
|
|
1893
|
+
}
|
|
1894
|
+
/** Coaching, as the SDK accepted it: what a learner is shown. */
|
|
1895
|
+
interface AiCoaching {
|
|
1896
|
+
text: string;
|
|
1897
|
+
/**
|
|
1898
|
+
* The words to work on, in reading order, each with the word of the text as
|
|
1899
|
+
* the SDK has it — never the model's spelling of it.
|
|
1900
|
+
*/
|
|
1901
|
+
words: (AiCoachingWord & {
|
|
1902
|
+
word: string;
|
|
1903
|
+
})[];
|
|
1904
|
+
provenance?: AiProvenance;
|
|
1905
|
+
usage?: GraderUsage;
|
|
1906
|
+
}
|
|
1907
|
+
/**
|
|
1908
|
+
* What a model may find wrong with an item, in the SDK's words:
|
|
1909
|
+
*
|
|
1910
|
+
* - `ambiguous` — a stem, passage or blank that reads two ways;
|
|
1911
|
+
* - `second-answer` — a distractor that is defensibly right, or an answer the
|
|
1912
|
+
* key does not accept;
|
|
1913
|
+
* - `wrong-key` — the answer marked right is not;
|
|
1914
|
+
* - `implausible-distractor` — an option nobody who read the question would pick;
|
|
1915
|
+
* - `cue` — grammar or wording that points at the answer;
|
|
1916
|
+
* - `level` — language above or below the level the item is for;
|
|
1917
|
+
* - `language` — a mistake in the item's own language;
|
|
1918
|
+
* - `sensitivity` — content a learner may find loaded or unfamiliar for
|
|
1919
|
+
* reasons that have nothing to do with what is assessed;
|
|
1920
|
+
* - `other`.
|
|
1921
|
+
*/
|
|
1922
|
+
type AiCritiqueKind = 'ambiguous' | 'second-answer' | 'wrong-key' | 'implausible-distractor' | 'cue' | 'level' | 'language' | 'sensitivity' | 'other';
|
|
1923
|
+
/** One field of an item a critique may point at, and what it says. */
|
|
1924
|
+
interface AiCritiqueField {
|
|
1925
|
+
path: string[];
|
|
1926
|
+
text: string;
|
|
1927
|
+
}
|
|
1928
|
+
/**
|
|
1929
|
+
* What a host's model is given to review an item. Built by
|
|
1930
|
+
* `aiCritiqueRequest`; for an author, never a learner, so it carries the
|
|
1931
|
+
* answer key.
|
|
1932
|
+
*/
|
|
1933
|
+
interface AiCritiqueRequest {
|
|
1934
|
+
feature: 'item-critique';
|
|
1935
|
+
facts: {
|
|
1936
|
+
activityType: string;
|
|
1937
|
+
/** The item as the author wrote it, answer key included. */
|
|
1938
|
+
item: Readonly<Record<string, unknown>>;
|
|
1939
|
+
/** Every text field a finding may point at, with its text: the only paths a finding may name. */
|
|
1940
|
+
fields: AiCritiqueField[];
|
|
1941
|
+
/**
|
|
1942
|
+
* What the SDK's own critic already found, so a model does not say it
|
|
1943
|
+
* again: code, path and message.
|
|
1944
|
+
*/
|
|
1945
|
+
findings: {
|
|
1946
|
+
path: string[];
|
|
1947
|
+
code: string;
|
|
1948
|
+
message: string;
|
|
1949
|
+
}[];
|
|
1950
|
+
/** The level the item is for, as the host names it (`A2`, `B1`), when it gave one. */
|
|
1951
|
+
level?: string;
|
|
1952
|
+
};
|
|
1953
|
+
/** The language to write findings in: the author's. */
|
|
1954
|
+
authorLocale?: string;
|
|
1955
|
+
}
|
|
1956
|
+
/** One finding as a host's port returns it. */
|
|
1957
|
+
interface AiCritiqueFindingResult {
|
|
1958
|
+
/** A path from `facts.fields`, exactly. */
|
|
1959
|
+
path: (string | number)[];
|
|
1960
|
+
kind: AiCritiqueKind;
|
|
1961
|
+
/** What is wrong, and what to do, addressed to the author. */
|
|
1962
|
+
message: string;
|
|
1963
|
+
/** Words copied from that field, when the finding is about some of them. */
|
|
1964
|
+
quote?: string;
|
|
1965
|
+
}
|
|
1966
|
+
/** What a host's critique port returns. */
|
|
1967
|
+
interface AiCritiqueResult {
|
|
1968
|
+
findings: AiCritiqueFindingResult[];
|
|
1969
|
+
provenance?: AiProvenance;
|
|
1970
|
+
usage?: GraderUsage;
|
|
1971
|
+
}
|
|
1972
|
+
/**
|
|
1973
|
+
* A model's finding, as the SDK accepted it: an `ItemFinding` — always
|
|
1974
|
+
* `advice`, since it is a model's opinion — whose code is `ai_` and the kind
|
|
1975
|
+
* (`ai_second_answer`), with the quote it pointed at.
|
|
1976
|
+
*/
|
|
1977
|
+
interface AiCritiqueFinding extends ItemFinding {
|
|
1978
|
+
severity: 'advice';
|
|
1979
|
+
quote?: string;
|
|
1980
|
+
}
|
|
1981
|
+
/** A critique, as the SDK accepted it. */
|
|
1982
|
+
interface AiCritique {
|
|
1983
|
+
findings: AiCritiqueFinding[];
|
|
1984
|
+
provenance?: AiProvenance;
|
|
1985
|
+
usage?: GraderUsage;
|
|
1986
|
+
}
|
|
1987
|
+
/**
|
|
1988
|
+
* The types a model can draft from a source. An interactive video takes the
|
|
1989
|
+
* first five; a written response is for a quiz of its own.
|
|
1990
|
+
*/
|
|
1991
|
+
type AiDraftType = 'multiple-choice' | 'fill-in-the-blanks' | 'gap-select' | 'dictation' | 'read-aloud' | 'written-response';
|
|
1992
|
+
/**
|
|
1993
|
+
* What drafts are written from: a passage or a script as text — an item
|
|
1994
|
+
* group's `stimulus.transcript` is one — or a video's captions, each with its
|
|
1995
|
+
* start and end in seconds, so a question can be placed where its caption ends.
|
|
1996
|
+
*/
|
|
1997
|
+
type AiDraftSource = {
|
|
1998
|
+
kind: 'passage' | 'transcript';
|
|
1999
|
+
text: string;
|
|
2000
|
+
} | {
|
|
2001
|
+
kind: 'captions';
|
|
2002
|
+
cues: {
|
|
2003
|
+
start: number;
|
|
2004
|
+
end: number;
|
|
2005
|
+
text: string;
|
|
2006
|
+
}[];
|
|
2007
|
+
};
|
|
2008
|
+
/** A caption as a model is given it: numbered, so a draft can name the one it is about. */
|
|
2009
|
+
interface AiDraftCaption {
|
|
2010
|
+
index: number;
|
|
2011
|
+
start: number;
|
|
2012
|
+
end: number;
|
|
2013
|
+
text: string;
|
|
2014
|
+
}
|
|
2015
|
+
/** One problem with a draft, as a model is told it on a repair. */
|
|
2016
|
+
interface AiDraftProblem {
|
|
2017
|
+
path: string[];
|
|
2018
|
+
code: string;
|
|
2019
|
+
message: string;
|
|
2020
|
+
severity: 'incomplete' | 'invalid' | 'warning';
|
|
2021
|
+
}
|
|
2022
|
+
/**
|
|
2023
|
+
* Fields of the host's for every draft of a type — settings a model never
|
|
2024
|
+
* writes: a read-aloud's `recording`, `scoring` and region-tagged `locale`, a
|
|
2025
|
+
* question's `shuffle` or `scoringStrategy`. They are put on each draft of
|
|
2026
|
+
* that type before it is checked, and never override what the model wrote, nor
|
|
2027
|
+
* the draft's `id`, `type` or `schemaVersion`.
|
|
2028
|
+
*/
|
|
2029
|
+
type AiDraftSettings = Partial<Record<AiDraftType, Readonly<Record<string, unknown>>>>;
|
|
2030
|
+
/**
|
|
2031
|
+
* What a host's model is given to draft items. Built by `aiDraftsRequest`, and
|
|
2032
|
+
* by `aiDraftsRepairRequest` for a second pass over the drafts that came back
|
|
2033
|
+
* unfinished or flawed in ways the model can fix.
|
|
2034
|
+
*/
|
|
2035
|
+
interface AiDraftsRequest {
|
|
2036
|
+
feature: 'draft-generation';
|
|
2037
|
+
facts: {
|
|
2038
|
+
/**
|
|
2039
|
+
* The types the author chose, in the author's order: the ones to lean on
|
|
2040
|
+
* first, and the order questions are shown in where several share a moment
|
|
2041
|
+
* of a video — or share the end of one.
|
|
2042
|
+
*/
|
|
2043
|
+
activityTypes: AiDraftType[];
|
|
2044
|
+
source: {
|
|
2045
|
+
kind: 'passage' | 'transcript';
|
|
2046
|
+
text: string;
|
|
2047
|
+
} | {
|
|
2048
|
+
kind: 'captions';
|
|
2049
|
+
captions: AiDraftCaption[];
|
|
2050
|
+
};
|
|
2051
|
+
/** How many drafts to write, at most. Absent: as many as the source is worth, up to `AI_DRAFTS_MAX_COUNT`. */
|
|
2052
|
+
count?: number;
|
|
2053
|
+
/** The language the items are written in. */
|
|
2054
|
+
locale?: string;
|
|
2055
|
+
/** The level they are for, as the host names it (`A2`, `B1`). */
|
|
2056
|
+
level?: string;
|
|
2057
|
+
/** What the author asked for, in their words. */
|
|
2058
|
+
instructions?: string;
|
|
2059
|
+
};
|
|
2060
|
+
/**
|
|
2061
|
+
* The JSON Schema of a reply — `{ drafts: [...] }` — for a model's structured
|
|
2062
|
+
* output, or its prompt. Plain JSON Schema a model can follow: types, required
|
|
2063
|
+
* fields, enums and descriptions, nothing a provider's structured output
|
|
2064
|
+
* commonly refuses. Each draft names its `type`, and fills the fields that
|
|
2065
|
+
* type uses.
|
|
2066
|
+
*/
|
|
2067
|
+
shape: Readonly<Record<string, unknown>>;
|
|
2068
|
+
/** The host's settings for every draft of a type. Not for the model; `checkAiDrafts` applies them. */
|
|
2069
|
+
settings?: AiDraftSettings;
|
|
2070
|
+
/**
|
|
2071
|
+
* On a repair: the drafts to fix, each as the model wrote it, with what is
|
|
2072
|
+
* wrong with it that the model can fix. The reply holds one draft for each,
|
|
2073
|
+
* in this order.
|
|
2074
|
+
*/
|
|
2075
|
+
repair?: {
|
|
2076
|
+
index: number;
|
|
2077
|
+
draft: Readonly<Record<string, unknown>>;
|
|
2078
|
+
problems: AiDraftProblem[];
|
|
2079
|
+
}[];
|
|
2080
|
+
}
|
|
2081
|
+
/** A draft as the SDK made it from a reply: ids minted, settings applied, checked, and critiqued. */
|
|
2082
|
+
interface AiGeneratedDraft {
|
|
2083
|
+
/** Its position in the first reply; a repaired draft keeps the position it replaces. */
|
|
2084
|
+
index: number;
|
|
2085
|
+
type: AiDraftType;
|
|
2086
|
+
/** The draft, an activity of its type once `validation` says `complete`. */
|
|
2087
|
+
draft: Record<string, unknown>;
|
|
2088
|
+
/** What `validateDraft` says of it. `complete` means valid — never approved. */
|
|
2089
|
+
validation: DraftValidationResult<unknown>;
|
|
2090
|
+
/** What the item critic says of it. */
|
|
2091
|
+
findings: ItemFinding[];
|
|
2092
|
+
/**
|
|
2093
|
+
* From captions: where the question goes, in seconds — the end of the
|
|
2094
|
+
* caption it named. Absent when it named no caption the source has: it
|
|
2095
|
+
* belongs at the end of the video.
|
|
2096
|
+
*/
|
|
2097
|
+
at?: number;
|
|
2098
|
+
/**
|
|
2099
|
+
* A dictation from captions whose transcript is words of the caption it
|
|
2100
|
+
* named: that caption's start and end, in seconds — the stretch of the
|
|
2101
|
+
* video that says it, for a host that cuts the recording from the video.
|
|
2102
|
+
*/
|
|
2103
|
+
clip?: {
|
|
2104
|
+
start: number;
|
|
2105
|
+
end: number;
|
|
2106
|
+
};
|
|
2107
|
+
/** The draft as the model wrote it, for a repair. */
|
|
2108
|
+
generated: Readonly<Record<string, unknown>>;
|
|
2109
|
+
}
|
|
2110
|
+
/** What `checkAiDrafts` makes of a reply. */
|
|
2111
|
+
interface AiDrafts {
|
|
2112
|
+
/** The drafts in the author's order of types, and in the order the model wrote them within a type. */
|
|
2113
|
+
drafts: AiGeneratedDraft[];
|
|
2114
|
+
/** What only the set shows: every right option in one place (`set_key_position_same`). */
|
|
2115
|
+
findings: ItemFinding[];
|
|
2116
|
+
provenance?: AiProvenance;
|
|
2117
|
+
usage?: GraderUsage;
|
|
2118
|
+
}
|
|
2119
|
+
/** One call `generateDrafts` made: what it cost, or why it went no further. */
|
|
2120
|
+
type AiDraftsCall = {
|
|
2121
|
+
ok: true;
|
|
2122
|
+
provenance?: AiProvenance;
|
|
2123
|
+
usage?: GraderUsage;
|
|
2124
|
+
} | {
|
|
2125
|
+
ok: false;
|
|
2126
|
+
refusal?: AiRefusal;
|
|
2127
|
+
error?: string;
|
|
2128
|
+
};
|
|
2129
|
+
/** What `generateDrafts` came to. */
|
|
2130
|
+
interface AiDraftsRun {
|
|
2131
|
+
/** The drafts, in the author's order of types, each the best pass of it. */
|
|
2132
|
+
drafts: AiGeneratedDraft[];
|
|
2133
|
+
/** What only the set shows, over the drafts as they ended. */
|
|
2134
|
+
findings: ItemFinding[];
|
|
2135
|
+
/** Every call, in order: the first, then each repair. */
|
|
2136
|
+
calls: AiDraftsCall[];
|
|
2137
|
+
}
|
|
2138
|
+
/**
|
|
2139
|
+
* An interactive video made of drafts: an item group whose stimulus is the
|
|
2140
|
+
* video and whose timeline opens a quiz at each moment a draft was placed —
|
|
2141
|
+
* and one at the end for every draft placed nowhere. A draft, like every
|
|
2142
|
+
* draft: checked, critiqued, and approved by a person, never by the SDK.
|
|
2143
|
+
*/
|
|
2144
|
+
interface AiVideoDraft {
|
|
2145
|
+
/** The item group, an interactive video once `validation` says `complete`. */
|
|
2146
|
+
group: Record<string, unknown>;
|
|
2147
|
+
/** What `validateItemGroupDraft` says of it. */
|
|
2148
|
+
validation: DraftValidationResult<unknown>;
|
|
2149
|
+
/** What `critiqueItemGroupDraft` says of it. */
|
|
2150
|
+
findings: ItemFinding[];
|
|
2151
|
+
}
|
|
2152
|
+
//#endregion
|
|
2153
|
+
export { DraftComplete as $, WrittenResponseLearnerResponse as $t, AiGapSelectFacts as A, ItemOutcome as At, AiRefusal as B, ReadAloudData as Bt, AiDraftsCall as C, GapSelectBank as Ct, AiFeature as D, GapSelectLearnerResponse as Dt, AiExplanationRequest as E, GapSelectGap as Et, AiMultipleChoiceFacts as F, MultipleChoiceData as Ft, AiVerdict as G, RecordingBounds as Gt, AiSoundFact as H, ReadAloudDimensionWeight as Ht, AiOptionFact as I, MultipleChoiceLearnerResponse as It, AiWritingCorrection as J, ScoringOutcome as Jt, AiVideoDraft as K, RecordingRef as Kt, AiProvenance as L, MultipleChoiceOption as Lt, AiGrade as M, LearnerResponseMap as Mt, AiHintRequest as N, MediaPlaybackPolicy as Nt, AiFillInTheBlanksFacts as O, InteractionEvent as Ot, AiItemFacts as P, MediaTrack as Pt, AiWritingFeedbackResult as Q, WrittenResponseData as Qt, AiReadingFacts as R, MultipleChoiceOptionMedia as Rt, AiDrafts as S, matchText as Sn, FillInTheBlanksLearnerResponse as St, AiDraftsRun as T, GapSelectData as Tt, AiSupportedActivityType as U, ReadAloudLearnerResponse as Ut, AiRubricCriterionFact as V, ReadAloudDimension as Vt, AiTextResult as W, ReadAloudSlowMedia as Wt, AiWritingFeedback as X, ValidationError as Xt, AiWritingFacts as Y, ScoringResult as Yt, AiWritingFeedbackRequest as Z, ValidationResult as Zt, AiDraftCaption as _, GradingState as _n, DictationEquivalence as _t, AiCoachingRequest as a, XAPIContextActivities as an, ItemFinding as at, AiDraftSource as b, TextMatchResult as bn, DictationTolerance as bt, AiCriterionJudgement as c, XAPIResult as cn, ActivityDataMap as ct, AiCritiqueFinding as d, XAPIVerbObject as dn, ActivityResult as dt, WrittenResponseRubric as en, DraftContext as et, AiCritiqueFindingResult as f, CriterionScore as fn, ActivityType as ft, AiDictationFacts as g, GraderUsage as gn, DictationData as gt, AiCritiqueResult as h, GraderKind as hn, DeferredScoringPartial as ht, AiCoaching as i, XAPIContext as in, DraftValidationResult as it, AiGeneratedDraft as j, LearnerResponse as jt, AiGapFact as k, InteractionKind as kt, AiCritique as l, XAPIScore as ln, ActivityFeedback as lt, AiCritiqueRequest as m, Grader as mn, DeferredReason as mt, AiActivityInput as n, XAPIActor as nn, DraftNotComplete as nt, AiCoachingResult as o, XAPIError as on, ItemFindingSeverity as ot, AiCritiqueKind as p, GradeRecord as pn, BlankConfig as pt, AiWordFact as q, ScoringDetail as qt, AiBlankFact as r, XAPIConfig as rn, DraftSeverity as rt, AiCoachingWord as s, XAPIObject as sn, ActivityData as st, ActivityAiPermissions as t, WrittenResponseRubricCriterion as tn, DraftIssue as tt, AiCritiqueField as u, XAPIStatement as un, ActivityMedia as ut, AiDraftProblem as v, InlineCorrection as vn, DictationLearnerResponse as vt, AiDraftsRequest as w, GapSelectChoice as wt, AiDraftType as x, levenshteinDistance as xn, FillInTheBlanksData as xt, AiDraftSettings as y, TextMatchPolicy as yn, DictationSlowMedia as yt, AiReadingWordFact as z, NativeControlHint as zt };
|
|
2154
|
+
//# sourceMappingURL=ai-B222dtHH.d.cts.map
|