@intellectif/lk-core 0.13.0 → 0.14.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 (55) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/README.md +1 -1
  3. package/dist/{activity-DfAmJ1sl.d.cts → activity-BVkg9VDo.d.cts} +153 -4
  4. package/dist/{activity-DfAmJ1sl.d.ts → activity-BVkg9VDo.d.ts} +153 -4
  5. package/dist/{chunk-DQKJUMUW.cjs → chunk-2NIALQEH.cjs} +24 -19
  6. package/dist/chunk-2NIALQEH.cjs.map +1 -0
  7. package/dist/{chunk-XKKKWKRH.js → chunk-6RND47QE.js} +694 -126
  8. package/dist/chunk-6RND47QE.js.map +1 -0
  9. package/dist/{chunk-FT7SAC3X.cjs → chunk-CZRUMHKW.cjs} +4 -4
  10. package/dist/{chunk-FT7SAC3X.cjs.map → chunk-CZRUMHKW.cjs.map} +1 -1
  11. package/dist/chunk-J5OIRIOW.cjs +1034 -0
  12. package/dist/chunk-J5OIRIOW.cjs.map +1 -0
  13. package/dist/{chunk-Y36S3X3L.js → chunk-NBADQ3JU.js} +7 -2
  14. package/dist/chunk-NBADQ3JU.js.map +1 -0
  15. package/dist/{chunk-PYP3HCRQ.js → chunk-R7PV3XIC.js} +2 -2
  16. package/dist/chunk-RTIR6R2U.js +1034 -0
  17. package/dist/chunk-RTIR6R2U.js.map +1 -0
  18. package/dist/{chunk-XONKWL6D.cjs → chunk-VCXIPLEW.cjs} +585 -17
  19. package/dist/chunk-VCXIPLEW.cjs.map +1 -0
  20. package/dist/{index-Bz9wUigA.d.cts → index-BE1bM2Wi.d.cts} +225 -3
  21. package/dist/{index-CJbbVE9o.d.ts → index-v2_-Fyn3.d.ts} +225 -3
  22. package/dist/index.cjs +113 -80
  23. package/dist/index.cjs.map +1 -1
  24. package/dist/index.d.cts +50 -17
  25. package/dist/index.d.ts +50 -17
  26. package/dist/index.js +39 -6
  27. package/dist/index.js.map +1 -1
  28. package/dist/schemas.cjs +11 -3
  29. package/dist/schemas.cjs.map +1 -1
  30. package/dist/schemas.d.cts +2 -2
  31. package/dist/schemas.d.ts +2 -2
  32. package/dist/schemas.js +10 -2
  33. package/dist/scoring.cjs +23 -3
  34. package/dist/scoring.cjs.map +1 -1
  35. package/dist/scoring.d.cts +471 -16
  36. package/dist/scoring.d.ts +471 -16
  37. package/dist/scoring.js +24 -4
  38. package/dist/xapi.cjs +3 -3
  39. package/dist/xapi.d.cts +1 -1
  40. package/dist/xapi.d.ts +1 -1
  41. package/dist/xapi.js +2 -2
  42. package/package.json +1 -1
  43. package/vectors/README.md +63 -3
  44. package/vectors/replay.d.mts +7 -2
  45. package/vectors/replay.mjs +83 -1
  46. package/vectors/scoring.json +10875 -2
  47. package/dist/chunk-DQKJUMUW.cjs.map +0 -1
  48. package/dist/chunk-FGN4LLSP.js +0 -387
  49. package/dist/chunk-FGN4LLSP.js.map +0 -1
  50. package/dist/chunk-UHQVIC3P.cjs +0 -387
  51. package/dist/chunk-UHQVIC3P.cjs.map +0 -1
  52. package/dist/chunk-XKKKWKRH.js.map +0 -1
  53. package/dist/chunk-XONKWL6D.cjs.map +0 -1
  54. package/dist/chunk-Y36S3X3L.js.map +0 -1
  55. /package/dist/{chunk-PYP3HCRQ.js.map → chunk-R7PV3XIC.js.map} +0 -0
package/dist/scoring.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { w as ItemOutcome, f as DictationData, A as ActivityData, L as LearnerResponse, e as ActivityType, K as ScoringResult } from './activity-DfAmJ1sl.js';
2
- export { T as TextMatchPolicy, O as TextMatchResult, a5 as levenshteinDistance, a6 as matchText } from './activity-DfAmJ1sl.js';
1
+ import { x as ItemOutcome, r as Grader, g as DictationData, A as ActivityData, R as ReadAloudData, _ as ValidationResult, P as ReadAloudLearnerResponse, q as GradeRecord, L as LearnerResponse, e as ActivityType, W as ScoringResult } from './activity-BVkg9VDo.js';
2
+ export { T as RecordingRef, X as TextMatchPolicy, Y as TextMatchResult, ad as levenshteinDistance, ae as matchText } from './activity-BVkg9VDo.js';
3
3
 
