@intellectif/lk-core 1.1.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 +15 -0
- package/README.md +2 -1
- package/dist/{activity-MBOYLBjR.d.cts → activity-D705CXng.d.cts} +324 -2
- package/dist/{activity-MBOYLBjR.d.ts → activity-D705CXng.d.ts} +324 -2
- package/dist/ai-check.cjs +174 -12
- package/dist/ai-check.cjs.map +1 -1
- package/dist/ai-check.d.cts +15 -3
- package/dist/ai-check.d.ts +15 -3
- package/dist/ai-check.js +166 -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-DlcqkM_L.d.ts → index-BChYtrEe.d.ts} +1 -1
- package/dist/{index-DlCW0XaQ.d.cts → index-VXt_v3YI.d.cts} +1 -1
- package/dist/index.cjs +68 -593
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +245 -60
- package/dist/index.d.ts +245 -60
- package/dist/index.js +68 -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 +2 -2
- package/dist/scoring.d.ts +2 -2
- 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-LDFQUZII.cjs +0 -950
- package/dist/chunk-LDFQUZII.cjs.map +0 -1
- package/dist/chunk-NQHETFDA.js +0 -950
- package/dist/chunk-NQHETFDA.js.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-LDFQUZII.cjs
DELETED
|
@@ -1,950 +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
|
-
|
|
6
|
-
|
|
7
|
-
var _chunkVSH7ECQLcjs = require('./chunk-VSH7ECQL.cjs');
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
var _chunkDSDXL3JOcjs = require('./chunk-DSDXL3JO.cjs');
|
|
13
|
-
|
|
14
|
-
// src/ai-text.ts
|
|
15
|
-
function cleanText(text) {
|
|
16
|
-
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();
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// src/ai.ts
|
|
20
|
-
var AI_EXPLANATION_TYPES = [
|
|
21
|
-
"multiple-choice",
|
|
22
|
-
"fill-in-the-blanks",
|
|
23
|
-
"gap-select",
|
|
24
|
-
"dictation"
|
|
25
|
-
];
|
|
26
|
-
var AI_HINT_TYPES = [
|
|
27
|
-
"multiple-choice",
|
|
28
|
-
"fill-in-the-blanks",
|
|
29
|
-
"gap-select"
|
|
30
|
-
];
|
|
31
|
-
var AI_TEXT_MAX_LENGTH = 2e3;
|
|
32
|
-
function aiSupports(activityType, feature) {
|
|
33
|
-
if (feature === "writing-feedback") {
|
|
34
|
-
return activityType === "written-response";
|
|
35
|
-
}
|
|
36
|
-
if (feature === "pronunciation-coaching") {
|
|
37
|
-
return activityType === "read-aloud";
|
|
38
|
-
}
|
|
39
|
-
const types = feature === "hint" ? AI_HINT_TYPES : AI_EXPLANATION_TYPES;
|
|
40
|
-
return types.includes(activityType);
|
|
41
|
-
}
|
|
42
|
-
function aiAllowedByContent(data, feature) {
|
|
43
|
-
const permissions = data.ai;
|
|
44
|
-
if (typeof permissions !== "object" || permissions === null) {
|
|
45
|
-
return true;
|
|
46
|
-
}
|
|
47
|
-
const key = feature === "hint" ? "hints" : "explanations";
|
|
48
|
-
return permissions[key] !== false;
|
|
49
|
-
}
|
|
50
|
-
function aiGradeOf(result) {
|
|
51
|
-
const ratio = result.maxScore > 0 ? result.score / result.maxScore : 0;
|
|
52
|
-
let category = "partly-correct";
|
|
53
|
-
if (ratio >= 1 - 1e-9) {
|
|
54
|
-
category = "correct";
|
|
55
|
-
} else if (ratio <= 1e-9) {
|
|
56
|
-
category = "incorrect";
|
|
57
|
-
}
|
|
58
|
-
return { score: result.score, maxScore: result.maxScore, passed: result.passed, category };
|
|
59
|
-
}
|
|
60
|
-
function placeholderOrder(passage) {
|
|
61
|
-
return [...passage.matchAll(_chunkDSDXL3JOcjs.PLACEHOLDER_RE)].map((match) => match[1]);
|
|
62
|
-
}
|
|
63
|
-
function numberedPassage(passage) {
|
|
64
|
-
let position = 0;
|
|
65
|
-
return passage.replace(_chunkDSDXL3JOcjs.PLACEHOLDER_RE, () => {
|
|
66
|
-
position += 1;
|
|
67
|
-
return `[${position}]`;
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
var legacyCorrect = (detail) => detail.correct === true;
|
|
71
|
-
function detailRight(detail) {
|
|
72
|
-
const outcome = detail.outcome;
|
|
73
|
-
return outcome === void 0 ? legacyCorrect(detail) : outcome === "correct";
|
|
74
|
-
}
|
|
75
|
-
function isAnswerOption(detail, chosen) {
|
|
76
|
-
switch (detail.outcome) {
|
|
77
|
-
case "correct":
|
|
78
|
-
case "incorrect-omission":
|
|
79
|
-
return true;
|
|
80
|
-
case "incorrect":
|
|
81
|
-
case "correct-omission":
|
|
82
|
-
return false;
|
|
83
|
-
default:
|
|
84
|
-
return chosen ? legacyCorrect(detail) : !legacyCorrect(detail);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
var firstString = (value) => Array.isArray(value) ? value[0] : value;
|
|
88
|
-
var listOf = (value) => value === void 0 ? null : Array.isArray(value) ? [...value] : [value];
|
|
89
|
-
function emptyResponse(type) {
|
|
90
|
-
switch (type) {
|
|
91
|
-
case "multiple-choice":
|
|
92
|
-
return { type: "multiple-choice", selectedOptionIds: [] };
|
|
93
|
-
case "fill-in-the-blanks":
|
|
94
|
-
return { type: "fill-in-the-blanks", answers: {} };
|
|
95
|
-
case "gap-select":
|
|
96
|
-
return { type: "gap-select", selections: {} };
|
|
97
|
-
case "dictation":
|
|
98
|
-
return { type: "dictation", text: "" };
|
|
99
|
-
default:
|
|
100
|
-
return null;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
var base = (data) => ({
|
|
104
|
-
activityId: data.id,
|
|
105
|
-
title: data.title,
|
|
106
|
-
...typeof data.locale === "string" ? { locale: data.locale } : {}
|
|
107
|
-
});
|
|
108
|
-
function buildAiFacts(data, response, details) {
|
|
109
|
-
const byId = new Map((_nullishCoalesce(details, () => ( []))).map((detail) => [detail.itemId, detail]));
|
|
110
|
-
const full = data.redacted !== true;
|
|
111
|
-
switch (data.type) {
|
|
112
|
-
case "multiple-choice": {
|
|
113
|
-
const item = data;
|
|
114
|
-
const chosen = new Set(
|
|
115
|
-
_optionalChain([response, 'optionalAccess', _ => _.type]) === "multiple-choice" ? response.selectedOptionIds : []
|
|
116
|
-
);
|
|
117
|
-
return {
|
|
118
|
-
...base(data),
|
|
119
|
-
activityType: "multiple-choice",
|
|
120
|
-
question: item.question,
|
|
121
|
-
mode: item.mode,
|
|
122
|
-
options: item.options.map((option) => {
|
|
123
|
-
const detail = byId.get(option.id);
|
|
124
|
-
const wasChosen = chosen.has(option.id);
|
|
125
|
-
let correct = null;
|
|
126
|
-
if (full && typeof option.isCorrect === "boolean") {
|
|
127
|
-
correct = option.isCorrect;
|
|
128
|
-
} else if (detail !== void 0) {
|
|
129
|
-
correct = isAnswerOption(detail, wasChosen);
|
|
130
|
-
}
|
|
131
|
-
return {
|
|
132
|
-
id: option.id,
|
|
133
|
-
text: option.text,
|
|
134
|
-
chosen: wasChosen,
|
|
135
|
-
correct,
|
|
136
|
-
...typeof option.feedback === "string" ? { feedback: option.feedback } : {}
|
|
137
|
-
};
|
|
138
|
-
})
|
|
139
|
-
};
|
|
140
|
-
}
|
|
141
|
-
case "fill-in-the-blanks": {
|
|
142
|
-
const item = data;
|
|
143
|
-
const answers = _optionalChain([response, 'optionalAccess', _2 => _2.type]) === "fill-in-the-blanks" ? response.answers : {};
|
|
144
|
-
const blanks = new Map(item.blanks.map((blank) => [blank.id, blank]));
|
|
145
|
-
return {
|
|
146
|
-
...base(data),
|
|
147
|
-
activityType: "fill-in-the-blanks",
|
|
148
|
-
passage: numberedPassage(item.passage),
|
|
149
|
-
blanks: placeholderOrder(item.passage).map((id, index) => {
|
|
150
|
-
const blank = blanks.get(id);
|
|
151
|
-
const detail = byId.get(id);
|
|
152
|
-
const accepted = full && Array.isArray(_optionalChain([blank, 'optionalAccess', _3 => _3.acceptedAnswers])) ? [...blank.acceptedAnswers] : listOf(_optionalChain([detail, 'optionalAccess', _4 => _4.correctResponse]));
|
|
153
|
-
return {
|
|
154
|
-
id,
|
|
155
|
-
position: index + 1,
|
|
156
|
-
typed: typeof answers[id] === "string" ? answers[id] : "",
|
|
157
|
-
accepted,
|
|
158
|
-
correct: detail === void 0 ? null : detailRight(detail),
|
|
159
|
-
...typeof _optionalChain([blank, 'optionalAccess', _5 => _5.hint]) === "string" ? { hint: blank.hint } : {},
|
|
160
|
-
...typeof _optionalChain([blank, 'optionalAccess', _6 => _6.feedback]) === "string" ? { feedback: blank.feedback } : {}
|
|
161
|
-
};
|
|
162
|
-
})
|
|
163
|
-
};
|
|
164
|
-
}
|
|
165
|
-
case "gap-select": {
|
|
166
|
-
const item = data;
|
|
167
|
-
const selections = _optionalChain([response, 'optionalAccess', _7 => _7.type]) === "gap-select" ? response.selections : {};
|
|
168
|
-
const gaps = new Map(item.gaps.map((gap) => [gap.id, gap]));
|
|
169
|
-
const banks = new Map((_nullishCoalesce(item.banks, () => ( []))).map((bank) => [bank.id, bank]));
|
|
170
|
-
return {
|
|
171
|
-
...base(data),
|
|
172
|
-
activityType: "gap-select",
|
|
173
|
-
passage: numberedPassage(item.passage),
|
|
174
|
-
gaps: placeholderOrder(item.passage).map((id, index) => {
|
|
175
|
-
const gap = gaps.get(id);
|
|
176
|
-
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]), () => ( [])))));
|
|
177
|
-
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));
|
|
178
|
-
const detail = byId.get(id);
|
|
179
|
-
const answerId = full && typeof _optionalChain([gap, 'optionalAccess', _16 => _16.correctChoiceId]) === "string" ? gap.correctChoiceId : firstString(_optionalChain([detail, 'optionalAccess', _17 => _17.correctResponse]));
|
|
180
|
-
return {
|
|
181
|
-
id,
|
|
182
|
-
position: index + 1,
|
|
183
|
-
choices: choices.map((choice) => choice.text),
|
|
184
|
-
chosen: textOf(selections[id]),
|
|
185
|
-
answer: textOf(answerId),
|
|
186
|
-
correct: detail === void 0 ? null : detailRight(detail),
|
|
187
|
-
...typeof _optionalChain([gap, 'optionalAccess', _18 => _18.feedback]) === "string" ? { feedback: gap.feedback } : {}
|
|
188
|
-
};
|
|
189
|
-
})
|
|
190
|
-
};
|
|
191
|
-
}
|
|
192
|
-
case "dictation": {
|
|
193
|
-
const item = data;
|
|
194
|
-
const typed = _optionalChain([response, 'optionalAccess', _19 => _19.type]) === "dictation" ? response.text : "";
|
|
195
|
-
const hasKey = full && typeof item.transcript === "string";
|
|
196
|
-
let words = null;
|
|
197
|
-
if (hasKey) {
|
|
198
|
-
words = _chunkDSDXL3JOcjs.alignDictation.call(void 0, item, typed).words.map((word) => ({
|
|
199
|
-
expected: word.reference,
|
|
200
|
-
typed: word.attempt,
|
|
201
|
-
status: word.status
|
|
202
|
-
}));
|
|
203
|
-
} else if (details !== null && details.length > 0) {
|
|
204
|
-
words = details.map((detail) => {
|
|
205
|
-
const expected = _nullishCoalesce(firstString(detail.correctResponse), () => ( ""));
|
|
206
|
-
const attempt = _nullishCoalesce(firstString(detail.learnerResponse), () => ( ""));
|
|
207
|
-
let status = "incorrect";
|
|
208
|
-
if (detailRight(detail)) {
|
|
209
|
-
status = "correct";
|
|
210
|
-
} else if (attempt === "") {
|
|
211
|
-
status = "missing";
|
|
212
|
-
} else if (expected === "") {
|
|
213
|
-
status = "extra";
|
|
214
|
-
}
|
|
215
|
-
return { expected, typed: attempt, status };
|
|
216
|
-
});
|
|
217
|
-
}
|
|
218
|
-
return {
|
|
219
|
-
...base(data),
|
|
220
|
-
activityType: "dictation",
|
|
221
|
-
transcript: hasKey ? item.transcript : null,
|
|
222
|
-
typed,
|
|
223
|
-
words
|
|
224
|
-
};
|
|
225
|
-
}
|
|
226
|
-
default:
|
|
227
|
-
return null;
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
function gradeLocally(data, response) {
|
|
231
|
-
if (data.redacted === true) {
|
|
232
|
-
return null;
|
|
233
|
-
}
|
|
234
|
-
try {
|
|
235
|
-
const result = _chunkVSH7ECQLcjs.score.call(void 0, data.type, data, response);
|
|
236
|
-
return { details: result.details, grade: aiGradeOf(result) };
|
|
237
|
-
} catch (e) {
|
|
238
|
-
return null;
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
function aiExplanationRequest(input) {
|
|
242
|
-
const { data, response, outcome, learnerLocale } = input;
|
|
243
|
-
if (!aiSupports(data.type, "explanation") || !aiAllowedByContent(data, "explanation")) {
|
|
244
|
-
return null;
|
|
245
|
-
}
|
|
246
|
-
let details = null;
|
|
247
|
-
let grade = null;
|
|
248
|
-
if (_optionalChain([outcome, 'optionalAccess', _20 => _20.status]) === "scored") {
|
|
249
|
-
const { score: recorded, maxScore, passed } = outcome;
|
|
250
|
-
if (!_chunkVSH7ECQLcjs.isGradeInRange.call(void 0, recorded, maxScore)) {
|
|
251
|
-
return null;
|
|
252
|
-
}
|
|
253
|
-
details = outcome.details;
|
|
254
|
-
grade = aiGradeOf({ score: recorded, maxScore, passed });
|
|
255
|
-
} else if (response !== null) {
|
|
256
|
-
const local = gradeLocally(data, response);
|
|
257
|
-
if (local !== null) {
|
|
258
|
-
details = local.details;
|
|
259
|
-
grade = local.grade;
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
if (grade === null) {
|
|
263
|
-
return null;
|
|
264
|
-
}
|
|
265
|
-
const facts = buildAiFacts(data, response, details);
|
|
266
|
-
if (facts === null) {
|
|
267
|
-
return null;
|
|
268
|
-
}
|
|
269
|
-
return {
|
|
270
|
-
feature: "explanation",
|
|
271
|
-
facts,
|
|
272
|
-
grade,
|
|
273
|
-
...learnerLocale !== void 0 ? { learnerLocale } : {}
|
|
274
|
-
};
|
|
275
|
-
}
|
|
276
|
-
function aiHintRequest(input) {
|
|
277
|
-
const { data, previousHints, learnerLocale } = input;
|
|
278
|
-
if (!aiSupports(data.type, "hint") || !aiAllowedByContent(data, "hint") || data.redacted === true) {
|
|
279
|
-
return null;
|
|
280
|
-
}
|
|
281
|
-
const response = _nullishCoalesce(input.response, () => ( emptyResponse(data.type)));
|
|
282
|
-
const local = response === null ? null : gradeLocally(data, response);
|
|
283
|
-
const facts = buildAiFacts(data, response, _nullishCoalesce(_optionalChain([local, 'optionalAccess', _21 => _21.details]), () => ( null)));
|
|
284
|
-
if (facts === null) {
|
|
285
|
-
return null;
|
|
286
|
-
}
|
|
287
|
-
return {
|
|
288
|
-
feature: "hint",
|
|
289
|
-
facts,
|
|
290
|
-
hintNumber: previousHints.length + 1,
|
|
291
|
-
previousHints: [...previousHints],
|
|
292
|
-
...learnerLocale !== void 0 ? { learnerLocale } : {}
|
|
293
|
-
};
|
|
294
|
-
}
|
|
295
|
-
var VERDICTS = ["correct", "partly-correct", "incorrect"];
|
|
296
|
-
function provenanceOf(raw) {
|
|
297
|
-
if (typeof raw !== "object" || raw === null) {
|
|
298
|
-
return void 0;
|
|
299
|
-
}
|
|
300
|
-
const kept = {};
|
|
301
|
-
for (const key of ["model", "promptHash", "generatedAt"]) {
|
|
302
|
-
const value = raw[key];
|
|
303
|
-
if (typeof value === "string" && value.length > 0 && value.length <= 200) {
|
|
304
|
-
kept[key] = value;
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
return Object.keys(kept).length > 0 ? kept : void 0;
|
|
308
|
-
}
|
|
309
|
-
function usageOf(raw) {
|
|
310
|
-
if (typeof raw !== "object" || raw === null) {
|
|
311
|
-
return void 0;
|
|
312
|
-
}
|
|
313
|
-
const kept = {};
|
|
314
|
-
for (const key of ["promptTokens", "completionTokens", "costUsd"]) {
|
|
315
|
-
const value = raw[key];
|
|
316
|
-
if (typeof value === "number" && Number.isFinite(value) && value >= 0) {
|
|
317
|
-
kept[key] = value;
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
return Object.keys(kept).length > 0 ? kept : void 0;
|
|
321
|
-
}
|
|
322
|
-
function readAiTextResult(raw) {
|
|
323
|
-
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
324
|
-
return { ok: false, refusal: "malformed" };
|
|
325
|
-
}
|
|
326
|
-
const record = raw;
|
|
327
|
-
if (typeof record.text !== "string") {
|
|
328
|
-
return { ok: false, refusal: "malformed" };
|
|
329
|
-
}
|
|
330
|
-
if (record.verdict !== void 0 && !VERDICTS.includes(record.verdict)) {
|
|
331
|
-
return { ok: false, refusal: "malformed" };
|
|
332
|
-
}
|
|
333
|
-
const text = cleanText(record.text);
|
|
334
|
-
if (text === "") {
|
|
335
|
-
return { ok: false, refusal: "empty" };
|
|
336
|
-
}
|
|
337
|
-
if ([...text].length > AI_TEXT_MAX_LENGTH) {
|
|
338
|
-
return { ok: false, refusal: "too-long" };
|
|
339
|
-
}
|
|
340
|
-
const provenance = provenanceOf(record.provenance);
|
|
341
|
-
const usage = usageOf(record.usage);
|
|
342
|
-
return {
|
|
343
|
-
ok: true,
|
|
344
|
-
result: {
|
|
345
|
-
text,
|
|
346
|
-
...record.verdict !== void 0 ? { verdict: record.verdict } : {},
|
|
347
|
-
...provenance !== void 0 ? { provenance } : {},
|
|
348
|
-
...usage !== void 0 ? { usage } : {}
|
|
349
|
-
}
|
|
350
|
-
};
|
|
351
|
-
}
|
|
352
|
-
function checkAiExplanation(raw, request) {
|
|
353
|
-
const read = readAiTextResult(raw);
|
|
354
|
-
if (!read.ok) {
|
|
355
|
-
return read;
|
|
356
|
-
}
|
|
357
|
-
if (read.result.verdict !== void 0 && read.result.verdict !== request.grade.category) {
|
|
358
|
-
return { ok: false, refusal: "contradicts-grade" };
|
|
359
|
-
}
|
|
360
|
-
return read;
|
|
361
|
-
}
|
|
362
|
-
function checkAiHint(raw, request) {
|
|
363
|
-
const read = readAiTextResult(raw);
|
|
364
|
-
if (!read.ok) {
|
|
365
|
-
return read;
|
|
366
|
-
}
|
|
367
|
-
if (hintRevealsAnswer(request.facts, read.result.text)) {
|
|
368
|
-
return { ok: false, refusal: "reveals-answer" };
|
|
369
|
-
}
|
|
370
|
-
return read;
|
|
371
|
-
}
|
|
372
|
-
function fold(text) {
|
|
373
|
-
const folded2 = text.normalize("NFKD").replace(/\p{M}/gu, "").toLowerCase().replace(/[^\p{L}\p{N}]+/gu, " ").trim();
|
|
374
|
-
return folded2 === "" ? [] : folded2.split(" ");
|
|
375
|
-
}
|
|
376
|
-
function containsWords(haystack, needle2) {
|
|
377
|
-
if (needle2.length === 0 || needle2.length > haystack.length) {
|
|
378
|
-
return false;
|
|
379
|
-
}
|
|
380
|
-
for (let start = 0; start + needle2.length <= haystack.length; start += 1) {
|
|
381
|
-
let match = true;
|
|
382
|
-
for (let offset = 0; offset < needle2.length; offset += 1) {
|
|
383
|
-
if (haystack[start + offset] !== needle2[offset]) {
|
|
384
|
-
match = false;
|
|
385
|
-
break;
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
if (match) {
|
|
389
|
-
return true;
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
return false;
|
|
393
|
-
}
|
|
394
|
-
var isShort = (words) => words.length === 1 && [...words[0]].length < 4;
|
|
395
|
-
function neighbours(passage, position) {
|
|
396
|
-
const marker = `[${position}]`;
|
|
397
|
-
const at = passage.indexOf(marker);
|
|
398
|
-
if (at === -1) {
|
|
399
|
-
return {};
|
|
400
|
-
}
|
|
401
|
-
const strip = (text) => text.replace(/\[\d+\]/g, " ");
|
|
402
|
-
const left = fold(strip(passage.slice(0, at)));
|
|
403
|
-
const right = fold(strip(passage.slice(at + marker.length)));
|
|
404
|
-
const before = left[left.length - 1];
|
|
405
|
-
const after = right[0];
|
|
406
|
-
return {
|
|
407
|
-
...before !== void 0 ? { before } : {},
|
|
408
|
-
...after !== void 0 ? { after } : {}
|
|
409
|
-
};
|
|
410
|
-
}
|
|
411
|
-
function hintRevealsAnswer(facts, hint) {
|
|
412
|
-
const words = fold(hint);
|
|
413
|
-
const reveals = (answer, passage, position) => {
|
|
414
|
-
const needle2 = fold(answer);
|
|
415
|
-
if (needle2.length === 0) {
|
|
416
|
-
return false;
|
|
417
|
-
}
|
|
418
|
-
if (!isShort(needle2) || passage === void 0 || position === void 0) {
|
|
419
|
-
return containsWords(words, needle2);
|
|
420
|
-
}
|
|
421
|
-
const { before, after } = neighbours(passage, position);
|
|
422
|
-
if (before === void 0 && after === void 0) {
|
|
423
|
-
return containsWords(words, needle2);
|
|
424
|
-
}
|
|
425
|
-
return before !== void 0 && containsWords(words, [before, ...needle2]) || after !== void 0 && containsWords(words, [...needle2, after]);
|
|
426
|
-
};
|
|
427
|
-
switch (facts.activityType) {
|
|
428
|
-
case "multiple-choice":
|
|
429
|
-
return facts.options.some((option) => option.correct === true && reveals(option.text));
|
|
430
|
-
case "fill-in-the-blanks":
|
|
431
|
-
return facts.blanks.some(
|
|
432
|
-
(blank) => (_nullishCoalesce(blank.accepted, () => ( []))).some((answer) => reveals(answer, facts.passage, blank.position))
|
|
433
|
-
);
|
|
434
|
-
case "gap-select":
|
|
435
|
-
return facts.gaps.some(
|
|
436
|
-
(gap) => gap.answer !== null && reveals(gap.answer, facts.passage, gap.position)
|
|
437
|
-
);
|
|
438
|
-
case "dictation":
|
|
439
|
-
return false;
|
|
440
|
-
default:
|
|
441
|
-
return false;
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
// src/ai-coaching.ts
|
|
446
|
-
var AI_COACHING_MAX_WORDS = 20;
|
|
447
|
-
var AI_COACHING_MAX_TIP_LENGTH = 500;
|
|
448
|
-
var MAX_SYMBOL_LENGTH = 16;
|
|
449
|
-
var DIMENSIONS = ["accuracy", "fluency", "completeness", "prosody"];
|
|
450
|
-
var hundredths = (value) => Math.round(value * 1e4) / 100;
|
|
451
|
-
var isFiniteNumber = (value) => typeof value === "number" && Number.isFinite(value);
|
|
452
|
-
function soundsOf(word) {
|
|
453
|
-
const sounds = [];
|
|
454
|
-
for (const phoneme of _nullishCoalesce(_optionalChain([word, 'optionalAccess', _22 => _22.phonemes]), () => ( []))) {
|
|
455
|
-
if (typeof phoneme.symbol !== "string") {
|
|
456
|
-
continue;
|
|
457
|
-
}
|
|
458
|
-
sounds.push({
|
|
459
|
-
symbol: phoneme.symbol,
|
|
460
|
-
...phoneme.accuracy !== void 0 ? { accuracy: phoneme.accuracy } : {},
|
|
461
|
-
...phoneme.heardAs !== void 0 && phoneme.heardAs.length > 0 ? { heardAs: phoneme.heardAs.map(({ symbol, score: score2 }) => ({ symbol, score: score2 })) } : {}
|
|
462
|
-
});
|
|
463
|
-
}
|
|
464
|
-
return sounds.length > 0 ? sounds : void 0;
|
|
465
|
-
}
|
|
466
|
-
function fromAssessment(item, assessment) {
|
|
467
|
-
const words = _chunkVSH7ECQLcjs.alignReadAloud.call(void 0, item, assessment).map((entry) => {
|
|
468
|
-
const sounds = entry.itemId !== void 0 && entry.wordIndex !== void 0 ? soundsOf(assessment.words[entry.wordIndex]) : void 0;
|
|
469
|
-
return {
|
|
470
|
-
...entry.itemId !== void 0 ? { itemId: entry.itemId } : {},
|
|
471
|
-
word: entry.reference,
|
|
472
|
-
heard: entry.heard,
|
|
473
|
-
state: entry.state,
|
|
474
|
-
...entry.accuracy !== void 0 ? { accuracy: entry.accuracy } : {},
|
|
475
|
-
...sounds !== void 0 ? { sounds } : {}
|
|
476
|
-
};
|
|
477
|
-
});
|
|
478
|
-
const scores = {};
|
|
479
|
-
for (const dimension of DIMENSIONS) {
|
|
480
|
-
const value = assessment.scores[dimension];
|
|
481
|
-
if (value !== void 0) {
|
|
482
|
-
scores[dimension] = value;
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
return {
|
|
486
|
-
assessor: assessment.assessor.kind,
|
|
487
|
-
...assessment.phonemeAlphabet !== void 0 ? { phonemeAlphabet: assessment.phonemeAlphabet } : {},
|
|
488
|
-
scores,
|
|
489
|
-
words
|
|
490
|
-
};
|
|
491
|
-
}
|
|
492
|
-
var marksThisText = (item, assessment) => _chunkVSH7ECQLcjs.validateSpeechAssessment.call(void 0, assessment).success && assessment.status === "assessed" && assessment.task === "scripted" && assessment.locale === item.locale && assessment.referenceText === item.referenceText;
|
|
493
|
-
var STATE_OF_OUTCOME = {
|
|
494
|
-
correct: "correct",
|
|
495
|
-
incorrect: "mispronounced",
|
|
496
|
-
"incorrect-omission": "omitted"
|
|
497
|
-
};
|
|
498
|
-
function fromGrade(grade) {
|
|
499
|
-
if (!Array.isArray(grade.details) || grade.details.length === 0) {
|
|
500
|
-
return null;
|
|
501
|
-
}
|
|
502
|
-
const words = [];
|
|
503
|
-
for (const detail of grade.details) {
|
|
504
|
-
const state = STATE_OF_OUTCOME[detail.outcome];
|
|
505
|
-
if (state === void 0 || typeof detail.itemId !== "string") {
|
|
506
|
-
continue;
|
|
507
|
-
}
|
|
508
|
-
const text = (value) => Array.isArray(value) ? value.join(" ") : value;
|
|
509
|
-
words.push({
|
|
510
|
-
itemId: detail.itemId,
|
|
511
|
-
word: text(detail.correctResponse),
|
|
512
|
-
heard: text(detail.learnerResponse),
|
|
513
|
-
state,
|
|
514
|
-
...state !== "omitted" && isFiniteNumber(detail.score) ? { accuracy: hundredths(detail.score) } : {}
|
|
515
|
-
});
|
|
516
|
-
}
|
|
517
|
-
if (words.length === 0) {
|
|
518
|
-
return null;
|
|
519
|
-
}
|
|
520
|
-
const scores = {};
|
|
521
|
-
for (const criterion of _nullishCoalesce(grade.criteria, () => ( []))) {
|
|
522
|
-
const dimension = DIMENSIONS.find((name) => name === criterion.name);
|
|
523
|
-
if (dimension !== void 0 && isFiniteNumber(criterion.score) && isFiniteNumber(criterion.maxScore) && criterion.maxScore > 0) {
|
|
524
|
-
scores[dimension] = hundredths(criterion.score / criterion.maxScore);
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
const kind = _optionalChain([grade, 'access', _23 => _23.grader, 'optionalAccess', _24 => _24.kind]);
|
|
528
|
-
return {
|
|
529
|
-
assessor: kind === "auto" || kind === "ai" || kind === "human" ? kind : "unknown",
|
|
530
|
-
scores,
|
|
531
|
-
words
|
|
532
|
-
};
|
|
533
|
-
}
|
|
534
|
-
function aiCoachingRequest(input) {
|
|
535
|
-
const { data, assessment, grade, learnerLocale } = input;
|
|
536
|
-
if (!aiSupports(data.type, "pronunciation-coaching") || !aiAllowedByContent(data, "pronunciation-coaching")) {
|
|
537
|
-
return null;
|
|
538
|
-
}
|
|
539
|
-
const item = data;
|
|
540
|
-
if (typeof item.referenceText !== "string" || typeof item.locale !== "string") {
|
|
541
|
-
return null;
|
|
542
|
-
}
|
|
543
|
-
const marks = assessment !== void 0 && assessment !== null ? marksThisText(item, assessment) ? fromAssessment(item, assessment) : null : grade !== void 0 && grade !== null ? fromGrade(grade) : null;
|
|
544
|
-
if (marks === null) {
|
|
545
|
-
return null;
|
|
546
|
-
}
|
|
547
|
-
const graded = grade !== void 0 && grade !== null && isFiniteNumber(grade.score) && isFiniteNumber(grade.maxScore) && typeof grade.passed === "boolean" ? { score: grade.score, maxScore: grade.maxScore, passed: grade.passed } : void 0;
|
|
548
|
-
return {
|
|
549
|
-
feature: "pronunciation-coaching",
|
|
550
|
-
facts: {
|
|
551
|
-
activityType: "read-aloud",
|
|
552
|
-
activityId: data.id,
|
|
553
|
-
title: data.title,
|
|
554
|
-
locale: item.locale,
|
|
555
|
-
referenceText: item.referenceText,
|
|
556
|
-
...typeof item.instructions === "string" ? { instructions: item.instructions } : {},
|
|
557
|
-
...marks
|
|
558
|
-
},
|
|
559
|
-
...graded !== void 0 ? { grade: graded } : {},
|
|
560
|
-
...learnerLocale !== void 0 ? { learnerLocale } : {}
|
|
561
|
-
};
|
|
562
|
-
}
|
|
563
|
-
var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
564
|
-
var symbolKey = (symbol) => symbol.normalize("NFC").trim();
|
|
565
|
-
function checkAiCoaching(raw, request) {
|
|
566
|
-
const read = readAiTextResult(raw);
|
|
567
|
-
if (!read.ok) {
|
|
568
|
-
return read;
|
|
569
|
-
}
|
|
570
|
-
const entries = raw.words;
|
|
571
|
-
const words = [];
|
|
572
|
-
if (entries !== void 0 && entries !== null) {
|
|
573
|
-
if (!Array.isArray(entries)) {
|
|
574
|
-
return { ok: false, refusal: "malformed" };
|
|
575
|
-
}
|
|
576
|
-
if (entries.length > AI_COACHING_MAX_WORDS) {
|
|
577
|
-
return { ok: false, refusal: "too-long" };
|
|
578
|
-
}
|
|
579
|
-
const facts = /* @__PURE__ */ new Map();
|
|
580
|
-
request.facts.words.forEach((fact, order) => {
|
|
581
|
-
if (fact.itemId !== void 0) {
|
|
582
|
-
facts.set(fact.itemId, { fact, order });
|
|
583
|
-
}
|
|
584
|
-
});
|
|
585
|
-
const seen = /* @__PURE__ */ new Set();
|
|
586
|
-
for (const entry of entries) {
|
|
587
|
-
if (!isRecord(entry) || typeof entry.itemId !== "string" || typeof entry.tip !== "string") {
|
|
588
|
-
return { ok: false, refusal: "malformed" };
|
|
589
|
-
}
|
|
590
|
-
const tip = cleanText(entry.tip);
|
|
591
|
-
if (tip === "") {
|
|
592
|
-
return { ok: false, refusal: "malformed" };
|
|
593
|
-
}
|
|
594
|
-
if ([...tip].length > AI_COACHING_MAX_TIP_LENGTH) {
|
|
595
|
-
return { ok: false, refusal: "too-long" };
|
|
596
|
-
}
|
|
597
|
-
if (seen.has(entry.itemId)) {
|
|
598
|
-
return { ok: false, refusal: "malformed" };
|
|
599
|
-
}
|
|
600
|
-
seen.add(entry.itemId);
|
|
601
|
-
const found = facts.get(entry.itemId);
|
|
602
|
-
if (found === void 0 || found.fact.state !== "mispronounced" && found.fact.state !== "omitted") {
|
|
603
|
-
return { ok: false, refusal: "contradicts-marks" };
|
|
604
|
-
}
|
|
605
|
-
const sound = checkSound(entry.sound, found.fact);
|
|
606
|
-
if (sound !== void 0 && "refusal" in sound) {
|
|
607
|
-
return sound;
|
|
608
|
-
}
|
|
609
|
-
words.push({
|
|
610
|
-
itemId: entry.itemId,
|
|
611
|
-
word: found.fact.word,
|
|
612
|
-
tip,
|
|
613
|
-
...sound !== void 0 ? { sound } : {},
|
|
614
|
-
order: found.order
|
|
615
|
-
});
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
|
-
const { text, provenance, usage } = read.result;
|
|
619
|
-
return {
|
|
620
|
-
ok: true,
|
|
621
|
-
coaching: {
|
|
622
|
-
text,
|
|
623
|
-
words: words.sort((a, b) => a.order - b.order).map(({ order: _order, ...word }) => word),
|
|
624
|
-
...provenance !== void 0 ? { provenance } : {},
|
|
625
|
-
...usage !== void 0 ? { usage } : {}
|
|
626
|
-
}
|
|
627
|
-
};
|
|
628
|
-
}
|
|
629
|
-
function checkSound(raw, fact) {
|
|
630
|
-
if (raw === void 0 || raw === null) {
|
|
631
|
-
return void 0;
|
|
632
|
-
}
|
|
633
|
-
if (!isRecord(raw) || typeof raw.expected !== "string" || raw.heard !== void 0 && raw.heard !== null && typeof raw.heard !== "string") {
|
|
634
|
-
return { ok: false, refusal: "malformed" };
|
|
635
|
-
}
|
|
636
|
-
const heard = typeof raw.heard === "string" ? raw.heard : void 0;
|
|
637
|
-
if ([...raw.expected].length > MAX_SYMBOL_LENGTH || heard !== void 0 && [...heard].length > MAX_SYMBOL_LENGTH) {
|
|
638
|
-
return { ok: false, refusal: "too-long" };
|
|
639
|
-
}
|
|
640
|
-
const expected = symbolKey(raw.expected);
|
|
641
|
-
const reported = _optionalChain([fact, 'access', _25 => _25.sounds, 'optionalAccess', _26 => _26.find, 'call', _27 => _27((sound) => symbolKey(sound.symbol) === expected)]);
|
|
642
|
-
if (reported === void 0) {
|
|
643
|
-
return { ok: false, refusal: "contradicts-marks" };
|
|
644
|
-
}
|
|
645
|
-
if (heard === void 0) {
|
|
646
|
-
return { expected: reported.symbol };
|
|
647
|
-
}
|
|
648
|
-
const instead = _optionalChain([reported, 'access', _28 => _28.heardAs, 'optionalAccess', _29 => _29.find, 'call', _30 => _30(
|
|
649
|
-
(candidate) => symbolKey(candidate.symbol) === symbolKey(heard)
|
|
650
|
-
)]);
|
|
651
|
-
if (instead === void 0) {
|
|
652
|
-
return { ok: false, refusal: "contradicts-marks" };
|
|
653
|
-
}
|
|
654
|
-
return { expected: reported.symbol, heard: instead.symbol };
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
// src/ai-writing.ts
|
|
658
|
-
var AI_WRITING_MAX_CORRECTIONS = 20;
|
|
659
|
-
var AI_WRITING_MAX_FIELD_LENGTH = 500;
|
|
660
|
-
var MAX_TAG_LENGTH = 40;
|
|
661
|
-
function aiWritingFeedbackRequest(input) {
|
|
662
|
-
const { data, response, learnerLocale } = input;
|
|
663
|
-
if (!aiSupports(data.type, "writing-feedback") || !aiAllowedByContent(data, "writing-feedback") || data.redacted === true) {
|
|
664
|
-
return null;
|
|
665
|
-
}
|
|
666
|
-
const text = _optionalChain([response, 'optionalAccess', _31 => _31.type]) === "written-response" ? response.text : "";
|
|
667
|
-
if (typeof text !== "string" || text.trim() === "") {
|
|
668
|
-
return null;
|
|
669
|
-
}
|
|
670
|
-
const item = data;
|
|
671
|
-
const wordCount = _chunkDSDXL3JOcjs.countWords.call(void 0, text);
|
|
672
|
-
const minWords = typeof item.minWords === "number" ? item.minWords : 0;
|
|
673
|
-
const maxWords = typeof item.maxWords === "number" ? item.maxWords : Number.POSITIVE_INFINITY;
|
|
674
|
-
const criteria = Array.isArray(_optionalChain([item, 'access', _32 => _32.rubric, 'optionalAccess', _33 => _33.criteria])) ? item.rubric.criteria : null;
|
|
675
|
-
const rubric = criteria === null || criteria.length === 0 ? null : criteria.map((criterion) => ({
|
|
676
|
-
name: criterion.name,
|
|
677
|
-
...typeof criterion.description === "string" ? { description: criterion.description } : {},
|
|
678
|
-
weight: criterion.weight
|
|
679
|
-
}));
|
|
680
|
-
const previousFeedback = [..._nullishCoalesce(input.previousFeedback, () => ( []))];
|
|
681
|
-
return {
|
|
682
|
-
feature: "writing-feedback",
|
|
683
|
-
facts: {
|
|
684
|
-
activityType: "written-response",
|
|
685
|
-
activityId: data.id,
|
|
686
|
-
title: data.title,
|
|
687
|
-
...typeof data.locale === "string" ? { locale: data.locale } : {},
|
|
688
|
-
prompt: typeof item.prompt === "string" ? item.prompt : "",
|
|
689
|
-
text,
|
|
690
|
-
wordCount,
|
|
691
|
-
minWords,
|
|
692
|
-
maxWords,
|
|
693
|
-
withinWordBounds: wordCount >= minWords && wordCount <= maxWords,
|
|
694
|
-
rubric,
|
|
695
|
-
...typeof item.languageTarget === "string" ? { languageTarget: item.languageTarget } : {}
|
|
696
|
-
},
|
|
697
|
-
draftNumber: previousFeedback.length + 1,
|
|
698
|
-
previousFeedback,
|
|
699
|
-
...learnerLocale !== void 0 ? { learnerLocale } : {}
|
|
700
|
-
};
|
|
701
|
-
}
|
|
702
|
-
var QUOTE_FOLD = {
|
|
703
|
-
"\u2018": "'",
|
|
704
|
-
"\u2019": "'",
|
|
705
|
-
"\u201B": "'",
|
|
706
|
-
"\u2032": "'",
|
|
707
|
-
"\u201C": '"',
|
|
708
|
-
"\u201D": '"',
|
|
709
|
-
"\u201E": '"',
|
|
710
|
-
"\u2033": '"'
|
|
711
|
-
};
|
|
712
|
-
function folded(text) {
|
|
713
|
-
let chars = "";
|
|
714
|
-
const from = [];
|
|
715
|
-
let space = false;
|
|
716
|
-
for (let at = 0; at < text.length; at += 1) {
|
|
717
|
-
const char = text[at];
|
|
718
|
-
if (/\s/u.test(char)) {
|
|
719
|
-
if (!space) {
|
|
720
|
-
chars += " ";
|
|
721
|
-
from.push(at);
|
|
722
|
-
space = true;
|
|
723
|
-
}
|
|
724
|
-
continue;
|
|
725
|
-
}
|
|
726
|
-
space = false;
|
|
727
|
-
chars += _nullishCoalesce(QUOTE_FOLD[char], () => ( char));
|
|
728
|
-
from.push(at);
|
|
729
|
-
}
|
|
730
|
-
return { chars, from };
|
|
731
|
-
}
|
|
732
|
-
var needle = (quote) => folded(quote).chars.trim();
|
|
733
|
-
function anchor(draft, quote, claimed, taken) {
|
|
734
|
-
const sought = needle(quote);
|
|
735
|
-
if (sought === "") {
|
|
736
|
-
return null;
|
|
737
|
-
}
|
|
738
|
-
if (claimed !== void 0) {
|
|
739
|
-
const { start, end } = claimed;
|
|
740
|
-
if (!Number.isSafeInteger(start) || !Number.isSafeInteger(end) || start < 0 || end > draft.text.length || start >= end) {
|
|
741
|
-
return null;
|
|
742
|
-
}
|
|
743
|
-
const span = draft.text.slice(start, end);
|
|
744
|
-
if (needle(span) !== sought) {
|
|
745
|
-
return null;
|
|
746
|
-
}
|
|
747
|
-
const lead = span.length - span.trimStart().length;
|
|
748
|
-
const trail = span.length - span.trimEnd().length;
|
|
749
|
-
return { start: start + lead, end: end - trail };
|
|
750
|
-
}
|
|
751
|
-
let first = null;
|
|
752
|
-
for (let at = draft.chars.indexOf(sought); at !== -1; at = draft.chars.indexOf(sought, at + 1)) {
|
|
753
|
-
const start = draft.from[at];
|
|
754
|
-
const end = draft.from[at + sought.length - 1] + 1;
|
|
755
|
-
const found = { start, end };
|
|
756
|
-
first ??= found;
|
|
757
|
-
const key = `${start}:${end}`;
|
|
758
|
-
if (!taken.has(key)) {
|
|
759
|
-
taken.add(key);
|
|
760
|
-
return found;
|
|
761
|
-
}
|
|
762
|
-
}
|
|
763
|
-
return first;
|
|
764
|
-
}
|
|
765
|
-
function textField(value, max) {
|
|
766
|
-
if (value === void 0 || value === null) {
|
|
767
|
-
return { ok: true, value: void 0 };
|
|
768
|
-
}
|
|
769
|
-
if (typeof value !== "string") {
|
|
770
|
-
return { ok: false, refusal: "malformed" };
|
|
771
|
-
}
|
|
772
|
-
const cleaned = cleanText(value);
|
|
773
|
-
if ([...cleaned].length > max) {
|
|
774
|
-
return { ok: false, refusal: "too-long" };
|
|
775
|
-
}
|
|
776
|
-
return { ok: true, value: cleaned === "" ? void 0 : cleaned };
|
|
777
|
-
}
|
|
778
|
-
var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
779
|
-
function checkCorrections(raw, text) {
|
|
780
|
-
if (raw === void 0 || raw === null) {
|
|
781
|
-
return { ok: true, corrections: [] };
|
|
782
|
-
}
|
|
783
|
-
if (!Array.isArray(raw)) {
|
|
784
|
-
return { ok: false, refusal: "malformed" };
|
|
785
|
-
}
|
|
786
|
-
if (raw.length > AI_WRITING_MAX_CORRECTIONS) {
|
|
787
|
-
return { ok: false, refusal: "too-long" };
|
|
788
|
-
}
|
|
789
|
-
const draft = { text, ...folded(text) };
|
|
790
|
-
const taken = /* @__PURE__ */ new Set();
|
|
791
|
-
const corrections = [];
|
|
792
|
-
for (const entry of raw) {
|
|
793
|
-
if (!isRecord2(entry) || typeof entry.original !== "string") {
|
|
794
|
-
return { ok: false, refusal: "malformed" };
|
|
795
|
-
}
|
|
796
|
-
if (typeof entry.corrected !== "string") {
|
|
797
|
-
return { ok: false, refusal: "malformed" };
|
|
798
|
-
}
|
|
799
|
-
if ([...entry.original].length > AI_WRITING_MAX_FIELD_LENGTH || [...entry.corrected].length > AI_WRITING_MAX_FIELD_LENGTH) {
|
|
800
|
-
return { ok: false, refusal: "too-long" };
|
|
801
|
-
}
|
|
802
|
-
if (needle(entry.original) === needle(entry.corrected)) {
|
|
803
|
-
return { ok: false, refusal: "malformed" };
|
|
804
|
-
}
|
|
805
|
-
const explanation = textField(entry.explanation, AI_WRITING_MAX_FIELD_LENGTH);
|
|
806
|
-
const category = textField(entry.category, MAX_TAG_LENGTH);
|
|
807
|
-
if (!explanation.ok) {
|
|
808
|
-
return explanation;
|
|
809
|
-
}
|
|
810
|
-
if (!category.ok) {
|
|
811
|
-
return category;
|
|
812
|
-
}
|
|
813
|
-
let claimed;
|
|
814
|
-
if (entry.range !== void 0 && entry.range !== null) {
|
|
815
|
-
if (!isRecord2(entry.range)) {
|
|
816
|
-
return { ok: false, refusal: "malformed" };
|
|
817
|
-
}
|
|
818
|
-
claimed = { start: entry.range.start, end: entry.range.end };
|
|
819
|
-
}
|
|
820
|
-
const at = anchor(draft, entry.original, claimed, taken);
|
|
821
|
-
if (at === null) {
|
|
822
|
-
return { ok: false, refusal: "misquotes-answer" };
|
|
823
|
-
}
|
|
824
|
-
corrections.push({
|
|
825
|
-
original: text.slice(at.start, at.end),
|
|
826
|
-
corrected: cleanText(entry.corrected),
|
|
827
|
-
...explanation.value !== void 0 ? { explanation: explanation.value } : {},
|
|
828
|
-
...category.value !== void 0 ? { category: category.value } : {},
|
|
829
|
-
range: at
|
|
830
|
-
});
|
|
831
|
-
}
|
|
832
|
-
return { ok: true, corrections };
|
|
833
|
-
}
|
|
834
|
-
function isJudgedScore(score2, maxScore) {
|
|
835
|
-
if (score2 === void 0) {
|
|
836
|
-
return maxScore === void 0 || typeof maxScore === "number" && maxScore > 0;
|
|
837
|
-
}
|
|
838
|
-
const max = _nullishCoalesce(maxScore, () => ( 1));
|
|
839
|
-
return typeof score2 === "number" && typeof max === "number" && Number.isFinite(score2) && Number.isFinite(max) && max > 0 && score2 >= 0 && score2 - max <= max * 1e-9;
|
|
840
|
-
}
|
|
841
|
-
function checkCriteria(raw, rubric) {
|
|
842
|
-
if (raw === void 0 || raw === null) {
|
|
843
|
-
return { ok: true, criteria: [] };
|
|
844
|
-
}
|
|
845
|
-
if (!Array.isArray(raw)) {
|
|
846
|
-
return { ok: false, refusal: "malformed" };
|
|
847
|
-
}
|
|
848
|
-
if (raw.length === 0) {
|
|
849
|
-
return { ok: true, criteria: [] };
|
|
850
|
-
}
|
|
851
|
-
if (rubric === null) {
|
|
852
|
-
return { ok: false, refusal: "malformed" };
|
|
853
|
-
}
|
|
854
|
-
const weights = new Map(rubric.map((criterion) => [criterion.name, criterion.weight]));
|
|
855
|
-
const seen = /* @__PURE__ */ new Set();
|
|
856
|
-
const criteria = [];
|
|
857
|
-
for (const entry of raw) {
|
|
858
|
-
if (!isRecord2(entry) || typeof entry.name !== "string") {
|
|
859
|
-
return { ok: false, refusal: "malformed" };
|
|
860
|
-
}
|
|
861
|
-
const weight = weights.get(entry.name);
|
|
862
|
-
if (weight === void 0 || seen.has(entry.name)) {
|
|
863
|
-
return { ok: false, refusal: "malformed" };
|
|
864
|
-
}
|
|
865
|
-
seen.add(entry.name);
|
|
866
|
-
if (!isJudgedScore(entry.score, entry.maxScore)) {
|
|
867
|
-
return { ok: false, refusal: "malformed" };
|
|
868
|
-
}
|
|
869
|
-
if (entry.notApplicable !== void 0 && typeof entry.notApplicable !== "boolean") {
|
|
870
|
-
return { ok: false, refusal: "malformed" };
|
|
871
|
-
}
|
|
872
|
-
const band = textField(entry.band, MAX_TAG_LENGTH);
|
|
873
|
-
const comment = textField(entry.comment, AI_WRITING_MAX_FIELD_LENGTH);
|
|
874
|
-
if (!band.ok) {
|
|
875
|
-
return band;
|
|
876
|
-
}
|
|
877
|
-
if (!comment.ok) {
|
|
878
|
-
return comment;
|
|
879
|
-
}
|
|
880
|
-
criteria.push({
|
|
881
|
-
name: entry.name,
|
|
882
|
-
...typeof entry.score === "number" ? { score: entry.score } : {},
|
|
883
|
-
...typeof entry.maxScore === "number" ? { maxScore: entry.maxScore } : {},
|
|
884
|
-
...band.value !== void 0 ? { band: band.value } : {},
|
|
885
|
-
...comment.value !== void 0 ? { comment: comment.value } : {},
|
|
886
|
-
...entry.notApplicable === true ? { notApplicable: true } : {},
|
|
887
|
-
weight
|
|
888
|
-
});
|
|
889
|
-
}
|
|
890
|
-
return { ok: true, criteria };
|
|
891
|
-
}
|
|
892
|
-
function checkAiWritingFeedback(raw, request) {
|
|
893
|
-
const read = readAiTextResult(raw);
|
|
894
|
-
if (!read.ok) {
|
|
895
|
-
return read;
|
|
896
|
-
}
|
|
897
|
-
const record = raw;
|
|
898
|
-
const corrections = checkCorrections(record.corrections, request.facts.text);
|
|
899
|
-
if (!corrections.ok) {
|
|
900
|
-
return corrections;
|
|
901
|
-
}
|
|
902
|
-
const rubric = request.facts.rubric;
|
|
903
|
-
const criteria = checkCriteria(record.criteria, rubric);
|
|
904
|
-
if (!criteria.ok) {
|
|
905
|
-
return criteria;
|
|
906
|
-
}
|
|
907
|
-
let indicativeScore = null;
|
|
908
|
-
const judged = new Set(criteria.criteria.map((criterion) => criterion.name));
|
|
909
|
-
if (_optionalChain([rubric, 'optionalAccess', _34 => _34.every, 'call', _35 => _35((criterion) => judged.has(criterion.name))])) {
|
|
910
|
-
const grade = _chunkVSH7ECQLcjs.gradeFromRubric.call(void 0, criteria.criteria);
|
|
911
|
-
indicativeScore = "unscorable" in grade ? null : grade.score;
|
|
912
|
-
}
|
|
913
|
-
const { text, provenance, usage } = read.result;
|
|
914
|
-
return {
|
|
915
|
-
ok: true,
|
|
916
|
-
feedback: {
|
|
917
|
-
text,
|
|
918
|
-
corrections: corrections.corrections,
|
|
919
|
-
criteria: criteria.criteria,
|
|
920
|
-
indicativeScore,
|
|
921
|
-
...provenance !== void 0 ? { provenance } : {},
|
|
922
|
-
...usage !== void 0 ? { usage } : {}
|
|
923
|
-
}
|
|
924
|
-
};
|
|
925
|
-
}
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
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_COACHING_MAX_WORDS = AI_COACHING_MAX_WORDS; exports.AI_COACHING_MAX_TIP_LENGTH = AI_COACHING_MAX_TIP_LENGTH; exports.aiCoachingRequest = aiCoachingRequest; exports.checkAiCoaching = checkAiCoaching; 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;
|
|
950
|
-
//# sourceMappingURL=chunk-LDFQUZII.cjs.map
|