@intellectif/lk-core 0.18.0 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +62 -0
- package/README.md +3 -1
- package/dist/{activity-HH8dxN1v.d.cts → activity-f0bgZSTP.d.cts} +163 -141
- package/dist/{activity-HH8dxN1v.d.ts → activity-f0bgZSTP.d.ts} +163 -141
- package/dist/ai-check.cjs +283 -0
- package/dist/ai-check.cjs.map +1 -0
- package/dist/ai-check.d.cts +106 -0
- package/dist/ai-check.d.ts +106 -0
- package/dist/ai-check.js +283 -0
- package/dist/ai-check.js.map +1 -0
- package/dist/chunk-BIJCO2HM.js +443 -0
- package/dist/chunk-BIJCO2HM.js.map +1 -0
- package/dist/chunk-FVZJ4X3Y.cjs +443 -0
- package/dist/chunk-FVZJ4X3Y.cjs.map +1 -0
- package/dist/{index-DejnxQpK.d.cts → index-7S3-TLjT.d.cts} +1 -1
- package/dist/{index-BrBBUb1i.d.ts → index-BA9f2ydg.d.ts} +1 -1
- package/dist/index.cjs +165 -428
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +149 -5
- package/dist/index.d.ts +149 -5
- package/dist/index.js +167 -430
- package/dist/index.js.map +1 -1
- package/dist/schemas.d.cts +2 -2
- package/dist/schemas.d.ts +2 -2
- package/dist/scoring.d.cts +2 -2
- package/dist/scoring.d.ts +2 -2
- package/dist/xapi.d.cts +1 -1
- package/dist/xapi.d.ts +1 -1
- package/package.json +11 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,67 @@
|
|
|
1
1
|
# @intellectif/lk-core
|
|
2
2
|
|
|
3
|
+
## 0.20.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 73a37e3: Delivery policies: what a school lets a learner see around a question — feedback, solutions, hints
|
|
8
|
+
and AI help — decided per paper rather than per item, so one question serves a practice lesson and a
|
|
9
|
+
final exam without being authored twice.
|
|
10
|
+
|
|
11
|
+
- **`DeliveryPolicy`** — `{ feedback?, solutions?, hints?, ai?: { hints?, explanations? } }`, with
|
|
12
|
+
`ai: false` as shorthand for both AI settings off. Every setting is a restriction that defaults to
|
|
13
|
+
`true`, so an empty policy is exactly the behaviour before policies existed; nothing can be switched
|
|
14
|
+
on.
|
|
15
|
+
- **`resolveDeliveryPolicy(policy)`** spells a policy out, every setting a boolean. It never throws:
|
|
16
|
+
`null` is unset, and any other value that is not exactly `true` restricts — a `"false"` from a form
|
|
17
|
+
means off, not on.
|
|
18
|
+
- **`validateDeliveryPolicy(policy)`** checks a policy before it is stored and names each problem at
|
|
19
|
+
its path, a misspelled setting included.
|
|
20
|
+
- **`combineDeliveryPolicies(...policies)`** keeps a setting on only where every policy leaves it on.
|
|
21
|
+
- **`OPEN_DELIVERY_POLICY`**, the policy that restricts nothing.
|
|
22
|
+
- **`planAttempt(entries, { delivery })`** records the policy the attempt is sat under as
|
|
23
|
+
`plan.delivery`, spelled out, and includes it in `planHash`. It throws on a policy that would not
|
|
24
|
+
validate. **A plan made without one is byte-identical to before, `planHash` included.**
|
|
25
|
+
- **`verifyAttemptPlan`** reports `deliveryChanged: true` when two plans were made under different
|
|
26
|
+
policies, and `matches` is then `false`. The field is present only when true, so every drift report
|
|
27
|
+
between plans without a policy keeps exactly its old shape.
|
|
28
|
+
|
|
29
|
+
No grade changes: nothing here reaches a scorer. Hint penalties, `ItemScoringPolicy` and retries —
|
|
30
|
+
which do — come next, with grade vectors.
|
|
31
|
+
|
|
32
|
+
See `docs/delivery.md`.
|
|
33
|
+
|
|
34
|
+
## 0.19.0
|
|
35
|
+
|
|
36
|
+
### Minor Changes
|
|
37
|
+
|
|
38
|
+
- d576382: Groundwork under the AI line: what a call cost, what was refused, and a way to test a prompt.
|
|
39
|
+
|
|
40
|
+
- **`AiTextResult.usage`** — what the call cost, as your provider reported it to your port. It is the
|
|
41
|
+
same `GraderUsage` a returned grade already carries (`promptTokens`, `completionTokens`, `costUsd`),
|
|
42
|
+
so one shape covers help for a learner and marking by a grader. The SDK never estimates it and never
|
|
43
|
+
adds it up; it carries what you send, and drops a number that is not finite and zero or more rather
|
|
44
|
+
than let it into your totals.
|
|
45
|
+
- **`ai-help-refused`** joins the interaction kinds: a host can now see, in production, that its model
|
|
46
|
+
wrote a hint containing the answer or an explanation of a verdict the SDK did not reach. The
|
|
47
|
+
development console warning said it only to a developer. The event carries the feature and the
|
|
48
|
+
reason and **never the text** — the text of a refused hint is the answer.
|
|
49
|
+
- **`@intellectif/lk-core/ai-check`, a new entry point: a test kit for your prompts.** `runAiCheck`
|
|
50
|
+
makes the calls a learner's questions would make — on items whose answers the SDK knows — and runs
|
|
51
|
+
the same checks it runs before a learner sees anything, reporting what would have been shown and
|
|
52
|
+
what would have been refused, with the reason. `aiCheckCases` hands over the cases (every type it
|
|
53
|
+
explains, answered right, wrong and partly right; every type it hints for, before an answer and
|
|
54
|
+
after a wrong one), and takes cases of your own built on your own items. `formatAiCheckReport`
|
|
55
|
+
prints a run for a CI log.
|
|
56
|
+
|
|
57
|
+
It is a separate entry point because it is for your test run, not a learner's browser: nothing a
|
|
58
|
+
page renders imports it.
|
|
59
|
+
|
|
60
|
+
The empty `@intellectif/lk-ai` placeholder has been deleted — it never held code, and the ports it
|
|
61
|
+
was planned for live here, beside the scorers whose readings the checks are.
|
|
62
|
+
|
|
63
|
+
See `docs/ai.md`.
|
|
64
|
+
|
|
3
65
|
## 0.18.0
|
|
4
66
|
|
|
5
67
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -26,7 +26,8 @@ npm install @intellectif/lk-core
|
|
|
26
26
|
- **Deferred grading, both directions** — `GradeRecord`, `gradeFromRubric()` (the weighted total as a pure function of the grader's judgements — never the model's arithmetic; declare `maxScore` per criterion if your grader works out of 100 or out of 9), `outcomeFromGrade()` (which refuses a record whose numbers cannot be a grade instead of mirroring them), `hasGrade()`.
|
|
27
27
|
- **Assessment composition** — `composeAssessmentScore()` for weighted, sectioned totals, with a `provisional` status so an unmarked essay never deflates a grade — and a grade out of contract (`NaN`, 85 "out of 1") never finalises one — and `RoundingPolicy` / `roundGrade` / `gte` / `classifyBand` keeping grade rounding and band classification as the two distinct operations they are.
|
|
28
28
|
- **Item groups** — `Stimulus` + `ItemGroup` (one passage or recording serving several questions), `flattenSequence()` for seeded, shuffle-atomic presentation order with stable slot identity. Slot ids are positional by default (`"3"`, `"1.0"`); author a `slotKey` on an entry and its id survives an insert, so a stored response cannot slide onto a different question.
|
|
29
|
-
- **
|
|
29
|
+
- **Delivery policies** — `DeliveryPolicy` says what a school lets a learner see around a question: feedback, solutions, hints and AI help. `resolveDeliveryPolicy` spells one out (an unreadable setting restricts, never allows), `validateDeliveryPolicy` checks one before it is stored, and `combineDeliveryPolicies` keeps a setting on only where every policy leaves it on.
|
|
30
|
+
- **Attempt plans** — `planAttempt(entries, { seed, shuffleEntries, points, delivery })` freezes the exact paper a learner sat, and the delivery policy it was sat under: slot order, per-slot points, a content hash of each item, and a `planHash` over the lot. `verifyAttemptPlan(plan, current)` reports the drift when that content is later edited (`changedSlotIds`, `changedStimulusSlotIds`, `changedPointsSlotIds`, `missingSlotIds`, …) — the thing you need during a remark or an appeal. `scoredItemsFromPlan(plan, outcomes)` turns collected outcomes into the `ScoredItem[]` composition wants, and defaults a slot with **no** outcome to `deferred` rather than silently shrinking the denominator.
|
|
30
31
|
- **Attempt state** — `serializeAttemptState(plan, progress)` / `restoreAttemptState(plan, snapshot)` round-trip an interrupted attempt (responses, submitted slots, position) and are **bound to the plan**: restoring answers onto a paper the learner never sat throws instead of succeeding quietly. `diffResponses(before, after)` reports what moved between two snapshots, including an answer the learner *cleared*.
|
|
31
32
|
- **Media playback policy** — `media.playback` on an audio recording: `maxPlays`, `seek`, `rate`, and advisory `nativeControlHints`. `resolvePlaybackPolicy()` derives the defaults once so the schema, the plan and the renderer cannot drift. Budgets are frozen into the plan by `planAttempt`, so editing `maxPlays` mid-window cannot change what a past learner was held to, and `planAttempt` refuses a paper where one recording is budgeted under two keys.
|
|
32
33
|
- **Play ledger** — `serializeMediaPlayLedger` / `restoreMediaPlayLedger` / `planMediaBudgets`, with `slotMediaKey()` / `stimulusMediaKey()` for the keys. Bound to its paper by `planHash`, and deliberately **separate** from `AttemptState`: a play is charged immediately while answers autosave on a debounce, and a build that predates the ledger would rewrite an `AttemptState` snapshot without the counts and erase them.
|
|
@@ -115,6 +116,7 @@ result.passed; // boolean, or null while provisional. Never record a provision
|
|
|
115
116
|
| `@intellectif/lk-core/schemas` | Zod schemas + `validateActivity` + JSON Schema export |
|
|
116
117
|
| `@intellectif/lk-core/scoring` | `score`, `evaluate`, `composeAssessmentScore`, rounding, text matching |
|
|
117
118
|
| `@intellectif/lk-core/xapi` | `xAPIBuilder`, `XAPIVerb`, `validateXAPIStatement` |
|
|
119
|
+
| `@intellectif/lk-core/ai-check` | `runAiCheck`, `aiCheckCases`, `formatAiCheckReport` — test your AI prompts in CI |
|
|
118
120
|
|
|
119
121
|
The barrel re-exports everything, and it is the **only** entry point for attempt
|
|
120
122
|
plans, attempt state, item groups, redaction, the type registry, authoring drafts
|
|
@@ -63,6 +63,147 @@ declare function levenshteinDistance(a: string, b: string, max: number): number;
|
|
|
63
63
|
*/
|
|
64
64
|
declare function matchText(input: string, accepted: string | readonly string[], policy?: TextMatchPolicy): TextMatchResult;
|
|
65
65
|
|
|
66
|
+
/**
|
|
67
|
+
* The return trip for deferred grading.
|
|
68
|
+
*
|
|
69
|
+
* `evaluate()` can say a submission is `deferred` — graded later by an AI or a
|
|
70
|
+
* human — but until now there was no type for the grade that comes BACK, so
|
|
71
|
+
* every consumer invented one. The shapes here are the intersection of two
|
|
72
|
+
* independent production graders (a rubric-based essay grader and a CEFR
|
|
73
|
+
* speaking grader) that converged on the same envelope: a normalised total,
|
|
74
|
+
* per-criterion scores with comments, grader-authored artefacts, a confidence
|
|
75
|
+
* signal, an explicit human-review flag, and provenance.
|
|
76
|
+
*
|
|
77
|
+
* The SDK models the SHAPE and the arithmetic. It never calls a model, never
|
|
78
|
+
* holds a key, and never decides what a rubric means — that stays yours.
|
|
79
|
+
*/
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Lifecycle of a deferred grade. A grade exists only in the `graded` state;
|
|
83
|
+
* every other state means "no grade, and here is why" — which is precisely the
|
|
84
|
+
* distinction that stops an ungraded submission being rendered as a zero.
|
|
85
|
+
*/
|
|
86
|
+
type GradingState = 'queued' | 'running' | 'graded' | 'failed' | 'skipped';
|
|
87
|
+
/**
|
|
88
|
+
* Who produced a grade.
|
|
89
|
+
*
|
|
90
|
+
* - `'auto'` — a non-generative measurement engine, for example a
|
|
91
|
+
* pronunciation-assessment service.
|
|
92
|
+
* - `'ai'` — a generative model.
|
|
93
|
+
* - `'human'` — a person.
|
|
94
|
+
*/
|
|
95
|
+
type GraderKind = 'ai' | 'human' | 'auto';
|
|
96
|
+
/** Provenance of a grade, so a re-grade two years later is explicable. */
|
|
97
|
+
interface Grader {
|
|
98
|
+
kind: GraderKind;
|
|
99
|
+
/** Identifier of the human grader, when `kind` is `'human'`. */
|
|
100
|
+
id?: string;
|
|
101
|
+
/** Model identifier, when `kind` is `'ai'` (e.g. a model name and version). */
|
|
102
|
+
model?: string;
|
|
103
|
+
/** Hash or id of the prompt/rubric revision used, for auditability. */
|
|
104
|
+
promptHash?: string;
|
|
105
|
+
}
|
|
106
|
+
/** Optional cost/usage telemetry emitted by an AI grader. */
|
|
107
|
+
interface GraderUsage {
|
|
108
|
+
promptTokens?: number;
|
|
109
|
+
completionTokens?: number;
|
|
110
|
+
costUsd?: number;
|
|
111
|
+
}
|
|
112
|
+
/** A grader's verdict on one rubric criterion. */
|
|
113
|
+
interface CriterionScore {
|
|
114
|
+
/** Matches a `WrittenResponseRubricCriterion.name` when a rubric is known. */
|
|
115
|
+
name: string;
|
|
116
|
+
/**
|
|
117
|
+
* The grader's score for this criterion, out of {@link CriterionScore.maxScore}
|
|
118
|
+
* (which defaults to 1, the SDK's scaled convention). Absent when the
|
|
119
|
+
* criterion is judged on an ordinal `band`.
|
|
120
|
+
*/
|
|
121
|
+
score?: number;
|
|
122
|
+
/**
|
|
123
|
+
* What `score` is out of. Defaults to `1`.
|
|
124
|
+
*
|
|
125
|
+
* Real graders rarely work in [0,1]: rubrics come back out of 100, out of 9
|
|
126
|
+
* (CEFR/IELTS bands), or out of a points total that differs between
|
|
127
|
+
* criteria. Before this field the only way to use {@link gradeFromRubric}
|
|
128
|
+
* was to normalise by hand first — and a grader that skipped that step got
|
|
129
|
+
* its scores REJECTED, which pushed integrators back to letting the model
|
|
130
|
+
* compute the weighted total itself. That is precisely the arithmetic this
|
|
131
|
+
* SDK exists to take away from a language model.
|
|
132
|
+
*
|
|
133
|
+
* Set it per criterion, so a rubric mixing a /100 criterion with a /9 band
|
|
134
|
+
* score still composes. `gradeFromRubric` normalises `score / maxScore`
|
|
135
|
+
* before weighting; the resulting ratio must land in [0,1].
|
|
136
|
+
*/
|
|
137
|
+
maxScore?: number;
|
|
138
|
+
/** Ordinal verdict when the rubric is banded rather than numeric (e.g. `"B1"`). */
|
|
139
|
+
band?: string;
|
|
140
|
+
/** The grader's comment for this criterion, addressed to the learner. */
|
|
141
|
+
comment?: string;
|
|
142
|
+
/**
|
|
143
|
+
* Weight actually applied, echoed from the rubric so the total is checkable.
|
|
144
|
+
* Defaults to `1`. Zero or more: `gradeFromRubric` refuses a negative weight
|
|
145
|
+
* rather than let it carry the total outside [0,1].
|
|
146
|
+
*/
|
|
147
|
+
weight?: number;
|
|
148
|
+
/** Not applicable to this submission (e.g. interaction on a monologue task). */
|
|
149
|
+
notApplicable?: boolean;
|
|
150
|
+
}
|
|
151
|
+
/** A suggested correction anchored in the learner's own text. */
|
|
152
|
+
interface InlineCorrection {
|
|
153
|
+
/** Verbatim span from the submission. */
|
|
154
|
+
original: string;
|
|
155
|
+
/** Suggested replacement. */
|
|
156
|
+
corrected: string;
|
|
157
|
+
explanation?: string;
|
|
158
|
+
/** Character offsets into the submitted text, so a UI can anchor it inline. */
|
|
159
|
+
range?: {
|
|
160
|
+
start: number;
|
|
161
|
+
end: number;
|
|
162
|
+
};
|
|
163
|
+
/** Error-type tag (`"article"`, `"tense"`, `"register"`). */
|
|
164
|
+
category?: string;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* A grade that came back from an asynchronous grader. `score` is scaled
|
|
168
|
+
* [0,1] against `maxScore`, matching every other score in the SDK. A grader
|
|
169
|
+
* that works in points does not have to normalise by hand: declare
|
|
170
|
+
* `maxScore` on each {@link CriterionScore} and let `gradeFromRubric` do the
|
|
171
|
+
* arithmetic, which is the whole point of handing it the judgements.
|
|
172
|
+
*
|
|
173
|
+
* `maxScore` is a positive, finite number and `score` a finite number from 0
|
|
174
|
+
* to it. `outcomeFromGrade` refuses a record that is not, rather than mirror
|
|
175
|
+
* numbers nobody can defend onto an outcome.
|
|
176
|
+
*/
|
|
177
|
+
interface GradeRecord {
|
|
178
|
+
score: number;
|
|
179
|
+
maxScore: number;
|
|
180
|
+
passed: boolean;
|
|
181
|
+
/** Narrative feedback addressed to the learner. */
|
|
182
|
+
feedback: string | null;
|
|
183
|
+
/** Per-criterion breakdown, when the grader worked against a rubric. */
|
|
184
|
+
criteria?: CriterionScore[];
|
|
185
|
+
/**
|
|
186
|
+
* Per-item marks carried by a grade — for example the per-word marks of a
|
|
187
|
+
* read-aloud grade. `outcomeFromGrade` does not mirror them onto the
|
|
188
|
+
* outcome; read `outcome.grade.details`.
|
|
189
|
+
*/
|
|
190
|
+
details?: ScoringDetail[];
|
|
191
|
+
/** Corrections anchored in the learner's text. */
|
|
192
|
+
corrections?: InlineCorrection[];
|
|
193
|
+
/** Supporting observations the grader cited. */
|
|
194
|
+
evidence?: string[];
|
|
195
|
+
/** How the grader reached this verdict. */
|
|
196
|
+
rationale?: string;
|
|
197
|
+
/** The grader's own confidence — a signal for routing to human review. */
|
|
198
|
+
confidence?: 'high' | 'medium' | 'low';
|
|
199
|
+
/** The grader is unsure or the submission is atypical; route to a human. */
|
|
200
|
+
requiresHumanReview?: boolean;
|
|
201
|
+
grader?: Grader;
|
|
202
|
+
usage?: GraderUsage;
|
|
203
|
+
/** ISO 8601 timestamp of when the grade was produced. */
|
|
204
|
+
gradedAt?: string;
|
|
205
|
+
}
|
|
206
|
+
|
|
66
207
|
/**
|
|
67
208
|
* The contract between an activity and a model the HOST runs, for help a
|
|
68
209
|
* learner reads: an explanation of a graded answer, and hints before one.
|
|
@@ -72,6 +213,7 @@ declare function matchText(input: string, accepted: string | readonly string[],
|
|
|
72
213
|
* SDK's own scorer — and checks what comes back. The model explains a verdict
|
|
73
214
|
* the SDK reached; it never reaches one.
|
|
74
215
|
*/
|
|
216
|
+
|
|
75
217
|
/**
|
|
76
218
|
* What an author allows AI to do for one item. Absent, or a feature left out,
|
|
77
219
|
* leaves the choice to the deployment. `false` switches a feature off for this
|
|
@@ -250,150 +392,20 @@ interface AiTextResult {
|
|
|
250
392
|
/** For an explanation: the verdict the model explained. */
|
|
251
393
|
verdict?: AiVerdict;
|
|
252
394
|
provenance?: AiProvenance;
|
|
253
|
-
}
|
|
254
|
-
/** Why a result was not shown. */
|
|
255
|
-
type AiRefusal = 'malformed' | 'empty' | 'too-long' | 'contradicts-grade' | 'reveals-answer';
|
|
256
|
-
|
|
257
|
-
/**
|
|
258
|
-
* The return trip for deferred grading.
|
|
259
|
-
*
|
|
260
|
-
* `evaluate()` can say a submission is `deferred` — graded later by an AI or a
|
|
261
|
-
* human — but until now there was no type for the grade that comes BACK, so
|
|
262
|
-
* every consumer invented one. The shapes here are the intersection of two
|
|
263
|
-
* independent production graders (a rubric-based essay grader and a CEFR
|
|
264
|
-
* speaking grader) that converged on the same envelope: a normalised total,
|
|
265
|
-
* per-criterion scores with comments, grader-authored artefacts, a confidence
|
|
266
|
-
* signal, an explicit human-review flag, and provenance.
|
|
267
|
-
*
|
|
268
|
-
* The SDK models the SHAPE and the arithmetic. It never calls a model, never
|
|
269
|
-
* holds a key, and never decides what a rubric means — that stays yours.
|
|
270
|
-
*/
|
|
271
|
-
|
|
272
|
-
/**
|
|
273
|
-
* Lifecycle of a deferred grade. A grade exists only in the `graded` state;
|
|
274
|
-
* every other state means "no grade, and here is why" — which is precisely the
|
|
275
|
-
* distinction that stops an ungraded submission being rendered as a zero.
|
|
276
|
-
*/
|
|
277
|
-
type GradingState = 'queued' | 'running' | 'graded' | 'failed' | 'skipped';
|
|
278
|
-
/**
|
|
279
|
-
* Who produced a grade.
|
|
280
|
-
*
|
|
281
|
-
* - `'auto'` — a non-generative measurement engine, for example a
|
|
282
|
-
* pronunciation-assessment service.
|
|
283
|
-
* - `'ai'` — a generative model.
|
|
284
|
-
* - `'human'` — a person.
|
|
285
|
-
*/
|
|
286
|
-
type GraderKind = 'ai' | 'human' | 'auto';
|
|
287
|
-
/** Provenance of a grade, so a re-grade two years later is explicable. */
|
|
288
|
-
interface Grader {
|
|
289
|
-
kind: GraderKind;
|
|
290
|
-
/** Identifier of the human grader, when `kind` is `'human'`. */
|
|
291
|
-
id?: string;
|
|
292
|
-
/** Model identifier, when `kind` is `'ai'` (e.g. a model name and version). */
|
|
293
|
-
model?: string;
|
|
294
|
-
/** Hash or id of the prompt/rubric revision used, for auditability. */
|
|
295
|
-
promptHash?: string;
|
|
296
|
-
}
|
|
297
|
-
/** Optional cost/usage telemetry emitted by an AI grader. */
|
|
298
|
-
interface GraderUsage {
|
|
299
|
-
promptTokens?: number;
|
|
300
|
-
completionTokens?: number;
|
|
301
|
-
costUsd?: number;
|
|
302
|
-
}
|
|
303
|
-
/** A grader's verdict on one rubric criterion. */
|
|
304
|
-
interface CriterionScore {
|
|
305
|
-
/** Matches a `WrittenResponseRubricCriterion.name` when a rubric is known. */
|
|
306
|
-
name: string;
|
|
307
|
-
/**
|
|
308
|
-
* The grader's score for this criterion, out of {@link CriterionScore.maxScore}
|
|
309
|
-
* (which defaults to 1, the SDK's scaled convention). Absent when the
|
|
310
|
-
* criterion is judged on an ordinal `band`.
|
|
311
|
-
*/
|
|
312
|
-
score?: number;
|
|
313
395
|
/**
|
|
314
|
-
* What
|
|
315
|
-
*
|
|
316
|
-
*
|
|
317
|
-
* (CEFR/IELTS bands), or out of a points total that differs between
|
|
318
|
-
* criteria. Before this field the only way to use {@link gradeFromRubric}
|
|
319
|
-
* was to normalise by hand first — and a grader that skipped that step got
|
|
320
|
-
* its scores REJECTED, which pushed integrators back to letting the model
|
|
321
|
-
* compute the weighted total itself. That is precisely the arithmetic this
|
|
322
|
-
* SDK exists to take away from a language model.
|
|
396
|
+
* What the call cost, as the provider reported it to your port: the same
|
|
397
|
+
* {@link GraderUsage} a returned grade carries, so one shape covers help for
|
|
398
|
+
* a learner and marking by a grader.
|
|
323
399
|
*
|
|
324
|
-
*
|
|
325
|
-
*
|
|
326
|
-
*
|
|
400
|
+
* The SDK never estimates it and never adds it up. It carries what a port
|
|
401
|
+
* sends through to `onInteraction`, beside the provenance, so the record of
|
|
402
|
+
* what a learner was shown and the record of what it cost are the same
|
|
403
|
+
* record. Numbers that are not finite and zero or more are dropped.
|
|
327
404
|
*/
|
|
328
|
-
maxScore?: number;
|
|
329
|
-
/** Ordinal verdict when the rubric is banded rather than numeric (e.g. `"B1"`). */
|
|
330
|
-
band?: string;
|
|
331
|
-
/** The grader's comment for this criterion, addressed to the learner. */
|
|
332
|
-
comment?: string;
|
|
333
|
-
/**
|
|
334
|
-
* Weight actually applied, echoed from the rubric so the total is checkable.
|
|
335
|
-
* Defaults to `1`. Zero or more: `gradeFromRubric` refuses a negative weight
|
|
336
|
-
* rather than let it carry the total outside [0,1].
|
|
337
|
-
*/
|
|
338
|
-
weight?: number;
|
|
339
|
-
/** Not applicable to this submission (e.g. interaction on a monologue task). */
|
|
340
|
-
notApplicable?: boolean;
|
|
341
|
-
}
|
|
342
|
-
/** A suggested correction anchored in the learner's own text. */
|
|
343
|
-
interface InlineCorrection {
|
|
344
|
-
/** Verbatim span from the submission. */
|
|
345
|
-
original: string;
|
|
346
|
-
/** Suggested replacement. */
|
|
347
|
-
corrected: string;
|
|
348
|
-
explanation?: string;
|
|
349
|
-
/** Character offsets into the submitted text, so a UI can anchor it inline. */
|
|
350
|
-
range?: {
|
|
351
|
-
start: number;
|
|
352
|
-
end: number;
|
|
353
|
-
};
|
|
354
|
-
/** Error-type tag (`"article"`, `"tense"`, `"register"`). */
|
|
355
|
-
category?: string;
|
|
356
|
-
}
|
|
357
|
-
/**
|
|
358
|
-
* A grade that came back from an asynchronous grader. `score` is scaled
|
|
359
|
-
* [0,1] against `maxScore`, matching every other score in the SDK. A grader
|
|
360
|
-
* that works in points does not have to normalise by hand: declare
|
|
361
|
-
* `maxScore` on each {@link CriterionScore} and let `gradeFromRubric` do the
|
|
362
|
-
* arithmetic, which is the whole point of handing it the judgements.
|
|
363
|
-
*
|
|
364
|
-
* `maxScore` is a positive, finite number and `score` a finite number from 0
|
|
365
|
-
* to it. `outcomeFromGrade` refuses a record that is not, rather than mirror
|
|
366
|
-
* numbers nobody can defend onto an outcome.
|
|
367
|
-
*/
|
|
368
|
-
interface GradeRecord {
|
|
369
|
-
score: number;
|
|
370
|
-
maxScore: number;
|
|
371
|
-
passed: boolean;
|
|
372
|
-
/** Narrative feedback addressed to the learner. */
|
|
373
|
-
feedback: string | null;
|
|
374
|
-
/** Per-criterion breakdown, when the grader worked against a rubric. */
|
|
375
|
-
criteria?: CriterionScore[];
|
|
376
|
-
/**
|
|
377
|
-
* Per-item marks carried by a grade — for example the per-word marks of a
|
|
378
|
-
* read-aloud grade. `outcomeFromGrade` does not mirror them onto the
|
|
379
|
-
* outcome; read `outcome.grade.details`.
|
|
380
|
-
*/
|
|
381
|
-
details?: ScoringDetail[];
|
|
382
|
-
/** Corrections anchored in the learner's text. */
|
|
383
|
-
corrections?: InlineCorrection[];
|
|
384
|
-
/** Supporting observations the grader cited. */
|
|
385
|
-
evidence?: string[];
|
|
386
|
-
/** How the grader reached this verdict. */
|
|
387
|
-
rationale?: string;
|
|
388
|
-
/** The grader's own confidence — a signal for routing to human review. */
|
|
389
|
-
confidence?: 'high' | 'medium' | 'low';
|
|
390
|
-
/** The grader is unsure or the submission is atypical; route to a human. */
|
|
391
|
-
requiresHumanReview?: boolean;
|
|
392
|
-
grader?: Grader;
|
|
393
405
|
usage?: GraderUsage;
|
|
394
|
-
/** ISO 8601 timestamp of when the grade was produced. */
|
|
395
|
-
gradedAt?: string;
|
|
396
406
|
}
|
|
407
|
+
/** Why a result was not shown. */
|
|
408
|
+
type AiRefusal = 'malformed' | 'empty' | 'too-long' | 'contradicts-grade' | 'reveals-answer';
|
|
397
409
|
|
|
398
410
|
/**
|
|
399
411
|
* An xAPI Activity object — the thing a statement is about.
|
|
@@ -1549,8 +1561,18 @@ type InteractionKind = 'option-selected' | 'option-deselected' | 'blank-filled'
|
|
|
1549
1561
|
* is never shown and never reported here.
|
|
1550
1562
|
*/
|
|
1551
1563
|
| 'ai-hint-shown'
|
|
1552
|
-
/** An AI explanation of the graded answer was shown (`provenance` when sent). */
|
|
1553
|
-
| 'ai-explanation-shown'
|
|
1564
|
+
/** An AI explanation of the graded answer was shown (`provenance`, `usage` when sent). */
|
|
1565
|
+
| 'ai-explanation-shown'
|
|
1566
|
+
/**
|
|
1567
|
+
* An AI answer was refused, and the learner was told help was not available
|
|
1568
|
+
* (`feature`, `reason` — an {@link AiRefusal} — and `hintNumber` for a hint).
|
|
1569
|
+
*
|
|
1570
|
+
* **The text itself is never carried**: a hint refused for revealing the
|
|
1571
|
+
* answer contains the answer, and this event is logged. It is how a host
|
|
1572
|
+
* watches its own model in production, where the development console warning
|
|
1573
|
+
* it accompanies is silent.
|
|
1574
|
+
*/
|
|
1575
|
+
| 'ai-help-refused' | (string & {});
|
|
1554
1576
|
/** Fired by activity components on every discrete learner interaction. */
|
|
1555
1577
|
interface InteractionEvent {
|
|
1556
1578
|
/** The kind of interaction that occurred. */
|