@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.
- package/dist/{activity-zdcAMtFB.d.cts → activity-wkzRemHx.d.cts} +113 -1
- package/dist/{activity-zdcAMtFB.d.ts → activity-wkzRemHx.d.ts} +113 -1
- package/dist/{chunk-QOAORTA4.cjs → chunk-4JR3UXX6.cjs} +51 -2
- package/dist/chunk-4JR3UXX6.cjs.map +1 -0
- package/dist/chunk-DUQVGLQ3.js +333 -0
- package/dist/chunk-DUQVGLQ3.js.map +1 -0
- package/dist/chunk-FCM5VJBS.cjs +187 -0
- package/dist/chunk-FCM5VJBS.cjs.map +1 -0
- package/dist/chunk-LSHDNA2T.js +187 -0
- package/dist/chunk-LSHDNA2T.js.map +1 -0
- package/dist/{chunk-5GJJHGY5.js → chunk-NUCEUU4P.js} +50 -1
- package/dist/chunk-NUCEUU4P.js.map +1 -0
- package/dist/{chunk-RUGIOQZY.cjs → chunk-R7PDJBRX.cjs} +29 -6
- package/dist/chunk-R7PDJBRX.cjs.map +1 -0
- package/dist/chunk-SM5HUGYU.cjs +333 -0
- package/dist/chunk-SM5HUGYU.cjs.map +1 -0
- package/dist/{chunk-HS7BYCGE.js → chunk-WV5WQ3SZ.js} +29 -6
- package/dist/chunk-WV5WQ3SZ.js.map +1 -0
- package/dist/index-BKyZrd94.d.cts +732 -0
- package/dist/index-CLmXzBhB.d.ts +732 -0
- package/dist/index.cjs +206 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +182 -6
- package/dist/index.d.ts +182 -6
- package/dist/index.js +203 -12
- package/dist/index.js.map +1 -1
- package/dist/schemas.cjs +17 -4
- package/dist/schemas.cjs.map +1 -1
- package/dist/schemas.d.cts +3 -460
- package/dist/schemas.d.ts +3 -460
- package/dist/schemas.js +16 -3
- package/dist/scoring.cjs +11 -4
- package/dist/scoring.cjs.map +1 -1
- package/dist/scoring.d.cts +199 -4
- package/dist/scoring.d.ts +199 -4
- package/dist/scoring.js +10 -3
- package/dist/xapi.cjs +5 -3
- package/dist/xapi.cjs.map +1 -1
- package/dist/xapi.d.cts +26 -2
- package/dist/xapi.d.ts +26 -2
- package/dist/xapi.js +6 -4
- package/package.json +38 -15
- package/dist/chunk-3YAVDV5F.js +0 -47
- package/dist/chunk-3YAVDV5F.js.map +0 -1
- package/dist/chunk-55O4M45K.js +0 -57
- package/dist/chunk-55O4M45K.js.map +0 -1
- package/dist/chunk-5GJJHGY5.js.map +0 -1
- package/dist/chunk-HS7BYCGE.js.map +0 -1
- package/dist/chunk-PIMX4B4D.cjs +0 -47
- package/dist/chunk-PIMX4B4D.cjs.map +0 -1
- package/dist/chunk-QOAORTA4.cjs.map +0 -1
- package/dist/chunk-QSVBYTNM.js +0 -94
- package/dist/chunk-QSVBYTNM.js.map +0 -1
- package/dist/chunk-RUGIOQZY.cjs.map +0 -1
- package/dist/chunk-THSZMZND.cjs +0 -94
- package/dist/chunk-THSZMZND.cjs.map +0 -1
- package/dist/chunk-YNTYWHZI.cjs +0 -57
- package/dist/chunk-YNTYWHZI.cjs.map +0 -1
package/dist/scoring.d.cts
CHANGED
|
@@ -1,13 +1,208 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export { T as TextMatchPolicy,
|
|
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 {
|
|
2
|
-
export { T as TextMatchPolicy,
|
|
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-
|
|
10
|
+
} from "./chunk-DUQVGLQ3.js";
|
|
7
11
|
import {
|
|
8
12
|
levenshteinDistance,
|
|
9
13
|
matchText
|
|
10
|
-
} from "./chunk-
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
-
|
|
7
|
-
|
|
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
|