@intellectif/lk-core 1.3.0 → 1.5.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 (85) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/README.md +2 -0
  3. package/dist/{ai-B222dtHH.d.ts → ai-BYryFkF9.d.cts} +462 -5
  4. package/dist/ai-BYryFkF9.d.cts.map +1 -0
  5. package/dist/{ai-B222dtHH.d.cts → ai-BYryFkF9.d.ts} +462 -5
  6. package/dist/ai-BYryFkF9.d.ts.map +1 -0
  7. package/dist/ai-check.cjs +274 -23
  8. package/dist/ai-check.cjs.map +1 -1
  9. package/dist/ai-check.d.cts +13 -5
  10. package/dist/ai-check.d.cts.map +1 -1
  11. package/dist/ai-check.d.ts +13 -5
  12. package/dist/ai-check.d.ts.map +1 -1
  13. package/dist/ai-check.js +274 -23
  14. package/dist/ai-check.js.map +1 -1
  15. package/dist/{ai-writing-DCVkv4Zj.cjs → ai-writing-Cen7-3qs.cjs} +520 -58
  16. package/dist/ai-writing-Cen7-3qs.cjs.map +1 -0
  17. package/dist/{ai-writing-CCBjTOM3.js → ai-writing-D8PdhJJz.js} +513 -75
  18. package/dist/ai-writing-D8PdhJJz.js.map +1 -0
  19. package/dist/{index-Dyu66R4-.d.cts → index-Bg0aeWtM.d.cts} +327 -3
  20. package/dist/index-Bg0aeWtM.d.cts.map +1 -0
  21. package/dist/{index-BWPRPIXL.d.ts → index-CxpiWedW.d.ts} +7 -217
  22. package/dist/index-CxpiWedW.d.ts.map +1 -0
  23. package/dist/{index-CXyOV8sm.d.cts → index-D7R3Ll8R.d.cts} +2 -2
  24. package/dist/{index-CXyOV8sm.d.cts.map → index-D7R3Ll8R.d.cts.map} +1 -1
  25. package/dist/{index-UGxRSopE.d.ts → index-DUaW1Tj5.d.ts} +327 -3
  26. package/dist/index-DUaW1Tj5.d.ts.map +1 -0
  27. package/dist/{index-O4_ExlAD.d.cts → index-qMbxed1B.d.cts} +7 -217
  28. package/dist/index-qMbxed1B.d.cts.map +1 -0
  29. package/dist/{index-CegM8o15.d.ts → index-wMc1E_aM.d.ts} +2 -2
  30. package/dist/{index-CegM8o15.d.ts.map → index-wMc1E_aM.d.ts.map} +1 -1
  31. package/dist/index.cjs +32 -130
  32. package/dist/index.cjs.map +1 -1
  33. package/dist/index.d.cts +115 -97
  34. package/dist/index.d.cts.map +1 -1
  35. package/dist/index.d.ts +115 -97
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +9 -117
  38. package/dist/index.js.map +1 -1
  39. package/dist/{item-group-DroFXgLb.cjs → item-group-DEB7nLID.cjs} +2 -2
  40. package/dist/{item-group-DroFXgLb.cjs.map → item-group-DEB7nLID.cjs.map} +1 -1
  41. package/dist/{item-group-DXiR8nOr.js → item-group-DdSUCUjM.js} +2 -2
  42. package/dist/{item-group-DXiR8nOr.js.map → item-group-DdSUCUjM.js.map} +1 -1
  43. package/dist/{registry-DVi0LR_i.cjs → registry-Ce00A_OW.cjs} +184 -4
  44. package/dist/registry-Ce00A_OW.cjs.map +1 -0
  45. package/dist/{registry-Dsx9mNsS.js → registry-DaZRpqjW.js} +155 -5
  46. package/dist/registry-DaZRpqjW.js.map +1 -0
  47. package/dist/{schemas-Bdw0nGPo.js → schemas-BioKFf3F.js} +3 -3
  48. package/dist/{schemas-Bdw0nGPo.js.map → schemas-BioKFf3F.js.map} +1 -1
  49. package/dist/{schemas-DVbAOrtd.cjs → schemas-DksueYsu.cjs} +3 -3
  50. package/dist/{schemas-DVbAOrtd.cjs.map → schemas-DksueYsu.cjs.map} +1 -1
  51. package/dist/schemas.cjs +2 -2
  52. package/dist/schemas.d.cts +1 -1
  53. package/dist/schemas.d.ts +1 -1
  54. package/dist/schemas.js +2 -2
  55. package/dist/{scoring-BZqUkuDF.js → scoring-BKNNTz-s.js} +657 -3
  56. package/dist/scoring-BKNNTz-s.js.map +1 -0
  57. package/dist/{scoring-B2_HEhMT.cjs → scoring-lKQVYsxy.cjs} +722 -2
  58. package/dist/scoring-lKQVYsxy.cjs.map +1 -0
  59. package/dist/scoring.cjs +8 -2
  60. package/dist/scoring.d.cts +3 -3
  61. package/dist/scoring.d.ts +3 -3
  62. package/dist/scoring.js +3 -3
  63. package/dist/{xapi-DNzkSJ0S.cjs → xapi-NYrTlpID.cjs} +2 -2
  64. package/dist/{xapi-DNzkSJ0S.cjs.map → xapi-NYrTlpID.cjs.map} +1 -1
  65. package/dist/{xapi-CMTph7wG.js → xapi-wR6gu4qS.js} +2 -2
  66. package/dist/{xapi-CMTph7wG.js.map → xapi-wR6gu4qS.js.map} +1 -1
  67. package/dist/xapi.cjs +1 -1
  68. package/dist/xapi.d.cts +1 -1
  69. package/dist/xapi.d.ts +1 -1
  70. package/dist/xapi.js +1 -2
  71. package/package.json +2 -2
  72. package/vectors/README.md +24 -1
  73. package/vectors/scoring.json +18182 -1
  74. package/dist/ai-B222dtHH.d.cts.map +0 -1
  75. package/dist/ai-B222dtHH.d.ts.map +0 -1
  76. package/dist/ai-writing-CCBjTOM3.js.map +0 -1
  77. package/dist/ai-writing-DCVkv4Zj.cjs.map +0 -1
  78. package/dist/index-BWPRPIXL.d.ts.map +0 -1
  79. package/dist/index-Dyu66R4-.d.cts.map +0 -1
  80. package/dist/index-O4_ExlAD.d.cts.map +0 -1
  81. package/dist/index-UGxRSopE.d.ts.map +0 -1
  82. package/dist/registry-DVi0LR_i.cjs.map +0 -1
  83. package/dist/registry-Dsx9mNsS.js.map +0 -1
  84. package/dist/scoring-B2_HEhMT.cjs.map +0 -1
  85. package/dist/scoring-BZqUkuDF.js.map +0 -1
