@intellectif/lk-core 1.2.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.
Files changed (103) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +2 -2
  3. package/dist/ai-B222dtHH.d.cts +2154 -0
  4. package/dist/ai-B222dtHH.d.cts.map +1 -0
  5. package/dist/ai-B222dtHH.d.ts +2154 -0
  6. package/dist/ai-B222dtHH.d.ts.map +1 -0
  7. package/dist/ai-check.cjs +749 -617
  8. package/dist/ai-check.cjs.map +1 -1
  9. package/dist/ai-check.d.cts +82 -80
  10. package/dist/ai-check.d.cts.map +1 -0
  11. package/dist/ai-check.d.ts +82 -80
  12. package/dist/ai-check.d.ts.map +1 -0
  13. package/dist/ai-check.js +747 -618
  14. package/dist/ai-check.js.map +1 -1
  15. package/dist/ai-writing-CCBjTOM3.js +2496 -0
  16. package/dist/ai-writing-CCBjTOM3.js.map +1 -0
  17. package/dist/ai-writing-DCVkv4Zj.cjs +2735 -0
  18. package/dist/ai-writing-DCVkv4Zj.cjs.map +1 -0
  19. package/dist/{index-BChYtrEe.d.ts → index-BWPRPIXL.d.ts} +263 -258
  20. package/dist/index-BWPRPIXL.d.ts.map +1 -0
  21. package/dist/index-CXyOV8sm.d.cts +153 -0
  22. package/dist/index-CXyOV8sm.d.cts.map +1 -0
  23. package/dist/index-CegM8o15.d.ts +153 -0
  24. package/dist/index-CegM8o15.d.ts.map +1 -0
  25. package/dist/index-Dyu66R4-.d.cts +1071 -0
  26. package/dist/index-Dyu66R4-.d.cts.map +1 -0
  27. package/dist/{index-VXt_v3YI.d.cts → index-O4_ExlAD.d.cts} +263 -258
  28. package/dist/index-O4_ExlAD.d.cts.map +1 -0
  29. package/dist/index-UGxRSopE.d.ts +1071 -0
  30. package/dist/index-UGxRSopE.d.ts.map +1 -0
  31. package/dist/index.cjs +1210 -1146
  32. package/dist/index.cjs.map +1 -1
  33. package/dist/index.d.cts +909 -857
  34. package/dist/index.d.cts.map +1 -0
  35. package/dist/index.d.ts +909 -857
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +1063 -1147
  38. package/dist/index.js.map +1 -1
  39. package/dist/item-group-DXiR8nOr.js +445 -0
  40. package/dist/item-group-DXiR8nOr.js.map +1 -0
  41. package/dist/item-group-DroFXgLb.cjs +505 -0
  42. package/dist/item-group-DroFXgLb.cjs.map +1 -0
  43. package/dist/registry-DVi0LR_i.cjs +5915 -0
  44. package/dist/registry-DVi0LR_i.cjs.map +1 -0
  45. package/dist/registry-Dsx9mNsS.js +5449 -0
  46. package/dist/registry-Dsx9mNsS.js.map +1 -0
  47. package/dist/schemas-Bdw0nGPo.js +152 -0
  48. package/dist/schemas-Bdw0nGPo.js.map +1 -0
  49. package/dist/schemas-DVbAOrtd.cjs +224 -0
  50. package/dist/schemas-DVbAOrtd.cjs.map +1 -0
  51. package/dist/schemas.cjs +16 -34
  52. package/dist/schemas.d.cts +2 -2
  53. package/dist/schemas.d.ts +2 -2
  54. package/dist/schemas.js +3 -34
  55. package/dist/scoring-B2_HEhMT.cjs +1748 -0
  56. package/dist/scoring-B2_HEhMT.cjs.map +1 -0
  57. package/dist/scoring-BZqUkuDF.js +1604 -0
  58. package/dist/scoring-BZqUkuDF.js.map +1 -0
  59. package/dist/scoring.cjs +37 -75
  60. package/dist/scoring.d.cts +3 -1103
  61. package/dist/scoring.d.ts +3 -1103
  62. package/dist/scoring.js +3 -75
  63. package/dist/xapi-CMTph7wG.js +332 -0
  64. package/dist/xapi-CMTph7wG.js.map +1 -0
  65. package/dist/xapi-DNzkSJ0S.cjs +362 -0
  66. package/dist/xapi-DNzkSJ0S.cjs.map +1 -0
  67. package/dist/xapi.cjs +7 -16
  68. package/dist/xapi.d.cts +2 -150
  69. package/dist/xapi.d.ts +2 -150
  70. package/dist/xapi.js +2 -16
  71. package/package.json +4 -4
  72. package/dist/activity-D705CXng.d.cts +0 -2162
  73. package/dist/activity-D705CXng.d.ts +0 -2162
  74. package/dist/chunk-46BXIGLG.cjs +0 -272
  75. package/dist/chunk-46BXIGLG.cjs.map +0 -1
  76. package/dist/chunk-4YANYTFN.cjs +0 -1256
  77. package/dist/chunk-4YANYTFN.cjs.map +0 -1
  78. package/dist/chunk-7KOWU4RO.js +0 -2207
  79. package/dist/chunk-7KOWU4RO.js.map +0 -1
  80. package/dist/chunk-7THXD7AN.cjs +0 -5057
  81. package/dist/chunk-7THXD7AN.cjs.map +0 -1
  82. package/dist/chunk-CYCTIXUN.cjs +0 -118
  83. package/dist/chunk-CYCTIXUN.cjs.map +0 -1
  84. package/dist/chunk-CYPA5BW5.js +0 -118
  85. package/dist/chunk-CYPA5BW5.js.map +0 -1
  86. package/dist/chunk-JTNDIBZO.js +0 -272
  87. package/dist/chunk-JTNDIBZO.js.map +0 -1
  88. package/dist/chunk-MV2N2WNS.js +0 -352
  89. package/dist/chunk-MV2N2WNS.js.map +0 -1
  90. package/dist/chunk-MXI26H62.cjs +0 -2207
  91. package/dist/chunk-MXI26H62.cjs.map +0 -1
  92. package/dist/chunk-QS7B42ZR.js +0 -5057
  93. package/dist/chunk-QS7B42ZR.js.map +0 -1
  94. package/dist/chunk-QX2ERL72.js +0 -1256
  95. package/dist/chunk-QX2ERL72.js.map +0 -1
  96. package/dist/chunk-ZWXQSKCX.cjs +0 -352
  97. package/dist/chunk-ZWXQSKCX.cjs.map +0 -1
  98. package/dist/schemas.cjs.map +0 -1
  99. package/dist/schemas.js.map +0 -1
  100. package/dist/scoring.cjs.map +0 -1
  101. package/dist/scoring.js.map +0 -1
  102. package/dist/xapi.cjs.map +0 -1
  103. package/dist/xapi.js.map +0 -1
