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