@intellectif/lk-core 0.3.1 → 0.5.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 (58) hide show
  1. package/dist/{activity-zdcAMtFB.d.cts → activity-wkzRemHx.d.cts} +113 -1
  2. package/dist/{activity-zdcAMtFB.d.ts → activity-wkzRemHx.d.ts} +113 -1
  3. package/dist/{chunk-QOAORTA4.cjs → chunk-4JR3UXX6.cjs} +51 -2
  4. package/dist/chunk-4JR3UXX6.cjs.map +1 -0
  5. package/dist/chunk-DUQVGLQ3.js +333 -0
  6. package/dist/chunk-DUQVGLQ3.js.map +1 -0
  7. package/dist/chunk-FCM5VJBS.cjs +187 -0
  8. package/dist/chunk-FCM5VJBS.cjs.map +1 -0
  9. package/dist/chunk-LSHDNA2T.js +187 -0
  10. package/dist/chunk-LSHDNA2T.js.map +1 -0
  11. package/dist/{chunk-5GJJHGY5.js → chunk-NUCEUU4P.js} +50 -1
  12. package/dist/chunk-NUCEUU4P.js.map +1 -0
  13. package/dist/{chunk-RUGIOQZY.cjs → chunk-R7PDJBRX.cjs} +29 -6
  14. package/dist/chunk-R7PDJBRX.cjs.map +1 -0
  15. package/dist/chunk-SM5HUGYU.cjs +333 -0
  16. package/dist/chunk-SM5HUGYU.cjs.map +1 -0
  17. package/dist/{chunk-HS7BYCGE.js → chunk-WV5WQ3SZ.js} +29 -6
  18. package/dist/chunk-WV5WQ3SZ.js.map +1 -0
  19. package/dist/index-BKyZrd94.d.cts +732 -0
  20. package/dist/index-CLmXzBhB.d.ts +732 -0
  21. package/dist/index.cjs +206 -15
  22. package/dist/index.cjs.map +1 -1
  23. package/dist/index.d.cts +182 -6
  24. package/dist/index.d.ts +182 -6
  25. package/dist/index.js +203 -12
  26. package/dist/index.js.map +1 -1
  27. package/dist/schemas.cjs +17 -4
  28. package/dist/schemas.cjs.map +1 -1
  29. package/dist/schemas.d.cts +3 -460
  30. package/dist/schemas.d.ts +3 -460
  31. package/dist/schemas.js +16 -3
  32. package/dist/scoring.cjs +11 -4
  33. package/dist/scoring.cjs.map +1 -1
  34. package/dist/scoring.d.cts +199 -4
  35. package/dist/scoring.d.ts +199 -4
  36. package/dist/scoring.js +10 -3
  37. package/dist/xapi.cjs +5 -3
  38. package/dist/xapi.cjs.map +1 -1
  39. package/dist/xapi.d.cts +26 -2
  40. package/dist/xapi.d.ts +26 -2
  41. package/dist/xapi.js +6 -4
  42. package/package.json +38 -15
  43. package/dist/chunk-3YAVDV5F.js +0 -47
  44. package/dist/chunk-3YAVDV5F.js.map +0 -1
  45. package/dist/chunk-55O4M45K.js +0 -57
  46. package/dist/chunk-55O4M45K.js.map +0 -1
  47. package/dist/chunk-5GJJHGY5.js.map +0 -1
  48. package/dist/chunk-HS7BYCGE.js.map +0 -1
  49. package/dist/chunk-PIMX4B4D.cjs +0 -47
  50. package/dist/chunk-PIMX4B4D.cjs.map +0 -1
  51. package/dist/chunk-QOAORTA4.cjs.map +0 -1
  52. package/dist/chunk-QSVBYTNM.js +0 -94
  53. package/dist/chunk-QSVBYTNM.js.map +0 -1
  54. package/dist/chunk-RUGIOQZY.cjs.map +0 -1
  55. package/dist/chunk-THSZMZND.cjs +0 -94
  56. package/dist/chunk-THSZMZND.cjs.map +0 -1
  57. package/dist/chunk-YNTYWHZI.cjs +0 -57
  58. package/dist/chunk-YNTYWHZI.cjs.map +0 -1