@@ -0,0 +1,1071 @@
1
+ import { At as ItemOutcome, Bt as ReadAloudData, Ut as ReadAloudLearnerResponse, Yt as ScoringResult, Zt as ValidationResult, ft as ActivityType, gt as DictationData, jt as LearnerResponse, mn as Grader, pn as GradeRecord, st as ActivityData } from "./ai-B222dtHH.js";
2
+ //#region src/scoring/rounding.d.ts
3
+ /**
4
+ * Rounding for assessment scores.
5
+ *
6
+ * Rounding is **two different operations** that must not share one policy, and
7
+ * conflating them silently inverts one of them:
8
+ *
9
+ * - **Grade rounding** decides the number a learner is shown and recorded
10
+ * against. It rounds to a fixed number of decimal places, conventionally
11
+ * half-up, so a learner shown "70%" is not recorded as a fail at 69.6.
12
+ * - **Band / level classification** decides which level someone is placed in.
13
+ * It deliberately **floors**: placing a learner above their real level is
14
+ * the more harmful error, so a band boundary must not be reached by
15
+ * rounding up.
16
+ *
17
+ * There is therefore no default `RoundingPolicy` anywhere in this SDK, and
18
+ * `dp` has no default either — an integrator's deliberate choice must never be
19
+ * supplied by us.
20
+ */
21
+ /** How a value is rounded to `dp` decimal places. */
22
+ type RoundingMode = 'half-up' | 'half-even' | 'floor' | 'ceil';
23
+ /** A rounding policy. Both fields are required — the SDK never guesses either. */
24
+ interface RoundingPolicy {
25
+ mode: RoundingMode;
26
+ /** Decimal places. Load-bearing: `dp: 2` is what stops 69.6 becoming a fail at 70. */
27
+ dp: number;
28
+ }
29
+ /**
30
+ * Rounds `value` to `policy.dp` places under `policy.mode`.
31
+ *
32
+ * Idempotent for every mode: rounding an already-rounded value returns it
33
+ * unchanged. That matters because a composed score is rounded once and then
34
+ * compared through {@link gte}, which rounds again.
35
+ */
36
+ declare function roundGrade(value: number, policy: RoundingPolicy): number;
37
+ /**
38
+ * Threshold comparison that rounds **both sides** before comparing.
39
+ *
40
+ * Comparing a raw float against a rounded threshold is how a learner ends up
41
+ * shown one number and recorded against another. Rounding both sides — and
42
+ * allowing an epsilon — makes "what the learner sees" and "what the gradebook
43
+ * decides" the same comparison.
44
+ */
45
+ declare function gte(value: number, threshold: number, policy: RoundingPolicy): boolean;
46
+ /** A named band with an inclusive lower bound, e.g. `{ name: 'B1', min: 0.6 }`. */
47
+ interface Band {
48
+ name: string;
49
+ /** Inclusive lower bound on the same scale as the value being classified. */
50
+ min: number;
51
+ }
52
+ /**
53
+ * Classifies a value into a band by **flooring**: the highest band whose `min`
54
+ * the value actually reaches. Deliberately does NOT round up to a boundary —
55
+ * over-placement is the more harmful error, so a learner just below a boundary
56
+ * stays below it.
57
+ *
58
+ * Returns `null` when the value reaches no band's minimum.
59
+ */
60
+ declare function classifyBand(value: number, bands: readonly Band[]): Band | null;
61
+ //#endregion
62
+ //#region src/types/speech.d.ts
63
+ /**
64
+ * How the assessor classified a word against the reference text: `none`, read
65
+ * as written; `mispronunciation`, read but not well enough; `omission`, in the
66
+ * reference but not heard; `insertion`, heard but not in the reference.
67
+ */
68
+ type SpeechWordError = 'none' | 'mispronunciation' | 'omission' | 'insertion';
69
+ /** A phoneme the assessor heard instead of the expected one, and its score. */
70
+ interface SpeechPhonemeCandidate {
71
+ /** The phoneme, in the assessment's `phonemeAlphabet`. */
72
+ symbol: string;
73
+ /** 0..100. */
74
+ score: number;
75
+ }
76
+ /** One phoneme of a word, as the assessor measured it. Assessors report different subsets. */
77
+ interface SpeechPhoneme {
78
+ /** The phoneme, in the assessment's `phonemeAlphabet`. Absent when the assessor does not name it. */
79
+ symbol?: string;
80
+ /** 0..100. Absent means not assessed, never 0. */
81
+ accuracy?: number;
82
+ /** Milliseconds from the start of the recording. */
83
+ startMs?: number;
84
+ /** Milliseconds. */
85
+ durationMs?: number;
86
+ /** The phonemes the assessor heard instead, in the order it reported them. */
87
+ heardAs?: SpeechPhonemeCandidate[];
88
+ }
89
+ /** One syllable of a word, as the assessor measured it. */
90
+ interface SpeechSyllable {
91
+ /** The syllable as the assessor reports it. */
92
+ text: string;
93
+ /** The letters of the word the syllable is written with, when the assessor reports them. */
94
+ grapheme?: string;
95
+ /** 0..100. Absent means not assessed, never 0. */
96
+ accuracy?: number;
97
+ /** Milliseconds from the start of the recording. */
98
+ startMs?: number;
99
+ /** Milliseconds. */
100
+ durationMs?: number;
101
+ }
102
+ /** One word of a {@link SpeechAssessment}. */
103
+ interface SpeechWord {
104
+ /** The word as the assessor reports it. */
105
+ text: string;
106
+ /** 0..100. Absent means not assessed — never read as 0. */
107
+ accuracy?: number;
108
+ /** The assessor's classification of the word. */
109
+ error: SpeechWordError;
110
+ /** The provider's own error label, verbatim, for audit. No grade reads it. */
111
+ vendorError?: string;
112
+ /** Milliseconds from the start of the recording. */
113
+ startMs?: number;
114
+ /** Milliseconds. */
115
+ durationMs?: number;
116
+ syllables?: SpeechSyllable[];
117
+ phonemes?: SpeechPhoneme[];
118
+ /**
119
+ * Confidences (0..1) that an unexpected pause came before the word, or that
120
+ * an expected one is missing. Display only: no grade reads them.
121
+ */
122
+ breaks?: {
123
+ unexpected?: number;
124
+ missing?: number;
125
+ };
126
+ }
127
+ /**
128
+ * A pronunciation assessor's result, in the SDK's neutral shape. Every score in
129
+ * it is on a 0..100 scale. Check one with `validateSpeechAssessment`.
130
+ */
131
+ interface SpeechAssessment {
132
+ assessmentVersion: '1.0';
133
+ /** `no_speech`: the assessor heard nothing to assess — which is never a score of 0. */
134
+ status: 'assessed' | 'no_speech';
135
+ /** `scripted`: measured against a known text. Only a scripted assessment grades a read-aloud item. */
136
+ task: 'scripted' | 'unscripted';
137
+ /** The locale the assessment was made for: canonical, with a region — the rule `ReadAloudData.locale` follows. */
138
+ locale: string;
139
+ /**
140
+ * The text the assessment was made against: the authored reference text,
141
+ * verbatim. Required when scripted, so a grade can refuse evidence made
142
+ * against another text.
143
+ */
144
+ referenceText?: string;
145
+ /**
146
+ * The `RecordingRef.key` of the recording that was assessed. Required when
147
+ * scripted, so a grade can refuse evidence made for another recording.
148
+ */
149
+ recordingKey?: string;
150
+ /** Who measured. Evidence from `'ai'` grades only when the caller opts in. */
151
+ assessor: Grader;
152
+ /** What every score is out of. Stated, so an adapter never leaves it to be assumed. */
153
+ scale: 100;
154
+ /** Utterance-level scores, 0..100. An absent score was not assessed; it is never read as 0. */
155
+ scores: {
156
+ accuracy?: number;
157
+ fluency?: number;
158
+ completeness?: number;
159
+ prosody?: number;
160
+ overall?: number;
161
+ };
162
+ /** What the assessor recognised, as text. */
163
+ recognizedText?: string;
164
+ /**
165
+ * Who judged omissions, insertions and mispronunciations. `assessor`: the
166
+ * assessor did, and its word `error` labels are trusted. `none`: it did not,
167
+ * so a heard word that differs from its reference word is marked
168
+ * mispronounced.
169
+ */
170
+ miscue: 'assessor' | 'none';
171
+ /** The alphabet phoneme symbols are written in. Required whenever a phoneme names a `symbol` or `heardAs`. */
172
+ phonemeAlphabet?: 'ipa' | 'sapi';
173
+ /** The words, in the order the assessor reported them. */
174
+ words: SpeechWord[];
175
+ /** Utterance-level prosody: `monotoneConfidence` is 0..1, display only. */
176
+ prosody?: {
177
+ monotoneConfidence?: number;
178
+ };
179
+ /** Recording quality. Informational only: no grade reads it. */
180
+ signal?: {
181
+ snrDb?: number;
182
+ };
183
+ }
184
+ /**
185
+ * The server's own measurement of a recording, from `inspectWav`. It is what
186
+ * decides whether a take is plausible speech; a duration the client reported
187
+ * never is.
188
+ */
189
+ interface SpeechMeasurement {
190
+ /** Length of the recording, in milliseconds. */
191
+ durationMs: number;
192
+ /** Milliseconds of the recording at or above the silence floor. At most `durationMs`. */
193
+ voicedMs: number;
194
+ }
195
+ /**
196
+ * When a take is too little speech to grade. Both fields are required: the SDK
197
+ * holds no default for either, because each is a decision about an
198
+ * application's learners and its assessor.
199
+ */
200
+ interface SpeechPlausibilityPolicy {
201
+ /** The most words per second of voiced time a genuine reading can reach. Finite and above 0. */
202
+ maxWordsPerSecond: number;
203
+ /** The least voiced time, in milliseconds, a gradable take contains. Finite and at least 0. */
204
+ minVoicedMs: number;
205
+ }
206
+ /**
207
+ * Why a read-aloud take cannot be graded:
208
+ * - `invalid_assessment` — the assessment fails `validateSpeechAssessment`;
209
+ * - `task_mismatch` — it is not a scripted assessment;
210
+ * - `locale_mismatch` — it was made for another locale than the item's;
211
+ * - `reference_mismatch` — it was made against another text than the item's;
212
+ * - `recording_mismatch` — it was made for another recording than the response's;
213
+ * - `assessor_not_accepted` — its assessor is `'ai'`, and the caller did not opt in;
214
+ * - `no_speech` — the assessor heard no speech;
215
+ * - `insufficient_voiced_time` — the recording holds less voiced time than the policy requires;
216
+ * - `implausible_speech_rate` — more words were recognised than the voiced time can hold;
217
+ * - `missing_dimension` — a weighted dimension has no score in the assessment.
218
+ */
219
+ type SpeechUnscorableCode = 'invalid_assessment' | 'task_mismatch' | 'locale_mismatch' | 'reference_mismatch' | 'recording_mismatch' | 'assessor_not_accepted' | 'no_speech' | 'insufficient_voiced_time' | 'implausible_speech_rate' | 'missing_dimension';
220
+ /** A read-aloud take that cannot be graded. Never a zero: see `outcomeFromUnscorable`. */
221
+ interface SpeechUnscorable {
222
+ unscorable: true;
223
+ /** Machine-readable. See {@link SpeechUnscorableCode}. */
224
+ code: SpeechUnscorableCode;
225
+ /** A developer-facing English sentence. */
226
+ reason: string;
227
+ }
228
+ /**
229
+ * Options for `gradeReadAloud`. The plausibility policy is required: the SDK
230
+ * holds no default for a threshold that decides whether a take is graded.
231
+ */
232
+ interface GradeReadAloudOptions {
233
+ /** The server's measurement of the recording (inspectWav). `null` only for a blank response. */
234
+ measured: SpeechMeasurement | null;
235
+ /** When a take is too little speech to grade. */
236
+ plausibility: SpeechPlausibilityPolicy;
237
+ /** Accept `assessor.kind: 'ai'` evidence. Off unless set. */
238
+ allowAiAssessor?: boolean;
239
+ /** Compare the pass line with both sides rounded, as `gradeFromRubric` does. The score stays unrounded. */
240
+ rounding?: RoundingPolicy;
241
+ }
242
+ /** How a word of a read-aloud attempt is marked. */
243
+ type ReadAloudWordState = 'correct' | 'mispronounced' | 'omitted' | 'inserted';
244
+ /**
245
+ * One entry of `alignReadAloud`: a reference word and what was heard for it,
246
+ * or a heard word that is not in the reference.
247
+ */
248
+ interface ReadAloudWordAlignment {
249
+ /** `w<n>` for a reference word, as `dictationReferenceWords` numbers them; absent for an `inserted` word. */
250
+ itemId?: string;
251
+ /** The normalised reference word; `''` for an `inserted` word. */
252
+ reference: string;
253
+ /**
254
+ * The normalised heard word: `''` for an `omitted` word, and also for an
255
+ * `inserted` word whose text normalises to nothing — an assessor can tag a
256
+ * dash as an insertion, and every insertion gets an entry. Branch on `state`,
257
+ * never on the empty string.
258
+ */
259
+ heard: string;
260
+ state: ReadAloudWordState;
261
+ /** The assessor word's accuracy (0..100), when it has one. */
262
+ accuracy?: number;
263
+ /** Index into `assessment.words` of the word this entry came from. */
264
+ wordIndex?: number;
265
+ }
266
+ /**
267
+ * What `inspectWav` found in a recording's bytes: the duration, format, peak
268
+ * level and voiced time of a 16-bit PCM WAV, or why it could not read one.
269
+ */
270
+ type WavInspection = {
271
+ valid: true;
272
+ /** Length of the audio, in milliseconds. */
273
+ durationMs: number;
274
+ sampleRate: number;
275
+ channels: number;
276
+ bitsPerSample: 16;
277
+ /** The loudest sample across every channel, in dBFS; `-Infinity` for digital silence or no samples. */
278
+ peakDbfs: number;
279
+ /** Milliseconds of the windows at or above the policy's silence floor. */
280
+ voicedMs: number;
281
+ } | {
282
+ valid: false;
283
+ reason: 'not_wav' | 'unsupported_encoding' | 'truncated';
284
+ };
285
+ /** How `inspectWav` decides which parts of a recording are voiced. Both fields are required. */
286
+ interface WavInspectionPolicy {
287
+ /** A window whose RMS level is at or above this many dBFS is voiced. Finite, and at most 0. */
288
+ silenceDbfs: number;
289
+ /** The window length, in milliseconds. Finite, and above 0. */
290
+ frameMs: number;
291
+ }
292
+ //#endregion
293
+ //#region src/scoring/compose.d.ts
294
+ /**
295
+ * Sectioned assessment scoring — weights, per-section thresholds, and an
296
+ * explicit reason when an attempt fails.
297
+ *
298
+ * This exists because the formula is invariably implemented twice: once on the
299
+ * server that records the grade, and again on the client that shows a learner
300
+ * their grade breakdown. Two implementations of one formula is exactly the
301
+ * duplication an SDK should remove, and they drift — usually in the scale
302
+ * (0–1 vs 0–100) or in whether a section override is honoured.
303
+ */
304
+ /** One item's contribution to a section. */
305
+ interface ScoredItem {
306
+ /**
307
+ * Identity of the SLOT, not the activity. The same activity can legitimately
308
+ * appear in two sections; keying on the activity id collapses them into one
309
+ * and silently scores the second occurrence as zero.
310
+ */
311
+ slotId: string;
312
+ /** The activity that filled this slot, for reporting. */
313
+ activityId?: string;
314
+ /** Maximum points this slot is worth. */
315
+ points: number;
316
+ /** What the learner achieved, or why there is no grade yet. */
317
+ outcome: ItemOutcome;
318
+ }
319
+ /** A weighted section of an assessment. */
320
+ interface AssessmentSectionInput {
321
+ id: string;
322
+ title?: string;
323
+ /** Relative weight. Weights are normalised by their sum, so they need not total 1. */
324
+ weight: number;
325
+ /** Overrides the assessment-wide section threshold for this section only. */
326
+ passThresholdOverride?: number;
327
+ items: ScoredItem[];
328
+ }
329
+ /** Policy for {@link composeAssessmentScore}. */
330
+ interface CompositionPolicy {
331
+ /** Scaled [0,1] overall score required to pass. */
332
+ passThreshold: number;
333
+ /** Scaled [0,1] score each section must reach, when sections gate the pass. */
334
+ sectionThreshold?: number;
335
+ /**
336
+ * How grades are rounded. Required, with no default: see `RoundingPolicy` —
337
+ * grade rounding and band classification are different operations and the
338
+ * SDK must not choose either for you.
339
+ */
340
+ rounding: RoundingPolicy;
341
+ }
342
+ /** Per-section result. */
343
+ interface SectionScore {
344
+ id: string;
345
+ title?: string;
346
+ /** The authored weight, verbatim. */
347
+ weight: number;
348
+ /**
349
+ * The weight ACTUALLY used in the total, so a client can rebuild the grade
350
+ * from `sections[]` and agree with the record:
351
+ *
352
+ * ```ts
353
+ * roundGrade(
354
+ * sections.reduce((sum, s) => sum + s.score * s.normalizedWeight, 0),
355
+ * policy.rounding,
356
+ * ) === result.score // exact, by construction
357
+ * ```
358
+ *
359
+ * Apply the same final rounding: the raw weighted sum of already-rounded
360
+ * section scores is not itself a rounded value (0.85 and 1.00 at equal
361
+ * weights sum to 0.925 against a recorded 0.93), so comparing it unrounded
362
+ * is off by up to half a quantum.
363
+ *
364
+ * Sections with nothing graded carry `0` here, because they contribute
365
+ * nothing; the remaining weights are renormalised among themselves.
366
+ */
367
+ normalizedWeight: number;
368
+ earnedPoints: number;
369
+ /** Points that are currently gradable — excludes items still awaiting a grade. */
370
+ gradedMaxPoints: number;
371
+ /** Every point in the section, whether graded yet or not. */
372
+ maxPoints: number;
373
+ /** Scaled [0,1] over the GRADED points, rounded once. */
374
+ score: number;
375
+ passed: boolean;
376
+ /** Threshold this section was judged against, after any override. */
377
+ appliedThreshold: number | null;
378
+ /**
379
+ * Slots still awaiting a grade — including any in {@link rejectedSlotIds},
380
+ * whose grade came back unusable and is still owed.
381
+ */
382
+ pendingSlotIds: string[];
383
+ /** See {@link AssessmentScore.rejectedSlotIds}; this section's share of them. */
384
+ rejectedSlotIds?: string[];
385
+ /** Slots that can never be graded, excluded from the denominator. */
386
+ unscorableSlotIds: string[];
387
+ }
388
+ /** Why an attempt failed, or `null` when it passed. */
389
+ type PassFailureReason = 'overall_below_threshold' | 'section_below_threshold' | 'both' | null;
390
+ /** Result of composing an assessment. */
391
+ interface AssessmentScore {
392
+ sections: SectionScore[];
393
+ /** Weighted total, scaled [0,1], rounded once. */
394
+ score: number;
395
+ /**
396
+ * Whether the attempt passed — `null` while `status` is `provisional`,
397
+ * because an attempt with work still ungraded has not passed OR failed yet.
398
+ * Returning `false` there would let a UI keyed on `passed` show a fail for
399
+ * an essay nobody has marked.
400
+ */
401
+ passed: boolean | null;
402
+ /** `null` while provisional, for the same reason as {@link passed}. */
403
+ passFailureReason: PassFailureReason;
404
+ /**
405
+ * `provisional` while any item is still awaiting a grade — the total is
406
+ * computed over what HAS been graded, so it can still move. Do not record a
407
+ * provisional score as final. Items that can NEVER be graded
408
+ * (`unscorableSlotIds`) do not hold the result provisional; items whose
409
+ * grade was rejected (`rejectedSlotIds`) do.
410
+ */
411
+ status: 'final' | 'provisional';
412
+ /**
413
+ * Slots still awaiting a grade, excluded from the denominator. `status` is
414
+ * `provisional` exactly when this is not empty. Includes every slot in
415
+ * {@link rejectedSlotIds}.
416
+ */
417
+ pendingSlotIds: string[];
418
+ /**
419
+ * Slots whose outcome claimed a grade that cannot be one: a `score` or
420
+ * `maxScore` that is not a finite number, a `maxScore` of 0 or less, or a
421
+ * score below 0 or above its `maxScore` (beyond float noise of one part in a
422
+ * billion) — and deferred outcomes `outcomeFromGrade` returned for such a
423
+ * record (`reason: 'grade_rejected'`). Each is also in
424
+ * {@link pendingSlotIds}: its real grade is still owed, so it holds the
425
+ * result `provisional` with `passed: null` rather than being counted (NaN or
426
+ * 4300% on the record) or dropped (a failing grade vanishing into a final
427
+ * pass). Re-grade these slots; waiting will not fix them.
428
+ *
429
+ * Present only when at least one slot was rejected, so a result without one
430
+ * is exactly what earlier versions returned.
431
+ */
432
+ rejectedSlotIds?: string[];
433
+ /** Slots that can never be graded. Excluded from the denominator. */
434
+ unscorableSlotIds: string[];
435
+ }
436
+ /**
437
+ * Composes per-item outcomes into a sectioned assessment score.
438
+ *
439
+ * Weights are normalised by their sum. Each section's score is computed over
440
+ * the points that are actually gradable and rounded ONCE, before any threshold
441
+ * comparison, so the number a learner is shown is the number that decides the
442
+ * outcome. Items still awaiting a grade are excluded from the denominator
443
+ * rather than counted as zero, and the result is reported as `provisional`
444
+ * until every item has a grade. An outcome whose score cannot be a grade out of
445
+ * its `maxScore` is not a grade either: it is held the same way and named in
446
+ * `rejectedSlotIds`.
447
+ */
448
+ declare function composeAssessmentScore(sections: readonly AssessmentSectionInput[], policy: CompositionPolicy): AssessmentScore;
449
+ //#endregion
450
+ //#region src/types/item-scoring.d.ts
451
+ /**
452
+ * How the questions on a paper are scored when a learner can try again or ask
453
+ * for hints: how many tries there are, which one counts, and what each try and
454
+ * each hint costs.
455
+ *
456
+ * It sits beside the {@link DeliveryPolicy} and is set per paper in the same
457
+ * way, but it is a separate object for a reason: every delivery setting takes
458
+ * something away from what a learner is shown and cannot move a grade, while
459
+ * every setting here moves one. A delivery policy can be read charitably; this
460
+ * one cannot, because a grade-moving setting has no safe direction to read a
461
+ * mistake in. {@link resolveItemScoringPolicy} throws on one.
462
+ *
463
+ * Every setting is optional, and absent or `null` is its default. The defaults
464
+ * are exactly how questions were scored before this policy existed: one try,
465
+ * nothing costs anything, and the first (only) try counts.
466
+ *
467
+ * **The SDK's components apply it in `practice`**, where they grade. An exam's
468
+ * hints stay free and uncounted, as they always were. See
469
+ * https://github.com/intellectif/learning-kit/blob/main/docs/scoring.md
470
+ *
471
+ * @see {@link scoreTries} for the arithmetic.
472
+ */
473
+ interface ItemScoringPolicy {
474
+ /**
475
+ * What each hint costs, as a fraction of the question's marks: `0.1` takes
476
+ * a tenth of them per hint. A hint is any a learner is shown before
477
+ * answering — an author's hint on a blank, a dictation's hint word, an AI
478
+ * hint — and is counted in the response's `hintsRevealed`.
479
+ *
480
+ * A number from 0 to 1. Default 0.
481
+ */
482
+ hintPenalty?: number | null;
483
+ /**
484
+ * How many more tries a learner gets after an answer that did not earn full
485
+ * marks: "Try again". A whole number from 0 to 10. Default 0 — one try, as
486
+ * before.
487
+ */
488
+ retries?: number | null;
489
+ /**
490
+ * What each try after the first costs, as a fraction of the question's
491
+ * marks: with `0.25`, a right answer on the second try earns 75%. A number
492
+ * from 0 to 1. Default 0.
493
+ *
494
+ * It applies only where a later try can count — `counts: 'best'` or
495
+ * `'last'` — and {@link validateItemScoringPolicy} refuses it otherwise.
496
+ */
497
+ retryPenalty?: number | null;
498
+ /**
499
+ * Which try's score is the question's score.
500
+ *
501
+ * - `first` (default): the first. Tries after it are for learning, and
502
+ * change no grade — which is why it is the default: switching tries on
503
+ * moves no grade until a school also chooses one of the others.
504
+ * - `best`: the highest, after what each try cost. Never goes down.
505
+ * - `last`: the latest the policy allows.
506
+ */
507
+ counts?: ItemScoringCount | null;
508
+ }
509
+ /** Which try counts: see {@link ItemScoringPolicy.counts}. */
510
+ type ItemScoringCount = 'first' | 'best' | 'last';
511
+ /**
512
+ * A policy with every setting spelled out: what
513
+ * {@link resolveItemScoringPolicy} returns and what an attempt plan records,
514
+ * so the record never depends on a default that may one day change.
515
+ */
516
+ interface ResolvedItemScoringPolicy {
517
+ hintPenalty: number;
518
+ retries: number;
519
+ retryPenalty: number;
520
+ counts: ItemScoringCount;
521
+ }
522
+ /** One thing {@link validateItemScoringPolicy} found wrong with a policy. */
523
+ interface ItemScoringPolicyIssue {
524
+ /** The setting, e.g. `hintPenalty`, or `''` for the policy itself. */
525
+ path: string;
526
+ message: string;
527
+ }
528
+ /** One graded try at a question, as {@link scoreTries} reads it. */
529
+ interface ItemTry {
530
+ /** What the try's answer scored, before anything it cost: from `score()`. */
531
+ score: number;
532
+ maxScore: number;
533
+ /**
534
+ * Hints shown before this try was submitted, counted from the start of the
535
+ * question — so a later try's count includes an earlier one's. The response's
536
+ * `hintsRevealed`. Anything but a whole number of 0 or more reads as 0.
537
+ */
538
+ hintsRevealed?: number | undefined;
539
+ }
540
+ /** One try, as {@link scoreTries} scored it. */
541
+ interface ItemTryScore {
542
+ /** What the answer scored, before anything it cost. */
543
+ score: number;
544
+ /** What it scored after what it cost, never below 0. */
545
+ scored: number;
546
+ maxScore: number;
547
+ /** The hints it was charged for. */
548
+ hintsRevealed: number;
549
+ /** What it cost, as a fraction of `maxScore`: hints, and the tries before it. */
550
+ penalty: number;
551
+ }
552
+ /** A question's score under a policy, from its tries. */
553
+ interface ItemTriesScore {
554
+ /** The counted try's score after what it cost, never below 0. */
555
+ score: number;
556
+ maxScore: number;
557
+ /** 0-based: the try whose score counts. */
558
+ counted: number;
559
+ /**
560
+ * Every try the policy allows, in order, as scored. A try past the
561
+ * allowance (`1 + retries`) is left out: a learner cannot be given one, so a
562
+ * record that holds one is not believed.
563
+ */
564
+ tries: ItemTryScore[];
565
+ }
566
+ //#endregion
567
+ //#region src/scoring/dictation/align.d.ts
568
+ /** One transcript word, or one extra word the learner typed, after alignment. */
569
+ interface DictationWordAlignment {
570
+ /** `w<n>`, the 1-based position of a transcript word; absent for an extra word. */
571
+ itemId?: string;
572
+ /** The transcript word, normalised; `''` for an extra word. */
573
+ reference: string;
574
+ /** The word the learner typed for it, normalised; `''` for a missing word. */
575
+ attempt: string;
576
+ /** How alike the two words are, in [0, 1]; `0` for a missing or an extra word. */
577
+ similarity: number;
578
+ status: 'correct' | 'incorrect' | 'missing' | 'extra';
579
+ }
580
+ /**
581
+ * The comparison a dictation grade is made of: the normalised strings, which
582
+ * candidate transcript the attempt was scored against, and the word pairings.
583
+ * The same data the scorer reads, so a marked display and the grade cannot
584
+ * disagree.
585
+ */
586
+ interface DictationAlignment {
587
+ /**
588
+ * Which candidate the attempt was scored against, by ORIGINAL position:
589
+ * `0` is `transcript`, `n` is `acceptedTranscripts[n − 1]`; `-1` when no
590
+ * candidate survives normalisation (stale or transcript-less data).
591
+ */
592
+ candidateIndex: number;
593
+ /** The chosen candidate, normalised; `''` when `candidateIndex` is `-1`. */
594
+ reference: string;
595
+ /** The attempt, normalised and possibly truncated. */
596
+ attempt: string;
597
+ /**
598
+ * Whether the learner's text was cut: at `DICTATION_MAX_TEXT_LENGTH` before or
599
+ * after normalisation, or while equivalence rules were growing it.
600
+ */
601
+ truncated: boolean;
602
+ /** The similarity of `attempt` to `reference` — the score. */
603
+ similarity: number;
604
+ /** Transcript words and extra words, in the order they align. */
605
+ words: DictationWordAlignment[];
606
+ }
607
+ /**
608
+ * What `alignDictation` reads. Every field optional, and read defensively: full
609
+ * data, a practice payload, a `redact(data, { reveal: 'after-submit' })`
610
+ * projection, or a plain redacted projection with no transcript at all — which
611
+ * yields `candidateIndex: -1`, never a throw.
612
+ */
613
+ type DictationReference = Partial<Pick<DictationData, 'transcript' | 'acceptedTranscripts' | 'tolerance'>>;
614
+ /**
615
+ * The words the scorer can emit a detail for, one list per candidate — index 0
616
+ * is the transcript, index n is `acceptedTranscripts[n − 1]` — each word in the
617
+ * normalised form `correctResponse` carries. A candidate that normalises to
618
+ * nothing gives an empty list. Derivable from the activity alone, which is what
619
+ * a validator of stored details joins against: by id AND by word, because
620
+ * positional ids alone cannot tell an edited transcript of the same length.
621
+ */
622
+ declare function dictationReferenceWords(data: DictationReference): readonly (readonly {
623
+ itemId: string;
624
+ word: string;
625
+ }[])[];
626
+ /**
627
+ * Compares a learner's text with a dictation's transcript(s). Pure and
628
+ * deterministic: no locale, no `Intl`, integer alignment costs, one pinned
629
+ * tie order.
630
+ *
631
+ * The attempt is cut at `DICTATION_MAX_TEXT_LENGTH` code points before and
632
+ * after normalisation — a cut that ends on a space leaves no empty word after
633
+ * it — and `truncated` is also set when the rules grew its
634
+ * working text past the normaliser's bound (see `normalizeDictationText`).
635
+ * Every candidate — the transcript and at most
636
+ * `DICTATION_MAX_ACCEPTED_TRANSCRIPTS` accepted transcripts — is normalised
637
+ * with the same tolerance and cut at `DICTATION_MAX_TRANSCRIPT_LENGTH`; the
638
+ * attempt is scored against each surviving candidate and the first best wins.
639
+ * Words are then paired by a word-level edit distance with exact-equality
640
+ * costs, backtracked from the end preferring a pairing, then a missing
641
+ * transcript word, then an extra typed word; a paired word's similarity is its
642
+ * own character similarity.
643
+ */
644
+ declare function alignDictation(data: DictationReference, text: string): DictationAlignment;
645
+ //#endregion
646
+ //#region src/scoring/dictation/chars.d.ts
647
+ /** One code-point edit between a reference string and an attempt. */
648
+ interface DictationCharOp {
649
+ op: 'equal' | 'substitute' | 'missing' | 'extra';
650
+ /** One code point of the reference, or `''` for an extra character. */
651
+ reference: string;
652
+ /** One code point of the attempt, or `''` for a missing character. */
653
+ attempt: string;
654
+ }
655
+ /**
656
+ * The code-point edit operations between two NORMALISED strings — the
657
+ * `reference` and `attempt` of a `DictationAlignment`, or one word pair from
658
+ * it — for a marked display: a character the learner typed right, typed
659
+ * wrong, left out, or added. Ties are broken preferring an equal or substituted
660
+ * pair, then a missing character, then an extra one, from the end of both
661
+ * strings; the number of non-`equal` ops is the edit distance.
662
+ *
663
+ * Decoration, never grading: it needs O(|reference|·|attempt|) memory, so the
664
+ * scorer does not call it, and a display computes it once per result.
665
+ */
666
+ declare function diffDictationChars(reference: string, attempt: string): DictationCharOp[];
667
+ //#endregion
668
+ //#region src/scoring/dictation/normalize.d.ts
669
+ /**
670
+ * The longest transcript a dictation accepts, in code points, before and after
671
+ * its equivalences are applied — and its rules may not grow it past twice
672
+ * `DICTATION_MAX_TEXT_LENGTH` on the way. A dictation is a sentence or two; the
673
+ * cap keeps the scorer's quadratic step bounded on the server that grades an
674
+ * exam.
675
+ */
676
+ declare const DICTATION_MAX_TRANSCRIPT_LENGTH = 2000;
677
+ /**
678
+ * The longest learner text the scorer compares, in code points. Text beyond it
679
+ * is cut off — before normalisation, and again after it, because a rewrite rule
680
+ * or a folded character can lengthen text — and the alignment says so.
681
+ */
682
+ declare const DICTATION_MAX_TEXT_LENGTH = 8000;
683
+ /** The longest `from` and `to` of an equivalence rule, in code points. */
684
+ declare const DICTATION_MAX_EQUIVALENCE_LENGTH = 200;
685
+ /**
686
+ * The most equivalence rules a dictation carries. The scorer reads no rule
687
+ * past this many, so stale data with more cannot slow a grading run.
688
+ */
689
+ declare const DICTATION_MAX_EQUIVALENCES = 100;
690
+ /**
691
+ * The most accepted transcripts a dictation carries. The scorer reads no more
692
+ * than this many, for the same reason.
693
+ */
694
+ declare const DICTATION_MAX_ACCEPTED_TRANSCRIPTS = 10;
695
+ //#endregion
696
+ //#region src/scoring/item-scoring.d.ts
697
+ /**
698
+ * The policy that changes nothing: one try, nothing costs anything, the first
699
+ * try counts. What an absent policy resolves to, and exactly how questions were
700
+ * scored before policies existed.
701
+ */
702
+ declare const DEFAULT_ITEM_SCORING_POLICY: ResolvedItemScoringPolicy;
703
+ /** The most tries after the first a policy can give. */
704
+ declare const ITEM_SCORING_MAX_RETRIES = 10;
705
+ /**
706
+ * Checks a policy before it is stored or used: every setting absent, `null`,
707
+ * or a value it can take — costs from 0 to 1, `retries` a whole number from 0
708
+ * to {@link ITEM_SCORING_MAX_RETRIES}, `counts` one of `first`, `best` or
709
+ * `last` — and no setting it does not know.
710
+ *
711
+ * It also refuses a `retryPenalty` above 0 while the first try counts: that
712
+ * cost can never be charged, and a school that set it believes retries cost
713
+ * something. Choose `counts: 'best'` or `'last'`.
714
+ */
715
+ declare function validateItemScoringPolicy(policy: unknown): {
716
+ success: true;
717
+ data: ItemScoringPolicy;
718
+ } | {
719
+ success: false;
720
+ issues: ItemScoringPolicyIssue[];
721
+ };
722
+ /**
723
+ * A policy with every setting spelled out.
724
+ *
725
+ * **It throws rather than guess.** A delivery policy reads a setting it cannot
726
+ * read as a restriction, because that is the safe way round; a setting here
727
+ * moves a grade, and there is no safe way round — a cost misread as 0 raises
728
+ * grades, misread as 1 lowers them. So a policy {@link validateItemScoringPolicy}
729
+ * refuses is refused here too, with a `RangeError` naming what is wrong, as an
730
+ * unreadable `rounding` is.
731
+ */
732
+ declare function resolveItemScoringPolicy(policy: unknown): ResolvedItemScoringPolicy;
733
+ /**
734
+ * A question's score under a policy, from its tries in the order they were
735
+ * made.
736
+ *
737
+ * - **What a try costs** is a fraction of the question's marks, subtracted
738
+ * from what its answer scored: `hintPenalty` for every hint shown before it
739
+ * (its `hintsRevealed`, which counts from the start of the question), and
740
+ * `retryPenalty` for every try before it. A try never scores below 0.
741
+ * - **Which try counts** is the policy's `counts`: the first, the best after
742
+ * costs (the earliest, where two are equal), or the last.
743
+ * - **Only the tries the policy allows are read**: the first `1 + retries`. A
744
+ * record with more cannot have come from a learner under this policy.
745
+ *
746
+ * Nothing that cost nothing is changed: with no cost to charge, a try's score
747
+ * is exactly what its answer scored.
748
+ *
749
+ * @throws RangeError for a policy {@link resolveItemScoringPolicy} refuses, for
750
+ * an empty list of tries, and for a try whose `score` and `maxScore`
751
+ * cannot be a grade — the rule `outcomeFromGrade` applies: `maxScore`
752
+ * positive and finite, `score` finite from 0 to it.
753
+ */
754
+ declare function scoreTries(tries: readonly ItemTry[], policy?: ItemScoringPolicy | ResolvedItemScoringPolicy | null): ItemTriesScore;
755
+ //#endregion
756
+ //#region src/scoring/pass-threshold.d.ts
757
+ /** Default minimum scaled score required to pass when `passThreshold` is absent. */
758
+ declare const DEFAULT_PASS_THRESHOLD = 0.7;
759
+ /**
760
+ * Returns `true` iff `score` meets or exceeds the activity's `passThreshold`,
761
+ * defaulting to {@link DEFAULT_PASS_THRESHOLD} (0.7) when the field is absent.
762
+ *
763
+ * Pass a {@link RoundingPolicy} to compare the way an assessment total is
764
+ * compared — both sides rounded, via {@link gte} — so an item shown as "70%"
765
+ * cannot be recorded as a fail at 69.6. It is **opt-in** rather than the
766
+ * default because switching it on changes item-level pass/fail for scores in
767
+ * the rounding band, and this SDK does not alter historical grades without an
768
+ * explicit decision. Absent, the comparison is the exact raw `>=` it has
769
+ * always been.
770
+ */
771
+ declare function computePassThreshold(activityData: ActivityData, score: number, rounding?: RoundingPolicy): boolean;
772
+ //#endregion
773
+ //#region src/scoring/speech/align.d.ts
774
+ /**
775
+ * Pairs the words of `assessment` with the words of `data.referenceText`, in
776
+ * reading order: what was read as written, what was mispronounced, what was
777
+ * left out, and what was said that the text does not contain.
778
+ *
779
+ * Reference words are the dictation tokeniser's, so `itemId` is the `w<n>`
780
+ * `dictationReferenceWords` gives, and both `reference` and `heard` are
781
+ * normalised — lowercased, with punctuation and spacing folded away. One
782
+ * assessor word can normalise to no token at all (a dash), or to several (a
783
+ * compatibility form), and two can merge into one (`rock&roll`); tokens are
784
+ * what is aligned, and each remembers the word it came from.
785
+ *
786
+ * A word the assessor tagged `insertion` is never aligned — the reference has
787
+ * nothing for it — so it is placed by the position of its own word, after the
788
+ * last token that came from an earlier word.
789
+ *
790
+ * Whether a paired word counts as read correctly follows `assessment.miscue`:
791
+ * an assessor that judges miscues is trusted, and one that does not
792
+ * (`miscue: 'none'`) has its recognised word compared with the reference word.
793
+ *
794
+ * `heard` is `''` for an omitted word, and also for an inserted word whose text
795
+ * normalises to nothing. Branch on `state`, never on the empty string.
796
+ *
797
+ * The heard tokens are bounded: they are taken in reading order until what the
798
+ * assessment's words spell would pass `DICTATION_MAX_TEXT_LENGTH` code points —
799
+ * the cap the dictation aligner reads a learner's text under — and always on a
800
+ * whole token, because half a token pairs with the wrong word. Every word is
801
+ * charged, an inserted one included, so that total is the whole of the text
802
+ * this function can answer with. Words past the budget contribute no token, so
803
+ * each reference word left with nothing read for it is `omitted`.
804
+ * `validateSpeechAssessment` refuses an assessment whose words spell more than
805
+ * that, measured by the same walk and against the same number, so a validated
806
+ * assessment never reaches the budget: it is a backstop, kept because this
807
+ * function is public and can be handed evidence this build did not check.
808
+ *
809
+ * **`data` is not validated.** Only the assessment is. A `referenceText` longer
810
+ * than `READ_ALOUD_MAX_REFERENCE_LENGTH` code points is cut by the tokeniser
811
+ * with no signal, and one that is absent or is not a string yields no reference
812
+ * word at all, so every heard token comes back `inserted` rather than refused.
813
+ * Validate the item with `validateActivity('read-aloud', data)` first.
814
+ * `gradeReadAloud` is safe without that because it runs the schema itself.
815
+ *
816
+ * Pure and deterministic: no locale, no `Intl`, no clock.
817
+ *
818
+ * @throws TypeError when `assessment` is not a well-formed
819
+ * {@link SpeechAssessment}; check it with `validateSpeechAssessment` first.
820
+ */
821
+ declare function alignReadAloud(data: Pick<ReadAloudData, 'referenceText'>, assessment: SpeechAssessment): ReadAloudWordAlignment[];
822
+ //#endregion
823
+ //#region src/scoring/speech/assessment.d.ts
824
+ /**
825
+ * Checks that `value` is a well-formed {@link SpeechAssessment}. It answers
826
+ * rather than throwing: anything that is not one — a non-object, an unknown key
827
+ * at any depth, a score off the 0..100 scale, a time that is negative or not
828
+ * finite — comes back as `{ success: false, errors }`, one error per problem,
829
+ * each with the path, message and code `validateActivity` gives a schema
830
+ * failure. An object that throws when it is read is the one exception, and the
831
+ * throw is its own: an own property getter, or a `Proxy` trap.
832
+ *
833
+ * Three rules span fields:
834
+ * - `scripted_binding_required` — a `scripted` assessment without
835
+ * `referenceText`, or without `recordingKey`, reported at each missing path;
836
+ * - `phoneme_alphabet_required` — a phoneme names a `symbol` or `heardAs` while
837
+ * `phonemeAlphabet` is absent, reported once, at `phonemeAlphabet`;
838
+ * - `too_big` at `words` — the words carry more text between them, written or
839
+ * normalised, than can be aligned against an item's text. Per-word caps bound
840
+ * no total, and the aligner pairs tokens, of which one word holds as many as
841
+ * its text spells. Every word counts towards both totals, an insertion
842
+ * included: the aligner carries one into its answer, so its text is text the
843
+ * call must hold.
844
+ *
845
+ * The total is measured on the words every field rule accepted, so it says
846
+ * nothing about an assessment that is going back to its adapter anyway.
847
+ *
848
+ * It checks the evidence alone. Whether an assessment belongs to an item and
849
+ * to a learner's recording is decided when it is graded.
850
+ */
851
+ declare function validateSpeechAssessment(value: unknown): ValidationResult<SpeechAssessment>;
852
+ //#endregion
853
+ //#region src/scoring/speech/grade.d.ts
854
+ /**
855
+ * Grades a read-aloud take from a {@link SpeechAssessment} the application
856
+ * obtained, the response that was submitted, and the server's own measurement
857
+ * of the recording.
858
+ *
859
+ * The weighted total is computed by `gradeFromRubric` from the authored
860
+ * dimensions that carry a weight above 0, each out of 100. The per-word marks
861
+ * in `details` are for display and review: **they never feed the score**, which
862
+ * is the assessor's utterance-level judgement, weighted as the item authored it.
863
+ *
864
+ * Evidence is refused, never scored 0, when it cannot be tied to this item and
865
+ * this recording, when the assessor heard nothing, when the recording holds too
866
+ * little voiced time for the words claimed, or when a weighted dimension has no
867
+ * score — see {@link SpeechUnscorableCode}. Store a refusal with
868
+ * `outcomeFromUnscorable`.
869
+ *
870
+ * A blank (`response.recording === null`) is a grade of 0 with a mark per
871
+ * reference word: the learner submitted, and read nothing. The assessment and
872
+ * the measurement are not read for it.
873
+ *
874
+ * @throws RangeError when `options.plausibility` or `options.rounding` cannot be
875
+ * applied, or when `options.measured` is not a pair of non-negative, finite
876
+ * millisecond counts with `voicedMs` inside `durationMs`.
877
+ * @throws TypeError when `response` is not a read-aloud response, or when a
878
+ * non-blank response is graded without an assessment or without a measurement.
879
+ * @throws ActivitySchemaError when `data` is not valid read-aloud data.
880
+ * @throws RedactedScoringError when `data` is a `redact()` projection. A
881
+ * read-aloud projection is a valid activity, so the authored feedback would
882
+ * simply be missing from the grade; grade against the full data server-side.
883
+ */
884
+ declare function gradeReadAloud(data: ReadAloudData, response: ReadAloudLearnerResponse, assessment: SpeechAssessment | null, options: GradeReadAloudOptions): GradeRecord | SpeechUnscorable;
885
+ //#endregion
886
+ //#region src/scoring/speech/limits.d.ts
887
+ /**
888
+ * The longest reference text a read-aloud item accepts, in code points, before
889
+ * and after normalisation. It is the dictation transcript cap on purpose: the
890
+ * reference is tokenised as a dictation transcript is, and the dictation
891
+ * normaliser cuts a transcript at that many code points without saying so, so
892
+ * a longer text would be graded against words it had silently lost.
893
+ */
894
+ declare const READ_ALOUD_MAX_REFERENCE_LENGTH = 2000;
895
+ /** The longest take a read-aloud item may allow, in seconds: the most `recording.maxSeconds` may be. */
896
+ declare const READ_ALOUD_MAX_SECONDS = 300;
897
+ /** The most takes a read-aloud item may allow: the most `recording.maxTakes` may be. */
898
+ declare const READ_ALOUD_MAX_TAKES = 20;
899
+ /**
900
+ * The largest weight a read-aloud dimension may carry. Weights are relative, so
901
+ * no rubric needs more, and the cap keeps a mistyped weight from overflowing
902
+ * the weighted total into a grade that cannot be computed.
903
+ */
904
+ declare const READ_ALOUD_MAX_DIMENSION_WEIGHT = 1000;
905
+ /**
906
+ * The most words a speech assessment may carry. It bounds one side of the
907
+ * alignment, and one side alone bounds nothing: the aligner pairs TOKENS, and
908
+ * one word normalises to as many as its text holds. What bounds the quadratic
909
+ * work is this cap, the 200-unit cap on a single word's text, and the two rules
910
+ * `validateSpeechAssessment` states over the total of them — what the words are
911
+ * written with, and what they spell once normalised.
912
+ */
913
+ declare const SPEECH_ASSESSMENT_MAX_WORDS = 1000;
914
+ //#endregion
915
+ //#region src/scoring/speech/wav.d.ts
916
+ /**
917
+ * Reads a 16-bit PCM WAV and measures it: `durationMs`, `sampleRate`,
918
+ * `channels`, the loudest sample as `peakDbfs`, and `voicedMs`, the time in
919
+ * windows whose RMS level reaches the policy's silence floor.
920
+ *
921
+ * Anything it cannot measure comes back as `{ valid: false, reason }` rather
922
+ * than a guess: `not_wav` for bytes that are not a RIFF/WAVE file, or whose
923
+ * chunks do not describe the audio before the audio arrives;
924
+ * `unsupported_encoding` for a WAV this reader does not read (compressed, or
925
+ * samples that are not 16-bit); `truncated` when the data chunk is missing or
926
+ * shorter than it declares.
927
+ *
928
+ * Both policy fields are required, because neither has an answer that is right
929
+ * for every microphone: `silenceDbfs` is where an application draws the line
930
+ * between silence and speech, and `frameMs` is how finely it looks.
931
+ *
932
+ * Pure and deterministic. It never throws on the bytes themselves — only on
933
+ * arguments it cannot use.
934
+ *
935
+ * @throws TypeError when `bytes` is not a `Uint8Array`.
936
+ * @throws RangeError when `silenceDbfs` is not a finite number at or below 0,
937
+ * or `frameMs` is not a finite number above 0.
938
+ */
939
+ declare function inspectWav(bytes: Uint8Array, policy: WavInspectionPolicy): WavInspection;
940
+ //#endregion
941
+ //#region src/scoring/unscorable.d.ts
942
+ /**
943
+ * Lifts a result that cannot be graded — a `SpeechUnscorable` from
944
+ * `gradeReadAloud`, or anything else carrying a `code` and a `reason` — into
945
+ * the `unscorable` arm of {@link ItemOutcome}, keeping its code.
946
+ *
947
+ * The code is what an application branches on; the reason is a developer-facing
948
+ * sentence. Stored this way, the result composes exactly as the unscorable
949
+ * outcomes `evaluate` returns do. `evaluate` never writes a code itself.
950
+ */
951
+ declare function outcomeFromUnscorable(result: {
952
+ code: string;
953
+ reason: string;
954
+ }): ItemOutcome;
955
+ //#endregion
956
+ //#region src/scoring/index.d.ts
957
+ /**
958
+ * Options for {@link score} and {@link evaluate}. Additive: with none, both
959
+ * behave exactly as they always have.
960
+ */
961
+ interface ScoringOptions {
962
+ /**
963
+ * Compare the pass line the way the score is displayed — both sides rounded,
964
+ * via {@link computePassThreshold} — so an item shown as "70%" is not
965
+ * recorded as a fail at 69.995. `passed` AND the authored feedback selected by
966
+ * it follow the rounded comparison. Opt-in, for the reason
967
+ * {@link computePassThreshold} gives: switching it on moves item-level
968
+ * pass/fail for scores in the rounding band.
969
+ *
970
+ * Checked at the call: `null` reads as no policy, and a policy that cannot be
971
+ * applied — an unknown `mode`, or a `dp` that is not a whole number from 0 to
972
+ * 15 — throws a `RangeError` instead of quietly failing every comparison.
973
+ */
974
+ rounding?: RoundingPolicy;
975
+ }
976
+ /**
977
+ * Options for {@link evaluate} and {@link evaluateTries}: {@link ScoringOptions},
978
+ * and the paper's scoring policy.
979
+ */
980
+ interface EvaluateOptions extends ScoringOptions {
981
+ /**
982
+ * What hints and tries cost, and which try counts — see
983
+ * {@link ItemScoringPolicy}. Absent or `null`, nothing costs anything and the
984
+ * outcome is exactly what it was before policies existed.
985
+ *
986
+ * `evaluate` reads the hints from the response's `hintsRevealed`. The count
987
+ * is the client's: when your server knows better — it served the AI hints,
988
+ * say — write its own count there before you evaluate.
989
+ *
990
+ * Checked at the call: a policy `validateItemScoringPolicy` refuses throws a
991
+ * `RangeError`. A grade-moving setting has no safe way to be misread. Under a
992
+ * policy, a scored outcome whose numbers cannot be a grade — a registered
993
+ * scorer out of contract — has nothing to charge a cost to, and comes back
994
+ * `unscorable`.
995
+ */
996
+ scoring?: ItemScoringPolicy | null;
997
+ }
998
+ /**
999
+ * Scores a learner response against activity data and returns a full
1000
+ * {@link ScoringResult}.
1001
+ *
1002
+ * Pure and deterministic with no side effects. It does **not** re-validate
1003
+ * `activityData` — schema validation is the component boundary's
1004
+ * responsibility; this is a low-level scoring primitive that trusts its typed
1005
+ * inputs. Dispatch is registry-backed: consumer-registered types with `sync`
1006
+ * scoring work here too. An unregistered type throws
1007
+ * {@link UnknownActivityTypeError}; a type whose grading is deferred (e.g.
1008
+ * `written-response`) throws {@link DeferredScoringError} — use
1009
+ * {@link evaluate}, which can express "not graded yet".
1010
+ */
1011
+ declare function score(activityType: ActivityType, activityData: ActivityData, learnerResponse: LearnerResponse, options?: ScoringOptions): ScoringResult;
1012
+ /**
1013
+ * Evaluates a learner response against an activity and returns an
1014
+ * {@link ItemOutcome} — the resilient, forward-compatible alternative to
1015
+ * {@link score}:
1016
+ *
1017
+ * - Synchronously graded types return `{ status: 'scored', ... }` with the
1018
+ * same numbers `score()` produces.
1019
+ * - Asynchronously graded types (e.g. `written-response`) return
1020
+ * `{ status: 'deferred', reason, partial }` instead of a fake zero — "not
1021
+ * graded yet" is expressible in the type system, never conflated with
1022
+ * "wrong".
1023
+ * - An unregistered `data.type` returns `{ status: 'unscorable' }` rather
1024
+ * than throwing, so a mixed-version content bank cannot crash an exam run.
1025
+ * - With a scoring policy (`options.scoring`), a scored outcome is charged what
1026
+ * the response's hints cost, and `passed` is read from what is left. The
1027
+ * `details` and authored `feedback` stay the answer's. For several tries at
1028
+ * one question, use {@link evaluateTries}.
1029
+ *
1030
+ * The activity type is read from `data.type` — there is no separate type
1031
+ * parameter to disagree with the payload.
1032
+ */
1033
+ declare function evaluate(data: ActivityData, response: LearnerResponse, options?: EvaluateOptions): ItemOutcome;
1034
+ /** A question's outcome over its tries: see {@link evaluateTries}. */
1035
+ interface ItemTriesOutcome {
1036
+ /**
1037
+ * The question's outcome under the policy. Scored: the counted try's
1038
+ * `details` and authored `feedback` — they describe that answer — with its
1039
+ * `score` after what it cost and `passed` read from that score. Anything
1040
+ * else — deferred, unscorable — is the first try's outcome as `evaluate`
1041
+ * gives it.
1042
+ */
1043
+ outcome: ItemOutcome;
1044
+ /** 0-based: the try whose score counts. `null` when the outcome is not scored. */
1045
+ counted: number | null;
1046
+ /**
1047
+ * Every try the policy allows, as scored: before and after what it cost.
1048
+ * Empty when the outcome is not scored, or when, with no policy, it is an
1049
+ * outcome `evaluate` passes through that cannot be a grade.
1050
+ */
1051
+ tries: ItemTryScore[];
1052
+ }
1053
+ /**
1054
+ * A question's outcome from every try a learner made at it, in order, under a
1055
+ * paper's scoring policy: what a server that scores practice itself calls,
1056
+ * with the tries it stored, to reach the grade the SDK's components reached.
1057
+ *
1058
+ * Each response is scored as {@link evaluate} scores it, and costed and chosen
1059
+ * as {@link scoreTries} does: hints from each response's `hintsRevealed`,
1060
+ * which counts from the start of the question; the tries before each one;
1061
+ * and the try `counts` names. Only the first `1 + retries` responses are read.
1062
+ *
1063
+ * With no policy it is `evaluate` of the first response: one try, as before.
1064
+ * With no response at all, the question was never answered —
1065
+ * `{ status: 'deferred', reason: 'no_response_recorded' }`, as a missing slot
1066
+ * is everywhere else.
1067
+ */
1068
+ declare function evaluateTries(data: ActivityData, responses: readonly LearnerResponse[], options?: EvaluateOptions): ItemTriesOutcome;
1069
+ //#endregion
1070
+ export { SpeechAssessment as $, diffDictationChars as A, ItemTry as B, validateItemScoringPolicy as C, DICTATION_MAX_TEXT_LENGTH as D, DICTATION_MAX_EQUIVALENCE_LENGTH as E, dictationReferenceWords as F, CompositionPolicy as G, ResolvedItemScoringPolicy as H, ItemScoringCount as I, SectionScore as J, PassFailureReason as K, ItemScoringPolicy as L, DictationReference as M, DictationWordAlignment as N, DICTATION_MAX_TRANSCRIPT_LENGTH as O, alignDictation as P, ReadAloudWordState as Q, ItemScoringPolicyIssue as R, scoreTries as S, DICTATION_MAX_EQUIVALENCES as T, AssessmentScore as U, ItemTryScore as V, AssessmentSectionInput as W, GradeReadAloudOptions as X, composeAssessmentScore as Y, ReadAloudWordAlignment as Z, DEFAULT_PASS_THRESHOLD as _, evaluateTries as a, SpeechUnscorable as at, ITEM_SCORING_MAX_RETRIES as b, inspectWav as c, SpeechWordError as ct, READ_ALOUD_MAX_SECONDS as d, Band as dt, SpeechMeasurement as et, READ_ALOUD_MAX_TAKES as f, RoundingMode as ft, alignReadAloud as g, roundGrade as gt, validateSpeechAssessment as h, gte as ht, evaluate as i, SpeechSyllable as it, DictationAlignment as j, DictationCharOp as k, READ_ALOUD_MAX_DIMENSION_WEIGHT as l, WavInspection as lt, gradeReadAloud as m, classifyBand as mt, ItemTriesOutcome as n, SpeechPhonemeCandidate as nt, score as o, SpeechUnscorableCode as ot, SPEECH_ASSESSMENT_MAX_WORDS as p, RoundingPolicy as pt, ScoredItem as q, ScoringOptions as r, SpeechPlausibilityPolicy as rt, outcomeFromUnscorable as s, SpeechWord as st, EvaluateOptions as t, SpeechPhoneme as tt, READ_ALOUD_MAX_REFERENCE_LENGTH as u, WavInspectionPolicy as ut, computePassThreshold as v, DICTATION_MAX_ACCEPTED_TRANSCRIPTS as w, resolveItemScoringPolicy as x, DEFAULT_ITEM_SCORING_POLICY as y, ItemTriesScore as z };
1071
+ //# sourceMappingURL=index-UGxRSopE.d.ts.map