@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.
- package/CHANGELOG.md +33 -0
- package/dist/ai-check.cjs +77 -3
- package/dist/ai-check.cjs.map +1 -1
- package/dist/ai-check.d.cts +11 -3
- package/dist/ai-check.d.cts.map +1 -1
- package/dist/ai-check.d.ts +11 -3
- package/dist/ai-check.d.ts.map +1 -1
- package/dist/ai-check.js +77 -3
- package/dist/ai-check.js.map +1 -1
- package/dist/{ai-writing-B7oMCt66.js → ai-writing-DQjI9EFN.js} +2514 -136
- package/dist/ai-writing-DQjI9EFN.js.map +1 -0
- package/dist/{ai-writing-DrPTV3R2.cjs → ai-writing-DbpZpGRk.cjs} +2614 -122
- package/dist/ai-writing-DbpZpGRk.cjs.map +1 -0
- package/dist/{ai-B1wYrqcB.d.ts → ai-zhc9yhRj.d.cts} +454 -2
- package/dist/ai-zhc9yhRj.d.cts.map +1 -0
- package/dist/{ai-B1wYrqcB.d.cts → ai-zhc9yhRj.d.ts} +454 -2
- package/dist/ai-zhc9yhRj.d.ts.map +1 -0
- package/dist/{index-XCCoPoTU.d.cts → index-BeC8tAWf.d.cts} +2 -2
- package/dist/{index-XCCoPoTU.d.cts.map → index-BeC8tAWf.d.cts.map} +1 -1
- package/dist/{index-BU99GFTl.d.ts → index-Byar5VVs.d.ts} +2 -2
- package/dist/{index-BU99GFTl.d.ts.map → index-Byar5VVs.d.ts.map} +1 -1
- package/dist/{index-DP9rvyOz.d.cts → index-Ccj8oL_E.d.cts} +2 -2
- package/dist/{index-DP9rvyOz.d.cts.map → index-Ccj8oL_E.d.cts.map} +1 -1
- package/dist/{index-BTkMQtws.d.ts → index-CxkqS62X.d.ts} +89 -7
- package/dist/index-CxkqS62X.d.ts.map +1 -0
- package/dist/{index-BM8LeDWn.d.cts → index-DLsUnGHt.d.cts} +89 -7
- package/dist/index-DLsUnGHt.d.cts.map +1 -0
- package/dist/{index-BdXs4-en.d.ts → index-f4YuMgFb.d.ts} +2 -2
- package/dist/{index-BdXs4-en.d.ts.map → index-f4YuMgFb.d.ts.map} +1 -1
- package/dist/index.cjs +282 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +262 -7
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +262 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +269 -7
- package/dist/index.js.map +1 -1
- package/dist/{item-group-CeDk7mfM.js → item-group-CKZk3txt.js} +2 -2
- package/dist/{item-group-CeDk7mfM.js.map → item-group-CKZk3txt.js.map} +1 -1
- package/dist/{item-group-8P3WFuUK.cjs → item-group-COP5OwEC.cjs} +2 -2
- package/dist/{item-group-8P3WFuUK.cjs.map → item-group-COP5OwEC.cjs.map} +1 -1
- package/dist/{registry-D92hz9iA.js → registry-Bw9grsCN.js} +339 -12
- package/dist/registry-Bw9grsCN.js.map +1 -0
- package/dist/{registry-hzrkTeIT.cjs → registry-C9O9txy5.cjs} +368 -11
- package/dist/registry-C9O9txy5.cjs.map +1 -0
- package/dist/{schemas-C7TViWCg.js → schemas-BJe3W6Xb.js} +3 -3
- package/dist/{schemas-C7TViWCg.js.map → schemas-BJe3W6Xb.js.map} +1 -1
- package/dist/{schemas-BWeskONy.cjs → schemas-CXlemRvB.cjs} +3 -3
- package/dist/{schemas-BWeskONy.cjs.map → schemas-CXlemRvB.cjs.map} +1 -1
- package/dist/schemas.cjs +3 -3
- package/dist/schemas.d.cts +1 -1
- package/dist/schemas.d.ts +1 -1
- package/dist/schemas.js +3 -3
- package/dist/{scoring-DD1GHcLI.js → scoring-BGKdff2i.js} +214 -26
- package/dist/scoring-BGKdff2i.js.map +1 -0
- package/dist/{scoring-CtkCFyFT.cjs → scoring-Db1CZeAn.cjs} +219 -25
- package/dist/scoring-Db1CZeAn.cjs.map +1 -0
- package/dist/scoring.cjs +3 -2
- package/dist/scoring.d.cts +3 -3
- package/dist/scoring.d.ts +3 -3
- package/dist/scoring.js +3 -3
- package/dist/{xapi-Cgicg-zU.js → xapi-BtBm8AWZ.js} +2 -2
- package/dist/{xapi-Cgicg-zU.js.map → xapi-BtBm8AWZ.js.map} +1 -1
- package/dist/{xapi-7btSpx6v.cjs → xapi-DOAqH9__.cjs} +2 -2
- package/dist/{xapi-7btSpx6v.cjs.map → xapi-DOAqH9__.cjs.map} +1 -1
- package/dist/xapi.cjs +1 -1
- package/dist/xapi.d.cts +1 -1
- package/dist/xapi.d.ts +1 -1
- package/dist/xapi.js +1 -1
- package/package.json +2 -2
- package/vectors/README.md +23 -0
- package/vectors/scoring.json +11967 -1
- package/vectors/validation.json +1298 -1
- package/vectors/variants.d.mts +20 -0
- package/vectors/variants.json +5753 -0
- package/vectors/variants.mjs +67 -0
- package/dist/ai-B1wYrqcB.d.cts.map +0 -1
- package/dist/ai-B1wYrqcB.d.ts.map +0 -1
- package/dist/ai-writing-B7oMCt66.js.map +0 -1
- package/dist/ai-writing-DrPTV3R2.cjs.map +0 -1
- package/dist/index-BM8LeDWn.d.cts.map +0 -1
- package/dist/index-BTkMQtws.d.ts.map +0 -1
- package/dist/registry-D92hz9iA.js.map +0 -1
- package/dist/registry-hzrkTeIT.cjs.map +0 -1
- package/dist/scoring-CtkCFyFT.cjs.map +0 -1
- package/dist/scoring-DD1GHcLI.js.map +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const require_registry = require("./registry-
|
|
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 !==
|
|
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 +
|
|
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-
|
|
2642
|
+
//# sourceMappingURL=scoring-Db1CZeAn.cjs.map
|