@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.
Files changed (38) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/README.md +31 -2
  3. package/dist/{activity-f0bgZSTP.d.cts → activity-xhGV-Aeh.d.cts} +31 -4
  4. package/dist/{activity-f0bgZSTP.d.ts → activity-xhGV-Aeh.d.ts} +31 -4
  5. package/dist/ai-check.cjs +5 -5
  6. package/dist/ai-check.d.cts +1 -1
  7. package/dist/ai-check.d.ts +1 -1
  8. package/dist/ai-check.js +2 -2
  9. package/dist/{chunk-BIJCO2HM.js → chunk-IHOEP4EB.js} +2 -2
  10. package/dist/{chunk-FVZJ4X3Y.cjs → chunk-NRTP7WCW.cjs} +4 -4
  11. package/dist/{chunk-FVZJ4X3Y.cjs.map → chunk-NRTP7WCW.cjs.map} +1 -1
  12. package/dist/{chunk-MMEQKFFQ.js → chunk-Q7MV3IXJ.js} +196 -8
  13. package/dist/chunk-Q7MV3IXJ.js.map +1 -0
  14. package/dist/{chunk-COBK2RUX.cjs → chunk-WQVZPIN7.cjs} +197 -9
  15. package/dist/chunk-WQVZPIN7.cjs.map +1 -0
  16. package/dist/{index-bpuDhvXV.d.cts → index-BuD8Hc8o.d.cts} +1 -1
  17. package/dist/{index-DMCM3e3P.d.ts → index-DnJ_PI4d.d.ts} +1 -1
  18. package/dist/index.cjs +175 -9
  19. package/dist/index.cjs.map +1 -1
  20. package/dist/index.d.cts +177 -7
  21. package/dist/index.d.ts +177 -7
  22. package/dist/index.js +171 -5
  23. package/dist/index.js.map +1 -1
  24. package/dist/schemas.d.cts +2 -2
  25. package/dist/schemas.d.ts +2 -2
  26. package/dist/scoring.cjs +14 -2
  27. package/dist/scoring.cjs.map +1 -1
  28. package/dist/scoring.d.cts +240 -4
  29. package/dist/scoring.d.ts +240 -4
  30. package/dist/scoring.js +13 -1
  31. package/dist/xapi.d.cts +1 -1
  32. package/dist/xapi.d.ts +1 -1
  33. package/package.json +1 -1
  34. package/vectors/README.md +10 -0
  35. package/vectors/scoring.json +2127 -1
  36. package/dist/chunk-COBK2RUX.cjs.map +0 -1
  37. package/dist/chunk-MMEQKFFQ.js.map +0 -1
  38. /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
- - **Attempt plans** — `planAttempt(entries, { seed, shuffleEntries, points })` freezes the exact paper a learner sat: 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.
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
- - [Upgrading](https://github.com/intellectif/learning-kit/blob/main/docs/upgrading.md) — start here on any upgrade from 0.3.x, 0.4.x or 0.5.x.
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 telemetry — how many hint words
1290
- * were shown before submitting — that the scorer ignores and a consumer may
1291
- * log or penalise, knowing it cannot be verified.
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
- /** Hint words revealed before submitting (practice only). Absent or 0 means none. */
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 telemetry — how many hint words
1290
- * were shown before submitting — that the scorer ignores and a consumer may
1291
- * log or penalise, knowing it cannot be verified.
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
- /** Hint words revealed before submitting (practice only). Absent or 0 means none. */
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 _chunkFVZJ4X3Ycjs = require('./chunk-FVZJ4X3Y.cjs');
7
- require('./chunk-COBK2RUX.cjs');
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 = _chunkFVZJ4X3Ycjs.aiExplanationRequest.call(void 0, { data, response });
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 = _chunkFVZJ4X3Ycjs.aiHintRequest.call(void 0, { data, response, previousHints });
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 ? _chunkFVZJ4X3Ycjs.checkAiExplanation.call(void 0, raw, one.request) : _chunkFVZJ4X3Ycjs.checkAiHint.call(void 0, raw, one.request);
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
  }
@@ -1,4 +1,4 @@
1
- import { j as AiExplanationRequest, p as AiHintRequest, u as AiRefusal, w as AiTextResult } from './activity-f0bgZSTP.cjs';
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
@@ -1,4 +1,4 @@
1
- import { j as AiExplanationRequest, p as AiHintRequest, u as AiRefusal, w as AiTextResult } from './activity-f0bgZSTP.js';
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
@@ -3,8 +3,8 @@ import {
3
3
  aiHintRequest,
4
4
  checkAiExplanation,
5
5
  checkAiHint
6
- } from "./chunk-BIJCO2HM.js";
7
- import "./chunk-MMEQKFFQ.js";
6
+ } from "./chunk-IHOEP4EB.js";
7
+ import "./chunk-Q7MV3IXJ.js";
8
8
  import "./chunk-D3GP5MET.js";
9
9
 
10
10
  // src/ai-check/cases.ts
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  isGradeInRange,
3
3
  score
4
- } from "./chunk-MMEQKFFQ.js";
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-BIJCO2HM.js.map
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 _chunkCOBK2RUXcjs = require('./chunk-COBK2RUX.cjs');
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 = _chunkCOBK2RUXcjs.score.call(void 0, data.type, data, response);
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 (!_chunkCOBK2RUXcjs.isGradeInRange.call(void 0, recorded, maxScore)) {
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-FVZJ4X3Y.cjs.map
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"]}