@intellectif/lk-core 0.14.0 → 0.15.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 (49) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +1 -0
  3. package/dist/{activity-BVkg9VDo.d.cts → activity-C7cR8U9n.d.cts} +35 -2
  4. package/dist/{activity-BVkg9VDo.d.ts → activity-C7cR8U9n.d.ts} +35 -2
  5. package/dist/{chunk-R7PV3XIC.js → chunk-54XYQ3CS.js} +7 -4
  6. package/dist/chunk-54XYQ3CS.js.map +1 -0
  7. package/dist/{chunk-J5OIRIOW.cjs → chunk-5KWF34LJ.cjs} +31 -31
  8. package/dist/{chunk-J5OIRIOW.cjs.map → chunk-5KWF34LJ.cjs.map} +1 -1
  9. package/dist/{chunk-CZRUMHKW.cjs → chunk-7ETU6EXQ.cjs} +13 -10
  10. package/dist/chunk-7ETU6EXQ.cjs.map +1 -0
  11. package/dist/{chunk-6RND47QE.js → chunk-DPHX4E7X.js} +62 -3
  12. package/dist/chunk-DPHX4E7X.js.map +1 -0
  13. package/dist/{chunk-NBADQ3JU.js → chunk-EEMVWBPP.js} +195 -4
  14. package/dist/chunk-EEMVWBPP.js.map +1 -0
  15. package/dist/{chunk-RTIR6R2U.js → chunk-L6YBQXSB.js} +2 -2
  16. package/dist/{chunk-VCXIPLEW.cjs → chunk-QFWEF5ST.cjs} +63 -4
  17. package/dist/chunk-QFWEF5ST.cjs.map +1 -0
  18. package/dist/chunk-W7AQP6AV.cjs +418 -0
  19. package/dist/chunk-W7AQP6AV.cjs.map +1 -0
  20. package/dist/{index-v2_-Fyn3.d.ts → index-BWyMDlUS.d.cts} +367 -2
  21. package/dist/{index-BE1bM2Wi.d.cts → index-D10NaztZ.d.ts} +367 -2
  22. package/dist/index.cjs +397 -96
  23. package/dist/index.cjs.map +1 -1
  24. package/dist/index.d.cts +103 -7
  25. package/dist/index.d.ts +103 -7
  26. package/dist/index.js +310 -9
  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 +3 -3
  34. package/dist/scoring.d.cts +2 -2
  35. package/dist/scoring.d.ts +2 -2
  36. package/dist/scoring.js +2 -2
  37. package/dist/xapi.cjs +3 -3
  38. package/dist/xapi.d.cts +1 -1
  39. package/dist/xapi.d.ts +1 -1
  40. package/dist/xapi.js +2 -2
  41. package/package.json +1 -1
  42. package/dist/chunk-2NIALQEH.cjs +0 -227
  43. package/dist/chunk-2NIALQEH.cjs.map +0 -1
  44. package/dist/chunk-6RND47QE.js.map +0 -1
  45. package/dist/chunk-CZRUMHKW.cjs.map +0 -1
  46. package/dist/chunk-NBADQ3JU.js.map +0 -1
  47. package/dist/chunk-R7PV3XIC.js.map +0 -1
  48. package/dist/chunk-VCXIPLEW.cjs.map +0 -1
  49. /package/dist/{chunk-RTIR6R2U.js.map → chunk-L6YBQXSB.js.map} +0 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,36 @@
1
1
  # @intellectif/lk-core
2
2
 
3
+ ## 0.15.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 5a80ea8: Interactive video: an item group whose stimulus is a video can now carry a `timeline` — quizzes at
8
+ moments of the video, each holding one or more of the group's own questions.
9
+
10
+ Additive throughout. `MediaTimeline`, `TimelineCue`, `TimelineChapter`, `MediaProgress` and
11
+ `MediaTrack` are new types; `ItemGroup.timeline`, `ActivityMedia.tracks` and `ActivityMedia.poster`
12
+ are new optional fields; `SequenceSlot.group` now carries the `cue` a question sits in, and
13
+ `AttemptPlanDrift` reports a moved quiz as `changedCueSlotIds`.
14
+
15
+ The schema places every question in exactly one quiz, keeps quiz ids unique, orders chapters, allows
16
+ only the five types a video can ask (multiple choice, fill-in-the-blanks, gap select, dictation,
17
+ read-aloud) and refuses a dictation with no recording of its own. `INTERACTIVE_VIDEO_ITEM_TYPES` and
18
+ `isInteractiveVideoItemType` are that list as data. `readMediaProgress` reads a stored resume point
19
+ without trusting it, `composeTimelineScore` folds a video's questions into an attempt's score, and
20
+ `createInteractiveVideoDraft` plus the `ig_timeline_*` authoring codes cover the editor.
21
+
22
+ Redaction carries the timeline, the poster and the caption tracks through: they are learner-visible
23
+ by definition. Nothing changed shape, so an older build that has never heard of a timeline drops it
24
+ and renders the same testlet with the same slots and the same grades.
25
+
26
+ See `docs/interactive-video.md`.
27
+
28
+ ## 0.14.1
29
+
30
+ ### Patch Changes
31
+
32
+ - ecc47e0: `validateXAPIStatement` now recognises a production browser build. In production it is meant to log a malformed statement and send it anyway, and to throw only in development. It read `NODE_ENV` in a way no bundler replaces, and a browser has no `process` to read at run time, so in every production browser app it took the development path and threw — a malformed statement brought down the activity that built it rather than being logged. It now reads the literal `process.env.NODE_ENV` your bundler substitutes. Node, where `process` exists, behaves as before, and no grade changes.
33
+
3
34
  ## 0.14.0
4
35
 
5
36
  ### Minor Changes
package/README.md CHANGED
@@ -15,6 +15,7 @@ npm install @intellectif/lk-core
15
15
  ## What's in the box
16
16
 
