@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
@@ -1,13 +1,208 @@
1
- import { A as ActivityData, L as LearnerResponse, h as ItemOutcome, e as ActivityType, m as ScoringResult } from './activity-zdcAMtFB.cjs';
2
- export { T as TextMatchPolicy, n as TextMatchResult, E as levenshteinDistance, G as matchText } from './activity-zdcAMtFB.cjs';
1
+ import { m as ItemOutcome, A as ActivityData, L as LearnerResponse, e as ActivityType, r as ScoringResult } from './activity-wkzRemHx.cjs';
2
+ export { T as TextMatchPolicy, s as TextMatchResult, P as levenshteinDistance, Q as matchText } from './activity-wkzRemHx.cjs';
3
+
4
+ /**
5
+ * Rounding for assessment scores.
6
+ *
7
+ * Rounding is **two different operations** that must not share one policy, and
8
+ * conflating them silently inverts one of them:
9
+ *
10
+ * - **Grade rounding** decides the number a learner is shown and recorded
11
+ * against. It rounds to a fixed number of decimal places, conventionally
12
+ * half-up, so a learner shown "70%" is not recorded as a fail at 69.6.
13
+ * - **Band / level classification** decides which level someone is placed in.
14
+ * It deliberately **floors**: placing a learner above their real level is
15
+ * the more harmful error, so a band boundary must not be reached by
16
+ * rounding up.
17
+ *
18
+ * There is therefore no default `RoundingPolicy` anywhere in this SDK, and
19
+ * `dp` has no default either — an integrator's deliberate choice must never be
20
+ * supplied by us.
21
+ */
22
+ /** How a value is rounded to `dp` decimal places. */
23
+ type RoundingMode = 'half-up' | 'half-even' | 'floor' | 'ceil';
24
+ /** A rounding policy. Both fields are required — the SDK never guesses either. */
25
+ interface RoundingPolicy {
26
+ mode: RoundingMode;
27
+ /** Decimal places. Load-bearing: `dp: 2` is what stops 69.6 becoming a fail at 70. */
28
+ dp: number;
29
+ }
30
+ /**
31
+ * Rounds `value` to `policy.dp` places under `policy.mode`.
32
+ *
33
+ * Idempotent for every mode: rounding an already-rounded value returns it
34
+ * unchanged. That matters because a composed score is rounded once and then
35
+ * compared through {@link gte}, which rounds again.
36
+ */
37
+ declare function roundGrade(value: number, policy: RoundingPolicy): number;
38
+ /**
39
+ * Threshold comparison that rounds **both sides** before comparing.
40
+ *
41
+ * Comparing a raw float against a rounded threshold is how a learner ends up
42
+ * shown one number and recorded against another. Rounding both sides — and
43
+ * allowing an epsilon — makes "what the learner sees" and "what the gradebook
44
+ * decides" the same comparison.
45
+ */
46
+ declare function gte(value: number, threshold: number, policy: RoundingPolicy): boolean;
47
+ /** A named band with an inclusive lower bound, e.g. `{ name: 'B1', min: 0.6 }`. */
48
+ interface Band {
49
+ name: string;
50
+ /** Inclusive lower bound on the same scale as the value being classified. */
51
+ min: number;
52
+ }
53
+ /**
54
+ * Classifies a value into a band by **flooring**: the highest band whose `min`
55
+ * the value actually reaches. Deliberately does NOT round up to a boundary —
56
+ * over-placement is the more harmful error, so a learner just below a boundary
57
+ * stays below it.
58
+ *
59
+ * Returns `null` when the value reaches no band's minimum.
60
+ */
61
+ declare function classifyBand(value: number, bands: readonly Band[]): Band | null;
62
+
63
+ /**
64
+ * Sectioned assessment scoring — weights, per-section thresholds, and an
65
+ * explicit reason when an attempt fails.
66
+ *
67
+ * This exists because the formula is invariably implemented twice: once on the
68
+ * server that records the grade, and again on the client that shows a learner
69
+ * their grade breakdown. Two implementations of one formula is exactly the
70
+ * duplication an SDK should remove, and they drift — usually in the scale
71
+ * (0–1 vs 0–100) or in whether a section override is honoured.
72
+ */
73
+ /** One item's contribution to a section. */
74
+ interface ScoredItem {
75
+ /**
76
+ * Identity of the SLOT, not the activity. The same activity can legitimately
77
+ * appear in two sections; keying on the activity id collapses them into one
78
+ * and silently scores the second occurrence as zero.
79
+ */
80
+ slotId: string;
81
+ /** The activity that filled this slot, for reporting. */
82
+ activityId?: string;
83
+ /** Maximum points this slot is worth. */
84
+ points: number;
85
+ /** What the learner achieved, or why there is no grade yet. */
86
+ outcome: ItemOutcome;
87
+ }
88
+ /** A weighted section of an assessment. */
89
+ interface AssessmentSectionInput {
90
+ id: string;
91
+ title?: string;
92
+ /** Relative weight. Weights are normalised by their sum, so they need not total 1. */
93
+ weight: number;
94
+ /** Overrides the assessment-wide section threshold for this section only. */
95
+ passThresholdOverride?: number;
96
+ items: ScoredItem[];
97
+ }
98
+ /** Policy for {@link composeAssessmentScore}. */
99
+ interface CompositionPolicy {
100
+ /** Scaled [0,1] overall score required to pass. */
101
+ passThreshold: number;
102
+ /** Scaled [0,1] score each section must reach, when sections gate the pass. */
103
+ sectionThreshold?: number;
104
+ /**
105
+ * How grades are rounded. Required, with no default: see `RoundingPolicy` —
106
+ * grade rounding and band classification are different operations and the
107
+ * SDK must not choose either for you.
108
+ */
109
+ rounding: RoundingPolicy;
110
+ }
111
+ /** Per-section result. */
112
+ interface SectionScore {
113
+ id: string;
114
+ title?: string;
115
+ /** The authored weight, verbatim. */
116
+ weight: number;
117
+ /**
118
+ * The weight ACTUALLY used in the total, so a client can rebuild the grade
119
+ * from `sections[]` and agree with the record:
120
+ *
121
+ * ```ts
122
+ * roundGrade(
123
+ * sections.reduce((sum, s) => sum + s.score * s.normalizedWeight, 0),
124
+ * policy.rounding,
125
+ * ) === result.score // exact, by construction
126
+ * ```
127
+ *
128
+ * Apply the same final rounding: the raw weighted sum of already-rounded
129
+ * section scores is not itself a rounded value (0.85 and 1.00 at equal
130
+ * weights sum to 0.925 against a recorded 0.93), so comparing it unrounded
131
+ * is off by up to half a quantum.
132
+ *
133
+ * Sections with nothing graded carry `0` here, because they contribute
134
+ * nothing; the remaining weights are renormalised among themselves.
135
+ */
136
+ normalizedWeight: number;
137
+ earnedPoints: number;
138
+ /** Points that are currently gradable — excludes items still awaiting a grade. */
139
+ gradedMaxPoints: number;
140
+ /** Every point in the section, whether graded yet or not. */
141
+ maxPoints: number;
142
+ /** Scaled [0,1] over the GRADED points, rounded once. */
143
+ score: number;
144
+ passed: boolean;
145
+ /** Threshold this section was judged against, after any override. */
146
+ appliedThreshold: number | null;
147
+ /** Slots still awaiting a grade. */
148
+ pendingSlotIds: string[];
149
+ /** Slots that can never be graded, excluded from the denominator. */
150
+ unscorableSlotIds: string[];
151
+ }
152
+ /** Why an attempt failed, or `null` when it passed. */
153
+ type PassFailureReason = 'overall_below_threshold' | 'section_below_threshold' | 'both' | null;
154
+ /** Result of composing an assessment. */
155
+ interface AssessmentScore {
156
+ sections: SectionScore[];
157
+ /** Weighted total, scaled [0,1], rounded once. */
158
+ score: number;
159
+ /**
160
+ * Whether the attempt passed — `null` while `status` is `provisional`,
161
+ * because an attempt with work still ungraded has not passed OR failed yet.
162
+ * Returning `false` there would let a UI keyed on `passed` show a fail for
163
+ * an essay nobody has marked.
164
+ */
165
+ passed: boolean | null;
166
+ /** `null` while provisional, for the same reason as {@link passed}. */
167
+ passFailureReason: PassFailureReason;
168
+ /**
169
+ * `provisional` while any item is still awaiting a grade — the total is
170
+ * computed over what HAS been graded, so it can still move. Do not record a
171
+ * provisional score as final. Items that can NEVER be graded
172
+ * (`unscorableSlotIds`) do not hold the result provisional.
173
+ */
174
+ status: 'final' | 'provisional';
175
+ pendingSlotIds: string[];
176
+ /** Slots that can never be graded. Excluded from the denominator. */
177
+ unscorableSlotIds: string[];
178
+ }
179
+ /**
180
+ * Composes per-item outcomes into a sectioned assessment score.
181
+ *
182
+ * Weights are normalised by their sum. Each section's score is computed over
183
+ * the points that are actually gradable and rounded ONCE, before any threshold
184
+ * comparison, so the number a learner is shown is the number that decides the
185
+ * outcome. Items still awaiting a grade are excluded from the denominator
186
+ * rather than counted as zero, and the result is reported as `provisional`
187
+ * until every item has a grade.
188
+ */
189
+ declare function composeAssessmentScore(sections: readonly AssessmentSectionInput[], policy: CompositionPolicy): AssessmentScore;
3
190
 
