@intellectif/lk-core 1.7.0 → 1.9.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 (86) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/dist/ai-check.cjs +77 -3
  3. package/dist/ai-check.cjs.map +1 -1
  4. package/dist/ai-check.d.cts +11 -3
  5. package/dist/ai-check.d.cts.map +1 -1
  6. package/dist/ai-check.d.ts +11 -3
  7. package/dist/ai-check.d.ts.map +1 -1
  8. package/dist/ai-check.js +77 -3
  9. package/dist/ai-check.js.map +1 -1
  10. package/dist/{ai-writing-B7oMCt66.js → ai-writing-DQjI9EFN.js} +2514 -136
  11. package/dist/ai-writing-DQjI9EFN.js.map +1 -0
  12. package/dist/{ai-writing-DrPTV3R2.cjs → ai-writing-DbpZpGRk.cjs} +2614 -122
  13. package/dist/ai-writing-DbpZpGRk.cjs.map +1 -0
  14. package/dist/{ai-B1wYrqcB.d.ts → ai-zhc9yhRj.d.cts} +454 -2
  15. package/dist/ai-zhc9yhRj.d.cts.map +1 -0
  16. package/dist/{ai-B1wYrqcB.d.cts → ai-zhc9yhRj.d.ts} +454 -2
  17. package/dist/ai-zhc9yhRj.d.ts.map +1 -0
  18. package/dist/{index-XCCoPoTU.d.cts → index-BeC8tAWf.d.cts} +2 -2
  19. package/dist/{index-XCCoPoTU.d.cts.map → index-BeC8tAWf.d.cts.map} +1 -1
  20. package/dist/{index-BU99GFTl.d.ts → index-Byar5VVs.d.ts} +2 -2
  21. package/dist/{index-BU99GFTl.d.ts.map → index-Byar5VVs.d.ts.map} +1 -1
  22. package/dist/{index-DP9rvyOz.d.cts → index-Ccj8oL_E.d.cts} +2 -2
  23. package/dist/{index-DP9rvyOz.d.cts.map → index-Ccj8oL_E.d.cts.map} +1 -1
  24. package/dist/{index-BTkMQtws.d.ts → index-CxkqS62X.d.ts} +89 -7
  25. package/dist/index-CxkqS62X.d.ts.map +1 -0
  26. package/dist/{index-BM8LeDWn.d.cts → index-DLsUnGHt.d.cts} +89 -7
  27. package/dist/index-DLsUnGHt.d.cts.map +1 -0
  28. package/dist/{index-BdXs4-en.d.ts → index-f4YuMgFb.d.ts} +2 -2
  29. package/dist/{index-BdXs4-en.d.ts.map → index-f4YuMgFb.d.ts.map} +1 -1
  30. package/dist/index.cjs +282 -6
  31. package/dist/index.cjs.map +1 -1
  32. package/dist/index.d.cts +262 -7
  33. package/dist/index.d.cts.map +1 -1
  34. package/dist/index.d.ts +262 -7
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +269 -7
  37. package/dist/index.js.map +1 -1
  38. package/dist/{item-group-CeDk7mfM.js → item-group-CKZk3txt.js} +2 -2
  39. package/dist/{item-group-CeDk7mfM.js.map → item-group-CKZk3txt.js.map} +1 -1
  40. package/dist/{item-group-8P3WFuUK.cjs → item-group-COP5OwEC.cjs} +2 -2
  41. package/dist/{item-group-8P3WFuUK.cjs.map → item-group-COP5OwEC.cjs.map} +1 -1
  42. package/dist/{registry-D92hz9iA.js → registry-Bw9grsCN.js} +339 -12
  43. package/dist/registry-Bw9grsCN.js.map +1 -0
  44. package/dist/{registry-hzrkTeIT.cjs → registry-C9O9txy5.cjs} +368 -11
  45. package/dist/registry-C9O9txy5.cjs.map +1 -0
  46. package/dist/{schemas-C7TViWCg.js → schemas-BJe3W6Xb.js} +3 -3
  47. package/dist/{schemas-C7TViWCg.js.map → schemas-BJe3W6Xb.js.map} +1 -1
  48. package/dist/{schemas-BWeskONy.cjs → schemas-CXlemRvB.cjs} +3 -3
  49. package/dist/{schemas-BWeskONy.cjs.map → schemas-CXlemRvB.cjs.map} +1 -1
  50. package/dist/schemas.cjs +3 -3
  51. package/dist/schemas.d.cts +1 -1
  52. package/dist/schemas.d.ts +1 -1
  53. package/dist/schemas.js +3 -3
  54. package/dist/{scoring-DD1GHcLI.js → scoring-BGKdff2i.js} +214 -26
  55. package/dist/scoring-BGKdff2i.js.map +1 -0
  56. package/dist/{scoring-CtkCFyFT.cjs → scoring-Db1CZeAn.cjs} +219 -25
  57. package/dist/scoring-Db1CZeAn.cjs.map +1 -0
  58. package/dist/scoring.cjs +3 -2
  59. package/dist/scoring.d.cts +3 -3
  60. package/dist/scoring.d.ts +3 -3
  61. package/dist/scoring.js +3 -3
  62. package/dist/{xapi-Cgicg-zU.js → xapi-BtBm8AWZ.js} +2 -2
  63. package/dist/{xapi-Cgicg-zU.js.map → xapi-BtBm8AWZ.js.map} +1 -1
  64. package/dist/{xapi-7btSpx6v.cjs → xapi-DOAqH9__.cjs} +2 -2
  65. package/dist/{xapi-7btSpx6v.cjs.map → xapi-DOAqH9__.cjs.map} +1 -1
  66. package/dist/xapi.cjs +1 -1
  67. package/dist/xapi.d.cts +1 -1
  68. package/dist/xapi.d.ts +1 -1
  69. package/dist/xapi.js +1 -1
  70. package/package.json +2 -2
  71. package/vectors/README.md +23 -0
  72. package/vectors/scoring.json +11967 -1
  73. package/vectors/validation.json +1298 -1
  74. package/vectors/variants.d.mts +20 -0
  75. package/vectors/variants.json +5753 -0
  76. package/vectors/variants.mjs +67 -0
  77. package/dist/ai-B1wYrqcB.d.cts.map +0 -1
  78. package/dist/ai-B1wYrqcB.d.ts.map +0 -1
  79. package/dist/ai-writing-B7oMCt66.js.map +0 -1
  80. package/dist/ai-writing-DrPTV3R2.cjs.map +0 -1
  81. package/dist/index-BM8LeDWn.d.cts.map +0 -1
  82. package/dist/index-BTkMQtws.d.ts.map +0 -1
  83. package/dist/registry-D92hz9iA.js.map +0 -1
  84. package/dist/registry-hzrkTeIT.cjs.map +0 -1
  85. package/dist/scoring-CtkCFyFT.cjs.map +0 -1
  86. package/dist/scoring-DD1GHcLI.js.map +0 -1
