@playcademy/vite-plugin 1.3.2-beta.1 → 1.3.2-beta.2
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/dist/index.js +1850 -497
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -24317,7 +24317,7 @@ import path2 from "node:path";
|
|
|
24317
24317
|
// package.json
|
|
24318
24318
|
var package_default = {
|
|
24319
24319
|
name: "@playcademy/vite-plugin",
|
|
24320
|
-
version: "1.3.2-beta.
|
|
24320
|
+
version: "1.3.2-beta.2",
|
|
24321
24321
|
type: "module",
|
|
24322
24322
|
exports: {
|
|
24323
24323
|
".": {
|
|
@@ -25934,7 +25934,7 @@ var package_default2;
|
|
|
25934
25934
|
var init_package = __esm(() => {
|
|
25935
25935
|
package_default2 = {
|
|
25936
25936
|
name: "@playcademy/sandbox",
|
|
25937
|
-
version: "0.8.1-beta.
|
|
25937
|
+
version: "0.8.1-beta.5",
|
|
25938
25938
|
description: "Local development server for Playcademy game development",
|
|
25939
25939
|
type: "module",
|
|
25940
25940
|
exports: {
|
|
@@ -34232,7 +34232,18 @@ function qtiDirectedPairValidationIssue(values, contract) {
|
|
|
34232
34232
|
}
|
|
34233
34233
|
return null;
|
|
34234
34234
|
}
|
|
34235
|
-
function
|
|
34235
|
+
function validNumericMatchValue(value, baseType) {
|
|
34236
|
+
const syntax = baseType === "integer" ? /^[+-]?\d+$/ : /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?$/i;
|
|
34237
|
+
if (!syntax.test(value.trim())) {
|
|
34238
|
+
return false;
|
|
34239
|
+
}
|
|
34240
|
+
const numeric = Number(value);
|
|
34241
|
+
return Number.isFinite(numeric) && (baseType === "float" || Number.isSafeInteger(numeric));
|
|
34242
|
+
}
|
|
34243
|
+
function sameScore(left, right) {
|
|
34244
|
+
return Number.isFinite(left) && Number.isFinite(right) && Math.abs(left - right) <= Number.EPSILON * Math.max(1, Math.abs(left), Math.abs(right));
|
|
34245
|
+
}
|
|
34246
|
+
function isRecord3(value) {
|
|
34236
34247
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
34237
34248
|
}
|
|
34238
34249
|
function stringField(value) {
|
|
@@ -34327,13 +34338,13 @@ function dedupeStandards(standards) {
|
|
|
34327
34338
|
return [...deduped.values()];
|
|
34328
34339
|
}
|
|
34329
34340
|
function qtiAlignmentStandardsFromMetadata(metadata2) {
|
|
34330
|
-
if (!
|
|
34341
|
+
if (!isRecord3(metadata2)) {
|
|
34331
34342
|
return [];
|
|
34332
34343
|
}
|
|
34333
34344
|
const standards = [];
|
|
34334
34345
|
const alignmentGroups = Array.isArray(metadata2.alignment) ? metadata2.alignment : [];
|
|
34335
34346
|
for (const group of alignmentGroups) {
|
|
34336
|
-
if (
|
|
34347
|
+
if (isRecord3(group)) {
|
|
34337
34348
|
const source = stringField(group.curriculum) || "QTI";
|
|
34338
34349
|
const domains2 = Array.isArray(group.domains) ? group.domains : [];
|
|
34339
34350
|
if (!domains2.length) {
|
|
@@ -34351,11 +34362,11 @@ function qtiAlignmentStandardsFromMetadata(metadata2) {
|
|
|
34351
34362
|
}
|
|
34352
34363
|
} else {
|
|
34353
34364
|
for (const domain of domains2) {
|
|
34354
|
-
if (
|
|
34365
|
+
if (isRecord3(domain)) {
|
|
34355
34366
|
const domainName = stringField(domain.name) || undefined;
|
|
34356
34367
|
const domainStandards = Array.isArray(domain.standards) ? domain.standards : [];
|
|
34357
34368
|
for (const standard of domainStandards) {
|
|
34358
|
-
if (
|
|
34369
|
+
if (isRecord3(standard)) {
|
|
34359
34370
|
const identifier = stringField(standard.identifier).trim();
|
|
34360
34371
|
const id = stringField(standard.id).trim() || identifier;
|
|
34361
34372
|
if (id) {
|
|
@@ -34406,12 +34417,9 @@ function isAssessmentAttemptSuperseded(attempt) {
|
|
|
34406
34417
|
return attempt.inProgress === ASSESSMENT_ATTEMPT_SUPERSEDED.inProgress && attempt.scoreStatus === ASSESSMENT_ATTEMPT_SUPERSEDED.scoreStatus;
|
|
34407
34418
|
}
|
|
34408
34419
|
function classifyAssessmentSubmission(attempt, submissionId) {
|
|
34409
|
-
if (isAssessmentAttemptAwaitingAward(attempt)) {
|
|
34420
|
+
if (isAssessmentAttemptAwaitingAward(attempt) || isAssessmentAttemptCompleted(attempt)) {
|
|
34410
34421
|
return attempt.submissionId === submissionId ? "replay" : "reject";
|
|
34411
34422
|
}
|
|
34412
|
-
if (isAssessmentAttemptCompleted(attempt)) {
|
|
34413
|
-
return "reject";
|
|
34414
|
-
}
|
|
34415
34423
|
return isAssessmentAttemptOpen(attempt) ? "submit" : "reject";
|
|
34416
34424
|
}
|
|
34417
34425
|
function isSameAssessmentAward(recorded, input) {
|
|
@@ -34503,10 +34511,13 @@ function canonicalJson(value) {
|
|
|
34503
34511
|
const entries = Object.entries(value).filter(([, entryValue]) => entryValue !== undefined).toSorted(([left], [right]) => compareCodeUnits(left, right)).map(([key, entryValue]) => `${JSON.stringify(key)}:${canonicalJson(entryValue)}`);
|
|
34504
34512
|
return `{${entries.join(",")}}`;
|
|
34505
34513
|
}
|
|
34506
|
-
async function
|
|
34507
|
-
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(canonicalJson(
|
|
34514
|
+
async function canonicalJsonSha256(value) {
|
|
34515
|
+
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(canonicalJson(value)));
|
|
34508
34516
|
return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
34509
34517
|
}
|
|
34518
|
+
function diagnosticRoutingRevision(manifest) {
|
|
34519
|
+
return canonicalJsonSha256(manifest);
|
|
34520
|
+
}
|
|
34510
34521
|
function issue(severity, code, message, path3, details) {
|
|
34511
34522
|
return { severity, code, message, ...path3 ? { path: path3 } : {}, ...details ? { details } : {} };
|
|
34512
34523
|
}
|
|
@@ -35563,11 +35574,11 @@ function itemResponsesEqual(left, right) {
|
|
|
35563
35574
|
function itemResponseUpdateMatches(current, update) {
|
|
35564
35575
|
return itemResponsesEqual(current, applyAssessmentItemResponseUpdate(current, update));
|
|
35565
35576
|
}
|
|
35566
|
-
function
|
|
35577
|
+
function resolveAssessmentItemReplay(state, input) {
|
|
35567
35578
|
const flow = assessmentFlowForPurpose(state.purpose);
|
|
35568
35579
|
if (flow !== "item-submit") {
|
|
35569
35580
|
return {
|
|
35570
|
-
action: "
|
|
35581
|
+
action: "conflict",
|
|
35571
35582
|
failure: assessmentFlowViolation("Items cannot be submitted individually in this flow.", { flow })
|
|
35572
35583
|
};
|
|
35573
35584
|
}
|
|
@@ -35576,7 +35587,7 @@ function prepareAssessmentItemSubmission(state, input) {
|
|
|
35576
35587
|
const sameRequest = priorSubmission.itemIdentifier === input.itemIdentifier && itemResponseUpdateMatches(state.responses[input.itemIdentifier], input.responses);
|
|
35577
35588
|
if (!sameRequest) {
|
|
35578
35589
|
return {
|
|
35579
|
-
action: "
|
|
35590
|
+
action: "conflict",
|
|
35580
35591
|
failure: assessmentFlowViolation("This item submission ID was already used for a different request.", {
|
|
35581
35592
|
submissionId: input.submissionId,
|
|
35582
35593
|
itemIdentifier: input.itemIdentifier,
|
|
@@ -35592,6 +35603,16 @@ function prepareAssessmentItemSubmission(state, input) {
|
|
|
35592
35603
|
submission: priorSubmission
|
|
35593
35604
|
};
|
|
35594
35605
|
}
|
|
35606
|
+
return { action: "missing" };
|
|
35607
|
+
}
|
|
35608
|
+
function prepareAssessmentItemSubmission(state, input) {
|
|
35609
|
+
const replay = resolveAssessmentItemReplay(state, input);
|
|
35610
|
+
if (replay.action === "conflict") {
|
|
35611
|
+
return { action: "reject", failure: replay.failure };
|
|
35612
|
+
}
|
|
35613
|
+
if (replay.action === "replay") {
|
|
35614
|
+
return replay;
|
|
35615
|
+
}
|
|
35595
35616
|
if (state.responseVersion !== input.expectedResponseVersion) {
|
|
35596
35617
|
return {
|
|
35597
35618
|
action: "reject",
|
|
@@ -35607,7 +35628,7 @@ function prepareAssessmentItemSubmission(state, input) {
|
|
|
35607
35628
|
failure: assessmentFlowViolation("Items must be submitted once and in candidate order.", {
|
|
35608
35629
|
itemIdentifier: input.itemIdentifier,
|
|
35609
35630
|
previousItemIdentifier: previousItemIdentifier ?? null,
|
|
35610
|
-
flow
|
|
35631
|
+
flow: assessmentFlowForPurpose(state.purpose)
|
|
35611
35632
|
})
|
|
35612
35633
|
};
|
|
35613
35634
|
}
|
|
@@ -35637,7 +35658,8 @@ function completeAssessmentItemSubmission(state, input, prepared, scoring, submi
|
|
|
35637
35658
|
responseVersion: prepared.responseVersion,
|
|
35638
35659
|
answered: prepared.answered,
|
|
35639
35660
|
score: scoring.score,
|
|
35640
|
-
isCorrect: scoring.isCorrect
|
|
35661
|
+
isCorrect: scoring.isCorrect,
|
|
35662
|
+
...scoring.grading ? { grading: scoring.grading } : {}
|
|
35641
35663
|
};
|
|
35642
35664
|
return {
|
|
35643
35665
|
responseVersion: prepared.responseVersion,
|
|
@@ -35792,6 +35814,145 @@ function assessmentPresentationForAttempt(assessment, attemptId) {
|
|
|
35792
35814
|
});
|
|
35793
35815
|
return { ...assessment, items };
|
|
35794
35816
|
}
|
|
35817
|
+
function normalizedMatchText(value, baseType) {
|
|
35818
|
+
const normalized = value.replace(ZERO_WIDTH_CHARACTERS, "").replace(CURLY_SINGLE_QUOTES, "'").replace(CURLY_DOUBLE_QUOTES, '"').replace(UNICODE_DASHES, "-");
|
|
35819
|
+
return baseType === "directedPair" ? normalized : normalized.replace(/[\s\u00A0]+/g, " ").trim();
|
|
35820
|
+
}
|
|
35821
|
+
function normalizedArithmeticText(value) {
|
|
35822
|
+
return value.replace(THOUSANDS_GROUP, (number) => number.replaceAll(",", "")).replace(ARITHMETIC_OPERATOR_SPACING, "$1").replaceAll("÷", "/").replace(/[\u00D7\u00B7]/g, "*");
|
|
35823
|
+
}
|
|
35824
|
+
function parsedMatchValue(value, baseType) {
|
|
35825
|
+
if (typeof value !== "string") {
|
|
35826
|
+
return null;
|
|
35827
|
+
}
|
|
35828
|
+
const normalized = normalizedMatchText(value, baseType);
|
|
35829
|
+
if (baseType === "identifier" && !IDENTIFIER_VALUE.test(normalized)) {
|
|
35830
|
+
return null;
|
|
35831
|
+
}
|
|
35832
|
+
if (baseType === "directedPair" && !DIRECTED_PAIR_VALUE.test(normalized)) {
|
|
35833
|
+
return null;
|
|
35834
|
+
}
|
|
35835
|
+
return normalized;
|
|
35836
|
+
}
|
|
35837
|
+
function matchValuesEqual(left, right, baseType) {
|
|
35838
|
+
return left === right || baseType !== "identifier" && baseType !== "directedPair" && normalizedArithmeticText(left) === normalizedArithmeticText(right);
|
|
35839
|
+
}
|
|
35840
|
+
function parsedMatchValues(values, baseType) {
|
|
35841
|
+
const parsed = values.map((value) => parsedMatchValue(value, baseType));
|
|
35842
|
+
return parsed.every((value) => value !== null) ? parsed : null;
|
|
35843
|
+
}
|
|
35844
|
+
function responseValues2(value) {
|
|
35845
|
+
if (typeof value === "string") {
|
|
35846
|
+
return [value];
|
|
35847
|
+
}
|
|
35848
|
+
return Array.isArray(value) ? value : null;
|
|
35849
|
+
}
|
|
35850
|
+
function unorderedMatch(responses, correct, baseType) {
|
|
35851
|
+
if (responses.length !== correct.length) {
|
|
35852
|
+
return false;
|
|
35853
|
+
}
|
|
35854
|
+
const matched = new Set;
|
|
35855
|
+
for (const response of responses) {
|
|
35856
|
+
const match = correct.findIndex((candidate, index) => !matched.has(index) && matchValuesEqual(response, candidate, baseType));
|
|
35857
|
+
if (match === -1) {
|
|
35858
|
+
return false;
|
|
35859
|
+
}
|
|
35860
|
+
matched.add(match);
|
|
35861
|
+
}
|
|
35862
|
+
return true;
|
|
35863
|
+
}
|
|
35864
|
+
function matchComparisonCorrect(rule, values) {
|
|
35865
|
+
if (rule.comparison.kind !== "match" || rule.comparison.correctValues.length === 0) {
|
|
35866
|
+
return false;
|
|
35867
|
+
}
|
|
35868
|
+
const responses = parsedMatchValues(values, rule.baseType);
|
|
35869
|
+
const correct = parsedMatchValues(rule.comparison.correctValues, rule.baseType);
|
|
35870
|
+
if (!responses || !correct) {
|
|
35871
|
+
return false;
|
|
35872
|
+
}
|
|
35873
|
+
if (rule.cardinality === "single") {
|
|
35874
|
+
if (responses.length !== 1) {
|
|
35875
|
+
return false;
|
|
35876
|
+
}
|
|
35877
|
+
if (rule.baseType === "string") {
|
|
35878
|
+
return correct.some((candidate) => matchValuesEqual(responses[0], candidate, rule.baseType));
|
|
35879
|
+
}
|
|
35880
|
+
return correct.length === 1 && matchValuesEqual(responses[0], correct[0], rule.baseType);
|
|
35881
|
+
}
|
|
35882
|
+
if (responses.length !== correct.length) {
|
|
35883
|
+
return false;
|
|
35884
|
+
}
|
|
35885
|
+
if (rule.cardinality === "ordered") {
|
|
35886
|
+
return responses.every((value, index) => matchValuesEqual(value, correct[index], rule.baseType));
|
|
35887
|
+
}
|
|
35888
|
+
return unorderedMatch(responses, correct, rule.baseType);
|
|
35889
|
+
}
|
|
35890
|
+
function roundedNumericValue(value, roundingMode, figures) {
|
|
35891
|
+
if (!Number.isFinite(value) || !Number.isSafeInteger(figures) || (roundingMode === "decimalPlaces" ? figures < 0 : figures <= 0)) {
|
|
35892
|
+
return null;
|
|
35893
|
+
}
|
|
35894
|
+
if (roundingMode === "decimalPlaces") {
|
|
35895
|
+
const multiplier2 = 10 ** figures;
|
|
35896
|
+
const rounded2 = Math.round(value * multiplier2) / multiplier2;
|
|
35897
|
+
return Number.isFinite(rounded2) ? rounded2 : null;
|
|
35898
|
+
}
|
|
35899
|
+
if (value === 0) {
|
|
35900
|
+
return 0;
|
|
35901
|
+
}
|
|
35902
|
+
const magnitude = Math.floor(Math.log10(Math.abs(value)));
|
|
35903
|
+
const multiplier = 10 ** (figures - magnitude - 1);
|
|
35904
|
+
const rounded = Math.round(value * multiplier) / multiplier;
|
|
35905
|
+
return Number.isFinite(rounded) ? rounded : null;
|
|
35906
|
+
}
|
|
35907
|
+
function numericResponseValue(value) {
|
|
35908
|
+
if (typeof value !== "string") {
|
|
35909
|
+
return null;
|
|
35910
|
+
}
|
|
35911
|
+
const numeric = Number(value);
|
|
35912
|
+
return Number.isFinite(numeric) ? numeric : null;
|
|
35913
|
+
}
|
|
35914
|
+
function numericComparisonCorrect(rule, values) {
|
|
35915
|
+
if (values.length !== 1 || rule.cardinality !== "single" || rule.baseType !== "integer" && rule.baseType !== "float" || rule.comparison.kind === "match") {
|
|
35916
|
+
return false;
|
|
35917
|
+
}
|
|
35918
|
+
const response = numericResponseValue(values[0]);
|
|
35919
|
+
const correct = rule.comparison.correctValue;
|
|
35920
|
+
if (response === null || !Number.isFinite(correct) || rule.baseType === "integer" && !Number.isSafeInteger(correct)) {
|
|
35921
|
+
return false;
|
|
35922
|
+
}
|
|
35923
|
+
if (rule.comparison.kind === "numeric-equal") {
|
|
35924
|
+
return response === correct;
|
|
35925
|
+
}
|
|
35926
|
+
const { roundingMode, figures } = rule.comparison;
|
|
35927
|
+
const roundedResponse = roundedNumericValue(response, roundingMode, figures);
|
|
35928
|
+
const roundedCorrect = roundedNumericValue(correct, roundingMode, figures);
|
|
35929
|
+
return roundedResponse !== null && roundedCorrect !== null && roundedResponse === roundedCorrect;
|
|
35930
|
+
}
|
|
35931
|
+
function ruleCorrect(rule, responses) {
|
|
35932
|
+
const response = responses?.[rule.responseIdentifier];
|
|
35933
|
+
if (response === undefined) {
|
|
35934
|
+
return false;
|
|
35935
|
+
}
|
|
35936
|
+
const values = responseValues2(response);
|
|
35937
|
+
if (!values) {
|
|
35938
|
+
return false;
|
|
35939
|
+
}
|
|
35940
|
+
return rule.comparison.kind === "match" ? matchComparisonCorrect(rule, values) : numericComparisonCorrect(rule, values);
|
|
35941
|
+
}
|
|
35942
|
+
function scorePlatformAssessmentItem(item, responses) {
|
|
35943
|
+
const verdicts = item.rules.map((rule) => ruleCorrect(rule, responses));
|
|
35944
|
+
const rawEarned = item.rules.reduce((total, rule, index) => total + (verdicts[index] ? rule.points : 0), 0);
|
|
35945
|
+
const earned = Math.min(item.maxScore, Math.max(0, rawEarned));
|
|
35946
|
+
return {
|
|
35947
|
+
itemIdentifier: item.itemIdentifier,
|
|
35948
|
+
score: {
|
|
35949
|
+
earned,
|
|
35950
|
+
possible: item.maxScore,
|
|
35951
|
+
normalized: item.maxScore <= 0 ? 0 : Math.min(1, Math.max(0, earned / item.maxScore))
|
|
35952
|
+
},
|
|
35953
|
+
isCorrect: verdicts.length > 0 && verdicts.every(Boolean)
|
|
35954
|
+
};
|
|
35955
|
+
}
|
|
35795
35956
|
function reviewStandardFieldsWithinLimits(input) {
|
|
35796
35957
|
return input.framework.trim().length <= TIMEBACK_ASSESSMENT_REVIEW_REQUEST_LIMITS.standardFieldLength && input.identifier.trim().length <= TIMEBACK_ASSESSMENT_REVIEW_REQUEST_LIMITS.standardFieldLength;
|
|
35797
35958
|
}
|
|
@@ -36278,6 +36439,64 @@ function assessmentItemEarnedScore(interactionPoints, maxScore) {
|
|
|
36278
36439
|
const earned = interactionPoints.reduce((sum, points) => sum + Math.max(0, points), 0);
|
|
36279
36440
|
return Math.min(earned, maxScore);
|
|
36280
36441
|
}
|
|
36442
|
+
async function assessmentChildResultPayloadHash(payload) {
|
|
36443
|
+
return canonicalJsonSha256({
|
|
36444
|
+
kind: ASSESSMENT_CHILD_RESULT_KIND,
|
|
36445
|
+
version: ASSESSMENT_CHILD_RESULT_VERSION,
|
|
36446
|
+
payload
|
|
36447
|
+
});
|
|
36448
|
+
}
|
|
36449
|
+
async function buildAssessmentChildResultCommand(payload, recordedAt) {
|
|
36450
|
+
const parsedPayload = AssessmentChildResultPayloadV1Schema.parse(payload);
|
|
36451
|
+
const command = {
|
|
36452
|
+
kind: ASSESSMENT_CHILD_RESULT_KIND,
|
|
36453
|
+
version: ASSESSMENT_CHILD_RESULT_VERSION,
|
|
36454
|
+
recordedAt,
|
|
36455
|
+
payloadHash: await assessmentChildResultPayloadHash(parsedPayload),
|
|
36456
|
+
payload: parsedPayload
|
|
36457
|
+
};
|
|
36458
|
+
return AssessmentChildResultCommandV1Schema.parse(command);
|
|
36459
|
+
}
|
|
36460
|
+
function normalizeOmittedResponses(value) {
|
|
36461
|
+
if (!isRecord(value) || !isRecord(value.payload) || value.payload.responses !== undefined) {
|
|
36462
|
+
return value;
|
|
36463
|
+
}
|
|
36464
|
+
return {
|
|
36465
|
+
...value,
|
|
36466
|
+
payload: {
|
|
36467
|
+
...value.payload,
|
|
36468
|
+
responses: {}
|
|
36469
|
+
}
|
|
36470
|
+
};
|
|
36471
|
+
}
|
|
36472
|
+
function parseAssessmentChildResultCommand(value) {
|
|
36473
|
+
const direct = AssessmentChildResultCommandV1Schema.safeParse(normalizeOmittedResponses(value));
|
|
36474
|
+
if (direct.success) {
|
|
36475
|
+
return direct.data;
|
|
36476
|
+
}
|
|
36477
|
+
if (!isRecord(value)) {
|
|
36478
|
+
return null;
|
|
36479
|
+
}
|
|
36480
|
+
const nested = AssessmentChildResultCommandV1Schema.safeParse(normalizeOmittedResponses(value[PLAYCADEMY_ASSESSMENT_ITEM_RESULT_METADATA_KEY]));
|
|
36481
|
+
return nested.success ? nested.data : null;
|
|
36482
|
+
}
|
|
36483
|
+
async function verifyAssessmentChildResultCommand(metadataOrCommand) {
|
|
36484
|
+
const command = parseAssessmentChildResultCommand(metadataOrCommand);
|
|
36485
|
+
if (!command) {
|
|
36486
|
+
return { ok: false, reason: "malformed" };
|
|
36487
|
+
}
|
|
36488
|
+
if (await assessmentChildResultPayloadHash(command.payload) !== command.payloadHash) {
|
|
36489
|
+
return { ok: false, reason: "payload_hash_mismatch" };
|
|
36490
|
+
}
|
|
36491
|
+
return { ok: true, command };
|
|
36492
|
+
}
|
|
36493
|
+
function assessmentChildResultId(attemptId, submissionId) {
|
|
36494
|
+
return deterministicUUID([
|
|
36495
|
+
ASSESSMENT_CHILD_RESULT_ID_NAMESPACE,
|
|
36496
|
+
IdentitySchema.parse(attemptId),
|
|
36497
|
+
IdentitySchema.parse(submissionId)
|
|
36498
|
+
].join("\x00"));
|
|
36499
|
+
}
|
|
36281
36500
|
function normalizeMasteryStandard(input) {
|
|
36282
36501
|
const standard = canonicalReviewStandardRef(input);
|
|
36283
36502
|
if (!standard) {
|
|
@@ -36318,7 +36537,19 @@ function isReviewItemOutcome(value) {
|
|
|
36318
36537
|
return isRecord(value) && typeof value.itemIdentifier === "string" && value.itemIdentifier.length > 0 && isAssessmentStandardRef(value.standard) && typeof value.answered === "boolean" && isAssessmentScore(value.score) && (value.isCorrect === null || typeof value.isCorrect === "boolean");
|
|
36319
36538
|
}
|
|
36320
36539
|
function isAssessmentItemSubmission(value) {
|
|
36321
|
-
return isRecord(value) && typeof value.submissionId === "string" && value.submissionId.length > 0 && typeof value.itemIdentifier === "string" && value.itemIdentifier.length > 0 && typeof value.submittedAt === "string" && Number.isInteger(value.responseVersion) && typeof value.answered === "boolean" && isAssessmentScore(value.score) && (value.isCorrect === null || typeof value.isCorrect === "boolean");
|
|
36540
|
+
return isRecord(value) && typeof value.submissionId === "string" && value.submissionId.length > 0 && typeof value.itemIdentifier === "string" && value.itemIdentifier.length > 0 && typeof value.submittedAt === "string" && Number.isInteger(value.responseVersion) && typeof value.answered === "boolean" && isAssessmentScore(value.score) && (value.isCorrect === null || typeof value.isCorrect === "boolean") && (value.grading === undefined || isAssessmentItemGrading(value.grading));
|
|
36541
|
+
}
|
|
36542
|
+
function isAssessmentItemGrading(value) {
|
|
36543
|
+
if (!isRecord(value) || !isNonEmptyString(value.graderVersion)) {
|
|
36544
|
+
return false;
|
|
36545
|
+
}
|
|
36546
|
+
if (value.source === "timeback-qti") {
|
|
36547
|
+
return true;
|
|
36548
|
+
}
|
|
36549
|
+
if (value.source === "platform-qti-adapter") {
|
|
36550
|
+
return isNonEmptyString(value.qtiGraderVersion);
|
|
36551
|
+
}
|
|
36552
|
+
return value.source === "platform-artifact" && isNonEmptyString(value.artifactVersion);
|
|
36322
36553
|
}
|
|
36323
36554
|
function isDiagnosticTrackResult(value) {
|
|
36324
36555
|
return isRecord(value) && isNonEmptyString(value.trackKey) && (value.groupKey === undefined || isNonEmptyString(value.groupKey)) && DIAGNOSTIC_TRACK_OUTCOMES.some((outcome) => outcome === value.outcome) && isNonEmptyString(value.resultKey);
|
|
@@ -36352,8 +36583,11 @@ function isDiagnosticRoutingState(value) {
|
|
|
36352
36583
|
const nextValid = value.next === null || isRecord(value.next) && isNonEmptyString(value.next.stageKey) && isNonEmptyString(value.next.trackKey) && isNonEmptyString(value.next.nodeKey) && isNonEmptyString(value.next.itemIdentifier);
|
|
36353
36584
|
return stagesValid && tracksValid && nextValid && (value.status === "in-progress" && value.next !== null || value.status === "ready-to-complete" && value.next === null);
|
|
36354
36585
|
}
|
|
36586
|
+
function isDiagnosticRoutingSnapshot(value, revision) {
|
|
36587
|
+
return isRecord(value) && value.kind === "platform-routed-diagnostic" && value.revision === revision && (value.status === "in-progress" || value.status === "ready-to-complete") && (value.next === null || isRecord(value.next) && isNonEmptyString(value.next.stageKey) && isNonEmptyString(value.next.trackKey) && isNonEmptyString(value.next.nodeKey) && isNonEmptyString(value.next.itemIdentifier)) && Array.isArray(value.tracks) && value.tracks.every((track) => isRecord(track) && isNonEmptyString(track.trackKey) && (track.groupKey === undefined || isNonEmptyString(track.groupKey)) && ["pending", "active", "completed", "skipped"].includes(String(track.status)) && Number.isInteger(track.administeredCount) && Number(track.administeredCount) >= 0);
|
|
36588
|
+
}
|
|
36355
36589
|
function isRoutedDiagnosticAttemptMetadata(value, itemSubmissions, responseVersion) {
|
|
36356
|
-
if (!isRecord(value) || !isNonEmptyString(value.definitionId) || !isNonEmptyString(value.diagnosticKey) || !isNonEmptyString(value.testName) || !isNonEmptyString(value.routingRevision) || !Array.isArray(value.ledger) || !value.ledger.every(isDiagnosticRoutingLedgerEntry) || !isDiagnosticRoutingState(value.state) || value.ledger.length !== itemSubmissions.length || responseVersion !== itemSubmissions.length) {
|
|
36590
|
+
if (!isRecord(value) || !isNonEmptyString(value.definitionId) || !isNonEmptyString(value.diagnosticKey) || !isNonEmptyString(value.testName) || !isNonEmptyString(value.routingRevision) || !Array.isArray(value.ledger) || !value.ledger.every(isDiagnosticRoutingLedgerEntry) || value.routingSnapshots !== undefined && (!Array.isArray(value.routingSnapshots) || value.routingSnapshots.length !== value.ledger.length || !value.routingSnapshots.every((snapshot) => isDiagnosticRoutingSnapshot(snapshot, String(value.routingRevision)))) || !isDiagnosticRoutingState(value.state) || value.ledger.length !== itemSubmissions.length || responseVersion !== itemSubmissions.length) {
|
|
36357
36591
|
return false;
|
|
36358
36592
|
}
|
|
36359
36593
|
const itemIdentifiers = new Set;
|
|
@@ -36439,6 +36673,12 @@ function playcademyAssessmentResultMetadata(value) {
|
|
|
36439
36673
|
diagnostic: { ...normalized.diagnostic, ledger: [] }
|
|
36440
36674
|
};
|
|
36441
36675
|
}
|
|
36676
|
+
if (isRecord(normalized.diagnostic) && Array.isArray(normalized.diagnostic.ledger) && normalized.diagnostic.ledger.length === 0 && normalized.diagnostic.routingSnapshots === undefined) {
|
|
36677
|
+
normalized = {
|
|
36678
|
+
...normalized,
|
|
36679
|
+
diagnostic: { ...normalized.diagnostic, routingSnapshots: [] }
|
|
36680
|
+
};
|
|
36681
|
+
}
|
|
36442
36682
|
if (isRecord(normalized.diagnostic) && isRecord(normalized.diagnostic.state)) {
|
|
36443
36683
|
const state = normalized.diagnostic.state;
|
|
36444
36684
|
const tracks = Array.isArray(state.tracks) ? state.tracks.map((track) => isRecord(track) ? {
|
|
@@ -36446,10 +36686,12 @@ function playcademyAssessmentResultMetadata(value) {
|
|
|
36446
36686
|
currentNodeKey: track.currentNodeKey ?? null,
|
|
36447
36687
|
terminal: track.terminal ?? null
|
|
36448
36688
|
} : track) : state.tracks;
|
|
36689
|
+
const routingSnapshots = Array.isArray(normalized.diagnostic.routingSnapshots) ? normalized.diagnostic.routingSnapshots.map((snapshot) => isRecord(snapshot) ? { ...snapshot, next: snapshot.next ?? null } : snapshot) : normalized.diagnostic.routingSnapshots;
|
|
36449
36690
|
normalized = {
|
|
36450
36691
|
...normalized,
|
|
36451
36692
|
diagnostic: {
|
|
36452
36693
|
...normalized.diagnostic,
|
|
36694
|
+
...routingSnapshots === undefined ? {} : { routingSnapshots },
|
|
36453
36695
|
state: {
|
|
36454
36696
|
...state,
|
|
36455
36697
|
...tracks === undefined ? {} : { tracks },
|
|
@@ -36526,6 +36768,20 @@ var RESPONSE_CARDINALITIES;
|
|
|
36526
36768
|
var RESPONSE_BASE_TYPES;
|
|
36527
36769
|
var POINT_INTERACTION_TYPES;
|
|
36528
36770
|
var INDEPENDENT_PROCESSING_TAGS;
|
|
36771
|
+
var PRIVATE_PLAYABLE_KEYS;
|
|
36772
|
+
var trimmedNonEmptyString;
|
|
36773
|
+
var finiteNumber2;
|
|
36774
|
+
var positiveFiniteNumber;
|
|
36775
|
+
var IDENTIFIER_VALUE;
|
|
36776
|
+
var DIRECTED_PAIR_VALUE;
|
|
36777
|
+
var scoringMatchComparisonSchema;
|
|
36778
|
+
var scoringNumericEqualComparisonSchema;
|
|
36779
|
+
var scoringNumericEqualRoundedComparisonSchema;
|
|
36780
|
+
var scoringRuleSchema;
|
|
36781
|
+
var scoringRulesSchema;
|
|
36782
|
+
var scoringItemSchema;
|
|
36783
|
+
var assessmentScoringArtifactItemsSchema;
|
|
36784
|
+
var assessmentScoringArtifactSchema;
|
|
36529
36785
|
var SUPPORTED_BASE_TYPES;
|
|
36530
36786
|
var SUPPORTED_CARDINALITIES;
|
|
36531
36787
|
var MATCH_CORRECT_TEMPLATES;
|
|
@@ -36555,19 +36811,45 @@ var DiagnosticRoutingTransitionSchema;
|
|
|
36555
36811
|
var DiagnosticRoutingManifestV1Schema;
|
|
36556
36812
|
var GRADE_VALUES;
|
|
36557
36813
|
var POINT_RESPONSE_PATTERN;
|
|
36814
|
+
var ZERO_WIDTH_CHARACTERS;
|
|
36815
|
+
var CURLY_SINGLE_QUOTES;
|
|
36816
|
+
var CURLY_DOUBLE_QUOTES;
|
|
36817
|
+
var UNICODE_DASHES;
|
|
36818
|
+
var ARITHMETIC_OPERATOR_SPACING;
|
|
36819
|
+
var THOUSANDS_GROUP;
|
|
36558
36820
|
var REVIEW_SELECTION_POLICY_VERSION = "unseen-first-least-recently-delivered-v1";
|
|
36559
36821
|
var PLAYCADEMY_REVIEW_BANK_MANIFEST_METADATA_KEY = "playcademyReviewBank";
|
|
36560
36822
|
var PLAYCADEMY_REVIEW_BANK_MANIFEST_VERSION = 1;
|
|
36561
36823
|
var DEFAULT_REVIEW_SELECTION_POLICY;
|
|
36562
36824
|
var ASSESSMENT_RUNTIME_FIXTURE_BUNDLE_VERSION = 8;
|
|
36825
|
+
var ASSESSMENT_CHILD_RESULT_KIND = "assessment-child-result";
|
|
36826
|
+
var ASSESSMENT_CHILD_RESULT_VERSION = 1;
|
|
36827
|
+
var ASSESSMENT_CHILD_RESULT_ID_NAMESPACE = "playcademy:assessment-child-result:v1";
|
|
36828
|
+
var IdentitySchema;
|
|
36829
|
+
var RecordedAtSchema;
|
|
36830
|
+
var ResponseKeySchema;
|
|
36831
|
+
var AssessmentResponseValueSchema;
|
|
36832
|
+
var AssessmentScoreSchema;
|
|
36833
|
+
var AssessmentStandardRefSchema;
|
|
36834
|
+
var DiagnosticRoutingLedgerEntryV1Schema;
|
|
36835
|
+
var AssessmentChildResultAttemptV1Schema;
|
|
36836
|
+
var AssessmentChildResultAdministrationV1Schema;
|
|
36837
|
+
var AssessmentChildResultGradingV1Schema;
|
|
36838
|
+
var ReviewChildResultContextV1Schema;
|
|
36839
|
+
var DiagnosticChildResultContextV1Schema;
|
|
36840
|
+
var CommonPayloadShape;
|
|
36841
|
+
var ReviewChildResultPayloadV1Schema;
|
|
36842
|
+
var DiagnosticChildResultPayloadV1Schema;
|
|
36843
|
+
var AssessmentChildResultPayloadV1Schema;
|
|
36844
|
+
var AssessmentChildResultCommandV1Schema;
|
|
36563
36845
|
var RuntimeSubjectSchema;
|
|
36564
36846
|
var RuntimeGradeSchema;
|
|
36565
36847
|
var OptionalQueryGradeSchema;
|
|
36566
|
-
var
|
|
36848
|
+
var AssessmentResponseValueSchema2;
|
|
36567
36849
|
var AssessmentRuntimeIdentitySchema;
|
|
36568
|
-
var
|
|
36850
|
+
var ResponseKeySchema2;
|
|
36569
36851
|
var StartAssessmentBaseSchema;
|
|
36570
|
-
var
|
|
36852
|
+
var AssessmentStandardRefSchema2;
|
|
36571
36853
|
var LatestAssessmentFilterBaseSchema;
|
|
36572
36854
|
var LatestAssessmentFiltersSchema;
|
|
36573
36855
|
var LatestRuntimeAssessmentQuerySchema;
|
|
@@ -36592,6 +36874,7 @@ var init_assessment_runtime2 = __esm(() => {
|
|
|
36592
36874
|
init_timeback3();
|
|
36593
36875
|
init_timeback3();
|
|
36594
36876
|
init_esm();
|
|
36877
|
+
init_esm();
|
|
36595
36878
|
init_timeback4();
|
|
36596
36879
|
init_src();
|
|
36597
36880
|
init_timeback3();
|
|
@@ -36599,6 +36882,9 @@ var init_assessment_runtime2 = __esm(() => {
|
|
|
36599
36882
|
init_uuid();
|
|
36600
36883
|
init_src();
|
|
36601
36884
|
init_uuid();
|
|
36885
|
+
init_esm();
|
|
36886
|
+
init_src();
|
|
36887
|
+
init_uuid();
|
|
36602
36888
|
init_src();
|
|
36603
36889
|
init_timeback4();
|
|
36604
36890
|
init_esm();
|
|
@@ -36737,6 +37023,93 @@ var init_assessment_runtime2 = __esm(() => {
|
|
|
36737
37023
|
"qti-sum",
|
|
36738
37024
|
"qti-base-value"
|
|
36739
37025
|
]);
|
|
37026
|
+
PRIVATE_PLAYABLE_KEYS = new Set([
|
|
37027
|
+
"answer",
|
|
37028
|
+
"answerkey",
|
|
37029
|
+
"correctanswer",
|
|
37030
|
+
"correctresponse",
|
|
37031
|
+
"correctresponses",
|
|
37032
|
+
"correct",
|
|
37033
|
+
"correctvalue",
|
|
37034
|
+
"correctvalues",
|
|
37035
|
+
"iscorrect",
|
|
37036
|
+
"responseprocessing",
|
|
37037
|
+
"rule",
|
|
37038
|
+
"rules",
|
|
37039
|
+
"scoring",
|
|
37040
|
+
"scoringrules",
|
|
37041
|
+
"solution",
|
|
37042
|
+
"expected",
|
|
37043
|
+
"expectedanswer",
|
|
37044
|
+
"expectedvalue",
|
|
37045
|
+
"expectedvalues"
|
|
37046
|
+
]);
|
|
37047
|
+
trimmedNonEmptyString = exports_external.string().min(1).refine((value) => value === value.trim());
|
|
37048
|
+
finiteNumber2 = exports_external.number().finite();
|
|
37049
|
+
positiveFiniteNumber = finiteNumber2.positive();
|
|
37050
|
+
IDENTIFIER_VALUE = /^[A-Za-z_][\w.-]*$/;
|
|
37051
|
+
DIRECTED_PAIR_VALUE = /^[A-Za-z_][\w.-]* [A-Za-z_][\w.-]*$/;
|
|
37052
|
+
scoringMatchComparisonSchema = exports_external.object({
|
|
37053
|
+
kind: exports_external.literal("match"),
|
|
37054
|
+
correctValues: exports_external.array(trimmedNonEmptyString).min(1).refine((values) => new Set(values).size === values.length)
|
|
37055
|
+
}).strict();
|
|
37056
|
+
scoringNumericEqualComparisonSchema = exports_external.object({ kind: exports_external.literal("numeric-equal"), correctValue: finiteNumber2 }).strict();
|
|
37057
|
+
scoringNumericEqualRoundedComparisonSchema = exports_external.object({
|
|
37058
|
+
kind: exports_external.literal("numeric-equal-rounded"),
|
|
37059
|
+
correctValue: finiteNumber2,
|
|
37060
|
+
roundingMode: exports_external.enum(["decimalPlaces", "significantFigures"]),
|
|
37061
|
+
figures: exports_external.number().int().safe().nonnegative()
|
|
37062
|
+
}).strict();
|
|
37063
|
+
scoringRuleSchema = exports_external.object({
|
|
37064
|
+
responseIdentifier: trimmedNonEmptyString,
|
|
37065
|
+
cardinality: exports_external.enum(["single", "multiple", "ordered"]),
|
|
37066
|
+
baseType: exports_external.enum(["string", "identifier", "integer", "float", "directedPair"]),
|
|
37067
|
+
points: positiveFiniteNumber,
|
|
37068
|
+
comparison: exports_external.discriminatedUnion("kind", [
|
|
37069
|
+
scoringMatchComparisonSchema,
|
|
37070
|
+
scoringNumericEqualComparisonSchema,
|
|
37071
|
+
scoringNumericEqualRoundedComparisonSchema
|
|
37072
|
+
])
|
|
37073
|
+
}).strict().refine((rule) => rule.comparison.kind === "match" || rule.cardinality === "single" && (rule.baseType === "integer" || rule.baseType === "float")).refine((rule) => rule.baseType !== "directedPair" || rule.cardinality === "multiple" && rule.comparison.kind === "match").refine((rule) => rule.comparison.kind !== "match" || rule.cardinality !== "single" || rule.baseType === "string" || rule.comparison.correctValues.length === 1).refine((rule) => rule.comparison.kind !== "numeric-equal-rounded" || rule.comparison.roundingMode === "decimalPlaces" || rule.comparison.figures > 0).refine((rule) => {
|
|
37074
|
+
if (rule.comparison.kind !== "match") {
|
|
37075
|
+
return rule.baseType !== "integer" || Number.isSafeInteger(rule.comparison.correctValue);
|
|
37076
|
+
}
|
|
37077
|
+
if (rule.baseType === "integer" || rule.baseType === "float") {
|
|
37078
|
+
return rule.comparison.correctValues.every((value) => validNumericMatchValue(value, rule.baseType));
|
|
37079
|
+
}
|
|
37080
|
+
if (rule.baseType === "identifier") {
|
|
37081
|
+
return rule.comparison.correctValues.every((value) => IDENTIFIER_VALUE.test(value));
|
|
37082
|
+
}
|
|
37083
|
+
return rule.baseType !== "directedPair" || rule.comparison.correctValues.every((value) => DIRECTED_PAIR_VALUE.test(value));
|
|
37084
|
+
});
|
|
37085
|
+
scoringRulesSchema = exports_external.array(scoringRuleSchema).min(1).refine((rules) => new Set(rules.map((rule) => rule.responseIdentifier)).size === rules.length);
|
|
37086
|
+
scoringItemSchema = exports_external.discriminatedUnion("strategy", [
|
|
37087
|
+
exports_external.object({
|
|
37088
|
+
strategy: exports_external.literal("qti"),
|
|
37089
|
+
itemIdentifier: trimmedNonEmptyString,
|
|
37090
|
+
maxScore: positiveFiniteNumber,
|
|
37091
|
+
reason: exports_external.enum([
|
|
37092
|
+
"response-mapping",
|
|
37093
|
+
"area-mapping",
|
|
37094
|
+
"unsupported-base-type",
|
|
37095
|
+
"unsupported-cardinality",
|
|
37096
|
+
"invalid-correct-response",
|
|
37097
|
+
"no-scorable-response",
|
|
37098
|
+
"unsupported-processing-template",
|
|
37099
|
+
"unsupported-response-processing",
|
|
37100
|
+
"cross-response-processing",
|
|
37101
|
+
"inconsistent-max-score"
|
|
37102
|
+
])
|
|
37103
|
+
}).strict(),
|
|
37104
|
+
exports_external.object({
|
|
37105
|
+
strategy: exports_external.literal("platform"),
|
|
37106
|
+
itemIdentifier: trimmedNonEmptyString,
|
|
37107
|
+
maxScore: positiveFiniteNumber,
|
|
37108
|
+
rules: scoringRulesSchema
|
|
37109
|
+
}).strict()
|
|
37110
|
+
]).refine((item) => item.strategy === "qti" || sameScore(item.rules.reduce((total, rule) => total + rule.points, 0), item.maxScore));
|
|
37111
|
+
assessmentScoringArtifactItemsSchema = exports_external.array(scoringItemSchema).refine((items) => new Set(items.map((item) => item.itemIdentifier)).size === items.length);
|
|
37112
|
+
assessmentScoringArtifactSchema = exports_external.object({ items: assessmentScoringArtifactItemsSchema }).strict();
|
|
36740
37113
|
SUPPORTED_BASE_TYPES = new Set([
|
|
36741
37114
|
"string",
|
|
36742
37115
|
"identifier",
|
|
@@ -36863,9 +37236,140 @@ var init_assessment_runtime2 = __esm(() => {
|
|
|
36863
37236
|
}).strict();
|
|
36864
37237
|
GRADE_VALUES = TIMEBACK_GRADES;
|
|
36865
37238
|
POINT_RESPONSE_PATTERN = /^-?\d+(?:\.\d+)? -?\d+(?:\.\d+)?$/;
|
|
37239
|
+
ZERO_WIDTH_CHARACTERS = /[\u200B\uFEFF\u200C\u200D]/g;
|
|
37240
|
+
CURLY_SINGLE_QUOTES = /[\u2018\u2019\u201A\u201B]/g;
|
|
37241
|
+
CURLY_DOUBLE_QUOTES = /[\u201C\u201D\u201E\u201F]/g;
|
|
37242
|
+
UNICODE_DASHES = /[\u2010\u2011\u2012\u2013\u2014\u2015\u2212]/g;
|
|
37243
|
+
ARITHMETIC_OPERATOR_SPACING = /\s*([()+\-*/xX\u00D7\u00F7\u00B7])\s*/g;
|
|
37244
|
+
THOUSANDS_GROUP = /\b\d{1,3}(?:,\d{3})+(?:\.\d+)?\b/g;
|
|
36866
37245
|
DEFAULT_REVIEW_SELECTION_POLICY = {
|
|
36867
37246
|
version: REVIEW_SELECTION_POLICY_VERSION
|
|
36868
37247
|
};
|
|
37248
|
+
IdentitySchema = exports_external.string().min(1).refine((value) => value.trim() === value, {
|
|
37249
|
+
message: "Identifiers must not contain surrounding whitespace"
|
|
37250
|
+
});
|
|
37251
|
+
RecordedAtSchema = exports_external.string().datetime().refine((value) => new Date(value).toISOString() === value, {
|
|
37252
|
+
message: "recordedAt must be a canonical UTC timestamp"
|
|
37253
|
+
});
|
|
37254
|
+
ResponseKeySchema = IdentitySchema;
|
|
37255
|
+
AssessmentResponseValueSchema = exports_external.custom(isAssessmentResponseValue, {
|
|
37256
|
+
message: `Response values are non-empty text, at most ${TIMEBACK_ASSESSMENT_RESPONSE_VALUE_MAX_LENGTH} characters each`
|
|
37257
|
+
});
|
|
37258
|
+
AssessmentScoreSchema = exports_external.object({
|
|
37259
|
+
earned: exports_external.number().finite().nonnegative(),
|
|
37260
|
+
possible: exports_external.number().finite().nonnegative(),
|
|
37261
|
+
normalized: exports_external.number().finite().min(0).max(1)
|
|
37262
|
+
}).strict().superRefine((score, context) => {
|
|
37263
|
+
const expectedNormalized = score.possible === 0 ? 0 : score.earned / score.possible;
|
|
37264
|
+
if (score.earned > score.possible) {
|
|
37265
|
+
context.addIssue({
|
|
37266
|
+
code: exports_external.ZodIssueCode.custom,
|
|
37267
|
+
message: "Earned score must not exceed possible score",
|
|
37268
|
+
path: ["earned"]
|
|
37269
|
+
});
|
|
37270
|
+
}
|
|
37271
|
+
if (score.normalized !== expectedNormalized) {
|
|
37272
|
+
context.addIssue({
|
|
37273
|
+
code: exports_external.ZodIssueCode.custom,
|
|
37274
|
+
message: "Normalized score must equal earned divided by possible",
|
|
37275
|
+
path: ["normalized"]
|
|
37276
|
+
});
|
|
37277
|
+
}
|
|
37278
|
+
});
|
|
37279
|
+
AssessmentStandardRefSchema = exports_external.object({
|
|
37280
|
+
framework: IdentitySchema,
|
|
37281
|
+
identifier: IdentitySchema
|
|
37282
|
+
}).strict();
|
|
37283
|
+
DiagnosticRoutingLedgerEntryV1Schema = exports_external.object({
|
|
37284
|
+
stageKey: IdentitySchema,
|
|
37285
|
+
trackKey: IdentitySchema,
|
|
37286
|
+
routingNodeKey: IdentitySchema,
|
|
37287
|
+
itemIdentifier: IdentitySchema,
|
|
37288
|
+
isCorrect: exports_external.boolean()
|
|
37289
|
+
}).strict();
|
|
37290
|
+
AssessmentChildResultAttemptV1Schema = exports_external.object({
|
|
37291
|
+
attemptId: IdentitySchema,
|
|
37292
|
+
gameId: IdentitySchema,
|
|
37293
|
+
studentId: IdentitySchema,
|
|
37294
|
+
activityId: IdentitySchema,
|
|
37295
|
+
courseId: IdentitySchema,
|
|
37296
|
+
integrationId: IdentitySchema,
|
|
37297
|
+
enrollmentId: IdentitySchema,
|
|
37298
|
+
selectedTest: exports_external.object({
|
|
37299
|
+
assessmentKey: IdentitySchema,
|
|
37300
|
+
identifier: IdentitySchema,
|
|
37301
|
+
contentRevision: IdentitySchema
|
|
37302
|
+
}).strict()
|
|
37303
|
+
}).strict();
|
|
37304
|
+
AssessmentChildResultAdministrationV1Schema = exports_external.object({
|
|
37305
|
+
submissionId: IdentitySchema,
|
|
37306
|
+
responseVersion: exports_external.number().int().positive(),
|
|
37307
|
+
itemIdentifier: IdentitySchema
|
|
37308
|
+
}).strict();
|
|
37309
|
+
AssessmentChildResultGradingV1Schema = exports_external.discriminatedUnion("source", [
|
|
37310
|
+
exports_external.object({
|
|
37311
|
+
source: exports_external.literal("timeback-qti"),
|
|
37312
|
+
graderVersion: IdentitySchema
|
|
37313
|
+
}).strict(),
|
|
37314
|
+
exports_external.object({
|
|
37315
|
+
source: exports_external.literal("platform-qti-adapter"),
|
|
37316
|
+
graderVersion: IdentitySchema,
|
|
37317
|
+
qtiGraderVersion: IdentitySchema
|
|
37318
|
+
}).strict(),
|
|
37319
|
+
exports_external.object({
|
|
37320
|
+
source: exports_external.literal("platform-artifact"),
|
|
37321
|
+
artifactVersion: IdentitySchema,
|
|
37322
|
+
graderVersion: IdentitySchema
|
|
37323
|
+
}).strict()
|
|
37324
|
+
]);
|
|
37325
|
+
ReviewChildResultContextV1Schema = exports_external.object({
|
|
37326
|
+
kind: exports_external.literal("review"),
|
|
37327
|
+
bankRevision: IdentitySchema,
|
|
37328
|
+
standard: AssessmentStandardRefSchema
|
|
37329
|
+
}).strict();
|
|
37330
|
+
DiagnosticChildResultContextV1Schema = exports_external.object({
|
|
37331
|
+
kind: exports_external.literal("platform-routed-diagnostic"),
|
|
37332
|
+
definitionId: IdentitySchema,
|
|
37333
|
+
diagnosticKey: IdentitySchema,
|
|
37334
|
+
routingRevision: IdentitySchema,
|
|
37335
|
+
transition: DiagnosticRoutingLedgerEntryV1Schema
|
|
37336
|
+
}).strict();
|
|
37337
|
+
CommonPayloadShape = {
|
|
37338
|
+
attempt: AssessmentChildResultAttemptV1Schema,
|
|
37339
|
+
administration: AssessmentChildResultAdministrationV1Schema,
|
|
37340
|
+
responses: exports_external.record(ResponseKeySchema, AssessmentResponseValueSchema),
|
|
37341
|
+
score: AssessmentScoreSchema,
|
|
37342
|
+
grading: AssessmentChildResultGradingV1Schema
|
|
37343
|
+
};
|
|
37344
|
+
ReviewChildResultPayloadV1Schema = exports_external.object({
|
|
37345
|
+
...CommonPayloadShape,
|
|
37346
|
+
isCorrect: exports_external.boolean().optional(),
|
|
37347
|
+
context: ReviewChildResultContextV1Schema
|
|
37348
|
+
}).strict();
|
|
37349
|
+
DiagnosticChildResultPayloadV1Schema = exports_external.object({
|
|
37350
|
+
...CommonPayloadShape,
|
|
37351
|
+
isCorrect: exports_external.boolean(),
|
|
37352
|
+
context: DiagnosticChildResultContextV1Schema
|
|
37353
|
+
}).strict().superRefine((payload, context) => {
|
|
37354
|
+
if (payload.context.transition.itemIdentifier !== payload.administration.itemIdentifier || payload.context.transition.isCorrect !== payload.isCorrect) {
|
|
37355
|
+
context.addIssue({
|
|
37356
|
+
code: exports_external.ZodIssueCode.custom,
|
|
37357
|
+
message: "Diagnostic transition must match the administered item and correctness",
|
|
37358
|
+
path: ["context", "transition"]
|
|
37359
|
+
});
|
|
37360
|
+
}
|
|
37361
|
+
});
|
|
37362
|
+
AssessmentChildResultPayloadV1Schema = exports_external.union([
|
|
37363
|
+
ReviewChildResultPayloadV1Schema,
|
|
37364
|
+
DiagnosticChildResultPayloadV1Schema
|
|
37365
|
+
]);
|
|
37366
|
+
AssessmentChildResultCommandV1Schema = exports_external.object({
|
|
37367
|
+
kind: exports_external.literal(ASSESSMENT_CHILD_RESULT_KIND),
|
|
37368
|
+
version: exports_external.literal(ASSESSMENT_CHILD_RESULT_VERSION),
|
|
37369
|
+
recordedAt: RecordedAtSchema,
|
|
37370
|
+
payloadHash: exports_external.string().regex(/^[a-f0-9]{64}$/),
|
|
37371
|
+
payload: AssessmentChildResultPayloadV1Schema
|
|
37372
|
+
}).strict();
|
|
36869
37373
|
RuntimeSubjectSchema = exports_external.enum(TIMEBACK_SUBJECTS);
|
|
36870
37374
|
RuntimeGradeSchema = exports_external.number().refine(isTimebackGrade, {
|
|
36871
37375
|
message: `Grade must be one of: ${TIMEBACK_GRADES.join(", ")}`
|
|
@@ -36876,14 +37380,14 @@ var init_assessment_runtime2 = __esm(() => {
|
|
|
36876
37380
|
}
|
|
36877
37381
|
return typeof value === "string" && value.trim() !== "" ? Number(value) : Number.NaN;
|
|
36878
37382
|
}, RuntimeGradeSchema.optional());
|
|
36879
|
-
|
|
37383
|
+
AssessmentResponseValueSchema2 = exports_external.custom(isAssessmentResponseValue, {
|
|
36880
37384
|
message: `Response values are non-empty text, at most ${TIMEBACK_ASSESSMENT_RESPONSE_VALUE_MAX_LENGTH} characters each`
|
|
36881
37385
|
});
|
|
36882
37386
|
AssessmentRuntimeIdentitySchema = exports_external.object({
|
|
36883
37387
|
gameId: exports_external.string().uuid(),
|
|
36884
37388
|
studentId: exports_external.string().trim().min(1)
|
|
36885
37389
|
});
|
|
36886
|
-
|
|
37390
|
+
ResponseKeySchema2 = exports_external.string().refine((key) => key.length > 0 && key.trim() === key, {
|
|
36887
37391
|
message: "Response keys must be non-empty without surrounding whitespace"
|
|
36888
37392
|
});
|
|
36889
37393
|
StartAssessmentBaseSchema = exports_external.object({
|
|
@@ -36891,7 +37395,7 @@ var init_assessment_runtime2 = __esm(() => {
|
|
|
36891
37395
|
subject: RuntimeSubjectSchema.optional(),
|
|
36892
37396
|
grade: RuntimeGradeSchema.optional()
|
|
36893
37397
|
});
|
|
36894
|
-
|
|
37398
|
+
AssessmentStandardRefSchema2 = exports_external.object({
|
|
36895
37399
|
framework: exports_external.string().trim().min(1).max(TIMEBACK_ASSESSMENT_REVIEW_REQUEST_LIMITS.standardFieldLength),
|
|
36896
37400
|
identifier: exports_external.string().trim().min(1).max(TIMEBACK_ASSESSMENT_REVIEW_REQUEST_LIMITS.standardFieldLength)
|
|
36897
37401
|
}).refine((standard) => {
|
|
@@ -36908,7 +37412,7 @@ var init_assessment_runtime2 = __esm(() => {
|
|
|
36908
37412
|
}),
|
|
36909
37413
|
LatestAssessmentFilterBaseSchema.extend({
|
|
36910
37414
|
purpose: exports_external.literal("mastery"),
|
|
36911
|
-
standard:
|
|
37415
|
+
standard: AssessmentStandardRefSchema2
|
|
36912
37416
|
})
|
|
36913
37417
|
]);
|
|
36914
37418
|
LatestRuntimeAssessmentQuerySchema = exports_external.intersection(AssessmentRuntimeIdentitySchema, LatestAssessmentFiltersSchema);
|
|
@@ -36922,26 +37426,26 @@ var init_assessment_runtime2 = __esm(() => {
|
|
|
36922
37426
|
}),
|
|
36923
37427
|
StartAssessmentBaseSchema.extend({
|
|
36924
37428
|
purpose: exports_external.literal("review"),
|
|
36925
|
-
standards: exports_external.array(
|
|
37429
|
+
standards: exports_external.array(AssessmentStandardRefSchema2).min(1).max(TIMEBACK_ASSESSMENT_REVIEW_REQUEST_LIMITS.standards),
|
|
36926
37430
|
candidateItemsPerStandard: exports_external.number().int().positive().max(TIMEBACK_ASSESSMENT_REVIEW_REQUEST_LIMITS.candidateItemsPerStandard).optional()
|
|
36927
37431
|
}),
|
|
36928
37432
|
StartAssessmentBaseSchema.extend({
|
|
36929
37433
|
purpose: exports_external.literal("mastery"),
|
|
36930
|
-
standard:
|
|
37434
|
+
standard: AssessmentStandardRefSchema2
|
|
36931
37435
|
})
|
|
36932
37436
|
]);
|
|
36933
37437
|
AssessmentPreparationReceiptSchema = exports_external.string().max(32000);
|
|
36934
37438
|
StartAssessmentRequestBodySchema = exports_external.intersection(StartAssessmentBodySchema, exports_external.object({ preparationReceipt: AssessmentPreparationReceiptSchema.optional() }));
|
|
36935
37439
|
SaveAssessmentBodySchema = exports_external.object({
|
|
36936
37440
|
expectedResponseVersion: exports_external.number().int().nonnegative(),
|
|
36937
|
-
responses: exports_external.record(
|
|
37441
|
+
responses: exports_external.record(ResponseKeySchema2, exports_external.record(ResponseKeySchema2, AssessmentResponseValueSchema2.nullable()))
|
|
36938
37442
|
});
|
|
36939
37443
|
SubmitAssessmentItemBodySchema = exports_external.object({
|
|
36940
37444
|
expectedResponseVersion: exports_external.number().int().nonnegative(),
|
|
36941
37445
|
submissionId: exports_external.string().trim().min(1),
|
|
36942
|
-
itemIdentifier:
|
|
36943
|
-
routingNodeKey:
|
|
36944
|
-
responses: exports_external.record(
|
|
37446
|
+
itemIdentifier: ResponseKeySchema2,
|
|
37447
|
+
routingNodeKey: ResponseKeySchema2.optional(),
|
|
37448
|
+
responses: exports_external.record(ResponseKeySchema2, AssessmentResponseValueSchema2.nullable())
|
|
36945
37449
|
});
|
|
36946
37450
|
SubmitAssessmentBodySchema = exports_external.object({
|
|
36947
37451
|
expectedResponseVersion: exports_external.number().int().nonnegative(),
|
|
@@ -61433,7 +61937,7 @@ var init_types2 = __esm(() => {
|
|
|
61433
61937
|
function kebabToTitleCase(kebabStr) {
|
|
61434
61938
|
return kebabStr.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
61435
61939
|
}
|
|
61436
|
-
function
|
|
61940
|
+
function isRecord4(value) {
|
|
61437
61941
|
return typeof value === "object" && value !== null;
|
|
61438
61942
|
}
|
|
61439
61943
|
function resolveIntegrationSubject(patchSubject, liveSubject, storedSubject) {
|
|
@@ -61510,9 +62014,9 @@ function getGeneratedMetricValue(event, type) {
|
|
|
61510
62014
|
return Number.isFinite(value) ? value : undefined;
|
|
61511
62015
|
}
|
|
61512
62016
|
function getMergedCaliperExtensions(event) {
|
|
61513
|
-
const objectActivityExtensions =
|
|
61514
|
-
const generatedExtensions =
|
|
61515
|
-
const eventExtensions =
|
|
62017
|
+
const objectActivityExtensions = isRecord4(event.object.activity?.extensions) ? event.object.activity.extensions : undefined;
|
|
62018
|
+
const generatedExtensions = isRecord4(event.generated?.extensions) ? event.generated.extensions : undefined;
|
|
62019
|
+
const eventExtensions = isRecord4(event.extensions) ? event.extensions : undefined;
|
|
61516
62020
|
return {
|
|
61517
62021
|
...objectActivityExtensions,
|
|
61518
62022
|
...generatedExtensions,
|
|
@@ -61521,7 +62025,7 @@ function getMergedCaliperExtensions(event) {
|
|
|
61521
62025
|
}
|
|
61522
62026
|
function getPlaycademyMetadata(event) {
|
|
61523
62027
|
const extensions = getMergedCaliperExtensions(event);
|
|
61524
|
-
return
|
|
62028
|
+
return isRecord4(extensions.playcademy) ? extensions.playcademy : undefined;
|
|
61525
62029
|
}
|
|
61526
62030
|
function getActivityId(event, playcademy) {
|
|
61527
62031
|
const metadataActivityId = getStringValue(playcademy?.activityId);
|
|
@@ -61573,7 +62077,7 @@ function buildResourceMetadata({
|
|
|
61573
62077
|
}
|
|
61574
62078
|
function getDurationSecondsFromExtensions(event) {
|
|
61575
62079
|
const extensions = getMergedCaliperExtensions(event);
|
|
61576
|
-
const playcademy =
|
|
62080
|
+
const playcademy = isRecord4(extensions.playcademy) ? extensions.playcademy : undefined;
|
|
61577
62081
|
const rawValue = extensions.durationSeconds ?? playcademy?.durationSeconds;
|
|
61578
62082
|
const value = typeof rawValue === "number" ? rawValue : Number(rawValue);
|
|
61579
62083
|
return Number.isFinite(value) ? value : undefined;
|
|
@@ -72031,7 +72535,7 @@ var AssessmentKeySchema;
|
|
|
72031
72535
|
var DiagnosticKeySchema;
|
|
72032
72536
|
var DiagnosticRoutingManifestSchema;
|
|
72033
72537
|
var DiagnosticAssessmentDefinitionInputSchema;
|
|
72034
|
-
var
|
|
72538
|
+
var AssessmentStandardRefSchema3;
|
|
72035
72539
|
var CreateAssessmentRequestSchema;
|
|
72036
72540
|
var UpdateAssessmentRequestSchema;
|
|
72037
72541
|
var CopyAssessmentRequestSchema;
|
|
@@ -72289,7 +72793,7 @@ var init_schemas4 = __esm(() => {
|
|
|
72289
72793
|
diagnosticKey: DiagnosticKeySchema,
|
|
72290
72794
|
routingManifest: DiagnosticRoutingManifestSchema
|
|
72291
72795
|
});
|
|
72292
|
-
|
|
72796
|
+
AssessmentStandardRefSchema3 = exports_external.object({
|
|
72293
72797
|
framework: exports_external.string().trim().min(1).max(TIMEBACK_ASSESSMENT_REVIEW_REQUEST_LIMITS.standardFieldLength),
|
|
72294
72798
|
identifier: exports_external.string().trim().min(1).max(TIMEBACK_ASSESSMENT_REVIEW_REQUEST_LIMITS.standardFieldLength)
|
|
72295
72799
|
});
|
|
@@ -72297,7 +72801,7 @@ var init_schemas4 = __esm(() => {
|
|
|
72297
72801
|
assessmentKey: AssessmentKeySchema,
|
|
72298
72802
|
title: exports_external.string().min(1, "Assessment title is required"),
|
|
72299
72803
|
purpose: AssessmentPurposeSchema,
|
|
72300
|
-
standard:
|
|
72804
|
+
standard: AssessmentStandardRefSchema3.optional()
|
|
72301
72805
|
}).superRefine((input, context2) => {
|
|
72302
72806
|
requireMasteryStandard(input, context2);
|
|
72303
72807
|
rejectSystemManagedReview(input, context2);
|
|
@@ -72305,7 +72809,7 @@ var init_schemas4 = __esm(() => {
|
|
|
72305
72809
|
UpdateAssessmentRequestSchema = exports_external.object({
|
|
72306
72810
|
title: exports_external.string().trim().min(1, "Assessment title is required").optional(),
|
|
72307
72811
|
purpose: AssessmentPurposeSchema.optional(),
|
|
72308
|
-
standard:
|
|
72812
|
+
standard: AssessmentStandardRefSchema3.optional(),
|
|
72309
72813
|
diagnostic: DiagnosticAssessmentDefinitionInputSchema.nullable().optional(),
|
|
72310
72814
|
status: AssessmentStatusSchema.optional()
|
|
72311
72815
|
}).refine((input) => input.title !== undefined || input.purpose !== undefined || input.standard !== undefined || input.diagnostic !== undefined || input.status !== undefined, {
|
|
@@ -72315,7 +72819,7 @@ var init_schemas4 = __esm(() => {
|
|
|
72315
72819
|
assessmentKey: AssessmentKeySchema,
|
|
72316
72820
|
testIdentifier: exports_external.string().trim().min(1, "Assessment identifier is required"),
|
|
72317
72821
|
purpose: AssessmentPurposeSchema,
|
|
72318
|
-
standard:
|
|
72822
|
+
standard: AssessmentStandardRefSchema3.optional()
|
|
72319
72823
|
}).superRefine((input, context2) => {
|
|
72320
72824
|
requireMasteryStandard(input, context2);
|
|
72321
72825
|
rejectSystemManagedReview(input, context2);
|
|
@@ -72324,7 +72828,7 @@ var init_schemas4 = __esm(() => {
|
|
|
72324
72828
|
assessmentKey: AssessmentKeySchema,
|
|
72325
72829
|
qtiTestIdentifier: exports_external.string().trim().min(1, "Assessment identifier is required"),
|
|
72326
72830
|
purpose: AssessmentPurposeSchema,
|
|
72327
|
-
standard:
|
|
72831
|
+
standard: AssessmentStandardRefSchema3.optional()
|
|
72328
72832
|
}).superRefine((input, context2) => {
|
|
72329
72833
|
requireMasteryStandard(input, context2);
|
|
72330
72834
|
rejectSystemManagedReview(input, context2);
|
|
@@ -120539,16 +121043,16 @@ function qtiResponseMappingMaximum(input) {
|
|
|
120539
121043
|
return null;
|
|
120540
121044
|
}
|
|
120541
121045
|
const defaultValue = mapping.defaultValue ?? 0;
|
|
120542
|
-
const
|
|
121046
|
+
const responseValues3 = input.responseValues ? [...new Set(input.responseValues)].map((value) => mapping.entries[value] ?? defaultValue) : entryValues;
|
|
120543
121047
|
let maximum;
|
|
120544
121048
|
if (input.cardinality === "single") {
|
|
120545
|
-
maximum = Math.max(0, defaultValue, ...
|
|
121049
|
+
maximum = Math.max(0, defaultValue, ...responseValues3);
|
|
120546
121050
|
} else {
|
|
120547
121051
|
if (!input.responseValues && defaultValue > 0) {
|
|
120548
121052
|
return null;
|
|
120549
121053
|
}
|
|
120550
|
-
const limit = Math.min(input.maximumResponses ??
|
|
120551
|
-
maximum =
|
|
121054
|
+
const limit = Math.min(input.maximumResponses ?? responseValues3.length, responseValues3.length);
|
|
121055
|
+
maximum = responseValues3.toSorted((left, right) => right - left).slice(0, limit).reduce((sum, value) => sum + Math.max(0, value), 0);
|
|
120552
121056
|
}
|
|
120553
121057
|
if (mapping.lowerBound !== undefined) {
|
|
120554
121058
|
maximum = Math.max(maximum, mapping.lowerBound);
|
|
@@ -121110,16 +121614,16 @@ function authoredMappingContext(interaction, declaration) {
|
|
|
121110
121614
|
return {};
|
|
121111
121615
|
}
|
|
121112
121616
|
const type = qtiAuthoringInteractionType(interaction);
|
|
121113
|
-
let
|
|
121617
|
+
let responseValues3;
|
|
121114
121618
|
if (type === "choice" || type === "inline-choice" || type === "order" || type === "hotspot") {
|
|
121115
|
-
|
|
121619
|
+
responseValues3 = interaction.choices?.map((choice) => String(choice.identifier)) ?? [];
|
|
121116
121620
|
} else if (type === "hottext") {
|
|
121117
|
-
|
|
121621
|
+
responseValues3 = (interaction.textSegments ?? []).flatMap((segment) => segment.selectable && segment.identifier ? [segment.identifier] : []);
|
|
121118
121622
|
} else if (type === "match" || type === "graphic-gap-match") {
|
|
121119
121623
|
const [left = [], right = []] = interaction.choiceSets ?? [];
|
|
121120
|
-
|
|
121624
|
+
responseValues3 = authoredResponsePairs(left.map((choice) => String(choice.identifier)), right.map((choice) => String(choice.identifier)));
|
|
121121
121625
|
}
|
|
121122
|
-
const capacity =
|
|
121626
|
+
const capacity = responseValues3?.length ?? Object.keys(declaration.mapping?.entries ?? {}).length;
|
|
121123
121627
|
const limitAttribute = type === "match" || type === "gap-match" || type === "graphic-associate" || type === "graphic-gap-match" ? "max-associations" : "max-choices";
|
|
121124
121628
|
const rawLimit = interaction.attributes?.[limitAttribute];
|
|
121125
121629
|
const declaredLimit = rawLimit === undefined ? undefined : Number(rawLimit);
|
|
@@ -121138,7 +121642,7 @@ function authoredMappingContext(interaction, declaration) {
|
|
|
121138
121642
|
maximumResponses = capacity;
|
|
121139
121643
|
}
|
|
121140
121644
|
return {
|
|
121141
|
-
...
|
|
121645
|
+
...responseValues3 ? { responseValues: responseValues3 } : {},
|
|
121142
121646
|
...maximumResponses > 0 ? { maximumResponses } : {}
|
|
121143
121647
|
};
|
|
121144
121648
|
}
|
|
@@ -121322,6 +121826,13 @@ function qtiGapContentMatchesGaps(nodes, gaps) {
|
|
|
121322
121826
|
const identifiers = gapContentIdentifiers(nodes);
|
|
121323
121827
|
return identifiers.length === gaps.length && identifiers.every((identifier, index2) => identifier === gaps[index2]);
|
|
121324
121828
|
}
|
|
121829
|
+
function qtiFloatValue(value) {
|
|
121830
|
+
if (value === undefined || !QTI_FLOAT_VALUE.test(value.trim())) {
|
|
121831
|
+
return;
|
|
121832
|
+
}
|
|
121833
|
+
const number7 = Number(value);
|
|
121834
|
+
return Number.isFinite(number7) ? number7 : undefined;
|
|
121835
|
+
}
|
|
121325
121836
|
function normalizedInteractionType(value) {
|
|
121326
121837
|
if (typeof value !== "string") {
|
|
121327
121838
|
return;
|
|
@@ -121540,24 +122051,24 @@ function interactionResponseSpace(item, declaration) {
|
|
|
121540
122051
|
}
|
|
121541
122052
|
case "match": {
|
|
121542
122053
|
const [source = [], target = []] = interaction.choiceSets;
|
|
121543
|
-
const
|
|
122054
|
+
const responseValues3 = responsePairs(source.map((choice) => choice.identifier), target.map((choice) => choice.identifier));
|
|
121544
122055
|
return {
|
|
121545
|
-
responseValues:
|
|
121546
|
-
maximumResponses: qtiAttributeMaximum(attributes2, "max-associations", 1,
|
|
122056
|
+
responseValues: responseValues3,
|
|
122057
|
+
maximumResponses: qtiAttributeMaximum(attributes2, "max-associations", 1, responseValues3.length)
|
|
121547
122058
|
};
|
|
121548
122059
|
}
|
|
121549
122060
|
case "hottext": {
|
|
121550
|
-
const
|
|
122061
|
+
const responseValues3 = interaction.textSegments.flatMap((segment) => segment.selectable && segment.identifier ? [segment.identifier] : []);
|
|
121551
122062
|
return {
|
|
121552
|
-
responseValues:
|
|
121553
|
-
maximumResponses: qtiAttributeMaximum(attributes2, "max-choices", 1,
|
|
122063
|
+
responseValues: responseValues3,
|
|
122064
|
+
maximumResponses: qtiAttributeMaximum(attributes2, "max-choices", 1, responseValues3.length)
|
|
121554
122065
|
};
|
|
121555
122066
|
}
|
|
121556
122067
|
case "gap-match": {
|
|
121557
|
-
const
|
|
122068
|
+
const responseValues3 = responsePairs(interaction.gapTexts.map((choice) => choice.identifier), interaction.gaps);
|
|
121558
122069
|
return {
|
|
121559
|
-
responseValues:
|
|
121560
|
-
maximumResponses: qtiAttributeMaximum(attributes2, "max-associations", 0,
|
|
122070
|
+
responseValues: responseValues3,
|
|
122071
|
+
maximumResponses: qtiAttributeMaximum(attributes2, "max-associations", 0, responseValues3.length)
|
|
121561
122072
|
};
|
|
121562
122073
|
}
|
|
121563
122074
|
case "hotspot": {
|
|
@@ -121567,10 +122078,10 @@ function interactionResponseSpace(item, declaration) {
|
|
|
121567
122078
|
};
|
|
121568
122079
|
}
|
|
121569
122080
|
case "graphic-gap-match": {
|
|
121570
|
-
const
|
|
122081
|
+
const responseValues3 = responsePairs(interaction.gapImages.map((choice) => choice.identifier), interaction.hotspots.map((hotspot) => hotspot.identifier));
|
|
121571
122082
|
return {
|
|
121572
|
-
responseValues:
|
|
121573
|
-
maximumResponses: qtiAttributeMaximum(attributes2, "max-associations", 0,
|
|
122083
|
+
responseValues: responseValues3,
|
|
122084
|
+
maximumResponses: qtiAttributeMaximum(attributes2, "max-associations", 0, responseValues3.length)
|
|
121574
122085
|
};
|
|
121575
122086
|
}
|
|
121576
122087
|
case "graphic-associate": {
|
|
@@ -121615,8 +122126,8 @@ function responseMappingMaximum(item, declaration, space) {
|
|
|
121615
122126
|
function qtiItemScoringValidationMessage(item) {
|
|
121616
122127
|
for (const declaration of item.responseDeclarations) {
|
|
121617
122128
|
const space = interactionResponseSpace(item, declaration);
|
|
121618
|
-
const { responseValues:
|
|
121619
|
-
if (
|
|
122129
|
+
const { responseValues: responseValues3, maximumResponses } = space;
|
|
122130
|
+
if (responseValues3 && declaration.correctValues.some((value) => !responseValues3.includes(value))) {
|
|
121620
122131
|
return `Response ${declaration.identifier} declares a correct value its interaction cannot produce.`;
|
|
121621
122132
|
}
|
|
121622
122133
|
const interaction = item.interactions.find((candidate) => candidate.responseIdentifier === declaration.identifier);
|
|
@@ -121656,13 +122167,13 @@ function qtiItemScoringValidationMessage(item) {
|
|
|
121656
122167
|
}
|
|
121657
122168
|
function qtiItemMaxScore(item) {
|
|
121658
122169
|
const maxScore = item.outcomeDeclarations.find((declaration) => declaration.identifier.toUpperCase() === "MAXSCORE");
|
|
121659
|
-
const declaredMaxScore =
|
|
121660
|
-
if (
|
|
122170
|
+
const declaredMaxScore = qtiFloatValue(maxScore?.defaultValues[0]);
|
|
122171
|
+
if (declaredMaxScore !== undefined && declaredMaxScore > 0) {
|
|
121661
122172
|
return declaredMaxScore;
|
|
121662
122173
|
}
|
|
121663
122174
|
const score = item.outcomeDeclarations.find((declaration) => declaration.identifier.toUpperCase() === "SCORE");
|
|
121664
|
-
const normalMaximum =
|
|
121665
|
-
if (
|
|
122175
|
+
const normalMaximum = qtiFloatValue(score?.attributes["normal-maximum"]);
|
|
122176
|
+
if (normalMaximum !== undefined && normalMaximum > 0) {
|
|
121666
122177
|
return normalMaximum;
|
|
121667
122178
|
}
|
|
121668
122179
|
const inferredMaximum = item.responseDeclarations.reduce((total, declaration) => {
|
|
@@ -121690,7 +122201,7 @@ function scoresResponsesIndependently(item, scoredIdentifiers) {
|
|
|
121690
122201
|
if (!processing) {
|
|
121691
122202
|
return true;
|
|
121692
122203
|
}
|
|
121693
|
-
if (processing.attributes.template || processing.conditions.length !== scoredIdentifiers.size || !processing.elements.every((element) => INDEPENDENT_PROCESSING_TAGS2.has(element.tagName)) || !processing.baseValues.every((base) =>
|
|
122204
|
+
if (processing.attributes.template || processing.conditions.length !== scoredIdentifiers.size || !processing.elements.every((element) => INDEPENDENT_PROCESSING_TAGS2.has(element.tagName)) || !processing.baseValues.every((base) => qtiFloatValue(base.value) !== undefined)) {
|
|
121694
122205
|
return false;
|
|
121695
122206
|
}
|
|
121696
122207
|
const declaredIdentifiers = new Set(item.responseDeclarations.map((declaration) => declaration.identifier));
|
|
@@ -121778,6 +122289,40 @@ function supportedQtiQuestionInteractionType(question) {
|
|
|
121778
122289
|
return null;
|
|
121779
122290
|
}
|
|
121780
122291
|
}
|
|
122292
|
+
function validNumericMatchValue2(value, baseType) {
|
|
122293
|
+
const syntax = baseType === "integer" ? /^[+-]?\d+$/ : /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?$/i;
|
|
122294
|
+
if (!syntax.test(value.trim())) {
|
|
122295
|
+
return false;
|
|
122296
|
+
}
|
|
122297
|
+
const numeric3 = Number(value);
|
|
122298
|
+
return Number.isFinite(numeric3) && (baseType === "float" || Number.isSafeInteger(numeric3));
|
|
122299
|
+
}
|
|
122300
|
+
function sameScore2(left, right) {
|
|
122301
|
+
return Number.isFinite(left) && Number.isFinite(right) && Math.abs(left - right) <= Number.EPSILON * Math.max(1, Math.abs(left), Math.abs(right));
|
|
122302
|
+
}
|
|
122303
|
+
function scoringItemMatchesPlayableItem(scoringItem, item) {
|
|
122304
|
+
if (!sameScore2(scoringItem.maxScore, item.maxScore)) {
|
|
122305
|
+
return false;
|
|
122306
|
+
}
|
|
122307
|
+
if (scoringItem.strategy === "qti") {
|
|
122308
|
+
return true;
|
|
122309
|
+
}
|
|
122310
|
+
const playableResponses = new Set(item.interactions.map((interaction) => interaction.responseIdentifier));
|
|
122311
|
+
const scoringResponses = new Set(scoringItem.rules.map((rule) => rule.responseIdentifier));
|
|
122312
|
+
return playableResponses.size === item.interactions.length && scoringResponses.size === scoringItem.rules.length && playableResponses.size === scoringResponses.size && [...playableResponses].every((identifier) => scoringResponses.has(identifier));
|
|
122313
|
+
}
|
|
122314
|
+
function normalizedKey(key) {
|
|
122315
|
+
return key.replaceAll(/[^a-z0-9]/gi, "").toLowerCase();
|
|
122316
|
+
}
|
|
122317
|
+
function containsPrivatePlayableAssessmentData(value) {
|
|
122318
|
+
if (Array.isArray(value)) {
|
|
122319
|
+
return value.some(containsPrivatePlayableAssessmentData);
|
|
122320
|
+
}
|
|
122321
|
+
if (!isRecord(value)) {
|
|
122322
|
+
return false;
|
|
122323
|
+
}
|
|
122324
|
+
return Object.entries(value).some(([key, entry2]) => PRIVATE_PLAYABLE_KEYS2.has(normalizedKey(key)) || containsPrivatePlayableAssessmentData(entry2));
|
|
122325
|
+
}
|
|
121781
122326
|
function childElements(node, localName2) {
|
|
121782
122327
|
return node.children.filter((child) => child.kind === "element" && (!localName2 || child.localName === localName2));
|
|
121783
122328
|
}
|
|
@@ -121819,6 +122364,22 @@ function numericCorrectValue(declaration) {
|
|
|
121819
122364
|
}
|
|
121820
122365
|
return parsedNumericValue(declaration.correctValues[0], declaration.baseType);
|
|
121821
122366
|
}
|
|
122367
|
+
function rawCorrectValues(declaration) {
|
|
122368
|
+
const correctResponses = childElements(declaration, "qti-correct-response");
|
|
122369
|
+
const correctResponse = correctResponses.length === 1 ? correctResponses[0] : undefined;
|
|
122370
|
+
if (!correctResponse || !onlyWhitespaceText(correctResponse.children)) {
|
|
122371
|
+
return null;
|
|
122372
|
+
}
|
|
122373
|
+
const values = childElements(correctResponse);
|
|
122374
|
+
if (values.length === 0 || values.some((value) => value.localName !== "qti-value" || !onlyAttributes(value, []))) {
|
|
122375
|
+
return null;
|
|
122376
|
+
}
|
|
122377
|
+
const rawValues = values.map((value) => {
|
|
122378
|
+
const text3 = directText(value);
|
|
122379
|
+
return text3 === null ? null : decodeXmlEntities(text3).trim();
|
|
122380
|
+
});
|
|
122381
|
+
return rawValues.every((value) => value !== null) ? rawValues : null;
|
|
122382
|
+
}
|
|
121822
122383
|
function declarationResult(raw, normalized) {
|
|
121823
122384
|
const identifier = raw.attributes.identifier?.trim();
|
|
121824
122385
|
if (!normalized || !identifier || normalized.identifier !== identifier) {
|
|
@@ -121835,16 +122396,23 @@ function declarationResult(raw, normalized) {
|
|
|
121835
122396
|
if (baseType === "directedPair" && cardinality !== "multiple") {
|
|
121836
122397
|
return { reason: "unsupported-cardinality" };
|
|
121837
122398
|
}
|
|
122399
|
+
const correctValues = rawCorrectValues(raw);
|
|
122400
|
+
if (!correctValues) {
|
|
122401
|
+
return { reason: "invalid-correct-response" };
|
|
122402
|
+
}
|
|
121838
122403
|
const declaration = {
|
|
121839
122404
|
identifier,
|
|
121840
122405
|
cardinality,
|
|
121841
122406
|
baseType,
|
|
121842
|
-
correctValues
|
|
122407
|
+
correctValues
|
|
121843
122408
|
};
|
|
121844
|
-
if (declaration.correctValues.length === 0 || declaration.correctValues.some((value) => value.trim() === "") || declaration.cardinality === "single" && declaration.correctValues.length !== 1 && declaration.baseType !== "string") {
|
|
122409
|
+
if (declaration.correctValues.length === 0 || declaration.correctValues.some((value) => value.trim() === "") || new Set(declaration.correctValues).size !== declaration.correctValues.length || declaration.cardinality === "single" && declaration.correctValues.length !== 1 && declaration.baseType !== "string") {
|
|
122410
|
+
return { reason: "invalid-correct-response" };
|
|
122411
|
+
}
|
|
122412
|
+
if (declaration.baseType === "identifier" && declaration.correctValues.some((value) => !IDENTIFIER_VALUE2.test(value))) {
|
|
121845
122413
|
return { reason: "invalid-correct-response" };
|
|
121846
122414
|
}
|
|
121847
|
-
if (declaration.baseType === "directedPair" &&
|
|
122415
|
+
if (declaration.baseType === "directedPair" && declaration.correctValues.some((value) => !DIRECTED_PAIR_VALUE2.test(value))) {
|
|
121848
122416
|
return { reason: "invalid-correct-response" };
|
|
121849
122417
|
}
|
|
121850
122418
|
if ((declaration.baseType === "integer" || declaration.baseType === "float") && declaration.correctValues.some((value) => parsedNumericValue(value, declaration.baseType) === null)) {
|
|
@@ -121860,16 +122428,14 @@ function scoringDeclarations(rawDeclarations, normalizedDeclarations) {
|
|
|
121860
122428
|
const declarations = new Map;
|
|
121861
122429
|
for (const [index2, rawDeclaration] of rawDeclarations.entries()) {
|
|
121862
122430
|
const normalized = normalizedDeclarations[index2];
|
|
121863
|
-
|
|
121864
|
-
|
|
121865
|
-
|
|
121866
|
-
|
|
121867
|
-
|
|
121868
|
-
|
|
121869
|
-
return { reason: "invalid-correct-response" };
|
|
121870
|
-
}
|
|
121871
|
-
declarations.set(result.declaration.identifier, result.declaration);
|
|
122431
|
+
const result = declarationResult(rawDeclaration, normalized);
|
|
122432
|
+
if ("reason" in result) {
|
|
122433
|
+
return result;
|
|
122434
|
+
}
|
|
122435
|
+
if (declarations.has(result.declaration.identifier)) {
|
|
122436
|
+
return { reason: "invalid-correct-response" };
|
|
121872
122437
|
}
|
|
122438
|
+
declarations.set(result.declaration.identifier, result.declaration);
|
|
121873
122439
|
}
|
|
121874
122440
|
return declarations.size > 0 ? { declarations } : { reason: "no-scorable-response" };
|
|
121875
122441
|
}
|
|
@@ -121879,15 +122445,12 @@ function declarationConstructReason(declarations) {
|
|
|
121879
122445
|
}
|
|
121880
122446
|
return declarations.some((declaration) => childElements(declaration, "qti-area-mapping").length > 0) ? "area-mapping" : null;
|
|
121881
122447
|
}
|
|
121882
|
-
function sameScore(left, right) {
|
|
121883
|
-
return Math.abs(left - right) <= Number.EPSILON * Math.max(1, Math.abs(left), Math.abs(right));
|
|
121884
|
-
}
|
|
121885
122448
|
function platformResult(itemIdentifier, parsedMaxScore, rules) {
|
|
121886
122449
|
const ruleMaximum = rules.reduce((sum, rule) => sum + rule.points, 0);
|
|
121887
122450
|
if (!Number.isFinite(ruleMaximum) || ruleMaximum <= 0) {
|
|
121888
122451
|
return fallback(itemIdentifier, parsedMaxScore, "no-scorable-response");
|
|
121889
122452
|
}
|
|
121890
|
-
if (!
|
|
122453
|
+
if (!sameScore2(parsedMaxScore, ruleMaximum)) {
|
|
121891
122454
|
return fallback(itemIdentifier, parsedMaxScore, "inconsistent-max-score");
|
|
121892
122455
|
}
|
|
121893
122456
|
return {
|
|
@@ -121948,9 +122511,8 @@ function comparisonResult(comparison, declaration) {
|
|
|
121948
122511
|
return null;
|
|
121949
122512
|
}
|
|
121950
122513
|
const roundingMode = comparison.attributes["rounding-mode"];
|
|
121951
|
-
const figures =
|
|
121952
|
-
|
|
121953
|
-
if (roundingMode !== "decimalPlaces" && roundingMode !== "significantFigures" || !validFigures) {
|
|
122514
|
+
const figures = parsedNumericValue(comparison.attributes.figures ?? "", "integer");
|
|
122515
|
+
if (roundingMode !== "decimalPlaces" && roundingMode !== "significantFigures" || figures === null || (roundingMode === "decimalPlaces" ? figures < 0 : figures <= 0)) {
|
|
121954
122516
|
return null;
|
|
121955
122517
|
}
|
|
121956
122518
|
return {
|
|
@@ -121982,8 +122544,47 @@ function additivePoints(setOutcome) {
|
|
|
121982
122544
|
return null;
|
|
121983
122545
|
}
|
|
121984
122546
|
const rawPoints = directText(pointValue);
|
|
121985
|
-
const points = rawPoints === null ?
|
|
121986
|
-
return
|
|
122547
|
+
const points = rawPoints === null ? null : parsedNumericValue(rawPoints, "float");
|
|
122548
|
+
return points !== null && points > 0 ? points : null;
|
|
122549
|
+
}
|
|
122550
|
+
function constantOutcomeValue(setOutcome, identifier, baseType) {
|
|
122551
|
+
if (!setOutcome || setOutcome.localName !== "qti-set-outcome-value" || !onlyAttributes(setOutcome, ["identifier"]) || setOutcome.attributes.identifier !== identifier || !onlyWhitespaceText(setOutcome.children)) {
|
|
122552
|
+
return null;
|
|
122553
|
+
}
|
|
122554
|
+
const values = childElements(setOutcome);
|
|
122555
|
+
const value = values.length === 1 ? values[0] : undefined;
|
|
122556
|
+
return value && value.localName === "qti-base-value" && onlyAttributes(value, ["base-type"]) && value.attributes["base-type"] === baseType ? directText(value) : null;
|
|
122557
|
+
}
|
|
122558
|
+
function feedbackOutcomeIsDeclared(item) {
|
|
122559
|
+
const feedbackOutcomes = childElements(item, "qti-outcome-declaration").filter((outcome) => outcome.attributes.identifier === "FEEDBACK");
|
|
122560
|
+
const feedback = feedbackOutcomes[0];
|
|
122561
|
+
return feedbackOutcomes.length === 1 && feedback !== undefined && onlyAttributes(feedback, ["identifier", "cardinality", "base-type"]) && feedback.attributes.cardinality === "single" && feedback.attributes["base-type"] === "identifier" && childElements(feedback).length === 0 && onlyWhitespaceText(feedback.children);
|
|
122562
|
+
}
|
|
122563
|
+
function binaryBranchRule(condition, declaration) {
|
|
122564
|
+
if (!onlyAttributes(condition, []) || !onlyWhitespaceText(condition.children)) {
|
|
122565
|
+
return null;
|
|
122566
|
+
}
|
|
122567
|
+
const branches = childElements(condition);
|
|
122568
|
+
const responseIf = branches[0];
|
|
122569
|
+
const responseElse = branches[1];
|
|
122570
|
+
if (branches.length !== 2 || responseIf?.localName !== "qti-response-if" || responseElse?.localName !== "qti-response-else" || !onlyAttributes(responseIf, []) || !onlyAttributes(responseElse, []) || !onlyWhitespaceText(responseIf.children) || !onlyWhitespaceText(responseElse.children)) {
|
|
122571
|
+
return null;
|
|
122572
|
+
}
|
|
122573
|
+
const ifChildren = childElements(responseIf);
|
|
122574
|
+
const elseChildren = childElements(responseElse);
|
|
122575
|
+
const [comparison, ifFeedback, ifScore] = ifChildren;
|
|
122576
|
+
const [elseFeedback, elseScore] = elseChildren;
|
|
122577
|
+
const comparisonPayload = comparison ? comparisonResult(comparison, declaration) : null;
|
|
122578
|
+
if (ifChildren.length !== 3 || elseChildren.length !== 2 || !comparisonPayload || constantOutcomeValue(ifFeedback, "FEEDBACK", "identifier") !== "CORRECT" || constantOutcomeValue(ifScore, "SCORE", "float") !== "1" || constantOutcomeValue(elseFeedback, "FEEDBACK", "identifier") !== "INCORRECT" || constantOutcomeValue(elseScore, "SCORE", "float") !== "0") {
|
|
122579
|
+
return null;
|
|
122580
|
+
}
|
|
122581
|
+
return {
|
|
122582
|
+
responseIdentifier: declaration.identifier,
|
|
122583
|
+
cardinality: declaration.cardinality,
|
|
122584
|
+
baseType: declaration.baseType,
|
|
122585
|
+
points: 1,
|
|
122586
|
+
comparison: comparisonPayload
|
|
122587
|
+
};
|
|
121987
122588
|
}
|
|
121988
122589
|
function additiveRule(condition, declarations) {
|
|
121989
122590
|
const responseIdentifiers = new Set(declarations.keys());
|
|
@@ -122061,7 +122662,7 @@ function scoreDefaultIsZero(item) {
|
|
|
122061
122662
|
}
|
|
122062
122663
|
const values = childElements(defaults[0], "qti-value");
|
|
122063
122664
|
const defaultText = values.length === 1 ? directText(values[0]) : null;
|
|
122064
|
-
return defaultText !== null && defaultText
|
|
122665
|
+
return defaultText !== null && parsedNumericValue(defaultText, "float") === 0;
|
|
122065
122666
|
}
|
|
122066
122667
|
function compileResponseProcessing(item, itemIdentifier, maxScore, declarations) {
|
|
122067
122668
|
const processingElements = childElements(item, "qti-response-processing");
|
|
@@ -122084,7 +122685,17 @@ function compileResponseProcessing(item, itemIdentifier, maxScore, declarations)
|
|
|
122084
122685
|
const response = declarations.get("RESPONSE");
|
|
122085
122686
|
return response && declarations.size === 1 ? platformResult(itemIdentifier, maxScore, [matchRule(response, 1)]) : fallback(itemIdentifier, maxScore, "unsupported-response-processing");
|
|
122086
122687
|
}
|
|
122087
|
-
if (!onlyAttributes(processing, []) || !onlyWhitespaceText(processing.children) || processingRules.length === 0 || processingRules.some((rule) => rule.localName !== "qti-response-condition")
|
|
122688
|
+
if (!onlyAttributes(processing, []) || !onlyWhitespaceText(processing.children) || processingRules.length === 0 || processingRules.some((rule) => rule.localName !== "qti-response-condition")) {
|
|
122689
|
+
return fallback(itemIdentifier, maxScore, "unsupported-response-processing");
|
|
122690
|
+
}
|
|
122691
|
+
if (processingRules.length === 1 && declarations.size === 1 && scoreDefaultIsZero(item) && feedbackOutcomeIsDeclared(item)) {
|
|
122692
|
+
const declaration = [...declarations.values()][0];
|
|
122693
|
+
const rule = binaryBranchRule(processingRules[0], declaration);
|
|
122694
|
+
if (rule) {
|
|
122695
|
+
return platformResult(itemIdentifier, maxScore, [rule]);
|
|
122696
|
+
}
|
|
122697
|
+
}
|
|
122698
|
+
if (!scoreDefaultIsZero(item)) {
|
|
122088
122699
|
return fallback(itemIdentifier, maxScore, "unsupported-response-processing");
|
|
122089
122700
|
}
|
|
122090
122701
|
const compiled = additiveRules(processingRules, declarations);
|
|
@@ -122137,7 +122748,7 @@ function compileQtiScoringArtifact(questions) {
|
|
|
122137
122748
|
});
|
|
122138
122749
|
return { items };
|
|
122139
122750
|
}
|
|
122140
|
-
function
|
|
122751
|
+
function isRecord32(value) {
|
|
122141
122752
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
122142
122753
|
}
|
|
122143
122754
|
function stringField2(value) {
|
|
@@ -122221,13 +122832,13 @@ function dedupeStandards2(standards) {
|
|
|
122221
122832
|
return [...deduped.values()];
|
|
122222
122833
|
}
|
|
122223
122834
|
function qtiAlignmentStandardsFromMetadata2(metadata2) {
|
|
122224
|
-
if (!
|
|
122835
|
+
if (!isRecord32(metadata2)) {
|
|
122225
122836
|
return [];
|
|
122226
122837
|
}
|
|
122227
122838
|
const standards = [];
|
|
122228
122839
|
const alignmentGroups = Array.isArray(metadata2.alignment) ? metadata2.alignment : [];
|
|
122229
122840
|
for (const group of alignmentGroups) {
|
|
122230
|
-
if (
|
|
122841
|
+
if (isRecord32(group)) {
|
|
122231
122842
|
const source = stringField2(group.curriculum) || "QTI";
|
|
122232
122843
|
const domains2 = Array.isArray(group.domains) ? group.domains : [];
|
|
122233
122844
|
if (!domains2.length) {
|
|
@@ -122245,11 +122856,11 @@ function qtiAlignmentStandardsFromMetadata2(metadata2) {
|
|
|
122245
122856
|
}
|
|
122246
122857
|
} else {
|
|
122247
122858
|
for (const domain3 of domains2) {
|
|
122248
|
-
if (
|
|
122859
|
+
if (isRecord32(domain3)) {
|
|
122249
122860
|
const domainName = stringField2(domain3.name) || undefined;
|
|
122250
122861
|
const domainStandards = Array.isArray(domain3.standards) ? domain3.standards : [];
|
|
122251
122862
|
for (const standard of domainStandards) {
|
|
122252
|
-
if (
|
|
122863
|
+
if (isRecord32(standard)) {
|
|
122253
122864
|
const identifier = stringField2(standard.identifier).trim();
|
|
122254
122865
|
const id = stringField2(standard.id).trim() || identifier;
|
|
122255
122866
|
if (id) {
|
|
@@ -122272,13 +122883,13 @@ function qtiAlignmentStandardsFromMetadata2(metadata2) {
|
|
|
122272
122883
|
return dedupeStandards2(standards);
|
|
122273
122884
|
}
|
|
122274
122885
|
function qtiStandardsFromMetadata(metadata2) {
|
|
122275
|
-
if (!
|
|
122886
|
+
if (!isRecord32(metadata2)) {
|
|
122276
122887
|
return [];
|
|
122277
122888
|
}
|
|
122278
122889
|
const standards = [];
|
|
122279
122890
|
const objectiveSets = Array.isArray(metadata2.learningObjectiveSet) ? metadata2.learningObjectiveSet : [];
|
|
122280
122891
|
for (const set3 of objectiveSets) {
|
|
122281
|
-
if (
|
|
122892
|
+
if (isRecord32(set3)) {
|
|
122282
122893
|
const source = stringField2(set3.source) || "QTI";
|
|
122283
122894
|
const ids = Array.isArray(set3.learningObjectiveIds) ? set3.learningObjectiveIds : [];
|
|
122284
122895
|
for (const id of ids) {
|
|
@@ -122333,13 +122944,27 @@ var NUMERIC_COMPARISON_ATTRIBUTES2;
|
|
|
122333
122944
|
var RESPONSE_CARDINALITIES2;
|
|
122334
122945
|
var RESPONSE_BASE_TYPES2;
|
|
122335
122946
|
var BLANK_SENTINEL22 = "";
|
|
122947
|
+
var QTI_FLOAT_VALUE;
|
|
122336
122948
|
var PLAYABLE_POINT_VALUE;
|
|
122337
122949
|
var POINT_INTERACTION_TYPES2;
|
|
122338
122950
|
var INDEPENDENT_PROCESSING_TAGS2;
|
|
122951
|
+
var PRIVATE_PLAYABLE_KEYS2;
|
|
122952
|
+
var trimmedNonEmptyString2;
|
|
122953
|
+
var finiteNumber22;
|
|
122954
|
+
var positiveFiniteNumber2;
|
|
122955
|
+
var IDENTIFIER_VALUE2;
|
|
122956
|
+
var DIRECTED_PAIR_VALUE2;
|
|
122957
|
+
var scoringMatchComparisonSchema2;
|
|
122958
|
+
var scoringNumericEqualComparisonSchema2;
|
|
122959
|
+
var scoringNumericEqualRoundedComparisonSchema2;
|
|
122960
|
+
var scoringRuleSchema2;
|
|
122961
|
+
var scoringRulesSchema2;
|
|
122962
|
+
var scoringItemSchema2;
|
|
122963
|
+
var assessmentScoringArtifactItemsSchema2;
|
|
122964
|
+
var assessmentScoringArtifactSchema2;
|
|
122339
122965
|
var SUPPORTED_BASE_TYPES2;
|
|
122340
122966
|
var SUPPORTED_CARDINALITIES2;
|
|
122341
122967
|
var MATCH_CORRECT_TEMPLATES2;
|
|
122342
|
-
var DIRECTED_PAIR_VALUE;
|
|
122343
122968
|
var QtiScoringArtifactValidationError;
|
|
122344
122969
|
var COMMON_CORE_MATH_FRAMEWORK_ALIASES2;
|
|
122345
122970
|
var COMMON_CORE_ELA_FRAMEWORK_ALIASES2;
|
|
@@ -122351,6 +122976,7 @@ var init_qti = __esm(() => {
|
|
|
122351
122976
|
init_timeback3();
|
|
122352
122977
|
init_timeback3();
|
|
122353
122978
|
init_timeback3();
|
|
122979
|
+
init_esm();
|
|
122354
122980
|
EVENT_HANDLER_ATTRIBUTE = /^on/i;
|
|
122355
122981
|
SCRIPT_SCHEMES2 = {
|
|
122356
122982
|
javascript: ["java", "script:"].join(""),
|
|
@@ -122575,6 +123201,7 @@ var init_qti = __esm(() => {
|
|
|
122575
123201
|
"string",
|
|
122576
123202
|
"uri"
|
|
122577
123203
|
]);
|
|
123204
|
+
QTI_FLOAT_VALUE = /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?$/i;
|
|
122578
123205
|
PLAYABLE_POINT_VALUE = /^-?\d+(?:\.\d+)? -?\d+(?:\.\d+)?$/;
|
|
122579
123206
|
POINT_INTERACTION_TYPES2 = new Set([
|
|
122580
123207
|
"select-point",
|
|
@@ -122593,6 +123220,93 @@ var init_qti = __esm(() => {
|
|
|
122593
123220
|
"qti-sum",
|
|
122594
123221
|
"qti-base-value"
|
|
122595
123222
|
]);
|
|
123223
|
+
PRIVATE_PLAYABLE_KEYS2 = new Set([
|
|
123224
|
+
"answer",
|
|
123225
|
+
"answerkey",
|
|
123226
|
+
"correctanswer",
|
|
123227
|
+
"correctresponse",
|
|
123228
|
+
"correctresponses",
|
|
123229
|
+
"correct",
|
|
123230
|
+
"correctvalue",
|
|
123231
|
+
"correctvalues",
|
|
123232
|
+
"iscorrect",
|
|
123233
|
+
"responseprocessing",
|
|
123234
|
+
"rule",
|
|
123235
|
+
"rules",
|
|
123236
|
+
"scoring",
|
|
123237
|
+
"scoringrules",
|
|
123238
|
+
"solution",
|
|
123239
|
+
"expected",
|
|
123240
|
+
"expectedanswer",
|
|
123241
|
+
"expectedvalue",
|
|
123242
|
+
"expectedvalues"
|
|
123243
|
+
]);
|
|
123244
|
+
trimmedNonEmptyString2 = exports_external.string().min(1).refine((value) => value === value.trim());
|
|
123245
|
+
finiteNumber22 = exports_external.number().finite();
|
|
123246
|
+
positiveFiniteNumber2 = finiteNumber22.positive();
|
|
123247
|
+
IDENTIFIER_VALUE2 = /^[A-Za-z_][\w.-]*$/;
|
|
123248
|
+
DIRECTED_PAIR_VALUE2 = /^[A-Za-z_][\w.-]* [A-Za-z_][\w.-]*$/;
|
|
123249
|
+
scoringMatchComparisonSchema2 = exports_external.object({
|
|
123250
|
+
kind: exports_external.literal("match"),
|
|
123251
|
+
correctValues: exports_external.array(trimmedNonEmptyString2).min(1).refine((values) => new Set(values).size === values.length)
|
|
123252
|
+
}).strict();
|
|
123253
|
+
scoringNumericEqualComparisonSchema2 = exports_external.object({ kind: exports_external.literal("numeric-equal"), correctValue: finiteNumber22 }).strict();
|
|
123254
|
+
scoringNumericEqualRoundedComparisonSchema2 = exports_external.object({
|
|
123255
|
+
kind: exports_external.literal("numeric-equal-rounded"),
|
|
123256
|
+
correctValue: finiteNumber22,
|
|
123257
|
+
roundingMode: exports_external.enum(["decimalPlaces", "significantFigures"]),
|
|
123258
|
+
figures: exports_external.number().int().safe().nonnegative()
|
|
123259
|
+
}).strict();
|
|
123260
|
+
scoringRuleSchema2 = exports_external.object({
|
|
123261
|
+
responseIdentifier: trimmedNonEmptyString2,
|
|
123262
|
+
cardinality: exports_external.enum(["single", "multiple", "ordered"]),
|
|
123263
|
+
baseType: exports_external.enum(["string", "identifier", "integer", "float", "directedPair"]),
|
|
123264
|
+
points: positiveFiniteNumber2,
|
|
123265
|
+
comparison: exports_external.discriminatedUnion("kind", [
|
|
123266
|
+
scoringMatchComparisonSchema2,
|
|
123267
|
+
scoringNumericEqualComparisonSchema2,
|
|
123268
|
+
scoringNumericEqualRoundedComparisonSchema2
|
|
123269
|
+
])
|
|
123270
|
+
}).strict().refine((rule) => rule.comparison.kind === "match" || rule.cardinality === "single" && (rule.baseType === "integer" || rule.baseType === "float")).refine((rule) => rule.baseType !== "directedPair" || rule.cardinality === "multiple" && rule.comparison.kind === "match").refine((rule) => rule.comparison.kind !== "match" || rule.cardinality !== "single" || rule.baseType === "string" || rule.comparison.correctValues.length === 1).refine((rule) => rule.comparison.kind !== "numeric-equal-rounded" || rule.comparison.roundingMode === "decimalPlaces" || rule.comparison.figures > 0).refine((rule) => {
|
|
123271
|
+
if (rule.comparison.kind !== "match") {
|
|
123272
|
+
return rule.baseType !== "integer" || Number.isSafeInteger(rule.comparison.correctValue);
|
|
123273
|
+
}
|
|
123274
|
+
if (rule.baseType === "integer" || rule.baseType === "float") {
|
|
123275
|
+
return rule.comparison.correctValues.every((value) => validNumericMatchValue2(value, rule.baseType));
|
|
123276
|
+
}
|
|
123277
|
+
if (rule.baseType === "identifier") {
|
|
123278
|
+
return rule.comparison.correctValues.every((value) => IDENTIFIER_VALUE2.test(value));
|
|
123279
|
+
}
|
|
123280
|
+
return rule.baseType !== "directedPair" || rule.comparison.correctValues.every((value) => DIRECTED_PAIR_VALUE2.test(value));
|
|
123281
|
+
});
|
|
123282
|
+
scoringRulesSchema2 = exports_external.array(scoringRuleSchema2).min(1).refine((rules) => new Set(rules.map((rule) => rule.responseIdentifier)).size === rules.length);
|
|
123283
|
+
scoringItemSchema2 = exports_external.discriminatedUnion("strategy", [
|
|
123284
|
+
exports_external.object({
|
|
123285
|
+
strategy: exports_external.literal("qti"),
|
|
123286
|
+
itemIdentifier: trimmedNonEmptyString2,
|
|
123287
|
+
maxScore: positiveFiniteNumber2,
|
|
123288
|
+
reason: exports_external.enum([
|
|
123289
|
+
"response-mapping",
|
|
123290
|
+
"area-mapping",
|
|
123291
|
+
"unsupported-base-type",
|
|
123292
|
+
"unsupported-cardinality",
|
|
123293
|
+
"invalid-correct-response",
|
|
123294
|
+
"no-scorable-response",
|
|
123295
|
+
"unsupported-processing-template",
|
|
123296
|
+
"unsupported-response-processing",
|
|
123297
|
+
"cross-response-processing",
|
|
123298
|
+
"inconsistent-max-score"
|
|
123299
|
+
])
|
|
123300
|
+
}).strict(),
|
|
123301
|
+
exports_external.object({
|
|
123302
|
+
strategy: exports_external.literal("platform"),
|
|
123303
|
+
itemIdentifier: trimmedNonEmptyString2,
|
|
123304
|
+
maxScore: positiveFiniteNumber2,
|
|
123305
|
+
rules: scoringRulesSchema2
|
|
123306
|
+
}).strict()
|
|
123307
|
+
]).refine((item) => item.strategy === "qti" || sameScore2(item.rules.reduce((total, rule) => total + rule.points, 0), item.maxScore));
|
|
123308
|
+
assessmentScoringArtifactItemsSchema2 = exports_external.array(scoringItemSchema2).refine((items) => new Set(items.map((item) => item.itemIdentifier)).size === items.length);
|
|
123309
|
+
assessmentScoringArtifactSchema2 = exports_external.object({ items: assessmentScoringArtifactItemsSchema2 }).strict();
|
|
122596
123310
|
SUPPORTED_BASE_TYPES2 = new Set([
|
|
122597
123311
|
"string",
|
|
122598
123312
|
"identifier",
|
|
@@ -122610,7 +123324,6 @@ var init_qti = __esm(() => {
|
|
|
122610
123324
|
"https://www.imsglobal.org/question/qti_v3p0/rptemplates/match_correct",
|
|
122611
123325
|
"https://purl.imsglobal.org/spec/qti/v3p0/rptemplates/match_correct"
|
|
122612
123326
|
].flatMap((template) => [template, `${template}.xml`]));
|
|
122613
|
-
DIRECTED_PAIR_VALUE = /^[A-Za-z_][\w.-]* [A-Za-z_][\w.-]*$/;
|
|
122614
123327
|
QtiScoringArtifactValidationError = class QtiScoringArtifactValidationError2 extends Error {
|
|
122615
123328
|
constructor(message) {
|
|
122616
123329
|
super(message);
|
|
@@ -124972,50 +125685,38 @@ var init_timeback_admin_service = __esm(async () => {
|
|
|
124972
125685
|
init_timeback_mastery_completion_util()
|
|
124973
125686
|
]);
|
|
124974
125687
|
});
|
|
124975
|
-
function normalizedKey(key) {
|
|
124976
|
-
return key.replaceAll(/[^a-z0-9]/gi, "").toLowerCase();
|
|
124977
|
-
}
|
|
124978
|
-
function containsPrivatePlayableKey(value) {
|
|
124979
|
-
if (Array.isArray(value)) {
|
|
124980
|
-
return value.some(containsPrivatePlayableKey);
|
|
124981
|
-
}
|
|
124982
|
-
if (!isRecord(value)) {
|
|
124983
|
-
return false;
|
|
124984
|
-
}
|
|
124985
|
-
return Object.entries(value).some(([key, entry2]) => PRIVATE_PLAYABLE_KEYS.has(normalizedKey(key)) || containsPrivatePlayableKey(entry2));
|
|
124986
|
-
}
|
|
124987
125688
|
function playableSchema(identity) {
|
|
124988
125689
|
return exports_external.object({
|
|
124989
125690
|
contractVersion: exports_external.literal(PLAYCADEMY_ASSESSMENT_CONTRACT_VERSION),
|
|
124990
125691
|
identifier: exports_external.literal(identity.qtiTestIdentifier),
|
|
124991
125692
|
contentRevision: exports_external.literal(identity.kind === "assessment" ? identity.contentRevision : identity.sourceContentRevision),
|
|
124992
|
-
title:
|
|
125693
|
+
title: trimmedNonEmptyString3,
|
|
124993
125694
|
items: exports_external.array(playableItemSchema)
|
|
124994
125695
|
}).strict();
|
|
124995
125696
|
}
|
|
124996
125697
|
function scoringSchema(identity) {
|
|
124997
125698
|
if (identity.kind === "assessment") {
|
|
124998
125699
|
return exports_external.object({
|
|
124999
|
-
items:
|
|
125700
|
+
items: assessmentScoringArtifactItemsSchema2,
|
|
125000
125701
|
reviewBank: exports_external.undefined(),
|
|
125001
125702
|
diagnostic: diagnosticContextSchema.optional()
|
|
125002
|
-
});
|
|
125703
|
+
}).strict();
|
|
125003
125704
|
}
|
|
125004
125705
|
return exports_external.object({
|
|
125005
|
-
items:
|
|
125706
|
+
items: assessmentScoringArtifactItemsSchema2,
|
|
125006
125707
|
reviewBank: exports_external.object({
|
|
125007
125708
|
sourceContentRevision: exports_external.literal(identity.sourceContentRevision),
|
|
125008
125709
|
bankRevision: exports_external.literal(identity.bankRevision),
|
|
125009
125710
|
items: exports_external.array(exports_external.object({
|
|
125010
|
-
itemIdentifier:
|
|
125711
|
+
itemIdentifier: trimmedNonEmptyString3,
|
|
125011
125712
|
standards: exports_external.array(exports_external.unknown())
|
|
125012
|
-
}))
|
|
125013
|
-
}),
|
|
125713
|
+
}).strict())
|
|
125714
|
+
}).strict(),
|
|
125014
125715
|
diagnostic: exports_external.undefined()
|
|
125015
|
-
}).refine((payload) => payload.reviewBank.items.length === payload.items.length && payload.reviewBank.items.every((item, index2) => item.itemIdentifier === payload.items[index2]?.itemIdentifier));
|
|
125716
|
+
}).strict().refine((payload) => payload.reviewBank.items.length === payload.items.length && payload.reviewBank.items.every((item, index2) => item.itemIdentifier === payload.items[index2]?.itemIdentifier));
|
|
125016
125717
|
}
|
|
125017
125718
|
function assertPlayableAssessmentArtifact(payload, identity) {
|
|
125018
|
-
if (!playableSchema(identity).safeParse(payload).success ||
|
|
125719
|
+
if (!playableSchema(identity).safeParse(payload).success || containsPrivatePlayableAssessmentData(payload)) {
|
|
125019
125720
|
throw new ValidationError("Playable assessment artifact payload is invalid or private");
|
|
125020
125721
|
}
|
|
125021
125722
|
}
|
|
@@ -125025,8 +125726,11 @@ function assertScoringAssessmentArtifact(payload, identity) {
|
|
|
125025
125726
|
}
|
|
125026
125727
|
}
|
|
125027
125728
|
function assertScoringArtifactMatchesPlayable(assessment, scoring, identity) {
|
|
125028
|
-
const scoringByIdentifier = new Map(scoring.items.map((item) => [item.itemIdentifier, item
|
|
125029
|
-
const playableMatches = assessment.items.every((item) =>
|
|
125729
|
+
const scoringByIdentifier = new Map(scoring.items.map((item) => [item.itemIdentifier, item]));
|
|
125730
|
+
const playableMatches = assessment.items.every((item) => {
|
|
125731
|
+
const scoringItem = scoringByIdentifier.get(item.identifier);
|
|
125732
|
+
return scoringItem !== undefined && scoringItemMatchesPlayableItem(scoringItem, item);
|
|
125733
|
+
});
|
|
125030
125734
|
const hasExactManagedMembership = identity.kind === "review-bank" || scoring.items.length === assessment.items.length;
|
|
125031
125735
|
if (!playableMatches || !hasExactManagedMembership) {
|
|
125032
125736
|
throw new ValidationError("Scoring artifact does not match the playable assessment");
|
|
@@ -125064,9 +125768,9 @@ function pinnedReviewBankFromAttempt(metadata2, bankIdentifier) {
|
|
|
125064
125768
|
}))
|
|
125065
125769
|
};
|
|
125066
125770
|
}
|
|
125067
|
-
var
|
|
125068
|
-
var
|
|
125069
|
-
var
|
|
125771
|
+
var trimmedNonEmptyString3;
|
|
125772
|
+
var finiteNumber3;
|
|
125773
|
+
var positiveFiniteNumber3;
|
|
125070
125774
|
var nonnegativeInteger;
|
|
125071
125775
|
var playableContentNodesSchema;
|
|
125072
125776
|
var playableChoiceSchema;
|
|
@@ -125078,76 +125782,54 @@ var playableGapImageSchema;
|
|
|
125078
125782
|
var playableInteractionBase;
|
|
125079
125783
|
var playableInteractionSchema;
|
|
125080
125784
|
var playableItemSchema;
|
|
125081
|
-
var scoringRuleSchema;
|
|
125082
|
-
var scoringItemSchema;
|
|
125083
|
-
var uniqueScoringItemsSchema;
|
|
125084
125785
|
var diagnosticContextSchema;
|
|
125085
125786
|
var init_assessment_artifact_runtime_util = __esm(() => {
|
|
125086
125787
|
init_esm();
|
|
125087
125788
|
init_src();
|
|
125789
|
+
init_qti();
|
|
125088
125790
|
init_errors2();
|
|
125089
|
-
|
|
125090
|
-
|
|
125091
|
-
|
|
125092
|
-
"correctanswer",
|
|
125093
|
-
"correctresponse",
|
|
125094
|
-
"correctresponses",
|
|
125095
|
-
"correct",
|
|
125096
|
-
"correctvalue",
|
|
125097
|
-
"correctvalues",
|
|
125098
|
-
"iscorrect",
|
|
125099
|
-
"responseprocessing",
|
|
125100
|
-
"rule",
|
|
125101
|
-
"rules",
|
|
125102
|
-
"scoring",
|
|
125103
|
-
"scoringrules",
|
|
125104
|
-
"solution",
|
|
125105
|
-
"expected",
|
|
125106
|
-
"expectedanswer",
|
|
125107
|
-
"expectedvalue",
|
|
125108
|
-
"expectedvalues"
|
|
125109
|
-
]);
|
|
125110
|
-
trimmedNonEmptyString = exports_external.string().min(1).refine((value) => value === value.trim());
|
|
125111
|
-
finiteNumber2 = exports_external.number().finite();
|
|
125791
|
+
trimmedNonEmptyString3 = exports_external.string().min(1).refine((value) => value === value.trim());
|
|
125792
|
+
finiteNumber3 = exports_external.number().finite();
|
|
125793
|
+
positiveFiniteNumber3 = finiteNumber3.positive();
|
|
125112
125794
|
nonnegativeInteger = exports_external.number().int().nonnegative();
|
|
125113
125795
|
playableContentNodesSchema = exports_external.array(exports_external.unknown());
|
|
125114
125796
|
playableChoiceSchema = exports_external.object({
|
|
125115
|
-
identifier:
|
|
125797
|
+
identifier: trimmedNonEmptyString3,
|
|
125116
125798
|
content: exports_external.string(),
|
|
125117
125799
|
contentNodes: playableContentNodesSchema.optional()
|
|
125118
125800
|
}).strict();
|
|
125119
125801
|
playableMatchChoiceSchema = exports_external.object({
|
|
125120
|
-
identifier:
|
|
125802
|
+
identifier: trimmedNonEmptyString3,
|
|
125121
125803
|
content: exports_external.string(),
|
|
125122
125804
|
contentNodes: playableContentNodesSchema.optional(),
|
|
125123
125805
|
matchMax: nonnegativeInteger
|
|
125124
125806
|
}).strict();
|
|
125125
125807
|
playableGraphicSchema = exports_external.object({
|
|
125126
|
-
src:
|
|
125127
|
-
width:
|
|
125128
|
-
height:
|
|
125808
|
+
src: trimmedNonEmptyString3,
|
|
125809
|
+
width: finiteNumber3.optional(),
|
|
125810
|
+
height: finiteNumber3.optional(),
|
|
125129
125811
|
description: exports_external.string().optional()
|
|
125130
125812
|
}).strict();
|
|
125131
125813
|
playableHotspotSchema = exports_external.object({
|
|
125132
|
-
identifier:
|
|
125814
|
+
identifier: trimmedNonEmptyString3,
|
|
125133
125815
|
shape: exports_external.enum(["circle", "ellipse", "rect", "poly", "default"]),
|
|
125134
|
-
coords: exports_external.array(
|
|
125816
|
+
coords: exports_external.array(finiteNumber3),
|
|
125135
125817
|
matchMax: nonnegativeInteger.optional(),
|
|
125136
125818
|
description: exports_external.string().optional()
|
|
125137
125819
|
}).strict();
|
|
125138
125820
|
playableGapTextSchema = exports_external.object({
|
|
125139
|
-
identifier:
|
|
125821
|
+
identifier: trimmedNonEmptyString3,
|
|
125140
125822
|
content: exports_external.string(),
|
|
125141
125823
|
matchMax: nonnegativeInteger
|
|
125142
125824
|
}).strict();
|
|
125143
125825
|
playableGapImageSchema = exports_external.object({
|
|
125144
|
-
identifier:
|
|
125826
|
+
identifier: trimmedNonEmptyString3,
|
|
125145
125827
|
image: playableGraphicSchema.optional(),
|
|
125146
125828
|
matchMax: nonnegativeInteger,
|
|
125147
125829
|
description: exports_external.string().optional()
|
|
125148
125830
|
}).strict();
|
|
125149
125831
|
playableInteractionBase = {
|
|
125150
|
-
responseIdentifier:
|
|
125832
|
+
responseIdentifier: trimmedNonEmptyString3,
|
|
125151
125833
|
prompt: exports_external.string().optional()
|
|
125152
125834
|
};
|
|
125153
125835
|
playableInteractionSchema = exports_external.discriminatedUnion("type", [
|
|
@@ -125187,7 +125869,7 @@ var init_assessment_artifact_runtime_util = __esm(() => {
|
|
|
125187
125869
|
...playableInteractionBase,
|
|
125188
125870
|
type: exports_external.literal("hottext"),
|
|
125189
125871
|
segments: exports_external.array(exports_external.object({
|
|
125190
|
-
identifier:
|
|
125872
|
+
identifier: trimmedNonEmptyString3.optional(),
|
|
125191
125873
|
content: exports_external.string(),
|
|
125192
125874
|
selectable: exports_external.boolean()
|
|
125193
125875
|
}).strict()),
|
|
@@ -125207,7 +125889,7 @@ var init_assessment_artifact_runtime_util = __esm(() => {
|
|
|
125207
125889
|
content: exports_external.string(),
|
|
125208
125890
|
contentNodes: playableContentNodesSchema,
|
|
125209
125891
|
gapTexts: exports_external.array(playableGapTextSchema),
|
|
125210
|
-
gaps: exports_external.array(
|
|
125892
|
+
gaps: exports_external.array(trimmedNonEmptyString3),
|
|
125211
125893
|
maxAssociations: nonnegativeInteger
|
|
125212
125894
|
}).strict(),
|
|
125213
125895
|
exports_external.object({
|
|
@@ -125241,59 +125923,17 @@ var init_assessment_artifact_runtime_util = __esm(() => {
|
|
|
125241
125923
|
}).strict()
|
|
125242
125924
|
]);
|
|
125243
125925
|
playableItemSchema = exports_external.object({
|
|
125244
|
-
identifier:
|
|
125926
|
+
identifier: trimmedNonEmptyString3,
|
|
125245
125927
|
title: exports_external.string(),
|
|
125246
125928
|
prompt: exports_external.string(),
|
|
125247
125929
|
promptContent: playableContentNodesSchema.optional(),
|
|
125248
|
-
maxScore:
|
|
125930
|
+
maxScore: positiveFiniteNumber3,
|
|
125249
125931
|
interactions: exports_external.array(playableInteractionSchema)
|
|
125250
125932
|
}).strict();
|
|
125251
|
-
scoringRuleSchema = exports_external.object({
|
|
125252
|
-
responseIdentifier: trimmedNonEmptyString,
|
|
125253
|
-
cardinality: exports_external.enum(["single", "multiple", "ordered"]),
|
|
125254
|
-
baseType: exports_external.enum(["string", "identifier", "integer", "float", "directedPair"]),
|
|
125255
|
-
points: finiteNumber2,
|
|
125256
|
-
comparison: exports_external.discriminatedUnion("kind", [
|
|
125257
|
-
exports_external.object({ kind: exports_external.literal("match"), correctValues: exports_external.array(exports_external.string()) }),
|
|
125258
|
-
exports_external.object({ kind: exports_external.literal("numeric-equal"), correctValue: finiteNumber2 }),
|
|
125259
|
-
exports_external.object({
|
|
125260
|
-
kind: exports_external.literal("numeric-equal-rounded"),
|
|
125261
|
-
correctValue: finiteNumber2,
|
|
125262
|
-
roundingMode: exports_external.enum(["decimalPlaces", "significantFigures"]),
|
|
125263
|
-
figures: exports_external.number().int().nonnegative()
|
|
125264
|
-
})
|
|
125265
|
-
])
|
|
125266
|
-
});
|
|
125267
|
-
scoringItemSchema = exports_external.discriminatedUnion("strategy", [
|
|
125268
|
-
exports_external.object({
|
|
125269
|
-
strategy: exports_external.literal("qti"),
|
|
125270
|
-
itemIdentifier: trimmedNonEmptyString,
|
|
125271
|
-
maxScore: finiteNumber2,
|
|
125272
|
-
reason: exports_external.enum([
|
|
125273
|
-
"response-mapping",
|
|
125274
|
-
"area-mapping",
|
|
125275
|
-
"unsupported-base-type",
|
|
125276
|
-
"unsupported-cardinality",
|
|
125277
|
-
"invalid-correct-response",
|
|
125278
|
-
"no-scorable-response",
|
|
125279
|
-
"unsupported-processing-template",
|
|
125280
|
-
"unsupported-response-processing",
|
|
125281
|
-
"cross-response-processing",
|
|
125282
|
-
"inconsistent-max-score"
|
|
125283
|
-
])
|
|
125284
|
-
}),
|
|
125285
|
-
exports_external.object({
|
|
125286
|
-
strategy: exports_external.literal("platform"),
|
|
125287
|
-
itemIdentifier: trimmedNonEmptyString,
|
|
125288
|
-
maxScore: finiteNumber2,
|
|
125289
|
-
rules: exports_external.array(scoringRuleSchema)
|
|
125290
|
-
})
|
|
125291
|
-
]);
|
|
125292
|
-
uniqueScoringItemsSchema = exports_external.array(scoringItemSchema).refine((items) => new Set(items.map((item) => item.itemIdentifier)).size === items.length);
|
|
125293
125933
|
diagnosticContextSchema = exports_external.object({
|
|
125294
|
-
routingRevision:
|
|
125934
|
+
routingRevision: trimmedNonEmptyString3,
|
|
125295
125935
|
routingManifest: exports_external.record(exports_external.unknown())
|
|
125296
|
-
});
|
|
125936
|
+
}).strict();
|
|
125297
125937
|
});
|
|
125298
125938
|
|
|
125299
125939
|
class AssessmentArtifactRuntimeLoader {
|
|
@@ -126211,16 +126851,8 @@ function prepareDiagnosticAssessmentResponses(assessment, current, input) {
|
|
|
126211
126851
|
const update2 = { [input.itemIdentifier]: input.responses };
|
|
126212
126852
|
validateAssessmentResponseUpdate(assessment, update2);
|
|
126213
126853
|
const responses = applyAssessmentResponseUpdate(current, update2);
|
|
126214
|
-
const item = assessment.items.find((candidate) => candidate.identifier === input.itemIdentifier);
|
|
126215
126854
|
const itemResponses = responses[input.itemIdentifier];
|
|
126216
|
-
|
|
126217
|
-
if (!item || !itemResponses || missingResponse) {
|
|
126218
|
-
throw new AssessmentRuntimeError("INVALID_RESPONSE", `Diagnostic item ${input.itemIdentifier} requires a response for every interaction.`, {
|
|
126219
|
-
itemIdentifier: input.itemIdentifier,
|
|
126220
|
-
...missingResponse ? { responseIdentifier: missingResponse.responseIdentifier } : {}
|
|
126221
|
-
});
|
|
126222
|
-
}
|
|
126223
|
-
validateAssessmentResponses(assessment, { [input.itemIdentifier]: itemResponses });
|
|
126855
|
+
validateAssessmentDraftResponses(assessment, itemResponses ? { [input.itemIdentifier]: itemResponses } : {});
|
|
126224
126856
|
return responses;
|
|
126225
126857
|
}
|
|
126226
126858
|
function validateAssessmentResponseUpdate(assessment, update2) {
|
|
@@ -127684,6 +128316,162 @@ var init_timeback_assessment_artifacts_util = __esm(() => {
|
|
|
127684
128316
|
init_timeback_assessment_runtime_util();
|
|
127685
128317
|
init_timeback_review_mapping_util();
|
|
127686
128318
|
});
|
|
128319
|
+
async function buildSubmissionChildResultCommand(input) {
|
|
128320
|
+
const { submission, context: context2 } = input;
|
|
128321
|
+
const { isCorrect } = submission;
|
|
128322
|
+
const common2 = {
|
|
128323
|
+
attempt: input.attempt,
|
|
128324
|
+
administration: {
|
|
128325
|
+
submissionId: submission.submissionId,
|
|
128326
|
+
responseVersion: submission.responseVersion,
|
|
128327
|
+
itemIdentifier: submission.itemIdentifier
|
|
128328
|
+
},
|
|
128329
|
+
responses: input.responses ?? {},
|
|
128330
|
+
score: submission.score,
|
|
128331
|
+
grading: input.grading
|
|
128332
|
+
};
|
|
128333
|
+
if (context2.kind === "review") {
|
|
128334
|
+
return buildAssessmentChildResultCommand({
|
|
128335
|
+
...common2,
|
|
128336
|
+
...typeof isCorrect === "boolean" ? { isCorrect } : {},
|
|
128337
|
+
context: context2
|
|
128338
|
+
}, submission.submittedAt);
|
|
128339
|
+
}
|
|
128340
|
+
if (typeof isCorrect !== "boolean") {
|
|
128341
|
+
throw new Error("Diagnostic child results require determinate correctness");
|
|
128342
|
+
}
|
|
128343
|
+
return buildAssessmentChildResultCommand({ ...common2, isCorrect, context: context2 }, submission.submittedAt);
|
|
128344
|
+
}
|
|
128345
|
+
function reviewDefinitionWithoutStandards(definition) {
|
|
128346
|
+
return Object.fromEntries(Object.entries(definition).filter(([field]) => field !== "standards"));
|
|
128347
|
+
}
|
|
128348
|
+
function assessmentChildLineItemMetadataMatches(key, stored, expected) {
|
|
128349
|
+
if (key !== "playcademyReviewQuestionDefinition" || typeof stored !== "object" || stored === null || typeof expected !== "object" || expected === null) {
|
|
128350
|
+
return canonicalJson2(stored) === canonicalJson2(expected);
|
|
128351
|
+
}
|
|
128352
|
+
const storedDefinition = stored;
|
|
128353
|
+
const expectedDefinition = expected;
|
|
128354
|
+
const storedStandards = storedDefinition.standards;
|
|
128355
|
+
const expectedStandards = expectedDefinition.standards;
|
|
128356
|
+
if (!Array.isArray(storedStandards) || !Array.isArray(expectedStandards)) {
|
|
128357
|
+
return false;
|
|
128358
|
+
}
|
|
128359
|
+
const sameDefinition = canonicalJson2(reviewDefinitionWithoutStandards(storedDefinition)) === canonicalJson2(reviewDefinitionWithoutStandards(expectedDefinition));
|
|
128360
|
+
function contains(standards, candidate) {
|
|
128361
|
+
return standards.some((standard) => canonicalJson2(standard) === canonicalJson2(candidate));
|
|
128362
|
+
}
|
|
128363
|
+
return sameDefinition && (expectedStandards.every((standard) => contains(storedStandards, standard)) || storedStandards.every((standard) => contains(expectedStandards, standard)));
|
|
128364
|
+
}
|
|
128365
|
+
function assessmentChildLineItemMatches(stored, expected) {
|
|
128366
|
+
if (stored.status !== expected.status || stored.parentAssessmentLineItem?.sourcedId !== expected.parentAssessmentLineItem.sourcedId || stored.course?.sourcedId !== expected.course.sourcedId || stored.resultValueMin !== expected.resultValueMin || stored.resultValueMax !== expected.resultValueMax || typeof stored.metadata !== "object" || stored.metadata === null) {
|
|
128367
|
+
return false;
|
|
128368
|
+
}
|
|
128369
|
+
return Object.entries(expected.metadata).every(([key, value]) => assessmentChildLineItemMetadataMatches(key, stored.metadata[key], value));
|
|
128370
|
+
}
|
|
128371
|
+
function buildAssessmentChildResultUpdate(command, childLineItemId) {
|
|
128372
|
+
return {
|
|
128373
|
+
status: "active",
|
|
128374
|
+
assessmentLineItem: { sourcedId: childLineItemId },
|
|
128375
|
+
student: { sourcedId: command.payload.attempt.studentId },
|
|
128376
|
+
score: command.payload.score.earned,
|
|
128377
|
+
scoreDate: command.recordedAt,
|
|
128378
|
+
...ASSESSMENT_ATTEMPT_COMPLETED,
|
|
128379
|
+
metadata: {
|
|
128380
|
+
[PLAYCADEMY_ASSESSMENT_ITEM_RESULT_METADATA_KEY]: command
|
|
128381
|
+
}
|
|
128382
|
+
};
|
|
128383
|
+
}
|
|
128384
|
+
async function classifyAssessmentChildResult(input) {
|
|
128385
|
+
const { result } = input;
|
|
128386
|
+
if (typeof result.metadata !== "object" || result.metadata === null || !(PLAYCADEMY_ASSESSMENT_ITEM_RESULT_METADATA_KEY in result.metadata)) {
|
|
128387
|
+
return { outcome: "conflict", reason: "malformed-command" };
|
|
128388
|
+
}
|
|
128389
|
+
const verification2 = await verifyAssessmentChildResultCommand(result.metadata);
|
|
128390
|
+
if (!verification2.ok) {
|
|
128391
|
+
return {
|
|
128392
|
+
outcome: "conflict",
|
|
128393
|
+
reason: verification2.reason === "payload_hash_mismatch" ? "payload-hash-mismatch" : "malformed-command"
|
|
128394
|
+
};
|
|
128395
|
+
}
|
|
128396
|
+
if (verification2.command.payloadHash !== input.command.payloadHash) {
|
|
128397
|
+
return { outcome: "conflict", reason: "payload-conflict" };
|
|
128398
|
+
}
|
|
128399
|
+
if (result.status === "tobedeleted") {
|
|
128400
|
+
return { outcome: "conflict", reason: "tombstoned" };
|
|
128401
|
+
}
|
|
128402
|
+
const expected = buildAssessmentChildResultUpdate(verification2.command, input.childLineItemId);
|
|
128403
|
+
const projectionChecks = [
|
|
128404
|
+
["status-mismatch", result.status, expected.status],
|
|
128405
|
+
["line-item-mismatch", result.assessmentLineItem?.sourcedId, input.childLineItemId],
|
|
128406
|
+
["student-mismatch", result.student?.sourcedId, expected.student.sourcedId],
|
|
128407
|
+
["score-mismatch", result.score, expected.score],
|
|
128408
|
+
["score-date-mismatch", result.scoreDate, expected.scoreDate],
|
|
128409
|
+
["score-status-mismatch", result.scoreStatus, expected.scoreStatus],
|
|
128410
|
+
["in-progress-mismatch", result.inProgress, expected.inProgress]
|
|
128411
|
+
];
|
|
128412
|
+
const mismatch = projectionChecks.find(([, stored, wanted]) => stored !== wanted);
|
|
128413
|
+
if (mismatch) {
|
|
128414
|
+
return { outcome: "conflict", reason: mismatch[0] };
|
|
128415
|
+
}
|
|
128416
|
+
const auxiliaryFields = [
|
|
128417
|
+
result.textScore,
|
|
128418
|
+
result.scoreScale,
|
|
128419
|
+
result.scorePercentile,
|
|
128420
|
+
result.comment,
|
|
128421
|
+
result.learningObjectiveSet,
|
|
128422
|
+
result.incomplete,
|
|
128423
|
+
result.late,
|
|
128424
|
+
result.missing
|
|
128425
|
+
];
|
|
128426
|
+
if (auxiliaryFields.some((value) => value !== undefined && value !== null)) {
|
|
128427
|
+
return { outcome: "conflict", reason: "auxiliary-field-mismatch" };
|
|
128428
|
+
}
|
|
128429
|
+
return { outcome: "replay" };
|
|
128430
|
+
}
|
|
128431
|
+
function hasApiStatus(error88, statusCode) {
|
|
128432
|
+
return isApiError(error88) && error88.statusCode === statusCode;
|
|
128433
|
+
}
|
|
128434
|
+
async function persistAssessmentChildResult(input) {
|
|
128435
|
+
const { assessmentResults, childLineItemId } = input;
|
|
128436
|
+
const verification2 = await verifyAssessmentChildResultCommand(input.command);
|
|
128437
|
+
if (!verification2.ok) {
|
|
128438
|
+
throw new Error(`Invalid assessment child result command: ${verification2.reason}`);
|
|
128439
|
+
}
|
|
128440
|
+
const { command } = verification2;
|
|
128441
|
+
const resultId = await assessmentChildResultId(command.payload.attempt.attemptId, command.payload.administration.submissionId);
|
|
128442
|
+
async function classifyOccupant() {
|
|
128443
|
+
const resolution = await classifyAssessmentChildResult({
|
|
128444
|
+
result: await assessmentResults.get(resultId),
|
|
128445
|
+
command,
|
|
128446
|
+
childLineItemId
|
|
128447
|
+
});
|
|
128448
|
+
return resolution.outcome === "replay" ? { outcome: "replay", resultId } : { outcome: "conflict", resultId, reason: resolution.reason };
|
|
128449
|
+
}
|
|
128450
|
+
try {
|
|
128451
|
+
return await classifyOccupant();
|
|
128452
|
+
} catch (error88) {
|
|
128453
|
+
if (!hasApiStatus(error88, 404)) {
|
|
128454
|
+
throw error88;
|
|
128455
|
+
}
|
|
128456
|
+
}
|
|
128457
|
+
try {
|
|
128458
|
+
await assessmentResults.create({
|
|
128459
|
+
sourcedId: resultId,
|
|
128460
|
+
...buildAssessmentChildResultUpdate(command, childLineItemId)
|
|
128461
|
+
});
|
|
128462
|
+
return { outcome: "created", resultId };
|
|
128463
|
+
} catch (error88) {
|
|
128464
|
+
if (!hasApiStatus(error88, 409)) {
|
|
128465
|
+
throw error88;
|
|
128466
|
+
}
|
|
128467
|
+
}
|
|
128468
|
+
return classifyOccupant();
|
|
128469
|
+
}
|
|
128470
|
+
var init_timeback_assessment_child_result_util = __esm(async () => {
|
|
128471
|
+
init_src();
|
|
128472
|
+
init_assessment_runtime2();
|
|
128473
|
+
await init_errors8();
|
|
128474
|
+
});
|
|
127687
128475
|
|
|
127688
128476
|
class AssessmentPreparationFlights {
|
|
127689
128477
|
limit;
|
|
@@ -127816,6 +128604,7 @@ function buildPreparedAssessmentMetadata(plan, timestamp6, testName) {
|
|
|
127816
128604
|
testName,
|
|
127817
128605
|
routingRevision: plan.diagnostic.routingRevision,
|
|
127818
128606
|
ledger: [],
|
|
128607
|
+
routingSnapshots: [],
|
|
127819
128608
|
state: plan.diagnostic.initialState
|
|
127820
128609
|
}
|
|
127821
128610
|
};
|
|
@@ -127867,7 +128656,7 @@ function declarationNeutralQtiXml(source) {
|
|
|
127867
128656
|
}
|
|
127868
128657
|
function managedMetadataRecord(metadata2, key) {
|
|
127869
128658
|
const managed = metadata2?.[key];
|
|
127870
|
-
return
|
|
128659
|
+
return isRecord4(managed) ? managed : null;
|
|
127871
128660
|
}
|
|
127872
128661
|
function sourceMetadata(metadata2) {
|
|
127873
128662
|
return Object.fromEntries(Object.entries(metadata2 ?? {}).filter(([key]) => !GENERATED_METADATA_KEYS.has(key)));
|
|
@@ -128068,7 +128857,7 @@ function assertManagedAssessmentPublication(test, expected) {
|
|
|
128068
128857
|
function managedAssessmentDiagnosticRoutingManifest(test, assessmentKey) {
|
|
128069
128858
|
const managed = managedMetadataRecord(test.metadata, PLAYCADEMY_MANAGED_ASSESSMENT_METADATA_KEY);
|
|
128070
128859
|
const manifest = managed?.diagnosticRoutingManifest;
|
|
128071
|
-
return managed?.assessmentKey === assessmentKey &&
|
|
128860
|
+
return managed?.assessmentKey === assessmentKey && isRecord4(manifest) ? manifest : null;
|
|
128072
128861
|
}
|
|
128073
128862
|
async function assertManagedAssessmentItem(item, expected) {
|
|
128074
128863
|
const managed = managedMetadataRecord(item.metadata, PLAYCADEMY_MANAGED_ITEM_METADATA_KEY);
|
|
@@ -128111,6 +128900,192 @@ var init_timeback_assessment_publication_util = __esm(() => {
|
|
|
128111
128900
|
]);
|
|
128112
128901
|
MANAGED_ASSET_URL = new RegExp(`https?://[^\\s"')]+/(${ASSESSMENT_ASSET_KEY_PREFIX}v1/sha256/[a-f0-9]{64}\\.[a-z0-9]+)`, "gi");
|
|
128113
128902
|
});
|
|
128903
|
+
function assessmentChildProjectionIsTerminal(result) {
|
|
128904
|
+
const state = { inProgress: result.inProgress ?? "", scoreStatus: result.scoreStatus };
|
|
128905
|
+
return isAssessmentAttemptAwaitingAward(state) || isAssessmentAttemptCompleted(state);
|
|
128906
|
+
}
|
|
128907
|
+
async function reconcileReviewChildResults({
|
|
128908
|
+
initial,
|
|
128909
|
+
writeOpen,
|
|
128910
|
+
crossBarrier,
|
|
128911
|
+
readLatest,
|
|
128912
|
+
restoreTerminal
|
|
128913
|
+
}) {
|
|
128914
|
+
if (initial.metadata.purpose !== "review") {
|
|
128915
|
+
throw new Error("A standards-review request selected a non-review attempt");
|
|
128916
|
+
}
|
|
128917
|
+
if (assessmentChildProjectionIsTerminal(initial.result)) {
|
|
128918
|
+
const settled = { result: initial.result, metadata: initial.metadata };
|
|
128919
|
+
await restoreTerminal(settled);
|
|
128920
|
+
return settled;
|
|
128921
|
+
}
|
|
128922
|
+
await writeOpen();
|
|
128923
|
+
await crossBarrier();
|
|
128924
|
+
const current = await readLatest();
|
|
128925
|
+
if (current.metadata.purpose !== "review") {
|
|
128926
|
+
throw new Error("A standards-review request selected a non-review attempt");
|
|
128927
|
+
}
|
|
128928
|
+
const reviewed = { result: current.result, metadata: current.metadata };
|
|
128929
|
+
if (assessmentChildProjectionIsTerminal(current.result)) {
|
|
128930
|
+
await restoreTerminal(reviewed);
|
|
128931
|
+
}
|
|
128932
|
+
return reviewed;
|
|
128933
|
+
}
|
|
128934
|
+
var init_timeback_assessment_reconciliation_util = __esm(() => {
|
|
128935
|
+
init_assessment_runtime2();
|
|
128936
|
+
});
|
|
128937
|
+
function resolveDiagnosticItemReplay(metadata2, input) {
|
|
128938
|
+
const priorIndex = metadata2.itemSubmissions.findIndex((submission) => submission.submissionId === input.submissionId);
|
|
128939
|
+
if (priorIndex === -1) {
|
|
128940
|
+
return { action: "missing" };
|
|
128941
|
+
}
|
|
128942
|
+
const prior = metadata2.itemSubmissions[priorIndex];
|
|
128943
|
+
const ledgerEntry = metadata2.diagnostic.ledger[priorIndex];
|
|
128944
|
+
if (!prior || !ledgerEntry || prior.itemIdentifier !== input.itemIdentifier || ledgerEntry.routingNodeKey !== input.routingNodeKey || !itemResponseUpdateMatches(metadata2.responses[input.itemIdentifier], input.responses)) {
|
|
128945
|
+
return {
|
|
128946
|
+
action: "conflict",
|
|
128947
|
+
failure: assessmentFlowViolation("This diagnostic submission ID was already used for a different request.", {
|
|
128948
|
+
submissionId: input.submissionId,
|
|
128949
|
+
routingNodeKey: input.routingNodeKey,
|
|
128950
|
+
itemIdentifier: input.itemIdentifier
|
|
128951
|
+
})
|
|
128952
|
+
};
|
|
128953
|
+
}
|
|
128954
|
+
return {
|
|
128955
|
+
action: "replay",
|
|
128956
|
+
submission: prior,
|
|
128957
|
+
ledgerEntry,
|
|
128958
|
+
priorIndex,
|
|
128959
|
+
routing: metadata2.diagnostic.routingSnapshots?.[priorIndex]
|
|
128960
|
+
};
|
|
128961
|
+
}
|
|
128962
|
+
function buildAssessmentItemReplayResult(attemptId, metadata2, submission) {
|
|
128963
|
+
return {
|
|
128964
|
+
attemptId,
|
|
128965
|
+
responseVersion: metadata2.responseVersion,
|
|
128966
|
+
status: "in_progress",
|
|
128967
|
+
responses: metadata2.responses,
|
|
128968
|
+
itemSubmissions: metadata2.itemSubmissions,
|
|
128969
|
+
submission
|
|
128970
|
+
};
|
|
128971
|
+
}
|
|
128972
|
+
function diagnosticReceipt(submission, ledgerEntry) {
|
|
128973
|
+
return {
|
|
128974
|
+
submissionId: submission.submissionId,
|
|
128975
|
+
routingNodeKey: ledgerEntry.routingNodeKey,
|
|
128976
|
+
itemIdentifier: submission.itemIdentifier,
|
|
128977
|
+
submittedAt: submission.submittedAt,
|
|
128978
|
+
responseVersion: submission.responseVersion,
|
|
128979
|
+
answered: submission.answered,
|
|
128980
|
+
score: submission.score,
|
|
128981
|
+
isCorrect: ledgerEntry.isCorrect
|
|
128982
|
+
};
|
|
128983
|
+
}
|
|
128984
|
+
function buildDiagnosticItemReplayResult(attemptId, submission, ledgerEntry, routing) {
|
|
128985
|
+
return {
|
|
128986
|
+
attemptId,
|
|
128987
|
+
responseVersion: submission.responseVersion,
|
|
128988
|
+
status: "in_progress",
|
|
128989
|
+
routing,
|
|
128990
|
+
submission: diagnosticReceipt(submission, ledgerEntry)
|
|
128991
|
+
};
|
|
128992
|
+
}
|
|
128993
|
+
function requireAssessmentMutationAccess(attemptId, integrationStatus, enrollment) {
|
|
128994
|
+
if (!enrollment || integrationStatus === "deactivated") {
|
|
128995
|
+
throw AssessmentRuntimeError.from(assessmentAttemptUnauthorized(attemptId));
|
|
128996
|
+
}
|
|
128997
|
+
return enrollment;
|
|
128998
|
+
}
|
|
128999
|
+
function committedReviewItemResults(metadata2, assessment) {
|
|
129000
|
+
const available = new Set(assessment.items.map((item) => item.identifier));
|
|
129001
|
+
const seen = new Set;
|
|
129002
|
+
return metadata2.itemSubmissions.map((submission) => {
|
|
129003
|
+
if (!available.has(submission.itemIdentifier) || seen.has(submission.itemIdentifier)) {
|
|
129004
|
+
throw AssessmentRuntimeError.from(assessmentFlowViolation("The persisted review item-submission ledger is corrupt.", {
|
|
129005
|
+
itemIdentifier: submission.itemIdentifier
|
|
129006
|
+
}));
|
|
129007
|
+
}
|
|
129008
|
+
seen.add(submission.itemIdentifier);
|
|
129009
|
+
return {
|
|
129010
|
+
itemIdentifier: submission.itemIdentifier,
|
|
129011
|
+
score: submission.score,
|
|
129012
|
+
isCorrect: submission.isCorrect
|
|
129013
|
+
};
|
|
129014
|
+
});
|
|
129015
|
+
}
|
|
129016
|
+
var init_timeback_assessment_replay_util = __esm(() => {
|
|
129017
|
+
init_assessment_runtime2();
|
|
129018
|
+
init_errors2();
|
|
129019
|
+
});
|
|
129020
|
+
function diagnosticRoutingFallbackReason(artifact, routingRevision) {
|
|
129021
|
+
if (!artifact) {
|
|
129022
|
+
return "artifact-unavailable";
|
|
129023
|
+
}
|
|
129024
|
+
if (!artifact.diagnostic) {
|
|
129025
|
+
return "diagnostic-context-missing";
|
|
129026
|
+
}
|
|
129027
|
+
return artifact.diagnostic.routingRevision === routingRevision ? "routing-manifest-invalid" : "routing-revision-mismatch";
|
|
129028
|
+
}
|
|
129029
|
+
function assertDiagnosticItemsAvailable(definitionId, manifest, assessment) {
|
|
129030
|
+
const itemIdentifiers = new Set(assessment.items.map((item) => item.identifier));
|
|
129031
|
+
const unknownItem = manifest.nodes.find((node) => !itemIdentifiers.has(node.itemIdentifier));
|
|
129032
|
+
if (unknownItem) {
|
|
129033
|
+
throw new AssessmentRuntimeError("SELECTED_TEST_VERSION_UNAVAILABLE", `Diagnostic routing references unavailable item ${unknownItem.itemIdentifier}.`, {
|
|
129034
|
+
definitionId,
|
|
129035
|
+
nodeKey: unknownItem.key,
|
|
129036
|
+
itemIdentifier: unknownItem.itemIdentifier
|
|
129037
|
+
});
|
|
129038
|
+
}
|
|
129039
|
+
}
|
|
129040
|
+
function replayAttemptDiagnosticRouting(metadata2, manifest) {
|
|
129041
|
+
const replayed = replayDiagnosticRouting(manifest, metadata2.diagnostic.ledger);
|
|
129042
|
+
if (!replayed.ok || canonicalJson2(replayed.state) !== canonicalJson2(metadata2.diagnostic.state)) {
|
|
129043
|
+
throw AssessmentRuntimeError.from(assessmentFlowViolation("The persisted diagnostic routing state is corrupt.", {
|
|
129044
|
+
definitionId: metadata2.diagnostic.definitionId,
|
|
129045
|
+
...replayed.ok ? {} : { failure: replayed.failure }
|
|
129046
|
+
}));
|
|
129047
|
+
}
|
|
129048
|
+
return replayed.state;
|
|
129049
|
+
}
|
|
129050
|
+
async function resolveCompiledDiagnosticRouting(metadata2, assessment, artifact) {
|
|
129051
|
+
const diagnostic = artifact?.diagnostic;
|
|
129052
|
+
if (diagnostic?.routingRevision !== metadata2.diagnostic.routingRevision) {
|
|
129053
|
+
return null;
|
|
129054
|
+
}
|
|
129055
|
+
const initialized = initializeDiagnosticRouting(diagnostic.routingManifest);
|
|
129056
|
+
if (!initialized.ok) {
|
|
129057
|
+
return null;
|
|
129058
|
+
}
|
|
129059
|
+
const routingRevision = await diagnosticRoutingRevision(initialized.manifest);
|
|
129060
|
+
if (routingRevision !== metadata2.diagnostic.routingRevision) {
|
|
129061
|
+
return null;
|
|
129062
|
+
}
|
|
129063
|
+
assertDiagnosticItemsAvailable(metadata2.diagnostic.definitionId, initialized.manifest, assessment);
|
|
129064
|
+
return {
|
|
129065
|
+
manifest: initialized.manifest,
|
|
129066
|
+
routingRevision,
|
|
129067
|
+
state: replayAttemptDiagnosticRouting(metadata2, initialized.manifest)
|
|
129068
|
+
};
|
|
129069
|
+
}
|
|
129070
|
+
function summarizeDiagnosticSubmission(metadata2, manifest) {
|
|
129071
|
+
const state = replayAttemptDiagnosticRouting(metadata2, manifest);
|
|
129072
|
+
const tracks = diagnosticTrackResults(state);
|
|
129073
|
+
if (!tracks) {
|
|
129074
|
+
throw AssessmentRuntimeError.from(assessmentFlowViolation("Diagnostic routing must be ready to complete before final submit.", { routingStatus: state.status }));
|
|
129075
|
+
}
|
|
129076
|
+
if (metadata2.itemSubmissions.length === 0) {
|
|
129077
|
+
throw AssessmentRuntimeError.from(assessmentFlowViolation("A diagnostic cannot be finalized without an administered item."));
|
|
129078
|
+
}
|
|
129079
|
+
const possible = metadata2.itemSubmissions.reduce((total, submission) => total + submission.score.possible, 0);
|
|
129080
|
+
const score = aggregateAssessmentScore(metadata2.itemSubmissions.map((submission) => submission.score.earned), possible);
|
|
129081
|
+
const correctQuestions = countCorrectQuestions(metadata2.itemSubmissions.map((submission) => submission.isCorrect));
|
|
129082
|
+
return { tracks, score, correctQuestions };
|
|
129083
|
+
}
|
|
129084
|
+
var init_timeback_assessment_routing_util = __esm(() => {
|
|
129085
|
+
init_assessment_runtime2();
|
|
129086
|
+
init_errors2();
|
|
129087
|
+
init_timeback_assessment_runtime_util();
|
|
129088
|
+
});
|
|
128114
129089
|
function validateAssessmentStatusTransition(current, next) {
|
|
128115
129090
|
if (current === next) {
|
|
128116
129091
|
return;
|
|
@@ -128187,6 +129162,157 @@ function validateUniqueAssessmentIdentifiers(testIdentifiers) {
|
|
|
128187
129162
|
var init_timeback_assessment_rules_util = __esm(() => {
|
|
128188
129163
|
init_errors2();
|
|
128189
129164
|
});
|
|
129165
|
+
async function scoreAssessmentItems({
|
|
129166
|
+
assessment,
|
|
129167
|
+
responses,
|
|
129168
|
+
artifactIdentity,
|
|
129169
|
+
purpose,
|
|
129170
|
+
itemIdentifiers,
|
|
129171
|
+
artifact,
|
|
129172
|
+
processResponse,
|
|
129173
|
+
onEvent
|
|
129174
|
+
}) {
|
|
129175
|
+
const scoringItems = assessmentScoringRequests(assessment, responses).filter((item) => !itemIdentifiers || itemIdentifiers.has(item.itemIdentifier));
|
|
129176
|
+
const artifactItems = new Map(artifact?.items.map((item) => [item.itemIdentifier, item]));
|
|
129177
|
+
const scored = new Map;
|
|
129178
|
+
const qti2 = [];
|
|
129179
|
+
for (const item of scoringItems) {
|
|
129180
|
+
const artifactItem = artifactItems.get(item.itemIdentifier);
|
|
129181
|
+
let fallback2 = null;
|
|
129182
|
+
if (!artifact) {
|
|
129183
|
+
fallback2 = { reason: "artifact-unavailable" };
|
|
129184
|
+
} else if (!artifactItem) {
|
|
129185
|
+
fallback2 = { reason: "artifact-item-missing" };
|
|
129186
|
+
} else if (artifactItem.strategy === "qti") {
|
|
129187
|
+
fallback2 = { reason: "artifact-qti", artifactReason: artifactItem.reason };
|
|
129188
|
+
} else {
|
|
129189
|
+
const startedAt = Date.now();
|
|
129190
|
+
try {
|
|
129191
|
+
scored.set(item.itemIdentifier, {
|
|
129192
|
+
...scorePlatformAssessmentItem(artifactItem, responses[item.itemIdentifier]),
|
|
129193
|
+
grading: PLATFORM_CHILD_GRADING
|
|
129194
|
+
});
|
|
129195
|
+
onEvent("assessment.item_scoring", {
|
|
129196
|
+
"app.assessment.qti_item_identifier": item.itemIdentifier,
|
|
129197
|
+
"app.assessment.purpose": purpose,
|
|
129198
|
+
"app.assessment.artifact_identity_kind": artifactIdentity.kind,
|
|
129199
|
+
"app.assessment.grading_source": "platform-artifact",
|
|
129200
|
+
"app.assessment.grading_duration_ms": Date.now() - startedAt
|
|
129201
|
+
});
|
|
129202
|
+
} catch (error88) {
|
|
129203
|
+
fallback2 = { reason: "platform-grader-error", error: error88 };
|
|
129204
|
+
}
|
|
129205
|
+
}
|
|
129206
|
+
if (fallback2) {
|
|
129207
|
+
qti2.push({ item, ...fallback2 });
|
|
129208
|
+
}
|
|
129209
|
+
}
|
|
129210
|
+
if (qti2.length > 0) {
|
|
129211
|
+
const fallbackTasks = qti2.flatMap(({ item, reason, artifactReason, error: graderError }) => {
|
|
129212
|
+
const interactionResults = [];
|
|
129213
|
+
let startedAt;
|
|
129214
|
+
let completedInteractions = 0;
|
|
129215
|
+
let failed = false;
|
|
129216
|
+
const omittedResponseCount = item.requests.filter((request) => responses[item.itemIdentifier]?.[request.identifier] === undefined).length;
|
|
129217
|
+
let qtiRemoteInteractionCount = 0;
|
|
129218
|
+
let syntheticIncorrectInteractionCount = 0;
|
|
129219
|
+
function fallbackEvent(outcome) {
|
|
129220
|
+
onEvent("assessment.item_scoring_fallback", {
|
|
129221
|
+
"app.assessment.qti_item_identifier": item.itemIdentifier,
|
|
129222
|
+
"app.assessment.purpose": purpose,
|
|
129223
|
+
"app.assessment.artifact_identity_kind": artifactIdentity.kind,
|
|
129224
|
+
"app.assessment.grading_source": omittedResponseCount > 0 ? "platform-qti-adapter" : "timeback-qti",
|
|
129225
|
+
"app.assessment.fallback_reason": reason,
|
|
129226
|
+
"app.assessment.fallback_duration_ms": startedAt === undefined ? 0 : Date.now() - startedAt,
|
|
129227
|
+
"app.assessment.qti_remote_interaction_count": qtiRemoteInteractionCount,
|
|
129228
|
+
"app.assessment.synthetic_incorrect_interaction_count": syntheticIncorrectInteractionCount,
|
|
129229
|
+
...artifactReason ? { "app.assessment.artifact_fallback_reason": artifactReason } : {},
|
|
129230
|
+
...graderError ? {
|
|
129231
|
+
"app.assessment.platform_grader_exception_type": errorType(graderError)
|
|
129232
|
+
} : {},
|
|
129233
|
+
...outcome
|
|
129234
|
+
});
|
|
129235
|
+
}
|
|
129236
|
+
function completeItem() {
|
|
129237
|
+
const earned = assessmentItemEarnedScore(interactionResults.map((result) => Number.isFinite(result.score) ? result.score : 0), item.maxScore);
|
|
129238
|
+
const possible = item.maxScore;
|
|
129239
|
+
fallbackEvent({ "app.assessment.fallback_outcome": "succeeded" });
|
|
129240
|
+
scored.set(item.itemIdentifier, {
|
|
129241
|
+
itemIdentifier: item.itemIdentifier,
|
|
129242
|
+
score: {
|
|
129243
|
+
earned,
|
|
129244
|
+
possible,
|
|
129245
|
+
normalized: possible === 0 ? 0 : earned / possible
|
|
129246
|
+
},
|
|
129247
|
+
isCorrect: assessmentItemCorrectness(interactionResults.map((result) => result.isCorrect)),
|
|
129248
|
+
grading: omittedResponseCount > 0 ? PLATFORM_QTI_ADAPTER_CHILD_GRADING : QTI_CHILD_GRADING
|
|
129249
|
+
});
|
|
129250
|
+
}
|
|
129251
|
+
if (item.requests.length === 0) {
|
|
129252
|
+
completeItem();
|
|
129253
|
+
return [];
|
|
129254
|
+
}
|
|
129255
|
+
return item.requests.map((request, index2) => async () => {
|
|
129256
|
+
startedAt ??= Date.now();
|
|
129257
|
+
try {
|
|
129258
|
+
const response = responses[item.itemIdentifier]?.[request.identifier];
|
|
129259
|
+
if (response === undefined) {
|
|
129260
|
+
syntheticIncorrectInteractionCount += 1;
|
|
129261
|
+
interactionResults[index2] = { score: 0, isCorrect: false };
|
|
129262
|
+
} else {
|
|
129263
|
+
qtiRemoteInteractionCount += 1;
|
|
129264
|
+
interactionResults[index2] = await processResponse(item.itemIdentifier, request);
|
|
129265
|
+
}
|
|
129266
|
+
completedInteractions += 1;
|
|
129267
|
+
if (completedInteractions === item.requests.length && !failed) {
|
|
129268
|
+
completeItem();
|
|
129269
|
+
}
|
|
129270
|
+
} catch (error88) {
|
|
129271
|
+
if (!failed) {
|
|
129272
|
+
failed = true;
|
|
129273
|
+
fallbackEvent({
|
|
129274
|
+
"app.assessment.fallback_outcome": "failed",
|
|
129275
|
+
"exception.type": errorType(error88),
|
|
129276
|
+
"app.error.message": errorMessage2(error88)
|
|
129277
|
+
});
|
|
129278
|
+
}
|
|
129279
|
+
throw error88;
|
|
129280
|
+
}
|
|
129281
|
+
});
|
|
129282
|
+
});
|
|
129283
|
+
await runWithConcurrency(fallbackTasks, SCORING_CONCURRENCY, (task) => task());
|
|
129284
|
+
}
|
|
129285
|
+
return scoringItems.map((item) => {
|
|
129286
|
+
const result = scored.get(item.itemIdentifier);
|
|
129287
|
+
if (!result) {
|
|
129288
|
+
throw new Error(`Assessment item ${item.itemIdentifier} was not scored`);
|
|
129289
|
+
}
|
|
129290
|
+
return result;
|
|
129291
|
+
});
|
|
129292
|
+
}
|
|
129293
|
+
var QTI_CHILD_GRADING;
|
|
129294
|
+
var PLATFORM_QTI_ADAPTER_CHILD_GRADING;
|
|
129295
|
+
var PLATFORM_CHILD_GRADING;
|
|
129296
|
+
var SCORING_CONCURRENCY = 4;
|
|
129297
|
+
var init_timeback_assessment_scoring_util = __esm(() => {
|
|
129298
|
+
init_assessment_runtime2();
|
|
129299
|
+
init_assessment_artifact_envelope_util();
|
|
129300
|
+
init_timeback_assessment_runtime_util();
|
|
129301
|
+
QTI_CHILD_GRADING = {
|
|
129302
|
+
source: "timeback-qti",
|
|
129303
|
+
graderVersion: "timeback-qti-process-response-v1"
|
|
129304
|
+
};
|
|
129305
|
+
PLATFORM_QTI_ADAPTER_CHILD_GRADING = {
|
|
129306
|
+
source: "platform-qti-adapter",
|
|
129307
|
+
graderVersion: "platform-qti-adapter-v1",
|
|
129308
|
+
qtiGraderVersion: "timeback-qti-process-response-v1"
|
|
129309
|
+
};
|
|
129310
|
+
PLATFORM_CHILD_GRADING = {
|
|
129311
|
+
source: "platform-artifact",
|
|
129312
|
+
artifactVersion: `assessment-artifact-v${ASSESSMENT_ARTIFACT_SCHEMA_VERSION}`,
|
|
129313
|
+
graderVersion: `platform-grader-v${ASSESSMENT_GRADER_PROTOCOL_VERSION}`
|
|
129314
|
+
};
|
|
129315
|
+
});
|
|
128190
129316
|
function qtiItemHref(client2, itemIdentifier) {
|
|
128191
129317
|
return `${client2.getQtiBaseUrl()}/assessment-items/${itemIdentifier}`;
|
|
128192
129318
|
}
|
|
@@ -128262,6 +129388,7 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
128262
129388
|
init_spans();
|
|
128263
129389
|
init_assessment_runtime2();
|
|
128264
129390
|
init_constants3();
|
|
129391
|
+
init_qti();
|
|
128265
129392
|
init_types2();
|
|
128266
129393
|
init_uuid();
|
|
128267
129394
|
init_errors2();
|
|
@@ -128270,13 +129397,18 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
128270
129397
|
init_timeback_assessment_artifacts_util();
|
|
128271
129398
|
init_timeback_assessment_preparation_util();
|
|
128272
129399
|
init_timeback_assessment_publication_util();
|
|
129400
|
+
init_timeback_assessment_reconciliation_util();
|
|
129401
|
+
init_timeback_assessment_replay_util();
|
|
129402
|
+
init_timeback_assessment_routing_util();
|
|
128273
129403
|
init_timeback_assessment_rules_util();
|
|
128274
129404
|
init_timeback_assessment_runtime_util();
|
|
129405
|
+
init_timeback_assessment_scoring_util();
|
|
128275
129406
|
init_timeback_qti_hydration_util();
|
|
128276
129407
|
await __promiseAll([
|
|
128277
129408
|
init_dist5(),
|
|
128278
129409
|
init_errors8(),
|
|
128279
|
-
init_assessment_artifact_loader_util()
|
|
129410
|
+
init_assessment_artifact_loader_util(),
|
|
129411
|
+
init_timeback_assessment_child_result_util()
|
|
128280
129412
|
]);
|
|
128281
129413
|
AssessmentPreparationPrerequisiteError = class AssessmentPreparationPrerequisiteError2 extends Error {
|
|
128282
129414
|
original;
|
|
@@ -128657,12 +129789,19 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
128657
129789
|
activityId: params.input.activityId,
|
|
128658
129790
|
source
|
|
128659
129791
|
}));
|
|
128660
|
-
await this.provisionPreparationPrerequisites(() => runWithConcurrency(assessment.items, TimebackAssessmentRuntimeService2.SCORING_CONCURRENCY, (item) =>
|
|
128661
|
-
|
|
128662
|
-
|
|
128663
|
-
|
|
128664
|
-
|
|
128665
|
-
|
|
129792
|
+
await this.provisionPreparationPrerequisites(() => runWithConcurrency(assessment.items, TimebackAssessmentRuntimeService2.SCORING_CONCURRENCY, (item) => {
|
|
129793
|
+
const indexed = catalog.bank.items.find((candidate) => candidate.itemIdentifier === item.identifier);
|
|
129794
|
+
if (!indexed) {
|
|
129795
|
+
throw new Error(`Review-bank index is missing item ${item.identifier}`);
|
|
129796
|
+
}
|
|
129797
|
+
return this.ensureReviewQuestionLineItem({
|
|
129798
|
+
parentLineItemId,
|
|
129799
|
+
integration: context2.integration,
|
|
129800
|
+
bankRevision: catalog.bank.bankRevision,
|
|
129801
|
+
standards: [...indexed.standards],
|
|
129802
|
+
item
|
|
129803
|
+
});
|
|
129804
|
+
}));
|
|
128666
129805
|
const attempt = await this.provisionPreparationPrerequisites(() => this.claimAttemptId({
|
|
128667
129806
|
gameId: params.gameId,
|
|
128668
129807
|
studentId: params.studentId,
|
|
@@ -128851,6 +129990,7 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
128851
129990
|
testName: assessment.title,
|
|
128852
129991
|
routingRevision: routing.routingRevision,
|
|
128853
129992
|
ledger: [],
|
|
129993
|
+
routingSnapshots: [],
|
|
128854
129994
|
state: routing.state
|
|
128855
129995
|
}
|
|
128856
129996
|
};
|
|
@@ -129010,15 +130150,6 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
129010
130150
|
grade: context2.integration.grade
|
|
129011
130151
|
})
|
|
129012
130152
|
});
|
|
129013
|
-
await this.ensureReviewChildResults({
|
|
129014
|
-
result,
|
|
129015
|
-
metadata: metadata2,
|
|
129016
|
-
integration: context2.integration,
|
|
129017
|
-
source,
|
|
129018
|
-
assessment,
|
|
129019
|
-
existingChildResults: refreshedListing.reviewChildren,
|
|
129020
|
-
lookupMissingResults: false
|
|
129021
|
-
});
|
|
129022
130153
|
await this.persistPendingSupersessionsBestEffort(db2, pendingSupersessions);
|
|
129023
130154
|
this.recordReviewPreparationPhase("provisioning", provisioningStartedAt);
|
|
129024
130155
|
this.recordReviewPreparationPhase("total", preparationStartedAt, {
|
|
@@ -129153,13 +130284,39 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
129153
130284
|
let preparationAttempts = 1;
|
|
129154
130285
|
while (true) {
|
|
129155
130286
|
const committed = await this.deps.assessmentRuntimeLock(this.deps.db, [assessmentRuntimeAttemptLockKey(params.attemptId)], async (db2) => {
|
|
129156
|
-
const attempt = await this.
|
|
130287
|
+
const { attempt, enrollment } = await this.resolveAttemptAuthorization(params, {
|
|
129157
130288
|
db: db2,
|
|
129158
130289
|
developerAccessValidated: true
|
|
129159
130290
|
});
|
|
129160
130291
|
if (this.isPlatformRoutedDiagnosticMetadata(attempt.metadata)) {
|
|
130292
|
+
this.requireActiveAttempt({ attempt, enrollment }, params.attemptId);
|
|
129161
130293
|
throw AssessmentRuntimeError.from(assessmentFlowViolation("Platform-routed diagnostic submissions require routingNodeKey.", { flow: "platform-routed-item-submit" }));
|
|
129162
130294
|
}
|
|
130295
|
+
const replay = resolveAssessmentItemReplay(attempt.metadata, input);
|
|
130296
|
+
if (replay.action === "conflict") {
|
|
130297
|
+
this.requireActiveAttempt({ attempt, enrollment }, params.attemptId);
|
|
130298
|
+
throw AssessmentRuntimeError.from(replay.failure);
|
|
130299
|
+
}
|
|
130300
|
+
if (replay.action === "replay") {
|
|
130301
|
+
const prior = replay.submission;
|
|
130302
|
+
const child2 = attempt.metadata.purpose === "review" ? await this.prepareReviewAssessmentChildResult({
|
|
130303
|
+
attempt,
|
|
130304
|
+
metadata: attempt.metadata,
|
|
130305
|
+
submission: prior
|
|
130306
|
+
}) : null;
|
|
130307
|
+
return {
|
|
130308
|
+
action: "complete",
|
|
130309
|
+
response: buildAssessmentItemReplayResult(attempt.result.sourcedId, attempt.metadata, prior),
|
|
130310
|
+
checkpoint: child2,
|
|
130311
|
+
projection: attempt.metadata.purpose === "review" ? {
|
|
130312
|
+
result: attempt.result,
|
|
130313
|
+
metadata: attempt.metadata,
|
|
130314
|
+
itemIdentifier: input.itemIdentifier,
|
|
130315
|
+
integration: attempt.integration
|
|
130316
|
+
} : null
|
|
130317
|
+
};
|
|
130318
|
+
}
|
|
130319
|
+
this.requireActiveAttempt({ attempt, enrollment }, params.attemptId);
|
|
129163
130320
|
this.assertInProgress(attempt.result);
|
|
129164
130321
|
if (!preview || preview.responseVersion !== attempt.metadata.responseVersion || preview.submissionId !== input.submissionId || preview.itemIdentifier !== input.itemIdentifier) {
|
|
129165
130322
|
if (preparationAttempts >= TimebackAssessmentRuntimeService2.ITEM_SUBMISSION_MAX_PREPARATION_ATTEMPTS) {
|
|
@@ -129178,6 +130335,11 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
129178
130335
|
throw AssessmentRuntimeError.from(transition.failure);
|
|
129179
130336
|
}
|
|
129180
130337
|
if (transition.action === "replay") {
|
|
130338
|
+
const child2 = attempt.metadata.purpose === "review" ? await this.prepareReviewAssessmentChildResult({
|
|
130339
|
+
attempt,
|
|
130340
|
+
metadata: attempt.metadata,
|
|
130341
|
+
submission: transition.submission
|
|
130342
|
+
}) : null;
|
|
129181
130343
|
return {
|
|
129182
130344
|
action: "complete",
|
|
129183
130345
|
response: {
|
|
@@ -129188,10 +130350,12 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
129188
130350
|
itemSubmissions: attempt.metadata.itemSubmissions,
|
|
129189
130351
|
submission: transition.submission
|
|
129190
130352
|
},
|
|
130353
|
+
checkpoint: child2,
|
|
129191
130354
|
projection: attempt.metadata.purpose === "review" ? {
|
|
129192
130355
|
result: attempt.result,
|
|
129193
130356
|
metadata: attempt.metadata,
|
|
129194
|
-
itemIdentifier: input.itemIdentifier
|
|
130357
|
+
itemIdentifier: input.itemIdentifier,
|
|
130358
|
+
integration: attempt.integration
|
|
129195
130359
|
} : null
|
|
129196
130360
|
};
|
|
129197
130361
|
}
|
|
@@ -129207,6 +130371,11 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
129207
130371
|
itemSubmissions: completed.itemSubmissions,
|
|
129208
130372
|
updatedAt: submittedAt
|
|
129209
130373
|
};
|
|
130374
|
+
const child = metadata2.purpose === "review" ? await this.prepareReviewAssessmentChildResult({
|
|
130375
|
+
attempt,
|
|
130376
|
+
metadata: metadata2,
|
|
130377
|
+
submission: completed.submission
|
|
130378
|
+
}) : null;
|
|
129210
130379
|
await this.putResultMetadata(attempt.result, metadata2);
|
|
129211
130380
|
return {
|
|
129212
130381
|
action: "complete",
|
|
@@ -129218,10 +130387,12 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
129218
130387
|
itemSubmissions: completed.itemSubmissions,
|
|
129219
130388
|
submission: completed.submission
|
|
129220
130389
|
},
|
|
130390
|
+
checkpoint: child,
|
|
129221
130391
|
projection: metadata2.purpose === "review" ? {
|
|
129222
130392
|
result: attempt.result,
|
|
129223
130393
|
metadata: metadata2,
|
|
129224
|
-
itemIdentifier: input.itemIdentifier
|
|
130394
|
+
itemIdentifier: input.itemIdentifier,
|
|
130395
|
+
integration: attempt.integration
|
|
129225
130396
|
} : null
|
|
129226
130397
|
};
|
|
129227
130398
|
});
|
|
@@ -129230,7 +130401,7 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
129230
130401
|
preview = await this.scoreItemSubmission(params, input);
|
|
129231
130402
|
} else {
|
|
129232
130403
|
if (committed.projection) {
|
|
129233
|
-
|
|
130404
|
+
this.projectReviewItemResponse(params, committed.projection, committed.checkpoint);
|
|
129234
130405
|
}
|
|
129235
130406
|
return committed.response;
|
|
129236
130407
|
}
|
|
@@ -129241,40 +130412,39 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
129241
130412
|
let preparationAttempts = 1;
|
|
129242
130413
|
while (true) {
|
|
129243
130414
|
const committed = await this.deps.assessmentRuntimeLock(this.deps.db, [assessmentRuntimeAttemptLockKey(params.attemptId)], async (db2) => {
|
|
129244
|
-
const attempt = await this.
|
|
129245
|
-
db: db2,
|
|
129246
|
-
developerAccessValidated: true
|
|
129247
|
-
});
|
|
130415
|
+
const { attempt, enrollment } = await this.resolveAttemptAuthorization(params, { db: db2, developerAccessValidated: true });
|
|
129248
130416
|
if (!this.isPlatformRoutedDiagnosticMetadata(attempt.metadata)) {
|
|
130417
|
+
this.requireActiveAttempt({ attempt, enrollment }, params.attemptId);
|
|
129249
130418
|
throw AssessmentRuntimeError.from(assessmentFlowViolation("routingNodeKey is accepted only for a platform-routed diagnostic.", { routingNodeKey: input.routingNodeKey }));
|
|
129250
130419
|
}
|
|
129251
130420
|
const metadata2 = attempt.metadata;
|
|
129252
|
-
const
|
|
129253
|
-
if (
|
|
129254
|
-
|
|
129255
|
-
|
|
129256
|
-
|
|
129257
|
-
|
|
129258
|
-
|
|
129259
|
-
|
|
129260
|
-
|
|
129261
|
-
|
|
129262
|
-
|
|
129263
|
-
|
|
129264
|
-
|
|
129265
|
-
|
|
129266
|
-
|
|
129267
|
-
|
|
130421
|
+
const replay = resolveDiagnosticItemReplay(metadata2, input);
|
|
130422
|
+
if (replay.action === "conflict") {
|
|
130423
|
+
this.requireActiveAttempt({ attempt, enrollment }, params.attemptId);
|
|
130424
|
+
throw AssessmentRuntimeError.from(replay.failure);
|
|
130425
|
+
}
|
|
130426
|
+
if (replay.action === "replay") {
|
|
130427
|
+
const { submission: prior, ledgerEntry, priorIndex } = replay;
|
|
130428
|
+
let routing = replay.routing;
|
|
130429
|
+
if (!routing) {
|
|
130430
|
+
const assessment = preview?.assessment ?? await this.loadAttemptAssessment(metadata2);
|
|
130431
|
+
const loaded2 = await this.loadAttemptDiagnosticRouting(metadata2, assessment, db2);
|
|
130432
|
+
const replayed = replayDiagnosticRouting(loaded2.manifest, metadata2.diagnostic.ledger.slice(0, priorIndex + 1));
|
|
130433
|
+
if (!replayed.ok) {
|
|
130434
|
+
throw AssessmentRuntimeError.from(assessmentFlowViolation("The committed diagnostic routing state cannot be replayed.", { failure: replayed.failure }));
|
|
130435
|
+
}
|
|
130436
|
+
routing = this.diagnosticRoutingSnapshot(metadata2, replayed.state);
|
|
129268
130437
|
}
|
|
130438
|
+
const child2 = await this.prepareDiagnosticAssessmentChildResult({
|
|
130439
|
+
attempt,
|
|
130440
|
+
metadata: metadata2,
|
|
130441
|
+
submission: prior,
|
|
130442
|
+
transition: ledgerEntry
|
|
130443
|
+
});
|
|
129269
130444
|
return {
|
|
129270
130445
|
action: "complete",
|
|
129271
|
-
response:
|
|
129272
|
-
|
|
129273
|
-
responseVersion: prior.responseVersion,
|
|
129274
|
-
status: "in_progress",
|
|
129275
|
-
routing: this.diagnosticRoutingSnapshot(metadata2, replayed.state),
|
|
129276
|
-
submission: this.diagnosticReceipt(prior, ledgerEntry)
|
|
129277
|
-
},
|
|
130446
|
+
response: buildDiagnosticItemReplayResult(attempt.result.sourcedId, prior, ledgerEntry, routing),
|
|
130447
|
+
checkpoint: child2,
|
|
129278
130448
|
projection: {
|
|
129279
130449
|
result: attempt.result,
|
|
129280
130450
|
metadata: metadata2,
|
|
@@ -129283,6 +130453,7 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
129283
130453
|
}
|
|
129284
130454
|
};
|
|
129285
130455
|
}
|
|
130456
|
+
this.requireActiveAttempt({ attempt, enrollment }, params.attemptId);
|
|
129286
130457
|
this.assertInProgress(attempt.result);
|
|
129287
130458
|
if (!preview || preview.responseVersion !== metadata2.responseVersion || preview.submissionId !== input.submissionId || preview.routingNodeKey !== input.routingNodeKey || preview.itemIdentifier !== input.itemIdentifier) {
|
|
129288
130459
|
if (preparationAttempts >= TimebackAssessmentRuntimeService2.ITEM_SUBMISSION_MAX_PREPARATION_ATTEMPTS) {
|
|
@@ -129316,14 +130487,17 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
129316
130487
|
}
|
|
129317
130488
|
const submittedAt = new Date().toISOString();
|
|
129318
130489
|
const responseVersion = metadata2.responseVersion + 1;
|
|
130490
|
+
const routingSnapshot = this.diagnosticRoutingSnapshot(metadata2, advanced.state);
|
|
130491
|
+
const routingSnapshots = metadata2.diagnostic.routingSnapshots ? [...metadata2.diagnostic.routingSnapshots, routingSnapshot] : undefined;
|
|
129319
130492
|
const submission = {
|
|
129320
130493
|
submissionId: input.submissionId,
|
|
129321
130494
|
itemIdentifier: input.itemIdentifier,
|
|
129322
130495
|
submittedAt,
|
|
129323
130496
|
responseVersion,
|
|
129324
|
-
answered:
|
|
130497
|
+
answered: Object.keys(responses[input.itemIdentifier] ?? {}).length > 0,
|
|
129325
130498
|
score: scoring.score,
|
|
129326
|
-
isCorrect: scoring.isCorrect
|
|
130499
|
+
isCorrect: scoring.isCorrect,
|
|
130500
|
+
grading: scoring.grading
|
|
129327
130501
|
};
|
|
129328
130502
|
const nextMetadata = {
|
|
129329
130503
|
...metadata2,
|
|
@@ -129334,9 +130508,16 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
129334
130508
|
diagnostic: {
|
|
129335
130509
|
...metadata2.diagnostic,
|
|
129336
130510
|
ledger: [...metadata2.diagnostic.ledger, advanced.ledgerEntry],
|
|
130511
|
+
...routingSnapshots ? { routingSnapshots } : {},
|
|
129337
130512
|
state: advanced.state
|
|
129338
130513
|
}
|
|
129339
130514
|
};
|
|
130515
|
+
const child = await this.prepareDiagnosticAssessmentChildResult({
|
|
130516
|
+
attempt,
|
|
130517
|
+
metadata: nextMetadata,
|
|
130518
|
+
submission,
|
|
130519
|
+
transition: advanced.ledgerEntry
|
|
130520
|
+
});
|
|
129340
130521
|
await this.putResultMetadata(attempt.result, nextMetadata);
|
|
129341
130522
|
return {
|
|
129342
130523
|
action: "complete",
|
|
@@ -129344,9 +130525,10 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
129344
130525
|
attemptId: attempt.result.sourcedId,
|
|
129345
130526
|
responseVersion,
|
|
129346
130527
|
status: "in_progress",
|
|
129347
|
-
routing:
|
|
129348
|
-
submission:
|
|
130528
|
+
routing: routingSnapshot,
|
|
130529
|
+
submission: diagnosticReceipt(submission, advanced.ledgerEntry)
|
|
129349
130530
|
},
|
|
130531
|
+
checkpoint: child,
|
|
129350
130532
|
projection: {
|
|
129351
130533
|
result: attempt.result,
|
|
129352
130534
|
metadata: nextMetadata,
|
|
@@ -129359,21 +130541,11 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
129359
130541
|
preparationAttempts += 1;
|
|
129360
130542
|
preview = await this.scoreDiagnosticItemSubmission(params, input);
|
|
129361
130543
|
} else {
|
|
129362
|
-
|
|
130544
|
+
this.projectDiagnosticItemResponse(committed.projection, preview?.assessment, committed.checkpoint);
|
|
129363
130545
|
return committed.response;
|
|
129364
130546
|
}
|
|
129365
130547
|
}
|
|
129366
130548
|
}
|
|
129367
|
-
diagnosticReceipt(submission, ledgerEntry) {
|
|
129368
|
-
return {
|
|
129369
|
-
submissionId: submission.submissionId,
|
|
129370
|
-
routingNodeKey: ledgerEntry.routingNodeKey,
|
|
129371
|
-
itemIdentifier: submission.itemIdentifier,
|
|
129372
|
-
submittedAt: submission.submittedAt,
|
|
129373
|
-
responseVersion: submission.responseVersion,
|
|
129374
|
-
answered: submission.answered
|
|
129375
|
-
};
|
|
129376
|
-
}
|
|
129377
130549
|
async prepareDiagnosticItemSubmission(params, input) {
|
|
129378
130550
|
try {
|
|
129379
130551
|
return await this.scoreDiagnosticItemSubmission(params, input);
|
|
@@ -129393,22 +130565,24 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
129393
130565
|
}
|
|
129394
130566
|
const prior = attempt.metadata.itemSubmissions.find((submission) => submission.submissionId === input.submissionId);
|
|
129395
130567
|
if (prior || !this.isOpen(attempt.result)) {
|
|
129396
|
-
return
|
|
129397
|
-
responseVersion: attempt.metadata.responseVersion,
|
|
129398
|
-
submissionId: input.submissionId,
|
|
129399
|
-
routingNodeKey: input.routingNodeKey,
|
|
129400
|
-
itemIdentifier: input.itemIdentifier,
|
|
129401
|
-
assessment: await this.loadAttemptAssessment(attempt.metadata),
|
|
129402
|
-
scoring: null
|
|
129403
|
-
};
|
|
130568
|
+
return null;
|
|
129404
130569
|
}
|
|
129405
130570
|
const assessment = await this.loadAttemptAssessment(attempt.metadata);
|
|
129406
|
-
const
|
|
130571
|
+
const artifactIdentity = assessmentArtifactIdentityFromAttempt(attempt.metadata);
|
|
130572
|
+
const scoringArtifact = await this.artifactLoader.loadMatchedScoring(assessment, artifactIdentity);
|
|
130573
|
+
const loaded = await this.loadAttemptDiagnosticRouting(attempt.metadata, assessment, this.deps.db, scoringArtifact);
|
|
129407
130574
|
const expected = loaded.state.next;
|
|
129408
130575
|
let scoring = null;
|
|
129409
130576
|
if (loaded.state.status === "in-progress" && expected?.nodeKey === input.routingNodeKey && expected.itemIdentifier === input.itemIdentifier && attempt.metadata.responseVersion === input.expectedResponseVersion) {
|
|
129410
130577
|
const responses = prepareDiagnosticAssessmentResponses(assessment, attempt.metadata.responses, input);
|
|
129411
|
-
scoring = await this.scoreItem(
|
|
130578
|
+
scoring = await this.scoreItem({
|
|
130579
|
+
assessment,
|
|
130580
|
+
responses,
|
|
130581
|
+
itemIdentifier: input.itemIdentifier,
|
|
130582
|
+
artifactIdentity,
|
|
130583
|
+
purpose: attempt.metadata.purpose,
|
|
130584
|
+
preparedArtifact: scoringArtifact
|
|
130585
|
+
});
|
|
129412
130586
|
}
|
|
129413
130587
|
return {
|
|
129414
130588
|
responseVersion: attempt.metadata.responseVersion,
|
|
@@ -129419,39 +130593,27 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
129419
130593
|
scoring
|
|
129420
130594
|
};
|
|
129421
130595
|
}
|
|
129422
|
-
async scoreItems(
|
|
129423
|
-
|
|
129424
|
-
|
|
129425
|
-
|
|
129426
|
-
const
|
|
129427
|
-
|
|
129428
|
-
|
|
129429
|
-
|
|
129430
|
-
|
|
129431
|
-
|
|
129432
|
-
const scoresByItem = scoringItems.map(() => []);
|
|
129433
|
-
const verdictsByItem = scoringItems.map(() => []);
|
|
129434
|
-
scoringTasks.forEach((task, taskIndex) => {
|
|
129435
|
-
const processed = interactionResults[taskIndex];
|
|
129436
|
-
scoresByItem[task.itemIndex].push(Number.isFinite(processed.score) ? processed.score : 0);
|
|
129437
|
-
verdictsByItem[task.itemIndex].push(processed.isCorrect);
|
|
129438
|
-
});
|
|
129439
|
-
return scoringItems.map((item, itemIndex) => {
|
|
129440
|
-
const earned = assessmentItemEarnedScore(scoresByItem[itemIndex], item.maxScore);
|
|
129441
|
-
const possible = item.maxScore;
|
|
129442
|
-
return {
|
|
129443
|
-
itemIdentifier: item.itemIdentifier,
|
|
129444
|
-
score: {
|
|
129445
|
-
earned,
|
|
129446
|
-
possible,
|
|
129447
|
-
normalized: possible === 0 ? 0 : earned / possible
|
|
129448
|
-
},
|
|
129449
|
-
isCorrect: assessmentItemCorrectness(verdictsByItem[itemIndex])
|
|
129450
|
-
};
|
|
130596
|
+
async scoreItems({
|
|
130597
|
+
preparedArtifact,
|
|
130598
|
+
...input
|
|
130599
|
+
}) {
|
|
130600
|
+
const artifact = preparedArtifact === undefined ? await this.artifactLoader.loadMatchedScoring(input.assessment, input.artifactIdentity) : preparedArtifact;
|
|
130601
|
+
return scoreAssessmentItems({
|
|
130602
|
+
...input,
|
|
130603
|
+
artifact,
|
|
130604
|
+
processResponse: (identifier, request) => this.requireClient().qtiApi.assessmentItems.processResponse(identifier, request),
|
|
130605
|
+
onEvent: addEvent
|
|
129451
130606
|
});
|
|
129452
130607
|
}
|
|
129453
|
-
async scoreItem(
|
|
129454
|
-
const [scoring] = await this.scoreItems(
|
|
130608
|
+
async scoreItem(input) {
|
|
130609
|
+
const [scoring] = await this.scoreItems({
|
|
130610
|
+
assessment: input.assessment,
|
|
130611
|
+
responses: input.responses,
|
|
130612
|
+
artifactIdentity: input.artifactIdentity,
|
|
130613
|
+
purpose: input.purpose,
|
|
130614
|
+
itemIdentifiers: new Set([input.itemIdentifier]),
|
|
130615
|
+
preparedArtifact: input.preparedArtifact
|
|
130616
|
+
});
|
|
129455
130617
|
return scoring;
|
|
129456
130618
|
}
|
|
129457
130619
|
async prepareItemSubmission(params, input) {
|
|
@@ -129468,7 +130630,8 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
129468
130630
|
}
|
|
129469
130631
|
async scoreItemSubmission(params, input) {
|
|
129470
130632
|
const attempt = await this.peekAttempt(params);
|
|
129471
|
-
|
|
130633
|
+
const prior = attempt.metadata.itemSubmissions.find((submission) => submission.submissionId === input.submissionId);
|
|
130634
|
+
if (prior || !this.isOpen(attempt.result)) {
|
|
129472
130635
|
return null;
|
|
129473
130636
|
}
|
|
129474
130637
|
const assessment = await this.loadAttemptAssessment(attempt.metadata);
|
|
@@ -129479,7 +130642,13 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
129479
130642
|
itemSubmissions: attempt.metadata.itemSubmissions,
|
|
129480
130643
|
assessment
|
|
129481
130644
|
}, input);
|
|
129482
|
-
const scoring = transition.action === "commit" ? await this.scoreItem(
|
|
130645
|
+
const scoring = transition.action === "commit" ? await this.scoreItem({
|
|
130646
|
+
assessment,
|
|
130647
|
+
responses: transition.responses,
|
|
130648
|
+
itemIdentifier: input.itemIdentifier,
|
|
130649
|
+
artifactIdentity: assessmentArtifactIdentityFromAttempt(attempt.metadata),
|
|
130650
|
+
purpose: attempt.metadata.purpose
|
|
130651
|
+
}) : null;
|
|
129483
130652
|
return {
|
|
129484
130653
|
responseVersion: attempt.metadata.responseVersion,
|
|
129485
130654
|
submissionId: input.submissionId,
|
|
@@ -129491,8 +130660,12 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
129491
130660
|
async scoreSubmission(attempt) {
|
|
129492
130661
|
const assessment = await this.loadAttemptAssessment(attempt.metadata);
|
|
129493
130662
|
validateAssessmentResponses(assessment, attempt.metadata.responses);
|
|
129494
|
-
const
|
|
129495
|
-
|
|
130663
|
+
const itemResults = attempt.metadata.purpose === "review" ? committedReviewItemResults(attempt.metadata, assessment) : await this.scoreItems({
|
|
130664
|
+
assessment,
|
|
130665
|
+
responses: attempt.metadata.responses,
|
|
130666
|
+
artifactIdentity: assessmentArtifactIdentityFromAttempt(attempt.metadata),
|
|
130667
|
+
purpose: attempt.metadata.purpose
|
|
130668
|
+
});
|
|
129496
130669
|
return {
|
|
129497
130670
|
responseVersion: attempt.metadata.responseVersion,
|
|
129498
130671
|
assessment,
|
|
@@ -129501,7 +130674,10 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
129501
130674
|
correctQuestions: countCorrectQuestions(itemResults.map((result) => result.isCorrect))
|
|
129502
130675
|
};
|
|
129503
130676
|
}
|
|
129504
|
-
async peekAttempt({
|
|
130677
|
+
async peekAttempt({
|
|
130678
|
+
studentId,
|
|
130679
|
+
attemptId
|
|
130680
|
+
}) {
|
|
129505
130681
|
const result = await this.requireClient().api.oneroster.assessmentResults.get(attemptId).catch((error88) => {
|
|
129506
130682
|
if (isApiError(error88) && error88.statusCode === 404) {
|
|
129507
130683
|
throw this.unauthorizedAttempt(attemptId);
|
|
@@ -129546,21 +130722,19 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
129546
130722
|
await this.deps.validateDeveloperAccess(params.user, params.gameId);
|
|
129547
130723
|
const preview = await this.prepareSubmission(params, input);
|
|
129548
130724
|
const submission = await this.deps.assessmentRuntimeLock(this.deps.db, [assessmentRuntimeAttemptLockKey(params.attemptId)], async (db2) => {
|
|
129549
|
-
const attempt = await this.
|
|
129550
|
-
db: db2,
|
|
129551
|
-
developerAccessValidated: true
|
|
129552
|
-
});
|
|
130725
|
+
const { attempt: identity, enrollment } = await this.resolveAttemptAuthorization(params, { db: db2, developerAccessValidated: true });
|
|
129553
130726
|
const submissionDisposition = classifyAssessmentSubmission({
|
|
129554
|
-
inProgress:
|
|
129555
|
-
scoreStatus:
|
|
129556
|
-
submissionId:
|
|
130727
|
+
inProgress: identity.result.inProgress ?? "",
|
|
130728
|
+
scoreStatus: identity.result.scoreStatus,
|
|
130729
|
+
submissionId: identity.metadata.submissionId
|
|
129557
130730
|
}, input.submissionId);
|
|
129558
130731
|
if (submissionDisposition === "replay") {
|
|
129559
130732
|
return {
|
|
129560
|
-
response: this.submittedResult(
|
|
129561
|
-
emission: this.replayCompletion(
|
|
130733
|
+
response: this.submittedResult(identity.result, identity.metadata),
|
|
130734
|
+
emission: this.replayCompletion(identity, input.submissionId, params, game2)
|
|
129562
130735
|
};
|
|
129563
130736
|
}
|
|
130737
|
+
const attempt = this.requireActiveAttempt({ attempt: identity, enrollment }, params.attemptId);
|
|
129564
130738
|
if (submissionDisposition === "reject") {
|
|
129565
130739
|
throw AssessmentRuntimeError.from(assessmentAlreadySubmitted(attempt.result.sourcedId));
|
|
129566
130740
|
}
|
|
@@ -129588,12 +130762,9 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
129588
130762
|
const { assessment, itemResults, score, correctQuestions } = scored;
|
|
129589
130763
|
const client2 = this.requireClient();
|
|
129590
130764
|
const timestamp6 = new Date().toISOString();
|
|
129591
|
-
const reviewOutcomes = attempt.metadata.purpose === "review" ?
|
|
129592
|
-
result: attempt.result,
|
|
130765
|
+
const reviewOutcomes = attempt.metadata.purpose === "review" ? this.reviewItemOutcomes({
|
|
129593
130766
|
metadata: attempt.metadata,
|
|
129594
|
-
itemResults
|
|
129595
|
-
submissionId: input.submissionId,
|
|
129596
|
-
timestamp: timestamp6
|
|
130767
|
+
itemResults
|
|
129597
130768
|
}) : null;
|
|
129598
130769
|
const finalization = buildAssessmentResultSubmission({
|
|
129599
130770
|
result: attempt.result,
|
|
@@ -129656,9 +130827,11 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
129656
130827
|
...submission.emission.attempt,
|
|
129657
130828
|
metadata: submission.emission.attempt.metadata
|
|
129658
130829
|
} : null;
|
|
130830
|
+
const { result, metadata: metadata2 } = submission.emission.attempt;
|
|
129659
130831
|
await Promise.allSettled([
|
|
129660
130832
|
this.emitSessionBestEffort(submission.emission),
|
|
129661
|
-
...diagnosticCompletion ? [this.restoreDiagnosticChildResults(diagnosticCompletion)] : []
|
|
130833
|
+
...diagnosticCompletion ? [this.restoreDiagnosticChildResults(diagnosticCompletion)] : [],
|
|
130834
|
+
...metadata2.purpose === "review" ? [this.restoreAllCompletedReviewChildResponses(result, metadata2)] : []
|
|
129662
130835
|
]);
|
|
129663
130836
|
}
|
|
129664
130837
|
return submission.response;
|
|
@@ -129808,17 +130981,7 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
129808
130981
|
async finalizeDiagnosticSubmission(input) {
|
|
129809
130982
|
const { attempt } = input;
|
|
129810
130983
|
const routing = await this.loadAttemptDiagnosticManifest(attempt.metadata, input.db);
|
|
129811
|
-
const
|
|
129812
|
-
const tracks = diagnosticTrackResults(state);
|
|
129813
|
-
if (!tracks) {
|
|
129814
|
-
throw AssessmentRuntimeError.from(assessmentFlowViolation("Diagnostic routing must be ready to complete before final submit.", { routingStatus: state.status }));
|
|
129815
|
-
}
|
|
129816
|
-
if (attempt.metadata.itemSubmissions.length === 0) {
|
|
129817
|
-
throw AssessmentRuntimeError.from(assessmentFlowViolation("A diagnostic cannot be finalized without an administered item."));
|
|
129818
|
-
}
|
|
129819
|
-
const possible = attempt.metadata.itemSubmissions.reduce((total, submission) => total + submission.score.possible, 0);
|
|
129820
|
-
const score = aggregateAssessmentScore(attempt.metadata.itemSubmissions.map((submission) => submission.score.earned), possible);
|
|
129821
|
-
const correctQuestions = countCorrectQuestions(attempt.metadata.itemSubmissions.map((submission) => submission.isCorrect));
|
|
130984
|
+
const { tracks, score, correctQuestions } = summarizeDiagnosticSubmission(attempt.metadata, routing.manifest);
|
|
129822
130985
|
const timestamp6 = new Date().toISOString();
|
|
129823
130986
|
const finalization = buildAssessmentResultSubmission({
|
|
129824
130987
|
result: attempt.result,
|
|
@@ -129906,10 +131069,13 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
129906
131069
|
}) : undefined;
|
|
129907
131070
|
const diagnostic = definition?.diagnosticKey ? await this.initializeHostedDiagnostic(definition, loaded.assessment) : null;
|
|
129908
131071
|
const reviewBank = purpose === "review" ? await buildReviewBankIndex(loaded.assessment, loaded.questions, { customOnly: true }) : null;
|
|
131072
|
+
const scoring = compileQtiScoringArtifact(loaded.questions);
|
|
131073
|
+
const fullyPlatformScored = scoring.items.every((item) => item.strategy === "platform");
|
|
129909
131074
|
return {
|
|
129910
131075
|
...test,
|
|
129911
131076
|
live: true,
|
|
129912
131077
|
assessment: loaded.assessment,
|
|
131078
|
+
...fullyPlatformScored ? { scoring } : {},
|
|
129913
131079
|
...diagnostic ? {
|
|
129914
131080
|
diagnostic: {
|
|
129915
131081
|
diagnosticKey: diagnostic.definition.diagnosticKey,
|
|
@@ -130254,21 +131420,31 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
130254
131420
|
if (resumed.metadata.purpose !== "review") {
|
|
130255
131421
|
throw new Error("A standards-review request selected a non-review attempt");
|
|
130256
131422
|
}
|
|
130257
|
-
|
|
130258
|
-
|
|
130259
|
-
|
|
130260
|
-
|
|
130261
|
-
|
|
130262
|
-
|
|
130263
|
-
|
|
130264
|
-
|
|
130265
|
-
|
|
130266
|
-
|
|
130267
|
-
|
|
130268
|
-
|
|
130269
|
-
|
|
131423
|
+
const reviewMetadata = resumed.metadata;
|
|
131424
|
+
let assessment;
|
|
131425
|
+
const current = await reconcileReviewChildResults({
|
|
131426
|
+
initial: resumed,
|
|
131427
|
+
writeOpen: async () => {
|
|
131428
|
+
const source = await this.loadPinnedReviewBankSource(reviewMetadata);
|
|
131429
|
+
assessment = projectReviewAssessment(source.assessment, source.bank, reviewMetadata.review.selections);
|
|
131430
|
+
await this.ensureReviewChildResults({
|
|
131431
|
+
result: resumed.result,
|
|
131432
|
+
metadata: reviewMetadata,
|
|
131433
|
+
integration: context2.integration,
|
|
131434
|
+
source,
|
|
131435
|
+
assessment,
|
|
131436
|
+
existingChildResults,
|
|
131437
|
+
lookupMissingResults: true
|
|
131438
|
+
});
|
|
131439
|
+
},
|
|
131440
|
+
crossBarrier: () => crossAssessmentAttemptLockBarrier(this.deps.assessmentRuntimeLock, this.deps.db, resumed.result.sourcedId),
|
|
131441
|
+
readLatest: () => this.peekAttempt({
|
|
131442
|
+
studentId: resumed.result.student.sourcedId,
|
|
131443
|
+
attemptId: resumed.result.sourcedId
|
|
131444
|
+
}),
|
|
131445
|
+
restoreTerminal: (latest) => this.restoreAllCompletedReviewChildResponses(latest.result, latest.metadata)
|
|
130270
131446
|
});
|
|
130271
|
-
return this.snapshot(
|
|
131447
|
+
return assessmentChildProjectionIsTerminal(current.result) ? this.snapshotForResult(current.result, current.metadata, context2.integration) : this.snapshot(current.result, current.metadata, assessment, context2.integration);
|
|
130272
131448
|
}
|
|
130273
131449
|
recordReviewPreparationPhase(phase, startedAt, counts = {}) {
|
|
130274
131450
|
addEvent("assessment.review_preparation_phase", {
|
|
@@ -130490,20 +131666,9 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
130490
131666
|
}
|
|
130491
131667
|
async initializeHostedDiagnostic(definition, assessment) {
|
|
130492
131668
|
const initialized = await this.initializeHostedDiagnosticManifest(definition);
|
|
130493
|
-
|
|
131669
|
+
assertDiagnosticItemsAvailable(definition.id, initialized.manifest, assessment);
|
|
130494
131670
|
return initialized;
|
|
130495
131671
|
}
|
|
130496
|
-
assertDiagnosticItemsAvailable(definition, manifest, assessment) {
|
|
130497
|
-
const itemIdentifiers = new Set(assessment.items.map((item) => item.identifier));
|
|
130498
|
-
const unknownItem = manifest.nodes.find((node) => !itemIdentifiers.has(node.itemIdentifier));
|
|
130499
|
-
if (unknownItem) {
|
|
130500
|
-
throw new AssessmentRuntimeError("SELECTED_TEST_VERSION_UNAVAILABLE", `Diagnostic routing references unavailable item ${unknownItem.itemIdentifier}.`, {
|
|
130501
|
-
definitionId: definition.id,
|
|
130502
|
-
nodeKey: unknownItem.key,
|
|
130503
|
-
itemIdentifier: unknownItem.itemIdentifier
|
|
130504
|
-
});
|
|
130505
|
-
}
|
|
130506
|
-
}
|
|
130507
131672
|
async initializeHostedDiagnosticManifest(definition) {
|
|
130508
131673
|
const initialized = initializeDiagnosticRouting(definition.diagnosticRoutingManifest);
|
|
130509
131674
|
if (!initialized.ok) {
|
|
@@ -130527,21 +131692,32 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
130527
131692
|
}
|
|
130528
131693
|
return initialized;
|
|
130529
131694
|
}
|
|
130530
|
-
|
|
130531
|
-
const
|
|
130532
|
-
|
|
130533
|
-
|
|
130534
|
-
|
|
130535
|
-
|
|
130536
|
-
|
|
130537
|
-
|
|
130538
|
-
|
|
130539
|
-
|
|
130540
|
-
|
|
131695
|
+
async loadAttemptDiagnosticRouting(metadata2, assessment, db2 = this.deps.db, preparedArtifact) {
|
|
131696
|
+
const startedAt = Date.now();
|
|
131697
|
+
const artifactIdentity = assessmentArtifactIdentityFromAttempt(metadata2);
|
|
131698
|
+
const artifact = preparedArtifact === undefined ? await this.artifactLoader.loadMatchedScoring(assessment, artifactIdentity) : preparedArtifact;
|
|
131699
|
+
const compiled = await resolveCompiledDiagnosticRouting(metadata2, assessment, artifact);
|
|
131700
|
+
if (compiled) {
|
|
131701
|
+
addEvent("assessment.diagnostic_routing_load", {
|
|
131702
|
+
"app.assessment.diagnostic_definition_id": metadata2.diagnostic.definitionId,
|
|
131703
|
+
"app.assessment.routing_source": "platform-artifact",
|
|
131704
|
+
"app.assessment.routing_duration_ms": Date.now() - startedAt
|
|
131705
|
+
});
|
|
131706
|
+
return compiled;
|
|
131707
|
+
}
|
|
131708
|
+
addEvent("assessment.diagnostic_routing_fallback", {
|
|
131709
|
+
"app.assessment.diagnostic_definition_id": metadata2.diagnostic.definitionId,
|
|
131710
|
+
"app.assessment.fallback_reason": diagnosticRoutingFallbackReason(artifact, metadata2.diagnostic.routingRevision),
|
|
131711
|
+
"app.assessment.fallback_duration_ms": Date.now() - startedAt
|
|
131712
|
+
});
|
|
130541
131713
|
const pinned = await this.loadAttemptDiagnosticManifest(metadata2, db2);
|
|
130542
|
-
|
|
130543
|
-
const state =
|
|
130544
|
-
return {
|
|
131714
|
+
assertDiagnosticItemsAvailable(pinned.definition.id, pinned.manifest, assessment);
|
|
131715
|
+
const state = replayAttemptDiagnosticRouting(metadata2, pinned.manifest);
|
|
131716
|
+
return {
|
|
131717
|
+
manifest: pinned.manifest,
|
|
131718
|
+
routingRevision: pinned.routingRevision,
|
|
131719
|
+
state
|
|
131720
|
+
};
|
|
130545
131721
|
}
|
|
130546
131722
|
async loadAssessmentSource(identifier, expectedRevision) {
|
|
130547
131723
|
const client2 = this.requireClient();
|
|
@@ -130676,11 +131852,167 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
130676
131852
|
}
|
|
130677
131853
|
return lineItemId;
|
|
130678
131854
|
}
|
|
131855
|
+
assessmentChildAttempt(attempt) {
|
|
131856
|
+
const { metadata: metadata2 } = attempt;
|
|
131857
|
+
const assessmentKey = metadata2.selectedTest.assessmentKey ?? assessmentKeyFromManagedQtiIdentifier(metadata2.selectedTest.identifier);
|
|
131858
|
+
if (!assessmentKey) {
|
|
131859
|
+
addEvent("assessment.child_result_legacy_attempt_skipped", {
|
|
131860
|
+
"app.assessment.attempt_id": attempt.result.sourcedId
|
|
131861
|
+
});
|
|
131862
|
+
return null;
|
|
131863
|
+
}
|
|
131864
|
+
return {
|
|
131865
|
+
attemptId: attempt.result.sourcedId,
|
|
131866
|
+
gameId: attempt.integration.gameId,
|
|
131867
|
+
studentId: attempt.result.student.sourcedId,
|
|
131868
|
+
activityId: metadata2.activityId,
|
|
131869
|
+
courseId: metadata2.courseId,
|
|
131870
|
+
integrationId: metadata2.integrationId,
|
|
131871
|
+
enrollmentId: metadata2.enrollmentId,
|
|
131872
|
+
selectedTest: {
|
|
131873
|
+
assessmentKey,
|
|
131874
|
+
identifier: metadata2.selectedTest.identifier,
|
|
131875
|
+
contentRevision: metadata2.selectedTest.contentRevision
|
|
131876
|
+
}
|
|
131877
|
+
};
|
|
131878
|
+
}
|
|
131879
|
+
reviewChildResultContext(metadata2, submission) {
|
|
131880
|
+
const selection = metadata2.review.selections.find((candidate) => candidate.itemIdentifier === submission.itemIdentifier);
|
|
131881
|
+
if (!selection) {
|
|
131882
|
+
throw new Error(`Committed review item ${submission.itemIdentifier} is not in its pinned selection`);
|
|
131883
|
+
}
|
|
131884
|
+
return {
|
|
131885
|
+
kind: "review",
|
|
131886
|
+
bankRevision: metadata2.review.bankRevision,
|
|
131887
|
+
standard: selection.standard
|
|
131888
|
+
};
|
|
131889
|
+
}
|
|
131890
|
+
prepareReviewAssessmentChildResult(input) {
|
|
131891
|
+
return this.prepareAssessmentChildResult({
|
|
131892
|
+
attempt: input.attempt,
|
|
131893
|
+
metadata: input.metadata,
|
|
131894
|
+
submission: input.submission,
|
|
131895
|
+
context: () => this.reviewChildResultContext(input.metadata, input.submission)
|
|
131896
|
+
});
|
|
131897
|
+
}
|
|
131898
|
+
prepareDiagnosticAssessmentChildResult(input) {
|
|
131899
|
+
return this.prepareAssessmentChildResult({
|
|
131900
|
+
attempt: input.attempt,
|
|
131901
|
+
metadata: input.metadata,
|
|
131902
|
+
submission: input.submission,
|
|
131903
|
+
context: () => ({
|
|
131904
|
+
kind: "platform-routed-diagnostic",
|
|
131905
|
+
definitionId: input.metadata.diagnostic.definitionId,
|
|
131906
|
+
diagnosticKey: input.metadata.diagnostic.diagnosticKey,
|
|
131907
|
+
routingRevision: input.metadata.diagnostic.routingRevision,
|
|
131908
|
+
transition: input.transition
|
|
131909
|
+
})
|
|
131910
|
+
});
|
|
131911
|
+
}
|
|
131912
|
+
async prepareAssessmentChildResult(input) {
|
|
131913
|
+
try {
|
|
131914
|
+
const attemptContext = this.assessmentChildAttempt({
|
|
131915
|
+
...input.attempt,
|
|
131916
|
+
metadata: input.metadata
|
|
131917
|
+
});
|
|
131918
|
+
if (!attemptContext) {
|
|
131919
|
+
return null;
|
|
131920
|
+
}
|
|
131921
|
+
const parentLineItemId = input.attempt.result.assessmentLineItem.sourcedId;
|
|
131922
|
+
const { itemIdentifier } = input.submission;
|
|
131923
|
+
const context2 = input.context();
|
|
131924
|
+
const [command, childLineItemId] = await Promise.all([
|
|
131925
|
+
buildSubmissionChildResultCommand({
|
|
131926
|
+
attempt: attemptContext,
|
|
131927
|
+
submission: input.submission,
|
|
131928
|
+
responses: input.metadata.responses[itemIdentifier],
|
|
131929
|
+
grading: input.submission.grading ?? QTI_CHILD_GRADING,
|
|
131930
|
+
context: context2
|
|
131931
|
+
}),
|
|
131932
|
+
context2.kind === "review" ? reviewQuestionLineItemId(parentLineItemId, itemIdentifier) : diagnosticQuestionLineItemId(parentLineItemId, itemIdentifier)
|
|
131933
|
+
]);
|
|
131934
|
+
return { command, childLineItemId };
|
|
131935
|
+
} catch (error88) {
|
|
131936
|
+
addEvent("assessment.child_result_prepare_failed", {
|
|
131937
|
+
"app.assessment.attempt_id": input.attempt.result.sourcedId,
|
|
131938
|
+
"app.assessment.submission_id": input.submission.submissionId,
|
|
131939
|
+
"app.assessment.qti_item_identifier": input.submission.itemIdentifier,
|
|
131940
|
+
"exception.type": errorType(error88),
|
|
131941
|
+
"app.error.message": errorMessage2(error88)
|
|
131942
|
+
});
|
|
131943
|
+
return null;
|
|
131944
|
+
}
|
|
131945
|
+
}
|
|
131946
|
+
async projectPreparedAssessmentChildResult(prepared) {
|
|
131947
|
+
if (!prepared) {
|
|
131948
|
+
return;
|
|
131949
|
+
}
|
|
131950
|
+
const startedAt = Date.now();
|
|
131951
|
+
const identity = {
|
|
131952
|
+
"app.assessment.attempt_id": prepared.command.payload.attempt.attemptId,
|
|
131953
|
+
"app.assessment.submission_id": prepared.command.payload.administration.submissionId
|
|
131954
|
+
};
|
|
131955
|
+
try {
|
|
131956
|
+
const disposition = await persistAssessmentChildResult({
|
|
131957
|
+
assessmentResults: this.requireClient().api.oneroster.assessmentResults,
|
|
131958
|
+
command: prepared.command,
|
|
131959
|
+
childLineItemId: prepared.childLineItemId
|
|
131960
|
+
});
|
|
131961
|
+
if (disposition.outcome === "conflict") {
|
|
131962
|
+
addEvent("assessment.child_result_conflict", {
|
|
131963
|
+
...identity,
|
|
131964
|
+
"app.assessment.child_result_id": disposition.resultId,
|
|
131965
|
+
"app.assessment.child_result_conflict_reason": disposition.reason
|
|
131966
|
+
});
|
|
131967
|
+
return;
|
|
131968
|
+
}
|
|
131969
|
+
addEvent("assessment.child_result_persisted", {
|
|
131970
|
+
...identity,
|
|
131971
|
+
"app.assessment.child_result_id": disposition.resultId,
|
|
131972
|
+
"app.assessment.child_result_outcome": disposition.outcome,
|
|
131973
|
+
"app.assessment.duration_ms": Date.now() - startedAt
|
|
131974
|
+
});
|
|
131975
|
+
} catch (error88) {
|
|
131976
|
+
addEvent("assessment.child_result_persist_failed", {
|
|
131977
|
+
...identity,
|
|
131978
|
+
"app.assessment.duration_ms": Date.now() - startedAt,
|
|
131979
|
+
"exception.type": errorType(error88),
|
|
131980
|
+
"app.error.message": errorMessage2(error88)
|
|
131981
|
+
});
|
|
131982
|
+
}
|
|
131983
|
+
}
|
|
131984
|
+
assertAssessmentChildLineItem(lineItemId, stored, expected) {
|
|
131985
|
+
if (assessmentChildLineItemMatches(stored, expected)) {
|
|
131986
|
+
return;
|
|
131987
|
+
}
|
|
131988
|
+
addEvent("assessment.question_line_item_conflict", {
|
|
131989
|
+
"app.assessment.line_item_id": lineItemId
|
|
131990
|
+
});
|
|
131991
|
+
throw AssessmentRuntimeError.from(assessmentFlowViolation("A deterministic assessment question line item is occupied by incompatible data.", { lineItemId }));
|
|
131992
|
+
}
|
|
130679
131993
|
async ensureDiagnosticQuestionLineItem(input) {
|
|
130680
131994
|
const client2 = this.requireClient();
|
|
130681
131995
|
const lineItemId = await diagnosticQuestionLineItemId(input.parentLineItemId, input.item.identifier);
|
|
131996
|
+
const expected = {
|
|
131997
|
+
status: ONEROSTER_STATUS2.active,
|
|
131998
|
+
parentAssessmentLineItem: { sourcedId: input.parentLineItemId },
|
|
131999
|
+
course: { sourcedId: input.integration.courseId },
|
|
132000
|
+
resultValueMin: 0,
|
|
132001
|
+
resultValueMax: input.item.maxScore,
|
|
132002
|
+
metadata: {
|
|
132003
|
+
playcademyDiagnosticQuestionDefinition: {
|
|
132004
|
+
version: 1,
|
|
132005
|
+
definitionId: input.metadata.diagnostic.definitionId,
|
|
132006
|
+
diagnosticKey: input.metadata.diagnostic.diagnosticKey,
|
|
132007
|
+
routingRevision: input.metadata.diagnostic.routingRevision,
|
|
132008
|
+
contentRevision: input.metadata.selectedTest.contentRevision,
|
|
132009
|
+
itemIdentifier: input.item.identifier
|
|
132010
|
+
}
|
|
132011
|
+
}
|
|
132012
|
+
};
|
|
130682
132013
|
try {
|
|
130683
|
-
await client2.api.oneroster.assessmentLineItems.get(lineItemId);
|
|
132014
|
+
const stored = await client2.api.oneroster.assessmentLineItems.get(lineItemId);
|
|
132015
|
+
this.assertAssessmentChildLineItem(lineItemId, stored, expected);
|
|
130684
132016
|
return lineItemId;
|
|
130685
132017
|
} catch (error88) {
|
|
130686
132018
|
if (!isApiError(error88) || error88.statusCode !== 404) {
|
|
@@ -130690,35 +132022,23 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
130690
132022
|
try {
|
|
130691
132023
|
await client2.api.oneroster.assessmentLineItems.create({
|
|
130692
132024
|
sourcedId: lineItemId,
|
|
130693
|
-
status: ONEROSTER_STATUS2.active,
|
|
130694
132025
|
title: input.item.title,
|
|
130695
132026
|
description: "adaptive diagnostic question",
|
|
130696
|
-
|
|
130697
|
-
course: { sourcedId: input.integration.courseId },
|
|
130698
|
-
resultValueMin: 0,
|
|
130699
|
-
resultValueMax: input.item.maxScore,
|
|
130700
|
-
metadata: {
|
|
130701
|
-
playcademyDiagnosticQuestionDefinition: {
|
|
130702
|
-
version: 1,
|
|
130703
|
-
definitionId: input.metadata.diagnostic.definitionId,
|
|
130704
|
-
diagnosticKey: input.metadata.diagnostic.diagnosticKey,
|
|
130705
|
-
routingRevision: input.metadata.diagnostic.routingRevision,
|
|
130706
|
-
contentRevision: input.metadata.selectedTest.contentRevision,
|
|
130707
|
-
itemIdentifier: input.item.identifier
|
|
130708
|
-
}
|
|
130709
|
-
}
|
|
132027
|
+
...expected
|
|
130710
132028
|
});
|
|
130711
132029
|
} catch (error88) {
|
|
130712
132030
|
if (!isApiError(error88) || error88.statusCode !== 409) {
|
|
130713
132031
|
throw error88;
|
|
130714
132032
|
}
|
|
132033
|
+
const stored = await client2.api.oneroster.assessmentLineItems.get(lineItemId);
|
|
132034
|
+
this.assertAssessmentChildLineItem(lineItemId, stored, expected);
|
|
130715
132035
|
addEvent("assessment.diagnostic_question_line_item_create_conflict", {
|
|
130716
132036
|
"app.assessment.line_item_id": lineItemId
|
|
130717
132037
|
});
|
|
130718
132038
|
}
|
|
130719
132039
|
return lineItemId;
|
|
130720
132040
|
}
|
|
130721
|
-
async projectDiagnosticItemResponse(projection, assessment) {
|
|
132041
|
+
async projectDiagnosticItemResponse(projection, assessment, checkpoint = null) {
|
|
130722
132042
|
try {
|
|
130723
132043
|
const loadedAssessment = assessment ?? await this.loadAttemptAssessment(projection.metadata);
|
|
130724
132044
|
const item = loadedAssessment.items.find((candidate) => candidate.identifier === projection.ledgerEntry.itemIdentifier);
|
|
@@ -130731,6 +132051,9 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
130731
132051
|
metadata: projection.metadata,
|
|
130732
132052
|
item
|
|
130733
132053
|
});
|
|
132054
|
+
if (checkpoint && checkpoint.childLineItemId !== childLineItemId) {
|
|
132055
|
+
throw new Error("Prepared diagnostic checkpoint resolved a different line item");
|
|
132056
|
+
}
|
|
130734
132057
|
const childResultId = await diagnosticQuestionResultId(projection.result.sourcedId, childLineItemId);
|
|
130735
132058
|
const finalized = buildFinalizedDiagnosticChildResult({
|
|
130736
132059
|
childLineItemId,
|
|
@@ -130739,7 +132062,10 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
130739
132062
|
metadata: projection.metadata,
|
|
130740
132063
|
ledgerEntry: projection.ledgerEntry
|
|
130741
132064
|
});
|
|
130742
|
-
await
|
|
132065
|
+
await Promise.all([
|
|
132066
|
+
this.projectPreparedAssessmentChildResult(checkpoint),
|
|
132067
|
+
this.requireClient().api.oneroster.assessmentResults.upsert(childResultId, finalized.resultUpdate)
|
|
132068
|
+
]);
|
|
130743
132069
|
} catch (error88) {
|
|
130744
132070
|
addEvent("assessment.diagnostic_child_projection_failed", {
|
|
130745
132071
|
"app.assessment.attempt_id": projection.result.sourcedId,
|
|
@@ -130800,41 +132126,43 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
130800
132126
|
async ensureReviewQuestionLineItem(input) {
|
|
130801
132127
|
const client2 = this.requireClient();
|
|
130802
132128
|
const lineItemId = await reviewQuestionLineItemId(input.parentLineItemId, input.item.identifier);
|
|
132129
|
+
const expected = {
|
|
132130
|
+
status: ONEROSTER_STATUS2.active,
|
|
132131
|
+
parentAssessmentLineItem: { sourcedId: input.parentLineItemId },
|
|
132132
|
+
course: { sourcedId: input.integration.courseId },
|
|
132133
|
+
resultValueMin: 0,
|
|
132134
|
+
resultValueMax: input.item.maxScore,
|
|
132135
|
+
metadata: {
|
|
132136
|
+
playcademyReviewQuestionDefinition: {
|
|
132137
|
+
version: 1,
|
|
132138
|
+
bankRevision: input.bankRevision,
|
|
132139
|
+
itemIdentifier: input.item.identifier,
|
|
132140
|
+
standards: input.standards
|
|
132141
|
+
}
|
|
132142
|
+
}
|
|
132143
|
+
};
|
|
130803
132144
|
try {
|
|
130804
|
-
await client2.api.oneroster.assessmentLineItems.get(lineItemId);
|
|
132145
|
+
const stored = await client2.api.oneroster.assessmentLineItems.get(lineItemId);
|
|
132146
|
+
this.assertAssessmentChildLineItem(lineItemId, stored, expected);
|
|
130805
132147
|
return lineItemId;
|
|
130806
132148
|
} catch (error88) {
|
|
130807
132149
|
if (!isApiError(error88) || error88.statusCode !== 404) {
|
|
130808
132150
|
throw error88;
|
|
130809
132151
|
}
|
|
130810
132152
|
}
|
|
130811
|
-
const indexed = input.bank.items.find((candidate) => candidate.itemIdentifier === input.item.identifier);
|
|
130812
|
-
if (!indexed) {
|
|
130813
|
-
throw new Error(`Review-bank index is missing item ${input.item.identifier}`);
|
|
130814
|
-
}
|
|
130815
132153
|
try {
|
|
130816
132154
|
await client2.api.oneroster.assessmentLineItems.create({
|
|
130817
132155
|
sourcedId: lineItemId,
|
|
130818
|
-
status: ONEROSTER_STATUS2.active,
|
|
130819
132156
|
title: input.item.title,
|
|
130820
132157
|
description: "standards review question",
|
|
130821
|
-
|
|
130822
|
-
course: { sourcedId: input.integration.courseId },
|
|
130823
|
-
resultValueMin: 0,
|
|
130824
|
-
resultValueMax: input.item.maxScore,
|
|
130825
|
-
metadata: {
|
|
130826
|
-
playcademyReviewQuestionDefinition: {
|
|
130827
|
-
version: 1,
|
|
130828
|
-
bankRevision: input.bank.bankRevision,
|
|
130829
|
-
itemIdentifier: input.item.identifier,
|
|
130830
|
-
standards: indexed.standards
|
|
130831
|
-
}
|
|
130832
|
-
}
|
|
132158
|
+
...expected
|
|
130833
132159
|
});
|
|
130834
132160
|
} catch (error88) {
|
|
130835
132161
|
if (!isApiError(error88) || error88.statusCode !== 409) {
|
|
130836
132162
|
throw error88;
|
|
130837
132163
|
}
|
|
132164
|
+
const stored = await client2.api.oneroster.assessmentLineItems.get(lineItemId);
|
|
132165
|
+
this.assertAssessmentChildLineItem(lineItemId, stored, expected);
|
|
130838
132166
|
addEvent("assessment.review_question_line_item_create_conflict", {
|
|
130839
132167
|
"app.assessment.line_item_id": lineItemId
|
|
130840
132168
|
});
|
|
@@ -130855,6 +132183,7 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
130855
132183
|
}
|
|
130856
132184
|
async ensureReviewChildResults(input) {
|
|
130857
132185
|
const itemByIdentifier = new Map(input.assessment.items.map((item) => [item.identifier, item]));
|
|
132186
|
+
const bankItemByIdentifier = new Map(input.source.bank.items.map((item) => [item.itemIdentifier, item]));
|
|
130858
132187
|
const physicalItems = new Set(input.metadata.review.selections.map((selection) => selection.itemIdentifier));
|
|
130859
132188
|
if (physicalItems.size !== input.metadata.review.selections.length) {
|
|
130860
132189
|
throw new Error("MVP review selection must use each physical item at most once");
|
|
@@ -130862,19 +132191,20 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
130862
132191
|
const administeredItems = administeredItemIdentifiers(input.metadata.itemSubmissions);
|
|
130863
132192
|
await runWithConcurrency(input.metadata.review.selections, TimebackAssessmentRuntimeService2.SCORING_CONCURRENCY, async (selection) => {
|
|
130864
132193
|
const item = itemByIdentifier.get(selection.itemIdentifier);
|
|
130865
|
-
|
|
130866
|
-
|
|
132194
|
+
const indexed = bankItemByIdentifier.get(selection.itemIdentifier);
|
|
132195
|
+
if (!item || !indexed) {
|
|
132196
|
+
throw new Error(`Review assessment or bank is missing selected item ${selection.itemIdentifier}`);
|
|
130867
132197
|
}
|
|
132198
|
+
const childLineItemId = await this.ensureReviewQuestionLineItem({
|
|
132199
|
+
parentLineItemId: input.result.assessmentLineItem.sourcedId,
|
|
132200
|
+
integration: input.integration,
|
|
132201
|
+
bankRevision: input.metadata.review.bankRevision,
|
|
132202
|
+
standards: [...indexed.standards],
|
|
132203
|
+
item
|
|
132204
|
+
});
|
|
130868
132205
|
if (!administeredItems.has(selection.itemIdentifier)) {
|
|
130869
|
-
await this.ensureReviewQuestionLineItem({
|
|
130870
|
-
parentLineItemId: input.result.assessmentLineItem.sourcedId,
|
|
130871
|
-
integration: input.integration,
|
|
130872
|
-
bank: input.source.bank,
|
|
130873
|
-
item
|
|
130874
|
-
});
|
|
130875
132206
|
return;
|
|
130876
132207
|
}
|
|
130877
|
-
const childLineItemId = await reviewQuestionLineItemId(input.result.assessmentLineItem.sourcedId, item.identifier);
|
|
130878
132208
|
const childResultId = await reviewQuestionResultId(input.result.sourcedId, childLineItemId);
|
|
130879
132209
|
const streamedChildResult = input.existingChildResults.get(childResultId);
|
|
130880
132210
|
let childResult = streamedChildResult;
|
|
@@ -130897,12 +132227,6 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
130897
132227
|
if (resolution.action === "reuse") {
|
|
130898
132228
|
return;
|
|
130899
132229
|
}
|
|
130900
|
-
await this.ensureReviewQuestionLineItem({
|
|
130901
|
-
parentLineItemId: input.result.assessmentLineItem.sourcedId,
|
|
130902
|
-
integration: input.integration,
|
|
130903
|
-
bank: input.source.bank,
|
|
130904
|
-
item
|
|
130905
|
-
});
|
|
130906
132230
|
if (resolution.action === "restore") {
|
|
130907
132231
|
addEvent("assessment.review_child_result_restore", {
|
|
130908
132232
|
"app.assessment.review_child_restore_reason": resolution.reason,
|
|
@@ -130920,24 +132244,43 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
130920
132244
|
});
|
|
130921
132245
|
});
|
|
130922
132246
|
}
|
|
130923
|
-
async putReviewChildResponse(
|
|
132247
|
+
async putReviewChildResponse(projection, checkpoint) {
|
|
132248
|
+
const { result, metadata: metadata2, itemIdentifier, integration } = projection;
|
|
130924
132249
|
const selection = metadata2.review.selections.find((candidate) => candidate.itemIdentifier === itemIdentifier);
|
|
130925
132250
|
if (!selection) {
|
|
130926
132251
|
return;
|
|
130927
132252
|
}
|
|
130928
132253
|
try {
|
|
130929
|
-
const
|
|
132254
|
+
const source = await this.loadPinnedReviewBankSource(metadata2);
|
|
132255
|
+
const item = source.assessment.items.find((candidate) => candidate.identifier === selection.itemIdentifier);
|
|
132256
|
+
const indexed = source.bank.items.find((candidate) => candidate.itemIdentifier === selection.itemIdentifier);
|
|
132257
|
+
if (!item || !indexed) {
|
|
132258
|
+
throw new Error(`Review assessment or bank is missing selected item ${selection.itemIdentifier}`);
|
|
132259
|
+
}
|
|
132260
|
+
const childLineItemId = await this.ensureReviewQuestionLineItem({
|
|
132261
|
+
parentLineItemId: result.assessmentLineItem.sourcedId,
|
|
132262
|
+
integration,
|
|
132263
|
+
bankRevision: metadata2.review.bankRevision,
|
|
132264
|
+
standards: [...indexed.standards],
|
|
132265
|
+
item
|
|
132266
|
+
});
|
|
132267
|
+
if (checkpoint && checkpoint.childLineItemId !== childLineItemId) {
|
|
132268
|
+
throw new Error("Prepared review checkpoint resolved a different line item");
|
|
132269
|
+
}
|
|
130930
132270
|
const childResultId = await reviewQuestionResultId(result.sourcedId, childLineItemId);
|
|
130931
132271
|
const childMetadata = buildReviewItemResultMetadata({
|
|
130932
132272
|
metadata: metadata2,
|
|
130933
132273
|
selection,
|
|
130934
132274
|
parentAttemptId: result.sourcedId
|
|
130935
132275
|
});
|
|
130936
|
-
await
|
|
130937
|
-
|
|
130938
|
-
|
|
130939
|
-
|
|
130940
|
-
|
|
132276
|
+
await Promise.all([
|
|
132277
|
+
this.projectPreparedAssessmentChildResult(checkpoint),
|
|
132278
|
+
this.requireClient().api.oneroster.assessmentResults.upsert(childResultId, buildOpenReviewChildResult({
|
|
132279
|
+
childLineItemId,
|
|
132280
|
+
student: result.student,
|
|
132281
|
+
metadata: childMetadata
|
|
132282
|
+
}))
|
|
132283
|
+
]);
|
|
130941
132284
|
} catch (error88) {
|
|
130942
132285
|
addEvent("assessment.review_child_response_projection_failed", {
|
|
130943
132286
|
"app.assessment.attempt_id": result.sourcedId,
|
|
@@ -130947,14 +132290,26 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
130947
132290
|
});
|
|
130948
132291
|
}
|
|
130949
132292
|
}
|
|
130950
|
-
async projectReviewItemResponse(params, projection) {
|
|
130951
|
-
|
|
132293
|
+
async projectReviewItemResponse(params, projection, checkpoint) {
|
|
132294
|
+
const terminal = assessmentChildProjectionIsTerminal(projection.result);
|
|
130952
132295
|
try {
|
|
130953
|
-
await
|
|
130954
|
-
|
|
130955
|
-
|
|
130956
|
-
|
|
130957
|
-
|
|
132296
|
+
await reconcileReviewChildResults({
|
|
132297
|
+
initial: projection,
|
|
132298
|
+
writeOpen: () => this.putReviewChildResponse(projection, checkpoint),
|
|
132299
|
+
crossBarrier: () => crossAssessmentAttemptLockBarrier(this.deps.assessmentRuntimeLock, this.deps.db, params.attemptId),
|
|
132300
|
+
readLatest: () => this.peekAttempt(params),
|
|
132301
|
+
restoreTerminal: async (latest) => {
|
|
132302
|
+
const repair = this.restoreCompletedReviewChildResponses(latest.result, latest.metadata, new Set([projection.itemIdentifier]));
|
|
132303
|
+
if (terminal) {
|
|
132304
|
+
await Promise.allSettled([
|
|
132305
|
+
this.projectPreparedAssessmentChildResult(checkpoint),
|
|
132306
|
+
repair
|
|
132307
|
+
]);
|
|
132308
|
+
} else {
|
|
132309
|
+
await repair;
|
|
132310
|
+
}
|
|
132311
|
+
}
|
|
132312
|
+
});
|
|
130958
132313
|
} catch (error88) {
|
|
130959
132314
|
addEvent("assessment.review_child_projection_barrier_failed", {
|
|
130960
132315
|
"app.assessment.attempt_id": params.attemptId,
|
|
@@ -130964,6 +132319,9 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
130964
132319
|
});
|
|
130965
132320
|
}
|
|
130966
132321
|
}
|
|
132322
|
+
restoreAllCompletedReviewChildResponses(result, metadata2) {
|
|
132323
|
+
return this.restoreCompletedReviewChildResponses(result, metadata2, new Set(metadata2.itemSubmissions.map((item) => item.itemIdentifier)));
|
|
132324
|
+
}
|
|
130967
132325
|
async restoreCompletedReviewChildResponses(result, metadata2, changedItemIdentifiers) {
|
|
130968
132326
|
const outcomes = metadata2.completion?.itemOutcomes;
|
|
130969
132327
|
const submissionId = metadata2.submissionId;
|
|
@@ -131007,28 +132365,22 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
131007
132365
|
}
|
|
131008
132366
|
});
|
|
131009
132367
|
}
|
|
131010
|
-
|
|
132368
|
+
reviewItemOutcomes(input) {
|
|
131011
132369
|
const resultByItem = new Map(input.itemResults.map((itemResult) => [itemResult.itemIdentifier, itemResult]));
|
|
131012
132370
|
const administeredItems = administeredItemIdentifiers(input.metadata.itemSubmissions);
|
|
131013
|
-
return
|
|
132371
|
+
return input.metadata.review.selections.filter((selection) => administeredItems.has(selection.itemIdentifier)).map((selection) => {
|
|
131014
132372
|
const itemResult = resultByItem.get(selection.itemIdentifier);
|
|
131015
|
-
|
|
132373
|
+
const submission = itemAdministration(input.metadata.itemSubmissions, selection.itemIdentifier);
|
|
132374
|
+
if (!itemResult || !submission) {
|
|
131016
132375
|
throw new Error(`Scoring omitted selected review item ${selection.itemIdentifier}`);
|
|
131017
132376
|
}
|
|
131018
|
-
|
|
131019
|
-
|
|
131020
|
-
|
|
131021
|
-
|
|
131022
|
-
|
|
131023
|
-
|
|
131024
|
-
|
|
131025
|
-
selection,
|
|
131026
|
-
itemResult,
|
|
131027
|
-
submissionId: input.submissionId,
|
|
131028
|
-
timestamp: input.timestamp
|
|
131029
|
-
});
|
|
131030
|
-
await this.requireClient().api.oneroster.assessmentResults.upsert(childResultId, finalized.resultUpdate);
|
|
131031
|
-
return finalized.outcome;
|
|
132377
|
+
return {
|
|
132378
|
+
itemIdentifier: selection.itemIdentifier,
|
|
132379
|
+
standard: selection.standard,
|
|
132380
|
+
answered: submission.answered,
|
|
132381
|
+
score: itemResult.score,
|
|
132382
|
+
isCorrect: itemResult.isCorrect
|
|
132383
|
+
};
|
|
131032
132384
|
});
|
|
131033
132385
|
}
|
|
131034
132386
|
async resolveAttemptAuthorization({ gameId, studentId, attemptId, user }, options = {}) {
|
|
@@ -131060,11 +132412,12 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
131060
132412
|
return { attempt: { result, metadata: metadata2, integration }, enrollment };
|
|
131061
132413
|
}
|
|
131062
132414
|
async authorizeAttempt(params, options = {}) {
|
|
131063
|
-
const
|
|
131064
|
-
|
|
131065
|
-
|
|
131066
|
-
|
|
131067
|
-
|
|
132415
|
+
const authorization = await this.resolveAttemptAuthorization(params, options);
|
|
132416
|
+
return this.requireActiveAttempt(authorization, params.attemptId);
|
|
132417
|
+
}
|
|
132418
|
+
requireActiveAttempt(authorization, attemptId) {
|
|
132419
|
+
const enrollment = requireAssessmentMutationAccess(attemptId, authorization.attempt.integration.status, authorization.enrollment);
|
|
132420
|
+
return { ...authorization.attempt, enrollment };
|
|
131068
132421
|
}
|
|
131069
132422
|
async authorizeAttemptRead(params) {
|
|
131070
132423
|
const { attempt, enrollment } = await this.resolveAttemptAuthorization(params);
|
|
@@ -132933,7 +134286,7 @@ function getTotalXpFromTimebackConfig(config5) {
|
|
|
132933
134286
|
return courseMetadata.metrics.totalXp;
|
|
132934
134287
|
}
|
|
132935
134288
|
const resourceMetadata = config5.resource.metadata;
|
|
132936
|
-
if (
|
|
134289
|
+
if (isRecord4(resourceMetadata) && typeof resourceMetadata.xp === "number") {
|
|
132937
134290
|
return resourceMetadata.xp;
|
|
132938
134291
|
}
|
|
132939
134292
|
return null;
|
|
@@ -133949,8 +135302,8 @@ var init_timeback_service = __esm(async () => {
|
|
|
133949
135302
|
});
|
|
133950
135303
|
}
|
|
133951
135304
|
static patchCourseMetadata(metadata2, totalXp, options) {
|
|
133952
|
-
const nextMetadata =
|
|
133953
|
-
const currentMetrics =
|
|
135305
|
+
const nextMetadata = isRecord4(metadata2) ? { ...metadata2 } : {};
|
|
135306
|
+
const currentMetrics = isRecord4(nextMetadata.metrics) ? nextMetadata.metrics : {};
|
|
133954
135307
|
const metrics2 = { ...currentMetrics };
|
|
133955
135308
|
if (totalXp === null) {
|
|
133956
135309
|
delete metrics2.totalXp;
|
|
@@ -133975,7 +135328,7 @@ var init_timeback_service = __esm(async () => {
|
|
|
133975
135328
|
if (goals === null) {
|
|
133976
135329
|
delete nextMetadata.goals;
|
|
133977
135330
|
} else {
|
|
133978
|
-
const currentGoals =
|
|
135331
|
+
const currentGoals = isRecord4(nextMetadata.goals) ? nextMetadata.goals : {};
|
|
133979
135332
|
const nextGoals = { ...currentGoals };
|
|
133980
135333
|
for (const [key, value] of Object.entries(goals)) {
|
|
133981
135334
|
if (value === null) {
|
|
@@ -133994,7 +135347,7 @@ var init_timeback_service = __esm(async () => {
|
|
|
133994
135347
|
if (options?.publishStatus !== undefined) {
|
|
133995
135348
|
if (options.publishStatus === null) {
|
|
133996
135349
|
delete nextMetadata.publishStatus;
|
|
133997
|
-
const alphaLearn =
|
|
135350
|
+
const alphaLearn = isRecord4(nextMetadata.AlphaLearn) ? { ...nextMetadata.AlphaLearn } : {};
|
|
133998
135351
|
delete alphaLearn.publishStatus;
|
|
133999
135352
|
if (Object.keys(alphaLearn).length > 0) {
|
|
134000
135353
|
nextMetadata.AlphaLearn = alphaLearn;
|
|
@@ -134003,7 +135356,7 @@ var init_timeback_service = __esm(async () => {
|
|
|
134003
135356
|
}
|
|
134004
135357
|
} else {
|
|
134005
135358
|
nextMetadata.publishStatus = options.publishStatus;
|
|
134006
|
-
const alphaLearn =
|
|
135359
|
+
const alphaLearn = isRecord4(nextMetadata.AlphaLearn) ? { ...nextMetadata.AlphaLearn } : {};
|
|
134007
135360
|
alphaLearn.publishStatus = options.publishStatus === "published" ? "active" : options.publishStatus;
|
|
134008
135361
|
nextMetadata.AlphaLearn = alphaLearn;
|
|
134009
135362
|
}
|
|
@@ -134021,9 +135374,9 @@ var init_timeback_service = __esm(async () => {
|
|
|
134021
135374
|
return Object.keys(nextMetadata).length > 0 ? nextMetadata : undefined;
|
|
134022
135375
|
}
|
|
134023
135376
|
static patchResourceMetadata(metadata2, options) {
|
|
134024
|
-
const nextMetadata =
|
|
134025
|
-
const playcademyMetadata =
|
|
134026
|
-
const masteryMetadata =
|
|
135377
|
+
const nextMetadata = isRecord4(metadata2) ? { ...metadata2 } : {};
|
|
135378
|
+
const playcademyMetadata = isRecord4(nextMetadata.playcademy) ? { ...nextMetadata.playcademy } : {};
|
|
135379
|
+
const masteryMetadata = isRecord4(playcademyMetadata.mastery) ? { ...playcademyMetadata.mastery } : {};
|
|
134027
135380
|
nextMetadata.subject = options.subject;
|
|
134028
135381
|
nextMetadata.grades = [options.grade];
|
|
134029
135382
|
if (options.totalXp === null) {
|