4
191
  /** Default minimum scaled score required to pass when `passThreshold` is absent. */
5
192
  declare const DEFAULT_PASS_THRESHOLD = 0.7;
6
193
  /**
7
194
  * Returns `true` iff `score` meets or exceeds the activity's `passThreshold`,
8
195
  * defaulting to {@link DEFAULT_PASS_THRESHOLD} (0.7) when the field is absent.
196
+ *
197
+ * Pass a {@link RoundingPolicy} to compare the way an assessment total is
198
+ * compared — both sides rounded, via {@link gte} — so an item shown as "70%"
199
+ * cannot be recorded as a fail at 69.6. It is **opt-in** rather than the
200
+ * default because switching it on changes item-level pass/fail for scores in
201
+ * the rounding band, and this SDK does not alter historical grades without an
202
+ * explicit decision. Absent, the comparison is the exact raw `>=` it has
203
+ * always been.
9
204
  */
10
- declare function computePassThreshold(activityData: ActivityData, score: number): boolean;
205
+ declare function computePassThreshold(activityData: ActivityData, score: number, rounding?: RoundingPolicy): boolean;
11
206
  /**
12
207
  * Scores a learner response against activity data and returns a full
13
208
  * {@link ScoringResult}.
@@ -41,4 +236,4 @@ declare function score(activityType: ActivityType, activityData: ActivityData, l
41
236
  */
