@intellectif/lk-core 0.22.0 → 1.1.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 +6 -6
- package/dist/{activity-CYc7SD3d.d.cts → activity-MBOYLBjR.d.cts} +128 -22
- package/dist/{activity-CYc7SD3d.d.ts → activity-MBOYLBjR.d.ts} +128 -22
- package/dist/ai-check.cjs +138 -9
- package/dist/ai-check.cjs.map +1 -1
- package/dist/ai-check.d.cts +6 -3
- package/dist/ai-check.d.ts +6 -3
- package/dist/ai-check.js +133 -4
- package/dist/ai-check.js.map +1 -1
- package/dist/{chunk-2CV6NU32.cjs → chunk-2JT2WUQ2.cjs} +77 -39
- package/dist/chunk-2JT2WUQ2.cjs.map +1 -0
- package/dist/{chunk-D4TOPSHV.cjs → chunk-DSDXL3JO.cjs} +1216 -1105
- package/dist/chunk-DSDXL3JO.cjs.map +1 -0
- package/dist/{chunk-D3GP5MET.js → chunk-FBKTION6.js} +1093 -982
- package/dist/chunk-FBKTION6.js.map +1 -0
- package/dist/{chunk-Q7MV3IXJ.js → chunk-IARLJ5MW.js} +15 -16
- package/dist/chunk-IARLJ5MW.js.map +1 -0
- package/dist/{chunk-OPMY2MNS.cjs → chunk-LDFQUZII.cjs} +248 -22
- package/dist/chunk-LDFQUZII.cjs.map +1 -0
- package/dist/{chunk-5UCR4UNR.js → chunk-NQHETFDA.js} +238 -12
- package/dist/chunk-NQHETFDA.js.map +1 -0
- package/dist/{chunk-X5DAXBJI.js → chunk-SJVRUPJT.js} +2 -2
- package/dist/{chunk-EOFWSGLU.cjs → chunk-TJJFKKH2.cjs} +4 -4
- package/dist/{chunk-EOFWSGLU.cjs.map → chunk-TJJFKKH2.cjs.map} +1 -1
- package/dist/{chunk-WQVZPIN7.cjs → chunk-VSH7ECQL.cjs} +44 -45
- package/dist/chunk-VSH7ECQL.cjs.map +1 -0
- package/dist/{chunk-Z7QQZ53L.js → chunk-XS6U2ARZ.js} +70 -32
- package/dist/chunk-XS6U2ARZ.js.map +1 -0
- package/dist/index-DlCW0XaQ.d.cts +548 -0
- package/dist/index-DlcqkM_L.d.ts +548 -0
- package/dist/index.cjs +129 -198
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +178 -21
- package/dist/index.d.ts +178 -21
- package/dist/index.js +34 -103
- package/dist/index.js.map +1 -1
- package/dist/schemas.cjs +3 -94
- package/dist/schemas.cjs.map +1 -1
- package/dist/schemas.d.cts +2 -3
- package/dist/schemas.d.ts +2 -3
- package/dist/schemas.js +6 -97
- 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 +4 -4
- package/vectors/README.md +32 -2
- package/vectors/scoring.json +1 -498
- package/vectors/validation.json +6742 -0
- package/vectors/validation.mjs +195 -0
- package/dist/chunk-2CV6NU32.cjs.map +0 -1
- package/dist/chunk-5UCR4UNR.js.map +0 -1
- package/dist/chunk-D3GP5MET.js.map +0 -1
- package/dist/chunk-D4TOPSHV.cjs.map +0 -1
- package/dist/chunk-OPMY2MNS.cjs.map +0 -1
- package/dist/chunk-Q7MV3IXJ.js.map +0 -1
- package/dist/chunk-WQVZPIN7.cjs.map +0 -1
- package/dist/chunk-Z7QQZ53L.js.map +0 -1
- package/dist/index-0Z3-nnAP.d.ts +0 -2303
- package/dist/index-CxxiNSSH.d.cts +0 -2303
- /package/dist/{chunk-X5DAXBJI.js.map → chunk-SJVRUPJT.js.map} +0 -0
|
@@ -2,12 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
var _chunkWQVZPIN7cjs = require('./chunk-WQVZPIN7.cjs');
|
|
6
5
|
|
|
7
6
|
|
|
7
|
+
var _chunkVSH7ECQLcjs = require('./chunk-VSH7ECQL.cjs');
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
var _chunkDSDXL3JOcjs = require('./chunk-DSDXL3JO.cjs');
|
|
11
13
|
|
|
12
14
|
// src/ai-text.ts
|
|
13
15
|
function cleanText(text) {
|
|
@@ -31,6 +33,9 @@ function aiSupports(activityType, feature) {
|
|
|
31
33
|
if (feature === "writing-feedback") {
|
|
32
34
|
return activityType === "written-response";
|
|
33
35
|
}
|
|
36
|
+
if (feature === "pronunciation-coaching") {
|
|
37
|
+
return activityType === "read-aloud";
|
|
38
|
+
}
|
|
34
39
|
const types = feature === "hint" ? AI_HINT_TYPES : AI_EXPLANATION_TYPES;
|
|
35
40
|
return types.includes(activityType);
|
|
36
41
|
}
|
|
@@ -53,15 +58,20 @@ function aiGradeOf(result) {
|
|
|
53
58
|
return { score: result.score, maxScore: result.maxScore, passed: result.passed, category };
|
|
54
59
|
}
|
|
55
60
|
function placeholderOrder(passage) {
|
|
56
|
-
return [...passage.matchAll(
|
|
61
|
+
return [...passage.matchAll(_chunkDSDXL3JOcjs.PLACEHOLDER_RE)].map((match) => match[1]);
|
|
57
62
|
}
|
|
58
63
|
function numberedPassage(passage) {
|
|
59
64
|
let position = 0;
|
|
60
|
-
return passage.replace(
|
|
65
|
+
return passage.replace(_chunkDSDXL3JOcjs.PLACEHOLDER_RE, () => {
|
|
61
66
|
position += 1;
|
|
62
67
|
return `[${position}]`;
|
|
63
68
|
});
|
|
64
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
|
+
}
|
|
65
75
|
function isAnswerOption(detail, chosen) {
|
|
66
76
|
switch (detail.outcome) {
|
|
67
77
|
case "correct":
|
|
@@ -71,7 +81,7 @@ function isAnswerOption(detail, chosen) {
|
|
|
71
81
|
case "correct-omission":
|
|
72
82
|
return false;
|
|
73
83
|
default:
|
|
74
|
-
return chosen ? detail
|
|
84
|
+
return chosen ? legacyCorrect(detail) : !legacyCorrect(detail);
|
|
75
85
|
}
|
|
76
86
|
}
|
|
77
87
|
var firstString = (value) => Array.isArray(value) ? value[0] : value;
|
|
@@ -145,7 +155,7 @@ function buildAiFacts(data, response, details) {
|
|
|
145
155
|
position: index + 1,
|
|
146
156
|
typed: typeof answers[id] === "string" ? answers[id] : "",
|
|
147
157
|
accepted,
|
|
148
|
-
correct: detail === void 0 ? null : detail
|
|
158
|
+
correct: detail === void 0 ? null : detailRight(detail),
|
|
149
159
|
...typeof _optionalChain([blank, 'optionalAccess', _5 => _5.hint]) === "string" ? { hint: blank.hint } : {},
|
|
150
160
|
...typeof _optionalChain([blank, 'optionalAccess', _6 => _6.feedback]) === "string" ? { feedback: blank.feedback } : {}
|
|
151
161
|
};
|
|
@@ -173,7 +183,7 @@ function buildAiFacts(data, response, details) {
|
|
|
173
183
|
choices: choices.map((choice) => choice.text),
|
|
174
184
|
chosen: textOf(selections[id]),
|
|
175
185
|
answer: textOf(answerId),
|
|
176
|
-
correct: detail === void 0 ? null : detail
|
|
186
|
+
correct: detail === void 0 ? null : detailRight(detail),
|
|
177
187
|
...typeof _optionalChain([gap, 'optionalAccess', _18 => _18.feedback]) === "string" ? { feedback: gap.feedback } : {}
|
|
178
188
|
};
|
|
179
189
|
})
|
|
@@ -185,7 +195,7 @@ function buildAiFacts(data, response, details) {
|
|
|
185
195
|
const hasKey = full && typeof item.transcript === "string";
|
|
186
196
|
let words = null;
|
|
187
197
|
if (hasKey) {
|
|
188
|
-
words =
|
|
198
|
+
words = _chunkDSDXL3JOcjs.alignDictation.call(void 0, item, typed).words.map((word) => ({
|
|
189
199
|
expected: word.reference,
|
|
190
200
|
typed: word.attempt,
|
|
191
201
|
status: word.status
|
|
@@ -195,7 +205,7 @@ function buildAiFacts(data, response, details) {
|
|
|
195
205
|
const expected = _nullishCoalesce(firstString(detail.correctResponse), () => ( ""));
|
|
196
206
|
const attempt = _nullishCoalesce(firstString(detail.learnerResponse), () => ( ""));
|
|
197
207
|
let status = "incorrect";
|
|
198
|
-
if (detail
|
|
208
|
+
if (detailRight(detail)) {
|
|
199
209
|
status = "correct";
|
|
200
210
|
} else if (attempt === "") {
|
|
201
211
|
status = "missing";
|
|
@@ -222,7 +232,7 @@ function gradeLocally(data, response) {
|
|
|
222
232
|
return null;
|
|
223
233
|
}
|
|
224
234
|
try {
|
|
225
|
-
const result =
|
|
235
|
+
const result = _chunkVSH7ECQLcjs.score.call(void 0, data.type, data, response);
|
|
226
236
|
return { details: result.details, grade: aiGradeOf(result) };
|
|
227
237
|
} catch (e) {
|
|
228
238
|
return null;
|
|
@@ -237,7 +247,7 @@ function aiExplanationRequest(input) {
|
|
|
237
247
|
let grade = null;
|
|
238
248
|
if (_optionalChain([outcome, 'optionalAccess', _20 => _20.status]) === "scored") {
|
|
239
249
|
const { score: recorded, maxScore, passed } = outcome;
|
|
240
|
-
if (!
|
|
250
|
+
if (!_chunkVSH7ECQLcjs.isGradeInRange.call(void 0, recorded, maxScore)) {
|
|
241
251
|
return null;
|
|
242
252
|
}
|
|
243
253
|
details = outcome.details;
|
|
@@ -432,6 +442,218 @@ function hintRevealsAnswer(facts, hint) {
|
|
|
432
442
|
}
|
|
433
443
|
}
|
|
434
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
|
+
|
|
435
657
|
// src/ai-writing.ts
|
|
436
658
|
var AI_WRITING_MAX_CORRECTIONS = 20;
|
|
437
659
|
var AI_WRITING_MAX_FIELD_LENGTH = 500;
|
|
@@ -441,15 +663,15 @@ function aiWritingFeedbackRequest(input) {
|
|
|
441
663
|
if (!aiSupports(data.type, "writing-feedback") || !aiAllowedByContent(data, "writing-feedback") || data.redacted === true) {
|
|
442
664
|
return null;
|
|
443
665
|
}
|
|
444
|
-
const text = _optionalChain([response, 'optionalAccess',
|
|
666
|
+
const text = _optionalChain([response, 'optionalAccess', _31 => _31.type]) === "written-response" ? response.text : "";
|
|
445
667
|
if (typeof text !== "string" || text.trim() === "") {
|
|
446
668
|
return null;
|
|
447
669
|
}
|
|
448
670
|
const item = data;
|
|
449
|
-
const wordCount =
|
|
671
|
+
const wordCount = _chunkDSDXL3JOcjs.countWords.call(void 0, text);
|
|
450
672
|
const minWords = typeof item.minWords === "number" ? item.minWords : 0;
|
|
451
673
|
const maxWords = typeof item.maxWords === "number" ? item.maxWords : Number.POSITIVE_INFINITY;
|
|
452
|
-
const criteria = Array.isArray(_optionalChain([item, 'access',
|
|
674
|
+
const criteria = Array.isArray(_optionalChain([item, 'access', _32 => _32.rubric, 'optionalAccess', _33 => _33.criteria])) ? item.rubric.criteria : null;
|
|
453
675
|
const rubric = criteria === null || criteria.length === 0 ? null : criteria.map((criterion) => ({
|
|
454
676
|
name: criterion.name,
|
|
455
677
|
...typeof criterion.description === "string" ? { description: criterion.description } : {},
|
|
@@ -553,7 +775,7 @@ function textField(value, max) {
|
|
|
553
775
|
}
|
|
554
776
|
return { ok: true, value: cleaned === "" ? void 0 : cleaned };
|
|
555
777
|
}
|
|
556
|
-
var
|
|
778
|
+
var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
557
779
|
function checkCorrections(raw, text) {
|
|
558
780
|
if (raw === void 0 || raw === null) {
|
|
559
781
|
return { ok: true, corrections: [] };
|
|
@@ -568,7 +790,7 @@ function checkCorrections(raw, text) {
|
|
|
568
790
|
const taken = /* @__PURE__ */ new Set();
|
|
569
791
|
const corrections = [];
|
|
570
792
|
for (const entry of raw) {
|
|
571
|
-
if (!
|
|
793
|
+
if (!isRecord2(entry) || typeof entry.original !== "string") {
|
|
572
794
|
return { ok: false, refusal: "malformed" };
|
|
573
795
|
}
|
|
574
796
|
if (typeof entry.corrected !== "string") {
|
|
@@ -590,7 +812,7 @@ function checkCorrections(raw, text) {
|
|
|
590
812
|
}
|
|
591
813
|
let claimed;
|
|
592
814
|
if (entry.range !== void 0 && entry.range !== null) {
|
|
593
|
-
if (!
|
|
815
|
+
if (!isRecord2(entry.range)) {
|
|
594
816
|
return { ok: false, refusal: "malformed" };
|
|
595
817
|
}
|
|
596
818
|
claimed = { start: entry.range.start, end: entry.range.end };
|
|
@@ -633,7 +855,7 @@ function checkCriteria(raw, rubric) {
|
|
|
633
855
|
const seen = /* @__PURE__ */ new Set();
|
|
634
856
|
const criteria = [];
|
|
635
857
|
for (const entry of raw) {
|
|
636
|
-
if (!
|
|
858
|
+
if (!isRecord2(entry) || typeof entry.name !== "string") {
|
|
637
859
|
return { ok: false, refusal: "malformed" };
|
|
638
860
|
}
|
|
639
861
|
const weight = weights.get(entry.name);
|
|
@@ -684,8 +906,8 @@ function checkAiWritingFeedback(raw, request) {
|
|
|
684
906
|
}
|
|
685
907
|
let indicativeScore = null;
|
|
686
908
|
const judged = new Set(criteria.criteria.map((criterion) => criterion.name));
|
|
687
|
-
if (_optionalChain([rubric, 'optionalAccess',
|
|
688
|
-
const grade =
|
|
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);
|
|
689
911
|
indicativeScore = "unscorable" in grade ? null : grade.score;
|
|
690
912
|
}
|
|
691
913
|
const { text, provenance, usage } = read.result;
|
|
@@ -720,5 +942,9 @@ function checkAiWritingFeedback(raw, request) {
|
|
|
720
942
|
|
|
721
943
|
|
|
722
944
|
|
|
723
|
-
|
|
724
|
-
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/learning-kit/learning-kit/packages/lk-core/dist/chunk-LDFQUZII.cjs","../src/ai-text.ts","../src/ai.ts","../src/ai-coaching.ts","../src/ai-writing.ts"],"names":["folded","needle","score","isRecord"],"mappings":"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;AACZ,IAAA;AACW,IAAA;AACA,IAAA;AACA,MAAA;AACX,IAAA;AACY,IAAA;AACA,MAAA;AACC,MAAA;AACP,MAAA;AACA,MAAA;AACG,MAAA;AACR,IAAA;AACH,EAAA;AACa,EAAA;AACf;AAGS;AACO,EAAA;AACL,IAAA;AACT,EAAA;AACY,EAAA;AAEHD,EAAAA;AAQX;AAGS;AAIK,EAAA;AACG,IAAA;AACf,EAAA;AACW,EAAA;AACI,IAAA;AACf,EAAA;AACQ,EAAA;AACO,IAAA;AACf,EAAA;AAEe,EAAA;AACA,IAAA;AACf,EAAA;AACM,EAAA;AACO,EAAA;AACP,EAAA;AACK,EAAA;AACJC,IAAAA;AACM,MAAA;AACX,IAAA;AACM,IAAA;AACF,IAAA;AACO,MAAA;AACX,IAAA;AACS,IAAA;AACJ,IAAA;AACM,MAAA;AACX,IAAA;AACU,IAAA;AACC,MAAA;AACX,IAAA;AACa,IAAA;AACP,IAAA;AACI,IAAA;AACD,MAAA;AACT,IAAA;AACa,IAAA;AACJ,MAAA;AACT,IAAA;AACS,IAAA;AACD,MAAA;AACK,MAAA;AACA,MAAA;AACF,MAAA;AACG,MAAA;AACF,MAAA;AACV,MAAA;AACD,IAAA;AACH,EAAA;AACa,EAAA;AACf;AA0BgB;AAID,EAAA;AACC,EAAA;AACL,IAAA;AACT,EAAA;AACe,EAAA;AACT,EAAA;AACD,EAAA;AACI,IAAA;AACT,EAAA;AACe,EAAA;AACT,EAAA;AACQ,EAAA;AACL,IAAA;AACT,EAAA;AACI,EAAA;AACW,EAAA;AACH,EAAA;AACJ,IAAA;AACN,IAAA;AACF,EAAA;AACc,EAAA;AACP,EAAA;AACD,IAAA;AACM,IAAA;AACR,MAAA;AACA,MAAA;AACU,MAAA;AACV,MAAA;AACI,MAAA;AACA,MAAA;AACN,IAAA;AACF,EAAA;AACF;AJqgBiB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"/home/runner/work/learning-kit/learning-kit/packages/lk-core/dist/chunk-LDFQUZII.cjs","sourcesContent":[null,"/**\n * How the SDK reads text a model wrote, before a learner does. Internal: shared\n * by the AI checks, and exported by none of the package's entry points.\n */\n\n/**\n * Plain text as the learner will read it: line endings unified, control\n * characters other than line breaks and tabs removed, runs of blank lines\n * collapsed, the ends trimmed.\n */\nexport function cleanText(text: string): string {\n return (\n text\n .replace(/\\r\\n?/g, '\\n')\n // biome-ignore lint/suspicious/noControlCharactersInRegex: removing them is the point\n .replace(/[\\u0000-\\u0008\\u000B\\u000C\\u000E-\\u001F\\u007F-\\u009F\\u2028\\u2029]/g, '')\n .replace(/\\n{3,}/g, '\\n\\n')\n .trim()\n );\n}\n","import { cleanText } from './ai-text.js';\nimport { PLACEHOLDER_RE } from './schemas/fill-in-the-blanks.js';\nimport { alignDictation } from './scoring/dictation/align.js';\nimport { isGradeInRange } from './scoring/grade-numbers.js';\nimport { score } from './scoring/index.js';\nimport type {\n ActivityData,\n DictationData,\n FillInTheBlanksData,\n GapSelectData,\n ItemOutcome,\n LearnerResponse,\n MultipleChoiceData,\n ScoringDetail,\n} from './types/activity.js';\nimport type {\n AiActivityInput,\n AiExplanationRequest,\n AiFeature,\n AiGrade,\n AiHintRequest,\n AiItemFacts,\n AiProvenance,\n AiRefusal,\n AiSupportedActivityType,\n AiTextResult,\n AiVerdict,\n AiWordFact,\n} from './types/ai.js';\nimport type { GraderUsage } from './types/grading.js';\n\n/**\n * The activity types the SDK builds facts for, and which of them take hints.\n * A dictation takes none: its answer is the whole sentence, and it has\n * word-by-word hints of its own.\n */\nexport const AI_EXPLANATION_TYPES: readonly AiSupportedActivityType[] = [\n 'multiple-choice',\n 'fill-in-the-blanks',\n 'gap-select',\n 'dictation',\n];\nexport const AI_HINT_TYPES: readonly AiSupportedActivityType[] = [\n 'multiple-choice',\n 'fill-in-the-blanks',\n 'gap-select',\n];\n\n/** The longest AI text shown, in code points. Longer is refused, not cut. */\nexport const AI_TEXT_MAX_LENGTH = 2000;\n\n/**\n * Whether the SDK supports `feature` for this activity type: explanations and\n * hints for the types it builds facts for, and writing feedback for a written\n * response, and coaching for a read-aloud. A consumer's own type has none.\n */\nexport function aiSupports(activityType: string, feature: AiFeature): boolean {\n if (feature === 'writing-feedback') {\n return activityType === 'written-response';\n }\n if (feature === 'pronunciation-coaching') {\n return activityType === 'read-aloud';\n }\n const types = feature === 'hint' ? AI_HINT_TYPES : AI_EXPLANATION_TYPES;\n return (types as readonly string[]).includes(activityType);\n}\n\n/**\n * Whether the item's author allows `feature`. Only an explicit `false`\n * switches it off: an author can refuse a feature, never force one on.\n *\n * Writing feedback and pronunciation coaching read `explanations`: each is a\n * model's words about the learner's own answer, and an author who switched one\n * off has switched off the others.\n */\nexport function aiAllowedByContent(data: AiActivityInput, feature: AiFeature): boolean {\n const permissions = (data as { ai?: unknown }).ai;\n if (typeof permissions !== 'object' || permissions === null) {\n return true;\n }\n const key = feature === 'hint' ? 'hints' : 'explanations';\n return (permissions as Record<string, unknown>)[key] !== false;\n}\n\n/** A grade as an explanation must speak to it: all marks, some, or none. */\nexport function aiGradeOf(result: { score: number; maxScore: number; passed: boolean }): AiGrade {\n const ratio = result.maxScore > 0 ? result.score / result.maxScore : 0;\n let category: AiVerdict = 'partly-correct';\n if (ratio >= 1 - 1e-9) {\n category = 'correct';\n } else if (ratio <= 1e-9) {\n category = 'incorrect';\n }\n return { score: result.score, maxScore: result.maxScore, passed: result.passed, category };\n}\n\n// ── Facts ──────────────────────────────────────────────────────────────\n\n/**\n * The ids of a passage's placeholders, in the order they appear. Read with the\n * schema's own expression, so a passage means here what it means to validation.\n */\nfunction placeholderOrder(passage: string): string[] {\n return [...passage.matchAll(PLACEHOLDER_RE)].map((match) => match[1] as string);\n}\n\n/** The passage with each placeholder written as `[n]`, in passage order. */\nfunction numberedPassage(passage: string): string {\n let position = 0;\n return passage.replace(PLACEHOLDER_RE, () => {\n position += 1;\n return `[${position}]`;\n });\n}\n\n/**\n * The `correct` flag a detail stored before 0.3 carries instead of an\n * `outcome`. On a blank, a gap or a word it meant the part was right; on a\n * multiple-choice option, that the learner acted rightly on it.\n */\nconst legacyCorrect = (detail: ScoringDetail): boolean =>\n (detail as { correct?: unknown }).correct === true;\n\n/** Whether a blank, a gap or a word was right, read from a server's detail. */\nfunction detailRight(detail: ScoringDetail): boolean {\n const outcome: unknown = detail.outcome;\n return outcome === undefined ? legacyCorrect(detail) : outcome === 'correct';\n}\n\n/** Whether an option is part of the answer, read from a server's detail (as the renderer reads it). */\nfunction isAnswerOption(detail: ScoringDetail, chosen: boolean): boolean {\n switch (detail.outcome) {\n case 'correct':\n case 'incorrect-omission':\n return true;\n case 'incorrect':\n case 'correct-omission':\n return false;\n default:\n return chosen ? legacyCorrect(detail) : !legacyCorrect(detail);\n }\n}\n\nconst firstString = (value: string | string[] | undefined): string | undefined =>\n Array.isArray(value) ? value[0] : value;\n\nconst listOf = (value: string | string[] | undefined): string[] | null =>\n value === undefined ? null : Array.isArray(value) ? [...value] : [value];\n\n/** The response a learner has before touching anything. */\nfunction emptyResponse(type: string): LearnerResponse | null {\n switch (type) {\n case 'multiple-choice':\n return { type: 'multiple-choice', selectedOptionIds: [] };\n case 'fill-in-the-blanks':\n return { type: 'fill-in-the-blanks', answers: {} };\n case 'gap-select':\n return { type: 'gap-select', selections: {} };\n case 'dictation':\n return { type: 'dictation', text: '' };\n default:\n return null;\n }\n}\n\nconst base = (data: AiActivityInput) => ({\n activityId: data.id,\n title: data.title,\n ...(typeof data.locale === 'string' ? { locale: data.locale } : {}),\n});\n\n/**\n * The facts a model is given about one item and one answer. `details` are the\n * SDK's per-part verdicts — from its own scorer, or from a server's outcome in\n * review — and every `correct` is `null` without them. A redacted item yields\n * facts without a key, which is what the learner's side holds; a port that\n * needs the key loads the item on its server.\n *\n * `null` for a type the SDK builds no facts for.\n */\nexport function buildAiFacts(\n data: AiActivityInput,\n response: LearnerResponse | null,\n details: readonly ScoringDetail[] | null,\n): AiItemFacts | null {\n const byId = new Map((details ?? []).map((detail) => [detail.itemId, detail]));\n const full = (data as { redacted?: unknown }).redacted !== true;\n switch (data.type) {\n case 'multiple-choice': {\n const item = data as unknown as MultipleChoiceData;\n const chosen = new Set(\n response?.type === 'multiple-choice' ? response.selectedOptionIds : [],\n );\n return {\n ...base(data),\n activityType: 'multiple-choice',\n question: item.question,\n mode: item.mode,\n options: item.options.map((option) => {\n const detail = byId.get(option.id);\n const wasChosen = chosen.has(option.id);\n let correct: boolean | null = null;\n if (full && typeof option.isCorrect === 'boolean') {\n correct = option.isCorrect;\n } else if (detail !== undefined) {\n correct = isAnswerOption(detail, wasChosen);\n }\n return {\n id: option.id,\n text: option.text,\n chosen: wasChosen,\n correct,\n ...(typeof option.feedback === 'string' ? { feedback: option.feedback } : {}),\n };\n }),\n };\n }\n case 'fill-in-the-blanks': {\n const item = data as unknown as FillInTheBlanksData;\n const answers = response?.type === 'fill-in-the-blanks' ? response.answers : {};\n const blanks = new Map(item.blanks.map((blank) => [blank.id, blank]));\n return {\n ...base(data),\n activityType: 'fill-in-the-blanks',\n passage: numberedPassage(item.passage),\n blanks: placeholderOrder(item.passage).map((id, index) => {\n const blank = blanks.get(id);\n const detail = byId.get(id);\n const accepted =\n full && Array.isArray(blank?.acceptedAnswers)\n ? [...blank.acceptedAnswers]\n : listOf(detail?.correctResponse);\n return {\n id,\n position: index + 1,\n typed: typeof answers[id] === 'string' ? (answers[id] as string) : '',\n accepted,\n correct: detail === undefined ? null : detailRight(detail),\n ...(typeof blank?.hint === 'string' ? { hint: blank.hint } : {}),\n ...(typeof blank?.feedback === 'string' ? { feedback: blank.feedback } : {}),\n };\n }),\n };\n }\n case 'gap-select': {\n const item = data as unknown as GapSelectData;\n const selections = response?.type === 'gap-select' ? response.selections : {};\n const gaps = new Map(item.gaps.map((gap) => [gap.id, gap]));\n const banks = new Map((item.banks ?? []).map((bank) => [bank.id, bank]));\n return {\n ...base(data),\n activityType: 'gap-select',\n passage: numberedPassage(item.passage),\n gaps: placeholderOrder(item.passage).map((id, index) => {\n const gap = gaps.get(id);\n const choices =\n gap?.choices ??\n (gap?.bankId === undefined ? [] : (banks.get(gap.bankId)?.choices ?? []));\n const textOf = (choiceId: string | undefined): string | null =>\n choiceId === undefined\n ? null\n : (choices.find((choice) => choice.id === choiceId)?.text ?? null);\n const detail = byId.get(id);\n const answerId =\n full && typeof gap?.correctChoiceId === 'string'\n ? gap.correctChoiceId\n : firstString(detail?.correctResponse);\n return {\n id,\n position: index + 1,\n choices: choices.map((choice) => choice.text),\n chosen: textOf(selections[id]),\n answer: textOf(answerId),\n correct: detail === undefined ? null : detailRight(detail),\n ...(typeof gap?.feedback === 'string' ? { feedback: gap.feedback } : {}),\n };\n }),\n };\n }\n case 'dictation': {\n const item = data as unknown as DictationData;\n const typed = response?.type === 'dictation' ? response.text : '';\n const hasKey = full && typeof item.transcript === 'string';\n let words: AiWordFact[] | null = null;\n if (hasKey) {\n words = alignDictation(item, typed).words.map((word) => ({\n expected: word.reference,\n typed: word.attempt,\n status: word.status,\n }));\n } else if (details !== null && details.length > 0) {\n words = details.map((detail) => {\n const expected = firstString(detail.correctResponse) ?? '';\n const attempt = firstString(detail.learnerResponse) ?? '';\n let status: 'correct' | 'incorrect' | 'missing' | 'extra' = 'incorrect';\n if (detailRight(detail)) {\n status = 'correct';\n } else if (attempt === '') {\n status = 'missing';\n } else if (expected === '') {\n status = 'extra';\n }\n return { expected, typed: attempt, status };\n });\n }\n return {\n ...base(data),\n activityType: 'dictation',\n transcript: hasKey ? item.transcript : null,\n typed,\n words,\n };\n }\n default:\n return null;\n }\n}\n\n/** The SDK's own grade of `response`, with its per-part details; `null` when it cannot grade. */\nfunction gradeLocally(\n data: AiActivityInput,\n response: LearnerResponse,\n): { details: ScoringDetail[]; grade: AiGrade } | null {\n if ((data as { redacted?: unknown }).redacted === true) {\n return null;\n }\n try {\n const result = score(data.type as never, data as unknown as ActivityData, response);\n return { details: result.details, grade: aiGradeOf(result) };\n } catch {\n return null;\n }\n}\n\n/**\n * The request for an explanation of `response`, or `null` when there is none\n * to give: an unsupported type, an author who switched explanations off,\n * nothing graded to explain, or a grade of record that cannot be a grade.\n *\n * A scored `outcome` — the grade of record a review shows — wins over grading\n * locally, so the explanation speaks to the grade on the screen. One whose\n * numbers cannot be a grade (the rule `outcomeFromGrade` applies: `maxScore`\n * positive and finite, `score` finite from 0 to it) gets no explanation at all:\n * explaining 85 \"out of 1\" would tell the model a wrong answer was correct, and\n * grading locally instead would speak to a grade the screen does not show.\n */\nexport function aiExplanationRequest(input: {\n data: AiActivityInput;\n response: LearnerResponse | null;\n outcome?: ItemOutcome;\n learnerLocale?: string;\n}): AiExplanationRequest | null {\n const { data, response, outcome, learnerLocale } = input;\n if (!aiSupports(data.type, 'explanation') || !aiAllowedByContent(data, 'explanation')) {\n return null;\n }\n let details: ScoringDetail[] | null = null;\n let grade: AiGrade | null = null;\n if (outcome?.status === 'scored') {\n // Read once, so the check and the grade handed on see the same numbers.\n const { score: recorded, maxScore, passed } = outcome;\n if (!isGradeInRange(recorded, maxScore)) {\n return null;\n }\n details = outcome.details;\n grade = aiGradeOf({ score: recorded, maxScore, passed });\n } else if (response !== null) {\n const local = gradeLocally(data, response);\n if (local !== null) {\n details = local.details;\n grade = local.grade;\n }\n }\n if (grade === null) {\n return null;\n }\n const facts = buildAiFacts(data, response, details);\n if (facts === null) {\n return null;\n }\n return {\n feature: 'explanation',\n facts,\n grade,\n ...(learnerLocale !== undefined ? { learnerLocale } : {}),\n };\n}\n\n/**\n * The request for the next hint on an unsubmitted answer, or `null` when\n * there is none to give: an unsupported type, an author who switched hints\n * off, or a redacted item — hints are for practice, where the item is whole.\n *\n * The facts carry the key, and which parts the learner has right so far, so a\n * model can hint where it helps. The learner never sees them; a hint that\n * contains an answer is refused by {@link checkAiHint}.\n */\nexport function aiHintRequest(input: {\n data: AiActivityInput;\n response: LearnerResponse | null;\n previousHints: readonly string[];\n learnerLocale?: string;\n}): AiHintRequest | null {\n const { data, previousHints, learnerLocale } = input;\n if (\n !aiSupports(data.type, 'hint') ||\n !aiAllowedByContent(data, 'hint') ||\n (data as { redacted?: unknown }).redacted === true\n ) {\n return null;\n }\n const response = input.response ?? emptyResponse(data.type);\n const local = response === null ? null : gradeLocally(data, response);\n const facts = buildAiFacts(data, response, local?.details ?? null);\n if (facts === null) {\n return null;\n }\n return {\n feature: 'hint',\n facts,\n hintNumber: previousHints.length + 1,\n previousHints: [...previousHints],\n ...(learnerLocale !== undefined ? { learnerLocale } : {}),\n };\n}\n\n// ── Checking what comes back ───────────────────────────────────────────\n\nconst VERDICTS: readonly string[] = ['correct', 'partly-correct', 'incorrect'];\n\n/** A provenance field a host may send, kept only when it is a short string. */\nfunction provenanceOf(raw: unknown): AiProvenance | undefined {\n if (typeof raw !== 'object' || raw === null) {\n return undefined;\n }\n const kept: AiProvenance = {};\n for (const key of ['model', 'promptHash', 'generatedAt'] as const) {\n const value = (raw as Record<string, unknown>)[key];\n if (typeof value === 'string' && value.length > 0 && value.length <= 200) {\n kept[key] = value;\n }\n }\n return Object.keys(kept).length > 0 ? kept : undefined;\n}\n\n/**\n * What the call cost, as a port reported it. Read on the same terms as the\n * provenance: a number that is not finite and zero or more says nothing about\n * a cost, so it is dropped rather than carried into a host's totals.\n */\nfunction usageOf(raw: unknown): GraderUsage | undefined {\n if (typeof raw !== 'object' || raw === null) {\n return undefined;\n }\n const kept: GraderUsage = {};\n for (const key of ['promptTokens', 'completionTokens', 'costUsd'] as const) {\n const value = (raw as Record<string, unknown>)[key];\n if (typeof value === 'number' && Number.isFinite(value) && value >= 0) {\n kept[key] = value;\n }\n }\n return Object.keys(kept).length > 0 ? kept : undefined;\n}\n\n/**\n * Reads what a port returned. Anything but an object with a string `text` is\n * `malformed`; text that is empty once cleaned, or longer than\n * {@link AI_TEXT_MAX_LENGTH}, is refused rather than shown or cut.\n */\nexport function readAiTextResult(\n raw: unknown,\n): { ok: true; result: AiTextResult } | { ok: false; refusal: AiRefusal } {\n if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {\n return { ok: false, refusal: 'malformed' };\n }\n const record = raw as Record<string, unknown>;\n if (typeof record.text !== 'string') {\n return { ok: false, refusal: 'malformed' };\n }\n if (record.verdict !== undefined && !VERDICTS.includes(record.verdict as string)) {\n return { ok: false, refusal: 'malformed' };\n }\n const text = cleanText(record.text);\n if (text === '') {\n return { ok: false, refusal: 'empty' };\n }\n if ([...text].length > AI_TEXT_MAX_LENGTH) {\n return { ok: false, refusal: 'too-long' };\n }\n const provenance = provenanceOf(record.provenance);\n const usage = usageOf(record.usage);\n return {\n ok: true,\n result: {\n text,\n ...(record.verdict !== undefined ? { verdict: record.verdict as AiVerdict } : {}),\n ...(provenance !== undefined ? { provenance } : {}),\n ...(usage !== undefined ? { usage } : {}),\n },\n };\n}\n\n/**\n * Checks an explanation a port returned for `request`. On top of\n * {@link readAiTextResult}: an explanation that states a verdict other than\n * the SDK's grade is refused (`contradicts-grade`) — it explains an answer\n * the learner did not get.\n */\nexport function checkAiExplanation(\n raw: unknown,\n request: AiExplanationRequest,\n): { ok: true; result: AiTextResult } | { ok: false; refusal: AiRefusal } {\n const read = readAiTextResult(raw);\n if (!read.ok) {\n return read;\n }\n if (read.result.verdict !== undefined && read.result.verdict !== request.grade.category) {\n return { ok: false, refusal: 'contradicts-grade' };\n }\n return read;\n}\n\n/**\n * Checks a hint a port returned for `request`. On top of\n * {@link readAiTextResult}: a hint that contains an answer is refused\n * (`reveals-answer`) — see {@link hintRevealsAnswer}.\n */\nexport function checkAiHint(\n raw: unknown,\n request: AiHintRequest,\n): { ok: true; result: AiTextResult } | { ok: false; refusal: AiRefusal } {\n const read = readAiTextResult(raw);\n if (!read.ok) {\n return read;\n }\n if (hintRevealsAnswer(request.facts, read.result.text)) {\n return { ok: false, refusal: 'reveals-answer' };\n }\n return read;\n}\n\n// ── The answer-leak guard ──────────────────────────────────────────────\n\n/**\n * Text folded for searching: compatibility forms read as what they stand for,\n * accents and other marks removed, lower-cased, and every run of anything but\n * letters and digits turned into one space. \"Está,\" and \"esta\" fold alike.\n */\nfunction fold(text: string): string[] {\n const folded = text\n .normalize('NFKD')\n .replace(/\\p{M}/gu, '')\n .toLowerCase()\n .replace(/[^\\p{L}\\p{N}]+/gu, ' ')\n .trim();\n return folded === '' ? [] : folded.split(' ');\n}\n\n/** Whether `needle` appears in `haystack` as a run of whole words. */\nfunction containsWords(haystack: readonly string[], needle: readonly string[]): boolean {\n if (needle.length === 0 || needle.length > haystack.length) {\n return false;\n }\n for (let start = 0; start + needle.length <= haystack.length; start += 1) {\n let match = true;\n for (let offset = 0; offset < needle.length; offset += 1) {\n if (haystack[start + offset] !== needle[offset]) {\n match = false;\n break;\n }\n }\n if (match) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * An answer too short to search for on its own: one word of fewer than four\n * characters — \"is\", \"the\", \"26\". A hint may use such a word freely; it\n * reveals the answer only when it writes it where the answer goes.\n */\nconst isShort = (words: readonly string[]): boolean =>\n words.length === 1 && [...(words[0] as string)].length < 4;\n\n/**\n * The words either side of the gap numbered `position` in a numbered\n * passage, other gaps left out.\n */\nfunction neighbours(passage: string, position: number): { before?: string; after?: string } {\n const marker = `[${position}]`;\n const at = passage.indexOf(marker);\n if (at === -1) {\n return {};\n }\n const strip = (text: string): string => text.replace(/\\[\\d+\\]/g, ' ');\n const left = fold(strip(passage.slice(0, at)));\n const right = fold(strip(passage.slice(at + marker.length)));\n const before = left[left.length - 1];\n const after = right[0];\n return {\n ...(before !== undefined ? { before } : {}),\n ...(after !== undefined ? { after } : {}),\n };\n}\n\n/**\n * Whether a hint gives away an answer, judged against the facts it was asked\n * for. It is a floor, not a proof: it finds an answer written out, not one\n * spelled letter by letter or described.\n *\n * - **Multiple choice:** the text of any correct option, as whole words.\n * - **Fill-in-the-blanks and gap select:** any accepted answer, as whole words.\n * An answer of one short word (\"is\", \"the\") counts only where the hint\n * writes it beside a neighbour it has in the passage — \"name is\", \"is\n * Rossi\" — so a hint may still use the word on its own.\n *\n * Case, accents and punctuation are ignored on both sides.\n */\nexport function hintRevealsAnswer(facts: AiItemFacts, hint: string): boolean {\n const words = fold(hint);\n const reveals = (answer: string, passage?: string, position?: number): boolean => {\n const needle = fold(answer);\n if (needle.length === 0) {\n return false;\n }\n if (!isShort(needle) || passage === undefined || position === undefined) {\n return containsWords(words, needle);\n }\n const { before, after } = neighbours(passage, position);\n if (before === undefined && after === undefined) {\n return containsWords(words, needle);\n }\n return (\n (before !== undefined && containsWords(words, [before, ...needle])) ||\n (after !== undefined && containsWords(words, [...needle, after]))\n );\n };\n switch (facts.activityType) {\n case 'multiple-choice':\n return facts.options.some((option) => option.correct === true && reveals(option.text));\n case 'fill-in-the-blanks':\n return facts.blanks.some((blank) =>\n (blank.accepted ?? []).some((answer) => reveals(answer, facts.passage, blank.position)),\n );\n case 'gap-select':\n return facts.gaps.some(\n (gap) => gap.answer !== null && reveals(gap.answer, facts.passage, gap.position),\n );\n case 'dictation':\n // Never asked: a dictation takes no AI hints (AI_HINT_TYPES).\n return false;\n default:\n return false;\n }\n}\n","import { aiAllowedByContent, aiSupports, readAiTextResult } from './ai.js';\nimport { cleanText } from './ai-text.js';\nimport { alignReadAloud } from './scoring/speech/align.js';\nimport { validateSpeechAssessment } from './scoring/speech/assessment.js';\nimport type { ReadAloudData } from './types/activity.js';\nimport type {\n AiActivityInput,\n AiCoaching,\n AiCoachingRequest,\n AiReadingFacts,\n AiReadingWordFact,\n AiRefusal,\n AiSoundFact,\n} from './types/ai.js';\nimport type { GradeRecord } from './types/grading.js';\nimport type { SpeechAssessment } from './types/speech.js';\n\n/** The most words one coaching may work on. More is refused, not cut. */\nexport const AI_COACHING_MAX_WORDS = 20;\n\n/** The longest tip for one word, in code points. */\nexport const AI_COACHING_MAX_TIP_LENGTH = 500;\n\n/** The longest sound symbol: a phoneme, not a sentence. */\nconst MAX_SYMBOL_LENGTH = 16;\n\n/** The dimensions a reading is scored on, as the facts name them. */\nconst DIMENSIONS = ['accuracy', 'fluency', 'completeness', 'prosody'] as const;\n\n/** Two decimals: what a stored score out of 1 was, out of 100, without float noise. */\nconst hundredths = (value: number): number => Math.round(value * 10000) / 100;\n\nconst isFiniteNumber = (value: unknown): value is number =>\n typeof value === 'number' && Number.isFinite(value);\n\n/** A word's sounds as the engine reported them: only those it names. */\nfunction soundsOf(word: SpeechAssessment['words'][number] | undefined): AiSoundFact[] | undefined {\n const sounds: AiSoundFact[] = [];\n for (const phoneme of word?.phonemes ?? []) {\n if (typeof phoneme.symbol !== 'string') {\n continue;\n }\n sounds.push({\n symbol: phoneme.symbol,\n ...(phoneme.accuracy !== undefined ? { accuracy: phoneme.accuracy } : {}),\n ...(phoneme.heardAs !== undefined && phoneme.heardAs.length > 0\n ? { heardAs: phoneme.heardAs.map(({ symbol, score }) => ({ symbol, score })) }\n : {}),\n });\n }\n return sounds.length > 0 ? sounds : undefined;\n}\n\n/** The facts from the engine's full assessment: every word's mark, and its sounds. */\nfunction fromAssessment(\n item: ReadAloudData,\n assessment: SpeechAssessment,\n): Pick<AiReadingFacts, 'assessor' | 'phonemeAlphabet' | 'scores' | 'words'> {\n const words: AiReadingWordFact[] = alignReadAloud(item, assessment).map((entry) => {\n const sounds =\n entry.itemId !== undefined && entry.wordIndex !== undefined\n ? soundsOf(assessment.words[entry.wordIndex])\n : undefined;\n return {\n ...(entry.itemId !== undefined ? { itemId: entry.itemId } : {}),\n word: entry.reference,\n heard: entry.heard,\n state: entry.state,\n ...(entry.accuracy !== undefined ? { accuracy: entry.accuracy } : {}),\n ...(sounds !== undefined ? { sounds } : {}),\n };\n });\n const scores: AiReadingFacts['scores'] = {};\n for (const dimension of DIMENSIONS) {\n const value = assessment.scores[dimension];\n if (value !== undefined) {\n scores[dimension] = value;\n }\n }\n return {\n assessor: assessment.assessor.kind,\n ...(assessment.phonemeAlphabet !== undefined\n ? { phonemeAlphabet: assessment.phonemeAlphabet }\n : {}),\n scores,\n words,\n };\n}\n\n/**\n * Whether an assessment holds marks on this text: one `validateSpeechAssessment`\n * accepts, of speech heard, made against the item's own text in its own locale\n * — the checks `gradeReadAloud` makes before it reads a mark. Anything else is\n * not marks on this reading, and would tell a model every word was left out.\n */\nconst marksThisText = (item: ReadAloudData, assessment: SpeechAssessment): boolean =>\n validateSpeechAssessment(assessment).success &&\n assessment.status === 'assessed' &&\n assessment.task === 'scripted' &&\n assessment.locale === item.locale &&\n assessment.referenceText === item.referenceText;\n\nconst STATE_OF_OUTCOME = {\n correct: 'correct',\n incorrect: 'mispronounced',\n 'incorrect-omission': 'omitted',\n} as const;\n\n/**\n * The facts from a stored grade alone: each word's mark and score, and no\n * sounds — a grade's details do not keep them.\n */\nfunction fromGrade(\n grade: GradeRecord,\n): Pick<AiReadingFacts, 'assessor' | 'scores' | 'words'> | null {\n if (!Array.isArray(grade.details) || grade.details.length === 0) {\n return null;\n }\n const words: AiReadingWordFact[] = [];\n for (const detail of grade.details) {\n const state = STATE_OF_OUTCOME[detail.outcome as keyof typeof STATE_OF_OUTCOME];\n if (state === undefined || typeof detail.itemId !== 'string') {\n continue;\n }\n const text = (value: string | string[]): string =>\n Array.isArray(value) ? value.join(' ') : value;\n words.push({\n itemId: detail.itemId,\n word: text(detail.correctResponse),\n heard: text(detail.learnerResponse),\n state,\n ...(state !== 'omitted' && isFiniteNumber(detail.score)\n ? { accuracy: hundredths(detail.score) }\n : {}),\n });\n }\n if (words.length === 0) {\n return null;\n }\n // Each dimension out of 100, whatever scale the grade kept it on. A criterion\n // without a scale to read it on is left out rather than given one.\n const scores: AiReadingFacts['scores'] = {};\n for (const criterion of grade.criteria ?? []) {\n const dimension = DIMENSIONS.find((name) => name === criterion.name);\n if (\n dimension !== undefined &&\n isFiniteNumber(criterion.score) &&\n isFiniteNumber(criterion.maxScore) &&\n criterion.maxScore > 0\n ) {\n scores[dimension] = hundredths(criterion.score / criterion.maxScore);\n }\n }\n const kind = grade.grader?.kind;\n return {\n assessor: kind === 'auto' || kind === 'ai' || kind === 'human' ? kind : 'unknown',\n scores,\n words,\n };\n}\n\n/**\n * The request for coaching on a graded reading, or `null` when there is none\n * to give: not a read-aloud, an author who switched explanations off (coaching\n * reads `ai.explanations`), or no marks.\n *\n * The marks come from the `assessment` when one is given — with each word's\n * sounds and what each was heard as — and then only from it: an assessment\n * `gradeReadAloud` would not read (no speech heard, unscripted, or made against\n * another text or locale) gives none, rather than a stored grade's marks the\n * learner is not looking at. With no assessment, they come from\n * `grade.details`, without sounds.\n *\n * The facts are the text as the learner saw it, those marks word by word, and\n * the engine's dimension scores. The marks are the engine's; the model\n * explains them.\n */\nexport function aiCoachingRequest(input: {\n data: AiActivityInput;\n assessment?: SpeechAssessment | null;\n grade?: GradeRecord | null;\n learnerLocale?: string;\n}): AiCoachingRequest | null {\n const { data, assessment, grade, learnerLocale } = input;\n if (\n !aiSupports(data.type, 'pronunciation-coaching') ||\n !aiAllowedByContent(data, 'pronunciation-coaching')\n ) {\n return null;\n }\n const item = data as unknown as ReadAloudData;\n if (typeof item.referenceText !== 'string' || typeof item.locale !== 'string') {\n return null;\n }\n // An assessment given is the marks, and the only ones: a learner looking at\n // it is not looking at a stored grade's, and coaching must be about what is\n // on screen.\n const marks =\n assessment !== undefined && assessment !== null\n ? marksThisText(item, assessment)\n ? fromAssessment(item, assessment)\n : null\n : grade !== undefined && grade !== null\n ? fromGrade(grade)\n : null;\n if (marks === null) {\n return null;\n }\n const graded =\n grade !== undefined &&\n grade !== null &&\n isFiniteNumber(grade.score) &&\n isFiniteNumber(grade.maxScore) &&\n typeof grade.passed === 'boolean'\n ? { score: grade.score, maxScore: grade.maxScore, passed: grade.passed }\n : undefined;\n return {\n feature: 'pronunciation-coaching',\n facts: {\n activityType: 'read-aloud',\n activityId: data.id,\n title: data.title,\n locale: item.locale,\n referenceText: item.referenceText,\n ...(typeof item.instructions === 'string' ? { instructions: item.instructions } : {}),\n ...marks,\n },\n ...(graded !== undefined ? { grade: graded } : {}),\n ...(learnerLocale !== undefined ? { learnerLocale } : {}),\n };\n}\n\n// ── Checking what comes back ───────────────────────────────────────────\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\n/** A symbol as it is compared: composed, and without the spaces around it. */\nconst symbolKey = (symbol: string): string => symbol.normalize('NFC').trim();\n\ntype Refused = { ok: false; refusal: AiRefusal };\n\n/**\n * Checks coaching a port returned for `request`, and turns it into what a\n * learner is shown. As every AI text, it is shown whole or not at all.\n *\n * - **The text** is read as every AI text is (`readAiTextResult`).\n * - **Every word coached is one the engine marked.** Each `itemId` names a word\n * of the text marked `mispronounced` or `omitted`, once; coaching on a word\n * read correctly, or one the text does not have, refuses the whole reply as\n * `contradicts-marks`. Praise belongs in `text`.\n * - **Every sound named is one the engine reported** for that word, and a sound\n * it was heard as is one the engine heard it as. Where the facts carry no\n * sounds — marks from a stored grade, or an assessor that reports none — any\n * sound is `contradicts-marks`: nothing stands behind it.\n * - **Nothing numeric is read.** A score in the reply is ignored: the marks and\n * the grade are the engine's.\n * - **Limits:** {@link AI_COACHING_MAX_WORDS} words, a tip of\n * {@link AI_COACHING_MAX_TIP_LENGTH} code points; over them `too-long`. A\n * shape that is not the one documented is `malformed`.\n *\n * The words come back in reading order, each with the word as the marks show it\n * (the SDK's reading of the text, never the model's), and each sound spelt as\n * the engine spelt it.\n */\nexport function checkAiCoaching(\n raw: unknown,\n request: AiCoachingRequest,\n): { ok: true; coaching: AiCoaching } | { ok: false; refusal: AiRefusal } {\n const read = readAiTextResult(raw);\n if (!read.ok) {\n return read;\n }\n const entries = (raw as Record<string, unknown>).words;\n const words: (AiCoaching['words'][number] & { order: number })[] = [];\n if (entries !== undefined && entries !== null) {\n if (!Array.isArray(entries)) {\n return { ok: false, refusal: 'malformed' };\n }\n if (entries.length > AI_COACHING_MAX_WORDS) {\n return { ok: false, refusal: 'too-long' };\n }\n const facts = new Map<string, { fact: AiReadingWordFact; order: number }>();\n request.facts.words.forEach((fact, order) => {\n if (fact.itemId !== undefined) {\n facts.set(fact.itemId, { fact, order });\n }\n });\n const seen = new Set<string>();\n for (const entry of entries) {\n if (!isRecord(entry) || typeof entry.itemId !== 'string' || typeof entry.tip !== 'string') {\n return { ok: false, refusal: 'malformed' };\n }\n const tip = cleanText(entry.tip);\n if (tip === '') {\n return { ok: false, refusal: 'malformed' };\n }\n if ([...tip].length > AI_COACHING_MAX_TIP_LENGTH) {\n return { ok: false, refusal: 'too-long' };\n }\n if (seen.has(entry.itemId)) {\n return { ok: false, refusal: 'malformed' };\n }\n seen.add(entry.itemId);\n const found = facts.get(entry.itemId);\n if (\n found === undefined ||\n (found.fact.state !== 'mispronounced' && found.fact.state !== 'omitted')\n ) {\n return { ok: false, refusal: 'contradicts-marks' };\n }\n const sound = checkSound(entry.sound, found.fact);\n if (sound !== undefined && 'refusal' in sound) {\n return sound;\n }\n words.push({\n itemId: entry.itemId,\n word: found.fact.word,\n tip,\n ...(sound !== undefined ? { sound } : {}),\n order: found.order,\n });\n }\n }\n const { text, provenance, usage } = read.result;\n return {\n ok: true,\n coaching: {\n text,\n words: words.sort((a, b) => a.order - b.order).map(({ order: _order, ...word }) => word),\n ...(provenance !== undefined ? { provenance } : {}),\n ...(usage !== undefined ? { usage } : {}),\n },\n };\n}\n\n/** A sound a model names, checked against the sounds the engine reported for the word. */\nfunction checkSound(\n raw: unknown,\n fact: AiReadingWordFact,\n): { expected: string; heard?: string } | Refused | undefined {\n if (raw === undefined || raw === null) {\n return undefined;\n }\n if (\n !isRecord(raw) ||\n typeof raw.expected !== 'string' ||\n (raw.heard !== undefined && raw.heard !== null && typeof raw.heard !== 'string')\n ) {\n return { ok: false, refusal: 'malformed' };\n }\n const heard = typeof raw.heard === 'string' ? raw.heard : undefined;\n if (\n [...raw.expected].length > MAX_SYMBOL_LENGTH ||\n (heard !== undefined && [...heard].length > MAX_SYMBOL_LENGTH)\n ) {\n return { ok: false, refusal: 'too-long' };\n }\n const expected = symbolKey(raw.expected);\n const reported = fact.sounds?.find((sound) => symbolKey(sound.symbol) === expected);\n if (reported === undefined) {\n return { ok: false, refusal: 'contradicts-marks' };\n }\n if (heard === undefined) {\n return { expected: reported.symbol };\n }\n const instead = reported.heardAs?.find(\n (candidate) => symbolKey(candidate.symbol) === symbolKey(heard),\n );\n if (instead === undefined) {\n return { ok: false, refusal: 'contradicts-marks' };\n }\n return { expected: reported.symbol, heard: instead.symbol };\n}\n","import { aiAllowedByContent, aiSupports, readAiTextResult } from './ai.js';\nimport { cleanText } from './ai-text.js';\nimport { countWords } from './count-words.js';\nimport { gradeFromRubric } from './grading.js';\nimport type { LearnerResponse, WrittenResponseData } from './types/activity.js';\nimport type {\n AiActivityInput,\n AiRefusal,\n AiRubricCriterionFact,\n AiWritingFeedback,\n AiWritingFeedbackRequest,\n} from './types/ai.js';\nimport type { CriterionScore, InlineCorrection } from './types/grading.js';\n\n/** The most corrections one piece of writing feedback may carry. More is refused, not cut. */\nexport const AI_WRITING_MAX_CORRECTIONS = 20;\n\n/** The longest quote, correction, explanation or criterion comment, in code points. */\nexport const AI_WRITING_MAX_FIELD_LENGTH = 500;\n\n/** The longest `category` or `band`: a tag, not a sentence. */\nconst MAX_TAG_LENGTH = 40;\n\n/**\n * The request for feedback on a draft, or `null` when there is none to give:\n * not a written response, an author who switched explanations off (writing\n * feedback reads `ai.explanations`), a redacted item — feedback is for\n * practice, where the item is whole — or a draft with nothing in it.\n *\n * The facts are the task as the learner saw it, the draft verbatim with its\n * word count recomputed as the grader will count it, and the rubric the answer\n * will be graded on. `previousFeedback` is the text of the feedback already\n * shown on earlier drafts, so a model can speak to what changed.\n */\nexport function aiWritingFeedbackRequest(input: {\n data: AiActivityInput;\n response: LearnerResponse | null;\n previousFeedback?: readonly string[];\n learnerLocale?: string;\n}): AiWritingFeedbackRequest | null {\n const { data, response, learnerLocale } = input;\n if (\n !aiSupports(data.type, 'writing-feedback') ||\n !aiAllowedByContent(data, 'writing-feedback') ||\n (data as { redacted?: unknown }).redacted === true\n ) {\n return null;\n }\n const text = response?.type === 'written-response' ? response.text : '';\n if (typeof text !== 'string' || text.trim() === '') {\n return null;\n }\n const item = data as unknown as WrittenResponseData;\n const wordCount = countWords(text);\n const minWords = typeof item.minWords === 'number' ? item.minWords : 0;\n const maxWords = typeof item.maxWords === 'number' ? item.maxWords : Number.POSITIVE_INFINITY;\n const criteria = Array.isArray(item.rubric?.criteria) ? item.rubric.criteria : null;\n const rubric: AiRubricCriterionFact[] | null =\n criteria === null || criteria.length === 0\n ? null\n : criteria.map((criterion) => ({\n name: criterion.name,\n ...(typeof criterion.description === 'string'\n ? { description: criterion.description }\n : {}),\n weight: criterion.weight,\n }));\n const previousFeedback = [...(input.previousFeedback ?? [])];\n return {\n feature: 'writing-feedback',\n facts: {\n activityType: 'written-response',\n activityId: data.id,\n title: data.title,\n ...(typeof data.locale === 'string' ? { locale: data.locale } : {}),\n prompt: typeof item.prompt === 'string' ? item.prompt : '',\n text,\n wordCount,\n minWords,\n maxWords,\n withinWordBounds: wordCount >= minWords && wordCount <= maxWords,\n rubric,\n ...(typeof item.languageTarget === 'string' ? { languageTarget: item.languageTarget } : {}),\n },\n draftNumber: previousFeedback.length + 1,\n previousFeedback,\n ...(learnerLocale !== undefined ? { learnerLocale } : {}),\n };\n}\n\n// ── Finding a quote in the draft ───────────────────────────────────────\n\n/** Typographic quotes a model writes for the plain ones a learner typed, and back. */\nconst QUOTE_FOLD: Readonly<Record<string, string>> = {\n '‘': \"'\",\n '’': \"'\",\n '‛': \"'\",\n '′': \"'\",\n '“': '\"',\n '”': '\"',\n '„': '\"',\n '″': '\"',\n};\n\n/**\n * Text as a quote is matched against it: typographic quotes folded to plain\n * ones and every run of whitespace one space — the two liberties a model takes\n * when it copies words out — with, for each folded character, where it came\n * from, so a match maps back to the learner's own text. Case is kept: a\n * correction of \"i\" to \"I\" is about case.\n */\nfunction folded(text: string): { chars: string; from: number[] } {\n let chars = '';\n const from: number[] = [];\n let space = false;\n for (let at = 0; at < text.length; at += 1) {\n const char = text[at] as string;\n if (/\\s/u.test(char)) {\n if (!space) {\n chars += ' ';\n from.push(at);\n space = true;\n }\n continue;\n }\n space = false;\n chars += QUOTE_FOLD[char] ?? char;\n from.push(at);\n }\n return { chars, from };\n}\n\n/** A quote as it is searched for: folded, and without the spaces around it. */\nconst needle = (quote: string): string => folded(quote).chars.trim();\n\n/**\n * Where `quote` sits in the draft: at `claimed`, when the model named a range —\n * which must then hold the quote — or else at its first occurrence not already\n * taken by an identical quote, so a mistake made twice is anchored twice. A\n * quote corrected more often than it occurs takes its first occurrence again.\n * `null` when the draft does not contain it where claimed, or at all.\n */\nfunction anchor(\n draft: { text: string; chars: string; from: number[] },\n quote: string,\n claimed: { start: number; end: number } | undefined,\n taken: Set<string>,\n): { start: number; end: number } | null {\n const sought = needle(quote);\n if (sought === '') {\n return null;\n }\n if (claimed !== undefined) {\n const { start, end } = claimed;\n if (\n !Number.isSafeInteger(start) ||\n !Number.isSafeInteger(end) ||\n start < 0 ||\n end > draft.text.length ||\n start >= end\n ) {\n return null;\n }\n const span = draft.text.slice(start, end);\n if (needle(span) !== sought) {\n return null;\n }\n // The span, without whitespace the model's range took in at either end.\n const lead = span.length - span.trimStart().length;\n const trail = span.length - span.trimEnd().length;\n return { start: start + lead, end: end - trail };\n }\n let first: { start: number; end: number } | null = null;\n for (let at = draft.chars.indexOf(sought); at !== -1; at = draft.chars.indexOf(sought, at + 1)) {\n const start = draft.from[at] as number;\n const end = (draft.from[at + sought.length - 1] as number) + 1;\n const found = { start, end };\n first ??= found;\n const key = `${start}:${end}`;\n if (!taken.has(key)) {\n taken.add(key);\n return found;\n }\n }\n return first;\n}\n\n// ── Checking what comes back ───────────────────────────────────────────\n\n/** A field that must be a string, at most `max` code points once cleaned; `undefined` when absent. */\nfunction textField(\n value: unknown,\n max: number,\n): { ok: true; value: string | undefined } | { ok: false; refusal: AiRefusal } {\n if (value === undefined || value === null) {\n return { ok: true, value: undefined };\n }\n if (typeof value !== 'string') {\n return { ok: false, refusal: 'malformed' };\n }\n const cleaned = cleanText(value);\n if ([...cleaned].length > max) {\n return { ok: false, refusal: 'too-long' };\n }\n return { ok: true, value: cleaned === '' ? undefined : cleaned };\n}\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\n/** The corrections, each anchored in the draft, or why the reply is refused. */\nfunction checkCorrections(\n raw: unknown,\n text: string,\n): { ok: true; corrections: InlineCorrection[] } | { ok: false; refusal: AiRefusal } {\n if (raw === undefined || raw === null) {\n return { ok: true, corrections: [] };\n }\n if (!Array.isArray(raw)) {\n return { ok: false, refusal: 'malformed' };\n }\n if (raw.length > AI_WRITING_MAX_CORRECTIONS) {\n return { ok: false, refusal: 'too-long' };\n }\n const draft = { text, ...folded(text) };\n const taken = new Set<string>();\n const corrections: InlineCorrection[] = [];\n for (const entry of raw) {\n if (!isRecord(entry) || typeof entry.original !== 'string') {\n return { ok: false, refusal: 'malformed' };\n }\n if (typeof entry.corrected !== 'string') {\n return { ok: false, refusal: 'malformed' };\n }\n if (\n [...entry.original].length > AI_WRITING_MAX_FIELD_LENGTH ||\n [...entry.corrected].length > AI_WRITING_MAX_FIELD_LENGTH\n ) {\n return { ok: false, refusal: 'too-long' };\n }\n // A correction that changes nothing corrects nothing.\n if (needle(entry.original) === needle(entry.corrected)) {\n return { ok: false, refusal: 'malformed' };\n }\n const explanation = textField(entry.explanation, AI_WRITING_MAX_FIELD_LENGTH);\n const category = textField(entry.category, MAX_TAG_LENGTH);\n if (!explanation.ok) {\n return explanation;\n }\n if (!category.ok) {\n return category;\n }\n let claimed: { start: number; end: number } | undefined;\n if (entry.range !== undefined && entry.range !== null) {\n if (!isRecord(entry.range)) {\n return { ok: false, refusal: 'malformed' };\n }\n claimed = { start: entry.range.start as number, end: entry.range.end as number };\n }\n const at = anchor(draft, entry.original, claimed, taken);\n if (at === null) {\n return { ok: false, refusal: 'misquotes-answer' };\n }\n corrections.push({\n original: text.slice(at.start, at.end),\n corrected: cleanText(entry.corrected),\n ...(explanation.value !== undefined ? { explanation: explanation.value } : {}),\n ...(category.value !== undefined ? { category: category.value } : {}),\n range: at,\n });\n }\n return { ok: true, corrections };\n}\n\n/** A criterion score a grade could be computed from: finite, 0 or more, and not above its maximum. */\nfunction isJudgedScore(score: unknown, maxScore: unknown): boolean {\n if (score === undefined) {\n return maxScore === undefined || (typeof maxScore === 'number' && maxScore > 0);\n }\n const max = maxScore ?? 1;\n return (\n typeof score === 'number' &&\n typeof max === 'number' &&\n Number.isFinite(score) &&\n Number.isFinite(max) &&\n max > 0 &&\n score >= 0 &&\n score - max <= max * 1e-9\n );\n}\n\n/** The judgements, each on a criterion the author wrote and weighted as the author weighted it. */\nfunction checkCriteria(\n raw: unknown,\n rubric: readonly AiRubricCriterionFact[] | null,\n): { ok: true; criteria: CriterionScore[] } | { ok: false; refusal: AiRefusal } {\n if (raw === undefined || raw === null) {\n return { ok: true, criteria: [] };\n }\n if (!Array.isArray(raw)) {\n return { ok: false, refusal: 'malformed' };\n }\n if (raw.length === 0) {\n return { ok: true, criteria: [] };\n }\n // Judging a criterion the author did not write is judging a different task.\n if (rubric === null) {\n return { ok: false, refusal: 'malformed' };\n }\n const weights = new Map(rubric.map((criterion) => [criterion.name, criterion.weight]));\n const seen = new Set<string>();\n const criteria: CriterionScore[] = [];\n for (const entry of raw) {\n if (!isRecord(entry) || typeof entry.name !== 'string') {\n return { ok: false, refusal: 'malformed' };\n }\n const weight = weights.get(entry.name);\n if (weight === undefined || seen.has(entry.name)) {\n return { ok: false, refusal: 'malformed' };\n }\n seen.add(entry.name);\n if (!isJudgedScore(entry.score, entry.maxScore)) {\n return { ok: false, refusal: 'malformed' };\n }\n if (entry.notApplicable !== undefined && typeof entry.notApplicable !== 'boolean') {\n return { ok: false, refusal: 'malformed' };\n }\n const band = textField(entry.band, MAX_TAG_LENGTH);\n const comment = textField(entry.comment, AI_WRITING_MAX_FIELD_LENGTH);\n if (!band.ok) {\n return band;\n }\n if (!comment.ok) {\n return comment;\n }\n criteria.push({\n name: entry.name,\n ...(typeof entry.score === 'number' ? { score: entry.score } : {}),\n ...(typeof entry.maxScore === 'number' ? { maxScore: entry.maxScore } : {}),\n ...(band.value !== undefined ? { band: band.value } : {}),\n ...(comment.value !== undefined ? { comment: comment.value } : {}),\n ...(entry.notApplicable === true ? { notApplicable: true } : {}),\n weight,\n });\n }\n return { ok: true, criteria };\n}\n\n/**\n * Checks writing feedback a port returned for `request`, and turns it into what\n * a learner is shown. The SDK shows a model's feedback whole or not at all: it\n * never drops the part it doubts and shows the rest.\n *\n * - **The text** is read as every AI text is (`readAiTextResult`): plain text,\n * not empty, at most {@link AI_TEXT_MAX_LENGTH} code points.\n * - **Every correction must quote the draft** — the check only this SDK can run.\n * The SDK finds each quote in the draft itself, typographic quotes and runs of\n * whitespace aside, and anchors it there; a range the model claims must hold\n * the quote. A correction of words the learner did not write refuses the\n * whole reply as `misquotes-answer`, as a hint that gives the answer away\n * refuses the hint. The `original` shown is the draft's own text at the range.\n * At most {@link AI_WRITING_MAX_CORRECTIONS}; a correction that changes\n * nothing is `malformed`.\n * - **Criteria are judgements; the arithmetic is the SDK's.** Each names a\n * criterion of the item's rubric, once; its score, when it has one, is a\n * score out of its `maxScore`. The rubric's weights are attached, whatever\n * the model sent. Anything else is `malformed`, as is any criterion on an\n * item with no rubric.\n * - **`indicativeScore`** is the rubric's weighted total, computed as\n * `gradeFromRubric` computes a grade, when every criterion of the rubric was\n * judged; `null` otherwise. An indication, never a grade.\n */\nexport function checkAiWritingFeedback(\n raw: unknown,\n request: AiWritingFeedbackRequest,\n): { ok: true; feedback: AiWritingFeedback } | { ok: false; refusal: AiRefusal } {\n const read = readAiTextResult(raw);\n if (!read.ok) {\n return read;\n }\n const record = raw as Record<string, unknown>;\n const corrections = checkCorrections(record.corrections, request.facts.text);\n if (!corrections.ok) {\n return corrections;\n }\n const rubric = request.facts.rubric;\n const criteria = checkCriteria(record.criteria, rubric);\n if (!criteria.ok) {\n return criteria;\n }\n let indicativeScore: number | null = null;\n const judged = new Set(criteria.criteria.map((criterion) => criterion.name));\n if (rubric?.every((criterion) => judged.has(criterion.name))) {\n const grade = gradeFromRubric(criteria.criteria);\n indicativeScore = 'unscorable' in grade ? null : grade.score;\n }\n const { text, provenance, usage } = read.result;\n return {\n ok: true,\n feedback: {\n text,\n corrections: corrections.corrections,\n criteria: criteria.criteria,\n indicativeScore,\n ...(provenance !== undefined ? { provenance } : {}),\n ...(usage !== undefined ? { usage } : {}),\n },\n };\n}\n"]}
|