@@ -0,0 +1,333 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
2
+
3
+
4
+
5
+
6
+ var _chunk4JR3UXX6cjs = require('./chunk-4JR3UXX6.cjs');
7
+
8
+ // src/scoring/rounding.ts
9
+ var EPSILON = 1e-9;
10
+ var TIE_TOLERANCE = 1e-6;
11
+ function scaled(value, dp) {
12
+ return value * 10 ** dp;
13
+ }
14
+ function noNegZero(value) {
15
+ return Object.is(value, -0) ? 0 : value;
16
+ }
17
+ function roundGrade(value, policy) {
18
+ if (!Number.isFinite(value)) {
19
+ return value;
20
+ }
21
+ const factor = 10 ** policy.dp;
22
+ const raw = scaled(value, policy.dp);
23
+ switch (policy.mode) {
24
+ case "floor":
25
+ return noNegZero(Math.floor(raw + EPSILON) / factor);
26
+ case "ceil":
27
+ return noNegZero(Math.ceil(raw - EPSILON) / factor);
28
+ case "half-even": {
29
+ const floored = Math.floor(raw);
30
+ if (Math.abs(raw - floored - 0.5) > TIE_TOLERANCE) {
31
+ return noNegZero(halfAway(raw) / factor);
32
+ }
33
+ const even = floored % 2 === 0 ? floored : floored + 1;
34
+ return noNegZero(even / factor);
35
+ }
36
+ default:
37
+ return noNegZero(halfAway(raw) / factor);
38
+ }
39
+ }
40
+ function halfAway(raw) {
41
+ const nudged = raw >= 0 ? raw + EPSILON : raw - EPSILON;
42
+ return raw >= 0 ? Math.round(nudged) : -Math.round(-nudged);
43
+ }
44
+ function gte(value, threshold, policy) {
45
+ return roundGrade(value, policy) >= roundGrade(threshold, policy) - EPSILON;
46
+ }
47
+ function classifyBand(value, bands) {
48
+ let best = null;
49
+ for (const band of bands) {
50
+ if (value >= band.min - EPSILON && (best === null || band.min > best.min)) {
51
+ best = band;
52
+ }
53
+ }
54
+ return best;
55
+ }
56
+
57
+ // src/grading.ts
58
+ function gradeFromRubric(criteria, activityData, options = {}) {
59
+ const corrupt = criteria.find(
60
+ (criterion) => criterion.notApplicable !== true && criterion.score !== void 0 && !Number.isFinite(criterion.score)
61
+ );
62
+ if (corrupt !== void 0) {
63
+ return {
64
+ unscorable: true,
65
+ reason: `Criterion "${corrupt.name}" has a non-finite score (${String(corrupt.score)}). A grade cannot be computed from it.`
66
+ };
67
+ }
68
+ const scoreable = criteria.filter(
69
+ (criterion) => criterion.notApplicable !== true && typeof criterion.score === "number"
70
+ );
71
+ const EPSILON2 = 1e-9;
72
+ const outOfRange = scoreable.find(
73
+ (criterion) => criterion.score < -EPSILON2 || criterion.score > 1 + EPSILON2
74
+ );
75
+ if (outOfRange !== void 0) {
76
+ return {
77
+ unscorable: true,
78
+ reason: `Criterion "${outOfRange.name}" has score ${outOfRange.score}, outside the scaled [0,1] range. Normalise grader output before building a GradeRecord.`
79
+ };
80
+ }
81
+ if (scoreable.length === 0) {
82
+ return {
83
+ unscorable: true,
84
+ reason: "No criterion carried a numeric score, so no weighted total can be computed."
85
+ };
86
+ }
87
+ const totalWeight = scoreable.reduce((sum, criterion) => sum + (_nullishCoalesce(criterion.weight, () => ( 1))), 0);
88
+ if (!(totalWeight > 0)) {
89
+ return {
90
+ unscorable: true,
91
+ reason: "Criterion weights do not sum to a positive number, so the weighted total is undefined."
92
+ };
93
+ }
94
+ const weighted = scoreable.reduce(
95
+ (sum, criterion) => sum + criterion.score * (_nullishCoalesce(criterion.weight, () => ( 1))),
96
+ 0
97
+ );
98
+ const rawScore = weighted / totalWeight;
99
+ if (!Number.isFinite(rawScore)) {
100
+ return {
101
+ unscorable: true,
102
+ reason: "The weighted total is not a finite number, so no grade can be produced."
103
+ };
104
+ }
105
+ const score2 = Math.min(1, Math.max(0, rawScore));
106
+ const passed = options.passThreshold !== void 0 ? score2 >= options.passThreshold : activityData !== void 0 ? computePassThreshold(activityData, score2) : score2 >= 0.7;
107
+ return {
108
+ score: score2,
109
+ maxScore: 1,
110
+ passed,
111
+ feedback: _nullishCoalesce(options.feedback, () => ( null)),
112
+ criteria: [...criteria]
113
+ };
114
+ }
115
+ function outcomeFromGrade(grade) {
116
+ return {
117
+ status: "graded",
118
+ grade,
119
+ score: grade.score,
120
+ maxScore: grade.maxScore,
121
+ passed: grade.passed,
122
+ feedback: grade.feedback
123
+ };
124
+ }
125
+ function hasGrade(outcome) {
126
+ return outcome.status === "scored" || outcome.status === "graded";
127
+ }
128
+
129
+ // src/scoring/compose.ts
130
+ function earned(item) {
131
+ const { outcome } = item;
132
+ if (hasGrade(outcome)) {
133
+ const max = outcome.maxScore > 0 ? outcome.maxScore : 1;
134
+ return { state: "graded", points: outcome.score / max * item.points };
135
+ }
136
+ if (outcome.status === "deferred") {
137
+ return { state: "pending" };
138
+ }
139
+ return { state: "unscorable" };
140
+ }
141
+ function composeAssessmentScore(sections, policy) {
142
+ const pendingAll = [];
143
+ const unscorableAll = [];
144
+ const partials = sections.map((section) => {
145
+ let earnedPoints = 0;
146
+ let gradedMaxPoints = 0;
147
+ let maxPoints = 0;
148
+ const pendingSlotIds = [];
149
+ const unscorableSlotIds = [];
150
+ for (const item of section.items) {
151
+ maxPoints += item.points;
152
+ const contribution = earned(item);
153
+ if (contribution.state === "pending") {
154
+ pendingSlotIds.push(item.slotId);
155
+ pendingAll.push(item.slotId);
156
+ continue;
157
+ }
158
+ if (contribution.state === "unscorable") {
159
+ unscorableSlotIds.push(item.slotId);
160
+ unscorableAll.push(item.slotId);
161
+ continue;
162
+ }
163
+ earnedPoints += contribution.points;
164
+ gradedMaxPoints += item.points;
165
+ }
166
+ const raw = gradedMaxPoints > 0 ? earnedPoints / gradedMaxPoints : 0;
167
+ const appliedThreshold = _nullishCoalesce(_nullishCoalesce(section.passThresholdOverride, () => ( policy.sectionThreshold)), () => ( null));
168
+ return {
169
+ section,
170
+ earnedPoints,
171
+ gradedMaxPoints,
172
+ maxPoints,
173
+ score: roundGrade(raw, policy.rounding),
174
+ appliedThreshold,
175
+ pendingSlotIds,
176
+ unscorableSlotIds
177
+ };
178
+ });
179
+ const contributingWeight = partials.reduce(
180
+ (sum, partial) => sum + (partial.gradedMaxPoints > 0 ? partial.section.weight : 0),
181
+ 0
182
+ );
183
+ const scored = partials.map((partial) => ({
184
+ id: partial.section.id,
185
+ ...partial.section.title !== void 0 ? { title: partial.section.title } : {},
186
+ weight: partial.section.weight,
187
+ normalizedWeight: partial.gradedMaxPoints > 0 && contributingWeight > 0 ? partial.section.weight / contributingWeight : 0,
188
+ earnedPoints: partial.earnedPoints,
189
+ gradedMaxPoints: partial.gradedMaxPoints,
190
+ maxPoints: partial.maxPoints,
191
+ score: partial.score,
192
+ // A section with nothing graded yet cannot be said to have failed.
193
+ passed: partial.appliedThreshold === null || partial.gradedMaxPoints === 0 ? true : gte(partial.score, partial.appliedThreshold, policy.rounding),
194
+ appliedThreshold: partial.appliedThreshold,
195
+ pendingSlotIds: partial.pendingSlotIds,
196
+ unscorableSlotIds: partial.unscorableSlotIds
197
+ }));
198
+ const weightedRaw = scored.reduce(
199
+ (sum, section) => sum + section.score * section.normalizedWeight,
200
+ 0
201
+ );
202
+ const score2 = roundGrade(weightedRaw, policy.rounding);
203
+ const status = pendingAll.length > 0 ? "provisional" : "final";
204
+ if (status === "provisional") {
205
+ return {
206
+ sections: scored,
207
+ score: score2,
208
+ passed: null,
209
+ passFailureReason: null,
210
+ status,
211
+ pendingSlotIds: pendingAll,
212
+ unscorableSlotIds: unscorableAll
213
+ };
214
+ }
215
+ if (scored.every((section) => section.gradedMaxPoints === 0)) {
216
+ return {
217
+ sections: scored,
218
+ score: score2,
219
+ passed: null,
220
+ passFailureReason: null,
221
+ status,
222
+ pendingSlotIds: pendingAll,
223
+ unscorableSlotIds: unscorableAll
224
+ };
225
+ }
226
+ const overallOk = gte(score2, policy.passThreshold, policy.rounding);
227
+ const sectionsOk = scored.every((section) => section.passed);
228
+ const passed = overallOk && sectionsOk;
229
+ const passFailureReason = passed ? null : !overallOk && !sectionsOk ? "both" : overallOk ? "section_below_threshold" : "overall_below_threshold";
230
+ return {
231
+ sections: scored,
232
+ score: score2,
233
+ passed,
234
+ passFailureReason,
235
+ status,
236
+ pendingSlotIds: pendingAll,
237
+ unscorableSlotIds: unscorableAll
238
+ };
239
+ }
240
+
241
+ // src/scoring/index.ts
242
+ var DEFAULT_PASS_THRESHOLD = 0.7;
243
+ function computePassThreshold(activityData, score2, rounding) {
244
+ const threshold = _nullishCoalesce(activityData.passThreshold, () => ( DEFAULT_PASS_THRESHOLD));
245
+ return rounding === void 0 ? score2 >= threshold : gte(score2, threshold, rounding);
246
+ }
247
+ function isRedacted(data) {
248
+ return typeof data === "object" && data !== null && data.redacted === true;
249
+ }
250
+ function selectFeedback(activityData, passed) {
251
+ const feedback = activityData.feedback;
252
+ if (feedback === void 0) {
253
+ return null;
254
+ }
255
+ return _nullishCoalesce((passed ? feedback.correct : feedback.incorrect), () => ( null));
256
+ }
257
+ function score(activityType, activityData, learnerResponse) {
258
+ const descriptor = _chunk4JR3UXX6cjs.getActivityTypeDescriptor.call(void 0, activityType);
259
+ if (descriptor === void 0) {
260
+ throw new (0, _chunk4JR3UXX6cjs.UnknownActivityTypeError)(String(activityType));
261
+ }
262
+ if (descriptor.scoring.kind === "deferred") {
263
+ throw new (0, _chunk4JR3UXX6cjs.DeferredScoringError)(descriptor.type);
264
+ }
265
+ if (isRedacted(activityData)) {
266
+ throw new (0, _chunk4JR3UXX6cjs.RedactedScoringError)(descriptor.type);
267
+ }
268
+ const result = descriptor.scoring.score(activityData, learnerResponse);
269
+ if (!Number.isFinite(result.score)) {
270
+ throw new (0, _chunk4JR3UXX6cjs.RedactedScoringError)(descriptor.type);
271
+ }
272
+ const passed = computePassThreshold(activityData, result.score);
273
+ return { ...result, passed, feedback: _nullishCoalesce(result.feedback, () => ( selectFeedback(activityData, passed))) };
274
+ }
275
+ function evaluate(data, response) {
276
+ const type = data.type;
277
+ const descriptor = typeof type === "string" ? _chunk4JR3UXX6cjs.getActivityTypeDescriptor.call(void 0, type) : void 0;
278
+ if (descriptor === void 0) {
279
+ return {
280
+ status: "unscorable",
281
+ reason: `Activity type "${String(type)}" is not registered`,
282
+ maxScore: 1
283
+ };
284
+ }
285
+ if (descriptor.scoring.kind === "deferred") {
286
+ const partial = _optionalChain([descriptor, 'access', _ => _.scoring, 'access', _2 => _2.partial, 'optionalCall', _3 => _3(data, response)]);
287
+ return {
288
+ status: "deferred",
289
+ reason: descriptor.scoring.reason,
290
+ maxScore: 1,
291
+ ...partial !== void 0 ? { partial } : {}
292
+ };
293
+ }
294
+ if (isRedacted(data)) {
295
+ return {
296
+ status: "unscorable",
297
+ reason: "Activity data is redacted (no answer key), so it cannot be scored on the client. Score against the full data server-side.",
298
+ maxScore: 1
299
+ };
300
+ }
301
+ const result = descriptor.scoring.score(data, response);
302
+ if (!Number.isFinite(result.score)) {
303
+ return {
304
+ status: "unscorable",
305
+ reason: `Scoring "${descriptor.type}" produced a non-finite score; the activity data is incomplete.`,
306
+ maxScore: result.maxScore
307
+ };
308
+ }
309
+ const passed = computePassThreshold(data, result.score);
310
+ return {
311
+ status: "scored",
312
+ score: result.score,
313
+ maxScore: result.maxScore,
314
+ passed,
315
+ feedback: _nullishCoalesce(result.feedback, () => ( selectFeedback(data, passed))),
316
+ details: result.details
317
+ };
318
+ }
319
+
320
+
321
+
322
+
323
+
324
+
325
+
326
+
327
+
328
+
329
+
330
+
331
+
332
+ exports.roundGrade = roundGrade; exports.gte = gte; exports.classifyBand = classifyBand; exports.composeAssessmentScore = composeAssessmentScore; exports.DEFAULT_PASS_THRESHOLD = DEFAULT_PASS_THRESHOLD; exports.computePassThreshold = computePassThreshold; exports.score = score; exports.evaluate = evaluate; exports.gradeFromRubric = gradeFromRubric; exports.outcomeFromGrade = outcomeFromGrade; exports.hasGrade = hasGrade;
333
+ //# sourceMappingURL=chunk-SM5HUGYU.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/home/runner/work/learning-kit/learning-kit/packages/lk-core/dist/chunk-SM5HUGYU.cjs","../src/scoring/rounding.ts","../src/grading.ts","../src/scoring/compose.ts","../src/scoring/index.ts"],"names":["EPSILON","score"],"mappings":"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","file":"/home/runner/work/learning-kit/learning-kit/packages/lk-core/dist/chunk-SM5HUGYU.cjs","sourcesContent":[null,"/**\n * Rounding for assessment scores.\n *\n * Rounding is **two different operations** that must not share one policy, and\n * conflating them silently inverts one of them:\n *\n * - **Grade rounding** decides the number a learner is shown and recorded\n * against. It rounds to a fixed number of decimal places, conventionally\n * half-up, so a learner shown \"70%\" is not recorded as a fail at 69.6.\n * - **Band / level classification** decides which level someone is placed in.\n * It deliberately **floors**: placing a learner above their real level is\n * the more harmful error, so a band boundary must not be reached by\n * rounding up.\n *\n * There is therefore no default `RoundingPolicy` anywhere in this SDK, and\n * `dp` has no default either — an integrator's deliberate choice must never be\n * supplied by us.\n */\n\n/** How a value is rounded to `dp` decimal places. */\nexport type RoundingMode = 'half-up' | 'half-even' | 'floor' | 'ceil';\n\n/** A rounding policy. Both fields are required — the SDK never guesses either. */\nexport interface RoundingPolicy {\n mode: RoundingMode;\n /** Decimal places. Load-bearing: `dp: 2` is what stops 69.6 becoming a fail at 70. */\n dp: number;\n}\n\n/**\n * Float-noise guard. Binary floating point makes `1.005 * 100` come out as\n * `100.49999999999999` and `0.29 * 100` come out as `28.999999999999996`;\n * without an allowance both would move a learner's grade a step. It is applied\n * in the direction each mode needs — see {@link roundGrade}.\n */\nconst EPSILON = 1e-9;\n\n/**\n * Tolerance for deciding \"is this exactly halfway?\" in half-even mode.\n * Deliberately much larger than {@link EPSILON}: if the tie test shared the\n * nudge's tolerance, the nudge would decide its own outcome. Safe for grades,\n * which live on a [0,1] scale where representation error is far below this.\n */\nconst TIE_TOLERANCE = 1e-6;\n\nfunction scaled(value: number, dp: number): number {\n return value * 10 ** dp;\n}\n\n/**\n * Normalises `-0` to `0` without touching anything else. `-0` JSON-serialises\n * as `0` but fails `Object.is(-0, 0)`, which surprises a strict-equality\n * gradebook comparison. Deliberately NOT `|| 0`: that also swallowed `NaN`\n * (reachable with an absurd `dp`, where `10 ** dp` overflows to Infinity) and\n * turned a visible failure into a plausible-looking grade of zero.\n */\nfunction noNegZero(value: number): number {\n return Object.is(value, -0) ? 0 : value;\n}\n\n/**\n * Rounds `value` to `policy.dp` places under `policy.mode`.\n *\n * Idempotent for every mode: rounding an already-rounded value returns it\n * unchanged. That matters because a composed score is rounded once and then\n * compared through {@link gte}, which rounds again.\n */\nexport function roundGrade(value: number, policy: RoundingPolicy): number {\n if (!Number.isFinite(value)) {\n return value;\n }\n const factor = 10 ** policy.dp;\n const raw = scaled(value, policy.dp);\n\n // The nudge must oppose the mode, never point uniformly away from zero.\n // `Math.ceil` on a value that is ALREADY exact at `dp` jumps a whole\n // quantum if you add epsilon first: 0.7 became 0.71, a true 0 became 0.01,\n // and the operation stopped being idempotent (0.7 -> 0.71 -> 0.72). So\n // `ceil` nudges down and `floor` nudges up — just far enough to absorb\n // representation error (0.29 * 100 is 28.999999999999996, which must still\n // floor to 29) without ever moving a value that was already exact.\n // Half modes keep the sign-aware nudge, which is what makes a .5 tie land\n // away from zero.\n switch (policy.mode) {\n case 'floor':\n return noNegZero(Math.floor(raw + EPSILON) / factor);\n case 'ceil':\n return noNegZero(Math.ceil(raw - EPSILON) / factor);\n case 'half-even': {\n // Detect the tie on the RAW value, and with a tolerance decoupled from\n // (and far larger than) the nudge epsilon. Testing the NUDGED value\n // against EPSILON let the nudge itself decide whether a tie was seen:\n // 0.375 was detected but 0.125 was not, so half-even silently degraded\n // to half-up for some exact ties — and only ever in the direction that\n // rounds a learner UP, the opposite of what this mode is chosen for.\n const floored = Math.floor(raw);\n if (Math.abs(raw - floored - 0.5) > TIE_TOLERANCE) {\n return noNegZero(halfAway(raw) / factor);\n }\n // Exactly halfway: pick the even neighbour (banker's rounding).\n const even = floored % 2 === 0 ? floored : floored + 1;\n return noNegZero(even / factor);\n }\n default:\n return noNegZero(halfAway(raw) / factor);\n }\n}\n\n/**\n * Half-up on a scaled value, with the sign-aware float-noise nudge.\n * `Math.round` breaks .5 ties toward +Infinity, so negatives are mirrored to\n * keep the tie breaking away from zero in both directions.\n */\nfunction halfAway(raw: number): number {\n const nudged = raw >= 0 ? raw + EPSILON : raw - EPSILON;\n return raw >= 0 ? Math.round(nudged) : -Math.round(-nudged);\n}\n\n/**\n * Threshold comparison that rounds **both sides** before comparing.\n *\n * Comparing a raw float against a rounded threshold is how a learner ends up\n * shown one number and recorded against another. Rounding both sides — and\n * allowing an epsilon — makes \"what the learner sees\" and \"what the gradebook\n * decides\" the same comparison.\n */\nexport function gte(value: number, threshold: number, policy: RoundingPolicy): boolean {\n return roundGrade(value, policy) >= roundGrade(threshold, policy) - EPSILON;\n}\n\n/** A named band with an inclusive lower bound, e.g. `{ name: 'B1', min: 0.6 }`. */\nexport interface Band {\n name: string;\n /** Inclusive lower bound on the same scale as the value being classified. */\n min: number;\n}\n\n/**\n * Classifies a value into a band by **flooring**: the highest band whose `min`\n * the value actually reaches. Deliberately does NOT round up to a boundary —\n * over-placement is the more harmful error, so a learner just below a boundary\n * stays below it.\n *\n * Returns `null` when the value reaches no band's minimum.\n */\nexport function classifyBand(value: number, bands: readonly Band[]): Band | null {\n let best: Band | null = null;\n for (const band of bands) {\n if (value >= band.min - EPSILON && (best === null || band.min > best.min)) {\n best = band;\n }\n }\n return best;\n}\n","import { computePassThreshold } from './scoring/index.js';\nimport type { ActivityData, ItemOutcome } from './types/activity.js';\nimport type { CriterionScore, GradeRecord } from './types/grading.js';\n\nexport type {\n CriterionScore,\n GradeRecord,\n Grader,\n GraderKind,\n GraderUsage,\n GradingState,\n InlineCorrection,\n} from './types/grading.js';\n\n/** Options for {@link gradeFromRubric}. */\nexport interface GradeFromRubricOptions {\n /**\n * Scaled score [0,1] required to pass. Defaults to the activity's\n * `passThreshold`, or {@link DEFAULT_PASS_THRESHOLD} when absent.\n */\n passThreshold?: number;\n /** Narrative feedback for the learner, carried onto the record verbatim. */\n feedback?: string | null;\n}\n\n/**\n * Computes the weighted total from per-criterion scores — the arithmetic that\n * must NOT be delegated to a language model.\n *\n * A grader is asked for judgement (how good is this criterion?), not for\n * mental arithmetic. Asking a model to also produce the weighted total makes\n * the final grade unverifiable and unreproducible: two runs can return\n * different totals for identical criterion scores. This function makes the\n * total a pure function of the judgements, so a grade can be recomputed and\n * audited years later.\n *\n * Weights are normalised by their sum, so they need not add to 1. Criteria\n * marked `notApplicable`, and those carrying no numeric `score` (a purely\n * banded judgement), are excluded from both numerator and denominator. When\n * nothing scoreable remains the result is `unscorable`, never a zero.\n */\nexport function gradeFromRubric(\n criteria: readonly CriterionScore[],\n activityData?: ActivityData,\n options: GradeFromRubricOptions = {},\n): GradeRecord | { unscorable: true; reason: string } {\n // A corrupt score is an ERROR, not an exclusion. Exclusions (`notApplicable`,\n // and band-only criteria with no numeric score) are deliberate authoring\n // decisions; a NaN or Infinity is a broken grader. Silently dropping it\n // would regrade the learner on fewer criteria — with different effective\n // weights — and nobody would know.\n const corrupt = criteria.find(\n (criterion) =>\n criterion.notApplicable !== true &&\n criterion.score !== undefined &&\n !Number.isFinite(criterion.score),\n );\n if (corrupt !== undefined) {\n return {\n unscorable: true,\n reason: `Criterion \"${corrupt.name}\" has a non-finite score (${String(corrupt.score)}). A grade cannot be computed from it.`,\n };\n }\n\n const scoreable = criteria.filter(\n (criterion) => criterion.notApplicable !== true && typeof criterion.score === 'number',\n );\n\n // `score` is documented as scaled [0,1]. A grader that returns raw points\n // (4 out of 5) would otherwise yield `{ score: 4, maxScore: 1 }` and pass\n // every threshold. Reject rather than clamp: silently rescaling someone's\n // grader is worse than telling them it is out of contract. A hair outside\n // the range is float noise and is clamped.\n const EPSILON = 1e-9;\n const outOfRange = scoreable.find(\n (criterion) =>\n (criterion.score as number) < -EPSILON || (criterion.score as number) > 1 + EPSILON,\n );\n if (outOfRange !== undefined) {\n return {\n unscorable: true,\n reason: `Criterion \"${outOfRange.name}\" has score ${outOfRange.score}, outside the scaled [0,1] range. Normalise grader output before building a GradeRecord.`,\n };\n }\n\n if (scoreable.length === 0) {\n return {\n unscorable: true,\n reason: 'No criterion carried a numeric score, so no weighted total can be computed.',\n };\n }\n\n const totalWeight = scoreable.reduce((sum, criterion) => sum + (criterion.weight ?? 1), 0);\n if (!(totalWeight > 0)) {\n return {\n unscorable: true,\n reason:\n 'Criterion weights do not sum to a positive number, so the weighted total is undefined.',\n };\n }\n\n const weighted = scoreable.reduce(\n (sum, criterion) => sum + (criterion.score as number) * (criterion.weight ?? 1),\n 0,\n );\n const rawScore = weighted / totalWeight;\n if (!Number.isFinite(rawScore)) {\n return {\n unscorable: true,\n reason: 'The weighted total is not a finite number, so no grade can be produced.',\n };\n }\n // Clamp the float-noise band only; anything genuinely out of range was\n // already rejected above.\n const score = Math.min(1, Math.max(0, rawScore));\n\n const passed =\n options.passThreshold !== undefined\n ? score >= options.passThreshold\n : activityData !== undefined\n ? computePassThreshold(activityData, score)\n : score >= 0.7;\n\n return {\n score,\n maxScore: 1,\n passed,\n feedback: options.feedback ?? null,\n criteria: [...criteria],\n };\n}\n\n/**\n * Lifts a {@link GradeRecord} into the `graded` arm of {@link ItemOutcome}, so\n * a grade that arrived asynchronously renders through the same path as a\n * synchronously scored item. The score/passed/feedback fields are mirrored\n * onto the outcome for uniform reads; `grade` carries the full record.\n */\nexport function outcomeFromGrade(grade: GradeRecord): ItemOutcome {\n return {\n status: 'graded',\n grade,\n score: grade.score,\n maxScore: grade.maxScore,\n passed: grade.passed,\n feedback: grade.feedback,\n };\n}\n\n/**\n * True when an outcome carries a real grade — either the SDK scored it\n * synchronously or a grader returned one. Use this instead of testing\n * `status === 'scored'`, which silently misses asynchronously graded work.\n */\nexport function hasGrade(\n outcome: ItemOutcome,\n): outcome is Extract<ItemOutcome, { status: 'scored' | 'graded' }> {\n return outcome.status === 'scored' || outcome.status === 'graded';\n}\n","import { hasGrade } from '../grading.js';\nimport type { ItemOutcome } from '../types/activity.js';\nimport { gte, type RoundingPolicy, roundGrade } from './rounding.js';\n\n/**\n * Sectioned assessment scoring — weights, per-section thresholds, and an\n * explicit reason when an attempt fails.\n *\n * This exists because the formula is invariably implemented twice: once on the\n * server that records the grade, and again on the client that shows a learner\n * their grade breakdown. Two implementations of one formula is exactly the\n * duplication an SDK should remove, and they drift — usually in the scale\n * (0–1 vs 0–100) or in whether a section override is honoured.\n */\n\n/** One item's contribution to a section. */\nexport interface ScoredItem {\n /**\n * Identity of the SLOT, not the activity. The same activity can legitimately\n * appear in two sections; keying on the activity id collapses them into one\n * and silently scores the second occurrence as zero.\n */\n slotId: string;\n /** The activity that filled this slot, for reporting. */\n activityId?: string;\n /** Maximum points this slot is worth. */\n points: number;\n /** What the learner achieved, or why there is no grade yet. */\n outcome: ItemOutcome;\n}\n\n/** A weighted section of an assessment. */\nexport interface AssessmentSectionInput {\n id: string;\n title?: string;\n /** Relative weight. Weights are normalised by their sum, so they need not total 1. */\n weight: number;\n /** Overrides the assessment-wide section threshold for this section only. */\n passThresholdOverride?: number;\n items: ScoredItem[];\n}\n\n/** Policy for {@link composeAssessmentScore}. */\nexport interface CompositionPolicy {\n /** Scaled [0,1] overall score required to pass. */\n passThreshold: number;\n /** Scaled [0,1] score each section must reach, when sections gate the pass. */\n sectionThreshold?: number;\n /**\n * How grades are rounded. Required, with no default: see `RoundingPolicy` —\n * grade rounding and band classification are different operations and the\n * SDK must not choose either for you.\n */\n rounding: RoundingPolicy;\n}\n\n/** Per-section result. */\nexport interface SectionScore {\n id: string;\n title?: string;\n /** The authored weight, verbatim. */\n weight: number;\n /**\n * The weight ACTUALLY used in the total, so a client can rebuild the grade\n * from `sections[]` and agree with the record:\n *\n * ```ts\n * roundGrade(\n * sections.reduce((sum, s) => sum + s.score * s.normalizedWeight, 0),\n * policy.rounding,\n * ) === result.score // exact, by construction\n * ```\n *\n * Apply the same final rounding: the raw weighted sum of already-rounded\n * section scores is not itself a rounded value (0.85 and 1.00 at equal\n * weights sum to 0.925 against a recorded 0.93), so comparing it unrounded\n * is off by up to half a quantum.\n *\n * Sections with nothing graded carry `0` here, because they contribute\n * nothing; the remaining weights are renormalised among themselves.\n */\n normalizedWeight: number;\n earnedPoints: number;\n /** Points that are currently gradable — excludes items still awaiting a grade. */\n gradedMaxPoints: number;\n /** Every point in the section, whether graded yet or not. */\n maxPoints: number;\n /** Scaled [0,1] over the GRADED points, rounded once. */\n score: number;\n passed: boolean;\n /** Threshold this section was judged against, after any override. */\n appliedThreshold: number | null;\n /** Slots still awaiting a grade. */\n pendingSlotIds: string[];\n /** Slots that can never be graded, excluded from the denominator. */\n unscorableSlotIds: string[];\n}\n\n/** Why an attempt failed, or `null` when it passed. */\nexport type PassFailureReason =\n | 'overall_below_threshold'\n | 'section_below_threshold'\n | 'both'\n | null;\n\n/** Result of composing an assessment. */\nexport interface AssessmentScore {\n sections: SectionScore[];\n /** Weighted total, scaled [0,1], rounded once. */\n score: number;\n /**\n * Whether the attempt passed — `null` while `status` is `provisional`,\n * because an attempt with work still ungraded has not passed OR failed yet.\n * Returning `false` there would let a UI keyed on `passed` show a fail for\n * an essay nobody has marked.\n */\n passed: boolean | null;\n /** `null` while provisional, for the same reason as {@link passed}. */\n passFailureReason: PassFailureReason;\n /**\n * `provisional` while any item is still awaiting a grade — the total is\n * computed over what HAS been graded, so it can still move. Do not record a\n * provisional score as final. Items that can NEVER be graded\n * (`unscorableSlotIds`) do not hold the result provisional.\n */\n status: 'final' | 'provisional';\n pendingSlotIds: string[];\n /** Slots that can never be graded. Excluded from the denominator. */\n unscorableSlotIds: string[];\n}\n\n/**\n * What one item contributes. THREE states, not two — collapsing the last two\n * into a single \"no points\" answer is what made an unscorable item block an\n * attempt from ever becoming final:\n *\n * - `graded` — real points, counted.\n * - `pending` — a grade is coming (deferred). Excluded from the denominator,\n * and the assessment stays `provisional` until it arrives.\n * - `unscorable` — a grade is never coming (unregistered type, redacted data,\n * incomplete key). Also excluded from the denominator, but it must NOT keep\n * the result provisional forever: `evaluate()` returns this precisely so a\n * mixed-version content bank does not crash an exam, and an attempt that\n * contains one still has to be recordable.\n */\ntype ItemContribution =\n | { state: 'graded'; points: number }\n | { state: 'pending' }\n | { state: 'unscorable' };\n\nfunction earned(item: ScoredItem): ItemContribution {\n const { outcome } = item;\n if (hasGrade(outcome)) {\n const max = outcome.maxScore > 0 ? outcome.maxScore : 1;\n return { state: 'graded', points: (outcome.score / max) * item.points };\n }\n if (outcome.status === 'deferred') {\n return { state: 'pending' };\n }\n return { state: 'unscorable' };\n}\n\n/**\n * Composes per-item outcomes into a sectioned assessment score.\n *\n * Weights are normalised by their sum. Each section's score is computed over\n * the points that are actually gradable and rounded ONCE, before any threshold\n * comparison, so the number a learner is shown is the number that decides the\n * outcome. Items still awaiting a grade are excluded from the denominator\n * rather than counted as zero, and the result is reported as `provisional`\n * until every item has a grade.\n */\nexport function composeAssessmentScore(\n sections: readonly AssessmentSectionInput[],\n policy: CompositionPolicy,\n): AssessmentScore {\n const pendingAll: string[] = [];\n const unscorableAll: string[] = [];\n\n // Pass 1 — score each section over the points that are actually gradable.\n const partials = sections.map((section) => {\n let earnedPoints = 0;\n let gradedMaxPoints = 0;\n let maxPoints = 0;\n const pendingSlotIds: string[] = [];\n const unscorableSlotIds: string[] = [];\n\n for (const item of section.items) {\n maxPoints += item.points;\n const contribution = earned(item);\n if (contribution.state === 'pending') {\n pendingSlotIds.push(item.slotId);\n pendingAll.push(item.slotId);\n continue;\n }\n if (contribution.state === 'unscorable') {\n unscorableSlotIds.push(item.slotId);\n unscorableAll.push(item.slotId);\n continue;\n }\n earnedPoints += contribution.points;\n gradedMaxPoints += item.points;\n }\n\n const raw = gradedMaxPoints > 0 ? earnedPoints / gradedMaxPoints : 0;\n const appliedThreshold = section.passThresholdOverride ?? policy.sectionThreshold ?? null;\n\n return {\n section,\n earnedPoints,\n gradedMaxPoints,\n maxPoints,\n score: roundGrade(raw, policy.rounding),\n appliedThreshold,\n pendingSlotIds,\n unscorableSlotIds,\n };\n });\n\n // Pass 2 — the LIVE weights. Only sections with something graded contribute,\n // and their weights are renormalised among themselves. A section whose items\n // are all still ungraded must not contribute a zero: that is the \"live\n // denominator\" bug, where a midterm reads 50% only because the essay has not\n // been marked and a learner sees a fail for work nobody has looked at.\n // These are the weights reported on each section, so that\n // `sum(score * normalizedWeight) === result.score` holds and a client\n // rendering the breakdown cannot disagree with the recorded grade.\n const contributingWeight = partials.reduce(\n (sum, partial) => sum + (partial.gradedMaxPoints > 0 ? partial.section.weight : 0),\n 0,\n );\n\n const scored: SectionScore[] = partials.map((partial) => ({\n id: partial.section.id,\n ...(partial.section.title !== undefined ? { title: partial.section.title } : {}),\n weight: partial.section.weight,\n normalizedWeight:\n partial.gradedMaxPoints > 0 && contributingWeight > 0\n ? partial.section.weight / contributingWeight\n : 0,\n earnedPoints: partial.earnedPoints,\n gradedMaxPoints: partial.gradedMaxPoints,\n maxPoints: partial.maxPoints,\n score: partial.score,\n // A section with nothing graded yet cannot be said to have failed.\n passed:\n partial.appliedThreshold === null || partial.gradedMaxPoints === 0\n ? true\n : gte(partial.score, partial.appliedThreshold, policy.rounding),\n appliedThreshold: partial.appliedThreshold,\n pendingSlotIds: partial.pendingSlotIds,\n unscorableSlotIds: partial.unscorableSlotIds,\n }));\n\n const weightedRaw = scored.reduce(\n (sum, section) => sum + section.score * section.normalizedWeight,\n 0,\n );\n const score = roundGrade(weightedRaw, policy.rounding);\n\n // Only work that is still COMING keeps the result provisional. Work that can\n // never be graded is excluded from the denominator but must not block the\n // attempt from being recorded.\n const status: 'final' | 'provisional' = pendingAll.length > 0 ? 'provisional' : 'final';\n\n if (status === 'provisional') {\n // Not passed and not failed: undetermined. Saying `false` here would let a\n // UI keyed on `passed` show a fail for unmarked work.\n return {\n sections: scored,\n score,\n passed: null,\n passFailureReason: null,\n status,\n pendingSlotIds: pendingAll,\n unscorableSlotIds: unscorableAll,\n };\n }\n\n // Nothing was gradable at all — every item unscorable, or an assessment with\n // no items. There is no evidence either way, so there is no verdict: a hard\n // `false` here records a fail at 0% for a learner whose work was never\n // gradable, which is the same fabricated-failure this module exists to\n // avoid, just reached through the other door.\n if (scored.every((section) => section.gradedMaxPoints === 0)) {\n return {\n sections: scored,\n score,\n passed: null,\n passFailureReason: null,\n status,\n pendingSlotIds: pendingAll,\n unscorableSlotIds: unscorableAll,\n };\n }\n\n const overallOk = gte(score, policy.passThreshold, policy.rounding);\n const sectionsOk = scored.every((section) => section.passed);\n const passed = overallOk && sectionsOk;\n\n const passFailureReason: PassFailureReason = passed\n ? null\n : !overallOk && !sectionsOk\n ? 'both'\n : overallOk\n ? 'section_below_threshold'\n : 'overall_below_threshold';\n\n return {\n sections: scored,\n score,\n passed,\n passFailureReason,\n status,\n pendingSlotIds: pendingAll,\n unscorableSlotIds: unscorableAll,\n };\n}\n","import { DeferredScoringError, RedactedScoringError, UnknownActivityTypeError } from '../errors.js';\nimport { getActivityTypeDescriptor } from '../registry/index.js';\nimport type {\n ActivityData,\n ActivityFeedback,\n ActivityType,\n ItemOutcome,\n LearnerResponse,\n ScoringResult,\n} from '../types/activity.js';\nimport { gte, type RoundingPolicy } from './rounding.js';\n\nexport type {\n AssessmentScore,\n AssessmentSectionInput,\n CompositionPolicy,\n PassFailureReason,\n ScoredItem,\n SectionScore,\n} from './compose.js';\nexport { composeAssessmentScore } from './compose.js';\nexport type { Band, RoundingMode, RoundingPolicy } from './rounding.js';\nexport { classifyBand, gte, roundGrade } from './rounding.js';\nexport type { TextMatchPolicy, TextMatchResult } from './text-match.js';\nexport { levenshteinDistance, matchText } from './text-match.js';\n\n/** Default minimum scaled score required to pass when `passThreshold` is absent. */\nexport const DEFAULT_PASS_THRESHOLD = 0.7;\n\n/**\n * Returns `true` iff `score` meets or exceeds the activity's `passThreshold`,\n * defaulting to {@link DEFAULT_PASS_THRESHOLD} (0.7) when the field is absent.\n *\n * Pass a {@link RoundingPolicy} to compare the way an assessment total is\n * compared — both sides rounded, via {@link gte} — so an item shown as \"70%\"\n * cannot be recorded as a fail at 69.6. It is **opt-in** rather than the\n * default because switching it on changes item-level pass/fail for scores in\n * the rounding band, and this SDK does not alter historical grades without an\n * explicit decision. Absent, the comparison is the exact raw `>=` it has\n * always been.\n */\nexport function computePassThreshold(\n activityData: ActivityData,\n score: number,\n rounding?: RoundingPolicy,\n): boolean {\n const threshold = activityData.passThreshold ?? DEFAULT_PASS_THRESHOLD;\n return rounding === undefined ? score >= threshold : gte(score, threshold, rounding);\n}\n\n/**\n * True when `data` is a `redact()` projection rather than full activity data.\n * Scoring a redacted item is always a bug: the answer key is gone by design,\n * so any \"score\" computed from it is meaningless (it used to come out `NaN`).\n */\nfunction isRedacted(data: unknown): boolean {\n return (\n typeof data === 'object' && data !== null && (data as { redacted?: unknown }).redacted === true\n );\n}\n\n/**\n * Selects the authored overall feedback for a result: `feedback.correct` when\n * the learner passed, `feedback.incorrect` otherwise; `null` when no matching\n * message was authored. Mirrors the selection the lk-react components applied\n * (keyed on `passed`, per the `ActivityFeedback` contract).\n */\nfunction selectFeedback(activityData: ActivityData, passed: boolean): string | null {\n const feedback = (activityData as { feedback?: ActivityFeedback }).feedback;\n if (feedback === undefined) {\n return null;\n }\n return (passed ? feedback.correct : feedback.incorrect) ?? null;\n}\n\n/**\n * Scores a learner response against activity data and returns a full\n * {@link ScoringResult}.\n *\n * Pure and deterministic with no side effects. It does **not** re-validate\n * `activityData` — schema validation is the component boundary's\n * responsibility; this is a low-level scoring primitive that trusts its typed\n * inputs. Dispatch is registry-backed: consumer-registered types with `sync`\n * scoring work here too. An unregistered type throws\n * {@link UnknownActivityTypeError}; a type whose grading is deferred (e.g.\n * `written-response`) throws {@link DeferredScoringError} — use\n * {@link evaluate}, which can express \"not graded yet\".\n */\nexport function score(\n activityType: ActivityType,\n activityData: ActivityData,\n learnerResponse: LearnerResponse,\n): ScoringResult {\n const descriptor = getActivityTypeDescriptor(activityType);\n if (descriptor === undefined) {\n throw new UnknownActivityTypeError(String(activityType));\n }\n if (descriptor.scoring.kind === 'deferred') {\n throw new DeferredScoringError(descriptor.type);\n }\n if (isRedacted(activityData)) {\n throw new RedactedScoringError(descriptor.type);\n }\n\n const result = descriptor.scoring.score(activityData, learnerResponse);\n if (!Number.isFinite(result.score)) {\n throw new RedactedScoringError(descriptor.type);\n }\n const passed = computePassThreshold(activityData, result.score);\n return { ...result, passed, feedback: result.feedback ?? selectFeedback(activityData, passed) };\n}\n\n/**\n * Evaluates a learner response against an activity and returns an\n * {@link ItemOutcome} — the resilient, forward-compatible alternative to\n * {@link score}:\n *\n * - Synchronously graded types return `{ status: 'scored', ... }` with the\n * same numbers `score()` produces.\n * - Asynchronously graded types (e.g. `written-response`) return\n * `{ status: 'deferred', reason, partial }` instead of a fake zero — \"not\n * graded yet\" is expressible in the type system, never conflated with\n * \"wrong\".\n * - An unregistered `data.type` returns `{ status: 'unscorable' }` rather\n * than throwing, so a mixed-version content bank cannot crash an exam run.\n *\n * The activity type is read from `data.type` — there is no separate type\n * parameter to disagree with the payload.\n */\nexport function evaluate(data: ActivityData, response: LearnerResponse): ItemOutcome {\n const type = (data as { type?: unknown }).type;\n const descriptor = typeof type === 'string' ? getActivityTypeDescriptor(type) : undefined;\n\n if (descriptor === undefined) {\n return {\n status: 'unscorable',\n reason: `Activity type \"${String(type)}\" is not registered`,\n maxScore: 1,\n };\n }\n\n if (descriptor.scoring.kind === 'deferred') {\n const partial = descriptor.scoring.partial?.(data, response);\n return {\n status: 'deferred',\n reason: descriptor.scoring.reason,\n maxScore: 1,\n ...(partial !== undefined ? { partial } : {}),\n };\n }\n\n if (isRedacted(data)) {\n return {\n status: 'unscorable',\n reason:\n 'Activity data is redacted (no answer key), so it cannot be scored on the client. Score against the full data server-side.',\n maxScore: 1,\n };\n }\n\n const result = descriptor.scoring.score(data, response);\n if (!Number.isFinite(result.score)) {\n // Defence in depth: incomplete data (a missing answer key, an empty\n // options array) used to divide by zero and surface as a real score of\n // NaN, which JSON-serializes to null in a grade column.\n return {\n status: 'unscorable',\n reason: `Scoring \"${descriptor.type}\" produced a non-finite score; the activity data is incomplete.`,\n maxScore: result.maxScore,\n };\n }\n const passed = computePassThreshold(data, result.score);\n return {\n status: 'scored',\n score: result.score,\n maxScore: result.maxScore,\n passed,\n feedback: result.feedback ?? selectFeedback(data, passed),\n details: result.details,\n };\n}\n"]}
@@ -1,6 +1,7 @@
1
1
  import {
2
- ActivitySchemaError
3
- } from "./chunk-3YAVDV5F.js";
2
+ ActivitySchemaError,
3
+ getActivityTypeDescriptor
4
+ } from "./chunk-NUCEUU4P.js";
4
5
 