4
4
  /**
5
5
  * Rounding for assessment scores.
@@ -188,6 +188,246 @@ interface AssessmentScore {
188
188
  */
189
189
  declare function composeAssessmentScore(sections: readonly AssessmentSectionInput[], policy: CompositionPolicy): AssessmentScore;
190
190
 
191
+ /**
192
+ * Speech assessment: the neutral shape a pronunciation assessor's result is
193
+ * mapped into, and the types a read-aloud grade reads and writes.
194
+ *
195
+ * The SDK calls no assessor and holds no key. The application maps its
196
+ * provider's response into a {@link SpeechAssessment}; the SDK validates that
197
+ * evidence, checks that it belongs to the item and to the learner's recording,
198
+ * and does the arithmetic.
199
+ */
200
+
201
+ /**
202
+ * How the assessor classified a word against the reference text: `none`, read
203
+ * as written; `mispronunciation`, read but not well enough; `omission`, in the
204
+ * reference but not heard; `insertion`, heard but not in the reference.
205
+ */
206
+ type SpeechWordError = 'none' | 'mispronunciation' | 'omission' | 'insertion';
207
+ /** A phoneme the assessor heard instead of the expected one, and its score. */
208
+ interface SpeechPhonemeCandidate {
209
+ /** The phoneme, in the assessment's `phonemeAlphabet`. */
210
+ symbol: string;
211
+ /** 0..100. */
212
+ score: number;
213
+ }
214
+ /** One phoneme of a word, as the assessor measured it. Assessors report different subsets. */
215
+ interface SpeechPhoneme {
216
+ /** The phoneme, in the assessment's `phonemeAlphabet`. Absent when the assessor does not name it. */
217
+ symbol?: string;
218
+ /** 0..100. Absent means not assessed, never 0. */
219
+ accuracy?: number;
220
+ /** Milliseconds from the start of the recording. */
221
+ startMs?: number;
222
+ /** Milliseconds. */
223
+ durationMs?: number;
224
+ /** The phonemes the assessor heard instead, in the order it reported them. */
225
+ heardAs?: SpeechPhonemeCandidate[];
226
+ }
227
+ /** One syllable of a word, as the assessor measured it. */
228
+ interface SpeechSyllable {
229
+ /** The syllable as the assessor reports it. */
230
+ text: string;
231
+ /** The letters of the word the syllable is written with, when the assessor reports them. */
232
+ grapheme?: string;
233
+ /** 0..100. Absent means not assessed, never 0. */
234
+ accuracy?: number;
235
+ /** Milliseconds from the start of the recording. */
236
+ startMs?: number;
237
+ /** Milliseconds. */
238
+ durationMs?: number;
239
+ }
240
+ /** One word of a {@link SpeechAssessment}. */
241
+ interface SpeechWord {
242
+ /** The word as the assessor reports it. */
243
+ text: string;
244
+ /** 0..100. Absent means not assessed — never read as 0. */
245
+ accuracy?: number;
246
+ /** The assessor's classification of the word. */
247
+ error: SpeechWordError;
248
+ /** The provider's own error label, verbatim, for audit. No grade reads it. */
249
+ vendorError?: string;
250
+ /** Milliseconds from the start of the recording. */
251
+ startMs?: number;
252
+ /** Milliseconds. */
253
+ durationMs?: number;
254
+ syllables?: SpeechSyllable[];
255
+ phonemes?: SpeechPhoneme[];
256
+ /**
257
+ * Confidences (0..1) that an unexpected pause came before the word, or that
258
+ * an expected one is missing. Display only: no grade reads them.
259
+ */
260
+ breaks?: {
261
+ unexpected?: number;
262
+ missing?: number;
263
+ };
264
+ }
265
+ /**
266
+ * A pronunciation assessor's result, in the SDK's neutral shape. Every score in
267
+ * it is on a 0..100 scale. Check one with `validateSpeechAssessment`.
268
+ */
269
+ interface SpeechAssessment {
270
+ assessmentVersion: '1.0';
271
+ /** `no_speech`: the assessor heard nothing to assess — which is never a score of 0. */
272
+ status: 'assessed' | 'no_speech';
273
+ /** `scripted`: measured against a known text. Only a scripted assessment grades a read-aloud item. */
274
+ task: 'scripted' | 'unscripted';
275
+ /** The locale the assessment was made for: canonical, with a region — the rule `ReadAloudData.locale` follows. */
276
+ locale: string;
277
+ /**
278
+ * The text the assessment was made against: the authored reference text,
279
+ * verbatim. Required when scripted, so a grade can refuse evidence made
280
+ * against another text.
281
+ */
282
+ referenceText?: string;
283
+ /**
284
+ * The `RecordingRef.key` of the recording that was assessed. Required when
285
+ * scripted, so a grade can refuse evidence made for another recording.
286
+ */
287
+ recordingKey?: string;
288
+ /** Who measured. Evidence from `'ai'` grades only when the caller opts in. */
289
+ assessor: Grader;
290
+ /** What every score is out of. Stated, so an adapter never leaves it to be assumed. */
291
+ scale: 100;
292
+ /** Utterance-level scores, 0..100. An absent score was not assessed; it is never read as 0. */
293
+ scores: {
294
+ accuracy?: number;
295
+ fluency?: number;
296
+ completeness?: number;
297
+ prosody?: number;
298
+ overall?: number;
299
+ };
300
+ /** What the assessor recognised, as text. */
301
+ recognizedText?: string;
302
+ /**
303
+ * Who judged omissions, insertions and mispronunciations. `assessor`: the
304
+ * assessor did, and its word `error` labels are trusted. `none`: it did not,
305
+ * so a heard word that differs from its reference word is marked
306
+ * mispronounced.
307
+ */
308
+ miscue: 'assessor' | 'none';
309
+ /** The alphabet phoneme symbols are written in. Required whenever a phoneme names a `symbol` or `heardAs`. */
310
+ phonemeAlphabet?: 'ipa' | 'sapi';
311
+ /** The words, in the order the assessor reported them. */
312
+ words: SpeechWord[];
313
+ /** Utterance-level prosody: `monotoneConfidence` is 0..1, display only. */
314
+ prosody?: {
315
+ monotoneConfidence?: number;
316
+ };
317
+ /** Recording quality. Informational only: no grade reads it. */
318
+ signal?: {
319
+ snrDb?: number;
320
+ };
321
+ }
322
+ /**
323
+ * The server's own measurement of a recording, from `inspectWav`. It is what
324
+ * decides whether a take is plausible speech; a duration the client reported
325
+ * never is.
326
+ */
327
+ interface SpeechMeasurement {
328
+ /** Length of the recording, in milliseconds. */
329
+ durationMs: number;
330
+ /** Milliseconds of the recording at or above the silence floor. At most `durationMs`. */
331
+ voicedMs: number;
332
+ }
333
+ /**
334
+ * When a take is too little speech to grade. Both fields are required: the SDK
335
+ * holds no default for either, because each is a decision about an
336
+ * application's learners and its assessor.
337
+ */
338
+ interface SpeechPlausibilityPolicy {
339
+ /** The most words per second of voiced time a genuine reading can reach. Finite and above 0. */
340
+ maxWordsPerSecond: number;
341
+ /** The least voiced time, in milliseconds, a gradable take contains. Finite and at least 0. */
342
+ minVoicedMs: number;
343
+ }
344
+ /**
345
+ * Why a read-aloud take cannot be graded:
346
+ * - `invalid_assessment` — the assessment fails `validateSpeechAssessment`;
347
+ * - `task_mismatch` — it is not a scripted assessment;
348
+ * - `locale_mismatch` — it was made for another locale than the item's;
349
+ * - `reference_mismatch` — it was made against another text than the item's;
350
+ * - `recording_mismatch` — it was made for another recording than the response's;
351
+ * - `assessor_not_accepted` — its assessor is `'ai'`, and the caller did not opt in;
352
+ * - `no_speech` — the assessor heard no speech;
353
+ * - `insufficient_voiced_time` — the recording holds less voiced time than the policy requires;
354
+ * - `implausible_speech_rate` — more words were recognised than the voiced time can hold;
355
+ * - `missing_dimension` — a weighted dimension has no score in the assessment.
356
+ */
357
+ 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';
358
+ /** A read-aloud take that cannot be graded. Never a zero: see `outcomeFromUnscorable`. */
359
+ interface SpeechUnscorable {
360
+ unscorable: true;
361
+ /** Machine-readable. See {@link SpeechUnscorableCode}. */
362
+ code: SpeechUnscorableCode;
363
+ /** A developer-facing English sentence. */
364
+ reason: string;
365
+ }
366
+ /**
367
+ * Options for `gradeReadAloud`. The plausibility policy is required: the SDK
368
+ * holds no default for a threshold that decides whether a take is graded.
369
+ */
370
+ interface GradeReadAloudOptions {
371
+ /** The server's measurement of the recording (inspectWav). `null` only for a blank response. */
372
+ measured: SpeechMeasurement | null;
373
+ /** When a take is too little speech to grade. */
374
+ plausibility: SpeechPlausibilityPolicy;
375
+ /** Accept `assessor.kind: 'ai'` evidence. Off unless set. */
376
+ allowAiAssessor?: boolean;
377
+ /** Compare the pass line with both sides rounded, as `gradeFromRubric` does. The score stays unrounded. */
378
+ rounding?: RoundingPolicy;
379
+ }
380
+ /** How a word of a read-aloud attempt is marked. */
381
+ type ReadAloudWordState = 'correct' | 'mispronounced' | 'omitted' | 'inserted';
382
+ /**
383
+ * One entry of `alignReadAloud`: a reference word and what was heard for it,
384
+ * or a heard word that is not in the reference.
385
+ */
386
+ interface ReadAloudWordAlignment {
387
+ /** `w<n>` for a reference word, as `dictationReferenceWords` numbers them; absent for an `inserted` word. */
388
+ itemId?: string;
389
+ /** The normalised reference word; `''` for an `inserted` word. */
390
+ reference: string;
391
+ /**
392
+ * The normalised heard word: `''` for an `omitted` word, and also for an
393
+ * `inserted` word whose text normalises to nothing — an assessor can tag a
394
+ * dash as an insertion, and every insertion gets an entry. Branch on `state`,
395
+ * never on the empty string.
396
+ */
397
+ heard: string;
398
+ state: ReadAloudWordState;
399
+ /** The assessor word's accuracy (0..100), when it has one. */
400
+ accuracy?: number;
401
+ /** Index into `assessment.words` of the word this entry came from. */
402
+ wordIndex?: number;
403
+ }
404
+ /**
405
+ * What `inspectWav` found in a recording's bytes: the duration, format, peak
406
+ * level and voiced time of a 16-bit PCM WAV, or why it could not read one.
407
+ */
408
+ type WavInspection = {
409
+ valid: true;
410
+ /** Length of the audio, in milliseconds. */
411
+ durationMs: number;
412
+ sampleRate: number;
413
+ channels: number;
414
+ bitsPerSample: 16;
415
+ /** The loudest sample across every channel, in dBFS; `-Infinity` for digital silence or no samples. */
416
+ peakDbfs: number;
417
+ /** Milliseconds of the windows at or above the policy's silence floor. */
418
+ voicedMs: number;
419
+ } | {
420
+ valid: false;
421
+ reason: 'not_wav' | 'unsupported_encoding' | 'truncated';
422
+ };
423
+ /** How `inspectWav` decides which parts of a recording are voiced. Both fields are required. */
424
+ interface WavInspectionPolicy {
425
+ /** A window whose RMS level is at or above this many dBFS is voiced. Finite, and at most 0. */
426
+ silenceDbfs: number;
427
+ /** The window length, in milliseconds. Finite, and above 0. */
428
+ frameMs: number;
429
+ }
430
+
191
431
  /** One transcript word, or one extra word the learner typed, after alignment. */