42
237
  declare function evaluate(data: ActivityData, response: LearnerResponse): ItemOutcome;
43
238
 
44
- export { DEFAULT_PASS_THRESHOLD, computePassThreshold, evaluate, score };
239
+ export { type AssessmentScore, type AssessmentSectionInput, type Band, type CompositionPolicy, DEFAULT_PASS_THRESHOLD, type PassFailureReason, type RoundingMode, type RoundingPolicy, type ScoredItem, type SectionScore, classifyBand, composeAssessmentScore, computePassThreshold, evaluate, gte, roundGrade, score };
package/dist/scoring.d.ts CHANGED
@@ -1,13 +1,208 @@
1
- import { A as ActivityData, L as LearnerResponse, h as ItemOutcome, e as ActivityType, m as ScoringResult } from './activity-zdcAMtFB.js';
2
- export { T as TextMatchPolicy, n as TextMatchResult, E as levenshteinDistance, G as matchText } from './activity-zdcAMtFB.js';
1
+ import { m as ItemOutcome, A as ActivityData, L as LearnerResponse, e as ActivityType, r as ScoringResult } from './activity-wkzRemHx.js';
2
+ export { T as TextMatchPolicy, s as TextMatchResult, P as levenshteinDistance, Q as matchText } from './activity-wkzRemHx.js';
3
+
4
+ /**
5
+ * Rounding for assessment scores.
6
+ *
7
+ * Rounding is **two different operations** that must not share one policy, and
8
+ * conflating them silently inverts one of them:
9
+ *
10
+ * - **Grade rounding** decides the number a learner is shown and recorded
11
+ * against. It rounds to a fixed number of decimal places, conventionally
12
+ * half-up, so a learner shown "70%" is not recorded as a fail at 69.6.
13
+ * - **Band / level classification** decides which level someone is placed in.
14
+ * It deliberately **floors**: placing a learner above their real level is
15
+ * the more harmful error, so a band boundary must not be reached by
16
+ * rounding up.
17
+ *
18
+ * There is therefore no default `RoundingPolicy` anywhere in this SDK, and
19
+ * `dp` has no default either — an integrator's deliberate choice must never be
20
+ * supplied by us.
21
+ */
22
+ /** How a value is rounded to `dp` decimal places. */
23
+ type RoundingMode = 'half-up' | 'half-even' | 'floor' | 'ceil';
24
+ /** A rounding policy. Both fields are required — the SDK never guesses either. */
25
+ interface RoundingPolicy {
26
+ mode: RoundingMode;
27
+ /** Decimal places. Load-bearing: `dp: 2` is what stops 69.6 becoming a fail at 70. */
28
+ dp: number;
29
+ }
30
+ /**
31
+ * Rounds `value` to `policy.dp` places under `policy.mode`.
32
+ *
33
+ * Idempotent for every mode: rounding an already-rounded value returns it
34
+ * unchanged. That matters because a composed score is rounded once and then
35
+ * compared through {@link gte}, which rounds again.
36
+ */
37
+ declare function roundGrade(value: number, policy: RoundingPolicy): number;
38
+ /**
39
+ * Threshold comparison that rounds **both sides** before comparing.
40
+ *
41
+ * Comparing a raw float against a rounded threshold is how a learner ends up
42
+ * shown one number and recorded against another. Rounding both sides — and
43
+ * allowing an epsilon — makes "what the learner sees" and "what the gradebook
44
+ * decides" the same comparison.
45
+ */
46
+ declare function gte(value: number, threshold: number, policy: RoundingPolicy): boolean;
47
+ /** A named band with an inclusive lower bound, e.g. `{ name: 'B1', min: 0.6 }`. */
48
+ interface Band {
49
+ name: string;
50
+ /** Inclusive lower bound on the same scale as the value being classified. */
51
+ min: number;
52
+ }
53
+ /**
54
+ * Classifies a value into a band by **flooring**: the highest band whose `min`
55
+ * the value actually reaches. Deliberately does NOT round up to a boundary —
56
+ * over-placement is the more harmful error, so a learner just below a boundary
57
+ * stays below it.
58
+ *
59
+ * Returns `null` when the value reaches no band's minimum.
60
+ */
61
+ declare function classifyBand(value: number, bands: readonly Band[]): Band | null;
62
+
63
+ /**
64
+ * Sectioned assessment scoring — weights, per-section thresholds, and an
65
+ * explicit reason when an attempt fails.
66
+ *
67
+ * This exists because the formula is invariably implemented twice: once on the
68
+ * server that records the grade, and again on the client that shows a learner
69
+ * their grade breakdown. Two implementations of one formula is exactly the
70
+ * duplication an SDK should remove, and they drift — usually in the scale
71
+ * (0–1 vs 0–100) or in whether a section override is honoured.
72
+ */
73
+ /** One item's contribution to a section. */
74
+ interface ScoredItem {
75
+ /**
76
+ * Identity of the SLOT, not the activity. The same activity can legitimately
77
+ * appear in two sections; keying on the activity id collapses them into one
78
+ * and silently scores the second occurrence as zero.
79
+ */
80
+ slotId: string;
81
+ /** The activity that filled this slot, for reporting. */
82
+ activityId?: string;
83
+ /** Maximum points this slot is worth. */
84
+ points: number;
85
+ /** What the learner achieved, or why there is no grade yet. */
86
+ outcome: ItemOutcome;
87
+ }
88
+ /** A weighted section of an assessment. */
89
+ interface AssessmentSectionInput {
90
+ id: string;
91
+ title?: string;
92
+ /** Relative weight. Weights are normalised by their sum, so they need not total 1. */
93
+ weight: number;
94
+ /** Overrides the assessment-wide section threshold for this section only. */
95
+ passThresholdOverride?: number;
96
+ items: ScoredItem[];
97
+ }
98
+ /** Policy for {@link composeAssessmentScore}. */
99
+ interface CompositionPolicy {
100
+ /** Scaled [0,1] overall score required to pass. */
101
+ passThreshold: number;
102
+ /** Scaled [0,1] score each section must reach, when sections gate the pass. */
103
+ sectionThreshold?: number;
104
+ /**
105
+ * How grades are rounded. Required, with no default: see `RoundingPolicy` —
106
+ * grade rounding and band classification are different operations and the
107
+ * SDK must not choose either for you.
108
+ */
109
+ rounding: RoundingPolicy;
110
+ }
111
+ /** Per-section result. */
112
+ interface SectionScore {
113
+ id: string;
114
+ title?: string;
115
+ /** The authored weight, verbatim. */
116
+ weight: number;
117
+ /**
118
+ * The weight ACTUALLY used in the total, so a client can rebuild the grade
119
+ * from `sections[]` and agree with the record:
120
+ *
121
+ * ```ts
122
+ * roundGrade(
123
+ * sections.reduce((sum, s) => sum + s.score * s.normalizedWeight, 0),
124
+ * policy.rounding,
125
+ * ) === result.score // exact, by construction
126
+ * ```
127
+ *
128
+ * Apply the same final rounding: the raw weighted sum of already-rounded
129
+ * section scores is not itself a rounded value (0.85 and 1.00 at equal
130
+ * weights sum to 0.925 against a recorded 0.93), so comparing it unrounded
131
+ * is off by up to half a quantum.
132
+ *
133
+ * Sections with nothing graded carry `0` here, because they contribute
134
+ * nothing; the remaining weights are renormalised among themselves.
135
+ */
136
+ normalizedWeight: number;
137
+ earnedPoints: number;
138
+ /** Points that are currently gradable — excludes items still awaiting a grade. */
139
+ gradedMaxPoints: number;
140
+ /** Every point in the section, whether graded yet or not. */
141
+ maxPoints: number;
142
+ /** Scaled [0,1] over the GRADED points, rounded once. */
143
+ score: number;
144
+ passed: boolean;
145
+ /** Threshold this section was judged against, after any override. */
146
+ appliedThreshold: number | null;
147
+ /** Slots still awaiting a grade. */
148
+ pendingSlotIds: string[];
149
+ /** Slots that can never be graded, excluded from the denominator. */
150
+ unscorableSlotIds: string[];
151
+ }
152
+ /** Why an attempt failed, or `null` when it passed. */
153
+ type PassFailureReason = 'overall_below_threshold' | 'section_below_threshold' | 'both' | null;
154
+ /** Result of composing an assessment. */
155
+ interface AssessmentScore {
156
+ sections: SectionScore[];
157
+ /** Weighted total, scaled [0,1], rounded once. */
158
+ score: number;
159
+ /**
160
+ * Whether the attempt passed — `null` while `status` is `provisional`,
161
+ * because an attempt with work still ungraded has not passed OR failed yet.
162
+ * Returning `false` there would let a UI keyed on `passed` show a fail for
163
+ * an essay nobody has marked.
164
+ */
165
+ passed: boolean | null;
166
+ /** `null` while provisional, for the same reason as {@link passed}. */
167
+ passFailureReason: PassFailureReason;
168
+ /**
169
+ * `provisional` while any item is still awaiting a grade — the total is
170
+ * computed over what HAS been graded, so it can still move. Do not record a
171
+ * provisional score as final. Items that can NEVER be graded
172
+ * (`unscorableSlotIds`) do not hold the result provisional.
173
+ */
174
+ status: 'final' | 'provisional';
175
+ pendingSlotIds: string[];
176
+ /** Slots that can never be graded. Excluded from the denominator. */
177
+ unscorableSlotIds: string[];
178
+ }
179
+ /**
180
+ * Composes per-item outcomes into a sectioned assessment score.
181
+ *
182
+ * Weights are normalised by their sum. Each section's score is computed over
183
+ * the points that are actually gradable and rounded ONCE, before any threshold
184
+ * comparison, so the number a learner is shown is the number that decides the
185
+ * outcome. Items still awaiting a grade are excluded from the denominator
186
+ * rather than counted as zero, and the result is reported as `provisional`
187
+ * until every item has a grade.
188
+ */
189
+ declare function composeAssessmentScore(sections: readonly AssessmentSectionInput[], policy: CompositionPolicy): AssessmentScore;
3
190
 