5
6
  // src/xapi/validators.ts
6
7
  import { z } from "zod/v4";
@@ -102,7 +103,14 @@ var XAPIVerb = {
102
103
  * canonical "submit" verb; the Activity Streams 1.0 IRI is the established
103
104
  * community choice (design decision recorded per Task 25.4).
104
105
  */
105
- SUBMITTED: "http://activitystrea.ms/schema/1.0/submit"
106
+ SUBMITTED: "http://activitystrea.ms/schema/1.0/submit",
107
+ /**
108
+ * Emitted when an asynchronous grader produces a grade for work that was
109
+ * previously only SUBMITTED. Distinct from `answered`, which asserts the
110
+ * grade existed at submission time — here the learner acted earlier and the
111
+ * grade arrived later, often from a different actor (an AI or a teacher).
112
+ */
113
+ SCORED: "http://adlnet.gov/expapi/verbs/scored"
106
114
  };
107
115
  var XAPI_VERB_DISPLAY = {
108
116
  ANSWERED: { "en-US": "answered" },
@@ -113,7 +121,8 @@ var XAPI_VERB_DISPLAY = {
113
121
  PASSED: { "en-US": "passed" },
114
122
  FAILED: { "en-US": "failed" },
115
123
  WATCHED: { "en-US": "watched" },
116
- SUBMITTED: { "en-US": "submitted" }
124
+ SUBMITTED: { "en-US": "submitted" },
125
+ SCORED: { "en-US": "scored" }
117
126
  };
118
127
 
119
128
  // src/xapi/builder.ts
@@ -236,11 +245,25 @@ var xAPIBuilder = {
236
245
  });