@@ -2732,6 +2732,97 @@ function countWords(text) {
2732
2732
  if (trimmed === "") return 0;
2733
2733
  return trimmed.split(/\s+/).length;
2734
2734
  }
2735
+ /** A label as labels are compared: composed, trimmed, its spacing collapsed, lower-cased. */
2736
+ function levelKey(label) {
2737
+ return label.normalize("NFC").trim().replace(/\s+/gu, " ").toLowerCase();
2738
+ }
2739
+ /**
2740
+ * The levels in `value`, or the first reason they cannot be used. `null` or
2741
+ * absent means the criterion has no levels, which is not a problem: it is
2742
+ * judged on a score.
2743
+ *
2744
+ * Each element and each field is read once, so a getter cannot pass the check
2745
+ * with one value and hand the grader another. The levels come back as written.
2746
+ */
2747
+ function readRubricLevels(value) {
2748
+ if (value === void 0 || value === null) return {
2749
+ ok: true,
2750
+ levels: null
2751
+ };
2752
+ if (!Array.isArray(value)) return {
2753
+ ok: false,
2754
+ problem: "not-a-list"
2755
+ };
2756
+ const count = value.length;
2757
+ if (count < 2) return {
2758
+ ok: false,
2759
+ problem: "too-few"
2760
+ };
2761
+ if (count > 10) return {
2762
+ ok: false,
2763
+ problem: "too-many"
2764
+ };
2765
+ const levels = [];
2766
+ const seen = /* @__PURE__ */ new Set();
2767
+ for (let index = 0; index < count; index += 1) {
2768
+ const entry = value[index];
2769
+ if (typeof entry !== "object" || entry === null || Array.isArray(entry)) return {
2770
+ ok: false,
2771
+ problem: "not-a-level",
2772
+ index
2773
+ };
2774
+ const record = entry;
2775
+ const label = record.label;
2776
+ const description = record.description;
2777
+ if (typeof label !== "string" || label.trim() === "") return {
2778
+ ok: false,
2779
+ problem: "no-label",
2780
+ index
2781
+ };
2782
+ if (description !== void 0 && typeof description !== "string") return {
2783
+ ok: false,
2784
+ problem: "bad-description",
2785
+ index
2786
+ };
2787
+ const key = levelKey(label);
2788
+ if (seen.has(key)) return {
2789
+ ok: false,
2790
+ problem: "duplicate-label",
2791
+ index
2792
+ };
2793
+ seen.add(key);
2794
+ levels.push({
2795
+ label,
2796
+ ...description !== void 0 ? { description } : {}
2797
+ });
2798
+ }
2799
+ return {
2800
+ ok: true,
2801
+ levels
2802
+ };
2803
+ }
2804
+ /**
2805
+ * How far level probabilities may sum from 1 and still be a distribution:
2806
+ * rounding in a grader's output, not a different judgement.
2807
+ */
2808
+ const LEVEL_PROBABILITY_TOLERANCE = 1e-6;
2809
+ /**
2810
+ * `values` as a distribution over `count` levels whose likeliest is level
2811
+ * `chosen`, a tie for likeliest included: each a finite number from 0 to 1,
2812
+ * together 1 within {@link LEVEL_PROBABILITY_TOLERANCE}. `null` when they are
2813
+ * not one.
2814
+ *
2815
+ * The AI check refuses a reply whose probabilities are not a distribution, and
2816
+ * calibration and routing read a grade's the same way, so probabilities the
2817
+ * check let through are never ignored downstream. Each element is read once,
2818
+ * a hole as `undefined`, into the copy returned.
2819
+ */
2820
+ function levelDistribution(values, count, chosen) {
2821
+ const read = Array.from(values);
2822
+ if (read.length !== count || !read.every((p) => typeof p === "number" && p >= 0 && p <= 1)) return null;
2823
+ const sum = read.reduce((total, p) => total + p, 0);
2824
+ return Math.abs(sum - 1) <= 1e-6 && read[chosen] === Math.max(...read) ? read : null;
2825
+ }
2735
2826
  //#endregion