4
191
  /** Default minimum scaled score required to pass when `passThreshold` is absent. */
5
192
  declare const DEFAULT_PASS_THRESHOLD = 0.7;
6
193
  /**
7
194
  * Returns `true` iff `score` meets or exceeds the activity's `passThreshold`,
8
195
  * defaulting to {@link DEFAULT_PASS_THRESHOLD} (0.7) when the field is absent.
196
+ *
197
+ * Pass a {@link RoundingPolicy} to compare the way an assessment total is
198
+ * compared — both sides rounded, via {@link gte} — so an item shown as "70%"
199
+ * cannot be recorded as a fail at 69.6. It is **opt-in** rather than the
200
+ * default because switching it on changes item-level pass/fail for scores in
201
+ * the rounding band, and this SDK does not alter historical grades without an
202
+ * explicit decision. Absent, the comparison is the exact raw `>=` it has
203
+ * always been.
9
204
  */
10
- declare function computePassThreshold(activityData: ActivityData, score: number): boolean;
205
+ declare function computePassThreshold(activityData: ActivityData, score: number, rounding?: RoundingPolicy): boolean;
11
206
  /**
12
207
  * Scores a learner response against activity data and returns a full
13
208
  * {@link ScoringResult}.
@@ -41,4 +236,4 @@ declare function score(activityType: ActivityType, activityData: ActivityData, l
41
236
  */