@@ -1,4 +1,4 @@
1
- const require_registry = require("./registry-hzrkTeIT.cjs");
1
+ const require_registry = require("./registry-C9O9txy5.cjs");
2
2
  let zod_v4 = require("zod/v4");
3
3
  zod_v4 = require_registry.__toESM(zod_v4, 1);
4
4
  // src/scoring/grade-numbers.ts
@@ -1407,6 +1407,10 @@ const SpeechAssessmentSchema = zod_v4.strictObject({
1407
1407
  overall: Score.optional()
1408
1408
  }),
1409
1409
  recognizedText: require_registry.maxUnits(zod_v4.string(), 8e3).optional(),
1410
+ transcript: zod_v4.strictObject({
1411
+ text: require_registry.maxUnits(zod_v4.string().min(1), 8e3),
1412
+ source: AssessorSchema
1413
+ }).optional(),
1410
1414
  miscue: zod_v4.enum(["assessor", "none"]),
1411
1415
  phonemeAlphabet: zod_v4.enum(["ipa", "sapi"]).optional(),
1412
1416
  words: zod_v4.array(WordSchema).max(require_registry.SPEECH_ASSESSMENT_MAX_WORDS),
@@ -1697,25 +1701,8 @@ function placeInsertions(assessment, entries) {
1697
1701
  });
1698
1702
  });
1699
1703
  }