17
17
  - **Activity schemas** for `multiple-choice`, `fill-in-the-blanks`, `written-response`, `gap-select`, `dictation` and `read-aloud`, with semantic refinements (e.g. *at least one correct option*, *single-select ⇒ exactly one correct*, *passage ↔ blank-id bijection*, *image/embed require `alt`*, *a dictation title never contains its whole transcript as words*, *a read-aloud's text leaves at least one word once punctuation is folded away*). Loose at every level, so your sidecar fields survive validation.
18
+ - **Interactive video** — an item group whose stimulus is a video can carry a `timeline`: quizzes at moments of the video, each holding questions of that group. The schema places every question in exactly one quiz, keeps the five embeddable types honest and refuses a dictation with no recording of its own; `readMediaProgress` reads a stored resume point without trusting it, and `composeTimelineScore` folds the video's questions into an attempt's score. See [docs/interactive-video.md](https://github.com/intellectif/learning-kit/blob/main/docs/interactive-video.md).
18
19
  - **`validateActivity(type, data)`** — the authoritative runtime validator. Returns a typed `{ success, data }` or a structured `{ success: false, errors[] }`, and **throws `UnknownActivityTypeError` if `type` is not registered** — so guard it when validating a content bank that may carry types this build does not know. `validateItemGroup(data)` is the equivalent for a `Stimulus` + `ItemGroup` container, which `validateActivity` cannot take.
19
20
  - **`validateDraft(type, draft)`** — for editors. Reports `complete`, `incomplete` (something not written yet) or `invalid` (something wrong), each issue with a `severity` and a `code` — documented for every problem the checks recognise — and deliberately no `success` boolean to misread. Stricter than `validateActivity`, never looser. **`createDraft(type, { newId })`** returns an empty draft to start from; a multiple-choice draft marks no option correct.
20
21
  - **An open type system** — `defineActivityType` / `registerActivityType` make an activity type a *value*, not a hard-coded union member: register one and `validateActivity`, `validateDraft`, `score`, `evaluate`, `redact` and `jsonSchemaFor` all work for it, with no SDK release. An `authoring` block on the descriptor gives it draft support.
@@ -384,6 +384,18 @@ interface ActivityDataMap {
384
384
  type ActivityType = keyof ActivityDataMap;
385
385
  /** Union of all valid activity data shapes. */
386
386
  type ActivityData = ActivityDataMap[ActivityType];
387
+ /** A text track for an `audio` or `video` recording. */
388
+ interface MediaTrack {
389
+ kind: 'captions' | 'subtitles';
390
+ /** WebVTT URL. A player may fetch it through a loader the host supplies, with the host's credentials. */
391
+ src: string;
392
+ /** BCP 47 language tag of the text (`en`, `es`, `pt-BR`). */
393
+ srclang: string;
394
+ /** Shown in the captions menu. Non-empty, at most 60 characters. */
395
+ label: string;
396
+ /** Selected when the learner has chosen no language yet. At most one track may be the default. */
397
+ default?: boolean;
398
+ }
387
399
  /**
388
400
  * Optional media (image/audio/video) shown above a question or passage.
389
401
  * URL-only: hosting/delivery is the consuming application's responsibility.
@@ -400,6 +412,15 @@ interface ActivityMedia {
400
412
  alt?: string;
401
413
  /** Optional WebVTT captions track URL for `audio`/`video`. */
402
414
  captionsUrl?: string;
415
+ /**
416
+ * Text tracks for `audio`/`video`, one per language: what `captionsUrl`
417
+ * cannot say — a language, a label, more than one. When present, a player
418
+ * that reads it ignores `captionsUrl`. At most 12, one `default`, and no two
419
+ * sharing a `kind` and a `srclang`.
420
+ */
421
+ tracks?: MediaTrack[];
422
+ /** An image shown before the first play. `video` only. */
423
+ poster?: string;
403
424
  /**
404
425
  * How the recording may be played. `audio` only — see
405
426
  * {@link MediaPlaybackPolicy}. Absent means today's behaviour exactly: the
@@ -1283,7 +1304,19 @@ type InteractionKind = 'option-selected' | 'option-deselected' | 'blank-filled'
1283
1304
  /** A stored take was sent for assessment. */
1284
1305
  | 'assessment-requested'
1285
1306
  /** An assessment request failed: the take has no grade. */
1286
- | 'assessment-failed' | (string & {});
1307
+ | 'assessment-failed'
1308
+ /** Interactive video: the learner started playback. */
1309
+ | 'video-played'
1310
+ /** Interactive video: playback paused, by the learner or by a quiz. */
1311
+ | 'video-paused'
1312
+ /** Interactive video: the playhead moved (`from`, `to`, and whether navigation `clamped` it). */
1313
+ | 'video-seeked' | 'video-rate-changed' | 'video-ended'
1314
+ /** Interactive video: a quiz opened (`cueId`, `at`). */
1315
+ | 'video-quiz-opened'
1316
+ /** Interactive video: one question of an open quiz was shown (`cueId`, `slotId`). */
1317
+ | 'video-quiz-question-shown' | 'video-quiz-skipped' | 'video-quiz-closed'
1318
+ /** Interactive video: captions turned on (the track's `srclang`) or off (`null`). */
1319
+ | 'video-captions-changed' | 'video-fullscreen-changed' | 'video-pip-changed' | (string & {});
1287
1320
  /** Fired by activity components on every discrete learner interaction. */
1288
1321
  interface InteractionEvent {
1289
1322
  /** The kind of interaction that occurred. */
@@ -1296,4 +1329,4 @@ interface InteractionEvent {
1296
1329
  payload: Record<string, unknown>;
1297
1330
  }
1298
1331
 
1299
- export { type WrittenResponseData as $, type ActivityData as A, type BlankConfig as B, type CriterionScore as C, type DeferredReason as D, type MultipleChoiceLearnerResponse as E, type FillInTheBlanksData as F, type GapSelectBank as G, type MultipleChoiceOption as H, type InlineCorrection as I, type MultipleChoiceOptionMedia as J, type ReadAloudDimension as K, type LearnerResponse as L, type MediaPlaybackPolicy as M, type NativeControlHint as N, type ReadAloudDimensionWeight as O, type ReadAloudLearnerResponse as P, type ReadAloudSlowMedia as Q, type ReadAloudData as R, type RecordingBounds as S, type RecordingRef as T, type ScoringDetail as U, type ScoringOutcome as V, type ScoringResult as W, type TextMatchPolicy as X, type TextMatchResult as Y, type ValidationError as Z, type ValidationResult as _, type ActivityDataMap as a, type WrittenResponseLearnerResponse as a0, type WrittenResponseRubric as a1, type WrittenResponseRubricCriterion as a2, type XAPIActor as a3, type XAPIConfig as a4, type XAPIContext as a5, type XAPIContextActivities as a6, type XAPIError as a7, type XAPIObject as a8, type XAPIResult as a9, type XAPIScore as aa, type XAPIStatement as ab, type XAPIVerbObject as ac, levenshteinDistance as ad, matchText as ae, type ActivityFeedback as b, type ActivityMedia as c, type ActivityResult as d, type ActivityType as e, type DeferredScoringPartial as f, type DictationData as g, type DictationEquivalence as h, type DictationLearnerResponse as i, type DictationSlowMedia as j, type DictationTolerance as k, type FillInTheBlanksLearnerResponse as l, type GapSelectChoice as m, type GapSelectData as n, type GapSelectGap as o, type GapSelectLearnerResponse as p, type GradeRecord as q, type Grader as r, type GraderKind as s, type GraderUsage as t, type GradingState as u, type InteractionEvent as v, type InteractionKind as w, type ItemOutcome as x, type LearnerResponseMap as y, type MultipleChoiceData as z };
1332
+ export { type ValidationResult as $, type ActivityData as A, type BlankConfig as B, type CriterionScore as C, type DeferredReason as D, type MultipleChoiceData as E, type FillInTheBlanksData as F, type GapSelectBank as G, type MultipleChoiceLearnerResponse as H, type InlineCorrection as I, type MultipleChoiceOption as J, type MultipleChoiceOptionMedia as K, type LearnerResponse as L, type MediaPlaybackPolicy as M, type NativeControlHint as N, type ReadAloudDimension as O, type ReadAloudDimensionWeight as P, type ReadAloudLearnerResponse as Q, type ReadAloudData as R, type ReadAloudSlowMedia as S, type RecordingBounds as T, type RecordingRef as U, type ScoringDetail as V, type ScoringOutcome as W, type ScoringResult as X, type TextMatchPolicy as Y, type TextMatchResult as Z, type ValidationError as _, type ActivityDataMap as a, type WrittenResponseData as a0, type WrittenResponseLearnerResponse as a1, type WrittenResponseRubric as a2, type WrittenResponseRubricCriterion as a3, type XAPIActor as a4, type XAPIConfig as a5, type XAPIContext as a6, type XAPIContextActivities as a7, type XAPIError as a8, type XAPIObject as a9, type XAPIResult as aa, type XAPIScore as ab, type XAPIStatement as ac, type XAPIVerbObject as ad, levenshteinDistance as ae, matchText as af, type ActivityFeedback as b, type ActivityMedia as c, type ActivityResult as d, type ActivityType as e, type DeferredScoringPartial as f, type DictationData as g, type DictationEquivalence as h, type DictationLearnerResponse as i, type DictationSlowMedia as j, type DictationTolerance as k, type FillInTheBlanksLearnerResponse as l, type GapSelectChoice as m, type GapSelectData as n, type GapSelectGap as o, type GapSelectLearnerResponse as p, type GradeRecord as q, type Grader as r, type GraderKind as s, type GraderUsage as t, type GradingState as u, type InteractionEvent as v, type InteractionKind as w, type ItemOutcome as x, type LearnerResponseMap as y, type MediaTrack as z };
@@ -384,6 +384,18 @@ interface ActivityDataMap {
384
384
  type ActivityType = keyof ActivityDataMap;
385
385
  /** Union of all valid activity data shapes. */
386
386
  type ActivityData = ActivityDataMap[ActivityType];
387
+ /** A text track for an `audio` or `video` recording. */
388
+ interface MediaTrack {
389
+ kind: 'captions' | 'subtitles';
390
+ /** WebVTT URL. A player may fetch it through a loader the host supplies, with the host's credentials. */
391
+ src: string;
392
+ /** BCP 47 language tag of the text (`en`, `es`, `pt-BR`). */
393
+ srclang: string;
394
+ /** Shown in the captions menu. Non-empty, at most 60 characters. */
395
+ label: string;
396
+ /** Selected when the learner has chosen no language yet. At most one track may be the default. */
397
+ default?: boolean;
398
+ }
387
399
  /**
388
400
  * Optional media (image/audio/video) shown above a question or passage.
389
401
  * URL-only: hosting/delivery is the consuming application's responsibility.
@@ -400,6 +412,15 @@ interface ActivityMedia {
400
412
  alt?: string;
401
413
  /** Optional WebVTT captions track URL for `audio`/`video`. */
402
414
  captionsUrl?: string;
415
+ /**
416
+ * Text tracks for `audio`/`video`, one per language: what `captionsUrl`
417
+ * cannot say — a language, a label, more than one. When present, a player
418
+ * that reads it ignores `captionsUrl`. At most 12, one `default`, and no two
419
+ * sharing a `kind` and a `srclang`.
420
+ */
421
+ tracks?: MediaTrack[];
422
+ /** An image shown before the first play. `video` only. */
423
+ poster?: string;
403
424
  /**
404
425
  * How the recording may be played. `audio` only — see
405
426
  * {@link MediaPlaybackPolicy}. Absent means today's behaviour exactly: the
@@ -1283,7 +1304,19 @@ type InteractionKind = 'option-selected' | 'option-deselected' | 'blank-filled'
1283
1304
  /** A stored take was sent for assessment. */
1284
1305
  | 'assessment-requested'
1285
1306
  /** An assessment request failed: the take has no grade. */
1286
- | 'assessment-failed' | (string & {});
1307
+ | 'assessment-failed'
1308
+ /** Interactive video: the learner started playback. */
1309
+ | 'video-played'
1310
+ /** Interactive video: playback paused, by the learner or by a quiz. */
1311
+ | 'video-paused'
1312
+ /** Interactive video: the playhead moved (`from`, `to`, and whether navigation `clamped` it). */
1313
+ | 'video-seeked' | 'video-rate-changed' | 'video-ended'
1314
+ /** Interactive video: a quiz opened (`cueId`, `at`). */
1315
+ | 'video-quiz-opened'
1316
+ /** Interactive video: one question of an open quiz was shown (`cueId`, `slotId`). */
1317
+ | 'video-quiz-question-shown' | 'video-quiz-skipped' | 'video-quiz-closed'
1318
+ /** Interactive video: captions turned on (the track's `srclang`) or off (`null`). */
1319
+ | 'video-captions-changed' | 'video-fullscreen-changed' | 'video-pip-changed' | (string & {});
1287
1320
  /** Fired by activity components on every discrete learner interaction. */
1288
1321
  interface InteractionEvent {
1289
1322
  /** The kind of interaction that occurred. */
@@ -1296,4 +1329,4 @@ interface InteractionEvent {
1296
1329
  payload: Record<string, unknown>;
1297
1330
  }
1298
1331
 
1299
- export { type WrittenResponseData as $, type ActivityData as A, type BlankConfig as B, type CriterionScore as C, type DeferredReason as D, type MultipleChoiceLearnerResponse as E, type FillInTheBlanksData as F, type GapSelectBank as G, type MultipleChoiceOption as H, type InlineCorrection as I, type MultipleChoiceOptionMedia as J, type ReadAloudDimension as K, type LearnerResponse as L, type MediaPlaybackPolicy as M, type NativeControlHint as N, type ReadAloudDimensionWeight as O, type ReadAloudLearnerResponse as P, type ReadAloudSlowMedia as Q, type ReadAloudData as R, type RecordingBounds as S, type RecordingRef as T, type ScoringDetail as U, type ScoringOutcome as V, type ScoringResult as W, type TextMatchPolicy as X, type TextMatchResult as Y, type ValidationError as Z, type ValidationResult as _, type ActivityDataMap as a, type WrittenResponseLearnerResponse as a0, type WrittenResponseRubric as a1, type WrittenResponseRubricCriterion as a2, type XAPIActor as a3, type XAPIConfig as a4, type XAPIContext as a5, type XAPIContextActivities as a6, type XAPIError as a7, type XAPIObject as a8, type XAPIResult as a9, type XAPIScore as aa, type XAPIStatement as ab, type XAPIVerbObject as ac, levenshteinDistance as ad, matchText as ae, type ActivityFeedback as b, type ActivityMedia as c, type ActivityResult as d, type ActivityType as e, type DeferredScoringPartial as f, type DictationData as g, type DictationEquivalence as h, type DictationLearnerResponse as i, type DictationSlowMedia as j, type DictationTolerance as k, type FillInTheBlanksLearnerResponse as l, type GapSelectChoice as m, type GapSelectData as n, type GapSelectGap as o, type GapSelectLearnerResponse as p, type GradeRecord as q, type Grader as r, type GraderKind as s, type GraderUsage as t, type GradingState as u, type InteractionEvent as v, type InteractionKind as w, type ItemOutcome as x, type LearnerResponseMap as y, type MultipleChoiceData as z };
1332
+ export { type ValidationResult as $, type ActivityData as A, type BlankConfig as B, type CriterionScore as C, type DeferredReason as D, type MultipleChoiceData as E, type FillInTheBlanksData as F, type GapSelectBank as G, type MultipleChoiceLearnerResponse as H, type InlineCorrection as I, type MultipleChoiceOption as J, type MultipleChoiceOptionMedia as K, type LearnerResponse as L, type MediaPlaybackPolicy as M, type NativeControlHint as N, type ReadAloudDimension as O, type ReadAloudDimensionWeight as P, type ReadAloudLearnerResponse as Q, type ReadAloudData as R, type ReadAloudSlowMedia as S, type RecordingBounds as T, type RecordingRef as U, type ScoringDetail as V, type ScoringOutcome as W, type ScoringResult as X, type TextMatchPolicy as Y, type TextMatchResult as Z, type ValidationError as _, type ActivityDataMap as a, type WrittenResponseData as a0, type WrittenResponseLearnerResponse as a1, type WrittenResponseRubric as a2, type WrittenResponseRubricCriterion as a3, type XAPIActor as a4, type XAPIConfig as a5, type XAPIContext as a6, type XAPIContextActivities as a7, type XAPIError as a8, type XAPIObject as a9, type XAPIResult as aa, type XAPIScore as ab, type XAPIStatement as ac, type XAPIVerbObject as ad, levenshteinDistance as ae, matchText as af, type ActivityFeedback as b, type ActivityMedia as c, type ActivityResult as d, type ActivityType as e, type DeferredScoringPartial as f, type DictationData as g, type DictationEquivalence as h, type DictationLearnerResponse as i, type DictationSlowMedia as j, type DictationTolerance as k, type FillInTheBlanksLearnerResponse as l, type GapSelectChoice as m, type GapSelectData as n, type GapSelectGap as o, type GapSelectLearnerResponse as p, type GradeRecord as q, type Grader as r, type GraderKind as s, type GraderUsage as t, type GradingState as u, type InteractionEvent as v, type InteractionKind as w, type ItemOutcome as x, type LearnerResponseMap as y, type MediaTrack as z };
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  ActivitySchemaError,
3
3
  getActivityTypeDescriptor
4
- } from "./chunk-6RND47QE.js";
4
+ } from "./chunk-DPHX4E7X.js";
5
5
 
6
6
  // src/xapi/validators.ts
7
7
  import { z } from "zod/v4";
@@ -66,8 +66,11 @@ var statementSchema = z.object({
66
66
  version: z.literal("1.0.3")
67
67
  });
68
68
  function isProduction() {
69
- const g = globalThis;
70
- return g.process?.env?.NODE_ENV === "production";
69
+ try {
70
+ return process.env.NODE_ENV === "production";
71
+ } catch {
72
+ return false;
73
+ }
71
74
  }
72
75
  function validateXAPIStatement(statement) {
73
76
  const result = statementSchema.safeParse(statement);
@@ -266,4 +269,4 @@ export {
266
269
  xAPIBuilder,
267
270
  xapiDefinitionFor
268
271
  };
269
- //# sourceMappingURL=chunk-R7PV3XIC.js.map
272
+ //# sourceMappingURL=chunk-54XYQ3CS.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/xapi/validators.ts","../src/xapi/verbs.ts","../src/xapi/builder.ts"],"sourcesContent":["import { z } from 'zod/v4';\nimport { ActivitySchemaError } from '../errors.js';\nimport type { ValidationError } from '../types/activity.js';\nimport type { XAPIStatement } from '../types/xapi.js';\n\nconst UUID_V4 = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;\n// Minimal IRI/URI check: a scheme followed by ':'.\nconst IRI = /^[a-z][a-z0-9+.-]*:/i;\n\nconst langMap = z.record(z.string(), z.string());\nconst extensions = z.record(z.string(), z.unknown());\n\nconst actorSchema = z\n .object({\n objectType: z.literal('Agent'),\n name: z.string().optional(),\n mbox: z\n .string()\n .regex(/^mailto:/, 'mbox must be a mailto IRI')\n .optional(),\n account: z.object({ homePage: z.string(), name: z.string() }).optional(),\n })\n .refine((actor) => actor.mbox !== undefined || actor.account !== undefined, {\n error: 'actor must be identified by an mbox or an account',\n });\n\nconst statementSchema = z.object({\n id: z.string().regex(UUID_V4, 'id must be a UUID v4'),\n actor: actorSchema,\n verb: z.object({\n id: z.string().regex(IRI, 'verb.id must be an IRI'),\n display: langMap,\n }),\n object: z.object({\n objectType: z.literal('Activity'),\n id: z.string().regex(IRI, 'object.id must be an IRI'),\n definition: z\n .object({\n name: langMap.optional(),\n description: langMap.optional(),\n type: z.string().optional(),\n interactionType: z.string().optional(),\n correctResponsesPattern: z.array(z.string()).optional(),\n choices: z.array(z.object({ id: z.string(), description: langMap.optional() })).optional(),\n extensions: extensions.optional(),\n })\n .optional(),\n }),\n result: z\n .object({\n // xAPI 1.0.3 permits scaled in [-1, 1]; the SDK only emits [0, 1].\n score: z\n .object({\n scaled: z.number().min(-1).max(1),\n raw: z.number().optional(),\n min: z.number().optional(),\n max: z.number().optional(),\n })\n .optional(),\n success: z.boolean().optional(),\n completion: z.boolean().optional(),\n duration: z.string().regex(/^P/, 'duration must be an ISO 8601 duration').optional(),\n response: z.string().optional(),\n extensions: extensions.optional(),\n })\n .optional(),\n context: z\n .object({\n platform: z.string().optional(),\n language: z.string().optional(),\n contextActivities: z\n .object({\n parent: z\n .array(z.object({ objectType: z.literal('Activity'), id: z.string() }))\n .optional(),\n grouping: z\n .array(z.object({ objectType: z.literal('Activity'), id: z.string() }))\n .optional(),\n category: z\n .array(z.object({ objectType: z.literal('Activity'), id: z.string() }))\n .optional(),\n other: z\n .array(z.object({ objectType: z.literal('Activity'), id: z.string() }))\n .optional(),\n })\n .optional(),\n extensions: extensions.optional(),\n })\n .optional(),\n timestamp: z\n .string()\n .refine((value) => !Number.isNaN(Date.parse(value)), 'timestamp must be ISO 8601'),\n version: z.literal('1.0.3'),\n});\n\n/**\n * The one global this module reads by name. Declared here, not globally: this\n * package compiles without Node's types on purpose, and nothing else in it may\n * come to rely on `process`.\n */\ndeclare const process: { env: { NODE_ENV?: string } };\n\n/**\n * Whether this is a production build.\n *\n * **The literal `process.env.NODE_ENV`, spelled exactly so.** It is the one\n * expression bundlers replace at build time, and a browser has no `process` to\n * read at run time. The reading this replaces,\n * `globalThis.process?.env?.NODE_ENV`, is not that expression, so no bundler\n * touched it, and in a browser it found no `process` and answered \"not\n * production\" in every production app — so a malformed statement threw there\n * instead of being logged and sent. Tests running in Node never saw it,\n * because Node has a `process`.\n *\n * This package's own build must leave the literal in its output, for the\n * consumer's bundler to replace. The `try` covers code that reaches a runtime\n * unbundled, with no `process` at all: that is not production, as it always\n * was.\n */\nfunction isProduction(): boolean {\n try {\n return process.env.NODE_ENV === 'production';\n } catch {\n return false;\n }\n}\n\n/**\n * Validates a constructed {@link XAPIStatement} against the xAPI 1.0.3\n * structural contract.\n *\n * In development a structural failure throws (caught early); in production it\n * logs a warning and returns (the statement is still sent — see the design's\n * Error Handling table). Uses a `zod/v4` schema rather than `ajv` + an\n * external JSON Schema to keep `lk-core` dependency-free.\n *\n * NOTE: `ActivitySchemaError` is reused here per the task spec; its\n * `activityType` field carries the sentinel `'xAPIStatement'`. A dedicated\n * `XAPIValidationError` would be semantically cleaner — flagged for review.\n */\nexport function validateXAPIStatement(statement: XAPIStatement): void {\n const result = statementSchema.safeParse(statement);\n if (result.success) {\n return;\n }\n\n const errors: ValidationError[] = result.error.issues.map((issue) => ({\n path: issue.path.map(String),\n message: issue.message,\n code: issue.code,\n }));\n\n if (isProduction()) {\n console.warn('[learning-kit] Invalid xAPI statement (sent anyway):', errors);\n return;\n }\n\n throw new ActivitySchemaError('xAPIStatement', errors);\n}\n","/**\n * The set of xAPI verbs emitted by SDK activities, keyed by a stable\n * identifier mapped to its canonical xAPI verb IRI (xAPI 1.0.3 / ADL registry).\n */\nexport const XAPIVerb = {\n ANSWERED: 'http://adlnet.gov/expapi/verbs/answered',\n COMPLETED: 'http://adlnet.gov/expapi/verbs/completed',\n EXPERIENCED: 'http://adlnet.gov/expapi/verbs/experienced',\n INTERACTED: 'http://adlnet.gov/expapi/verbs/interacted',\n ATTEMPTED: 'http://adlnet.gov/expapi/verbs/attempted',\n PASSED: 'http://adlnet.gov/expapi/verbs/passed',\n FAILED: 'http://adlnet.gov/expapi/verbs/failed',\n WATCHED: 'https://w3id.org/xapi/video/verbs/watched',\n /**\n * Emitted for deferred-grading submissions (e.g. written-response): the\n * learner has submitted work whose grade does not exist yet, so `answered`\n * (which implies a scored response) would be wrong. xAPI 1.0.3 defines no\n * canonical \"submit\" verb; the Activity Streams 1.0 IRI is the established\n * community choice (design decision recorded per Task 25.4).\n */\n SUBMITTED: 'http://activitystrea.ms/schema/1.0/submit',\n /**\n * Emitted when an asynchronous grader produces a grade for work that was\n * previously only SUBMITTED. Distinct from `answered`, which asserts the\n * grade existed at submission time — here the learner acted earlier and the\n * grade arrived later, often from a different actor (an AI or a teacher).\n */\n SCORED: 'http://adlnet.gov/expapi/verbs/scored',\n} as const;\n\n/** Union of the valid `XAPIVerb` keys. */\nexport type XAPIVerbKey = keyof typeof XAPIVerb;\n\n/**\n * Default `en-US` display label for each verb, used to populate\n * `XAPIVerbObject.display` when the builder resolves a verb key.\n */\nexport const XAPI_VERB_DISPLAY: Record<XAPIVerbKey, Record<string, string>> = {\n ANSWERED: { 'en-US': 'answered' },\n COMPLETED: { 'en-US': 'completed' },\n EXPERIENCED: { 'en-US': 'experienced' },\n INTERACTED: { 'en-US': 'interacted' },\n ATTEMPTED: { 'en-US': 'attempted' },\n PASSED: { 'en-US': 'passed' },\n FAILED: { 'en-US': 'failed' },\n WATCHED: { 'en-US': 'watched' },\n SUBMITTED: { 'en-US': 'submitted' },\n SCORED: { 'en-US': 'scored' },\n};\n","import { getActivityTypeDescriptor } from '../registry/index.js';\nimport type { ScoringResult } from '../types/activity.js';\nimport type {\n XAPIActor,\n XAPIContext,\n XAPIObject,\n XAPIResult,\n XAPIStatement,\n} from '../types/xapi.js';\nimport { validateXAPIStatement } from './validators.js';\nimport { XAPI_VERB_DISPLAY, XAPIVerb, type XAPIVerbKey } from './verbs.js';\n\n/** Caller-supplied activity object descriptor (mapped to an xAPI Activity). */\nexport interface XAPIObjectParams {\n /** Activity IRI. */\n id: string;\n name?: Record<string, string>;\n description?: Record<string, string>;\n /** Activity type IRI. */\n type?: string;\n /** xAPI cmi.interaction type (e.g. `choice`, `fill-in`, `long-fill-in`). */\n interactionType?: string;\n /** Correct-responses patterns, per the xAPI CRP format. */\n correctResponsesPattern?: string[];\n /** For choice-family interactions: the available components. */\n choices?: Array<{ id: string; description?: Record<string, string> }>;\n}\n\n/** Parameters for the generic {@link xAPIBuilder.buildStatement}. */\nexport interface XAPIStatementParams {\n actor: XAPIActor;\n verb: XAPIVerbKey;\n object: XAPIObjectParams;\n result?: XAPIResult;\n context?: XAPIContext;\n}\n\n/** Parameters for {@link xAPIBuilder.buildAnsweredStatement} (verb = ANSWERED). */\nexport interface AnsweredStatementParams {\n actor: XAPIActor;\n object: XAPIObjectParams;\n scoringResult: ScoringResult;\n timeSpentMs: number;\n /** Serialized learner response string. */\n response?: string;\n context?: XAPIContext;\n resultExtensions?: Record<string, unknown>;\n}\n\n/**\n * Parameters for {@link xAPIBuilder.buildSubmittedStatement} (verb =\n * SUBMITTED — deferred-grading submissions). Carries NO score, success, or\n * completion: the grade does not exist yet (Req 22.7).\n */\nexport interface SubmittedStatementParams {\n actor: XAPIActor;\n object: XAPIObjectParams;\n timeSpentMs: number;\n /** Serialized learner response string (e.g. the submitted text). */\n response?: string;\n context?: XAPIContext;\n resultExtensions?: Record<string, unknown>;\n}\n\n/** Parameters for {@link xAPIBuilder.buildCompletedStatement} (verb = COMPLETED). */\nexport interface CompletedStatementParams {\n actor: XAPIActor;\n object: XAPIObjectParams;\n scoringResult?: ScoringResult;\n timeSpentMs: number;\n context?: XAPIContext;\n resultExtensions?: Record<string, unknown>;\n}\n\n/**\n * Converts a millisecond duration to an ISO 8601 seconds string with\n * centisecond precision (`PT1.23S`), per the xAPI 1.0.3 recommendation of\n * 0.01-second accuracy. Sub-second durations are preserved (previously any\n * duration under 500 ms collapsed to `PT0S`). Negative inputs (clock skew)\n * clamp to zero — a negative ISO duration is invalid.\n */\nfunction msToIsoDuration(ms: number): string {\n return `PT${(Math.round(Math.max(0, ms) / 10) / 100).toFixed(2)}S`;\n}\n\n/**\n * UUID v4 that works outside secure contexts. `crypto.randomUUID` is\n * SecureContext-gated (undefined on plain-http LAN/staging origins), and a\n * statement is built at SUBMIT time — throwing here would lose the learner's\n * answer. `crypto.getRandomValues` is NOT gated, so the fallback still emits\n * a spec-valid v4 UUID (the statement validator enforces the v4 format).\n */\nfunction uuidv4(): string {\n const c = (\n globalThis as {\n crypto?: { randomUUID?: () => string; getRandomValues?: (a: Uint8Array) => Uint8Array };\n }\n ).crypto;\n if (c?.randomUUID) {\n return c.randomUUID();\n }\n const bytes = new Uint8Array(16);\n if (c?.getRandomValues) {\n c.getRandomValues(bytes);\n } else {\n for (let i = 0; i < 16; i += 1) {\n bytes[i] = Math.floor(Math.random() * 256);\n }\n }\n bytes[6] = ((bytes[6] as number) & 0x0f) | 0x40;\n bytes[8] = ((bytes[8] as number) & 0x3f) | 0x80;\n const hex = [...bytes].map((b) => b.toString(16).padStart(2, '0')).join('');\n return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;\n}\n\n/** Builds the xAPI Activity object, including `definition` only when populated. */\nfunction buildObject(params: XAPIObjectParams): XAPIObject {\n const definition: NonNullable<XAPIObject['definition']> = {\n ...(params.name !== undefined ? { name: params.name } : {}),\n ...(params.description !== undefined ? { description: params.description } : {}),\n ...(params.type !== undefined ? { type: params.type } : {}),\n ...(params.interactionType !== undefined ? { interactionType: params.interactionType } : {}),\n ...(params.correctResponsesPattern !== undefined\n ? { correctResponsesPattern: params.correctResponsesPattern }\n : {}),\n ...(params.choices !== undefined ? { choices: params.choices } : {}),\n };\n return {\n objectType: 'Activity',\n id: params.id,\n ...(Object.keys(definition).length > 0 ? { definition } : {}),\n };\n}\n\n/**\n * Maps a {@link ScoringResult} + timing to an {@link XAPIResult} per the\n * normative mapping in the design document.\n */\nfunction buildResult(\n timeSpentMs: number,\n scoringResult: ScoringResult | undefined,\n resultExtensions: Record<string, unknown> | undefined,\n response: string | undefined,\n): XAPIResult {\n return {\n completion: true,\n duration: msToIsoDuration(timeSpentMs),\n ...(scoringResult !== undefined\n ? {\n score: {\n scaled: scoringResult.score,\n raw: scoringResult.score,\n min: 0,\n max: scoringResult.maxScore,\n },\n success: scoringResult.passed,\n }\n : {}),\n ...(response !== undefined ? { response } : {}),\n ...(resultExtensions !== undefined ? { extensions: resultExtensions } : {}),\n };\n}\n\n/**\n * Constructs xAPI 1.0.3 Statements. Every call stamps a fresh UUID v4 `id`,\n * an ISO 8601 `timestamp`, and `version: '1.0.3'`. Not pure by design —\n * statements are unique events.\n */\nexport const xAPIBuilder = {\n buildStatement(params: XAPIStatementParams): XAPIStatement {\n const statement: XAPIStatement = {\n id: uuidv4(),\n actor: params.actor,\n verb: {\n id: XAPIVerb[params.verb],\n display: XAPI_VERB_DISPLAY[params.verb],\n },\n object: buildObject(params.object),\n ...(params.result !== undefined ? { result: params.result } : {}),\n ...(params.context !== undefined ? { context: params.context } : {}),\n timestamp: new Date().toISOString(),\n version: '1.0.3',\n };\n // Design xAPIBuilder step 4: validate every statement (throws in dev,\n // warns in prod). All build*Statement helpers route through here.\n validateXAPIStatement(statement);\n return statement;\n },\n\n buildAnsweredStatement(params: AnsweredStatementParams): XAPIStatement {\n return this.buildStatement({\n actor: params.actor,\n verb: 'ANSWERED',\n object: params.object,\n result: buildResult(\n params.timeSpentMs,\n params.scoringResult,\n params.resultExtensions,\n params.response,\n ),\n ...(params.context !== undefined ? { context: params.context } : {}),\n });\n },\n\n /**\n * Builds a statement for a deferred-grading submission (e.g. a written\n * response). Verb is SUBMITTED and the result deliberately omits `score`,\n * `success`, and `completion` — none of them exist until the asynchronous\n * grader runs; only `duration`, `response`, and extensions are recorded.\n */\n buildSubmittedStatement(params: SubmittedStatementParams): XAPIStatement {\n return this.buildStatement({\n actor: params.actor,\n verb: 'SUBMITTED',\n object: params.object,\n result: {\n duration: msToIsoDuration(params.timeSpentMs),\n ...(params.response !== undefined ? { response: params.response } : {}),\n ...(params.resultExtensions !== undefined ? { extensions: params.resultExtensions } : {}),\n },\n ...(params.context !== undefined ? { context: params.context } : {}),\n });\n },\n\n buildCompletedStatement(params: CompletedStatementParams): XAPIStatement {\n return this.buildStatement({\n actor: params.actor,\n verb: 'COMPLETED',\n object: params.object,\n result: buildResult(\n params.timeSpentMs,\n params.scoringResult,\n params.resultExtensions,\n undefined,\n ),\n ...(params.context !== undefined ? { context: params.context } : {}),\n });\n },\n};\n\n/**\n * Builds the xAPI `object.definition` interop fields for an activity from its\n * REGISTERED descriptor, rather than having every renderer hand-roll them.\n *\n * `ActivityTypeDescriptor.interop` declares the activity-type IRI, the\n * cmi.interaction type and how to derive `correctResponsesPattern`; before\n * this, those fields had no reader anywhere and each component rebuilt the\n * same strings inline — the same \"declared but never used\" defect the SDK\n * fixed elsewhere. Reading them here also means a consumer-registered type\n * gets correct interop for free.\n *\n * Returns an empty object for an unregistered type or one that declares no\n * interop, so it is always safe to spread.\n */\nexport function xapiDefinitionFor(data: { type: string }): Partial<XAPIObjectParams> {\n const descriptor = getActivityTypeDescriptor(data.type);\n const interop = descriptor?.interop;\n if (interop === undefined) {\n return {};\n }\n const pattern = interop.correctResponsesPattern?.(data);\n return {\n ...(interop.xapiActivityTypeIri !== undefined ? { type: interop.xapiActivityTypeIri } : {}),\n ...(interop.xapiInteractionType !== undefined\n ? { interactionType: interop.xapiInteractionType }\n : {}),\n ...(pattern !== undefined && pattern.length > 0 ? { correctResponsesPattern: pattern } : {}),\n };\n}\n"],"mappings":"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","names":[]}
@@ -14,7 +14,7 @@
14
14
 
15
15
 
16
16
 
17
- var _chunkVCXIPLEWcjs = require('./chunk-VCXIPLEW.cjs');
17
+ var _chunkQFWEF5STcjs = require('./chunk-QFWEF5ST.cjs');
18
18
 
19
19
  // src/is-redacted.ts
20
20
  function isRedacted(data) {
@@ -299,12 +299,12 @@ function composeAssessmentScore(sections, policy) {
299
299
  function diffDictationChars(reference, attempt) {
300
300
  const referencePoints = Array.from(reference);
301
301
  const attemptPoints = Array.from(attempt);
302
- return _chunkVCXIPLEWcjs.alignSequences.call(void 0, referencePoints, attemptPoints).map(
302
+ return _chunkQFWEF5STcjs.alignSequences.call(void 0, referencePoints, attemptPoints).map(
303
303
  ({ step, referenceIndex, attemptIndex }) => {
304
- if (step === _chunkVCXIPLEWcjs.STEP.missing) {
304
+ if (step === _chunkQFWEF5STcjs.STEP.missing) {
305
305
  return { op: "missing", reference: referencePoints[referenceIndex], attempt: "" };
306
306
  }
307
- if (step === _chunkVCXIPLEWcjs.STEP.extra) {
307
+ if (step === _chunkQFWEF5STcjs.STEP.extra) {
308
308
  return { op: "extra", reference: "", attempt: attemptPoints[attemptIndex] };
309
309
  }
310
310
  const referencePoint = referencePoints[referenceIndex];
@@ -326,14 +326,14 @@ function heardTokens(words) {
326
326
  const tokens = [];
327
327
  let length = 0;
328
328
  for (const [wordIndex, word] of words.entries()) {
329
- const normalized = _chunkVCXIPLEWcjs.normalizeDictationText.call(void 0, word.text);
329
+ const normalized = _chunkQFWEF5STcjs.normalizeDictationText.call(void 0, word.text);
330
330
  if (normalized === "") {
331
331
  continue;
332
332
  }
333
333
  const inserted = word.error === "insertion";
334
334
  for (const text of normalized.split(" ")) {
335
- length += _chunkVCXIPLEWcjs.codePointLength.call(void 0, text) + (length === 0 ? 0 : 1);
336
- if (!inserted && length <= _chunkVCXIPLEWcjs.SPEECH_ASSESSMENT_MAX_TEXT_LENGTH) {
335
+ length += _chunkQFWEF5STcjs.codePointLength.call(void 0, text) + (length === 0 ? 0 : 1);
336
+ if (!inserted && length <= _chunkQFWEF5STcjs.SPEECH_ASSESSMENT_MAX_TEXT_LENGTH) {
337
337
  tokens.push({ text, wordIndex });
338
338
  }
339
339
  }
@@ -384,7 +384,7 @@ var SpeechAssessmentSchema = _v4.z.strictObject({
384
384
  assessmentVersion: _v4.z.literal("1.0"),
385
385
  status: _v4.z.enum(["assessed", "no_speech"]),
386
386
  task: _v4.z.enum(["scripted", "unscripted"]),
387
- locale: _v4.z.string().regex(_chunkVCXIPLEWcjs.CANONICAL_LOCALE_RE),
387
+ locale: _v4.z.string().regex(_chunkQFWEF5STcjs.CANONICAL_LOCALE_RE),
388
388
  referenceText: _v4.z.string().max(8e3).optional(),
389
389
  recordingKey: _v4.z.string().min(1).max(1024).optional(),
390
390
  assessor: AssessorSchema,
@@ -399,7 +399,7 @@ var SpeechAssessmentSchema = _v4.z.strictObject({
399
399
  recognizedText: _v4.z.string().max(8e3).optional(),
400
400
  miscue: _v4.z.enum(["assessor", "none"]),
401
401
  phonemeAlphabet: _v4.z.enum(["ipa", "sapi"]).optional(),
402
- words: _v4.z.array(WordSchema).max(_chunkVCXIPLEWcjs.SPEECH_ASSESSMENT_MAX_WORDS),
402
+ words: _v4.z.array(WordSchema).max(_chunkQFWEF5STcjs.SPEECH_ASSESSMENT_MAX_WORDS),
403
403
  prosody: _v4.z.strictObject({ monotoneConfidence: Confidence.optional() }).optional(),
404
404
  signal: _v4.z.strictObject({ snrDb: _v4.z.number().optional() }).optional()
405
405
  });
@@ -421,21 +421,21 @@ function rawTextLength(words) {
421
421
  }
422
422
  function totalTextErrors(words) {
423
423
  const raw = rawTextLength(words);
424
- if (raw > _chunkVCXIPLEWcjs.SPEECH_ASSESSMENT_MAX_TEXT_LENGTH) {
424
+ if (raw > _chunkQFWEF5STcjs.SPEECH_ASSESSMENT_MAX_TEXT_LENGTH) {
425
425
  return [
426
426
  {
427
427
  path: ["words"],
428
- message: `The words of an assessment carry ${raw} characters between them, and at most ${_chunkVCXIPLEWcjs.SPEECH_ASSESSMENT_MAX_TEXT_LENGTH} can be aligned against the text of an item.`,
428
+ message: `The words of an assessment carry ${raw} characters between them, and at most ${_chunkQFWEF5STcjs.SPEECH_ASSESSMENT_MAX_TEXT_LENGTH} can be aligned against the text of an item.`,
429
429
  code: "too_big"
430
430
  }
431
431
  ];
432
432
  }
433
433
  const normalized = heardTokens(words).length;
434
- if (normalized > _chunkVCXIPLEWcjs.SPEECH_ASSESSMENT_MAX_TEXT_LENGTH) {
434
+ if (normalized > _chunkQFWEF5STcjs.SPEECH_ASSESSMENT_MAX_TEXT_LENGTH) {
435
435
  return [
436
436
  {
437
437
  path: ["words"],
438
- message: `The words of an assessment spell ${normalized} characters between them once they are normalised, and at most ${_chunkVCXIPLEWcjs.SPEECH_ASSESSMENT_MAX_TEXT_LENGTH} can be aligned against the text of an item. One character can stand for several.`,
438
+ message: `The words of an assessment spell ${normalized} characters between them once they are normalised, and at most ${_chunkQFWEF5STcjs.SPEECH_ASSESSMENT_MAX_TEXT_LENGTH} can be aligned against the text of an item. One character can stand for several.`,
439
439
  code: "too_big"
440
440
  }
441
441
  ];
@@ -505,16 +505,16 @@ function alignReadAloud(data, assessment) {
505
505
  return alignValidated(data, checked.data);
506
506
  }
507
507
  function alignValidated(data, assessment) {
508
- const reference = _chunkVCXIPLEWcjs.dictationReferenceWords.call(void 0, {
508
+ const reference = _chunkQFWEF5STcjs.dictationReferenceWords.call(void 0, {
509
509
  transcript: data.referenceText
510
510
  })[0];
511
511
  const heard = heardTokens(assessment.words).tokens;
512
512
  const entries = [];
513
- for (const { step, referenceIndex, attemptIndex } of _chunkVCXIPLEWcjs.alignSequences.call(void 0,
513
+ for (const { step, referenceIndex, attemptIndex } of _chunkQFWEF5STcjs.alignSequences.call(void 0,
514
514
  reference.map((word) => word.word),
515
515
  heard.map((token) => token.text)
516
516
  )) {
517
- if (step === _chunkVCXIPLEWcjs.STEP.extra) {
517
+ if (step === _chunkQFWEF5STcjs.STEP.extra) {
518
518
  const token2 = heard[attemptIndex];
519
519
  entries.push({
520
520
  reference: "",
@@ -526,7 +526,7 @@ function alignValidated(data, assessment) {
526
526
  continue;
527
527
  }
528
528
  const referenceWord = reference[referenceIndex];
529
- if (step === _chunkVCXIPLEWcjs.STEP.missing) {
529
+ if (step === _chunkQFWEF5STcjs.STEP.missing) {
530
530
  entries.push({
531
531
  itemId: referenceWord.itemId,
532
532
  reference: referenceWord.word,
@@ -579,7 +579,7 @@ function placeInsertions(assessment, entries) {
579
579
  reference: "",
580
580
  // Every insertion is accounted for, including one whose text normalises
581
581
  // to nothing: a reader of `wordIndex` must find each word it looks for.
582
- heard: _chunkVCXIPLEWcjs.normalizeDictationText.call(void 0, word.text),
582
+ heard: _chunkQFWEF5STcjs.normalizeDictationText.call(void 0, word.text),
583
583
  state: "inserted",
584
584
  ...accuracyOf(word),
585
585
  wordIndex
@@ -616,9 +616,9 @@ function readResponseRecording(value) {
616
616
  function spokenWordCount(assessment) {
617
617
  const recognized = assessment.recognizedText;
618
618
  if (recognized !== void 0 && recognized.trim() !== "") {
619
- return _chunkVCXIPLEWcjs.countWords.call(void 0, recognized);
619
+ return _chunkQFWEF5STcjs.countWords.call(void 0, recognized);
620
620
  }
621
- return _chunkVCXIPLEWcjs.countWords.call(void 0,
621
+ return _chunkQFWEF5STcjs.countWords.call(void 0,
622
622
  assessment.words.filter((word) => word.error !== "omission" && word.error !== "insertion").map((word) => word.text).join(" ")
623
623
  );
624
624
  }
@@ -642,9 +642,9 @@ function gradeReadAloud(data, response, assessment, options) {
642
642
  );
643
643
  }
644
644
  const rounding = roundingPolicyOf(_optionalChain([options, 'optionalAccess', _6 => _6.rounding]));
645
- const parsed = _chunkVCXIPLEWcjs.ReadAloudDataSchema.safeParse(data);
645
+ const parsed = _chunkQFWEF5STcjs.ReadAloudDataSchema.safeParse(data);
646
646
  if (!parsed.success) {
647
- throw new (0, _chunkVCXIPLEWcjs.ActivitySchemaError)(
647
+ throw new (0, _chunkQFWEF5STcjs.ActivitySchemaError)(
648
648
  "read-aloud",
649
649
  parsed.error.issues.map((issue) => ({
650
650
  path: issue.path.map(String),
@@ -654,7 +654,7 @@ function gradeReadAloud(data, response, assessment, options) {
654
654
  );
655
655
  }
656
656
  if (isRedacted(data) || isRedacted(parsed.data)) {
657
- throw new (0, _chunkVCXIPLEWcjs.RedactedScoringError)("read-aloud");
657
+ throw new (0, _chunkQFWEF5STcjs.RedactedScoringError)("read-aloud");
658
658
  }
659
659
  const item = parsed.data;
660
660
  const checkedResponse = readResponseRecording(response);
@@ -807,7 +807,7 @@ function gradeReadAloud(data, response, assessment, options) {
807
807
  };
808
808
  }
809
809
  function referenceWordsOf(data) {
810
- return _chunkVCXIPLEWcjs.dictationReferenceWords.call(void 0, { transcript: data.referenceText })[0];
810
+ return _chunkQFWEF5STcjs.dictationReferenceWords.call(void 0, { transcript: data.referenceText })[0];
811
811
  }
812
812
 
813
813
  // src/scoring/speech/wav.ts
@@ -949,19 +949,19 @@ function outcomeFromUnscorable(result) {
949
949
  // src/scoring/index.ts
950
950
  function score(activityType, activityData, learnerResponse, options) {
951
951
  const rounding = roundingPolicyOf(_optionalChain([options, 'optionalAccess', _9 => _9.rounding]));
952
- const descriptor = _chunkVCXIPLEWcjs.getActivityTypeDescriptor.call(void 0, activityType);
952
+ const descriptor = _chunkQFWEF5STcjs.getActivityTypeDescriptor.call(void 0, activityType);
953
953
  if (descriptor === void 0) {
954
- throw new (0, _chunkVCXIPLEWcjs.UnknownActivityTypeError)(String(activityType));
954
+ throw new (0, _chunkQFWEF5STcjs.UnknownActivityTypeError)(String(activityType));
955
955
  }
956
956
  if (descriptor.scoring.kind === "deferred") {
957
- throw new (0, _chunkVCXIPLEWcjs.DeferredScoringError)(descriptor.type);
957
+ throw new (0, _chunkQFWEF5STcjs.DeferredScoringError)(descriptor.type);
958
958
  }
959
959
  if (isRedacted(activityData)) {
960
- throw new (0, _chunkVCXIPLEWcjs.RedactedScoringError)(descriptor.type);
960
+ throw new (0, _chunkQFWEF5STcjs.RedactedScoringError)(descriptor.type);
961
961
  }
962
962
  const result = descriptor.scoring.score(activityData, learnerResponse);
963
963
  if (!Number.isFinite(result.score)) {
964
- throw new (0, _chunkVCXIPLEWcjs.RedactedScoringError)(descriptor.type);
964
+ throw new (0, _chunkQFWEF5STcjs.RedactedScoringError)(descriptor.type);
965
965
  }
966
966
  const passed = computePassThreshold(activityData, result.score, rounding);
967
967
  return { ...result, passed, feedback: _nullishCoalesce(result.feedback, () => ( selectFeedback(activityData, passed))) };
@@ -969,7 +969,7 @@ function score(activityType, activityData, learnerResponse, options) {
969
969
  function evaluate(data, response, options) {
970
970
  const rounding = roundingPolicyOf(_optionalChain([options, 'optionalAccess', _10 => _10.rounding]));
971
971
  const type = data.type;
972
- const descriptor = typeof type === "string" ? _chunkVCXIPLEWcjs.getActivityTypeDescriptor.call(void 0, type) : void 0;
972
+ const descriptor = typeof type === "string" ? _chunkQFWEF5STcjs.getActivityTypeDescriptor.call(void 0, type) : void 0;
973
973
  if (descriptor === void 0) {
974
974
  return {
975
975
  status: "unscorable",
@@ -1031,4 +1031,4 @@ function evaluate(data, response, options) {
1031
1031
 
1032
1032
 
1033
1033
  exports.roundGrade = roundGrade; exports.gte = gte; exports.classifyBand = classifyBand; exports.DEFAULT_PASS_THRESHOLD = DEFAULT_PASS_THRESHOLD; exports.computePassThreshold = computePassThreshold; exports.gradeFromRubric = gradeFromRubric; exports.outcomeFromGrade = outcomeFromGrade; exports.hasGrade = hasGrade; exports.composeAssessmentScore = composeAssessmentScore; exports.diffDictationChars = diffDictationChars; exports.validateSpeechAssessment = validateSpeechAssessment; exports.alignReadAloud = alignReadAloud; exports.gradeReadAloud = gradeReadAloud; exports.inspectWav = inspectWav; exports.outcomeFromUnscorable = outcomeFromUnscorable; exports.score = score; exports.evaluate = evaluate;
1034
- //# sourceMappingURL=chunk-J5OIRIOW.cjs.map
1034
+ //# sourceMappingURL=chunk-5KWF34LJ.cjs.map