42
237
  declare function evaluate(data: ActivityData, response: LearnerResponse): ItemOutcome;
43
238
 
44
- export { DEFAULT_PASS_THRESHOLD, computePassThreshold, evaluate, score };
239
+ export { type AssessmentScore, type AssessmentSectionInput, type Band, type CompositionPolicy, DEFAULT_PASS_THRESHOLD, type PassFailureReason, type RoundingMode, type RoundingPolicy, type ScoredItem, type SectionScore, classifyBand, composeAssessmentScore, computePassThreshold, evaluate, gte, roundGrade, score };
package/dist/scoring.js CHANGED
@@ -1,20 +1,27 @@
1
1
  import {
2
2
  DEFAULT_PASS_THRESHOLD,
3
+ classifyBand,
4
+ composeAssessmentScore,
3
5
  computePassThreshold,
4
6
  evaluate,
7
+ gte,
8
+ roundGrade,
5
9
  score
6
- } from "./chunk-QSVBYTNM.js";
10
+ } from "./chunk-DUQVGLQ3.js";
7
11
  import {
8
12
  levenshteinDistance,
9
13
  matchText
10
- } from "./chunk-5GJJHGY5.js";
11
- import "./chunk-3YAVDV5F.js";
14
+ } from "./chunk-NUCEUU4P.js";
12
15
  export {
13
16
  DEFAULT_PASS_THRESHOLD,
17
+ classifyBand,
18
+ composeAssessmentScore,
14
19
  computePassThreshold,
15
20
  evaluate,
21
+ gte,
16
22
  levenshteinDistance,
17
23
  matchText,
24
+ roundGrade,
18
25
  score
19
26
  };
