@intellectif/lk-core 1.0.0 → 1.2.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 +30 -0
- package/README.md +2 -1
- package/dist/{activity-DgpBJeBc.d.cts → activity-D705CXng.d.cts} +439 -7
- package/dist/{activity-DgpBJeBc.d.ts → activity-D705CXng.d.ts} +439 -7
- package/dist/ai-check.cjs +300 -9
- package/dist/ai-check.cjs.map +1 -1
- package/dist/ai-check.d.cts +18 -3
- package/dist/ai-check.d.ts +18 -3
- package/dist/ai-check.js +295 -4
- package/dist/ai-check.js.map +1 -1
- package/dist/{chunk-TJJFKKH2.cjs → chunk-46BXIGLG.cjs} +4 -4
- package/dist/{chunk-TJJFKKH2.cjs.map → chunk-46BXIGLG.cjs.map} +1 -1
- package/dist/{chunk-VSH7ECQL.cjs → chunk-4YANYTFN.cjs} +43 -43
- package/dist/{chunk-VSH7ECQL.cjs.map → chunk-4YANYTFN.cjs.map} +1 -1
- package/dist/chunk-7KOWU4RO.js +2207 -0
- package/dist/chunk-7KOWU4RO.js.map +1 -0
- package/dist/{chunk-DSDXL3JO.cjs → chunk-7THXD7AN.cjs} +657 -197
- package/dist/chunk-7THXD7AN.cjs.map +1 -0
- package/dist/chunk-CYCTIXUN.cjs +118 -0
- package/dist/chunk-CYCTIXUN.cjs.map +1 -0
- package/dist/chunk-CYPA5BW5.js +118 -0
- package/dist/chunk-CYPA5BW5.js.map +1 -0
- package/dist/{chunk-SJVRUPJT.js → chunk-JTNDIBZO.js} +2 -2
- package/dist/{chunk-XS6U2ARZ.js → chunk-MV2N2WNS.js} +6 -112
- package/dist/chunk-MV2N2WNS.js.map +1 -0
- package/dist/chunk-MXI26H62.cjs +2207 -0
- package/dist/chunk-MXI26H62.cjs.map +1 -0
- package/dist/{chunk-FBKTION6.js → chunk-QS7B42ZR.js} +640 -180
- package/dist/chunk-QS7B42ZR.js.map +1 -0
- package/dist/{chunk-IARLJ5MW.js → chunk-QX2ERL72.js} +2 -2
- package/dist/{chunk-2JT2WUQ2.cjs → chunk-ZWXQSKCX.cjs} +14 -120
- package/dist/chunk-ZWXQSKCX.cjs.map +1 -0
- package/dist/{index-BVNeSCWC.d.ts → index-BChYtrEe.d.ts} +1 -1
- package/dist/{index-BlOLlwOi.d.cts → index-VXt_v3YI.d.cts} +1 -1
- package/dist/index.cjs +76 -593
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +308 -66
- package/dist/index.d.ts +308 -66
- package/dist/index.js +76 -593
- package/dist/index.js.map +1 -1
- package/dist/schemas.cjs +5 -3
- package/dist/schemas.cjs.map +1 -1
- package/dist/schemas.d.cts +2 -2
- package/dist/schemas.d.ts +2 -2
- package/dist/schemas.js +5 -3
- package/dist/scoring.cjs +3 -3
- package/dist/scoring.d.cts +242 -242
- package/dist/scoring.d.ts +242 -242
- package/dist/scoring.js +2 -2
- package/dist/xapi.cjs +3 -3
- package/dist/xapi.d.cts +1 -1
- package/dist/xapi.d.ts +1 -1
- package/dist/xapi.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-2JT2WUQ2.cjs.map +0 -1
- package/dist/chunk-DSDXL3JO.cjs.map +0 -1
- package/dist/chunk-FBKTION6.js.map +0 -1
- package/dist/chunk-RCZCP5OE.js +0 -729
- package/dist/chunk-RCZCP5OE.js.map +0 -1
- package/dist/chunk-W5KB3DNT.cjs +0 -729
- package/dist/chunk-W5KB3DNT.cjs.map +0 -1
- package/dist/chunk-XS6U2ARZ.js.map +0 -1
- /package/dist/{chunk-SJVRUPJT.js.map → chunk-JTNDIBZO.js.map} +0 -0
- /package/dist/{chunk-IARLJ5MW.js.map → chunk-QX2ERL72.js.map} +0 -0
package/dist/chunk-W5KB3DNT.cjs
DELETED
|
@@ -1,729 +0,0 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
var _chunkVSH7ECQLcjs = require('./chunk-VSH7ECQL.cjs');
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
var _chunkDSDXL3JOcjs = require('./chunk-DSDXL3JO.cjs');
|
|
11
|
-
|
|
12
|
-
// src/ai-text.ts
|
|
13
|
-
function cleanText(text) {
|
|
14
|
-
return text.replace(/\r\n?/g, "\n").replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F-\u009F\u2028\u2029]/g, "").replace(/\n{3,}/g, "\n\n").trim();
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
// src/ai.ts
|
|
18
|
-
var AI_EXPLANATION_TYPES = [
|
|
19
|
-
"multiple-choice",
|
|
20
|
-
"fill-in-the-blanks",
|
|
21
|
-
"gap-select",
|
|
22
|
-
"dictation"
|
|
23
|
-
];
|
|
24
|
-
var AI_HINT_TYPES = [
|
|
25
|
-
"multiple-choice",
|
|
26
|
-
"fill-in-the-blanks",
|
|
27
|
-
"gap-select"
|
|
28
|
-
];
|
|
29
|
-
var AI_TEXT_MAX_LENGTH = 2e3;
|
|
30
|
-
function aiSupports(activityType, feature) {
|
|
31
|
-
if (feature === "writing-feedback") {
|
|
32
|
-
return activityType === "written-response";
|
|
33
|
-
}
|
|
34
|
-
const types = feature === "hint" ? AI_HINT_TYPES : AI_EXPLANATION_TYPES;
|
|
35
|
-
return types.includes(activityType);
|
|
36
|
-
}
|
|
37
|
-
function aiAllowedByContent(data, feature) {
|
|
38
|
-
const permissions = data.ai;
|
|
39
|
-
if (typeof permissions !== "object" || permissions === null) {
|
|
40
|
-
return true;
|
|
41
|
-
}
|
|
42
|
-
const key = feature === "hint" ? "hints" : "explanations";
|
|
43
|
-
return permissions[key] !== false;
|
|
44
|
-
}
|
|
45
|
-
function aiGradeOf(result) {
|
|
46
|
-
const ratio = result.maxScore > 0 ? result.score / result.maxScore : 0;
|
|
47
|
-
let category = "partly-correct";
|
|
48
|
-
if (ratio >= 1 - 1e-9) {
|
|
49
|
-
category = "correct";
|
|
50
|
-
} else if (ratio <= 1e-9) {
|
|
51
|
-
category = "incorrect";
|
|
52
|
-
}
|
|
53
|
-
return { score: result.score, maxScore: result.maxScore, passed: result.passed, category };
|
|
54
|
-
}
|
|
55
|
-
function placeholderOrder(passage) {
|
|
56
|
-
return [...passage.matchAll(_chunkDSDXL3JOcjs.PLACEHOLDER_RE)].map((match) => match[1]);
|
|
57
|
-
}
|
|
58
|
-
function numberedPassage(passage) {
|
|
59
|
-
let position = 0;
|
|
60
|
-
return passage.replace(_chunkDSDXL3JOcjs.PLACEHOLDER_RE, () => {
|
|
61
|
-
position += 1;
|
|
62
|
-
return `[${position}]`;
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
var legacyCorrect = (detail) => detail.correct === true;
|
|
66
|
-
function detailRight(detail) {
|
|
67
|
-
const outcome = detail.outcome;
|
|
68
|
-
return outcome === void 0 ? legacyCorrect(detail) : outcome === "correct";
|
|
69
|
-
}
|
|
70
|
-
function isAnswerOption(detail, chosen) {
|
|
71
|
-
switch (detail.outcome) {
|
|
72
|
-
case "correct":
|
|
73
|
-
case "incorrect-omission":
|
|
74
|
-
return true;
|
|
75
|
-
case "incorrect":
|
|
76
|
-
case "correct-omission":
|
|
77
|
-
return false;
|
|
78
|
-
default:
|
|
79
|
-
return chosen ? legacyCorrect(detail) : !legacyCorrect(detail);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
var firstString = (value) => Array.isArray(value) ? value[0] : value;
|
|
83
|
-
var listOf = (value) => value === void 0 ? null : Array.isArray(value) ? [...value] : [value];
|
|
84
|
-
function emptyResponse(type) {
|
|
85
|
-
switch (type) {
|
|
86
|
-
case "multiple-choice":
|
|
87
|
-
return { type: "multiple-choice", selectedOptionIds: [] };
|
|
88
|
-
case "fill-in-the-blanks":
|
|
89
|
-
return { type: "fill-in-the-blanks", answers: {} };
|
|
90
|
-
case "gap-select":
|
|
91
|
-
return { type: "gap-select", selections: {} };
|
|
92
|
-
case "dictation":
|
|
93
|
-
return { type: "dictation", text: "" };
|
|
94
|
-
default:
|
|
95
|
-
return null;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
var base = (data) => ({
|
|
99
|
-
activityId: data.id,
|
|
100
|
-
title: data.title,
|
|
101
|
-
...typeof data.locale === "string" ? { locale: data.locale } : {}
|
|
102
|
-
});
|
|
103
|
-
function buildAiFacts(data, response, details) {
|
|
104
|
-
const byId = new Map((_nullishCoalesce(details, () => ( []))).map((detail) => [detail.itemId, detail]));
|
|
105
|
-
const full = data.redacted !== true;
|
|
106
|
-
switch (data.type) {
|
|
107
|
-
case "multiple-choice": {
|
|
108
|
-
const item = data;
|
|
109
|
-
const chosen = new Set(
|
|
110
|
-
_optionalChain([response, 'optionalAccess', _ => _.type]) === "multiple-choice" ? response.selectedOptionIds : []
|
|
111
|
-
);
|
|
112
|
-
return {
|
|
113
|
-
...base(data),
|
|
114
|
-
activityType: "multiple-choice",
|
|
115
|
-
question: item.question,
|
|
116
|
-
mode: item.mode,
|
|
117
|
-
options: item.options.map((option) => {
|
|
118
|
-
const detail = byId.get(option.id);
|
|
119
|
-
const wasChosen = chosen.has(option.id);
|
|
120
|
-
let correct = null;
|
|
121
|
-
if (full && typeof option.isCorrect === "boolean") {
|
|
122
|
-
correct = option.isCorrect;
|
|
123
|
-
} else if (detail !== void 0) {
|
|
124
|
-
correct = isAnswerOption(detail, wasChosen);
|
|
125
|
-
}
|
|
126
|
-
return {
|
|
127
|
-
id: option.id,
|
|
128
|
-
text: option.text,
|
|
129
|
-
chosen: wasChosen,
|
|
130
|
-
correct,
|
|
131
|
-
...typeof option.feedback === "string" ? { feedback: option.feedback } : {}
|
|
132
|
-
};
|
|
133
|
-
})
|
|
134
|
-
};
|
|
135
|
-
}
|
|
136
|
-
case "fill-in-the-blanks": {
|
|
137
|
-
const item = data;
|
|
138
|
-
const answers = _optionalChain([response, 'optionalAccess', _2 => _2.type]) === "fill-in-the-blanks" ? response.answers : {};
|
|
139
|
-
const blanks = new Map(item.blanks.map((blank) => [blank.id, blank]));
|
|
140
|
-
return {
|
|
141
|
-
...base(data),
|
|
142
|
-
activityType: "fill-in-the-blanks",
|
|
143
|
-
passage: numberedPassage(item.passage),
|
|
144
|
-
blanks: placeholderOrder(item.passage).map((id, index) => {
|
|
145
|
-
const blank = blanks.get(id);
|
|
146
|
-
const detail = byId.get(id);
|
|
147
|
-
const accepted = full && Array.isArray(_optionalChain([blank, 'optionalAccess', _3 => _3.acceptedAnswers])) ? [...blank.acceptedAnswers] : listOf(_optionalChain([detail, 'optionalAccess', _4 => _4.correctResponse]));
|
|
148
|
-
return {
|
|
149
|
-
id,
|
|
150
|
-
position: index + 1,
|
|
151
|
-
typed: typeof answers[id] === "string" ? answers[id] : "",
|
|
152
|
-
accepted,
|
|
153
|
-
correct: detail === void 0 ? null : detailRight(detail),
|
|
154
|
-
...typeof _optionalChain([blank, 'optionalAccess', _5 => _5.hint]) === "string" ? { hint: blank.hint } : {},
|
|
155
|
-
...typeof _optionalChain([blank, 'optionalAccess', _6 => _6.feedback]) === "string" ? { feedback: blank.feedback } : {}
|
|
156
|
-
};
|
|
157
|
-
})
|
|
158
|
-
};
|
|
159
|
-
}
|
|
160
|
-
case "gap-select": {
|
|
161
|
-
const item = data;
|
|
162
|
-
const selections = _optionalChain([response, 'optionalAccess', _7 => _7.type]) === "gap-select" ? response.selections : {};
|
|
163
|
-
const gaps = new Map(item.gaps.map((gap) => [gap.id, gap]));
|
|
164
|
-
const banks = new Map((_nullishCoalesce(item.banks, () => ( []))).map((bank) => [bank.id, bank]));
|
|
165
|
-
return {
|
|
166
|
-
...base(data),
|
|
167
|
-
activityType: "gap-select",
|
|
168
|
-
passage: numberedPassage(item.passage),
|
|
169
|
-
gaps: placeholderOrder(item.passage).map((id, index) => {
|
|
170
|
-
const gap = gaps.get(id);
|
|
171
|
-
const choices = _nullishCoalesce(_optionalChain([gap, 'optionalAccess', _8 => _8.choices]), () => ( (_optionalChain([gap, 'optionalAccess', _9 => _9.bankId]) === void 0 ? [] : _nullishCoalesce(_optionalChain([banks, 'access', _10 => _10.get, 'call', _11 => _11(gap.bankId), 'optionalAccess', _12 => _12.choices]), () => ( [])))));
|
|
172
|
-
const textOf = (choiceId) => choiceId === void 0 ? null : _nullishCoalesce(_optionalChain([choices, 'access', _13 => _13.find, 'call', _14 => _14((choice) => choice.id === choiceId), 'optionalAccess', _15 => _15.text]), () => ( null));
|
|
173
|
-
const detail = byId.get(id);
|
|
174
|
-
const answerId = full && typeof _optionalChain([gap, 'optionalAccess', _16 => _16.correctChoiceId]) === "string" ? gap.correctChoiceId : firstString(_optionalChain([detail, 'optionalAccess', _17 => _17.correctResponse]));
|
|
175
|
-
return {
|
|
176
|
-
id,
|
|
177
|
-
position: index + 1,
|
|
178
|
-
choices: choices.map((choice) => choice.text),
|
|
179
|
-
chosen: textOf(selections[id]),
|
|
180
|
-
answer: textOf(answerId),
|
|
181
|
-
correct: detail === void 0 ? null : detailRight(detail),
|
|
182
|
-
...typeof _optionalChain([gap, 'optionalAccess', _18 => _18.feedback]) === "string" ? { feedback: gap.feedback } : {}
|
|
183
|
-
};
|
|
184
|
-
})
|
|
185
|
-
};
|
|
186
|
-
}
|
|
187
|
-
case "dictation": {
|
|
188
|
-
const item = data;
|
|
189
|
-
const typed = _optionalChain([response, 'optionalAccess', _19 => _19.type]) === "dictation" ? response.text : "";
|
|
190
|
-
const hasKey = full && typeof item.transcript === "string";
|
|
191
|
-
let words = null;
|
|
192
|
-
if (hasKey) {
|
|
193
|
-
words = _chunkDSDXL3JOcjs.alignDictation.call(void 0, item, typed).words.map((word) => ({
|
|
194
|
-
expected: word.reference,
|
|
195
|
-
typed: word.attempt,
|
|
196
|
-
status: word.status
|
|
197
|
-
}));
|
|
198
|
-
} else if (details !== null && details.length > 0) {
|
|
199
|
-
words = details.map((detail) => {
|
|
200
|
-
const expected = _nullishCoalesce(firstString(detail.correctResponse), () => ( ""));
|
|
201
|
-
const attempt = _nullishCoalesce(firstString(detail.learnerResponse), () => ( ""));
|
|
202
|
-
let status = "incorrect";
|
|
203
|
-
if (detailRight(detail)) {
|
|
204
|
-
status = "correct";
|
|
205
|
-
} else if (attempt === "") {
|
|
206
|
-
status = "missing";
|
|
207
|
-
} else if (expected === "") {
|
|
208
|
-
status = "extra";
|
|
209
|
-
}
|
|
210
|
-
return { expected, typed: attempt, status };
|
|
211
|
-
});
|
|
212
|
-
}
|
|
213
|
-
return {
|
|
214
|
-
...base(data),
|
|
215
|
-
activityType: "dictation",
|
|
216
|
-
transcript: hasKey ? item.transcript : null,
|
|
217
|
-
typed,
|
|
218
|
-
words
|
|
219
|
-
};
|
|
220
|
-
}
|
|
221
|
-
default:
|
|
222
|
-
return null;
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
function gradeLocally(data, response) {
|
|
226
|
-
if (data.redacted === true) {
|
|
227
|
-
return null;
|
|
228
|
-
}
|
|
229
|
-
try {
|
|
230
|
-
const result = _chunkVSH7ECQLcjs.score.call(void 0, data.type, data, response);
|
|
231
|
-
return { details: result.details, grade: aiGradeOf(result) };
|
|
232
|
-
} catch (e) {
|
|
233
|
-
return null;
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
function aiExplanationRequest(input) {
|
|
237
|
-
const { data, response, outcome, learnerLocale } = input;
|
|
238
|
-
if (!aiSupports(data.type, "explanation") || !aiAllowedByContent(data, "explanation")) {
|
|
239
|
-
return null;
|
|
240
|
-
}
|
|
241
|
-
let details = null;
|
|
242
|
-
let grade = null;
|
|
243
|
-
if (_optionalChain([outcome, 'optionalAccess', _20 => _20.status]) === "scored") {
|
|
244
|
-
const { score: recorded, maxScore, passed } = outcome;
|
|
245
|
-
if (!_chunkVSH7ECQLcjs.isGradeInRange.call(void 0, recorded, maxScore)) {
|
|
246
|
-
return null;
|
|
247
|
-
}
|
|
248
|
-
details = outcome.details;
|
|
249
|
-
grade = aiGradeOf({ score: recorded, maxScore, passed });
|
|
250
|
-
} else if (response !== null) {
|
|
251
|
-
const local = gradeLocally(data, response);
|
|
252
|
-
if (local !== null) {
|
|
253
|
-
details = local.details;
|
|
254
|
-
grade = local.grade;
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
if (grade === null) {
|
|
258
|
-
return null;
|
|
259
|
-
}
|
|
260
|
-
const facts = buildAiFacts(data, response, details);
|
|
261
|
-
if (facts === null) {
|
|
262
|
-
return null;
|
|
263
|
-
}
|
|
264
|
-
return {
|
|
265
|
-
feature: "explanation",
|
|
266
|
-
facts,
|
|
267
|
-
grade,
|
|
268
|
-
...learnerLocale !== void 0 ? { learnerLocale } : {}
|
|
269
|
-
};
|
|
270
|
-
}
|
|
271
|
-
function aiHintRequest(input) {
|
|
272
|
-
const { data, previousHints, learnerLocale } = input;
|
|
273
|
-
if (!aiSupports(data.type, "hint") || !aiAllowedByContent(data, "hint") || data.redacted === true) {
|
|
274
|
-
return null;
|
|
275
|
-
}
|
|
276
|
-
const response = _nullishCoalesce(input.response, () => ( emptyResponse(data.type)));
|
|
277
|
-
const local = response === null ? null : gradeLocally(data, response);
|
|
278
|
-
const facts = buildAiFacts(data, response, _nullishCoalesce(_optionalChain([local, 'optionalAccess', _21 => _21.details]), () => ( null)));
|
|
279
|
-
if (facts === null) {
|
|
280
|
-
return null;
|
|
281
|
-
}
|
|
282
|
-
return {
|
|
283
|
-
feature: "hint",
|
|
284
|
-
facts,
|
|
285
|
-
hintNumber: previousHints.length + 1,
|
|
286
|
-
previousHints: [...previousHints],
|
|
287
|
-
...learnerLocale !== void 0 ? { learnerLocale } : {}
|
|
288
|
-
};
|
|
289
|
-
}
|
|
290
|
-
var VERDICTS = ["correct", "partly-correct", "incorrect"];
|
|
291
|
-
function provenanceOf(raw) {
|
|
292
|
-
if (typeof raw !== "object" || raw === null) {
|
|
293
|
-
return void 0;
|
|
294
|
-
}
|
|
295
|
-
const kept = {};
|
|
296
|
-
for (const key of ["model", "promptHash", "generatedAt"]) {
|
|
297
|
-
const value = raw[key];
|
|
298
|
-
if (typeof value === "string" && value.length > 0 && value.length <= 200) {
|
|
299
|
-
kept[key] = value;
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
return Object.keys(kept).length > 0 ? kept : void 0;
|
|
303
|
-
}
|
|
304
|
-
function usageOf(raw) {
|
|
305
|
-
if (typeof raw !== "object" || raw === null) {
|
|
306
|
-
return void 0;
|
|
307
|
-
}
|
|
308
|
-
const kept = {};
|
|
309
|
-
for (const key of ["promptTokens", "completionTokens", "costUsd"]) {
|
|
310
|
-
const value = raw[key];
|
|
311
|
-
if (typeof value === "number" && Number.isFinite(value) && value >= 0) {
|
|
312
|
-
kept[key] = value;
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
return Object.keys(kept).length > 0 ? kept : void 0;
|
|
316
|
-
}
|
|
317
|
-
function readAiTextResult(raw) {
|
|
318
|
-
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
319
|
-
return { ok: false, refusal: "malformed" };
|
|
320
|
-
}
|
|
321
|
-
const record = raw;
|
|
322
|
-
if (typeof record.text !== "string") {
|
|
323
|
-
return { ok: false, refusal: "malformed" };
|
|
324
|
-
}
|
|
325
|
-
if (record.verdict !== void 0 && !VERDICTS.includes(record.verdict)) {
|
|
326
|
-
return { ok: false, refusal: "malformed" };
|
|
327
|
-
}
|
|
328
|
-
const text = cleanText(record.text);
|
|
329
|
-
if (text === "") {
|
|
330
|
-
return { ok: false, refusal: "empty" };
|
|
331
|
-
}
|
|
332
|
-
if ([...text].length > AI_TEXT_MAX_LENGTH) {
|
|
333
|
-
return { ok: false, refusal: "too-long" };
|
|
334
|
-
}
|
|
335
|
-
const provenance = provenanceOf(record.provenance);
|
|
336
|
-
const usage = usageOf(record.usage);
|
|
337
|
-
return {
|
|
338
|
-
ok: true,
|
|
339
|
-
result: {
|
|
340
|
-
text,
|
|
341
|
-
...record.verdict !== void 0 ? { verdict: record.verdict } : {},
|
|
342
|
-
...provenance !== void 0 ? { provenance } : {},
|
|
343
|
-
...usage !== void 0 ? { usage } : {}
|
|
344
|
-
}
|
|
345
|
-
};
|
|
346
|
-
}
|
|
347
|
-
function checkAiExplanation(raw, request) {
|
|
348
|
-
const read = readAiTextResult(raw);
|
|
349
|
-
if (!read.ok) {
|
|
350
|
-
return read;
|
|
351
|
-
}
|
|
352
|
-
if (read.result.verdict !== void 0 && read.result.verdict !== request.grade.category) {
|
|
353
|
-
return { ok: false, refusal: "contradicts-grade" };
|
|
354
|
-
}
|
|
355
|
-
return read;
|
|
356
|
-
}
|
|
357
|
-
function checkAiHint(raw, request) {
|
|
358
|
-
const read = readAiTextResult(raw);
|
|
359
|
-
if (!read.ok) {
|
|
360
|
-
return read;
|
|
361
|
-
}
|
|
362
|
-
if (hintRevealsAnswer(request.facts, read.result.text)) {
|
|
363
|
-
return { ok: false, refusal: "reveals-answer" };
|
|
364
|
-
}
|
|
365
|
-
return read;
|
|
366
|
-
}
|
|
367
|
-
function fold(text) {
|
|
368
|
-
const folded2 = text.normalize("NFKD").replace(/\p{M}/gu, "").toLowerCase().replace(/[^\p{L}\p{N}]+/gu, " ").trim();
|
|
369
|
-
return folded2 === "" ? [] : folded2.split(" ");
|
|
370
|
-
}
|
|
371
|
-
function containsWords(haystack, needle2) {
|
|
372
|
-
if (needle2.length === 0 || needle2.length > haystack.length) {
|
|
373
|
-
return false;
|
|
374
|
-
}
|
|
375
|
-
for (let start = 0; start + needle2.length <= haystack.length; start += 1) {
|
|
376
|
-
let match = true;
|
|
377
|
-
for (let offset = 0; offset < needle2.length; offset += 1) {
|
|
378
|
-
if (haystack[start + offset] !== needle2[offset]) {
|
|
379
|
-
match = false;
|
|
380
|
-
break;
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
if (match) {
|
|
384
|
-
return true;
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
return false;
|
|
388
|
-
}
|
|
389
|
-
var isShort = (words) => words.length === 1 && [...words[0]].length < 4;
|
|
390
|
-
function neighbours(passage, position) {
|
|
391
|
-
const marker = `[${position}]`;
|
|
392
|
-
const at = passage.indexOf(marker);
|
|
393
|
-
if (at === -1) {
|
|
394
|
-
return {};
|
|
395
|
-
}
|
|
396
|
-
const strip = (text) => text.replace(/\[\d+\]/g, " ");
|
|
397
|
-
const left = fold(strip(passage.slice(0, at)));
|
|
398
|
-
const right = fold(strip(passage.slice(at + marker.length)));
|
|
399
|
-
const before = left[left.length - 1];
|
|
400
|
-
const after = right[0];
|
|
401
|
-
return {
|
|
402
|
-
...before !== void 0 ? { before } : {},
|
|
403
|
-
...after !== void 0 ? { after } : {}
|
|
404
|
-
};
|
|
405
|
-
}
|
|
406
|
-
function hintRevealsAnswer(facts, hint) {
|
|
407
|
-
const words = fold(hint);
|
|
408
|
-
const reveals = (answer, passage, position) => {
|
|
409
|
-
const needle2 = fold(answer);
|
|
410
|
-
if (needle2.length === 0) {
|
|
411
|
-
return false;
|
|
412
|
-
}
|
|
413
|
-
if (!isShort(needle2) || passage === void 0 || position === void 0) {
|
|
414
|
-
return containsWords(words, needle2);
|
|
415
|
-
}
|
|
416
|
-
const { before, after } = neighbours(passage, position);
|
|
417
|
-
if (before === void 0 && after === void 0) {
|
|
418
|
-
return containsWords(words, needle2);
|
|
419
|
-
}
|
|
420
|
-
return before !== void 0 && containsWords(words, [before, ...needle2]) || after !== void 0 && containsWords(words, [...needle2, after]);
|
|
421
|
-
};
|
|
422
|
-
switch (facts.activityType) {
|
|
423
|
-
case "multiple-choice":
|
|
424
|
-
return facts.options.some((option) => option.correct === true && reveals(option.text));
|
|
425
|
-
case "fill-in-the-blanks":
|
|
426
|
-
return facts.blanks.some(
|
|
427
|
-
(blank) => (_nullishCoalesce(blank.accepted, () => ( []))).some((answer) => reveals(answer, facts.passage, blank.position))
|
|
428
|
-
);
|
|
429
|
-
case "gap-select":
|
|
430
|
-
return facts.gaps.some(
|
|
431
|
-
(gap) => gap.answer !== null && reveals(gap.answer, facts.passage, gap.position)
|
|
432
|
-
);
|
|
433
|
-
case "dictation":
|
|
434
|
-
return false;
|
|
435
|
-
default:
|
|
436
|
-
return false;
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
// src/ai-writing.ts
|
|
441
|
-
var AI_WRITING_MAX_CORRECTIONS = 20;
|
|
442
|
-
var AI_WRITING_MAX_FIELD_LENGTH = 500;
|
|
443
|
-
var MAX_TAG_LENGTH = 40;
|
|
444
|
-
function aiWritingFeedbackRequest(input) {
|
|
445
|
-
const { data, response, learnerLocale } = input;
|
|
446
|
-
if (!aiSupports(data.type, "writing-feedback") || !aiAllowedByContent(data, "writing-feedback") || data.redacted === true) {
|
|
447
|
-
return null;
|
|
448
|
-
}
|
|
449
|
-
const text = _optionalChain([response, 'optionalAccess', _22 => _22.type]) === "written-response" ? response.text : "";
|
|
450
|
-
if (typeof text !== "string" || text.trim() === "") {
|
|
451
|
-
return null;
|
|
452
|
-
}
|
|
453
|
-
const item = data;
|
|
454
|
-
const wordCount = _chunkDSDXL3JOcjs.countWords.call(void 0, text);
|
|
455
|
-
const minWords = typeof item.minWords === "number" ? item.minWords : 0;
|
|
456
|
-
const maxWords = typeof item.maxWords === "number" ? item.maxWords : Number.POSITIVE_INFINITY;
|
|
457
|
-
const criteria = Array.isArray(_optionalChain([item, 'access', _23 => _23.rubric, 'optionalAccess', _24 => _24.criteria])) ? item.rubric.criteria : null;
|
|
458
|
-
const rubric = criteria === null || criteria.length === 0 ? null : criteria.map((criterion) => ({
|
|
459
|
-
name: criterion.name,
|
|
460
|
-
...typeof criterion.description === "string" ? { description: criterion.description } : {},
|
|
461
|
-
weight: criterion.weight
|
|
462
|
-
}));
|
|
463
|
-
const previousFeedback = [..._nullishCoalesce(input.previousFeedback, () => ( []))];
|
|
464
|
-
return {
|
|
465
|
-
feature: "writing-feedback",
|
|
466
|
-
facts: {
|
|
467
|
-
activityType: "written-response",
|
|
468
|
-
activityId: data.id,
|
|
469
|
-
title: data.title,
|
|
470
|
-
...typeof data.locale === "string" ? { locale: data.locale } : {},
|
|
471
|
-
prompt: typeof item.prompt === "string" ? item.prompt : "",
|
|
472
|
-
text,
|
|
473
|
-
wordCount,
|
|
474
|
-
minWords,
|
|
475
|
-
maxWords,
|
|
476
|
-
withinWordBounds: wordCount >= minWords && wordCount <= maxWords,
|
|
477
|
-
rubric,
|
|
478
|
-
...typeof item.languageTarget === "string" ? { languageTarget: item.languageTarget } : {}
|
|
479
|
-
},
|
|
480
|
-
draftNumber: previousFeedback.length + 1,
|
|
481
|
-
previousFeedback,
|
|
482
|
-
...learnerLocale !== void 0 ? { learnerLocale } : {}
|
|
483
|
-
};
|
|
484
|
-
}
|
|
485
|
-
var QUOTE_FOLD = {
|
|
486
|
-
"\u2018": "'",
|
|
487
|
-
"\u2019": "'",
|
|
488
|
-
"\u201B": "'",
|
|
489
|
-
"\u2032": "'",
|
|
490
|
-
"\u201C": '"',
|
|
491
|
-
"\u201D": '"',
|
|
492
|
-
"\u201E": '"',
|
|
493
|
-
"\u2033": '"'
|
|
494
|
-
};
|
|
495
|
-
function folded(text) {
|
|
496
|
-
let chars = "";
|
|
497
|
-
const from = [];
|
|
498
|
-
let space = false;
|
|
499
|
-
for (let at = 0; at < text.length; at += 1) {
|
|
500
|
-
const char = text[at];
|
|
501
|
-
if (/\s/u.test(char)) {
|
|
502
|
-
if (!space) {
|
|
503
|
-
chars += " ";
|
|
504
|
-
from.push(at);
|
|
505
|
-
space = true;
|
|
506
|
-
}
|
|
507
|
-
continue;
|
|
508
|
-
}
|
|
509
|
-
space = false;
|
|
510
|
-
chars += _nullishCoalesce(QUOTE_FOLD[char], () => ( char));
|
|
511
|
-
from.push(at);
|
|
512
|
-
}
|
|
513
|
-
return { chars, from };
|
|
514
|
-
}
|
|
515
|
-
var needle = (quote) => folded(quote).chars.trim();
|
|
516
|
-
function anchor(draft, quote, claimed, taken) {
|
|
517
|
-
const sought = needle(quote);
|
|
518
|
-
if (sought === "") {
|
|
519
|
-
return null;
|
|
520
|
-
}
|
|
521
|
-
if (claimed !== void 0) {
|
|
522
|
-
const { start, end } = claimed;
|
|
523
|
-
if (!Number.isSafeInteger(start) || !Number.isSafeInteger(end) || start < 0 || end > draft.text.length || start >= end) {
|
|
524
|
-
return null;
|
|
525
|
-
}
|
|
526
|
-
const span = draft.text.slice(start, end);
|
|
527
|
-
if (needle(span) !== sought) {
|
|
528
|
-
return null;
|
|
529
|
-
}
|
|
530
|
-
const lead = span.length - span.trimStart().length;
|
|
531
|
-
const trail = span.length - span.trimEnd().length;
|
|
532
|
-
return { start: start + lead, end: end - trail };
|
|
533
|
-
}
|
|
534
|
-
let first = null;
|
|
535
|
-
for (let at = draft.chars.indexOf(sought); at !== -1; at = draft.chars.indexOf(sought, at + 1)) {
|
|
536
|
-
const start = draft.from[at];
|
|
537
|
-
const end = draft.from[at + sought.length - 1] + 1;
|
|
538
|
-
const found = { start, end };
|
|
539
|
-
first ??= found;
|
|
540
|
-
const key = `${start}:${end}`;
|
|
541
|
-
if (!taken.has(key)) {
|
|
542
|
-
taken.add(key);
|
|
543
|
-
return found;
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
|
-
return first;
|
|
547
|
-
}
|
|
548
|
-
function textField(value, max) {
|
|
549
|
-
if (value === void 0 || value === null) {
|
|
550
|
-
return { ok: true, value: void 0 };
|
|
551
|
-
}
|
|
552
|
-
if (typeof value !== "string") {
|
|
553
|
-
return { ok: false, refusal: "malformed" };
|
|
554
|
-
}
|
|
555
|
-
const cleaned = cleanText(value);
|
|
556
|
-
if ([...cleaned].length > max) {
|
|
557
|
-
return { ok: false, refusal: "too-long" };
|
|
558
|
-
}
|
|
559
|
-
return { ok: true, value: cleaned === "" ? void 0 : cleaned };
|
|
560
|
-
}
|
|
561
|
-
var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
562
|
-
function checkCorrections(raw, text) {
|
|
563
|
-
if (raw === void 0 || raw === null) {
|
|
564
|
-
return { ok: true, corrections: [] };
|
|
565
|
-
}
|
|
566
|
-
if (!Array.isArray(raw)) {
|
|
567
|
-
return { ok: false, refusal: "malformed" };
|
|
568
|
-
}
|
|
569
|
-
if (raw.length > AI_WRITING_MAX_CORRECTIONS) {
|
|
570
|
-
return { ok: false, refusal: "too-long" };
|
|
571
|
-
}
|
|
572
|
-
const draft = { text, ...folded(text) };
|
|
573
|
-
const taken = /* @__PURE__ */ new Set();
|
|
574
|
-
const corrections = [];
|
|
575
|
-
for (const entry of raw) {
|
|
576
|
-
if (!isRecord(entry) || typeof entry.original !== "string") {
|
|
577
|
-
return { ok: false, refusal: "malformed" };
|
|
578
|
-
}
|
|
579
|
-
if (typeof entry.corrected !== "string") {
|
|
580
|
-
return { ok: false, refusal: "malformed" };
|
|
581
|
-
}
|
|
582
|
-
if ([...entry.original].length > AI_WRITING_MAX_FIELD_LENGTH || [...entry.corrected].length > AI_WRITING_MAX_FIELD_LENGTH) {
|
|
583
|
-
return { ok: false, refusal: "too-long" };
|
|
584
|
-
}
|
|
585
|
-
if (needle(entry.original) === needle(entry.corrected)) {
|
|
586
|
-
return { ok: false, refusal: "malformed" };
|
|
587
|
-
}
|
|
588
|
-
const explanation = textField(entry.explanation, AI_WRITING_MAX_FIELD_LENGTH);
|
|
589
|
-
const category = textField(entry.category, MAX_TAG_LENGTH);
|
|
590
|
-
if (!explanation.ok) {
|
|
591
|
-
return explanation;
|
|
592
|
-
}
|
|
593
|
-
if (!category.ok) {
|
|
594
|
-
return category;
|
|
595
|
-
}
|
|
596
|
-
let claimed;
|
|
597
|
-
if (entry.range !== void 0 && entry.range !== null) {
|
|
598
|
-
if (!isRecord(entry.range)) {
|
|
599
|
-
return { ok: false, refusal: "malformed" };
|
|
600
|
-
}
|
|
601
|
-
claimed = { start: entry.range.start, end: entry.range.end };
|
|
602
|
-
}
|
|
603
|
-
const at = anchor(draft, entry.original, claimed, taken);
|
|
604
|
-
if (at === null) {
|
|
605
|
-
return { ok: false, refusal: "misquotes-answer" };
|
|
606
|
-
}
|
|
607
|
-
corrections.push({
|
|
608
|
-
original: text.slice(at.start, at.end),
|
|
609
|
-
corrected: cleanText(entry.corrected),
|
|
610
|
-
...explanation.value !== void 0 ? { explanation: explanation.value } : {},
|
|
611
|
-
...category.value !== void 0 ? { category: category.value } : {},
|
|
612
|
-
range: at
|
|
613
|
-
});
|
|
614
|
-
}
|
|
615
|
-
return { ok: true, corrections };
|
|
616
|
-
}
|
|
617
|
-
function isJudgedScore(score2, maxScore) {
|
|
618
|
-
if (score2 === void 0) {
|
|
619
|
-
return maxScore === void 0 || typeof maxScore === "number" && maxScore > 0;
|
|
620
|
-
}
|
|
621
|
-
const max = _nullishCoalesce(maxScore, () => ( 1));
|
|
622
|
-
return typeof score2 === "number" && typeof max === "number" && Number.isFinite(score2) && Number.isFinite(max) && max > 0 && score2 >= 0 && score2 - max <= max * 1e-9;
|
|
623
|
-
}
|
|
624
|
-
function checkCriteria(raw, rubric) {
|
|
625
|
-
if (raw === void 0 || raw === null) {
|
|
626
|
-
return { ok: true, criteria: [] };
|
|
627
|
-
}
|
|
628
|
-
if (!Array.isArray(raw)) {
|
|
629
|
-
return { ok: false, refusal: "malformed" };
|
|
630
|
-
}
|
|
631
|
-
if (raw.length === 0) {
|
|
632
|
-
return { ok: true, criteria: [] };
|
|
633
|
-
}
|
|
634
|
-
if (rubric === null) {
|
|
635
|
-
return { ok: false, refusal: "malformed" };
|
|
636
|
-
}
|
|
637
|
-
const weights = new Map(rubric.map((criterion) => [criterion.name, criterion.weight]));
|
|
638
|
-
const seen = /* @__PURE__ */ new Set();
|
|
639
|
-
const criteria = [];
|
|
640
|
-
for (const entry of raw) {
|
|
641
|
-
if (!isRecord(entry) || typeof entry.name !== "string") {
|
|
642
|
-
return { ok: false, refusal: "malformed" };
|
|
643
|
-
}
|
|
644
|
-
const weight = weights.get(entry.name);
|
|
645
|
-
if (weight === void 0 || seen.has(entry.name)) {
|
|
646
|
-
return { ok: false, refusal: "malformed" };
|
|
647
|
-
}
|
|
648
|
-
seen.add(entry.name);
|
|
649
|
-
if (!isJudgedScore(entry.score, entry.maxScore)) {
|
|
650
|
-
return { ok: false, refusal: "malformed" };
|
|
651
|
-
}
|
|
652
|
-
if (entry.notApplicable !== void 0 && typeof entry.notApplicable !== "boolean") {
|
|
653
|
-
return { ok: false, refusal: "malformed" };
|
|
654
|
-
}
|
|
655
|
-
const band = textField(entry.band, MAX_TAG_LENGTH);
|
|
656
|
-
const comment = textField(entry.comment, AI_WRITING_MAX_FIELD_LENGTH);
|
|
657
|
-
if (!band.ok) {
|
|
658
|
-
return band;
|
|
659
|
-
}
|
|
660
|
-
if (!comment.ok) {
|
|
661
|
-
return comment;
|
|
662
|
-
}
|
|
663
|
-
criteria.push({
|
|
664
|
-
name: entry.name,
|
|
665
|
-
...typeof entry.score === "number" ? { score: entry.score } : {},
|
|
666
|
-
...typeof entry.maxScore === "number" ? { maxScore: entry.maxScore } : {},
|
|
667
|
-
...band.value !== void 0 ? { band: band.value } : {},
|
|
668
|
-
...comment.value !== void 0 ? { comment: comment.value } : {},
|
|
669
|
-
...entry.notApplicable === true ? { notApplicable: true } : {},
|
|
670
|
-
weight
|
|
671
|
-
});
|
|
672
|
-
}
|
|
673
|
-
return { ok: true, criteria };
|
|
674
|
-
}
|
|
675
|
-
function checkAiWritingFeedback(raw, request) {
|
|
676
|
-
const read = readAiTextResult(raw);
|
|
677
|
-
if (!read.ok) {
|
|
678
|
-
return read;
|
|
679
|
-
}
|
|
680
|
-
const record = raw;
|
|
681
|
-
const corrections = checkCorrections(record.corrections, request.facts.text);
|
|
682
|
-
if (!corrections.ok) {
|
|
683
|
-
return corrections;
|
|
684
|
-
}
|
|
685
|
-
const rubric = request.facts.rubric;
|
|
686
|
-
const criteria = checkCriteria(record.criteria, rubric);
|
|
687
|
-
if (!criteria.ok) {
|
|
688
|
-
return criteria;
|
|
689
|
-
}
|
|
690
|
-
let indicativeScore = null;
|
|
691
|
-
const judged = new Set(criteria.criteria.map((criterion) => criterion.name));
|
|
692
|
-
if (_optionalChain([rubric, 'optionalAccess', _25 => _25.every, 'call', _26 => _26((criterion) => judged.has(criterion.name))])) {
|
|
693
|
-
const grade = _chunkVSH7ECQLcjs.gradeFromRubric.call(void 0, criteria.criteria);
|
|
694
|
-
indicativeScore = "unscorable" in grade ? null : grade.score;
|
|
695
|
-
}
|
|
696
|
-
const { text, provenance, usage } = read.result;
|
|
697
|
-
return {
|
|
698
|
-
ok: true,
|
|
699
|
-
feedback: {
|
|
700
|
-
text,
|
|
701
|
-
corrections: corrections.corrections,
|
|
702
|
-
criteria: criteria.criteria,
|
|
703
|
-
indicativeScore,
|
|
704
|
-
...provenance !== void 0 ? { provenance } : {},
|
|
705
|
-
...usage !== void 0 ? { usage } : {}
|
|
706
|
-
}
|
|
707
|
-
};
|
|
708
|
-
}
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
exports.AI_EXPLANATION_TYPES = AI_EXPLANATION_TYPES; exports.AI_HINT_TYPES = AI_HINT_TYPES; exports.AI_TEXT_MAX_LENGTH = AI_TEXT_MAX_LENGTH; exports.aiSupports = aiSupports; exports.aiAllowedByContent = aiAllowedByContent; exports.aiGradeOf = aiGradeOf; exports.buildAiFacts = buildAiFacts; exports.aiExplanationRequest = aiExplanationRequest; exports.aiHintRequest = aiHintRequest; exports.readAiTextResult = readAiTextResult; exports.checkAiExplanation = checkAiExplanation; exports.checkAiHint = checkAiHint; exports.hintRevealsAnswer = hintRevealsAnswer; exports.AI_WRITING_MAX_CORRECTIONS = AI_WRITING_MAX_CORRECTIONS; exports.AI_WRITING_MAX_FIELD_LENGTH = AI_WRITING_MAX_FIELD_LENGTH; exports.aiWritingFeedbackRequest = aiWritingFeedbackRequest; exports.checkAiWritingFeedback = checkAiWritingFeedback;
|
|
729
|
-
//# sourceMappingURL=chunk-W5KB3DNT.cjs.map
|