@intellectif/lk-core 1.4.0 → 1.6.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 +102 -0
- package/README.md +2 -0
- package/dist/ai-check.cjs +175 -17
- package/dist/ai-check.cjs.map +1 -1
- package/dist/ai-check.d.cts +13 -5
- package/dist/ai-check.d.cts.map +1 -1
- package/dist/ai-check.d.ts +13 -5
- package/dist/ai-check.d.ts.map +1 -1
- package/dist/ai-check.js +175 -17
- package/dist/ai-check.js.map +1 -1
- package/dist/{ai-CnmLPMzd.d.ts → ai-i3jwXnpX.d.cts} +242 -7
- package/dist/ai-i3jwXnpX.d.cts.map +1 -0
- package/dist/{ai-CnmLPMzd.d.cts → ai-i3jwXnpX.d.ts} +242 -7
- package/dist/ai-i3jwXnpX.d.ts.map +1 -0
- package/dist/{ai-writing-G6tnCGzw.cjs → ai-writing-C4YRVrFi.cjs} +683 -72
- package/dist/ai-writing-C4YRVrFi.cjs.map +1 -0
- package/dist/{ai-writing-CPpH9Qxq.js → ai-writing-OJ5m_1EB.js} +663 -88
- package/dist/ai-writing-OJ5m_1EB.js.map +1 -0
- package/dist/{index-BqMXWuNC.d.cts → index--U7hHV0s.d.cts} +2 -2
- package/dist/{index-BqMXWuNC.d.cts.map → index--U7hHV0s.d.cts.map} +1 -1
- package/dist/{index-B54baFUA.d.ts → index-CFRgZLhH.d.cts} +45 -12
- package/dist/index-CFRgZLhH.d.cts.map +1 -0
- package/dist/{index-CVWvymVb.d.ts → index-DeD8EI1D.d.ts} +327 -3
- package/dist/index-DeD8EI1D.d.ts.map +1 -0
- package/dist/{index-CcHDTxIZ.d.ts → index-Dj92T76o.d.ts} +2 -2
- package/dist/{index-CcHDTxIZ.d.ts.map → index-Dj92T76o.d.ts.map} +1 -1
- package/dist/{index-BAfEHCnD.d.cts → index-Uh1u57yq.d.cts} +327 -3
- package/dist/index-Uh1u57yq.d.cts.map +1 -0
- package/dist/{index-2nigSf4N.d.cts → index-WBHSrIUU.d.ts} +45 -12
- package/dist/index-WBHSrIUU.d.ts.map +1 -0
- package/dist/index.cjs +88 -131
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +217 -95
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +217 -95
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +60 -118
- package/dist/index.js.map +1 -1
- package/dist/{item-group-DXiR8nOr.js → item-group-BbvvH_Ob.js} +3 -2
- package/dist/item-group-BbvvH_Ob.js.map +1 -0
- package/dist/{item-group-DroFXgLb.cjs → item-group-CQRbb8Rx.cjs} +3 -2
- package/dist/item-group-CQRbb8Rx.cjs.map +1 -0
- package/dist/{registry-Dsx9mNsS.js → registry-B23o9UWh.js} +218 -21
- package/dist/registry-B23o9UWh.js.map +1 -0
- package/dist/{registry-DVi0LR_i.cjs → registry-Ih5n4PVZ.cjs} +265 -20
- package/dist/registry-Ih5n4PVZ.cjs.map +1 -0
- package/dist/{schemas-DVbAOrtd.cjs → schemas-Q2dyOgc2.cjs} +77 -14
- package/dist/schemas-Q2dyOgc2.cjs.map +1 -0
- package/dist/{schemas-Bdw0nGPo.js → schemas-yId-bJhD.js} +72 -15
- package/dist/schemas-yId-bJhD.js.map +1 -0
- package/dist/schemas.cjs +5 -2
- package/dist/schemas.d.cts +2 -2
- package/dist/schemas.d.ts +2 -2
- package/dist/schemas.js +4 -3
- package/dist/{scoring-BZqUkuDF.js → scoring-CLMdk8i9.js} +657 -3
- package/dist/scoring-CLMdk8i9.js.map +1 -0
- package/dist/{scoring-B2_HEhMT.cjs → scoring-CaPS6q8b.cjs} +722 -2
- package/dist/scoring-CaPS6q8b.cjs.map +1 -0
- package/dist/scoring.cjs +8 -2
- package/dist/scoring.d.cts +3 -3
- package/dist/scoring.d.ts +3 -3
- package/dist/scoring.js +3 -3
- package/dist/{xapi-DNzkSJ0S.cjs → xapi-BX0hvRRB.cjs} +2 -2
- package/dist/{xapi-DNzkSJ0S.cjs.map → xapi-BX0hvRRB.cjs.map} +1 -1
- package/dist/{xapi-CMTph7wG.js → xapi-Cdk9fTvV.js} +2 -2
- package/dist/{xapi-CMTph7wG.js.map → xapi-Cdk9fTvV.js.map} +1 -1
- package/dist/xapi.cjs +1 -1
- package/dist/xapi.d.cts +1 -1
- package/dist/xapi.d.ts +1 -1
- package/dist/xapi.js +1 -2
- package/package.json +2 -2
- package/vectors/README.md +24 -1
- package/vectors/scoring.json +18182 -1
- package/dist/ai-CnmLPMzd.d.cts.map +0 -1
- package/dist/ai-CnmLPMzd.d.ts.map +0 -1
- package/dist/ai-writing-CPpH9Qxq.js.map +0 -1
- package/dist/ai-writing-G6tnCGzw.cjs.map +0 -1
- package/dist/index-2nigSf4N.d.cts.map +0 -1
- package/dist/index-B54baFUA.d.ts.map +0 -1
- package/dist/index-BAfEHCnD.d.cts.map +0 -1
- package/dist/index-CVWvymVb.d.ts.map +0 -1
- package/dist/item-group-DXiR8nOr.js.map +0 -1
- package/dist/item-group-DroFXgLb.cjs.map +0 -1
- package/dist/registry-DVi0LR_i.cjs.map +0 -1
- package/dist/registry-Dsx9mNsS.js.map +0 -1
- package/dist/schemas-Bdw0nGPo.js.map +0 -1
- package/dist/schemas-DVbAOrtd.cjs.map +0 -1
- package/dist/scoring-B2_HEhMT.cjs.map +0 -1
- package/dist/scoring-BZqUkuDF.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,107 @@
|
|
|
1
1
|
# @intellectif/lk-core
|
|
2
2
|
|
|
3
|
+
## 1.6.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#120](https://github.com/intellectif/learning-kit/pull/120) [`d5ee618`](https://github.com/intellectif/learning-kit/commit/d5ee6185d2a167794352b218486881c405b13b52) Thanks [@diego28e](https://github.com/diego28e)! - **Exporting and importing content**: list and re-point every file an item or a group uses, keep what each recording says for the author's side only, and describe content in one JSON Schema.
|
|
8
|
+
|
|
9
|
+
**Action required:** none. Every field is optional and every function new. Two cases to check:
|
|
10
|
+
|
|
11
|
+
- **A field you already store under these names.** If your content keeps a media `transcript` that is not a string, or a `script` that is not a list of `{ speaker, text }` turns, it now fails validation.
|
|
12
|
+
- **A `switch` over the critic's codes.** The critic has three new codes, so a `switch` over them without a `default` branch needs them.
|
|
13
|
+
|
|
14
|
+
- **Files:** `listMediaRefs(entry)` lists every media address in an activity or an item group: each one's path, kind, owner and language. It covers an item's `media`, a multiple-choice option's `media`, dictation's and read-aloud's `slowMedia`, and a stimulus's `media`, with their captions, tracks and posters. A recording with no file yet is listed with `url: null`.
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
const files = listMediaRefs(entry).filter((ref) => ref.url !== null);
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
`mapMediaRefs(entry, fn)` returns a copy with each address replaced and nothing else changed. With `(ref) => ref.url` the copy equals the input. A type you register says where it keeps files with `mediaSlots` on its descriptor.
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
const imported = mapMediaRefs(entry, (ref) =>
|
|
24
|
+
ref.url === null ? null : storedUrlFor(ref.url)
|
|
25
|
+
);
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
- **What a recording says:** an item's media, a listening option's media and a stimulus can carry an author-only `transcript` (plain text) and `script` (turns, each a `speaker` and a `text`).
|
|
29
|
+
|
|
30
|
+
- Both are removed by `redact()` and `redactItemGroup()` at every reveal level, and refused by `assertRedacted()`. No request for a learner's AI help reads them.
|
|
31
|
+
- `transcriptOf(holder)` gives the author-side text, the script as `Speaker: text` lines when there is no transcript. Pass it to `aiDraftsRequest` as its `source`.
|
|
32
|
+
|
|
33
|
+
```jsonc
|
|
34
|
+
{
|
|
35
|
+
"type": "audio",
|
|
36
|
+
"url": "https://cdn.example/station.mp3",
|
|
37
|
+
"transcript": "Where does the train leave from? Platform four.",
|
|
38
|
+
"script": [
|
|
39
|
+
{ "speaker": "Anna", "text": "Where does the train leave from?" },
|
|
40
|
+
{ "speaker": "Ben", "text": "Platform four." }
|
|
41
|
+
]
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
- **Practice scored in the browser:** `withoutAuthorOnly(entry)` returns an item or a group without its author-only fields, and with its key and your own fields. Unlike `redact()`, whose output scorers refuse, it scores exactly as the stored item. Every vector of the grade-stability corpus shows it. Exams still go through `redact()`.
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
res.json(withoutAuthorOnly(practiceItem));
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
- **The critic's new codes:** `media_script_differs` (a transcript and a script that say different words), `media_transcript_differs` (a dictation's or a read-aloud's recording saying other words than the item), and `media_transcript_unused` (on a picture or an embed). The AI critic now reads a recording's transcript and script. It no longer reads a caption track's address or language, a poster, or a speaker's label.
|
|
52
|
+
- **JSON Schema:** `contentJsonSchema()` is one draft 2020-12 document whose root is a sequence entry: any registered activity type, or an item group with typed items. `jsonSchemaFor(type, { target: 'draft-2020-12' })` gives one type's schema in that draft. Draft 7 stays the default, so the existing exports do not change.
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
writeFileSync("schema.json", JSON.stringify(contentJsonSchema(), null, 2));
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Guide: https://github.com/intellectif/learning-kit/blob/main/docs/authoring.md#exporting-and-importing-content-16
|
|
59
|
+
|
|
60
|
+
## 1.5.0
|
|
61
|
+
|
|
62
|
+
### Minor Changes
|
|
63
|
+
|
|
64
|
+
- [#112](https://github.com/intellectif/learning-kit/pull/112) [`3fc1049`](https://github.com/intellectif/learning-kit/commit/3fc1049d67e14f6f4a12c235f19952a988472978) Thanks [@diego28e](https://github.com/diego28e)! - **Assisted grading**: a model grades a written response for the record, and in an exam no model's grade counts until it has been measured against your teachers.
|
|
65
|
+
|
|
66
|
+
**Action required:** none. Nothing grades until you call it. Four union members are new: `DeferredReason` gains `awaiting_human_review`, `AiRefusal` gains `misses-criteria` and `out-of-scale`, and `AiCheckCase['feature']` gains `grading`. A `switch` over one of them without a `default` branch needs them.
|
|
67
|
+
|
|
68
|
+
- **Levels on a rubric:** a criterion can carry `levels`, lowest first, from 2 to 10, each a `label` and an optional `description`. A level scores `index / (levels − 1)`.
|
|
69
|
+
- `validateActivity` does not check them in 1.x, so a field of that name you already use stays valid.
|
|
70
|
+
- The critic warns `wr_levels_unreadable` when it cannot read them.
|
|
71
|
+
- `redact()` passes readable levels to the learner, `label` and `description` only.
|
|
72
|
+
- **The request and its check:**
|
|
73
|
+
- `aiGradingRequest` builds what a model is told: the response verbatim, the rubric with its levels, and a group's material.
|
|
74
|
+
- `checkAiGrading` accepts a grade whole or not at all. It needs every criterion once (`misses-criteria`), each on its own scale (`out-of-scale`), with corrections that quote the response (`misquotes-answer`).
|
|
75
|
+
- **The grade:** `gradeFromJudgement` computes the total as `gradeFromRubric` does, with the item's weights and pass line. It records the model and the prompt revision on `grader`.
|
|
76
|
+
- **Calibration:** `calibrationReport` measures one grader configuration (a model, a prompt revision, a rubric and a target) against people's grades of the same responses. It reports:
|
|
77
|
+
|
|
78
|
+
- exact and adjacent agreement, quadratic kappa and a confusion matrix, on levels;
|
|
79
|
+
- Pearson's r, mean absolute error and the standardised mean difference, everywhere;
|
|
80
|
+
- calibration error and the Brier score, where grades carry level probabilities;
|
|
81
|
+
- the degradation from two people's agreement.
|
|
82
|
+
|
|
83
|
+
It ships no thresholds.
|
|
84
|
+
|
|
85
|
+
- **Routing:** `routeGrade` decides whether a grade counts or goes to a person, and gives every reason, in a fixed order:
|
|
86
|
+
|
|
87
|
+
- no matching calibration, in an exam;
|
|
88
|
+
- a report below your thresholds;
|
|
89
|
+
- a model unsure of a level, or one that asked for review;
|
|
90
|
+
- low confidence;
|
|
91
|
+
- the audit sample, chosen by `inAuditSample`.
|
|
92
|
+
|
|
93
|
+
`outcomeFromRoutedGrade` holds a grade for a person as `deferred` / `awaiting_human_review`, with the grade kept on `pendingGrade`, so `composeAssessmentScore` keeps the attempt provisional.
|
|
94
|
+
|
|
95
|
+
- **New optional fields:**
|
|
96
|
+
- `levels` on a rubric criterion, its redacted form included;
|
|
97
|
+
- `levelProbabilities` on `CriterionScore`;
|
|
98
|
+
- `routing` on `GradeRecord`;
|
|
99
|
+
- `pendingGrade` on a deferred `ItemOutcome`.
|
|
100
|
+
- **`@intellectif/lk-core/ai-check`** has five grading cases, for a `grading` port.
|
|
101
|
+
- **The grade-stability corpus** gains 123 vectors for the new functions. The 635 before them are unchanged.
|
|
102
|
+
|
|
103
|
+
Guide: https://github.com/intellectif/learning-kit/blob/main/docs/grading.md
|
|
104
|
+
|
|
3
105
|
## 1.4.0
|
|
4
106
|
|
|
5
107
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -25,6 +25,7 @@ npm install @intellectif/lk-core
|
|
|
25
25
|
- **Dictation marking as data** — a dictation is graded by the character-level similarity of the typed text to the transcript, with one `ScoringDetail` per word carrying its own `score`. `alignDictation(data, text)` returns the same comparison the grade was made of (normalised strings, word pairings, which accepted transcript won), `diffDictationChars(reference, attempt)` the character-level edits for a marked display, and `dictationReferenceWords(data)` the ids and words a stored result can be checked against — so a review screen or an analytics job never re-implements the arithmetic. `score()` and `evaluate()` take an optional `{ rounding }` to compare the pass line as displayed.
|
|
26
26
|
- **`evaluate(data, response)` → `ItemOutcome`** — the resilient result: `scored`, `deferred` (graded later by an AI or a human), `graded` (the grade came back), or `unscorable`. "Not graded yet" is expressible in the type system and is never conflated with a zero.
|
|
27
27
|
- **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()`.
|
|
28
|
+
- **Assisted grading, behind a calibration gate** — `aiGradingRequest` / `checkAiGrading` hold a model's grade of a written response to every criterion of the rubric, each on its own scale (levels the author writes, or a score); `gradeFromJudgement` computes the total. `calibrationReport` measures a model, prompt, rubric and target against your teachers' grades, and `routeGrade` sends a grade to a person, for reasons it names: in an exam, until a matching calibration is supplied. No thresholds ship: they are yours.
|
|
28
29
|
- **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.
|
|
29
30
|
- **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.
|
|
30
31
|
- **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.
|
|
@@ -161,6 +162,7 @@ Zod is the single runtime dependency, pinned to one exact version and private: n
|
|
|
161
162
|
- [Delivery policies](https://github.com/intellectif/learning-kit/blob/main/docs/delivery.md) — `DeliveryPolicy`, and recording it with an attempt.
|
|
162
163
|
- [Scoring policies](https://github.com/intellectif/learning-kit/blob/main/docs/scoring.md) — `ItemScoringPolicy`: tries, which counts, what hints cost, and scoring them on a server.
|
|
163
164
|
- [AI help for learners](https://github.com/intellectif/learning-kit/blob/main/docs/ai.md) — the AI contract, the checks, and the `ai-check` kit for your prompts.
|
|
165
|
+
- [Grading with a model](https://github.com/intellectif/learning-kit/blob/main/docs/grading.md) — a model's grade for the record: levels, calibration against your teachers, and where a grade goes.
|
|
164
166
|
- [Project README](https://github.com/intellectif/learning-kit#readme) — the full picture, including the React renderers.
|
|
165
167
|
- [Contributing](https://github.com/intellectif/learning-kit/blob/main/CONTRIBUTING.md) — adding a new activity type.
|
|
166
168
|
|
package/dist/ai-check.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_ai_writing = require("./ai-writing-
|
|
3
|
-
const require_scoring = require("./scoring-
|
|
2
|
+
const require_ai_writing = require("./ai-writing-C4YRVrFi.cjs");
|
|
3
|
+
const require_scoring = require("./scoring-CaPS6q8b.cjs");
|
|
4
4
|
//#region src/ai-check/cases.ts
|
|
5
5
|
/**
|
|
6
6
|
* The items the cases are built from. Small, ordinary language-course content
|
|
@@ -17,7 +17,10 @@ const require_scoring = require("./scoring-B2_HEhMT.cjs");
|
|
|
17
17
|
* twice — so a correction of words nobody wrote is caught;
|
|
18
18
|
* - questions in item groups: a reading passage that prints a question's
|
|
19
19
|
* answer, which a hint must point to without quoting, and a run of blanks
|
|
20
|
-
* whose instructions only the group states
|
|
20
|
+
* whose instructions only the group states;
|
|
21
|
+
* - essays to grade on a rubric of three criteria, two judged on levels and
|
|
22
|
+
* one on a score, so a grade that drops a criterion or leaves a scale is
|
|
23
|
+
* caught.
|
|
21
24
|
*/
|
|
22
25
|
const capital = {
|
|
23
26
|
schemaVersion: "1.0",
|
|
@@ -205,6 +208,117 @@ const weekend = {
|
|
|
205
208
|
}
|
|
206
209
|
] }
|
|
207
210
|
};
|
|
211
|
+
/**
|
|
212
|
+
* The weekend task set as an exam question: graded on three criteria, two on
|
|
213
|
+
* levels and one on a score. Three, so a prompt that names only the ones it
|
|
214
|
+
* expects drops one; two scales, so a grade must keep to each.
|
|
215
|
+
*/
|
|
216
|
+
const weekendExam = {
|
|
217
|
+
schemaVersion: "1.0",
|
|
218
|
+
type: "written-response",
|
|
219
|
+
id: "ai-check-writing-exam",
|
|
220
|
+
title: "Your weekend",
|
|
221
|
+
prompt: "Write a few sentences about what you did last weekend.",
|
|
222
|
+
minWords: 15,
|
|
223
|
+
maxWords: 120,
|
|
224
|
+
languageTarget: "en-A2",
|
|
225
|
+
rubric: { criteria: [
|
|
226
|
+
{
|
|
227
|
+
name: "Task",
|
|
228
|
+
description: "Says what the learner did last weekend, in order",
|
|
229
|
+
weight: 1,
|
|
230
|
+
levels: [
|
|
231
|
+
{
|
|
232
|
+
label: "Not met",
|
|
233
|
+
description: "Not about last weekend"
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
label: "Partly met",
|
|
237
|
+
description: "About the weekend, but thin or out of order"
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
label: "Met",
|
|
241
|
+
description: "Says what they did, in order"
|
|
242
|
+
}
|
|
243
|
+
]
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
name: "Grammar",
|
|
247
|
+
description: "Past tenses, articles, agreement",
|
|
248
|
+
weight: 2,
|
|
249
|
+
levels: [
|
|
250
|
+
{
|
|
251
|
+
label: "Limited",
|
|
252
|
+
description: "Errors in most sentences, some blocking meaning"
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
label: "Basic",
|
|
256
|
+
description: "Frequent errors in past tenses and articles"
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
label: "Good",
|
|
260
|
+
description: "Some errors, none that blocks meaning"
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
label: "Very good",
|
|
264
|
+
description: "Past tenses, articles and agreement almost always right"
|
|
265
|
+
}
|
|
266
|
+
]
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
name: "Vocabulary",
|
|
270
|
+
description: "Everyday words, used correctly",
|
|
271
|
+
weight: 1
|
|
272
|
+
}
|
|
273
|
+
] }
|
|
274
|
+
};
|
|
275
|
+
/** The tides summary set as an exam question, graded against its group's passage. */
|
|
276
|
+
const tidesSummaryExam = {
|
|
277
|
+
schemaVersion: "1.0",
|
|
278
|
+
type: "written-response",
|
|
279
|
+
id: "ai-check-tides-summary-exam",
|
|
280
|
+
title: "Summary",
|
|
281
|
+
prompt: "Summarise the text in one or two sentences.",
|
|
282
|
+
minWords: 10,
|
|
283
|
+
maxWords: 60,
|
|
284
|
+
languageTarget: "en-B1",
|
|
285
|
+
rubric: { criteria: [{
|
|
286
|
+
name: "Content",
|
|
287
|
+
description: "What the text says, and nothing it does not",
|
|
288
|
+
weight: 2,
|
|
289
|
+
levels: [
|
|
290
|
+
{
|
|
291
|
+
label: "Wrong",
|
|
292
|
+
description: "Misstates what the text says"
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
label: "Partly right",
|
|
296
|
+
description: "Gets one of its points wrong, or leaves it out"
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
label: "Right",
|
|
300
|
+
description: "States its points as the text does"
|
|
301
|
+
}
|
|
302
|
+
]
|
|
303
|
+
}, {
|
|
304
|
+
name: "Language",
|
|
305
|
+
description: "Clear sentences, in the learner's own words",
|
|
306
|
+
weight: 1
|
|
307
|
+
}] }
|
|
308
|
+
};
|
|
309
|
+
const tidesExam = {
|
|
310
|
+
schemaVersion: "1.0",
|
|
311
|
+
type: "item-group",
|
|
312
|
+
id: "ai-check-tides-exam",
|
|
313
|
+
title: "Part 2 — Reading",
|
|
314
|
+
stimulus: {
|
|
315
|
+
id: "ai-check-tides-exam-text",
|
|
316
|
+
kind: "text",
|
|
317
|
+
title: "Tides",
|
|
318
|
+
body: TIDES
|
|
319
|
+
},
|
|
320
|
+
items: [springTides, tidesSummaryExam]
|
|
321
|
+
};
|
|
208
322
|
const weather = {
|
|
209
323
|
schemaVersion: "1.0",
|
|
210
324
|
type: "read-aloud",
|
|
@@ -492,6 +606,21 @@ function writingCase(id, about, text, previousFeedback = [], learnerLocale, task
|
|
|
492
606
|
request
|
|
493
607
|
};
|
|
494
608
|
}
|
|
609
|
+
function gradingCase(id, about, text, extra = {}) {
|
|
610
|
+
const built = require_ai_writing.aiGradingRequest({
|
|
611
|
+
data: extra.data ?? weekendExam,
|
|
612
|
+
response: drafted(text),
|
|
613
|
+
...extra.group ? { group: extra.group } : {},
|
|
614
|
+
...extra.learnerLocale !== void 0 ? { learnerLocale: extra.learnerLocale } : {}
|
|
615
|
+
});
|
|
616
|
+
if (!built.ok) throw new Error(`ai-check: no grading request for "${id}": ${built.problem}`);
|
|
617
|
+
return {
|
|
618
|
+
id,
|
|
619
|
+
feature: "grading",
|
|
620
|
+
about,
|
|
621
|
+
request: built.request
|
|
622
|
+
};
|
|
623
|
+
}
|
|
495
624
|
function coachingCase(id, about, marks, learnerLocale) {
|
|
496
625
|
const assessment = marks.assessment ?? forecast(true);
|
|
497
626
|
const grade = require_scoring.gradeReadAloud(weather, {
|
|
@@ -528,8 +657,9 @@ function coachingCase(id, about, marks, learnerLocale) {
|
|
|
528
657
|
/**
|
|
529
658
|
* The cases {@link runAiCheck} runs: every type the SDK explains, answered
|
|
530
659
|
* right, wrong and — where a type can be — partly right; every type it hints
|
|
531
|
-
* for, before an answer and after a wrong one;
|
|
532
|
-
* mistakes, without, and revised
|
|
660
|
+
* for, before an answer and after a wrong one; drafts of an essay, with
|
|
661
|
+
* mistakes, without, and revised; and essays to grade, on the task, with
|
|
662
|
+
* mistakes to quote, off the task, and against a passage.
|
|
533
663
|
*
|
|
534
664
|
* They are ordinary calls. What makes them a test is that the SDK knows the
|
|
535
665
|
* answer to each, so it can check what your model writes about it.
|
|
@@ -652,7 +782,15 @@ function aiCheckCases() {
|
|
|
652
782
|
locale: "en",
|
|
653
783
|
instructions: "Focus on what Maria and her grandmother buy.",
|
|
654
784
|
settings: { "read-aloud": READ_ALOUD_SETTINGS }
|
|
655
|
-
})
|
|
785
|
+
}),
|
|
786
|
+
gradingCase("grading-on-task", "An essay that does the task, with one slip in its tenses", "Last weekend I went to the market with my sister and we bought some apples. In the afternoon we watched a film at home, and on Sunday I visit my grandmother."),
|
|
787
|
+
gradingCase("grading-mistakes", "An essay with tense, article and agreement mistakes, each to quote as written", "Last weekend I go to the market and buyed a apple. My friends was there too, so we don't go home early. Then I go to the cinema."),
|
|
788
|
+
gradingCase("grading-off-task", "An essay about something other than last weekend", "My favourite food is pizza. I like it with cheese and tomato, and I eat it every Friday with my family at a small restaurant near our house."),
|
|
789
|
+
gradingCase("grading-summary", "A summary graded against its group's passage, getting the spring tides wrong", "The tide comes in twice a day because the moon pulls the ocean. The highest tides come only at full moon.", {
|
|
790
|
+
data: tidesSummaryExam,
|
|
791
|
+
group: tidesExam
|
|
792
|
+
}),
|
|
793
|
+
gradingCase("grading-in-spanish", "The essay with mistakes, with comments asked for in Spanish", "Last weekend I go to the market and buyed a apple. My friends was there too, so we don't go home early. Then I go to the cinema.", { learnerLocale: "es" })
|
|
656
794
|
];
|
|
657
795
|
}
|
|
658
796
|
//#endregion
|
|
@@ -681,6 +819,7 @@ function aiCheckCases() {
|
|
|
681
819
|
* pronunciationCoaching: (request) => callMyModel(COACHING_PROMPT, request),
|
|
682
820
|
* critique: (request) => callMyModel(CRITIQUE_PROMPT, request),
|
|
683
821
|
* drafts: (request) => callMyModel(DRAFTS_PROMPT, request),
|
|
822
|
+
* grading: (request) => callMyModel(GRADING_PROMPT, request),
|
|
684
823
|
* });
|
|
685
824
|
* console.log(formatAiCheckReport(report));
|
|
686
825
|
* if (report.refused > 0 || report.errors > 0) {
|
|
@@ -693,18 +832,27 @@ function aiCheckCases() {
|
|
|
693
832
|
* to take most seriously — that prompt gives answers away. Next come
|
|
694
833
|
* `misquotes-answer` on a writing case — that prompt corrects words the learner
|
|
695
834
|
* never wrote — and `contradicts-marks` on a coaching case: that prompt coaches
|
|
696
|
-
* words the engine heard as right, or sounds it never reported.
|
|
835
|
+
* words the engine heard as right, or sounds it never reported. On a grading
|
|
836
|
+
* case, `misses-criteria` and `out-of-scale` mean a grade nobody can use: one
|
|
837
|
+
* that leaves a criterion out grades a different task, and one off a
|
|
838
|
+
* criterion's scale grades on a scale the author did not write.
|
|
839
|
+
*
|
|
840
|
+
* A grading case the SDK accepts is a reply of the right shape, not a right
|
|
841
|
+
* grade: whether a model grades as your teachers do is what a calibration
|
|
842
|
+
* measures (`calibrationReport`), on your own responses.
|
|
697
843
|
*/
|
|
698
|
-
const REFUSALS =
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
"too-long",
|
|
702
|
-
"contradicts-grade",
|
|
703
|
-
"reveals-answer",
|
|
704
|
-
"misquotes-answer",
|
|
705
|
-
"contradicts-marks",
|
|
706
|
-
"contradicts-item"
|
|
707
|
-
|
|
844
|
+
const REFUSALS = Object.keys({
|
|
845
|
+
malformed: true,
|
|
846
|
+
empty: true,
|
|
847
|
+
"too-long": true,
|
|
848
|
+
"contradicts-grade": true,
|
|
849
|
+
"reveals-answer": true,
|
|
850
|
+
"misquotes-answer": true,
|
|
851
|
+
"contradicts-marks": true,
|
|
852
|
+
"contradicts-item": true,
|
|
853
|
+
"misses-criteria": true,
|
|
854
|
+
"out-of-scale": true
|
|
855
|
+
});
|
|
708
856
|
/** The port a case is for. */
|
|
709
857
|
function portFor(ports, one) {
|
|
710
858
|
switch (one.request.feature) {
|
|
@@ -713,6 +861,7 @@ function portFor(ports, one) {
|
|
|
713
861
|
case "pronunciation-coaching": return ports.pronunciationCoaching;
|
|
714
862
|
case "item-critique": return ports.critique;
|
|
715
863
|
case "draft-generation": return ports.drafts;
|
|
864
|
+
case "grading": return ports.grading;
|
|
716
865
|
default: return ports.writingFeedback;
|
|
717
866
|
}
|
|
718
867
|
}
|
|
@@ -735,6 +884,15 @@ async function runCase(ports, one) {
|
|
|
735
884
|
};
|
|
736
885
|
}
|
|
737
886
|
const ms = now() - started;
|
|
887
|
+
if (one.request.feature === "grading") {
|
|
888
|
+
const checked = require_ai_writing.checkAiGrading(raw, one.request);
|
|
889
|
+
return checked.ok ? {
|
|
890
|
+
case: one,
|
|
891
|
+
ok: true,
|
|
892
|
+
grading: checked.judgement,
|
|
893
|
+
ms
|
|
894
|
+
} : refused(one, raw, checked.refusal, ms);
|
|
895
|
+
}
|
|
738
896
|
if (one.request.feature === "draft-generation") {
|
|
739
897
|
let next = 0;
|
|
740
898
|
const checked = require_ai_writing.checkAiDrafts(raw, one.request, { newId: () => {
|