20
27
  //# sourceMappingURL=scoring.js.map
package/dist/xapi.cjs CHANGED
@@ -3,12 +3,14 @@
3
3
 
4
4
 
5
5
 
6
- var _chunkRUGIOQZYcjs = require('./chunk-RUGIOQZY.cjs');
7
- require('./chunk-PIMX4B4D.cjs');
8
6
 
7
+ var _chunkR7PDJBRXcjs = require('./chunk-R7PDJBRX.cjs');
8
+ require('./chunk-4JR3UXX6.cjs');
9
9
 
10
10
 
11
11
 
12
12
 
13
- exports.XAPIVerb = _chunkRUGIOQZYcjs.XAPIVerb; exports.XAPI_VERB_DISPLAY = _chunkRUGIOQZYcjs.XAPI_VERB_DISPLAY; exports.validateXAPIStatement = _chunkRUGIOQZYcjs.validateXAPIStatement; exports.xAPIBuilder = _chunkRUGIOQZYcjs.xAPIBuilder;
13
+
14
+
15
+ exports.XAPIVerb = _chunkR7PDJBRXcjs.XAPIVerb; exports.XAPI_VERB_DISPLAY = _chunkR7PDJBRXcjs.XAPI_VERB_DISPLAY; exports.validateXAPIStatement = _chunkR7PDJBRXcjs.validateXAPIStatement; exports.xAPIBuilder = _chunkR7PDJBRXcjs.xAPIBuilder; exports.xapiDefinitionFor = _chunkR7PDJBRXcjs.xapiDefinitionFor;
14
16
  //# sourceMappingURL=xapi.cjs.map