237
246
  }
238
247
  };
248
+ function xapiDefinitionFor(data) {
249
+ const descriptor = getActivityTypeDescriptor(data.type);
250
+ const interop = descriptor?.interop;
251
+ if (interop === void 0) {
252
+ return {};
253
+ }
254
+ const pattern = interop.correctResponsesPattern?.(data);
255
+ return {
256
+ ...interop.xapiActivityTypeIri !== void 0 ? { type: interop.xapiActivityTypeIri } : {},
257
+ ...interop.xapiInteractionType !== void 0 ? { interactionType: interop.xapiInteractionType } : {},
258
+ ...pattern !== void 0 && pattern.length > 0 ? { correctResponsesPattern: pattern } : {}
259
+ };
260
+ }
239
261
 
240
262
  export {
241
263
  validateXAPIStatement,
242
264
  XAPIVerb,
243
265
  XAPI_VERB_DISPLAY,
244
- xAPIBuilder
266
+ xAPIBuilder,
267
+ xapiDefinitionFor
245
268
  };
246
- //# sourceMappingURL=chunk-HS7BYCGE.js.map
269
+ //# sourceMappingURL=chunk-WV5WQ3SZ.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/** True when running under `NODE_ENV=production`, safe in any environment. */\nfunction isProduction(): boolean {\n const g = globalThis as { process?: { env?: Record<string, string | undefined> } };\n return g.process?.env?.NODE_ENV === 'production';\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":[]}