@intellectif/lk-core 0.19.0 → 0.21.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 +47 -0
- package/README.md +31 -2
- package/dist/{activity-f0bgZSTP.d.cts → activity-xhGV-Aeh.d.cts} +31 -4
- package/dist/{activity-f0bgZSTP.d.ts → activity-xhGV-Aeh.d.ts} +31 -4
- package/dist/ai-check.cjs +5 -5
- package/dist/ai-check.d.cts +1 -1
- package/dist/ai-check.d.ts +1 -1
- package/dist/ai-check.js +2 -2
- package/dist/{chunk-BIJCO2HM.js → chunk-IHOEP4EB.js} +2 -2
- package/dist/{chunk-FVZJ4X3Y.cjs → chunk-NRTP7WCW.cjs} +4 -4
- package/dist/{chunk-FVZJ4X3Y.cjs.map → chunk-NRTP7WCW.cjs.map} +1 -1
- package/dist/{chunk-MMEQKFFQ.js → chunk-Q7MV3IXJ.js} +196 -8
- package/dist/chunk-Q7MV3IXJ.js.map +1 -0
- package/dist/{chunk-COBK2RUX.cjs → chunk-WQVZPIN7.cjs} +197 -9
- package/dist/chunk-WQVZPIN7.cjs.map +1 -0
- package/dist/{index-bpuDhvXV.d.cts → index-BuD8Hc8o.d.cts} +1 -1
- package/dist/{index-DMCM3e3P.d.ts → index-DnJ_PI4d.d.ts} +1 -1
- package/dist/index.cjs +175 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +177 -7
- package/dist/index.d.ts +177 -7
- package/dist/index.js +171 -5
- package/dist/index.js.map +1 -1
- package/dist/schemas.d.cts +2 -2
- package/dist/schemas.d.ts +2 -2
- package/dist/scoring.cjs +14 -2
- package/dist/scoring.cjs.map +1 -1
- package/dist/scoring.d.cts +240 -4
- package/dist/scoring.d.ts +240 -4
- package/dist/scoring.js +13 -1
- package/dist/xapi.d.cts +1 -1
- package/dist/xapi.d.ts +1 -1
- package/package.json +1 -1
- package/vectors/README.md +10 -0
- package/vectors/scoring.json +2127 -1
- package/dist/chunk-COBK2RUX.cjs.map +0 -1
- package/dist/chunk-MMEQKFFQ.js.map +0 -1
- /package/dist/{chunk-BIJCO2HM.js.map → chunk-IHOEP4EB.js.map} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,52 @@
|
|
|
1
1
|
# @intellectif/lk-core
|
|
2
2
|
|
|
3
|
+
## 0.21.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#78](https://github.com/intellectif/learning-kit/pull/78) [`30a9b3d`](https://github.com/intellectif/learning-kit/commit/30a9b3da9be60589d63516752a02cc2f44bdf73b) Thanks [@diego28e](https://github.com/diego28e)! - **Scoring policies**: "Try again" in practice, which try counts, and what tries and hints cost — set per paper, recorded in the plan, and no grade moves without one.
|
|
8
|
+
|
|
9
|
+
**Action required:** only if you build an `InteractiveVideoQuestion` by hand (add `scoring`, e.g. `DEFAULT_ITEM_SCORING_POLICY`) or implement a complete `LkStrings` of your own (nine new keys). Everyone else upgrades with no change.
|
|
10
|
+
|
|
11
|
+
- **lk-core:** `ItemScoringPolicy` — `retries` (0–10), `counts` (`'first'` by default, `'best'`, `'last'`), `retryPenalty` and `hintPenalty` (fractions of the marks). `scoreTries` does the arithmetic: a cost is subtracted, never below zero, with floating-point residue under a pass line removed. `evaluate(item, response, { scoring })` charges the response's hints; `evaluateTries(item, responses, { scoring })` picks the try that counts. `validateItemScoringPolicy` and `resolveItemScoringPolicy` refuse a policy that cannot be applied — a `RangeError`, never a guess. `planAttempt(…, { scoring })` records it in the plan and `planHash`, and `verifyAttemptPlan` reports `scoringChanged`. With no policy every number is what it was; 43 new grade vectors pin the arithmetic.
|
|
12
|
+
- **Responses:** multiple-choice, fill-in-the-blanks and gap-select responses gain an optional `hintsRevealed`, as dictation's already had: in `practice`, the hints a learner was shown, counted from the start of the question.
|
|
13
|
+
- **lk-react:** `scoring` on every activity, `<ActivitySequence>` and `<InteractiveVideo>`; the paper's policy wins over a question's. "Try again" after an answer short of full marks keeps the answer and clears the marks; "Show answer" (or "Keep this answer") ends the tries. While a try is on offer the right answer and "Explain my answer" wait. `onComplete` fires after every try with the question's score so far. A set waits for any question being tried again, and for the question on screen while it offers a try, and closes every question's tries once it reports; the video closes them at Finish.
|
|
14
|
+
- **Fixed:** a dictation under `delivery={{ solutions: false }}` hid "Show solution" but its marks still named every word they corrected. They now say a word is wrong or missing without naming it, and draw no letter diff.
|
|
15
|
+
- Nine new strings: `tryAgain`, `showAnswer`, `keepAnswer`, `triesLeft`, `countedTry`, `scoreBeforeCosts`, `hintCost`, `dictationWordWrongUnnamed`, `dictationWordMissingUnnamed`.
|
|
16
|
+
|
|
17
|
+
Guide: https://github.com/intellectif/learning-kit/blob/main/docs/scoring.md
|
|
18
|
+
|
|
19
|
+
## 0.20.0
|
|
20
|
+
|
|
21
|
+
### Minor Changes
|
|
22
|
+
|
|
23
|
+
- 73a37e3: Delivery policies: what a school lets a learner see around a question — feedback, solutions, hints
|
|
24
|
+
and AI help — decided per paper rather than per item, so one question serves a practice lesson and a
|
|
25
|
+
final exam without being authored twice.
|
|
26
|
+
|
|
27
|
+
- **`DeliveryPolicy`** — `{ feedback?, solutions?, hints?, ai?: { hints?, explanations? } }`, with
|
|
28
|
+
`ai: false` as shorthand for both AI settings off. Every setting is a restriction that defaults to
|
|
29
|
+
`true`, so an empty policy is exactly the behaviour before policies existed; nothing can be switched
|
|
30
|
+
on.
|
|
31
|
+
- **`resolveDeliveryPolicy(policy)`** spells a policy out, every setting a boolean. It never throws:
|
|
32
|
+
`null` is unset, and any other value that is not exactly `true` restricts — a `"false"` from a form
|
|
33
|
+
means off, not on.
|
|
34
|
+
- **`validateDeliveryPolicy(policy)`** checks a policy before it is stored and names each problem at
|
|
35
|
+
its path, a misspelled setting included.
|
|
36
|
+
- **`combineDeliveryPolicies(...policies)`** keeps a setting on only where every policy leaves it on.
|
|
37
|
+
- **`OPEN_DELIVERY_POLICY`**, the policy that restricts nothing.
|
|
38
|
+
- **`planAttempt(entries, { delivery })`** records the policy the attempt is sat under as
|
|
39
|
+
`plan.delivery`, spelled out, and includes it in `planHash`. It throws on a policy that would not
|
|
40
|
+
validate. **A plan made without one is byte-identical to before, `planHash` included.**
|
|
41
|
+
- **`verifyAttemptPlan`** reports `deliveryChanged: true` when two plans were made under different
|
|
42
|
+
policies, and `matches` is then `false`. The field is present only when true, so every drift report
|
|
43
|
+
between plans without a policy keeps exactly its old shape.
|
|
44
|
+
|
|
45
|
+
No grade changes: nothing here reaches a scorer. Hint penalties, `ItemScoringPolicy` and retries —
|
|
46
|
+
which do — come next, with grade vectors.
|
|
47
|
+
|
|
48
|
+
See `docs/delivery.md`.
|
|
49
|
+
|
|
3
50
|
## 0.19.0
|
|
4
51
|
|
|
5
52
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -26,7 +26,9 @@ npm install @intellectif/lk-core
|
|
|
26
26
|
- **Deferred grading, both directions** — `GradeRecord`, `gradeFromRubric()` (the weighted total as a pure function of the grader's judgements — never the model's arithmetic; declare `maxScore` per criterion if your grader works out of 100 or out of 9), `outcomeFromGrade()` (which refuses a record whose numbers cannot be a grade instead of mirroring them), `hasGrade()`.
|
|
27
27
|
- **Assessment composition** — `composeAssessmentScore()` for weighted, sectioned totals, with a `provisional` status so an unmarked essay never deflates a grade — and a grade out of contract (`NaN`, 85 "out of 1") never finalises one — and `RoundingPolicy` / `roundGrade` / `gte` / `classifyBand` keeping grade rounding and band classification as the two distinct operations they are.
|
|
28
28
|
- **Item groups** — `Stimulus` + `ItemGroup` (one passage or recording serving several questions), `flattenSequence()` for seeded, shuffle-atomic presentation order with stable slot identity. Slot ids are positional by default (`"3"`, `"1.0"`); author a `slotKey` on an entry and its id survives an insert, so a stored response cannot slide onto a different question.
|
|
29
|
-
- **
|
|
29
|
+
- **Delivery policies** — `DeliveryPolicy` says what a school lets a learner see around a question: feedback, solutions, hints and AI help. `resolveDeliveryPolicy` spells one out (an unreadable setting restricts, never allows), `validateDeliveryPolicy` checks one before it is stored, and `combineDeliveryPolicies` keeps a setting on only where every policy leaves it on.
|
|
30
|
+
- **Scoring policies** — `ItemScoringPolicy` says how a paper's questions are scored when a learner can try again or ask for hints: `retries`, which try `counts`, and what a try and a hint cost. `scoreTries` does the arithmetic, `evaluate(…, { scoring })` and `evaluateTries` reach a question's outcome from its answers, and `validateItemScoringPolicy` / `resolveItemScoringPolicy` refuse a policy that cannot be applied rather than guess — every setting moves a grade. No policy changes nothing, and the arithmetic is pinned by the grade vectors.
|
|
31
|
+
- **Attempt plans** — `planAttempt(entries, { seed, shuffleEntries, points, delivery })` freezes the exact paper a learner sat, and the delivery policy it was sat under: slot order, per-slot points, a content hash of each item, and a `planHash` over the lot. `verifyAttemptPlan(plan, current)` reports the drift when that content is later edited (`changedSlotIds`, `changedStimulusSlotIds`, `changedPointsSlotIds`, `missingSlotIds`, …) — the thing you need during a remark or an appeal. `scoredItemsFromPlan(plan, outcomes)` turns collected outcomes into the `ScoredItem[]` composition wants, and defaults a slot with **no** outcome to `deferred` rather than silently shrinking the denominator.
|
|
30
32
|
- **Attempt state** — `serializeAttemptState(plan, progress)` / `restoreAttemptState(plan, snapshot)` round-trip an interrupted attempt (responses, submitted slots, position) and are **bound to the plan**: restoring answers onto a paper the learner never sat throws instead of succeeding quietly. `diffResponses(before, after)` reports what moved between two snapshots, including an answer the learner *cleared*.
|
|
31
33
|
- **Media playback policy** — `media.playback` on an audio recording: `maxPlays`, `seek`, `rate`, and advisory `nativeControlHints`. `resolvePlaybackPolicy()` derives the defaults once so the schema, the plan and the renderer cannot drift. Budgets are frozen into the plan by `planAttempt`, so editing `maxPlays` mid-window cannot change what a past learner was held to, and `planAttempt` refuses a paper where one recording is budgeted under two keys.
|
|
32
34
|
- **Play ledger** — `serializeMediaPlayLedger` / `restoreMediaPlayLedger` / `planMediaBudgets`, with `slotMediaKey()` / `stimulusMediaKey()` for the keys. Bound to its paper by `planHash`, and deliberately **separate** from `AttemptState`: a play is charged immediately while answers autosave on a debounce, and a build that predates the ledger would rewrite an `AttemptState` snapshot without the counts and erase them.
|
|
@@ -124,12 +126,39 @@ and content hashing — those have no subpath of their own.
|
|
|
124
126
|
All exports ship as ESM + CJS with `.d.ts` types. Tree-shakeable; `sideEffects: false`. Node >= 20. In browsers, the SDK's own code needs `Object.hasOwn` — Chrome and Edge 93, Firefox 92, Safari 15.4 — because output targets ES2022 and nothing is polyfilled.
|
|
125
127
|
Zod is the single runtime dependency: the package depends on `zod@^3.25` and imports the **Zod 4 API** from its `zod/v4` subpath, so it coexists with an app still on Zod 3.
|
|
126
128
|
|
|
129
|
+
## Releases & upgrading
|
|
130
|
+
|
|
131
|
+
- **What is supported, and since when** — [Features](https://github.com/intellectif/learning-kit/blob/main/docs/features.md): every activity and capability, the `lk-core` / `lk-react` versions it arrived in, and its guide.
|
|
132
|
+
- **What changed** — [GitHub Releases](https://github.com/intellectif/learning-kit/releases), one per package version, and the same notes in each package's `CHANGELOG.md` (it ships in the npm package, so it is in your `node_modules` too). Notes written since release notes gained a template — every release after lk-core 0.20.0 / lk-react 20.0.0 — start with a one-line summary and **Action required**: `none`, or what to do. Earlier notes lead with the detail; the version map in the upgrading guide says what each one asks of you.
|
|
133
|
+
- **How to upgrade** — [Upgrading](https://github.com/intellectif/learning-kit/blob/main/docs/upgrading.md): which `lk-core` goes with which `lk-react`, and a section per release saying what, if anything, you need to change. A `lk-react` major is often only the `lk-core` peer bump; the map says so.
|
|
134
|
+
- **Hearing about a release** — on GitHub, **Watch → Custom → Releases**, or subscribe to [the releases feed](https://github.com/intellectif/learning-kit/releases.atom). Better still, let your dependency bot bring each release to you as a pull request, with its notes — and keep the two packages together in one, since they move together:
|
|
135
|
+
|
|
136
|
+
```yaml
|
|
137
|
+
# .github/dependabot.yml — one entry per directory with a package.json
|
|
138
|
+
version: 2
|
|
139
|
+
updates:
|
|
140
|
+
- package-ecosystem: npm
|
|
141
|
+
directory: /
|
|
142
|
+
schedule: { interval: weekly }
|
|
143
|
+
groups:
|
|
144
|
+
learning-kit:
|
|
145
|
+
patterns: ["@intellectif/lk-*"]
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
With Renovate: `"packageRules": [{ "matchPackageNames": ["@intellectif/lk-core", "@intellectif/lk-react"], "groupName": "learning-kit" }]`.
|
|
149
|
+
|
|
150
|
+
`lk-core` is still `0.x`, and a caret range on a `0.x` version matches only that minor: `^0.18.0` never installs `0.19.0`. A bot is how you find out there is one.
|
|
151
|
+
|
|
127
152
|
## Documentation
|
|
128
153
|
|
|
129
|
-
- [
|
|
154
|
+
- [Features](https://github.com/intellectif/learning-kit/blob/main/docs/features.md) — everything supported, and the versions each arrived in.
|
|
155
|
+
- [Upgrading](https://github.com/intellectif/learning-kit/blob/main/docs/upgrading.md) — which `lk-core` goes with which `lk-react`, and what each release asks of you.
|
|
130
156
|
- [Changelog](https://github.com/intellectif/learning-kit/blob/main/packages/lk-core/CHANGELOG.md) — every release, with the reasoning.
|
|
131
157
|
- [Grade-stability vectors](https://github.com/intellectif/learning-kit/blob/main/packages/lk-core/vectors/README.md) — the package's grading frozen as data; replay it against the build you install.
|
|
132
158
|
- [Authoring & content storage](https://github.com/intellectif/learning-kit/blob/main/docs/authoring.md) — data model, validation, fetch → validate → render flow, and building an editor (the draft issue codes).
|
|
159
|
+
- [Delivery policies](https://github.com/intellectif/learning-kit/blob/main/docs/delivery.md) — `DeliveryPolicy`, and recording it with an attempt.
|
|
160
|
+
- [Scoring policies](https://github.com/intellectif/learning-kit/blob/main/docs/scoring.md) — `ItemScoringPolicy`: tries, which counts, what hints cost, and scoring them on a server.
|
|
161
|
+
- [AI help for learners](https://github.com/intellectif/learning-kit/blob/main/docs/ai.md) — the AI contract, the checks, and the `ai-check` kit for your prompts.
|
|
133
162
|
- [Project README](https://github.com/intellectif/learning-kit#readme) — the full picture, including the React renderers.
|
|
134
163
|
- [Contributing](https://github.com/intellectif/learning-kit/blob/main/CONTRIBUTING.md) — adding a new activity type.
|
|
135
164
|
|
|
@@ -1253,12 +1253,28 @@ interface MultipleChoiceLearnerResponse {
|
|
|
1253
1253
|
type: 'multiple-choice';
|
|
1254
1254
|
/** IDs of the options the learner selected. */
|
|
1255
1255
|
selectedOptionIds: string[];
|
|
1256
|
+
/**
|
|
1257
|
+
* Hints the learner was shown before submitting this answer, counted from
|
|
1258
|
+
* the start of the question — the author's and AI hints alike. Written by
|
|
1259
|
+
* the SDK's components in `practice`; absent or 0 means none. A scoring
|
|
1260
|
+
* policy's `hintPenalty` charges for them. Client-reported, like any count
|
|
1261
|
+
* a browser makes.
|
|
1262
|
+
*/
|
|
1263
|
+
hintsRevealed?: number;
|
|
1256
1264
|
}
|
|
1257
1265
|
/** Learner response for a Fill-in-the-Blanks activity. */
|
|
1258
1266
|
interface FillInTheBlanksLearnerResponse {
|
|
1259
1267
|
type: 'fill-in-the-blanks';
|
|
1260
1268
|
/** Map of blank ID to the learner's typed answer. */
|
|
1261
1269
|
answers: Record<string, string>;
|
|
1270
|
+
/**
|
|
1271
|
+
* Hints the learner was shown before submitting this answer, counted from
|
|
1272
|
+
* the start of the question — the author's and AI hints alike. Written by
|
|
1273
|
+
* the SDK's components in `practice`; absent or 0 means none. A scoring
|
|
1274
|
+
* policy's `hintPenalty` charges for them. Client-reported, like any count
|
|
1275
|
+
* a browser makes.
|
|
1276
|
+
*/
|
|
1277
|
+
hintsRevealed?: number;
|
|
1262
1278
|
}
|
|
1263
1279
|
/**
|
|
1264
1280
|
* Learner response for a Gap Select activity.
|
|
@@ -1273,6 +1289,14 @@ interface GapSelectLearnerResponse {
|
|
|
1273
1289
|
type: 'gap-select';
|
|
1274
1290
|
/** Map of gap ID to the ID of the choice the learner selected. */
|
|
1275
1291
|
selections: Record<string, string>;
|
|
1292
|
+
/**
|
|
1293
|
+
* Hints the learner was shown before submitting this answer, counted from
|
|
1294
|
+
* the start of the question — the author's and AI hints alike. Written by
|
|
1295
|
+
* the SDK's components in `practice`; absent or 0 means none. A scoring
|
|
1296
|
+
* policy's `hintPenalty` charges for them. Client-reported, like any count
|
|
1297
|
+
* a browser makes.
|
|
1298
|
+
*/
|
|
1299
|
+
hintsRevealed?: number;
|
|
1276
1300
|
}
|
|
1277
1301
|
/** Learner response for a Written Response activity. */
|
|
1278
1302
|
interface WrittenResponseLearnerResponse {
|
|
@@ -1286,15 +1310,18 @@ interface WrittenResponseLearnerResponse {
|
|
|
1286
1310
|
* Learner response for a Dictation activity.
|
|
1287
1311
|
*
|
|
1288
1312
|
* `text` is exactly what the learner typed; every normalisation happens in the
|
|
1289
|
-
* scorer. `hintsRevealed` is client-reported
|
|
1290
|
-
*
|
|
1291
|
-
*
|
|
1313
|
+
* scorer. `hintsRevealed` is client-reported — how many hint words were shown
|
|
1314
|
+
* before submitting. The scorer ignores it; a scoring policy's `hintPenalty`
|
|
1315
|
+
* charges for it, knowing it cannot be verified.
|
|
1292
1316
|
*/
|
|
1293
1317
|
interface DictationLearnerResponse {
|
|
1294
1318
|
type: 'dictation';
|
|
1295
1319
|
/** The learner's text, as typed. */
|
|
1296
1320
|
text: string;
|
|
1297
|
-
/**
|
|
1321
|
+
/**
|
|
1322
|
+
* Hint words shown before submitting (practice only), counted from the start
|
|
1323
|
+
* of the question. Absent or 0 means none.
|
|
1324
|
+
*/
|
|
1298
1325
|
hintsRevealed?: number;
|
|
1299
1326
|
}
|
|
1300
1327
|
/**
|
|
@@ -1253,12 +1253,28 @@ interface MultipleChoiceLearnerResponse {
|
|
|
1253
1253
|
type: 'multiple-choice';
|
|
1254
1254
|
/** IDs of the options the learner selected. */
|
|
1255
1255
|
selectedOptionIds: string[];
|
|
1256
|
+
/**
|
|
1257
|
+
* Hints the learner was shown before submitting this answer, counted from
|
|
1258
|
+
* the start of the question — the author's and AI hints alike. Written by
|
|
1259
|
+
* the SDK's components in `practice`; absent or 0 means none. A scoring
|
|
1260
|
+
* policy's `hintPenalty` charges for them. Client-reported, like any count
|
|
1261
|
+
* a browser makes.
|
|
1262
|
+
*/
|
|
1263
|
+
hintsRevealed?: number;
|
|
1256
1264
|
}
|
|
1257
1265
|
/** Learner response for a Fill-in-the-Blanks activity. */
|
|
1258
1266
|
interface FillInTheBlanksLearnerResponse {
|
|
1259
1267
|
type: 'fill-in-the-blanks';
|
|
1260
1268
|
/** Map of blank ID to the learner's typed answer. */
|
|
1261
1269
|
answers: Record<string, string>;
|
|
1270
|
+
/**
|
|
1271
|
+
* Hints the learner was shown before submitting this answer, counted from
|
|
1272
|
+
* the start of the question — the author's and AI hints alike. Written by
|
|
1273
|
+
* the SDK's components in `practice`; absent or 0 means none. A scoring
|
|
1274
|
+
* policy's `hintPenalty` charges for them. Client-reported, like any count
|
|
1275
|
+
* a browser makes.
|
|
1276
|
+
*/
|
|
1277
|
+
hintsRevealed?: number;
|
|
1262
1278
|
}
|
|
1263
1279
|
/**
|
|
1264
1280
|
* Learner response for a Gap Select activity.
|
|
@@ -1273,6 +1289,14 @@ interface GapSelectLearnerResponse {
|
|
|
1273
1289
|
type: 'gap-select';
|
|
1274
1290
|
/** Map of gap ID to the ID of the choice the learner selected. */
|
|
1275
1291
|
selections: Record<string, string>;
|
|
1292
|
+
/**
|
|
1293
|
+
* Hints the learner was shown before submitting this answer, counted from
|
|
1294
|
+
* the start of the question — the author's and AI hints alike. Written by
|
|
1295
|
+
* the SDK's components in `practice`; absent or 0 means none. A scoring
|
|
1296
|
+
* policy's `hintPenalty` charges for them. Client-reported, like any count
|
|
1297
|
+
* a browser makes.
|
|
1298
|
+
*/
|
|
1299
|
+
hintsRevealed?: number;
|
|
1276
1300
|
}
|
|
1277
1301
|
/** Learner response for a Written Response activity. */
|
|
1278
1302
|
interface WrittenResponseLearnerResponse {
|
|
@@ -1286,15 +1310,18 @@ interface WrittenResponseLearnerResponse {
|
|
|
1286
1310
|
* Learner response for a Dictation activity.
|
|
1287
1311
|
*
|
|
1288
1312
|
* `text` is exactly what the learner typed; every normalisation happens in the
|
|
1289
|
-
* scorer. `hintsRevealed` is client-reported
|
|
1290
|
-
*
|
|
1291
|
-
*
|
|
1313
|
+
* scorer. `hintsRevealed` is client-reported — how many hint words were shown
|
|
1314
|
+
* before submitting. The scorer ignores it; a scoring policy's `hintPenalty`
|
|
1315
|
+
* charges for it, knowing it cannot be verified.
|
|
1292
1316
|
*/
|
|
1293
1317
|
interface DictationLearnerResponse {
|
|
1294
1318
|
type: 'dictation';
|
|
1295
1319
|
/** The learner's text, as typed. */
|
|
1296
1320
|
text: string;
|
|
1297
|
-
/**
|
|
1321
|
+
/**
|
|
1322
|
+
* Hint words shown before submitting (practice only), counted from the start
|
|
1323
|
+
* of the question. Absent or 0 means none.
|
|
1324
|
+
*/
|
|
1298
1325
|
hintsRevealed?: number;
|
|
1299
1326
|
}
|
|
1300
1327
|
/**
|
package/dist/ai-check.cjs
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
var
|
|
7
|
-
require('./chunk-
|
|
6
|
+
var _chunkNRTP7WCWcjs = require('./chunk-NRTP7WCW.cjs');
|
|
7
|
+
require('./chunk-WQVZPIN7.cjs');
|
|
8
8
|
require('./chunk-D4TOPSHV.cjs');
|
|
9
9
|
|
|
10
10
|
// src/ai-check/cases.ts
|
|
@@ -82,14 +82,14 @@ var picked = (choices) => ({
|
|
|
82
82
|
});
|
|
83
83
|
var wrote = (text) => ({ type: "dictation", text });
|
|
84
84
|
function explanationCase(id, about, data, response) {
|
|
85
|
-
const request =
|
|
85
|
+
const request = _chunkNRTP7WCWcjs.aiExplanationRequest.call(void 0, { data, response });
|
|
86
86
|
if (request === null) {
|
|
87
87
|
throw new Error(`ai-check: no explanation request for "${id}"`);
|
|
88
88
|
}
|
|
89
89
|
return { id, feature: "explanation", about, request };
|
|
90
90
|
}
|
|
91
91
|
function hintCase(id, about, data, response, previousHints = []) {
|
|
92
|
-
const request =
|
|
92
|
+
const request = _chunkNRTP7WCWcjs.aiHintRequest.call(void 0, { data, response, previousHints });
|
|
93
93
|
if (request === null) {
|
|
94
94
|
throw new Error(`ai-check: no hint request for "${id}"`);
|
|
95
95
|
}
|
|
@@ -193,7 +193,7 @@ async function runCase(ports, one) {
|
|
|
193
193
|
};
|
|
194
194
|
}
|
|
195
195
|
const ms = now() - started;
|
|
196
|
-
const checked = explaining ?
|
|
196
|
+
const checked = explaining ? _chunkNRTP7WCWcjs.checkAiExplanation.call(void 0, raw, one.request) : _chunkNRTP7WCWcjs.checkAiHint.call(void 0, raw, one.request);
|
|
197
197
|
if (checked.ok) {
|
|
198
198
|
return { case: one, ok: true, result: checked.result, ms };
|
|
199
199
|
}
|
package/dist/ai-check.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { j as AiExplanationRequest, p as AiHintRequest, u as AiRefusal, w as AiTextResult } from './activity-
|
|
1
|
+
import { j as AiExplanationRequest, p as AiHintRequest, u as AiRefusal, w as AiTextResult } from './activity-xhGV-Aeh.cjs';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* One call to make against a host's port, and what a learner was looking at
|
package/dist/ai-check.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { j as AiExplanationRequest, p as AiHintRequest, u as AiRefusal, w as AiTextResult } from './activity-
|
|
1
|
+
import { j as AiExplanationRequest, p as AiHintRequest, u as AiRefusal, w as AiTextResult } from './activity-xhGV-Aeh.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* One call to make against a host's port, and what a learner was looking at
|
package/dist/ai-check.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
isGradeInRange,
|
|
3
3
|
score
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-Q7MV3IXJ.js";
|
|
5
5
|
import {
|
|
6
6
|
PLACEHOLDER_RE,
|
|
7
7
|
alignDictation
|
|
@@ -440,4 +440,4 @@ export {
|
|
|
440
440
|
checkAiHint,
|
|
441
441
|
hintRevealsAnswer
|
|
442
442
|
};
|
|
443
|
-
//# sourceMappingURL=chunk-
|
|
443
|
+
//# sourceMappingURL=chunk-IHOEP4EB.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
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
2
|
|
|
3
3
|
|
|
4
|
-
var
|
|
4
|
+
var _chunkWQVZPIN7cjs = require('./chunk-WQVZPIN7.cjs');
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
|
|
@@ -212,7 +212,7 @@ function gradeLocally(data, response) {
|
|
|
212
212
|
return null;
|
|
213
213
|
}
|
|
214
214
|
try {
|
|
215
|
-
const result =
|
|
215
|
+
const result = _chunkWQVZPIN7cjs.score.call(void 0, data.type, data, response);
|
|
216
216
|
return { details: result.details, grade: aiGradeOf(result) };
|
|
217
217
|
} catch (e) {
|
|
218
218
|
return null;
|
|
@@ -227,7 +227,7 @@ function aiExplanationRequest(input) {
|
|
|
227
227
|
let grade = null;
|
|
228
228
|
if (_optionalChain([outcome, 'optionalAccess', _20 => _20.status]) === "scored") {
|
|
229
229
|
const { score: recorded, maxScore, passed } = outcome;
|
|
230
|
-
if (!
|
|
230
|
+
if (!_chunkWQVZPIN7cjs.isGradeInRange.call(void 0, recorded, maxScore)) {
|
|
231
231
|
return null;
|
|
232
232
|
}
|
|
233
233
|
details = outcome.details;
|
|
@@ -440,4 +440,4 @@ function hintRevealsAnswer(facts, hint) {
|
|
|
440
440
|
|
|
441
441
|
|
|
442
442
|
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;
|
|
443
|
-
//# sourceMappingURL=chunk-
|
|
443
|
+
//# sourceMappingURL=chunk-NRTP7WCW.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/home/runner/work/learning-kit/learning-kit/packages/lk-core/dist/chunk-FVZJ4X3Y.cjs","../src/ai.ts"],"names":[],"mappings":"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","file":"/home/runner/work/learning-kit/learning-kit/packages/lk-core/dist/chunk-FVZJ4X3Y.cjs","sourcesContent":[null,"import { 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. A type it builds\n * no facts for — a written response, a read-aloud, a consumer's own — has\n * neither.\n */\nexport function aiSupports(activityType: string, feature: AiFeature): boolean {\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 */\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/** 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 ? detail.correct : !detail.correct;\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 : detail.correct,\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 : detail.correct,\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 (detail.correct) {\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 * 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 */\nfunction 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\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"]}
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/learning-kit/learning-kit/packages/lk-core/dist/chunk-NRTP7WCW.cjs","../src/ai.ts"],"names":[],"mappings":"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","file":"/home/runner/work/learning-kit/learning-kit/packages/lk-core/dist/chunk-NRTP7WCW.cjs","sourcesContent":[null,"import { 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. A type it builds\n * no facts for — a written response, a read-aloud, a consumer's own — has\n * neither.\n */\nexport function aiSupports(activityType: string, feature: AiFeature): boolean {\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 */\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/** 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 ? detail.correct : !detail.correct;\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 : detail.correct,\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 : detail.correct,\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 (detail.correct) {\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 * 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 */\nfunction 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\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"]}
|