package/dist/xapi.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["/home/runner/work/learning-kit/learning-kit/packages/lk-core/dist/xapi.cjs"],"names":[],"mappings":"AAAA;AACE;AACA;AACA;AACA;AACF,wDAA6B;AAC7B,gCAA6B;AAC7B;AACE;AACA;AACA;AACA;AACF,6OAAC","file":"/home/runner/work/learning-kit/learning-kit/packages/lk-core/dist/xapi.cjs"}
1
+ {"version":3,"sources":["/home/runner/work/learning-kit/learning-kit/packages/lk-core/dist/xapi.cjs"],"names":[],"mappings":"AAAA;AACE;AACA;AACA;AACA;AACA;AACF,wDAA6B;AAC7B,gCAA6B;AAC7B;AACE;AACA;AACA;AACA;AACA;AACF,8SAAC","file":"/home/runner/work/learning-kit/learning-kit/packages/lk-core/dist/xapi.cjs"}
package/dist/xapi.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { X as XAPIActor, m as ScoringResult, t as XAPIContext, x as XAPIResult, z as XAPIStatement } from './activity-zdcAMtFB.cjs';
1
+ import { X as XAPIActor, r as ScoringResult, y as XAPIContext, J as XAPIResult, N as XAPIStatement } from './activity-wkzRemHx.cjs';
2
2
 
3
3
  /**
4
4
  * The set of xAPI verbs emitted by SDK activities, keyed by a stable
@@ -21,6 +21,13 @@ declare const XAPIVerb: {
21
21
  * community choice (design decision recorded per Task 25.4).
22
22
  */
23
23
  readonly SUBMITTED: "http://activitystrea.ms/schema/1.0/submit";
24
+ /**
25
+ * Emitted when an asynchronous grader produces a grade for work that was
26
+ * previously only SUBMITTED. Distinct from `answered`, which asserts the
27
+ * grade existed at submission time — here the learner acted earlier and the
28
+ * grade arrived later, often from a different actor (an AI or a teacher).
29
+ */
30
+ readonly SCORED: "http://adlnet.gov/expapi/verbs/scored";
24
31
  };
25
32
  /** Union of the valid `XAPIVerb` keys. */
26
33
  type XAPIVerbKey = keyof typeof XAPIVerb;
@@ -107,6 +114,23 @@ declare const xAPIBuilder: {
107
114
  buildSubmittedStatement(params: SubmittedStatementParams): XAPIStatement;
108
115
  buildCompletedStatement(params: CompletedStatementParams): XAPIStatement;
109
116
  };
117
+ /**
118
+ * Builds the xAPI `object.definition` interop fields for an activity from its
119
+ * REGISTERED descriptor, rather than having every renderer hand-roll them.
120
+ *
121
+ * `ActivityTypeDescriptor.interop` declares the activity-type IRI, the
122
+ * cmi.interaction type and how to derive `correctResponsesPattern`; before
123
+ * this, those fields had no reader anywhere and each component rebuilt the
124
+ * same strings inline — the same "declared but never used" defect the SDK
125
+ * fixed elsewhere. Reading them here also means a consumer-registered type
126
+ * gets correct interop for free.
127
+ *
128
+ * Returns an empty object for an unregistered type or one that declares no
129
+ * interop, so it is always safe to spread.
130
+ */
131
+ declare function xapiDefinitionFor(data: {
132
+ type: string;
133
+ }): Partial<XAPIObjectParams>;
110
134
 
111
135
  /**
112
136
  * Validates a constructed {@link XAPIStatement} against the xAPI 1.0.3
@@ -123,4 +147,4 @@ declare const xAPIBuilder: {
123
147
  */
124
148
  declare function validateXAPIStatement(statement: XAPIStatement): void;
125
149
 
126
- export { type AnsweredStatementParams, type CompletedStatementParams, type SubmittedStatementParams, type XAPIObjectParams, type XAPIStatementParams, XAPIVerb, type XAPIVerbKey, XAPI_VERB_DISPLAY, validateXAPIStatement, xAPIBuilder };
150
+ export { type AnsweredStatementParams, type CompletedStatementParams, type SubmittedStatementParams, type XAPIObjectParams, type XAPIStatementParams, XAPIVerb, type XAPIVerbKey, XAPI_VERB_DISPLAY, validateXAPIStatement, xAPIBuilder, xapiDefinitionFor };
package/dist/xapi.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { X as XAPIActor, m as ScoringResult, t as XAPIContext, x as XAPIResult, z as XAPIStatement } from './activity-zdcAMtFB.js';
1
+ import { X as XAPIActor, r as ScoringResult, y as XAPIContext, J as XAPIResult, N as XAPIStatement } from './activity-wkzRemHx.js';
2
2
 
