@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/dist/index.js CHANGED
@@ -45,15 +45,18 @@ import {
45
45
  checkAiHint,
46
46
  hintRevealsAnswer,
47
47
  readAiTextResult
48
- } from "./chunk-BIJCO2HM.js";
48
+ } from "./chunk-IHOEP4EB.js";
49
49
  import {
50
+ DEFAULT_ITEM_SCORING_POLICY,
50
51
  DEFAULT_PASS_THRESHOLD,
52
+ ITEM_SCORING_MAX_RETRIES,
51
53
  alignReadAloud,
52
54
  classifyBand,
53
55
  composeAssessmentScore,
54
56
  computePassThreshold,
55
57
  diffDictationChars,
56
58
  evaluate,
59
+ evaluateTries,
57
60
  gradeFromRubric,
58
61
  gradeReadAloud,
59
62
  gte,
@@ -61,10 +64,13 @@ import {
61
64
  inspectWav,
62
65
  outcomeFromGrade,
63
66
  outcomeFromUnscorable,
67
+ resolveItemScoringPolicy,
64
68
  roundGrade,
65
69
  score,
70
+ scoreTries,
71
+ validateItemScoringPolicy,
66
72
  validateSpeechAssessment
67
- } from "./chunk-MMEQKFFQ.js";
73
+ } from "./chunk-Q7MV3IXJ.js";
68
74
  import {
69
75
  ActivitySchemaError,
70
76
  AiPermissionsSchema,
@@ -208,6 +214,106 @@ function contentHash(value) {
208
214
  return fingerprint(canonicalJson(value));
209
215
  }
210
216
 
217
+ // src/delivery.ts
218
+ var OPEN_DELIVERY_POLICY = Object.freeze({
219
+ feedback: true,
220
+ solutions: true,
221
+ hints: true,
222
+ ai: Object.freeze({ hints: true, explanations: true })
223
+ });
224
+ var SETTINGS = ["feedback", "solutions", "hints"];
225
+ var AI_SETTINGS = ["hints", "explanations"];
226
+ function allows(value) {
227
+ return value === void 0 || value === null || value === true;
228
+ }
229
+ function resolveDeliveryPolicy(policy) {
230
+ if (policy === void 0 || policy === null) {
231
+ return OPEN_DELIVERY_POLICY;
232
+ }
233
+ if (typeof policy !== "object" || Array.isArray(policy)) {
234
+ return {
235
+ feedback: false,
236
+ solutions: false,
237
+ hints: false,
238
+ ai: { hints: false, explanations: false }
239
+ };
240
+ }
241
+ const given = policy;
242
+ const ai = given.ai;
243
+ const aiGiven = ai === void 0 || ai === null || ai === true ? {} : typeof ai === "object" && !Array.isArray(ai) ? ai : { hints: false, explanations: false };
244
+ return {
245
+ feedback: allows(given.feedback),
246
+ solutions: allows(given.solutions),
247
+ hints: allows(given.hints),
248
+ ai: { hints: allows(aiGiven.hints), explanations: allows(aiGiven.explanations) }
249
+ };
250
+ }
251
+ function combineDeliveryPolicies(...policies) {
252
+ const resolved = policies.map(resolveDeliveryPolicy);
253
+ const every = (read) => resolved.every(read);
254
+ return {
255
+ feedback: every((one) => one.feedback),
256
+ solutions: every((one) => one.solutions),
257
+ hints: every((one) => one.hints),
258
+ ai: {
259
+ hints: every((one) => one.ai.hints),
260
+ explanations: every((one) => one.ai.explanations)
261
+ }
262
+ };
263
+ }
264
+ function validateDeliveryPolicy(policy) {
265
+ const issues = [];
266
+ if (policy === void 0 || policy === null) {
267
+ return { success: true, data: {} };
268
+ }
269
+ if (typeof policy !== "object" || Array.isArray(policy)) {
270
+ return {
271
+ success: false,
272
+ issues: [{ path: "", message: "A delivery policy is an object of settings." }]
273
+ };
274
+ }
275
+ const given = policy;
276
+ for (const key of Object.keys(given)) {
277
+ if (key === "ai") {
278
+ continue;
279
+ }
280
+ if (!SETTINGS.includes(key)) {
281
+ issues.push({
282
+ path: key,
283
+ message: `"${key}" is not a delivery setting. The settings are feedback, solutions, hints and ai.`
284
+ });
285
+ continue;
286
+ }
287
+ const value = given[key];
288
+ if (value !== true && value !== false && value !== null && value !== void 0) {
289
+ issues.push({ path: key, message: `"${key}" must be true, false or null.` });
290
+ }
291
+ }
292
+ const ai = given.ai;
293
+ if (ai !== void 0 && ai !== null && ai !== true && ai !== false) {
294
+ if (typeof ai !== "object" || Array.isArray(ai)) {
295
+ issues.push({
296
+ path: "ai",
297
+ message: '"ai" must be true, false, or an object of hints and explanations.'
298
+ });
299
+ } else {
300
+ for (const [key, value] of Object.entries(ai)) {
301
+ if (!AI_SETTINGS.includes(key)) {
302
+ issues.push({
303
+ path: `ai.${key}`,
304
+ message: `"ai.${key}" is not an AI setting. The settings are hints and explanations.`
305
+ });
306
+ continue;
307
+ }
308
+ if (value !== true && value !== false && value !== null && value !== void 0) {
309
+ issues.push({ path: `ai.${key}`, message: `"ai.${key}" must be true, false or null.` });
310
+ }
311
+ }
312
+ }
313
+ }
314
+ return issues.length > 0 ? { success: false, issues } : { success: true, data: given };
315
+ }
316
+
211
317
  // src/shuffle.ts
212
318
  function hashSeed(input) {
213
319
  let h = 2166136261 >>> 0;
@@ -511,6 +617,8 @@ function planSlot(slot, points) {
511
617
  }
512
618
  function planAttempt(entries, options = {}) {
513
619
  const { seed, shuffleEntries, points } = options;
620
+ const delivery = frozenDelivery(options.delivery);
621
+ const scoring = frozenScoring(options.scoring);
514
622
  const slots = flattenSequence(entries, {
515
623
  ...shuffleEntries !== void 0 ? { shuffleEntries } : {},
516
624
  ...seed !== void 0 ? { seed } : {}
@@ -525,7 +633,22 @@ function planAttempt(entries, options = {}) {
525
633
  // The plan's own fingerprint covers the slots verbatim — identity, order,
526
634
  // points and content hashes — so one stored value answers "is this still
527
635
  // the paper that was sat?" and the per-slot hashes then say what moved.
528
- planHash: contentHash(planned),
636
+ // A policy joins it when there is one: the same paper sat with hints and
637
+ // without is two different attempts. Without one, the fingerprint is the
638
+ // one every plan before policies had.
639
+ // So does a scoring policy: the same answers are worth something else when
640
+ // a hint costs a tenth of the marks. Each joins only when it is there, so
641
+ // a plan with neither — or with a delivery policy alone — keeps the
642
+ // fingerprint it always had.
643
+ planHash: contentHash(
644
+ delivery === void 0 && scoring === void 0 ? planned : {
645
+ slots: planned,
646
+ ...delivery !== void 0 ? { delivery } : {},
647
+ ...scoring !== void 0 ? { scoring } : {}
648
+ }
649
+ ),
650
+ ...delivery !== void 0 ? { delivery } : {},
651
+ ...scoring !== void 0 ? { scoring } : {},
529
652
  slots: planned,
530
653
  totalPoints
531
654
  };
@@ -540,6 +663,8 @@ function verifyAttemptPlan(plan, current) {
540
663
  const changedCueSlotIds = [];
541
664
  const changedPointsSlotIds = [];
542
665
  const reorderedSlotIds = [];
666
+ const deliveryChanged = contentHash(plan.delivery ?? null) !== contentHash(current.delivery ?? null);
667
+ const scoringChanged = contentHash(plan.scoring ?? null) !== contentHash(current.scoring ?? null);
543
668
  for (const slot of plan.slots) {
544
669
  const now = after.get(slot.slotId);
545
670
  if (now === void 0) {
@@ -562,16 +687,47 @@ function verifyAttemptPlan(plan, current) {
562
687
  }
563
688
  }
564
689
  return {
565
- matches: missingSlotIds.length === 0 && addedSlotIds.length === 0 && changedSlotIds.length === 0 && changedStimulusSlotIds.length === 0 && changedCueSlotIds.length === 0 && changedPointsSlotIds.length === 0 && reorderedSlotIds.length === 0,
690
+ matches: missingSlotIds.length === 0 && addedSlotIds.length === 0 && changedSlotIds.length === 0 && changedStimulusSlotIds.length === 0 && changedCueSlotIds.length === 0 && changedPointsSlotIds.length === 0 && reorderedSlotIds.length === 0 && !deliveryChanged && !scoringChanged,
566
691
  missingSlotIds,
567
692
  addedSlotIds,
568
693
  changedSlotIds,
569
694
  changedStimulusSlotIds,
570
695
  changedCueSlotIds,
571
696
  changedPointsSlotIds,
572
- reorderedSlotIds
697
+ reorderedSlotIds,
698
+ // Present only when it is true, as `rejectedSlotIds` is on a composed
699
+ // score: a drift report between two plans without a policy — every one
700
+ // written before policies existed — keeps exactly the shape it had.
701
+ ...deliveryChanged ? { deliveryChanged: true } : {},
702
+ ...scoringChanged ? { scoringChanged: true } : {}
573
703
  };
574
704
  }
705
+ function frozenDelivery(policy) {
706
+ if (policy === void 0 || policy === null) {
707
+ return void 0;
708
+ }
709
+ const checked = validateDeliveryPolicy(policy);
710
+ if (!checked.success) {
711
+ const first = checked.issues[0];
712
+ throw new Error(
713
+ `planAttempt: the delivery policy is not one to record \u2014 ${first?.message ?? "invalid"}` + (checked.issues.length > 1 ? ` (and ${checked.issues.length - 1} more)` : "")
714
+ );
715
+ }
716
+ return resolveDeliveryPolicy(checked.data);
717
+ }
718
+ function frozenScoring(policy) {
719
+ if (policy === void 0 || policy === null) {
720
+ return void 0;
721
+ }
722
+ const checked = validateItemScoringPolicy(policy);
723
+ if (!checked.success) {
724
+ const first = checked.issues[0];
725
+ throw new Error(
726
+ `planAttempt: the scoring policy is not one to record \u2014 ${first?.message ?? "invalid"}` + (checked.issues.length > 1 ? ` (and ${checked.issues.length - 1} more)` : "")
727
+ );
728
+ }
729
+ return resolveItemScoringPolicy(checked.data);
730
+ }
575
731
  function missingOutcome(slot, policy) {
576
732
  if (typeof policy === "function") {
577
733
  return policy(slot);
@@ -1509,6 +1665,7 @@ export {
1509
1665
  ActivitySchemaError,
1510
1666
  AiPermissionsSchema,
1511
1667
  BlankConfigSchema,
1668
+ DEFAULT_ITEM_SCORING_POLICY,
1512
1669
  DEFAULT_PASS_THRESHOLD,
1513
1670
  DICTATION_MAX_ACCEPTED_TRANSCRIPTS,
1514
1671
  DICTATION_MAX_EQUIVALENCES,
@@ -1527,6 +1684,7 @@ export {
1527
1684
  GapSelectDataSchema,
1528
1685
  GapSelectGapSchema,
1529
1686
  INTERACTIVE_VIDEO_ITEM_TYPES,
1687
+ ITEM_SCORING_MAX_RETRIES,
1530
1688
  ItemGroupSchema,
1531
1689
  MediaPlaybackSchema,
1532
1690
  MediaSchema,
@@ -1537,6 +1695,7 @@ export {
1537
1695
  MultipleChoiceOptionMediaSchema,
1538
1696
  MultipleChoiceOptionSchema,
1539
1697
  NativeControlHintSchema,
1698
+ OPEN_DELIVERY_POLICY,
1540
1699
  READ_ALOUD_MAX_DIMENSION_WEIGHT,
1541
1700
  READ_ALOUD_MAX_REFERENCE_LENGTH,
1542
1701
  READ_ALOUD_MAX_SECONDS,
@@ -1589,6 +1748,7 @@ export {
1589
1748
  checkAiExplanation,
1590
1749
  checkAiHint,
1591
1750
  classifyBand,
1751
+ combineDeliveryPolicies,
1592
1752
  composeAssessmentScore,
1593
1753
  composeTimelineScore,
1594
1754
  computePassThreshold,
@@ -1605,6 +1765,7 @@ export {
1605
1765
  diffResponses,
1606
1766
  entryKeyOf,
1607
1767
  evaluate,
1768
+ evaluateTries,
1608
1769
  fillInTheBlanksJsonSchema,
1609
1770
  fillInTheBlanksType,
1610
1771
  fingerprint,
@@ -1639,11 +1800,14 @@ export {
1639
1800
  redactItemGroup,
1640
1801
  registerActivityType,
1641
1802
  registeredActivityTypes,
1803
+ resolveDeliveryPolicy,
1804
+ resolveItemScoringPolicy,
1642
1805
  resolvePlaybackPolicy,
1643
1806
  restoreAttemptState,
1644
1807
  restoreMediaPlayLedger,
1645
1808
  roundGrade,
1646
1809
  score,
1810
+ scoreTries,
1647
1811
  scoredItemsFromPlan,
1648
1812
  seededShuffle,
1649
1813
  serializeAttemptState,
@@ -1652,9 +1816,11 @@ export {
1652
1816
  stimulusJsonSchema,
1653
1817
  stimulusMediaKey,
1654
1818
  validateActivity,
1819
+ validateDeliveryPolicy,
1655
1820
  validateDraft,
1656
1821
  validateItemGroup,
1657
1822
  validateItemGroupDraft,
1823
+ validateItemScoringPolicy,
1658
1824
  validateSpeechAssessment,
1659
1825
  validateXAPIStatement,
1660
1826
  verifyAttemptPlan,