192
432
  interface DictationWordAlignment {
193
433
  /** `w<n>`, the 1-based position of a transcript word; absent for an extra word. */
@@ -314,8 +554,236 @@ declare const DICTATION_MAX_EQUIVALENCES = 100;
314
554
  */
315
555
  declare const DICTATION_MAX_ACCEPTED_TRANSCRIPTS = 10;
316
556
 
557
+ /**
558
+ * The pass line, and the authored feedback it selects.
559
+ *
560
+ * A module of its own so grade-producing code outside the scoring barrel can
561
+ * import it without importing the barrel: `gradeReadAloud`, under
562
+ * `scoring/speech/`, calls `gradeFromRubric`, so if `grading.ts` imported the
563
+ * barrel the barrel would import itself through them. `scoring/index.ts`
564
+ * re-exports the pass line and its default by name; the feedback selector
565
+ * beside them stays internal.
566
+ */
567
+
317
568
  /** Default minimum scaled score required to pass when `passThreshold` is absent. */
318
569
  declare const DEFAULT_PASS_THRESHOLD = 0.7;
570
+ /**
571
+ * Returns `true` iff `score` meets or exceeds the activity's `passThreshold`,
572
+ * defaulting to {@link DEFAULT_PASS_THRESHOLD} (0.7) when the field is absent.
573
+ *
574
+ * Pass a {@link RoundingPolicy} to compare the way an assessment total is
575
+ * compared — both sides rounded, via {@link gte} — so an item shown as "70%"
576
+ * cannot be recorded as a fail at 69.6. It is **opt-in** rather than the
577
+ * default because switching it on changes item-level pass/fail for scores in
578
+ * the rounding band, and this SDK does not alter historical grades without an
579
+ * explicit decision. Absent, the comparison is the exact raw `>=` it has
580
+ * always been.
581
+ */
582
+ declare function computePassThreshold(activityData: ActivityData, score: number, rounding?: RoundingPolicy): boolean;
583
+
584
+ /**
585
+ * Marking a read-aloud take word by word: the item's own reference words,
586
+ * paired with the words an assessor heard.
587
+ *
588
+ * The assessor's word list is never taken as the item's. Providers disagree
589
+ * about what a word is, and a provider can be changed; `w<n>` is the position
590
+ * of a REFERENCE word, exactly as `dictationReferenceWords` numbers it, so a
591
+ * mark stored today still names the same word tomorrow. The pairing is the
592
+ * dictation aligner's, run over the dictation normaliser's tokens, so a
593
+ * read-aloud mark and a dictation mark are made the same way.
594
+ */
595
+
596
+ /**
597
+ * Pairs the words of `assessment` with the words of `data.referenceText`, in
598
+ * reading order: what was read as written, what was mispronounced, what was
599
+ * left out, and what was said that the text does not contain.
600
+ *
601
+ * Reference words are the dictation tokeniser's, so `itemId` is the `w<n>`
602
+ * `dictationReferenceWords` gives, and both `reference` and `heard` are
603
+ * normalised — lowercased, with punctuation and spacing folded away. One
604
+ * assessor word can normalise to no token at all (a dash), or to several (a
605
+ * compatibility form), and two can merge into one (`rock&roll`); tokens are
606
+ * what is aligned, and each remembers the word it came from.
607
+ *
608
+ * A word the assessor tagged `insertion` is never aligned — the reference has
609
+ * nothing for it — so it is placed by the position of its own word, after the
610
+ * last token that came from an earlier word.
611
+ *
612
+ * Whether a paired word counts as read correctly follows `assessment.miscue`:
613
+ * an assessor that judges miscues is trusted, and one that does not
614
+ * (`miscue: 'none'`) has its recognised word compared with the reference word.
615
+ *
616
+ * `heard` is `''` for an omitted word, and also for an inserted word whose text
617
+ * normalises to nothing. Branch on `state`, never on the empty string.
618
+ *
619
+ * The heard tokens are bounded: they are taken in reading order until what the
620
+ * assessment's words spell would pass `DICTATION_MAX_TEXT_LENGTH` code points —
621
+ * the cap the dictation aligner reads a learner's text under — and always on a
622
+ * whole token, because half a token pairs with the wrong word. Every word is
623
+ * charged, an inserted one included, so that total is the whole of the text
624
+ * this function can answer with. Words past the budget contribute no token, so
625
+ * each reference word left with nothing read for it is `omitted`.
626
+ * `validateSpeechAssessment` refuses an assessment whose words spell more than
627
+ * that, measured by the same walk and against the same number, so a validated
628
+ * assessment never reaches the budget: it is a backstop, kept because this
629
+ * function is public and can be handed evidence this build did not check.
630
+ *
631
+ * **`data` is not validated.** Only the assessment is. A `referenceText` longer
632
+ * than `READ_ALOUD_MAX_REFERENCE_LENGTH` code points is cut by the tokeniser
633
+ * with no signal, and one that is absent or is not a string yields no reference
634
+ * word at all, so every heard token comes back `inserted` rather than refused.
635
+ * Validate the item with `validateActivity('read-aloud', data)` first.
636
+ * `gradeReadAloud` is safe without that because it runs the schema itself.
637
+ *
638
+ * Pure and deterministic: no locale, no `Intl`, no clock.
639
+ *
640
+ * @throws TypeError when `assessment` is not a well-formed
641
+ * {@link SpeechAssessment}; check it with `validateSpeechAssessment` first.
642
+ */
643
+ declare function alignReadAloud(data: Pick<ReadAloudData, 'referenceText'>, assessment: SpeechAssessment): ReadAloudWordAlignment[];
644
+
645
+ /**
646
+ * Checks that `value` is a well-formed {@link SpeechAssessment}. It answers
647
+ * rather than throwing: anything that is not one — a non-object, an unknown key
648
+ * at any depth, a score off the 0..100 scale, a time that is negative or not
649
+ * finite — comes back as `{ success: false, errors }`, one error per problem,
650
+ * each with the path, message and code `validateActivity` gives a schema
651
+ * failure. An object that throws when it is read is the one exception, and the
652
+ * throw is its own: an own property getter, or a `Proxy` trap.
653
+ *
654
+ * Three rules span fields:
655
+ * - `scripted_binding_required` — a `scripted` assessment without
656
+ * `referenceText`, or without `recordingKey`, reported at each missing path;
657
+ * - `phoneme_alphabet_required` — a phoneme names a `symbol` or `heardAs` while
658
+ * `phonemeAlphabet` is absent, reported once, at `phonemeAlphabet`;
659
+ * - `too_big` at `words` — the words carry more text between them, written or
660
+ * normalised, than can be aligned against an item's text. Per-word caps bound
661
+ * no total, and the aligner pairs tokens, of which one word holds as many as
662
+ * its text spells. Every word counts towards both totals, an insertion
663
+ * included: the aligner carries one into its answer, so its text is text the
664
+ * call must hold.
665
+ *
666
+ * The total is measured on the words every field rule accepted, so it says
667
+ * nothing about an assessment that is going back to its adapter anyway.
668
+ *
669
+ * It checks the evidence alone. Whether an assessment belongs to an item and
670
+ * to a learner's recording is decided when it is graded.
671
+ */
672
+ declare function validateSpeechAssessment(value: unknown): ValidationResult<SpeechAssessment>;
673
+
674
+ /**
675
+ * Grades a read-aloud take from a {@link SpeechAssessment} the application
676
+ * obtained, the response that was submitted, and the server's own measurement
677
+ * of the recording.
678
+ *
679
+ * The weighted total is computed by `gradeFromRubric` from the authored
680
+ * dimensions that carry a weight above 0, each out of 100. The per-word marks
681
+ * in `details` are for display and review: **they never feed the score**, which
682
+ * is the assessor's utterance-level judgement, weighted as the item authored it.
683
+ *
684
+ * Evidence is refused, never scored 0, when it cannot be tied to this item and
685
+ * this recording, when the assessor heard nothing, when the recording holds too
686
+ * little voiced time for the words claimed, or when a weighted dimension has no
687
+ * score — see {@link SpeechUnscorableCode}. Store a refusal with
688
+ * `outcomeFromUnscorable`.
689
+ *
690
+ * A blank (`response.recording === null`) is a grade of 0 with a mark per
691
+ * reference word: the learner submitted, and read nothing. The assessment and
692
+ * the measurement are not read for it.
693
+ *
694
+ * @throws RangeError when `options.plausibility` or `options.rounding` cannot be
695
+ * applied, or when `options.measured` is not a pair of non-negative, finite
696
+ * millisecond counts with `voicedMs` inside `durationMs`.
697
+ * @throws TypeError when `response` is not a read-aloud response, or when a
698
+ * non-blank response is graded without an assessment or without a measurement.
699
+ * @throws ActivitySchemaError when `data` is not valid read-aloud data.
700
+ * @throws RedactedScoringError when `data` is a `redact()` projection. A
701
+ * read-aloud projection is a valid activity, so the authored feedback would
702
+ * simply be missing from the grade; grade against the full data server-side.
703
+ */
704
+ declare function gradeReadAloud(data: ReadAloudData, response: ReadAloudLearnerResponse, assessment: SpeechAssessment | null, options: GradeReadAloudOptions): GradeRecord | SpeechUnscorable;
705
+
706
+ /**
707
+ * The longest reference text a read-aloud item accepts, in code points, before
708
+ * and after normalisation. It is the dictation transcript cap on purpose: the
709
+ * reference is tokenised as a dictation transcript is, and the dictation
710
+ * normaliser cuts a transcript at that many code points without saying so, so
711
+ * a longer text would be graded against words it had silently lost.
712
+ */
713
+ declare const READ_ALOUD_MAX_REFERENCE_LENGTH = 2000;
714
+ /** The longest take a read-aloud item may allow, in seconds: the most `recording.maxSeconds` may be. */
715
+ declare const READ_ALOUD_MAX_SECONDS = 300;
716
+ /** The most takes a read-aloud item may allow: the most `recording.maxTakes` may be. */
717
+ declare const READ_ALOUD_MAX_TAKES = 20;
718
+ /**
719
+ * The largest weight a read-aloud dimension may carry. Weights are relative, so
720
+ * no rubric needs more, and the cap keeps a mistyped weight from overflowing
721
+ * the weighted total into a grade that cannot be computed.
722
+ */
723
+ declare const READ_ALOUD_MAX_DIMENSION_WEIGHT = 1000;
724
+ /**
725
+ * The most words a speech assessment may carry. It bounds one side of the
726
+ * alignment, and one side alone bounds nothing: the aligner pairs TOKENS, and
727
+ * one word normalises to as many as its text holds. What bounds the quadratic
728
+ * work is this cap, the 200-unit cap on a single word's text, and the two rules
729
+ * `validateSpeechAssessment` states over the total of them — what the words are
730
+ * written with, and what they spell once normalised.
731
+ */
732
+ declare const SPEECH_ASSESSMENT_MAX_WORDS = 1000;
733
+
734
+ /**
735
+ * Measuring a recording from its bytes: how long it is, how loud it gets, and
736
+ * how much of it is voiced.
737
+ *
738
+ * This is the server's own evidence about a take. A duration a browser reported
739
+ * is a claim, and a claim decides nothing: whether a recording holds enough
740
+ * speech to grade is answered from the samples. Only 16-bit PCM WAV is read —
741
+ * the one encoding an application can produce without a decoder — and anything
742
+ * else is reported as unread rather than guessed at.
743
+ *
744
+ * Isomorphic: a `DataView` over the caller's bytes, no `Buffer`, no `node:`
745
+ * imports, and no allocation proportional to the audio.
746
+ */
747
+
748
+ /**
749
+ * Reads a 16-bit PCM WAV and measures it: `durationMs`, `sampleRate`,
750
+ * `channels`, the loudest sample as `peakDbfs`, and `voicedMs`, the time in
751
+ * windows whose RMS level reaches the policy's silence floor.
752
+ *
753
+ * Anything it cannot measure comes back as `{ valid: false, reason }` rather
754
+ * than a guess: `not_wav` for bytes that are not a RIFF/WAVE file, or whose
755
+ * chunks do not describe the audio before the audio arrives;
756
+ * `unsupported_encoding` for a WAV this reader does not read (compressed, or
757
+ * samples that are not 16-bit); `truncated` when the data chunk is missing or
758
+ * shorter than it declares.
759
+ *
760
+ * Both policy fields are required, because neither has an answer that is right
761
+ * for every microphone: `silenceDbfs` is where an application draws the line
762
+ * between silence and speech, and `frameMs` is how finely it looks.
763
+ *
764
+ * Pure and deterministic. It never throws on the bytes themselves — only on
765
+ * arguments it cannot use.
766
+ *
767
+ * @throws TypeError when `bytes` is not a `Uint8Array`.
768
+ * @throws RangeError when `silenceDbfs` is not a finite number at or below 0,
769
+ * or `frameMs` is not a finite number above 0.
770
+ */
771
+ declare function inspectWav(bytes: Uint8Array, policy: WavInspectionPolicy): WavInspection;
772
+
773
+ /**
774
+ * Lifts a result that cannot be graded — a `SpeechUnscorable` from
775
+ * `gradeReadAloud`, or anything else carrying a `code` and a `reason` — into
776
+ * the `unscorable` arm of {@link ItemOutcome}, keeping its code.
777
+ *
778
+ * The code is what an application branches on; the reason is a developer-facing
779
+ * sentence. Stored this way, the result composes exactly as the unscorable
780
+ * outcomes `evaluate` returns do. `evaluate` never writes a code itself.
781
+ */
782
+ declare function outcomeFromUnscorable(result: {
783
+ code: string;
784
+ reason: string;
785
+ }): ItemOutcome;
786
+
319
787
  /**
320
788
  * Options for {@link score} and {@link evaluate}. Additive: with none, both
321
789
  * behave exactly as they always have.
@@ -335,19 +803,6 @@ interface ScoringOptions {
335
803
  */
336
804
  rounding?: RoundingPolicy;
337
805
  }
