@intellectif/lk-core 0.12.0 → 0.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.md +2 -1
  3. package/dist/{activity-B2QxK15j.d.cts → activity-DfAmJ1sl.d.cts} +134 -7
  4. package/dist/{activity-B2QxK15j.d.ts → activity-DfAmJ1sl.d.ts} +134 -7
  5. package/dist/{chunk-VYE6BYON.cjs → chunk-DQKJUMUW.cjs} +38 -15
  6. package/dist/chunk-DQKJUMUW.cjs.map +1 -0
  7. package/dist/{chunk-RK2NVTHZ.js → chunk-FGN4LLSP.js} +50 -6
  8. package/dist/chunk-FGN4LLSP.js.map +1 -0
  9. package/dist/{chunk-O2IVKTHV.cjs → chunk-FT7SAC3X.cjs} +4 -4
  10. package/dist/{chunk-O2IVKTHV.cjs.map → chunk-FT7SAC3X.cjs.map} +1 -1
  11. package/dist/{chunk-UQPNJP2O.js → chunk-PYP3HCRQ.js} +2 -2
  12. package/dist/{chunk-4OHWLCI6.cjs → chunk-UHQVIC3P.cjs} +58 -14
  13. package/dist/chunk-UHQVIC3P.cjs.map +1 -0
  14. package/dist/{chunk-FVN37GIH.js → chunk-XKKKWKRH.js} +1944 -177
  15. package/dist/chunk-XKKKWKRH.js.map +1 -0
  16. package/dist/{chunk-J6POE4HL.cjs → chunk-XONKWL6D.cjs} +1785 -18
  17. package/dist/chunk-XONKWL6D.cjs.map +1 -0
  18. package/dist/{chunk-7JMMPE7O.js → chunk-Y36S3X3L.js} +26 -3
  19. package/dist/chunk-Y36S3X3L.js.map +1 -0
  20. package/dist/{index-B_scInLM.d.cts → index-BefQ8FAS.d.cts} +192 -15
  21. package/dist/{index-CRKtWP5G.d.ts → index-BiIvdASA.d.ts} +192 -15
  22. package/dist/index.cjs +132 -74
  23. package/dist/index.cjs.map +1 -1
  24. package/dist/index.d.cts +19 -6
  25. package/dist/index.d.ts +19 -6
  26. package/dist/index.js +64 -6
  27. package/dist/index.js.map +1 -1
  28. package/dist/schemas.cjs +17 -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 +16 -2
  33. package/dist/scoring.cjs +19 -3
  34. package/dist/scoring.cjs.map +1 -1
  35. package/dist/scoring.d.cts +150 -5
  36. package/dist/scoring.d.ts +150 -5
  37. package/dist/scoring.js +18 -2
  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 +9 -1
  44. package/vectors/scoring.json +12023 -1
  45. package/dist/chunk-4OHWLCI6.cjs.map +0 -1
  46. package/dist/chunk-7JMMPE7O.js.map +0 -1
  47. package/dist/chunk-FVN37GIH.js.map +0 -1
  48. package/dist/chunk-J6POE4HL.cjs.map +0 -1
  49. package/dist/chunk-RK2NVTHZ.js.map +0 -1
  50. package/dist/chunk-VYE6BYON.cjs.map +0 -1
  51. /package/dist/{chunk-UQPNJP2O.js.map → chunk-PYP3HCRQ.js.map} +0 -0
@@ -1,3 +1,3 @@
1
- import './activity-B2QxK15j.cjs';
2
- export { B as BlankConfigSchema, F as FeedbackSchema, a as FillInTheBlanksDataSchema, G as GapSelectBankSchema, b as GapSelectChoiceSchema, c as GapSelectDataSchema, d as GapSelectGapSchema, e as ItemGroupSchema, M as MediaPlaybackSchema, f as MediaSchema, g as MediaUrlSchema, h as MultipleChoiceDataSchema, i as MultipleChoiceOptionMediaSchema, j as MultipleChoiceOptionSchema, N as NativeControlHintSchema, R as RedactedActivity, k as RedactedBlankConfig, l as RedactedBlankConfigSchema, m as RedactedFillInTheBlanksData, n as RedactedFillInTheBlanksDataSchema, o as RedactedGapSelectBank, p as RedactedGapSelectBankSchema, q as RedactedGapSelectChoice, r as RedactedGapSelectChoiceSchema, s as RedactedGapSelectData, t as RedactedGapSelectDataSchema, u as RedactedGapSelectGap, v as RedactedGapSelectGapSchema, w as RedactedItemGroupSchema, x as RedactedMediaSchema, y as RedactedMultipleChoiceData, z as RedactedMultipleChoiceDataSchema, A as RedactedMultipleChoiceOption, C as RedactedMultipleChoiceOptionMedia, D as RedactedMultipleChoiceOptionMediaSchema, E as RedactedMultipleChoiceOptionSchema, H as RedactedStimulus, J as RedactedStimulusSchema, K as RedactedWrittenResponseData, L as RedactedWrittenResponseDataSchema, U as StimulusSchema, V as TextMatchPolicySchema, W as WrittenResponseDataSchema, X as WrittenResponseRubricCriterionSchema, Y as WrittenResponseRubricSchema, Z as fillInTheBlanksJsonSchema, _ as gapSelectJsonSchema, $ as itemGroupJsonSchema, a0 as jsonSchemaFor, a1 as multipleChoiceJsonSchema, a2 as stimulusJsonSchema, a3 as validateActivity, a4 as validateItemGroup, a5 as writtenResponseJsonSchema } from './index-B_scInLM.cjs';
1
+ import './activity-DfAmJ1sl.cjs';
2
+ export { B as BlankConfigSchema, D as DictationDataSchema, a as DictationEquivalenceSchema, b as DictationSlowMediaSchema, c as DictationToleranceSchema, F as FeedbackSchema, d as FillInTheBlanksDataSchema, G as GapSelectBankSchema, e as GapSelectChoiceSchema, f as GapSelectDataSchema, g as GapSelectGapSchema, h as ItemGroupSchema, M as MediaPlaybackSchema, i as MediaSchema, j as MediaUrlSchema, k as MultipleChoiceDataSchema, l as MultipleChoiceOptionMediaSchema, m as MultipleChoiceOptionSchema, N as NativeControlHintSchema, R as RedactedActivity, n as RedactedBlankConfig, o as RedactedBlankConfigSchema, p as RedactedDictationData, q as RedactedDictationDataSchema, r as RedactedDictationSlowMedia, s as RedactedDictationSlowMediaSchema, t as RedactedFillInTheBlanksData, u as RedactedFillInTheBlanksDataSchema, v as RedactedGapSelectBank, w as RedactedGapSelectBankSchema, x as RedactedGapSelectChoice, y as RedactedGapSelectChoiceSchema, z as RedactedGapSelectData, A as RedactedGapSelectDataSchema, C as RedactedGapSelectGap, E as RedactedGapSelectGapSchema, H as RedactedItemGroupSchema, J as RedactedMediaSchema, K as RedactedMultipleChoiceData, L as RedactedMultipleChoiceDataSchema, O as RedactedMultipleChoiceOption, P as RedactedMultipleChoiceOptionMedia, Q as RedactedMultipleChoiceOptionMediaSchema, S as RedactedMultipleChoiceOptionSchema, T as RedactedStimulus, U as RedactedStimulusSchema, V as RedactedWrittenResponseData, W as RedactedWrittenResponseDataSchema, a0 as StimulusSchema, a1 as TextMatchPolicySchema, a2 as WrittenResponseDataSchema, a3 as WrittenResponseRubricCriterionSchema, a4 as WrittenResponseRubricSchema, a5 as dictationJsonSchema, a6 as fillInTheBlanksJsonSchema, a7 as gapSelectJsonSchema, a8 as itemGroupJsonSchema, a9 as jsonSchemaFor, aa as multipleChoiceJsonSchema, ab as stimulusJsonSchema, ac as validateActivity, ad as validateItemGroup, ae as writtenResponseJsonSchema } from './index-BefQ8FAS.cjs';
3
3
  import 'zod/v4';