2736
2827
  //#region src/scoring/strategies/all-or-nothing.ts
2737
2828
  /**
@@ -2948,6 +3039,7 @@ const ITEM_FINDING_SEVERITY = {
2948
3039
  gs_bank_no_distractor: "warning",
2949
3040
  gs_answer_in_passage: "warning",
2950
3041
  wr_criterion_name_duplicate: "warning",
3042
+ wr_levels_unreadable: "warning",
2951
3043
  ra_text_long_for_time: "warning",
2952
3044
  set_key_position_same: "warning"
2953
3045
  };
@@ -3198,14 +3290,38 @@ function critiqueGapSelect(draft) {
3198
3290
  });
3199
3291
  return findings;
3200
3292
  }
3293
+ /** Why a criterion's levels cannot be graded on, said to the author. */
3294
+ const LEVELS_PROBLEM = {
3295
+ "not-a-list": (criterion) => `Criterion ${criterion}'s levels are not a list`,
3296
+ "too-few": (criterion) => `Criterion ${criterion} has fewer than 2 levels`,
3297
+ "too-many": (criterion) => `Criterion ${criterion} has more than 10 levels`,
3298
+ "not-a-level": (criterion, level) => `Level ${level} of criterion ${criterion} is not a level`,
3299
+ "no-label": (criterion, level) => `Level ${level} of criterion ${criterion} has no label`,
3300
+ "bad-description": (criterion, level) => `Level ${level} of criterion ${criterion} has a description that is not text`,
3301
+ "duplicate-label": (criterion, level) => `Level ${level} of criterion ${criterion} has the label of an earlier level`
3302
+ };
3201
3303
  function critiqueWrittenResponse(draft) {
3202
3304
  const rubric = isRecord(draft.rubric) ? draft.rubric : void 0;
3203
- return duplicates(rubric === void 0 ? [] : listOf(rubric.criteria), (criterion) => isRecord(criterion) ? written(criterion.name) : void 0).map(({ first, second }) => finding("wr_criterion_name_duplicate", [
3305
+ const criteria = rubric === void 0 ? [] : listOf(rubric.criteria);
3306
+ const names = duplicates(criteria, (criterion) => isRecord(criterion) ? written(criterion.name) : void 0).map(({ first, second }) => finding("wr_criterion_name_duplicate", [
3204
3307
  "rubric",
3205
3308
  "criteria",
3206
3309
  second,
3207
3310
  "name"
3208
3311
  ], `Criteria ${ordinal(first)} and ${ordinal(second)} have the same name. Feedback and grades name a criterion, so one of them cannot be told apart.`));
3312
+ const levels = criteria.flatMap((criterion, index) => {
3313
+ if (!isRecord(criterion)) return [];
3314
+ const read = readRubricLevels(criterion.levels);
3315
+ if (read.ok) return [];
3316
+ const level = ordinal(read.index ?? 0);
3317
+ return [finding("wr_levels_unreadable", [
3318
+ "rubric",
3319
+ "criteria",
3320
+ index,
3321
+ "levels"
3322
+ ], `${LEVELS_PROBLEM[read.problem](ordinal(index), level)}, so a grader cannot judge against them.`)];
3323
+ });
3324
+ return [...names, ...levels];
3209
3325
  }
3210
3326
  /** 150 words a minute: a fluent adult reading aloud. A learner is slower. */
3211
3327
  const WORDS_PER_SECOND = 2.5;
@@ -4387,7 +4503,11 @@ const WrittenResponseRubricSchema = z.looseObject({
4387
4503
  const RedactedWrittenResponseRubricCriterionSchema = z.strictObject({
4388
4504
  name: z.string().min(1),
4389
4505
  description: z.string().optional(),
4390
- weight: z.number().min(0)
4506
+ weight: z.number().min(0),
4507
+ levels: z.array(z.strictObject({
4508
+ label: z.string().min(1),
4509
+ description: z.string().optional()
4510
+ })).min(2).max(10).optional()
4391
4511
  });
4392
4512
  const RedactedWrittenResponseRubricSchema = z.strictObject({
4393
4513
  label: z.string().optional(),
@@ -4829,6 +4949,32 @@ function defineActivityType(descriptor) {
4829
4949
  return descriptor;
4830
4950
  }
4831
4951
  //#endregion
4952
+ //#region src/registry/keep-when.ts
4953
+ /**
4954
+ * A nested field policy that keeps its field only when the field's value
4955
+ * passes a test. Internal: the symbol is never exported, so no host policy can
4956
+ * carry one, and `FieldPolicy`'s string keys never see it.
4957
+ *
4958
+ * It can only drop a field the policy would otherwise emit, never emit one it
4959
+ * would drop, so redaction stays fail-closed. It exists for a field
4960
+ * `validateActivity` does not check in 1.x — a rubric criterion's `levels` —
4961
+ * whose malformed values would otherwise reach the strict redacted schema and
4962
+ * make `redact()` throw on content it accepted before.
4963
+ */
4964
+ const KEEP_WHEN = Symbol("lk.keepWhen");
4965
+ /** `policy`, kept only for values `test` accepts. */
4966
+ function keepWhen(policy, test) {
4967
+ const guarded = {
4968
+ ...policy,
4969
+ [KEEP_WHEN]: test
4970
+ };
4971
+ return Object.freeze(guarded);
4972
+ }
4973
+ /** The test a nested policy keeps its field by, when it has one. */
4974
+ function guardOf(policy) {
4975
+ return policy[KEEP_WHEN];
4976
+ }
4977
+ //#endregion
4832
4978
  //#region src/registry/builtins.ts
4833
4979
  /**
4834
4980
  * Field-sensitivity policies for the built-in types (R7). Fail-closed:
@@ -5046,7 +5192,11 @@ const RUBRIC_FIELD_POLICY = {
5046
5192
  criteria: {
5047
5193
  name: "public",
5048
5194
  description: "public",
5049
- weight: "public"
5195
+ weight: "public",
5196
+ levels: keepWhen({
5197
+ label: "public",
5198
+ description: "public"
5199
+ }, (value) => readRubricLevels(value).ok)
5050
5200
  }
5051
5201
  };
5052
5202
  const WRITTEN_RESPONSE_FIELD_POLICY = {
@@ -5444,6 +5594,6 @@ function registeredActivityTypes() {
5444
5594
  return [...registry.keys()];
5445
5595
  }
5446
5596
  //#endregion
5447
- export { dictationReferenceWords as $, matchText as A, issue as B, SPEECH_ASSESSMENT_MAX_TEXT_LENGTH as C, keyPositionFinding as D, MultipleChoiceOptionMediaSchema as E, covers as F, groupCaptionsField as G, refusesEmpty as H, isMissingId as I, RedactedScoringError as J, ActivitySchemaError as K, isRecord as L, DRAFT_ISSUE_SEVERITY as M, checkMedia as N, normaliseFinding as O, coveredPathsOf as P, alignSequences as Q, isUnset as R, READ_ALOUD_MAX_TAKES as S, MultipleChoiceDataSchema as T, DictationDataSchema as U, pathKey as V, GROUP_CAPTIONS_REVEAL_DICTATION as W, STEP as X, UnknownActivityTypeError as Y, alignDictation as Z, ReadAloudDataSchema as _, MediaSchema as _t, readSchema as a, codePointLength as at, READ_ALOUD_MAX_REFERENCE_LENGTH as b, dictationType as c, BUILT_IN_ACTIVITY_TYPES as ct, multipleChoiceType as d, containsWords as dt, DICTATION_MAX_ACCEPTED_TRANSCRIPTS as et, readAloudType as f, fold as ft, GapSelectDataSchema as g, FillInTheBlanksDataSchema as gt, WrittenResponseDataSchema as h, revealsAnswer as ht, isOwnSchema as i, DICTATION_MAX_TRANSCRIPT_LENGTH as it, countWords as j, levenshteinDistance as k, fillInTheBlanksType as l, everyBuiltInTypeHandled as lt, defineActivityType as m, placeholderOrder as mt, registerActivityType as n, DICTATION_MAX_EQUIVALENCE_LENGTH as nt, standardJsonSchemaOf as o, normalizeDictationText as ot, writtenResponseType as p, numberedPassage as pt, DeferredScoringError as q, registeredActivityTypes as r, DICTATION_MAX_TEXT_LENGTH as rt, MEDIA_FIELD_POLICY as s, withValidationScope as st, getActivityTypeDescriptor as t, DICTATION_MAX_EQUIVALENCES as tt, gapSelectType as u, isBuiltInActivityType as ut, CANONICAL_LOCALE_RE as v, RedactedMediaSchema as vt, SPEECH_ASSESSMENT_MAX_WORDS as w, READ_ALOUD_MAX_SECONDS as x, READ_ALOUD_MAX_DIMENSION_WEIGHT as y, maxUnits as yt, isUnwritten as z };
5597
+ export { RedactedScoringError as $, levenshteinDistance as A, covers as B, READ_ALOUD_MAX_TAKES as C, RedactedMediaSchema as Ct, MultipleChoiceOptionMediaSchema as D, MultipleChoiceDataSchema as E, readRubricLevels as F, issue as G, isRecord as H, countWords as I, DictationDataSchema as J, pathKey as K, DRAFT_ISSUE_SEVERITY as L, LEVEL_PROBABILITY_TOLERANCE as M, levelDistribution as N, keyPositionFinding as O, levelKey as P, DeferredScoringError as Q, checkMedia as R, READ_ALOUD_MAX_SECONDS as S, MediaSchema as St, SPEECH_ASSESSMENT_MAX_WORDS as T, isUnset as U, isMissingId as V, isUnwritten as W, groupCaptionsField as X, GROUP_CAPTIONS_REVEAL_DICTATION as Y, ActivitySchemaError as Z, GapSelectDataSchema as _, fold as _t, readSchema as a, DICTATION_MAX_ACCEPTED_TRANSCRIPTS as at, READ_ALOUD_MAX_DIMENSION_WEIGHT as b, revealsAnswer as bt, dictationType as c, DICTATION_MAX_TEXT_LENGTH as ct, multipleChoiceType as d, normalizeDictationText as dt, UnknownActivityTypeError as et, readAloudType as f, withValidationScope as ft, WrittenResponseDataSchema as g, containsWords as gt, defineActivityType as h, isBuiltInActivityType as ht, isOwnSchema as i, dictationReferenceWords as it, matchText as j, normaliseFinding as k, fillInTheBlanksType as l, DICTATION_MAX_TRANSCRIPT_LENGTH as lt, guardOf as m, everyBuiltInTypeHandled as mt, registerActivityType as n, alignDictation as nt, standardJsonSchemaOf as o, DICTATION_MAX_EQUIVALENCES as ot, writtenResponseType as p, BUILT_IN_ACTIVITY_TYPES as pt, refusesEmpty as q, registeredActivityTypes as r, alignSequences as rt, MEDIA_FIELD_POLICY as s, DICTATION_MAX_EQUIVALENCE_LENGTH as st, getActivityTypeDescriptor as t, STEP as tt, gapSelectType as u, codePointLength as ut, ReadAloudDataSchema as v, numberedPassage as vt, SPEECH_ASSESSMENT_MAX_TEXT_LENGTH as w, maxUnits as wt, READ_ALOUD_MAX_REFERENCE_LENGTH as x, FillInTheBlanksDataSchema as xt, CANONICAL_LOCALE_RE as y, placeholderOrder as yt, coveredPathsOf as z };
5448
5598
 
5449
- //# sourceMappingURL=registry-Dsx9mNsS.js.map
5599
+ //# sourceMappingURL=registry-DaZRpqjW.js.map