@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.
- package/CHANGELOG.md +31 -0
- package/README.md +1 -0
- package/dist/{activity-BVkg9VDo.d.cts → activity-C7cR8U9n.d.cts} +35 -2
- package/dist/{activity-BVkg9VDo.d.ts → activity-C7cR8U9n.d.ts} +35 -2
- package/dist/{chunk-R7PV3XIC.js → chunk-54XYQ3CS.js} +7 -4
- package/dist/chunk-54XYQ3CS.js.map +1 -0
- package/dist/{chunk-J5OIRIOW.cjs → chunk-5KWF34LJ.cjs} +31 -31
- package/dist/{chunk-J5OIRIOW.cjs.map → chunk-5KWF34LJ.cjs.map} +1 -1
- package/dist/{chunk-CZRUMHKW.cjs → chunk-7ETU6EXQ.cjs} +13 -10
- package/dist/chunk-7ETU6EXQ.cjs.map +1 -0
- package/dist/{chunk-6RND47QE.js → chunk-DPHX4E7X.js} +62 -3
- package/dist/chunk-DPHX4E7X.js.map +1 -0
- package/dist/{chunk-NBADQ3JU.js → chunk-EEMVWBPP.js} +195 -4
- package/dist/chunk-EEMVWBPP.js.map +1 -0
- package/dist/{chunk-RTIR6R2U.js → chunk-L6YBQXSB.js} +2 -2
- package/dist/{chunk-VCXIPLEW.cjs → chunk-QFWEF5ST.cjs} +63 -4
- package/dist/chunk-QFWEF5ST.cjs.map +1 -0
- package/dist/chunk-W7AQP6AV.cjs +418 -0
- package/dist/chunk-W7AQP6AV.cjs.map +1 -0
- package/dist/{index-v2_-Fyn3.d.ts → index-BWyMDlUS.d.cts} +367 -2
- package/dist/{index-BE1bM2Wi.d.cts → index-D10NaztZ.d.ts} +367 -2
- package/dist/index.cjs +397 -96
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +103 -7
- package/dist/index.d.ts +103 -7
- package/dist/index.js +310 -9
- package/dist/index.js.map +1 -1
- package/dist/schemas.cjs +11 -3
- package/dist/schemas.cjs.map +1 -1
- package/dist/schemas.d.cts +2 -2
- package/dist/schemas.d.ts +2 -2
- package/dist/schemas.js +10 -2
- package/dist/scoring.cjs +3 -3
- package/dist/scoring.d.cts +2 -2
- package/dist/scoring.d.ts +2 -2
- package/dist/scoring.js +2 -2
- package/dist/xapi.cjs +3 -3
- package/dist/xapi.d.cts +1 -1
- package/dist/xapi.d.ts +1 -1
- package/dist/xapi.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-2NIALQEH.cjs +0 -227
- package/dist/chunk-2NIALQEH.cjs.map +0 -1
- package/dist/chunk-6RND47QE.js.map +0 -1
- package/dist/chunk-CZRUMHKW.cjs.map +0 -1
- package/dist/chunk-NBADQ3JU.js.map +0 -1
- package/dist/chunk-R7PV3XIC.js.map +0 -1
- package/dist/chunk-VCXIPLEW.cjs.map +0 -1
- /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'
|
|
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
|
|
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'
|
|
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
|
|
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-
|
|
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
|
-
|
|
70
|
-
|
|
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-
|
|
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
|
|
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
|
|
302
|
+
return _chunkQFWEF5STcjs.alignSequences.call(void 0, referencePoints, attemptPoints).map(
|
|
303
303
|
({ step, referenceIndex, attemptIndex }) => {
|
|
304
|
-
if (step ===
|
|
304
|
+
if (step === _chunkQFWEF5STcjs.STEP.missing) {
|
|
305
305
|
return { op: "missing", reference: referencePoints[referenceIndex], attempt: "" };
|
|
306
306
|
}
|
|
307
|
-
if (step ===
|
|
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 =
|
|
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 +=
|
|
336
|
-
if (!inserted && 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(
|
|
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(
|
|
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 >
|
|
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 ${
|
|
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 >
|
|
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 ${
|
|
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 =
|
|
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
|
|
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 ===
|
|
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 ===
|
|
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:
|
|
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
|
|
619
|
+
return _chunkQFWEF5STcjs.countWords.call(void 0, recognized);
|
|
620
620
|
}
|
|
621
|
-
return
|
|
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 =
|
|
645
|
+
const parsed = _chunkQFWEF5STcjs.ReadAloudDataSchema.safeParse(data);
|
|
646
646
|
if (!parsed.success) {
|
|
647
|
-
throw new (0,
|
|
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,
|
|
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
|
|
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 =
|
|
952
|
+
const descriptor = _chunkQFWEF5STcjs.getActivityTypeDescriptor.call(void 0, activityType);
|
|
953
953
|
if (descriptor === void 0) {
|
|
954
|
-
throw new (0,
|
|
954
|
+
throw new (0, _chunkQFWEF5STcjs.UnknownActivityTypeError)(String(activityType));
|
|
955
955
|
}
|
|
956
956
|
if (descriptor.scoring.kind === "deferred") {
|
|
957
|
-
throw new (0,
|
|
957
|
+
throw new (0, _chunkQFWEF5STcjs.DeferredScoringError)(descriptor.type);
|
|
958
958
|
}
|
|
959
959
|
if (isRedacted(activityData)) {
|
|
960
|
-
throw new (0,
|
|
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,
|
|
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" ?
|
|
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-
|
|
1034
|
+
//# sourceMappingURL=chunk-5KWF34LJ.cjs.map
|