package/dist/schemas.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- import './activity-B2QxK15j.js';
2
- export { B as BlankConfigSchema, F as FeedbackSchema, a as FillInTheBlanksDataSchema, G as GapSelectBankSchema, b as GapSelectChoiceSchema, c as GapSelectDataSchema, d as GapSelectGapSchema, e as ItemGroupSchema, M as MediaPlaybackSchema, f as MediaSchema, g as MediaUrlSchema, h as MultipleChoiceDataSchema, i as MultipleChoiceOptionMediaSchema, j as MultipleChoiceOptionSchema, N as NativeControlHintSchema, R as RedactedActivity, k as RedactedBlankConfig, l as RedactedBlankConfigSchema, m as RedactedFillInTheBlanksData, n as RedactedFillInTheBlanksDataSchema, o as RedactedGapSelectBank, p as RedactedGapSelectBankSchema, q as RedactedGapSelectChoice, r as RedactedGapSelectChoiceSchema, s as RedactedGapSelectData, t as RedactedGapSelectDataSchema, u as RedactedGapSelectGap, v as RedactedGapSelectGapSchema, w as RedactedItemGroupSchema, x as RedactedMediaSchema, y as RedactedMultipleChoiceData, z as RedactedMultipleChoiceDataSchema, A as RedactedMultipleChoiceOption, C as RedactedMultipleChoiceOptionMedia, D as RedactedMultipleChoiceOptionMediaSchema, E as RedactedMultipleChoiceOptionSchema, H as RedactedStimulus, J as RedactedStimulusSchema, K as RedactedWrittenResponseData, L as RedactedWrittenResponseDataSchema, U as StimulusSchema, V as TextMatchPolicySchema, W as WrittenResponseDataSchema, X as WrittenResponseRubricCriterionSchema, Y as WrittenResponseRubricSchema, Z as fillInTheBlanksJsonSchema, _ as gapSelectJsonSchema, $ as itemGroupJsonSchema, a0 as jsonSchemaFor, a1 as multipleChoiceJsonSchema, a2 as stimulusJsonSchema, a3 as validateActivity, a4 as validateItemGroup, a5 as writtenResponseJsonSchema } from './index-CRKtWP5G.js';
1
+ import './activity-DfAmJ1sl.js';
2
+ export { B as BlankConfigSchema, D as DictationDataSchema, a as DictationEquivalenceSchema, b as DictationSlowMediaSchema, c as DictationToleranceSchema, F as FeedbackSchema, d as FillInTheBlanksDataSchema, G as GapSelectBankSchema, e as GapSelectChoiceSchema, f as GapSelectDataSchema, g as GapSelectGapSchema, h as ItemGroupSchema, M as MediaPlaybackSchema, i as MediaSchema, j as MediaUrlSchema, k as MultipleChoiceDataSchema, l as MultipleChoiceOptionMediaSchema, m as MultipleChoiceOptionSchema, N as NativeControlHintSchema, R as RedactedActivity, n as RedactedBlankConfig, o as RedactedBlankConfigSchema, p as RedactedDictationData, q as RedactedDictationDataSchema, r as RedactedDictationSlowMedia, s as RedactedDictationSlowMediaSchema, t as RedactedFillInTheBlanksData, u as RedactedFillInTheBlanksDataSchema, v as RedactedGapSelectBank, w as RedactedGapSelectBankSchema, x as RedactedGapSelectChoice, y as RedactedGapSelectChoiceSchema, z as RedactedGapSelectData, A as RedactedGapSelectDataSchema, C as RedactedGapSelectGap, E as RedactedGapSelectGapSchema, H as RedactedItemGroupSchema, J as RedactedMediaSchema, K as RedactedMultipleChoiceData, L as RedactedMultipleChoiceDataSchema, O as RedactedMultipleChoiceOption, P as RedactedMultipleChoiceOptionMedia, Q as RedactedMultipleChoiceOptionMediaSchema, S as RedactedMultipleChoiceOptionSchema, T as RedactedStimulus, U as RedactedStimulusSchema, V as RedactedWrittenResponseData, W as RedactedWrittenResponseDataSchema, a0 as StimulusSchema, a1 as TextMatchPolicySchema, a2 as WrittenResponseDataSchema, a3 as WrittenResponseRubricCriterionSchema, a4 as WrittenResponseRubricSchema, a5 as dictationJsonSchema, a6 as fillInTheBlanksJsonSchema, a7 as gapSelectJsonSchema, a8 as itemGroupJsonSchema, a9 as jsonSchemaFor, aa as multipleChoiceJsonSchema, ab as stimulusJsonSchema, ac as validateActivity, ad as validateItemGroup, ae as writtenResponseJsonSchema } from './index-BiIvdASA.js';
3
3
  import 'zod/v4';