3
3
  /**
4
4
  * The set of xAPI verbs emitted by SDK activities, keyed by a stable
@@ -21,6 +21,13 @@ declare const XAPIVerb: {
21
21
  * community choice (design decision recorded per Task 25.4).
22
22
  */
23
23
  readonly SUBMITTED: "http://activitystrea.ms/schema/1.0/submit";
24
+ /**
25
+ * Emitted when an asynchronous grader produces a grade for work that was
26
+ * previously only SUBMITTED. Distinct from `answered`, which asserts the
27
+ * grade existed at submission time — here the learner acted earlier and the
28
+ * grade arrived later, often from a different actor (an AI or a teacher).
29
+ */
30
+ readonly SCORED: "http://adlnet.gov/expapi/verbs/scored";
24
31
  };
25
32
  /** Union of the valid `XAPIVerb` keys. */
26
33
  type XAPIVerbKey = keyof typeof XAPIVerb;
@@ -107,6 +114,23 @@ declare const xAPIBuilder: {
107
114
  buildSubmittedStatement(params: SubmittedStatementParams): XAPIStatement;
108
115
  buildCompletedStatement(params: CompletedStatementParams): XAPIStatement;
109
116
  };
117
+ /**
118
+ * Builds the xAPI `object.definition` interop fields for an activity from its
119
+ * REGISTERED descriptor, rather than having every renderer hand-roll them.
120
+ *
121
+ * `ActivityTypeDescriptor.interop` declares the activity-type IRI, the
122
+ * cmi.interaction type and how to derive `correctResponsesPattern`; before
123
+ * this, those fields had no reader anywhere and each component rebuilt the
124
+ * same strings inline — the same "declared but never used" defect the SDK
125
+ * fixed elsewhere. Reading them here also means a consumer-registered type
126
+ * gets correct interop for free.
127
+ *
128
+ * Returns an empty object for an unregistered type or one that declares no
129
+ * interop, so it is always safe to spread.
130
+ */
131
+ declare function xapiDefinitionFor(data: {
132
+ type: string;
133
+ }): Partial<XAPIObjectParams>;
110
134
 
111
135
  /**
112
136
  * Validates a constructed {@link XAPIStatement} against the xAPI 1.0.3
@@ -123,4 +147,4 @@ declare const xAPIBuilder: {
123
147
  */
124
148
  declare function validateXAPIStatement(statement: XAPIStatement): void;
125
149
 
126
- export { type AnsweredStatementParams, type CompletedStatementParams, type SubmittedStatementParams, type XAPIObjectParams, type XAPIStatementParams, XAPIVerb, type XAPIVerbKey, XAPI_VERB_DISPLAY, validateXAPIStatement, xAPIBuilder };
150
+ export { type AnsweredStatementParams, type CompletedStatementParams, type SubmittedStatementParams, type XAPIObjectParams, type XAPIStatementParams, XAPIVerb, type XAPIVerbKey, XAPI_VERB_DISPLAY, validateXAPIStatement, xAPIBuilder, xapiDefinitionFor };
package/dist/xapi.js CHANGED
@@ -2,13 +2,15 @@ import {
2
2
  XAPIVerb,
3
3
  XAPI_VERB_DISPLAY,
4
4
  validateXAPIStatement,
5
- xAPIBuilder
6
- } from "./chunk-HS7BYCGE.js";
7
- import "./chunk-3YAVDV5F.js";
5
+ xAPIBuilder,
6
+ xapiDefinitionFor
7
+ } from "./chunk-WV5WQ3SZ.js";
8
+ import "./chunk-NUCEUU4P.js";
8
9
  export {
9
10
  XAPIVerb,
10
11
  XAPI_VERB_DISPLAY,
11
12
  validateXAPIStatement,
12
- xAPIBuilder
13
+ xAPIBuilder,
14
+ xapiDefinitionFor
13
15
  };
14
16
  //# sourceMappingURL=xapi.js.map