1700
- // src/scoring/speech/grade.ts
1701
- /**
1702
- * The read-aloud grade: the arithmetic over a pronunciation assessment's
1703
- * dimension scores, and the checks that decide whether that evidence may be
1704
- * graded at all.
1705
- *
1706
- * The SDK calls no assessor and holds no key. It is given the application's
1707
- * assessment and the server's own measurement of the recording, and it refuses
1708
- * evidence it cannot tie to this item, this text and this recording — a refusal
1709
- * is never a zero, because a learner who was not heard has not failed.
1710
- */
1711
- /**
1712
- * Float slack when comparing two measurements of the same recording: voiced
1713
- * time is summed window by window, so it can land a hair above the duration it
1714
- * was measured from without either number being wrong.
1715
- */
1716
- const MEASUREMENT_TOLERANCE = 1e-6;
1717
1704
  /** What every assessor score is out of, and what a criterion declares. */
1718
- const ASSESSMENT_SCALE = 100;
1705
+ const ASSESSMENT_SCALE$1 = 100;
1719
1706
  /** A value named in an error message, without serialising the object it came from. */
1720
1707
  function describeValue$1(value) {
1721
1708
  return typeof value === "number" ? String(value) : `of type ${typeof value}`;
@@ -1732,10 +1719,10 @@ function describeValue$1(value) {
1732
1719
  * recording-key binding. Every field is read once, and what is returned is what
1733
1720
  * was checked.
1734
1721
  */
1735
- function readResponseRecording(value) {
1722
+ function readResponseRecording(value, expected = "read-aloud") {
1736
1723
  if (typeof value !== "object" || value === null) return;
1737
1724
  const { type, recording } = value;
1738
- if (type !== "read-aloud") return;
1725
+ if (type !== expected) return;
1739
1726
  if (recording === null) return { recording: null };
1740
1727
  if (typeof recording !== "object") return;
1741
1728
  const { key, mimeType } = recording;
@@ -1766,7 +1753,7 @@ function spokenWordCount(assessment) {
1766
1753
  */
1767
1754
  function scoreOf(entry) {
1768
1755
  if (entry.state === "omitted") return { score: 0 };
1769
- return entry.accuracy !== void 0 ? { score: entry.accuracy / ASSESSMENT_SCALE } : {};
1756
+ return entry.accuracy !== void 0 ? { score: entry.accuracy / ASSESSMENT_SCALE$1 } : {};
1770
1757
  }
1771
1758
  /**
1772
1759
  * Grades a read-aloud take from a {@link SpeechAssessment} the application
@@ -1837,7 +1824,7 @@ function gradeReadAloud(data, response, assessment, options) {
1837
1824
  const measured = options.measured;
1838
1825
  if (measured === null || measured === void 0) throw new TypeError("gradeReadAloud needs `options.measured`, the server's own measurement of the recording (inspectWav), for a response that carries a recording. A duration the client reported is not evidence.");
1839
1826
  const { durationMs, voicedMs } = measured;
1840
- if (typeof durationMs !== "number" || !Number.isFinite(durationMs) || durationMs < 0 || typeof voicedMs !== "number" || !Number.isFinite(voicedMs) || voicedMs < 0 || voicedMs > durationMs + MEASUREMENT_TOLERANCE) throw new RangeError(`Invalid recording measurement (durationMs ${describeValue$1(durationMs)}, voicedMs ${describeValue$1(voicedMs)}): expected finite millisecond counts, 0 or above, with voicedMs inside durationMs.`);
1827
+ if (typeof durationMs !== "number" || !Number.isFinite(durationMs) || durationMs < 0 || typeof voicedMs !== "number" || !Number.isFinite(voicedMs) || voicedMs < 0 || voicedMs > durationMs + 1e-6) throw new RangeError(`Invalid recording measurement (durationMs ${describeValue$1(durationMs)}, voicedMs ${describeValue$1(voicedMs)}): expected finite millisecond counts, 0 or above, with voicedMs inside durationMs.`);
1841
1828
  const unscorable = (code, reason) => ({
1842
1829
  unscorable: true,
1843
1830
  code,
@@ -1861,7 +1848,7 @@ function gradeReadAloud(data, response, assessment, options) {
1861
1848
  const graded = gradeFromRubric(weighed.map((dimension) => ({
1862
1849
  name: dimension.name,
1863
1850
  score: evidence.scores[dimension.name],
1864
- maxScore: ASSESSMENT_SCALE,
1851
+ maxScore: ASSESSMENT_SCALE$1,
1865
1852
  weight: dimension.weight
1866
1853
  })), item, rounding !== void 0 ? { rounding } : {});
1867
1854
  if ("unscorable" in graded) return unscorable("invalid_assessment", `No weighted total could be computed from the assessment's scores: ${graded.reason}`);
@@ -1888,6 +1875,207 @@ function gradeReadAloud(data, response, assessment, options) {
1888
1875
  function referenceWordsOf(data) {
1889
1876
  return require_registry.dictationReferenceWords({ transcript: data.referenceText })[0];
1890
1877
  }
1878
+ // src/scoring/speech/grade-speaking.ts
1879
+ /**
1880
+ * The spoken-answer grade: a rubric scored partly by a judge, from the
1881
+ * transcript of what the learner said, and partly from an engine's measures of
1882
+ * how they said it.
1883
+ *
1884
+ * As for a read-aloud, the SDK calls no engine and no model. It is given the
1885
+ * application's unscripted assessment of the take, a judgement of the criteria
1886
+ * a judge scores, and the server's own measurement of the recording, and it
1887
+ * refuses evidence it cannot tie to this item and this recording — a refusal is
1888
+ * never a zero.
1889
+ */
1890
+ /** What every assessor score is out of. */
1891
+ const ASSESSMENT_SCALE = 100;
1892
+ /** The assessment score each measure is read from. */
1893
+ const MEASURE_SCORE = {
1894
+ pronunciation: "accuracy",
1895
+ fluency: "fluency",
1896
+ prosody: "prosody"
1897
+ };
1898
+ /**
1899
+ * How many words were spoken, for the speech-rate check: the transcript's when
1900
+ * the evidence carries one — it is the text the judge grades — and otherwise
1901
+ * what the engine recognised.
1902
+ */
1903
+ function wordsSpoken(evidence) {
1904
+ return evidence.transcript !== void 0 ? require_registry.countWords(evidence.transcript.text) : spokenWordCount(evidence);
1905
+ }
1906
+ /**
1907
+ * The judged criteria, each read once and matched to the criterion of the
1908
+ * rubric it judges — or why the judgement cannot be graded with this item.
1909
+ */
1910
+ function matchJudgement(judgement, byJudge) {
1911
+ const listed = judgement.criteria;
1912
+ if (!Array.isArray(listed)) return "The judgement carries no list of criteria.";
1913
+ const wanted = new Map(byJudge.map((criterion) => [require_registry.levelKey(criterion.name), criterion]));
1914
+ const judged = /* @__PURE__ */ new Set();
1915
+ const criteria = [];
1916
+ for (const one of listed) {
1917
+ const { name, score, level, probabilities, comment } = one ?? {};
1918
+ const key = typeof name === "string" ? require_registry.levelKey(name) : void 0;
1919
+ const criterion = key === void 0 ? void 0 : wanted.get(key);
1920
+ if (key === void 0 || criterion === void 0 || judged.has(key)) return `The judgement names criterion ${typeof name === "string" ? `"${name}"` : describeValue$1(name)}, which is not one the judge scores on this item once.`;
1921
+ if (typeof score !== "number" || !(score >= 0 && score <= 1)) return `The judgement scores "${criterion.name}" ${describeValue$1(score)}, off its 0..1 scale.`;
1922
+ judged.add(key);
1923
+ criteria.push({
1924
+ name: criterion.name,
1925
+ score,
1926
+ weight: criterion.weight,
1927
+ ...level !== void 0 ? { band: level } : {},
1928
+ ...probabilities !== void 0 ? { levelProbabilities: [...probabilities] } : {},
1929
+ ...comment !== void 0 ? { comment } : {}
1930
+ });
1931
+ }
1932
+ const left = byJudge.find((criterion) => !judged.has(require_registry.levelKey(criterion.name)));
1933
+ if (left !== void 0) return `The judgement leaves out criterion "${left.name}". A total over fewer criteria would grade a different task.`;
1934
+ return criteria;
1935
+ }
1936
+ /**
1937
+ * Grades a spoken answer to a `speaking-response` item (1.9).
1938
+ *
1939
+ * Each criterion of the rubric is scored once:
1940
+ *
1941
+ * - **by the judge** when it names no `measure` — what was said, how it was
1942
+ * organised, the range and accuracy of the language — from
1943
+ * `evidence.judgement`: a model's reply as `checkAiGrading` read it, or a
1944
+ * person's marks in the same shape, each from 0 to 1;
1945
+ * - **from the engine** when it names one: `pronunciation` is the assessment's
1946
+ * accuracy, `fluency` its fluency, `prosody` its prosody, each out of 100. With
1947
+ * `options.judgeMeasures` the judge scores those too, and the engine's scores
1948
+ * are not read.
1949
+ *
1950
+ * The weighted total is `gradeFromRubric`'s, over the item's weights and pass
1951
+ * line. Evidence is refused rather than scored, as a read-aloud's is: an
1952
+ * assessment that is not `unscripted`, not for the item's locale or not for
1953
+ * this recording; one from a model, unless `allowAiAssessor`; a take with no
1954
+ * speech, too little voiced time, or more words in its transcript than the
1955
+ * voiced time holds; a weighted measure the assessment has no score for
1956
+ * (`missing_dimension`); and a judgement that does not score each criterion the
1957
+ * judge scores exactly once, on its 0..1 scale (`judgement_mismatch`). A
1958
+ * judgement is matched whenever one is given, so marks meant for another item,
1959
+ * or for measures the judge was not asked to score, are refused.
1960
+ *
1961
+ * A blank (`response.recording === null`) is a grade of 0: the learner
1962
+ * submitted and said nothing. The evidence and the measurement are not read.
1963
+ *
1964
+ * With a judge, the grade is the judge's, as `gradeFromJudgement` makes one:
1965
+ * its feedback (or the authored feedback, when it gave none), its corrections
1966
+ * and rationale, `requiresHumanReview`, and `grader: { kind: 'ai', model,
1967
+ * promptHash }` — so `routeGrade` routes a spoken answer as it routes a written
1968
+ * one. Without one, the feedback is the authored feedback and the grader is the
1969
+ * engine that measured.
1970
+ *
1971
+ * @throws RangeError when `options.plausibility` or `options.rounding` cannot be
1972
+ * applied, or `options.measured` is not a pair of finite, non-negative
1973
+ * millisecond counts with `voicedMs` inside `durationMs`.
1974
+ * @throws TypeError when `response` is not a speaking-response response, or a
1975
+ * recording is graded without an assessment, a measurement, or — when the
1976
+ * judge scores a criterion — a judgement.
1977
+ * @throws ActivitySchemaError when `data` is not valid speaking-response data.
1978
+ * @throws RedactedScoringError when `data` is a `redact()` projection.
1979
+ */
1980
+ function gradeSpeakingResponse(data, response, evidence, options) {
1981
+ const maxWordsPerSecond = options?.plausibility?.maxWordsPerSecond;
1982
+ if (typeof maxWordsPerSecond !== "number" || !Number.isFinite(maxWordsPerSecond) || maxWordsPerSecond <= 0) throw new RangeError(`Invalid plausibility policy (maxWordsPerSecond ${describeValue$1(maxWordsPerSecond)}): expected a finite number above 0.`);
1983
+ const minVoicedMs = options?.plausibility?.minVoicedMs;
1984
+ if (typeof minVoicedMs !== "number" || !Number.isFinite(minVoicedMs) || minVoicedMs < 0) throw new RangeError(`Invalid plausibility policy (minVoicedMs ${describeValue$1(minVoicedMs)}): expected a finite number of milliseconds, 0 or above.`);
1985
+ const rounding = roundingPolicyOf(options?.rounding);
1986
+ const parsed = require_registry.SpeakingResponseDataSchema.safeParse(data);
1987
+ if (!parsed.success) throw new require_registry.ActivitySchemaError("speaking-response", parsed.error.issues.map((issue) => ({
1988
+ path: issue.path.map(String),
1989
+ message: issue.message,
1990
+ code: issue.code
1991
+ })));
1992
+ if (isRedacted(data) || isRedacted(parsed.data)) throw new require_registry.RedactedScoringError("speaking-response");
1993
+ const item = parsed.data;
1994
+ const checkedResponse = readResponseRecording(response, "speaking-response");
1995
+ if (checkedResponse === void 0) throw new TypeError("gradeSpeakingResponse needs a speaking-response response: { type: \"speaking-response\", recording: null } for a blank, or a recording with a non-empty `key` and a `mimeType`.");
1996
+ const recording = checkedResponse.recording;
1997
+ if (recording === null) {
1998
+ const passed = computePassThreshold(item, 0, rounding);
1999
+ return {
2000
+ score: 0,
2001
+ maxScore: 1,
2002
+ passed,
2003
+ feedback: selectFeedback(item, passed),
2004
+ criteria: []
2005
+ };
2006
+ }
2007
+ const judgeMeasures = options.judgeMeasures === true;
2008
+ const byJudge = item.rubric.criteria.filter((criterion) => judgeMeasures || criterion.measure === void 0);
2009
+ const byEngine = item.rubric.criteria.filter((criterion) => !judgeMeasures && criterion.measure !== void 0);
2010
+ const assessment = evidence?.assessment;
2011
+ if (assessment === null || assessment === void 0) throw new TypeError("gradeSpeakingResponse needs the engine's unscripted assessment of a response that carries a recording. Pass one, or grade the blank (recording: null).");
2012
+ const judgement = evidence.judgement ?? void 0;
2013
+ if (judgement === void 0 && byJudge.length > 0) throw new TypeError(`gradeSpeakingResponse needs a judgement of the criteria a judge scores on this item: ${byJudge.map((criterion) => `"${criterion.name}"`).join(", ")}.`);
2014
+ const measured = options.measured;
2015
+ if (measured === null || measured === void 0) throw new TypeError("gradeSpeakingResponse needs `options.measured`, the server's own measurement of the recording (inspectWav), for a response that carries a recording. A duration the client reported is not evidence.");
2016
+ const { durationMs, voicedMs } = measured;
2017
+ if (typeof durationMs !== "number" || !Number.isFinite(durationMs) || durationMs < 0 || typeof voicedMs !== "number" || !Number.isFinite(voicedMs) || voicedMs < 0 || voicedMs > durationMs + 1e-6) throw new RangeError(`Invalid recording measurement (durationMs ${describeValue$1(durationMs)}, voicedMs ${describeValue$1(voicedMs)}): expected finite millisecond counts, 0 or above, with voicedMs inside durationMs.`);
2018
+ const unscorable = (code, reason) => ({
2019
+ unscorable: true,
2020
+ code,
2021
+ reason
2022
+ });
2023
+ const checked = validateSpeechAssessment(assessment);
2024
+ if (!checked.success) return unscorable("invalid_assessment", `The speech assessment is not well formed (${describeValidationError(checked.errors[0])}).`);
2025
+ const engine = checked.data;
2026
+ if (engine.task !== "unscripted") return unscorable("task_mismatch", `The assessment was made for a "${engine.task}" task; a spoken answer is assessed unscripted, against what the learner said.`);
2027
+ if (engine.locale !== item.locale) return unscorable("locale_mismatch", `The assessment was made for locale "${engine.locale}", and the item is answered in "${item.locale}". Pronunciation is assessed against one locale's speech.`);
2028
+ if (engine.recordingKey !== recording.key) return unscorable("recording_mismatch", engine.recordingKey === void 0 ? `The assessment names no recording, and this response submitted "${recording.key}". Unscripted evidence must carry the key of the recording it measured.` : `The assessment was made for recording "${engine.recordingKey}", and this response submitted "${recording.key}".`);
2029
+ if (engine.assessor.kind === "ai" && options.allowAiAssessor !== true) return unscorable("assessor_not_accepted", "The assessment was produced by a generative model. Pass `allowAiAssessor: true` to accept that as measurement evidence.");
2030
+ if (engine.status === "no_speech") return unscorable("no_speech", "The assessor heard no speech in this recording.");
2031
+ if (voicedMs < minVoicedMs) return unscorable("insufficient_voiced_time", `The recording holds ${voicedMs} ms of voiced time, and the policy asks for at least ${minVoicedMs} ms.`);
2032
+ const words = wordsSpoken(engine);
2033
+ if (words > 0 && (voicedMs === 0 || words / (voicedMs / 1e3) > maxWordsPerSecond)) return unscorable("implausible_speech_rate", `The assessment claims ${words} word(s) in ${voicedMs} ms of voiced time, above the ${maxWordsPerSecond} word(s) per second the policy allows.`);
2034
+ const missing = byEngine.find((criterion) => criterion.weight > 0 && engine.scores[MEASURE_SCORE[criterion.measure]] === void 0);
2035
+ if (missing !== void 0) {
2036
+ const score = MEASURE_SCORE[missing.measure];
2037
+ return unscorable("missing_dimension", `The item scores "${missing.name}" from ${missing.measure}, and the assessment carries no ${score} score. An absent score is not a 0.`);
2038
+ }
2039
+ const criteria = [];
2040
+ for (const criterion of byEngine) {
2041
+ const score = engine.scores[MEASURE_SCORE[criterion.measure]];
2042
+ if (score !== void 0) criteria.push({
2043
+ name: criterion.name,
2044
+ score,
2045
+ maxScore: ASSESSMENT_SCALE,
2046
+ weight: criterion.weight
2047
+ });
2048
+ }
2049
+ if (judgement !== void 0) {
2050
+ const judged = matchJudgement(judgement, byJudge);
2051
+ if (typeof judged === "string") return unscorable("judgement_mismatch", judged);
2052
+ criteria.push(...judged);
2053
+ }
2054
+ const order = new Map(item.rubric.criteria.map((criterion, index) => [require_registry.levelKey(criterion.name), index]));
2055
+ criteria.sort((a, b) => order.get(require_registry.levelKey(a.name)) - order.get(require_registry.levelKey(b.name)));
2056
+ const graded = gradeFromRubric(criteria, item, rounding !== void 0 ? { rounding } : {});
2057
+ if ("unscorable" in graded) return unscorable("invalid_assessment", `No weighted total could be computed from the evidence: ${graded.reason}`);
2058
+ if (byJudge.length === 0 || judgement === void 0) return {
2059
+ ...graded,
2060
+ feedback: selectFeedback(item, graded.passed),
2061
+ grader: { ...engine.assessor }
2062
+ };
2063
+ const { provenance, usage } = judgement;
2064
+ return {
2065
+ ...graded,
2066
+ feedback: judgement.feedback ?? selectFeedback(item, graded.passed),
2067
+ ...judgement.corrections.length > 0 ? { corrections: [...judgement.corrections] } : {},
2068
+ ...judgement.rationale !== void 0 ? { rationale: judgement.rationale } : {},
2069
+ requiresHumanReview: judgement.requiresHumanReview,
2070
+ grader: {
2071
+ kind: "ai",
2072
+ ...provenance?.model !== void 0 ? { model: provenance.model } : {},
2073
+ ...provenance?.promptHash !== void 0 ? { promptHash: provenance.promptHash } : {}
2074
+ },
2075
+ ...usage !== void 0 ? { usage } : {},
2076
+ ...provenance?.generatedAt !== void 0 ? { gradedAt: provenance.generatedAt } : {}
2077
+ };
2078
+ }
1891
2079
  // src/scoring/speech/wav.ts
1892
2080
  /** `RIFF` + the file size + `WAVE`, before the first chunk. */
1893
2081
  const HEADER_BYTES = 12;
@@ -2336,6 +2524,12 @@ Object.defineProperty(exports, "gradeReadAloud", {
2336
2524
  return gradeReadAloud;
2337
2525
  }
2338
2526
  });
2527
+ Object.defineProperty(exports, "gradeSpeakingResponse", {
2528
+ enumerable: true,
2529
+ get: function() {
2530
+ return gradeSpeakingResponse;
2531
+ }
2532
+ });
2339
2533
  Object.defineProperty(exports, "graderConfiguration", {
2340
2534
  enumerable: true,
2341
2535
  get: function() {
@@ -2445,4 +2639,4 @@ Object.defineProperty(exports, "validateSpeechAssessment", {
2445
2639
  }
2446
2640
  });
2447
2641
 
2448
- //# sourceMappingURL=scoring-CtkCFyFT.cjs.map
2642
+ //# sourceMappingURL=scoring-Db1CZeAn.cjs.map