package/dist/schemas.js CHANGED
@@ -3,6 +3,7 @@ import {
3
3
  RedactedItemGroupSchema,
4
4
  RedactedStimulusSchema,
5
5
  StimulusSchema,
6
+ dictationJsonSchema,
6
7
  fillInTheBlanksJsonSchema,
7
8
  gapSelectJsonSchema,
8
9
  itemGroupJsonSchema,
@@ -12,9 +13,13 @@ import {
12
13
  validateActivity,
13
14
  validateItemGroup,
14
15
  writtenResponseJsonSchema
15
- } from "./chunk-7JMMPE7O.js";
16
+ } from "./chunk-Y36S3X3L.js";
16
17
  import {
17
18
  BlankConfigSchema,
19
+ DictationDataSchema,
20
+ DictationEquivalenceSchema,
21
+ DictationSlowMediaSchema,
22
+ DictationToleranceSchema,
18
23
  FeedbackSchema,
19
24
  FillInTheBlanksDataSchema,
20
25
  GapSelectBankSchema,
@@ -29,6 +34,8 @@ import {
29
34
  MultipleChoiceOptionSchema,
30
35
  NativeControlHintSchema,
31
36
  RedactedBlankConfigSchema,
37
+ RedactedDictationDataSchema,
38
+ RedactedDictationSlowMediaSchema,
32
39
  RedactedFillInTheBlanksDataSchema,
33
40
  RedactedGapSelectBankSchema,
34
41
  RedactedGapSelectChoiceSchema,
@@ -43,9 +50,13 @@ import {
43
50
  WrittenResponseDataSchema,
44
51
  WrittenResponseRubricCriterionSchema,
45
52
  WrittenResponseRubricSchema
46
- } from "./chunk-FVN37GIH.js";
53
+ } from "./chunk-XKKKWKRH.js";
47
54
  export {
48
55
  BlankConfigSchema,
56
+ DictationDataSchema,
57
+ DictationEquivalenceSchema,
58
+ DictationSlowMediaSchema,
59
+ DictationToleranceSchema,
49
60
  FeedbackSchema,
50
61
  FillInTheBlanksDataSchema,
51
62
  GapSelectBankSchema,
@@ -61,6 +72,8 @@ export {
61
72
  MultipleChoiceOptionSchema,
62
73
  NativeControlHintSchema,
63
74
  RedactedBlankConfigSchema,
75
+ RedactedDictationDataSchema,
76
+ RedactedDictationSlowMediaSchema,
64
77
  RedactedFillInTheBlanksDataSchema,
65
78
  RedactedGapSelectBankSchema,
66
79
  RedactedGapSelectChoiceSchema,
@@ -78,6 +91,7 @@ export {
78
91
  WrittenResponseDataSchema,
79
92
  WrittenResponseRubricCriterionSchema,
80
93
  WrittenResponseRubricSchema,
94
+ dictationJsonSchema,
81
95
  fillInTheBlanksJsonSchema,
82
96
  gapSelectJsonSchema,
83
97
  itemGroupJsonSchema,
package/dist/scoring.cjs CHANGED
@@ -7,11 +7,10 @@
7
7
 
8
8
 
9
9
 
10
- var _chunk4OHWLCI6cjs = require('./chunk-4OHWLCI6.cjs');
11
10
 
11
+ var _chunkUHQVIC3Pcjs = require('./chunk-UHQVIC3P.cjs');
12
12
 
13
13
 
14
- var _chunkJ6POE4HLcjs = require('./chunk-J6POE4HL.cjs');
15
14
 
16
15
 
17
16
 
@@ -20,8 +19,25 @@ var _chunkJ6POE4HLcjs = require('./chunk-J6POE4HL.cjs');
20
19
 
21
20
 
22
21
 
22
+ var _chunkXONKWL6Dcjs = require('./chunk-XONKWL6D.cjs');
23
23
 
24
24
 
25
25
 
26
- exports.DEFAULT_PASS_THRESHOLD = _chunk4OHWLCI6cjs.DEFAULT_PASS_THRESHOLD; exports.classifyBand = _chunk4OHWLCI6cjs.classifyBand; exports.composeAssessmentScore = _chunk4OHWLCI6cjs.composeAssessmentScore; exports.computePassThreshold = _chunk4OHWLCI6cjs.computePassThreshold; exports.evaluate = _chunk4OHWLCI6cjs.evaluate; exports.gte = _chunk4OHWLCI6cjs.gte; exports.levenshteinDistance = _chunkJ6POE4HLcjs.levenshteinDistance; exports.matchText = _chunkJ6POE4HLcjs.matchText; exports.roundGrade = _chunk4OHWLCI6cjs.roundGrade; exports.score = _chunk4OHWLCI6cjs.score;
26
+
27
+
28
+
29
+
30
+
31
+
32
+
33
+
34
+
35
+
36
+
37
+
38
+
39
+
40
+
41
+
42
+ exports.DEFAULT_PASS_THRESHOLD = _chunkUHQVIC3Pcjs.DEFAULT_PASS_THRESHOLD; exports.DICTATION_MAX_ACCEPTED_TRANSCRIPTS = _chunkXONKWL6Dcjs.DICTATION_MAX_ACCEPTED_TRANSCRIPTS; exports.DICTATION_MAX_EQUIVALENCES = _chunkXONKWL6Dcjs.DICTATION_MAX_EQUIVALENCES; exports.DICTATION_MAX_EQUIVALENCE_LENGTH = _chunkXONKWL6Dcjs.DICTATION_MAX_EQUIVALENCE_LENGTH; exports.DICTATION_MAX_TEXT_LENGTH = _chunkXONKWL6Dcjs.DICTATION_MAX_TEXT_LENGTH; exports.DICTATION_MAX_TRANSCRIPT_LENGTH = _chunkXONKWL6Dcjs.DICTATION_MAX_TRANSCRIPT_LENGTH; exports.alignDictation = _chunkXONKWL6Dcjs.alignDictation; exports.classifyBand = _chunkUHQVIC3Pcjs.classifyBand; exports.composeAssessmentScore = _chunkUHQVIC3Pcjs.composeAssessmentScore; exports.computePassThreshold = _chunkUHQVIC3Pcjs.computePassThreshold; exports.dictationReferenceWords = _chunkXONKWL6Dcjs.dictationReferenceWords; exports.diffDictationChars = _chunkUHQVIC3Pcjs.diffDictationChars; exports.evaluate = _chunkUHQVIC3Pcjs.evaluate; exports.gte = _chunkUHQVIC3Pcjs.gte; exports.levenshteinDistance = _chunkXONKWL6Dcjs.levenshteinDistance; exports.matchText = _chunkXONKWL6Dcjs.matchText; exports.roundGrade = _chunkUHQVIC3Pcjs.roundGrade; exports.score = _chunkUHQVIC3Pcjs.score;
27
43
  //# sourceMappingURL=scoring.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["/home/runner/work/learning-kit/learning-kit/packages/lk-core/dist/scoring.cjs"],"names":[],"mappings":"AAAA;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACF,wDAA6B;AAC7B;AACE;AACA;AACF,wDAA6B;AAC7B;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACF,yjBAAC","file":"/home/runner/work/learning-kit/learning-kit/packages/lk-core/dist/scoring.cjs"}
1
+ {"version":3,"sources":["/home/runner/work/learning-kit/learning-kit/packages/lk-core/dist/scoring.cjs"],"names":[],"mappings":"AAAA;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACF,wDAA6B;AAC7B;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACF,wDAA6B;AAC7B;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACF,usCAAC","file":"/home/runner/work/learning-kit/learning-kit/packages/lk-core/dist/scoring.cjs"}
@@ -1,5 +1,5 @@
1
- import { r as ItemOutcome, A as ActivityData, L as LearnerResponse, e as ActivityType, x as ScoringResult } from './activity-B2QxK15j.cjs';
2
- export { T as TextMatchPolicy, y as TextMatchResult, $ as levenshteinDistance, a0 as matchText } from './activity-B2QxK15j.cjs';
1
+ import { w as ItemOutcome, f as DictationData, A as ActivityData, L as LearnerResponse, e as ActivityType, K as ScoringResult } from './activity-DfAmJ1sl.cjs';
2
+ export { T as TextMatchPolicy, O as TextMatchResult, a5 as levenshteinDistance, a6 as matchText } from './activity-DfAmJ1sl.cjs';
3
3
 
4
4
  /**
5
5
  * Rounding for assessment scores.
@@ -188,8 +188,153 @@ interface AssessmentScore {
188
188
  */
189
189
  declare function composeAssessmentScore(sections: readonly AssessmentSectionInput[], policy: CompositionPolicy): AssessmentScore;
190
190
 
191
+ /** One transcript word, or one extra word the learner typed, after alignment. */
192
+ interface DictationWordAlignment {
193
+ /** `w<n>`, the 1-based position of a transcript word; absent for an extra word. */
194
+ itemId?: string;
195
+ /** The transcript word, normalised; `''` for an extra word. */
196
+ reference: string;
197
+ /** The word the learner typed for it, normalised; `''` for a missing word. */
198
+ attempt: string;
199
+ /** How alike the two words are, in [0, 1]; `0` for a missing or an extra word. */
200
+ similarity: number;
201
+ status: 'correct' | 'incorrect' | 'missing' | 'extra';
202
+ }
203
+ /**
204
+ * The comparison a dictation grade is made of: the normalised strings, which
205
+ * candidate transcript the attempt was scored against, and the word pairings.
206
+ * The same data the scorer reads, so a marked display and the grade cannot
207
+ * disagree.
208
+ */
209
+ interface DictationAlignment {
210
+ /**
211
+ * Which candidate the attempt was scored against, by ORIGINAL position:
212
+ * `0` is `transcript`, `n` is `acceptedTranscripts[n − 1]`; `-1` when no
213
+ * candidate survives normalisation (stale or transcript-less data).
214
+ */
215
+ candidateIndex: number;
216
+ /** The chosen candidate, normalised; `''` when `candidateIndex` is `-1`. */
217
+ reference: string;
218
+ /** The attempt, normalised and possibly truncated. */
219
+ attempt: string;
220
+ /**
221
+ * Whether the learner's text was cut: at `DICTATION_MAX_TEXT_LENGTH` before or
222
+ * after normalisation, or while equivalence rules were growing it.
223
+ */
224
+ truncated: boolean;
225
+ /** The similarity of `attempt` to `reference` — the score. */
226
+ similarity: number;
227
+ /** Transcript words and extra words, in the order they align. */
228
+ words: DictationWordAlignment[];
229
+ }
230
+ /**
231
+ * What `alignDictation` reads. Every field optional, and read defensively: full
232
+ * data, a practice payload, a `redact(data, { reveal: 'after-submit' })`
233
+ * projection, or a plain redacted projection with no transcript at all — which
234
+ * yields `candidateIndex: -1`, never a throw.
235
+ */
236
+ type DictationReference = Partial<Pick<DictationData, 'transcript' | 'acceptedTranscripts' | 'tolerance'>>;
237
+ /**
238
+ * The words the scorer can emit a detail for, one list per candidate — index 0
239
+ * is the transcript, index n is `acceptedTranscripts[n − 1]` — each word in the
240
+ * normalised form `correctResponse` carries. A candidate that normalises to
241
+ * nothing gives an empty list. Derivable from the activity alone, which is what
242
+ * a validator of stored details joins against: by id AND by word, because
243
+ * positional ids alone cannot tell an edited transcript of the same length.
244
+ */
245
+ declare function dictationReferenceWords(data: DictationReference): readonly (readonly {
246
+ itemId: string;
247
+ word: string;
248
+ }[])[];
249
+ /**
250
+ * Compares a learner's text with a dictation's transcript(s). Pure and
251
+ * deterministic: no locale, no `Intl`, integer alignment costs, one pinned
252
+ * tie order.
253
+ *
254
+ * The attempt is cut at `DICTATION_MAX_TEXT_LENGTH` code points before and
255
+ * after normalisation — a cut that ends on a space leaves no empty word after
256
+ * it — and `truncated` is also set when the rules grew its
257
+ * working text past the normaliser's bound (see `normalizeDictationText`).
258
+ * Every candidate — the transcript and at most
259
+ * `DICTATION_MAX_ACCEPTED_TRANSCRIPTS` accepted transcripts — is normalised
260
+ * with the same tolerance and cut at `DICTATION_MAX_TRANSCRIPT_LENGTH`; the
261
+ * attempt is scored against each surviving candidate and the first best wins.
262
+ * Words are then paired by a word-level edit distance with exact-equality
263
+ * costs, backtracked from the end preferring a pairing, then a missing
264
+ * transcript word, then an extra typed word; a paired word's similarity is its
265
+ * own character similarity.
266
+ */
267
+ declare function alignDictation(data: DictationReference, text: string): DictationAlignment;
268
+
269
+ /** One code-point edit between a reference string and an attempt. */
270
+ interface DictationCharOp {
271
+ op: 'equal' | 'substitute' | 'missing' | 'extra';
272
+ /** One code point of the reference, or `''` for an extra character. */
273
+ reference: string;
274
+ /** One code point of the attempt, or `''` for a missing character. */
275
+ attempt: string;
276
+ }
277
+ /**
278
+ * The code-point edit operations between two NORMALISED strings — the
279
+ * `reference` and `attempt` of a `DictationAlignment`, or one word pair from
280
+ * it — for a marked display: a character the learner typed right, typed
281
+ * wrong, left out, or added. Ties are broken preferring an equal or substituted
282
+ * pair, then a missing character, then an extra one, from the end of both
283
+ * strings; the number of non-`equal` ops is the edit distance.
284
+ *
285
+ * Decoration, never grading: it needs O(|reference|·|attempt|) memory, so the
286
+ * scorer does not call it, and a display computes it once per result.
287
+ */
288
+ declare function diffDictationChars(reference: string, attempt: string): DictationCharOp[];
289
+
290
+ /**
291
+ * The longest transcript a dictation accepts, in code points, before and after
292
+ * its equivalences are applied — and its rules may not grow it past twice
293
+ * `DICTATION_MAX_TEXT_LENGTH` on the way. A dictation is a sentence or two; the
294
+ * cap keeps the scorer's quadratic step bounded on the server that grades an
295
+ * exam.
296
+ */
297
+ declare const DICTATION_MAX_TRANSCRIPT_LENGTH = 2000;
298
+ /**
299
+ * The longest learner text the scorer compares, in code points. Text beyond it
300
+ * is cut off — before normalisation, and again after it, because a rewrite rule
301
+ * or a folded character can lengthen text — and the alignment says so.
302
+ */
303
+ declare const DICTATION_MAX_TEXT_LENGTH = 8000;
304
+ /** The longest `from` and `to` of an equivalence rule, in code points. */
305
+ declare const DICTATION_MAX_EQUIVALENCE_LENGTH = 200;
306
+ /**
307
+ * The most equivalence rules a dictation carries. The scorer reads no rule
308
+ * past this many, so stale data with more cannot slow a grading run.
309
+ */
310
+ declare const DICTATION_MAX_EQUIVALENCES = 100;
311
+ /**
312
+ * The most accepted transcripts a dictation carries. The scorer reads no more
313
+ * than this many, for the same reason.
314
+ */
315
+ declare const DICTATION_MAX_ACCEPTED_TRANSCRIPTS = 10;
316
+
191
317
  /** Default minimum scaled score required to pass when `passThreshold` is absent. */
192
318
  declare const DEFAULT_PASS_THRESHOLD = 0.7;
319
+ /**
320
+ * Options for {@link score} and {@link evaluate}. Additive: with none, both
321
+ * behave exactly as they always have.
322
+ */
323
+ interface ScoringOptions {
324
+ /**
325
+ * Compare the pass line the way the score is displayed — both sides rounded,
326
+ * via {@link computePassThreshold} — so an item shown as "70%" is not
327
+ * recorded as a fail at 69.995. `passed` AND the authored feedback selected by
328
+ * it follow the rounded comparison. Opt-in, for the reason
329
+ * {@link computePassThreshold} gives: switching it on moves item-level
330
+ * pass/fail for scores in the rounding band.
331
+ *
332
+ * Checked at the call: `null` reads as no policy, and a policy that cannot be
333
+ * applied — an unknown `mode`, or a `dp` that is not a whole number from 0 to
334
+ * 15 — throws a `RangeError` instead of quietly failing every comparison.
335
+ */
336
+ rounding?: RoundingPolicy;
337
+ }
193
338
  /**
194
339
  * Returns `true` iff `score` meets or exceeds the activity's `passThreshold`,
195
340
  * defaulting to {@link DEFAULT_PASS_THRESHOLD} (0.7) when the field is absent.
@@ -216,7 +361,7 @@ declare function computePassThreshold(activityData: ActivityData, score: number,
216
361
  * `written-response`) throws {@link DeferredScoringError} — use
217
362
  * {@link evaluate}, which can express "not graded yet".
218
363
  */
219
- declare function score(activityType: ActivityType, activityData: ActivityData, learnerResponse: LearnerResponse): ScoringResult;
364
+ declare function score(activityType: ActivityType, activityData: ActivityData, learnerResponse: LearnerResponse, options?: ScoringOptions): ScoringResult;
220
365
  /**
221
366
  * Evaluates a learner response against an activity and returns an
222
367
  * {@link ItemOutcome} — the resilient, forward-compatible alternative to
@@ -234,6 +379,6 @@ declare function score(activityType: ActivityType, activityData: ActivityData, l
234
379
  * The activity type is read from `data.type` — there is no separate type
235
380
  * parameter to disagree with the payload.
236
381
  */
237
- declare function evaluate(data: ActivityData, response: LearnerResponse): ItemOutcome;
382
+ declare function evaluate(data: ActivityData, response: LearnerResponse, options?: ScoringOptions): ItemOutcome;
238
383
 
239
- export { type AssessmentScore, type AssessmentSectionInput, type Band, type CompositionPolicy, DEFAULT_PASS_THRESHOLD, type PassFailureReason, type RoundingMode, type RoundingPolicy, type ScoredItem, type SectionScore, classifyBand, composeAssessmentScore, computePassThreshold, evaluate, gte, roundGrade, score };
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 };
package/dist/scoring.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { r as ItemOutcome, A as ActivityData, L as LearnerResponse, e as ActivityType, x as ScoringResult } from './activity-B2QxK15j.js';
2
- export { T as TextMatchPolicy, y as TextMatchResult, $ as levenshteinDistance, a0 as matchText } from './activity-B2QxK15j.js';
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';
3
3
 
4
4
  /**
5
5
  * Rounding for assessment scores.
@@ -188,8 +188,153 @@ interface AssessmentScore {
188
188
  */
189
189
  declare function composeAssessmentScore(sections: readonly AssessmentSectionInput[], policy: CompositionPolicy): AssessmentScore;
190
190
 
191
+ /** One transcript word, or one extra word the learner typed, after alignment. */
192
+ interface DictationWordAlignment {
193
+ /** `w<n>`, the 1-based position of a transcript word; absent for an extra word. */
194
+ itemId?: string;
195
+ /** The transcript word, normalised; `''` for an extra word. */
196
+ reference: string;
197
+ /** The word the learner typed for it, normalised; `''` for a missing word. */
198
+ attempt: string;
199
+ /** How alike the two words are, in [0, 1]; `0` for a missing or an extra word. */
200
+ similarity: number;
201
+ status: 'correct' | 'incorrect' | 'missing' | 'extra';
202
+ }
203
+ /**
204
+ * The comparison a dictation grade is made of: the normalised strings, which
205
+ * candidate transcript the attempt was scored against, and the word pairings.
206
+ * The same data the scorer reads, so a marked display and the grade cannot
207
+ * disagree.
208
+ */
209
+ interface DictationAlignment {
210
+ /**
211
+ * Which candidate the attempt was scored against, by ORIGINAL position:
212
+ * `0` is `transcript`, `n` is `acceptedTranscripts[n − 1]`; `-1` when no
213
+ * candidate survives normalisation (stale or transcript-less data).
214
+ */
215
+ candidateIndex: number;
216
+ /** The chosen candidate, normalised; `''` when `candidateIndex` is `-1`. */
217
+ reference: string;
218
+ /** The attempt, normalised and possibly truncated. */
219
+ attempt: string;
220
+ /**
221
+ * Whether the learner's text was cut: at `DICTATION_MAX_TEXT_LENGTH` before or
222
+ * after normalisation, or while equivalence rules were growing it.
223
+ */
224
+ truncated: boolean;
225
+ /** The similarity of `attempt` to `reference` — the score. */
226
+ similarity: number;
227
+ /** Transcript words and extra words, in the order they align. */
228
+ words: DictationWordAlignment[];
229
+ }
230
+ /**
231
+ * What `alignDictation` reads. Every field optional, and read defensively: full
232
+ * data, a practice payload, a `redact(data, { reveal: 'after-submit' })`
233
+ * projection, or a plain redacted projection with no transcript at all — which
234
+ * yields `candidateIndex: -1`, never a throw.
235
+ */
236
+ type DictationReference = Partial<Pick<DictationData, 'transcript' | 'acceptedTranscripts' | 'tolerance'>>;
237
+ /**
238
+ * The words the scorer can emit a detail for, one list per candidate — index 0
239
+ * is the transcript, index n is `acceptedTranscripts[n − 1]` — each word in the
240
+ * normalised form `correctResponse` carries. A candidate that normalises to
241
+ * nothing gives an empty list. Derivable from the activity alone, which is what
242
+ * a validator of stored details joins against: by id AND by word, because
243
+ * positional ids alone cannot tell an edited transcript of the same length.
244
+ */
245
+ declare function dictationReferenceWords(data: DictationReference): readonly (readonly {
246
+ itemId: string;
247
+ word: string;
248
+ }[])[];
249
+ /**
250
+ * Compares a learner's text with a dictation's transcript(s). Pure and
251
+ * deterministic: no locale, no `Intl`, integer alignment costs, one pinned
252
+ * tie order.
253
+ *
254
+ * The attempt is cut at `DICTATION_MAX_TEXT_LENGTH` code points before and
255
+ * after normalisation — a cut that ends on a space leaves no empty word after
256
+ * it — and `truncated` is also set when the rules grew its
257
+ * working text past the normaliser's bound (see `normalizeDictationText`).
258
+ * Every candidate — the transcript and at most
259
+ * `DICTATION_MAX_ACCEPTED_TRANSCRIPTS` accepted transcripts — is normalised
260
+ * with the same tolerance and cut at `DICTATION_MAX_TRANSCRIPT_LENGTH`; the
261
+ * attempt is scored against each surviving candidate and the first best wins.
262
+ * Words are then paired by a word-level edit distance with exact-equality
263
+ * costs, backtracked from the end preferring a pairing, then a missing
264
+ * transcript word, then an extra typed word; a paired word's similarity is its
265
+ * own character similarity.
266
+ */
267
+ declare function alignDictation(data: DictationReference, text: string): DictationAlignment;
268
+
269
+ /** One code-point edit between a reference string and an attempt. */
270
+ interface DictationCharOp {
271
+ op: 'equal' | 'substitute' | 'missing' | 'extra';
272
+ /** One code point of the reference, or `''` for an extra character. */
273
+ reference: string;
274
+ /** One code point of the attempt, or `''` for a missing character. */
275
+ attempt: string;
276
+ }
277
+ /**
278
+ * The code-point edit operations between two NORMALISED strings — the
279
+ * `reference` and `attempt` of a `DictationAlignment`, or one word pair from
280
+ * it — for a marked display: a character the learner typed right, typed
281
+ * wrong, left out, or added. Ties are broken preferring an equal or substituted
282
+ * pair, then a missing character, then an extra one, from the end of both
283
+ * strings; the number of non-`equal` ops is the edit distance.
284
+ *
285
+ * Decoration, never grading: it needs O(|reference|·|attempt|) memory, so the
286
+ * scorer does not call it, and a display computes it once per result.
287
+ */
288
+ declare function diffDictationChars(reference: string, attempt: string): DictationCharOp[];
289
+
290
+ /**
291
+ * The longest transcript a dictation accepts, in code points, before and after
292
+ * its equivalences are applied — and its rules may not grow it past twice
293
+ * `DICTATION_MAX_TEXT_LENGTH` on the way. A dictation is a sentence or two; the
294
+ * cap keeps the scorer's quadratic step bounded on the server that grades an
295
+ * exam.
296
+ */
297
+ declare const DICTATION_MAX_TRANSCRIPT_LENGTH = 2000;
298
+ /**
299
+ * The longest learner text the scorer compares, in code points. Text beyond it
300
+ * is cut off — before normalisation, and again after it, because a rewrite rule
301
+ * or a folded character can lengthen text — and the alignment says so.
302
+ */
303
+ declare const DICTATION_MAX_TEXT_LENGTH = 8000;
304
+ /** The longest `from` and `to` of an equivalence rule, in code points. */
305
+ declare const DICTATION_MAX_EQUIVALENCE_LENGTH = 200;
306
+ /**
307
+ * The most equivalence rules a dictation carries. The scorer reads no rule
308
+ * past this many, so stale data with more cannot slow a grading run.
309
+ */
310
+ declare const DICTATION_MAX_EQUIVALENCES = 100;
311
+ /**
312
+ * The most accepted transcripts a dictation carries. The scorer reads no more
313
+ * than this many, for the same reason.
314
+ */
315
+ declare const DICTATION_MAX_ACCEPTED_TRANSCRIPTS = 10;
316
+
191
317
  /** Default minimum scaled score required to pass when `passThreshold` is absent. */
192
318
  declare const DEFAULT_PASS_THRESHOLD = 0.7;
319
+ /**
320
+ * Options for {@link score} and {@link evaluate}. Additive: with none, both
321
+ * behave exactly as they always have.
322
+ */
323
+ interface ScoringOptions {
324
+ /**
325
+ * Compare the pass line the way the score is displayed — both sides rounded,
326
+ * via {@link computePassThreshold} — so an item shown as "70%" is not
327
+ * recorded as a fail at 69.995. `passed` AND the authored feedback selected by
328
+ * it follow the rounded comparison. Opt-in, for the reason
329
+ * {@link computePassThreshold} gives: switching it on moves item-level
330
+ * pass/fail for scores in the rounding band.
331
+ *
332
+ * Checked at the call: `null` reads as no policy, and a policy that cannot be
333
+ * applied — an unknown `mode`, or a `dp` that is not a whole number from 0 to
334
+ * 15 — throws a `RangeError` instead of quietly failing every comparison.
335
+ */
336
+ rounding?: RoundingPolicy;
337
+ }
193
338
  /**
194
339
  * Returns `true` iff `score` meets or exceeds the activity's `passThreshold`,
195
340
  * defaulting to {@link DEFAULT_PASS_THRESHOLD} (0.7) when the field is absent.
@@ -216,7 +361,7 @@ declare function computePassThreshold(activityData: ActivityData, score: number,
216
361
  * `written-response`) throws {@link DeferredScoringError} — use
217
362
  * {@link evaluate}, which can express "not graded yet".
218
363
  */
219
- declare function score(activityType: ActivityType, activityData: ActivityData, learnerResponse: LearnerResponse): ScoringResult;
364
+ declare function score(activityType: ActivityType, activityData: ActivityData, learnerResponse: LearnerResponse, options?: ScoringOptions): ScoringResult;
220
365
  /**
221
366
  * Evaluates a learner response against an activity and returns an
222
367
  * {@link ItemOutcome} — the resilient, forward-compatible alternative to
@@ -234,6 +379,6 @@ declare function score(activityType: ActivityType, activityData: ActivityData, l
234
379
  * The activity type is read from `data.type` — there is no separate type
235
380
  * parameter to disagree with the payload.
236
381
  */
237
- declare function evaluate(data: ActivityData, response: LearnerResponse): ItemOutcome;
382
+ declare function evaluate(data: ActivityData, response: LearnerResponse, options?: ScoringOptions): ItemOutcome;
238
383
 
239
- export { type AssessmentScore, type AssessmentSectionInput, type Band, type CompositionPolicy, DEFAULT_PASS_THRESHOLD, type PassFailureReason, type RoundingMode, type RoundingPolicy, type ScoredItem, type SectionScore, classifyBand, composeAssessmentScore, computePassThreshold, evaluate, gte, roundGrade, score };
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 };
package/dist/scoring.js CHANGED
@@ -3,20 +3,36 @@ import {
3
3
  classifyBand,
4
4
  composeAssessmentScore,
5
5
  computePassThreshold,
6
+ diffDictationChars,
6
7
  evaluate,
7
8
  gte,
8
9
  roundGrade,
9
10
  score
10
- } from "./chunk-RK2NVTHZ.js";
11
+ } from "./chunk-FGN4LLSP.js";
11
12
  import {
13
+ DICTATION_MAX_ACCEPTED_TRANSCRIPTS,
14
+ DICTATION_MAX_EQUIVALENCES,
15
+ DICTATION_MAX_EQUIVALENCE_LENGTH,
16
+ DICTATION_MAX_TEXT_LENGTH,
17
+ DICTATION_MAX_TRANSCRIPT_LENGTH,
18
+ alignDictation,
19
+ dictationReferenceWords,
12
20
  levenshteinDistance,
13
21
  matchText
14
- } from "./chunk-FVN37GIH.js";
22
+ } from "./chunk-XKKKWKRH.js";
15
23
  export {
16
24
  DEFAULT_PASS_THRESHOLD,
25
+ DICTATION_MAX_ACCEPTED_TRANSCRIPTS,
26
+ DICTATION_MAX_EQUIVALENCES,
27
+ DICTATION_MAX_EQUIVALENCE_LENGTH,
28
+ DICTATION_MAX_TEXT_LENGTH,
29
+ DICTATION_MAX_TRANSCRIPT_LENGTH,
30
+ alignDictation,
17
31
  classifyBand,
18
32
  composeAssessmentScore,
19
33
  computePassThreshold,
34
+ dictationReferenceWords,
35
+ diffDictationChars,
20
36
  evaluate,
21
37
  gte,
22
38
  levenshteinDistance,
package/dist/xapi.cjs CHANGED
@@ -4,13 +4,13 @@
4
4
 
5
5
 
6
6
 
7
- var _chunkO2IVKTHVcjs = require('./chunk-O2IVKTHV.cjs');
8
- require('./chunk-J6POE4HL.cjs');
7
+ var _chunkFT7SAC3Xcjs = require('./chunk-FT7SAC3X.cjs');
8
+ require('./chunk-XONKWL6D.cjs');
9
9
 
10
10
 
11
11
 
12
12
 
13
13
 
14
14
 
15
- exports.XAPIVerb = _chunkO2IVKTHVcjs.XAPIVerb; exports.XAPI_VERB_DISPLAY = _chunkO2IVKTHVcjs.XAPI_VERB_DISPLAY; exports.validateXAPIStatement = _chunkO2IVKTHVcjs.validateXAPIStatement; exports.xAPIBuilder = _chunkO2IVKTHVcjs.xAPIBuilder; exports.xapiDefinitionFor = _chunkO2IVKTHVcjs.xapiDefinitionFor;
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;
16
16
  //# sourceMappingURL=xapi.cjs.map
package/dist/xapi.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { X as XAPIActor, x as ScoringResult, O as XAPIContext, U as XAPIResult, Z as XAPIStatement } from './activity-B2QxK15j.cjs';
1
+ import { X as XAPIActor, K as ScoringResult, Z as XAPIContext, a1 as XAPIResult, a3 as XAPIStatement } from './activity-DfAmJ1sl.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, x as ScoringResult, O as XAPIContext, U as XAPIResult, Z as XAPIStatement } from './activity-B2QxK15j.js';
1
+ import { X as XAPIActor, K as ScoringResult, Z as XAPIContext, a1 as XAPIResult, a3 as XAPIStatement } from './activity-DfAmJ1sl.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-UQPNJP2O.js";
8
- import "./chunk-FVN37GIH.js";
7
+ } from "./chunk-PYP3HCRQ.js";
8
+ import "./chunk-XKKKWKRH.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.12.0",
3
+ "version": "0.13.1",
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",
package/vectors/README.md CHANGED
@@ -56,8 +56,16 @@ a known hazard carry a `note` saying why they exist.
56
56
  - **Written-response word counts**, recomputed from the text rather than taken
57
57
  from the client's `wordCount`, with inclusive `minWords` / `maxWords` bounds.
58
58
  - **`matchText` and `levenshteinDistance`** across every authored policy.
59
+ - **`alignDictation`, `diffDictationChars` and `dictationReferenceWords`**, with
60
+ the five **`DICTATION_MAX_*`** limits: how a dictation reads the learner's text
61
+ and each transcript — punctuation, compatibility forms, joiners and other
62
+ invisible characters, lone surrogates — how equivalence rules rewrite them,
63
+ which transcript an attempt is compared with when several are accepted and how
64
+ a tie breaks, the words a transcript is split into, how words and characters
65
+ are paired, and where each limit cuts.
59
66
  - **`computePassThreshold` and `DEFAULT_PASS_THRESHOLD`**, including an authored
60
- threshold of `0`.
67
+ threshold of `0`, and the opt-in `rounding` option of `score`, both where it
68
+ turns a raw fail in the rounding band into a pass and where it changes nothing.
61
69
  - **`roundGrade`, `gte` and `classifyBand`** in every mode, including float
62
70
  edges, negative zero, half-even ties and near-ties, and bands that share a
63
71
  minimum.