338
- /**
339
- * Returns `true` iff `score` meets or exceeds the activity's `passThreshold`,
340
- * defaulting to {@link DEFAULT_PASS_THRESHOLD} (0.7) when the field is absent.
341
- *
342
- * Pass a {@link RoundingPolicy} to compare the way an assessment total is
343
- * compared — both sides rounded, via {@link gte} — so an item shown as "70%"
344
- * cannot be recorded as a fail at 69.6. It is **opt-in** rather than the
345
- * default because switching it on changes item-level pass/fail for scores in
346
- * the rounding band, and this SDK does not alter historical grades without an
347
- * explicit decision. Absent, the comparison is the exact raw `>=` it has
348
- * always been.
349
- */
350
- declare function computePassThreshold(activityData: ActivityData, score: number, rounding?: RoundingPolicy): boolean;
351
806
  /**
352
807
  * Scores a learner response against activity data and returns a full
353
808
  * {@link ScoringResult}.
@@ -381,4 +836,4 @@ declare function score(activityType: ActivityType, activityData: ActivityData, l
381
836
  */
382
837
  declare function evaluate(data: ActivityData, response: LearnerResponse, options?: ScoringOptions): ItemOutcome;
383
838
 
384
- export { type AssessmentScore, type AssessmentSectionInput, type Band, type CompositionPolicy, 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 PassFailureReason, type RoundingMode, type RoundingPolicy, type ScoredItem, type ScoringOptions, type SectionScore, alignDictation, classifyBand, composeAssessmentScore, computePassThreshold, dictationReferenceWords, diffDictationChars, evaluate, gte, roundGrade, score };
839
+ export { type AssessmentScore, type AssessmentSectionInput, type Band, type CompositionPolicy, 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 GradeReadAloudOptions, GradeRecord, ItemOutcome, 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 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, gradeReadAloud, gte, inspectWav, outcomeFromUnscorable, roundGrade, score, validateSpeechAssessment };
package/dist/scoring.js CHANGED
@@ -1,25 +1,35 @@
1
1
  import {
2
2
  DEFAULT_PASS_THRESHOLD,
3
+ alignReadAloud,
3
4
  classifyBand,
4
5
  composeAssessmentScore,
5
6
  computePassThreshold,
6
7
  diffDictationChars,
7
8
  evaluate,
9
+ gradeReadAloud,
8
10
  gte,
11
+ inspectWav,
12
+ outcomeFromUnscorable,
9
13
  roundGrade,
10
- score
11
- } from "./chunk-FGN4LLSP.js";
14
+ score,
15
+ validateSpeechAssessment
16
+ } from "./chunk-RTIR6R2U.js";
12
17
  import {
13
18
  DICTATION_MAX_ACCEPTED_TRANSCRIPTS,
14
19
  DICTATION_MAX_EQUIVALENCES,
15
20
  DICTATION_MAX_EQUIVALENCE_LENGTH,
16
21
  DICTATION_MAX_TEXT_LENGTH,
17
22
  DICTATION_MAX_TRANSCRIPT_LENGTH,
23
+ READ_ALOUD_MAX_DIMENSION_WEIGHT,
24
+ READ_ALOUD_MAX_REFERENCE_LENGTH,
25
+ READ_ALOUD_MAX_SECONDS,
26
+ READ_ALOUD_MAX_TAKES,
27
+ SPEECH_ASSESSMENT_MAX_WORDS,
18
28
  alignDictation,
19
29
  dictationReferenceWords,
20
30
  levenshteinDistance,
21
31
  matchText
22
- } from "./chunk-XKKKWKRH.js";
32
+ } from "./chunk-6RND47QE.js";
23
33
  export {
24
34
  DEFAULT_PASS_THRESHOLD,
25
35
  DICTATION_MAX_ACCEPTED_TRANSCRIPTS,
@@ -27,17 +37,27 @@ export {
27
37
  DICTATION_MAX_EQUIVALENCE_LENGTH,
28
38
  DICTATION_MAX_TEXT_LENGTH,
29
39
  DICTATION_MAX_TRANSCRIPT_LENGTH,
40
+ READ_ALOUD_MAX_DIMENSION_WEIGHT,
41
+ READ_ALOUD_MAX_REFERENCE_LENGTH,
42
+ READ_ALOUD_MAX_SECONDS,
43
+ READ_ALOUD_MAX_TAKES,
44
+ SPEECH_ASSESSMENT_MAX_WORDS,
30
45
  alignDictation,
46
+ alignReadAloud,
31
47
  classifyBand,
32
48
  composeAssessmentScore,
33
49
  computePassThreshold,
34
50
  dictationReferenceWords,
35
51
  diffDictationChars,
36
52
  evaluate,
53
+ gradeReadAloud,
37
54
  gte,
55
+ inspectWav,
38
56
  levenshteinDistance,
39
57
  matchText,
58
+ outcomeFromUnscorable,
40
59
  roundGrade,
41
- score
60
+ score,
61
+ validateSpeechAssessment
42
62
  };
43
63
  //# sourceMappingURL=scoring.js.map
package/dist/xapi.cjs CHANGED
@@ -4,13 +4,13 @@
4
4
 
5
5
 
6
6
 
7
- var _chunkFT7SAC3Xcjs = require('./chunk-FT7SAC3X.cjs');
8
- require('./chunk-XONKWL6D.cjs');
7
+ var _chunkCZRUMHKWcjs = require('./chunk-CZRUMHKW.cjs');
8
+ require('./chunk-VCXIPLEW.cjs');
9
9
 
10
10
 
11
11
 
12
12
 
13
13
 
14
14
 
15
- exports.XAPIVerb = _chunkFT7SAC3Xcjs.XAPIVerb; exports.XAPI_VERB_DISPLAY = _chunkFT7SAC3Xcjs.XAPI_VERB_DISPLAY; exports.validateXAPIStatement = _chunkFT7SAC3Xcjs.validateXAPIStatement; exports.xAPIBuilder = _chunkFT7SAC3Xcjs.xAPIBuilder; exports.xapiDefinitionFor = _chunkFT7SAC3Xcjs.xapiDefinitionFor;
15
+ exports.XAPIVerb = _chunkCZRUMHKWcjs.XAPIVerb; exports.XAPI_VERB_DISPLAY = _chunkCZRUMHKWcjs.XAPI_VERB_DISPLAY; exports.validateXAPIStatement = _chunkCZRUMHKWcjs.validateXAPIStatement; exports.xAPIBuilder = _chunkCZRUMHKWcjs.xAPIBuilder; exports.xapiDefinitionFor = _chunkCZRUMHKWcjs.xapiDefinitionFor;
16
16
  //# sourceMappingURL=xapi.cjs.map
package/dist/xapi.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { X as XAPIActor, K as ScoringResult, Z as XAPIContext, a1 as XAPIResult, a3 as XAPIStatement } from './activity-DfAmJ1sl.cjs';
1
+ import { a3 as XAPIActor, W as ScoringResult, a5 as XAPIContext, a9 as XAPIResult, ab as XAPIStatement } from './activity-BVkg9VDo.cjs';
2
2
 
3
3
  /**
4
4
  * The set of xAPI verbs emitted by SDK activities, keyed by a stable
package/dist/xapi.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { X as XAPIActor, K as ScoringResult, Z as XAPIContext, a1 as XAPIResult, a3 as XAPIStatement } from './activity-DfAmJ1sl.js';
1
+ import { a3 as XAPIActor, W as ScoringResult, a5 as XAPIContext, a9 as XAPIResult, ab as XAPIStatement } from './activity-BVkg9VDo.js';
2
2
 
3
3
  /**
4
4
  * The set of xAPI verbs emitted by SDK activities, keyed by a stable
package/dist/xapi.js CHANGED
@@ -4,8 +4,8 @@ import {
4
4
  validateXAPIStatement,
5
5
  xAPIBuilder,
6
6
  xapiDefinitionFor
7
- } from "./chunk-PYP3HCRQ.js";
8
- import "./chunk-XKKKWKRH.js";
7
+ } from "./chunk-R7PV3XIC.js";
8
+ import "./chunk-6RND47QE.js";
9
9
  export {
10
10
  XAPIVerb,
11
11
  XAPI_VERB_DISPLAY,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intellectif/lk-core",
3
- "version": "0.13.0",
3
+ "version": "0.14.0",
4
4
  "description": "Framework-free assessment core: Zod activity schemas, pure scoring, rubric & deferred grading, weighted assessment composition, exam attempt plans, and xAPI 1.0.3.",
5
5
  "keywords": [
6
6
  "assessment",