@playcademy/sandbox 0.8.1-beta.4 → 0.8.1-beta.5
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/cli.js +1798 -489
- package/dist/server.js +1798 -489
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1128,7 +1128,7 @@ var package_default;
|
|
|
1128
1128
|
var init_package = __esm(() => {
|
|
1129
1129
|
package_default = {
|
|
1130
1130
|
name: "@playcademy/sandbox",
|
|
1131
|
-
version: "0.8.1-beta.
|
|
1131
|
+
version: "0.8.1-beta.5",
|
|
1132
1132
|
description: "Local development server for Playcademy game development",
|
|
1133
1133
|
type: "module",
|
|
1134
1134
|
exports: {
|
|
@@ -9302,7 +9302,18 @@ function qtiDirectedPairValidationIssue(values, contract) {
|
|
|
9302
9302
|
}
|
|
9303
9303
|
return null;
|
|
9304
9304
|
}
|
|
9305
|
-
function
|
|
9305
|
+
function validNumericMatchValue(value, baseType) {
|
|
9306
|
+
const syntax = baseType === "integer" ? /^[+-]?\d+$/ : /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?$/i;
|
|
9307
|
+
if (!syntax.test(value.trim())) {
|
|
9308
|
+
return false;
|
|
9309
|
+
}
|
|
9310
|
+
const numeric = Number(value);
|
|
9311
|
+
return Number.isFinite(numeric) && (baseType === "float" || Number.isSafeInteger(numeric));
|
|
9312
|
+
}
|
|
9313
|
+
function sameScore(left, right) {
|
|
9314
|
+
return Number.isFinite(left) && Number.isFinite(right) && Math.abs(left - right) <= Number.EPSILON * Math.max(1, Math.abs(left), Math.abs(right));
|
|
9315
|
+
}
|
|
9316
|
+
function isRecord3(value) {
|
|
9306
9317
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
9307
9318
|
}
|
|
9308
9319
|
function stringField(value) {
|
|
@@ -9397,13 +9408,13 @@ function dedupeStandards(standards) {
|
|
|
9397
9408
|
return [...deduped.values()];
|
|
9398
9409
|
}
|
|
9399
9410
|
function qtiAlignmentStandardsFromMetadata(metadata2) {
|
|
9400
|
-
if (!
|
|
9411
|
+
if (!isRecord3(metadata2)) {
|
|
9401
9412
|
return [];
|
|
9402
9413
|
}
|
|
9403
9414
|
const standards = [];
|
|
9404
9415
|
const alignmentGroups = Array.isArray(metadata2.alignment) ? metadata2.alignment : [];
|
|
9405
9416
|
for (const group of alignmentGroups) {
|
|
9406
|
-
if (
|
|
9417
|
+
if (isRecord3(group)) {
|
|
9407
9418
|
const source = stringField(group.curriculum) || "QTI";
|
|
9408
9419
|
const domains2 = Array.isArray(group.domains) ? group.domains : [];
|
|
9409
9420
|
if (!domains2.length) {
|
|
@@ -9421,11 +9432,11 @@ function qtiAlignmentStandardsFromMetadata(metadata2) {
|
|
|
9421
9432
|
}
|
|
9422
9433
|
} else {
|
|
9423
9434
|
for (const domain of domains2) {
|
|
9424
|
-
if (
|
|
9435
|
+
if (isRecord3(domain)) {
|
|
9425
9436
|
const domainName = stringField(domain.name) || undefined;
|
|
9426
9437
|
const domainStandards = Array.isArray(domain.standards) ? domain.standards : [];
|
|
9427
9438
|
for (const standard of domainStandards) {
|
|
9428
|
-
if (
|
|
9439
|
+
if (isRecord3(standard)) {
|
|
9429
9440
|
const identifier = stringField(standard.identifier).trim();
|
|
9430
9441
|
const id = stringField(standard.id).trim() || identifier;
|
|
9431
9442
|
if (id) {
|
|
@@ -9476,12 +9487,9 @@ function isAssessmentAttemptSuperseded(attempt) {
|
|
|
9476
9487
|
return attempt.inProgress === ASSESSMENT_ATTEMPT_SUPERSEDED.inProgress && attempt.scoreStatus === ASSESSMENT_ATTEMPT_SUPERSEDED.scoreStatus;
|
|
9477
9488
|
}
|
|
9478
9489
|
function classifyAssessmentSubmission(attempt, submissionId) {
|
|
9479
|
-
if (isAssessmentAttemptAwaitingAward(attempt)) {
|
|
9490
|
+
if (isAssessmentAttemptAwaitingAward(attempt) || isAssessmentAttemptCompleted(attempt)) {
|
|
9480
9491
|
return attempt.submissionId === submissionId ? "replay" : "reject";
|
|
9481
9492
|
}
|
|
9482
|
-
if (isAssessmentAttemptCompleted(attempt)) {
|
|
9483
|
-
return "reject";
|
|
9484
|
-
}
|
|
9485
9493
|
return isAssessmentAttemptOpen(attempt) ? "submit" : "reject";
|
|
9486
9494
|
}
|
|
9487
9495
|
function isSameAssessmentAward(recorded, input) {
|
|
@@ -9573,10 +9581,13 @@ function canonicalJson(value) {
|
|
|
9573
9581
|
const entries = Object.entries(value).filter(([, entryValue]) => entryValue !== undefined).toSorted(([left], [right]) => compareCodeUnits(left, right)).map(([key, entryValue]) => `${JSON.stringify(key)}:${canonicalJson(entryValue)}`);
|
|
9574
9582
|
return `{${entries.join(",")}}`;
|
|
9575
9583
|
}
|
|
9576
|
-
async function
|
|
9577
|
-
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(canonicalJson(
|
|
9584
|
+
async function canonicalJsonSha256(value) {
|
|
9585
|
+
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(canonicalJson(value)));
|
|
9578
9586
|
return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
9579
9587
|
}
|
|
9588
|
+
function diagnosticRoutingRevision(manifest) {
|
|
9589
|
+
return canonicalJsonSha256(manifest);
|
|
9590
|
+
}
|
|
9580
9591
|
function issue(severity, code, message, path, details) {
|
|
9581
9592
|
return { severity, code, message, ...path ? { path } : {}, ...details ? { details } : {} };
|
|
9582
9593
|
}
|
|
@@ -10633,11 +10644,11 @@ function itemResponsesEqual(left, right) {
|
|
|
10633
10644
|
function itemResponseUpdateMatches(current, update) {
|
|
10634
10645
|
return itemResponsesEqual(current, applyAssessmentItemResponseUpdate(current, update));
|
|
10635
10646
|
}
|
|
10636
|
-
function
|
|
10647
|
+
function resolveAssessmentItemReplay(state, input) {
|
|
10637
10648
|
const flow = assessmentFlowForPurpose(state.purpose);
|
|
10638
10649
|
if (flow !== "item-submit") {
|
|
10639
10650
|
return {
|
|
10640
|
-
action: "
|
|
10651
|
+
action: "conflict",
|
|
10641
10652
|
failure: assessmentFlowViolation("Items cannot be submitted individually in this flow.", { flow })
|
|
10642
10653
|
};
|
|
10643
10654
|
}
|
|
@@ -10646,7 +10657,7 @@ function prepareAssessmentItemSubmission(state, input) {
|
|
|
10646
10657
|
const sameRequest = priorSubmission.itemIdentifier === input.itemIdentifier && itemResponseUpdateMatches(state.responses[input.itemIdentifier], input.responses);
|
|
10647
10658
|
if (!sameRequest) {
|
|
10648
10659
|
return {
|
|
10649
|
-
action: "
|
|
10660
|
+
action: "conflict",
|
|
10650
10661
|
failure: assessmentFlowViolation("This item submission ID was already used for a different request.", {
|
|
10651
10662
|
submissionId: input.submissionId,
|
|
10652
10663
|
itemIdentifier: input.itemIdentifier,
|
|
@@ -10662,6 +10673,16 @@ function prepareAssessmentItemSubmission(state, input) {
|
|
|
10662
10673
|
submission: priorSubmission
|
|
10663
10674
|
};
|
|
10664
10675
|
}
|
|
10676
|
+
return { action: "missing" };
|
|
10677
|
+
}
|
|
10678
|
+
function prepareAssessmentItemSubmission(state, input) {
|
|
10679
|
+
const replay = resolveAssessmentItemReplay(state, input);
|
|
10680
|
+
if (replay.action === "conflict") {
|
|
10681
|
+
return { action: "reject", failure: replay.failure };
|
|
10682
|
+
}
|
|
10683
|
+
if (replay.action === "replay") {
|
|
10684
|
+
return replay;
|
|
10685
|
+
}
|
|
10665
10686
|
if (state.responseVersion !== input.expectedResponseVersion) {
|
|
10666
10687
|
return {
|
|
10667
10688
|
action: "reject",
|
|
@@ -10677,7 +10698,7 @@ function prepareAssessmentItemSubmission(state, input) {
|
|
|
10677
10698
|
failure: assessmentFlowViolation("Items must be submitted once and in candidate order.", {
|
|
10678
10699
|
itemIdentifier: input.itemIdentifier,
|
|
10679
10700
|
previousItemIdentifier: previousItemIdentifier ?? null,
|
|
10680
|
-
flow
|
|
10701
|
+
flow: assessmentFlowForPurpose(state.purpose)
|
|
10681
10702
|
})
|
|
10682
10703
|
};
|
|
10683
10704
|
}
|
|
@@ -10707,7 +10728,8 @@ function completeAssessmentItemSubmission(state, input, prepared, scoring, submi
|
|
|
10707
10728
|
responseVersion: prepared.responseVersion,
|
|
10708
10729
|
answered: prepared.answered,
|
|
10709
10730
|
score: scoring.score,
|
|
10710
|
-
isCorrect: scoring.isCorrect
|
|
10731
|
+
isCorrect: scoring.isCorrect,
|
|
10732
|
+
...scoring.grading ? { grading: scoring.grading } : {}
|
|
10711
10733
|
};
|
|
10712
10734
|
return {
|
|
10713
10735
|
responseVersion: prepared.responseVersion,
|
|
@@ -10862,6 +10884,145 @@ function assessmentPresentationForAttempt(assessment, attemptId) {
|
|
|
10862
10884
|
});
|
|
10863
10885
|
return { ...assessment, items };
|
|
10864
10886
|
}
|
|
10887
|
+
function normalizedMatchText(value, baseType) {
|
|
10888
|
+
const normalized = value.replace(ZERO_WIDTH_CHARACTERS, "").replace(CURLY_SINGLE_QUOTES, "'").replace(CURLY_DOUBLE_QUOTES, '"').replace(UNICODE_DASHES, "-");
|
|
10889
|
+
return baseType === "directedPair" ? normalized : normalized.replace(/[\s\u00A0]+/g, " ").trim();
|
|
10890
|
+
}
|
|
10891
|
+
function normalizedArithmeticText(value) {
|
|
10892
|
+
return value.replace(THOUSANDS_GROUP, (number) => number.replaceAll(",", "")).replace(ARITHMETIC_OPERATOR_SPACING, "$1").replaceAll("÷", "/").replace(/[\u00D7\u00B7]/g, "*");
|
|
10893
|
+
}
|
|
10894
|
+
function parsedMatchValue(value, baseType) {
|
|
10895
|
+
if (typeof value !== "string") {
|
|
10896
|
+
return null;
|
|
10897
|
+
}
|
|
10898
|
+
const normalized = normalizedMatchText(value, baseType);
|
|
10899
|
+
if (baseType === "identifier" && !IDENTIFIER_VALUE.test(normalized)) {
|
|
10900
|
+
return null;
|
|
10901
|
+
}
|
|
10902
|
+
if (baseType === "directedPair" && !DIRECTED_PAIR_VALUE.test(normalized)) {
|
|
10903
|
+
return null;
|
|
10904
|
+
}
|
|
10905
|
+
return normalized;
|
|
10906
|
+
}
|
|
10907
|
+
function matchValuesEqual(left, right, baseType) {
|
|
10908
|
+
return left === right || baseType !== "identifier" && baseType !== "directedPair" && normalizedArithmeticText(left) === normalizedArithmeticText(right);
|
|
10909
|
+
}
|
|
10910
|
+
function parsedMatchValues(values, baseType) {
|
|
10911
|
+
const parsed = values.map((value) => parsedMatchValue(value, baseType));
|
|
10912
|
+
return parsed.every((value) => value !== null) ? parsed : null;
|
|
10913
|
+
}
|
|
10914
|
+
function responseValues2(value) {
|
|
10915
|
+
if (typeof value === "string") {
|
|
10916
|
+
return [value];
|
|
10917
|
+
}
|
|
10918
|
+
return Array.isArray(value) ? value : null;
|
|
10919
|
+
}
|
|
10920
|
+
function unorderedMatch(responses, correct, baseType) {
|
|
10921
|
+
if (responses.length !== correct.length) {
|
|
10922
|
+
return false;
|
|
10923
|
+
}
|
|
10924
|
+
const matched = new Set;
|
|
10925
|
+
for (const response of responses) {
|
|
10926
|
+
const match = correct.findIndex((candidate, index) => !matched.has(index) && matchValuesEqual(response, candidate, baseType));
|
|
10927
|
+
if (match === -1) {
|
|
10928
|
+
return false;
|
|
10929
|
+
}
|
|
10930
|
+
matched.add(match);
|
|
10931
|
+
}
|
|
10932
|
+
return true;
|
|
10933
|
+
}
|
|
10934
|
+
function matchComparisonCorrect(rule, values) {
|
|
10935
|
+
if (rule.comparison.kind !== "match" || rule.comparison.correctValues.length === 0) {
|
|
10936
|
+
return false;
|
|
10937
|
+
}
|
|
10938
|
+
const responses = parsedMatchValues(values, rule.baseType);
|
|
10939
|
+
const correct = parsedMatchValues(rule.comparison.correctValues, rule.baseType);
|
|
10940
|
+
if (!responses || !correct) {
|
|
10941
|
+
return false;
|
|
10942
|
+
}
|
|
10943
|
+
if (rule.cardinality === "single") {
|
|
10944
|
+
if (responses.length !== 1) {
|
|
10945
|
+
return false;
|
|
10946
|
+
}
|
|
10947
|
+
if (rule.baseType === "string") {
|
|
10948
|
+
return correct.some((candidate) => matchValuesEqual(responses[0], candidate, rule.baseType));
|
|
10949
|
+
}
|
|
10950
|
+
return correct.length === 1 && matchValuesEqual(responses[0], correct[0], rule.baseType);
|
|
10951
|
+
}
|
|
10952
|
+
if (responses.length !== correct.length) {
|
|
10953
|
+
return false;
|
|
10954
|
+
}
|
|
10955
|
+
if (rule.cardinality === "ordered") {
|
|
10956
|
+
return responses.every((value, index) => matchValuesEqual(value, correct[index], rule.baseType));
|
|
10957
|
+
}
|
|
10958
|
+
return unorderedMatch(responses, correct, rule.baseType);
|
|
10959
|
+
}
|
|
10960
|
+
function roundedNumericValue(value, roundingMode, figures) {
|
|
10961
|
+
if (!Number.isFinite(value) || !Number.isSafeInteger(figures) || (roundingMode === "decimalPlaces" ? figures < 0 : figures <= 0)) {
|
|
10962
|
+
return null;
|
|
10963
|
+
}
|
|
10964
|
+
if (roundingMode === "decimalPlaces") {
|
|
10965
|
+
const multiplier2 = 10 ** figures;
|
|
10966
|
+
const rounded2 = Math.round(value * multiplier2) / multiplier2;
|
|
10967
|
+
return Number.isFinite(rounded2) ? rounded2 : null;
|
|
10968
|
+
}
|
|
10969
|
+
if (value === 0) {
|
|
10970
|
+
return 0;
|
|
10971
|
+
}
|
|
10972
|
+
const magnitude = Math.floor(Math.log10(Math.abs(value)));
|
|
10973
|
+
const multiplier = 10 ** (figures - magnitude - 1);
|
|
10974
|
+
const rounded = Math.round(value * multiplier) / multiplier;
|
|
10975
|
+
return Number.isFinite(rounded) ? rounded : null;
|
|
10976
|
+
}
|
|
10977
|
+
function numericResponseValue(value) {
|
|
10978
|
+
if (typeof value !== "string") {
|
|
10979
|
+
return null;
|
|
10980
|
+
}
|
|
10981
|
+
const numeric = Number(value);
|
|
10982
|
+
return Number.isFinite(numeric) ? numeric : null;
|
|
10983
|
+
}
|
|
10984
|
+
function numericComparisonCorrect(rule, values) {
|
|
10985
|
+
if (values.length !== 1 || rule.cardinality !== "single" || rule.baseType !== "integer" && rule.baseType !== "float" || rule.comparison.kind === "match") {
|
|
10986
|
+
return false;
|
|
10987
|
+
}
|
|
10988
|
+
const response = numericResponseValue(values[0]);
|
|
10989
|
+
const correct = rule.comparison.correctValue;
|
|
10990
|
+
if (response === null || !Number.isFinite(correct) || rule.baseType === "integer" && !Number.isSafeInteger(correct)) {
|
|
10991
|
+
return false;
|
|
10992
|
+
}
|
|
10993
|
+
if (rule.comparison.kind === "numeric-equal") {
|
|
10994
|
+
return response === correct;
|
|
10995
|
+
}
|
|
10996
|
+
const { roundingMode, figures } = rule.comparison;
|
|
10997
|
+
const roundedResponse = roundedNumericValue(response, roundingMode, figures);
|
|
10998
|
+
const roundedCorrect = roundedNumericValue(correct, roundingMode, figures);
|
|
10999
|
+
return roundedResponse !== null && roundedCorrect !== null && roundedResponse === roundedCorrect;
|
|
11000
|
+
}
|
|
11001
|
+
function ruleCorrect(rule, responses) {
|
|
11002
|
+
const response = responses?.[rule.responseIdentifier];
|
|
11003
|
+
if (response === undefined) {
|
|
11004
|
+
return false;
|
|
11005
|
+
}
|
|
11006
|
+
const values = responseValues2(response);
|
|
11007
|
+
if (!values) {
|
|
11008
|
+
return false;
|
|
11009
|
+
}
|
|
11010
|
+
return rule.comparison.kind === "match" ? matchComparisonCorrect(rule, values) : numericComparisonCorrect(rule, values);
|
|
11011
|
+
}
|
|
11012
|
+
function scorePlatformAssessmentItem(item, responses) {
|
|
11013
|
+
const verdicts = item.rules.map((rule) => ruleCorrect(rule, responses));
|
|
11014
|
+
const rawEarned = item.rules.reduce((total, rule, index) => total + (verdicts[index] ? rule.points : 0), 0);
|
|
11015
|
+
const earned = Math.min(item.maxScore, Math.max(0, rawEarned));
|
|
11016
|
+
return {
|
|
11017
|
+
itemIdentifier: item.itemIdentifier,
|
|
11018
|
+
score: {
|
|
11019
|
+
earned,
|
|
11020
|
+
possible: item.maxScore,
|
|
11021
|
+
normalized: item.maxScore <= 0 ? 0 : Math.min(1, Math.max(0, earned / item.maxScore))
|
|
11022
|
+
},
|
|
11023
|
+
isCorrect: verdicts.length > 0 && verdicts.every(Boolean)
|
|
11024
|
+
};
|
|
11025
|
+
}
|
|
10865
11026
|
function reviewStandardFieldsWithinLimits(input) {
|
|
10866
11027
|
return input.framework.trim().length <= TIMEBACK_ASSESSMENT_REVIEW_REQUEST_LIMITS.standardFieldLength && input.identifier.trim().length <= TIMEBACK_ASSESSMENT_REVIEW_REQUEST_LIMITS.standardFieldLength;
|
|
10867
11028
|
}
|
|
@@ -11348,6 +11509,64 @@ function assessmentItemEarnedScore(interactionPoints, maxScore) {
|
|
|
11348
11509
|
const earned = interactionPoints.reduce((sum, points) => sum + Math.max(0, points), 0);
|
|
11349
11510
|
return Math.min(earned, maxScore);
|
|
11350
11511
|
}
|
|
11512
|
+
async function assessmentChildResultPayloadHash(payload) {
|
|
11513
|
+
return canonicalJsonSha256({
|
|
11514
|
+
kind: ASSESSMENT_CHILD_RESULT_KIND,
|
|
11515
|
+
version: ASSESSMENT_CHILD_RESULT_VERSION,
|
|
11516
|
+
payload
|
|
11517
|
+
});
|
|
11518
|
+
}
|
|
11519
|
+
async function buildAssessmentChildResultCommand(payload, recordedAt) {
|
|
11520
|
+
const parsedPayload = AssessmentChildResultPayloadV1Schema.parse(payload);
|
|
11521
|
+
const command = {
|
|
11522
|
+
kind: ASSESSMENT_CHILD_RESULT_KIND,
|
|
11523
|
+
version: ASSESSMENT_CHILD_RESULT_VERSION,
|
|
11524
|
+
recordedAt,
|
|
11525
|
+
payloadHash: await assessmentChildResultPayloadHash(parsedPayload),
|
|
11526
|
+
payload: parsedPayload
|
|
11527
|
+
};
|
|
11528
|
+
return AssessmentChildResultCommandV1Schema.parse(command);
|
|
11529
|
+
}
|
|
11530
|
+
function normalizeOmittedResponses(value) {
|
|
11531
|
+
if (!isRecord(value) || !isRecord(value.payload) || value.payload.responses !== undefined) {
|
|
11532
|
+
return value;
|
|
11533
|
+
}
|
|
11534
|
+
return {
|
|
11535
|
+
...value,
|
|
11536
|
+
payload: {
|
|
11537
|
+
...value.payload,
|
|
11538
|
+
responses: {}
|
|
11539
|
+
}
|
|
11540
|
+
};
|
|
11541
|
+
}
|
|
11542
|
+
function parseAssessmentChildResultCommand(value) {
|
|
11543
|
+
const direct = AssessmentChildResultCommandV1Schema.safeParse(normalizeOmittedResponses(value));
|
|
11544
|
+
if (direct.success) {
|
|
11545
|
+
return direct.data;
|
|
11546
|
+
}
|
|
11547
|
+
if (!isRecord(value)) {
|
|
11548
|
+
return null;
|
|
11549
|
+
}
|
|
11550
|
+
const nested = AssessmentChildResultCommandV1Schema.safeParse(normalizeOmittedResponses(value[PLAYCADEMY_ASSESSMENT_ITEM_RESULT_METADATA_KEY]));
|
|
11551
|
+
return nested.success ? nested.data : null;
|
|
11552
|
+
}
|
|
11553
|
+
async function verifyAssessmentChildResultCommand(metadataOrCommand) {
|
|
11554
|
+
const command = parseAssessmentChildResultCommand(metadataOrCommand);
|
|
11555
|
+
if (!command) {
|
|
11556
|
+
return { ok: false, reason: "malformed" };
|
|
11557
|
+
}
|
|
11558
|
+
if (await assessmentChildResultPayloadHash(command.payload) !== command.payloadHash) {
|
|
11559
|
+
return { ok: false, reason: "payload_hash_mismatch" };
|
|
11560
|
+
}
|
|
11561
|
+
return { ok: true, command };
|
|
11562
|
+
}
|
|
11563
|
+
function assessmentChildResultId(attemptId, submissionId) {
|
|
11564
|
+
return deterministicUUID([
|
|
11565
|
+
ASSESSMENT_CHILD_RESULT_ID_NAMESPACE,
|
|
11566
|
+
IdentitySchema.parse(attemptId),
|
|
11567
|
+
IdentitySchema.parse(submissionId)
|
|
11568
|
+
].join("\x00"));
|
|
11569
|
+
}
|
|
11351
11570
|
function normalizeMasteryStandard(input) {
|
|
11352
11571
|
const standard = canonicalReviewStandardRef(input);
|
|
11353
11572
|
if (!standard) {
|
|
@@ -11388,7 +11607,19 @@ function isReviewItemOutcome(value) {
|
|
|
11388
11607
|
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");
|
|
11389
11608
|
}
|
|
11390
11609
|
function isAssessmentItemSubmission(value) {
|
|
11391
|
-
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");
|
|
11610
|
+
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));
|
|
11611
|
+
}
|
|
11612
|
+
function isAssessmentItemGrading(value) {
|
|
11613
|
+
if (!isRecord(value) || !isNonEmptyString(value.graderVersion)) {
|
|
11614
|
+
return false;
|
|
11615
|
+
}
|
|
11616
|
+
if (value.source === "timeback-qti") {
|
|
11617
|
+
return true;
|
|
11618
|
+
}
|
|
11619
|
+
if (value.source === "platform-qti-adapter") {
|
|
11620
|
+
return isNonEmptyString(value.qtiGraderVersion);
|
|
11621
|
+
}
|
|
11622
|
+
return value.source === "platform-artifact" && isNonEmptyString(value.artifactVersion);
|
|
11392
11623
|
}
|
|
11393
11624
|
function isDiagnosticTrackResult(value) {
|
|
11394
11625
|
return isRecord(value) && isNonEmptyString(value.trackKey) && (value.groupKey === undefined || isNonEmptyString(value.groupKey)) && DIAGNOSTIC_TRACK_OUTCOMES.some((outcome) => outcome === value.outcome) && isNonEmptyString(value.resultKey);
|
|
@@ -11422,8 +11653,11 @@ function isDiagnosticRoutingState(value) {
|
|
|
11422
11653
|
const nextValid = value.next === null || isRecord(value.next) && isNonEmptyString(value.next.stageKey) && isNonEmptyString(value.next.trackKey) && isNonEmptyString(value.next.nodeKey) && isNonEmptyString(value.next.itemIdentifier);
|
|
11423
11654
|
return stagesValid && tracksValid && nextValid && (value.status === "in-progress" && value.next !== null || value.status === "ready-to-complete" && value.next === null);
|
|
11424
11655
|
}
|
|
11656
|
+
function isDiagnosticRoutingSnapshot(value, revision) {
|
|
11657
|
+
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);
|
|
11658
|
+
}
|
|
11425
11659
|
function isRoutedDiagnosticAttemptMetadata(value, itemSubmissions, responseVersion) {
|
|
11426
|
-
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) {
|
|
11660
|
+
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) {
|
|
11427
11661
|
return false;
|
|
11428
11662
|
}
|
|
11429
11663
|
const itemIdentifiers = new Set;
|
|
@@ -11509,6 +11743,12 @@ function playcademyAssessmentResultMetadata(value) {
|
|
|
11509
11743
|
diagnostic: { ...normalized.diagnostic, ledger: [] }
|
|
11510
11744
|
};
|
|
11511
11745
|
}
|
|
11746
|
+
if (isRecord(normalized.diagnostic) && Array.isArray(normalized.diagnostic.ledger) && normalized.diagnostic.ledger.length === 0 && normalized.diagnostic.routingSnapshots === undefined) {
|
|
11747
|
+
normalized = {
|
|
11748
|
+
...normalized,
|
|
11749
|
+
diagnostic: { ...normalized.diagnostic, routingSnapshots: [] }
|
|
11750
|
+
};
|
|
11751
|
+
}
|
|
11512
11752
|
if (isRecord(normalized.diagnostic) && isRecord(normalized.diagnostic.state)) {
|
|
11513
11753
|
const state = normalized.diagnostic.state;
|
|
11514
11754
|
const tracks = Array.isArray(state.tracks) ? state.tracks.map((track) => isRecord(track) ? {
|
|
@@ -11516,10 +11756,12 @@ function playcademyAssessmentResultMetadata(value) {
|
|
|
11516
11756
|
currentNodeKey: track.currentNodeKey ?? null,
|
|
11517
11757
|
terminal: track.terminal ?? null
|
|
11518
11758
|
} : track) : state.tracks;
|
|
11759
|
+
const routingSnapshots = Array.isArray(normalized.diagnostic.routingSnapshots) ? normalized.diagnostic.routingSnapshots.map((snapshot) => isRecord(snapshot) ? { ...snapshot, next: snapshot.next ?? null } : snapshot) : normalized.diagnostic.routingSnapshots;
|
|
11519
11760
|
normalized = {
|
|
11520
11761
|
...normalized,
|
|
11521
11762
|
diagnostic: {
|
|
11522
11763
|
...normalized.diagnostic,
|
|
11764
|
+
...routingSnapshots === undefined ? {} : { routingSnapshots },
|
|
11523
11765
|
state: {
|
|
11524
11766
|
...state,
|
|
11525
11767
|
...tracks === undefined ? {} : { tracks },
|
|
@@ -11565,7 +11807,7 @@ function playcademyDiagnosticAssessmentItemResultMetadata(value) {
|
|
|
11565
11807
|
const normalized = metadata2.responses === undefined ? { ...metadata2, responses: {} } : metadata2;
|
|
11566
11808
|
return isPlaycademyDiagnosticAssessmentItemResultMetadataV1(normalized) ? normalized : null;
|
|
11567
11809
|
}
|
|
11568
|
-
var SCRIPT_SCHEMES, DATA_RASTER_IMAGE_PATTERN, QTI_MATHML_ALLOWED_TAGS, metadataSymbol, parser, SUPPORTED_QTI_INTERACTION_TYPES, URL_ATTRIBUTES, IMAGE_URL_ATTRIBUTES, INTERACTION_TYPES, EMPHASIS_TAGS, STRONG_TAGS, CONTENT_SKIPPED_TAGS, BLOCK_CONTENT_KINDS, GAP_MATCH_TOKEN_TAGS, STAGE_GRAPHIC_EXCLUDED_CONTAINERS, BLANK_SENTINEL = "", MARKED_BLANK_PATTERN, MARKED_BLANK_GLOBAL_PATTERN, MATCH_CORRECT_TEMPLATE = "http://www.imsglobal.org/question/qti_v3p0/rptemplates/match_correct", MAP_RESPONSE_TEMPLATE = "https://purl.imsglobal.org/spec/qti/v3p0/rptemplates/map_response", MAP_RESPONSE_TEMPLATES, RESPONSE_PROCESSING_TEMPLATES, VOID_BODY_ELEMENTS, URL_ATTRIBUTES2, IMAGE_URL_ATTRIBUTES2, VOID_CHOICE_ELEMENTS, NUMERIC_COMPARISON_ATTRIBUTES, RESPONSE_CARDINALITIES, RESPONSE_BASE_TYPES, POINT_INTERACTION_TYPES, INDEPENDENT_PROCESSING_TAGS, SUPPORTED_BASE_TYPES, SUPPORTED_CARDINALITIES, MATCH_CORRECT_TEMPLATES, UUID_PATTERN, COMMON_CORE_MATH_FRAMEWORK_ALIASES, COMMON_CORE_ELA_FRAMEWORK_ALIASES, COMMON_CORE_FRAMEWORK_ALIASES, ASSESSMENT_ATTEMPT_OPEN, ASSESSMENT_ATTEMPT_COMPLETED, ASSESSMENT_ATTEMPT_AWAITING_AWARD, ASSESSMENT_ATTEMPT_SUPERSEDED, ASSESSMENT_RUNTIME_ERROR_STATUS, ROUTING_KEY_MAX_LENGTH = 128, ROUTING_RESULT_KEY_MAX_LENGTH = 256, ROUTING_STAGE_LIMIT = 32, ROUTING_TRACK_LIMIT = 128, ROUTING_NODE_LIMIT = 1000, ROUTING_TERMINAL_LIMIT = 1000, ROUTING_GROUP_LIMIT = 128, ROUTING_PREDICATE_DEPTH_LIMIT = 8, ROUTING_EXPLORATION_STATE_LIMIT = 200000, RoutingKeySchema, RoutingResultKeySchema, DiagnosticTrackOutcomeSchema, DiagnosticActivationPredicateSchema, DiagnosticRoutingTransitionSchema, DiagnosticRoutingManifestV1Schema, GRADE_VALUES, POINT_RESPONSE_PATTERN, REVIEW_SELECTION_POLICY_VERSION = "unseen-first-least-recently-delivered-v1", PLAYCADEMY_REVIEW_BANK_MANIFEST_METADATA_KEY = "playcademyReviewBank", PLAYCADEMY_REVIEW_BANK_MANIFEST_VERSION = 1, DEFAULT_REVIEW_SELECTION_POLICY, ASSESSMENT_RUNTIME_FIXTURE_BUNDLE_VERSION = 8, RuntimeSubjectSchema, RuntimeGradeSchema, OptionalQueryGradeSchema,
|
|
11810
|
+
var SCRIPT_SCHEMES, DATA_RASTER_IMAGE_PATTERN, QTI_MATHML_ALLOWED_TAGS, metadataSymbol, parser, SUPPORTED_QTI_INTERACTION_TYPES, URL_ATTRIBUTES, IMAGE_URL_ATTRIBUTES, INTERACTION_TYPES, EMPHASIS_TAGS, STRONG_TAGS, CONTENT_SKIPPED_TAGS, BLOCK_CONTENT_KINDS, GAP_MATCH_TOKEN_TAGS, STAGE_GRAPHIC_EXCLUDED_CONTAINERS, BLANK_SENTINEL = "", MARKED_BLANK_PATTERN, MARKED_BLANK_GLOBAL_PATTERN, MATCH_CORRECT_TEMPLATE = "http://www.imsglobal.org/question/qti_v3p0/rptemplates/match_correct", MAP_RESPONSE_TEMPLATE = "https://purl.imsglobal.org/spec/qti/v3p0/rptemplates/map_response", MAP_RESPONSE_TEMPLATES, RESPONSE_PROCESSING_TEMPLATES, VOID_BODY_ELEMENTS, URL_ATTRIBUTES2, IMAGE_URL_ATTRIBUTES2, VOID_CHOICE_ELEMENTS, NUMERIC_COMPARISON_ATTRIBUTES, RESPONSE_CARDINALITIES, RESPONSE_BASE_TYPES, POINT_INTERACTION_TYPES, INDEPENDENT_PROCESSING_TAGS, PRIVATE_PLAYABLE_KEYS, trimmedNonEmptyString, finiteNumber2, positiveFiniteNumber, IDENTIFIER_VALUE, DIRECTED_PAIR_VALUE, scoringMatchComparisonSchema, scoringNumericEqualComparisonSchema, scoringNumericEqualRoundedComparisonSchema, scoringRuleSchema, scoringRulesSchema, scoringItemSchema, assessmentScoringArtifactItemsSchema, assessmentScoringArtifactSchema, SUPPORTED_BASE_TYPES, SUPPORTED_CARDINALITIES, MATCH_CORRECT_TEMPLATES, UUID_PATTERN, COMMON_CORE_MATH_FRAMEWORK_ALIASES, COMMON_CORE_ELA_FRAMEWORK_ALIASES, COMMON_CORE_FRAMEWORK_ALIASES, ASSESSMENT_ATTEMPT_OPEN, ASSESSMENT_ATTEMPT_COMPLETED, ASSESSMENT_ATTEMPT_AWAITING_AWARD, ASSESSMENT_ATTEMPT_SUPERSEDED, ASSESSMENT_RUNTIME_ERROR_STATUS, ROUTING_KEY_MAX_LENGTH = 128, ROUTING_RESULT_KEY_MAX_LENGTH = 256, ROUTING_STAGE_LIMIT = 32, ROUTING_TRACK_LIMIT = 128, ROUTING_NODE_LIMIT = 1000, ROUTING_TERMINAL_LIMIT = 1000, ROUTING_GROUP_LIMIT = 128, ROUTING_PREDICATE_DEPTH_LIMIT = 8, ROUTING_EXPLORATION_STATE_LIMIT = 200000, RoutingKeySchema, RoutingResultKeySchema, DiagnosticTrackOutcomeSchema, DiagnosticActivationPredicateSchema, DiagnosticRoutingTransitionSchema, DiagnosticRoutingManifestV1Schema, GRADE_VALUES, POINT_RESPONSE_PATTERN, ZERO_WIDTH_CHARACTERS, CURLY_SINGLE_QUOTES, CURLY_DOUBLE_QUOTES, UNICODE_DASHES, ARITHMETIC_OPERATOR_SPACING, THOUSANDS_GROUP, REVIEW_SELECTION_POLICY_VERSION = "unseen-first-least-recently-delivered-v1", PLAYCADEMY_REVIEW_BANK_MANIFEST_METADATA_KEY = "playcademyReviewBank", PLAYCADEMY_REVIEW_BANK_MANIFEST_VERSION = 1, DEFAULT_REVIEW_SELECTION_POLICY, ASSESSMENT_RUNTIME_FIXTURE_BUNDLE_VERSION = 8, ASSESSMENT_CHILD_RESULT_KIND = "assessment-child-result", ASSESSMENT_CHILD_RESULT_VERSION = 1, ASSESSMENT_CHILD_RESULT_ID_NAMESPACE = "playcademy:assessment-child-result:v1", IdentitySchema, RecordedAtSchema, ResponseKeySchema, AssessmentResponseValueSchema, AssessmentScoreSchema, AssessmentStandardRefSchema, DiagnosticRoutingLedgerEntryV1Schema, AssessmentChildResultAttemptV1Schema, AssessmentChildResultAdministrationV1Schema, AssessmentChildResultGradingV1Schema, ReviewChildResultContextV1Schema, DiagnosticChildResultContextV1Schema, CommonPayloadShape, ReviewChildResultPayloadV1Schema, DiagnosticChildResultPayloadV1Schema, AssessmentChildResultPayloadV1Schema, AssessmentChildResultCommandV1Schema, RuntimeSubjectSchema, RuntimeGradeSchema, OptionalQueryGradeSchema, AssessmentResponseValueSchema2, AssessmentRuntimeIdentitySchema, ResponseKeySchema2, StartAssessmentBaseSchema, AssessmentStandardRefSchema2, LatestAssessmentFilterBaseSchema, LatestAssessmentFiltersSchema, LatestRuntimeAssessmentQuerySchema, StartAssessmentBodySchema, AssessmentPreparationReceiptSchema, StartAssessmentRequestBodySchema, SaveAssessmentBodySchema, SubmitAssessmentItemBodySchema, SubmitAssessmentBodySchema, FinalizeAssessmentBodySchema, PrepareRuntimeAssessmentRequestSchema, StartRuntimeAssessmentRequestSchema, SaveRuntimeAssessmentRequestSchema, SubmitAssessmentItemRuntimeRequestSchema, SubmitRuntimeAssessmentRequestSchema, FinalizeRuntimeAssessmentRequestSchema;
|
|
11569
11811
|
var init_assessment_runtime2 = __esm(() => {
|
|
11570
11812
|
init_timeback3();
|
|
11571
11813
|
init_timeback3();
|
|
@@ -11574,6 +11816,7 @@ var init_assessment_runtime2 = __esm(() => {
|
|
|
11574
11816
|
init_timeback3();
|
|
11575
11817
|
init_timeback3();
|
|
11576
11818
|
init_esm();
|
|
11819
|
+
init_esm();
|
|
11577
11820
|
init_timeback4();
|
|
11578
11821
|
init_src();
|
|
11579
11822
|
init_timeback3();
|
|
@@ -11581,6 +11824,9 @@ var init_assessment_runtime2 = __esm(() => {
|
|
|
11581
11824
|
init_uuid();
|
|
11582
11825
|
init_src();
|
|
11583
11826
|
init_uuid();
|
|
11827
|
+
init_esm();
|
|
11828
|
+
init_src();
|
|
11829
|
+
init_uuid();
|
|
11584
11830
|
init_src();
|
|
11585
11831
|
init_timeback4();
|
|
11586
11832
|
init_esm();
|
|
@@ -11719,6 +11965,93 @@ var init_assessment_runtime2 = __esm(() => {
|
|
|
11719
11965
|
"qti-sum",
|
|
11720
11966
|
"qti-base-value"
|
|
11721
11967
|
]);
|
|
11968
|
+
PRIVATE_PLAYABLE_KEYS = new Set([
|
|
11969
|
+
"answer",
|
|
11970
|
+
"answerkey",
|
|
11971
|
+
"correctanswer",
|
|
11972
|
+
"correctresponse",
|
|
11973
|
+
"correctresponses",
|
|
11974
|
+
"correct",
|
|
11975
|
+
"correctvalue",
|
|
11976
|
+
"correctvalues",
|
|
11977
|
+
"iscorrect",
|
|
11978
|
+
"responseprocessing",
|
|
11979
|
+
"rule",
|
|
11980
|
+
"rules",
|
|
11981
|
+
"scoring",
|
|
11982
|
+
"scoringrules",
|
|
11983
|
+
"solution",
|
|
11984
|
+
"expected",
|
|
11985
|
+
"expectedanswer",
|
|
11986
|
+
"expectedvalue",
|
|
11987
|
+
"expectedvalues"
|
|
11988
|
+
]);
|
|
11989
|
+
trimmedNonEmptyString = exports_external.string().min(1).refine((value) => value === value.trim());
|
|
11990
|
+
finiteNumber2 = exports_external.number().finite();
|
|
11991
|
+
positiveFiniteNumber = finiteNumber2.positive();
|
|
11992
|
+
IDENTIFIER_VALUE = /^[A-Za-z_][\w.-]*$/;
|
|
11993
|
+
DIRECTED_PAIR_VALUE = /^[A-Za-z_][\w.-]* [A-Za-z_][\w.-]*$/;
|
|
11994
|
+
scoringMatchComparisonSchema = exports_external.object({
|
|
11995
|
+
kind: exports_external.literal("match"),
|
|
11996
|
+
correctValues: exports_external.array(trimmedNonEmptyString).min(1).refine((values) => new Set(values).size === values.length)
|
|
11997
|
+
}).strict();
|
|
11998
|
+
scoringNumericEqualComparisonSchema = exports_external.object({ kind: exports_external.literal("numeric-equal"), correctValue: finiteNumber2 }).strict();
|
|
11999
|
+
scoringNumericEqualRoundedComparisonSchema = exports_external.object({
|
|
12000
|
+
kind: exports_external.literal("numeric-equal-rounded"),
|
|
12001
|
+
correctValue: finiteNumber2,
|
|
12002
|
+
roundingMode: exports_external.enum(["decimalPlaces", "significantFigures"]),
|
|
12003
|
+
figures: exports_external.number().int().safe().nonnegative()
|
|
12004
|
+
}).strict();
|
|
12005
|
+
scoringRuleSchema = exports_external.object({
|
|
12006
|
+
responseIdentifier: trimmedNonEmptyString,
|
|
12007
|
+
cardinality: exports_external.enum(["single", "multiple", "ordered"]),
|
|
12008
|
+
baseType: exports_external.enum(["string", "identifier", "integer", "float", "directedPair"]),
|
|
12009
|
+
points: positiveFiniteNumber,
|
|
12010
|
+
comparison: exports_external.discriminatedUnion("kind", [
|
|
12011
|
+
scoringMatchComparisonSchema,
|
|
12012
|
+
scoringNumericEqualComparisonSchema,
|
|
12013
|
+
scoringNumericEqualRoundedComparisonSchema
|
|
12014
|
+
])
|
|
12015
|
+
}).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) => {
|
|
12016
|
+
if (rule.comparison.kind !== "match") {
|
|
12017
|
+
return rule.baseType !== "integer" || Number.isSafeInteger(rule.comparison.correctValue);
|
|
12018
|
+
}
|
|
12019
|
+
if (rule.baseType === "integer" || rule.baseType === "float") {
|
|
12020
|
+
return rule.comparison.correctValues.every((value) => validNumericMatchValue(value, rule.baseType));
|
|
12021
|
+
}
|
|
12022
|
+
if (rule.baseType === "identifier") {
|
|
12023
|
+
return rule.comparison.correctValues.every((value) => IDENTIFIER_VALUE.test(value));
|
|
12024
|
+
}
|
|
12025
|
+
return rule.baseType !== "directedPair" || rule.comparison.correctValues.every((value) => DIRECTED_PAIR_VALUE.test(value));
|
|
12026
|
+
});
|
|
12027
|
+
scoringRulesSchema = exports_external.array(scoringRuleSchema).min(1).refine((rules) => new Set(rules.map((rule) => rule.responseIdentifier)).size === rules.length);
|
|
12028
|
+
scoringItemSchema = exports_external.discriminatedUnion("strategy", [
|
|
12029
|
+
exports_external.object({
|
|
12030
|
+
strategy: exports_external.literal("qti"),
|
|
12031
|
+
itemIdentifier: trimmedNonEmptyString,
|
|
12032
|
+
maxScore: positiveFiniteNumber,
|
|
12033
|
+
reason: exports_external.enum([
|
|
12034
|
+
"response-mapping",
|
|
12035
|
+
"area-mapping",
|
|
12036
|
+
"unsupported-base-type",
|
|
12037
|
+
"unsupported-cardinality",
|
|
12038
|
+
"invalid-correct-response",
|
|
12039
|
+
"no-scorable-response",
|
|
12040
|
+
"unsupported-processing-template",
|
|
12041
|
+
"unsupported-response-processing",
|
|
12042
|
+
"cross-response-processing",
|
|
12043
|
+
"inconsistent-max-score"
|
|
12044
|
+
])
|
|
12045
|
+
}).strict(),
|
|
12046
|
+
exports_external.object({
|
|
12047
|
+
strategy: exports_external.literal("platform"),
|
|
12048
|
+
itemIdentifier: trimmedNonEmptyString,
|
|
12049
|
+
maxScore: positiveFiniteNumber,
|
|
12050
|
+
rules: scoringRulesSchema
|
|
12051
|
+
}).strict()
|
|
12052
|
+
]).refine((item) => item.strategy === "qti" || sameScore(item.rules.reduce((total, rule) => total + rule.points, 0), item.maxScore));
|
|
12053
|
+
assessmentScoringArtifactItemsSchema = exports_external.array(scoringItemSchema).refine((items) => new Set(items.map((item) => item.itemIdentifier)).size === items.length);
|
|
12054
|
+
assessmentScoringArtifactSchema = exports_external.object({ items: assessmentScoringArtifactItemsSchema }).strict();
|
|
11722
12055
|
SUPPORTED_BASE_TYPES = new Set([
|
|
11723
12056
|
"string",
|
|
11724
12057
|
"identifier",
|
|
@@ -11845,9 +12178,140 @@ var init_assessment_runtime2 = __esm(() => {
|
|
|
11845
12178
|
}).strict();
|
|
11846
12179
|
GRADE_VALUES = TIMEBACK_GRADES;
|
|
11847
12180
|
POINT_RESPONSE_PATTERN = /^-?\d+(?:\.\d+)? -?\d+(?:\.\d+)?$/;
|
|
12181
|
+
ZERO_WIDTH_CHARACTERS = /[\u200B\uFEFF\u200C\u200D]/g;
|
|
12182
|
+
CURLY_SINGLE_QUOTES = /[\u2018\u2019\u201A\u201B]/g;
|
|
12183
|
+
CURLY_DOUBLE_QUOTES = /[\u201C\u201D\u201E\u201F]/g;
|
|
12184
|
+
UNICODE_DASHES = /[\u2010\u2011\u2012\u2013\u2014\u2015\u2212]/g;
|
|
12185
|
+
ARITHMETIC_OPERATOR_SPACING = /\s*([()+\-*/xX\u00D7\u00F7\u00B7])\s*/g;
|
|
12186
|
+
THOUSANDS_GROUP = /\b\d{1,3}(?:,\d{3})+(?:\.\d+)?\b/g;
|
|
11848
12187
|
DEFAULT_REVIEW_SELECTION_POLICY = {
|
|
11849
12188
|
version: REVIEW_SELECTION_POLICY_VERSION
|
|
11850
12189
|
};
|
|
12190
|
+
IdentitySchema = exports_external.string().min(1).refine((value) => value.trim() === value, {
|
|
12191
|
+
message: "Identifiers must not contain surrounding whitespace"
|
|
12192
|
+
});
|
|
12193
|
+
RecordedAtSchema = exports_external.string().datetime().refine((value) => new Date(value).toISOString() === value, {
|
|
12194
|
+
message: "recordedAt must be a canonical UTC timestamp"
|
|
12195
|
+
});
|
|
12196
|
+
ResponseKeySchema = IdentitySchema;
|
|
12197
|
+
AssessmentResponseValueSchema = exports_external.custom(isAssessmentResponseValue, {
|
|
12198
|
+
message: `Response values are non-empty text, at most ${TIMEBACK_ASSESSMENT_RESPONSE_VALUE_MAX_LENGTH} characters each`
|
|
12199
|
+
});
|
|
12200
|
+
AssessmentScoreSchema = exports_external.object({
|
|
12201
|
+
earned: exports_external.number().finite().nonnegative(),
|
|
12202
|
+
possible: exports_external.number().finite().nonnegative(),
|
|
12203
|
+
normalized: exports_external.number().finite().min(0).max(1)
|
|
12204
|
+
}).strict().superRefine((score, context) => {
|
|
12205
|
+
const expectedNormalized = score.possible === 0 ? 0 : score.earned / score.possible;
|
|
12206
|
+
if (score.earned > score.possible) {
|
|
12207
|
+
context.addIssue({
|
|
12208
|
+
code: exports_external.ZodIssueCode.custom,
|
|
12209
|
+
message: "Earned score must not exceed possible score",
|
|
12210
|
+
path: ["earned"]
|
|
12211
|
+
});
|
|
12212
|
+
}
|
|
12213
|
+
if (score.normalized !== expectedNormalized) {
|
|
12214
|
+
context.addIssue({
|
|
12215
|
+
code: exports_external.ZodIssueCode.custom,
|
|
12216
|
+
message: "Normalized score must equal earned divided by possible",
|
|
12217
|
+
path: ["normalized"]
|
|
12218
|
+
});
|
|
12219
|
+
}
|
|
12220
|
+
});
|
|
12221
|
+
AssessmentStandardRefSchema = exports_external.object({
|
|
12222
|
+
framework: IdentitySchema,
|
|
12223
|
+
identifier: IdentitySchema
|
|
12224
|
+
}).strict();
|
|
12225
|
+
DiagnosticRoutingLedgerEntryV1Schema = exports_external.object({
|
|
12226
|
+
stageKey: IdentitySchema,
|
|
12227
|
+
trackKey: IdentitySchema,
|
|
12228
|
+
routingNodeKey: IdentitySchema,
|
|
12229
|
+
itemIdentifier: IdentitySchema,
|
|
12230
|
+
isCorrect: exports_external.boolean()
|
|
12231
|
+
}).strict();
|
|
12232
|
+
AssessmentChildResultAttemptV1Schema = exports_external.object({
|
|
12233
|
+
attemptId: IdentitySchema,
|
|
12234
|
+
gameId: IdentitySchema,
|
|
12235
|
+
studentId: IdentitySchema,
|
|
12236
|
+
activityId: IdentitySchema,
|
|
12237
|
+
courseId: IdentitySchema,
|
|
12238
|
+
integrationId: IdentitySchema,
|
|
12239
|
+
enrollmentId: IdentitySchema,
|
|
12240
|
+
selectedTest: exports_external.object({
|
|
12241
|
+
assessmentKey: IdentitySchema,
|
|
12242
|
+
identifier: IdentitySchema,
|
|
12243
|
+
contentRevision: IdentitySchema
|
|
12244
|
+
}).strict()
|
|
12245
|
+
}).strict();
|
|
12246
|
+
AssessmentChildResultAdministrationV1Schema = exports_external.object({
|
|
12247
|
+
submissionId: IdentitySchema,
|
|
12248
|
+
responseVersion: exports_external.number().int().positive(),
|
|
12249
|
+
itemIdentifier: IdentitySchema
|
|
12250
|
+
}).strict();
|
|
12251
|
+
AssessmentChildResultGradingV1Schema = exports_external.discriminatedUnion("source", [
|
|
12252
|
+
exports_external.object({
|
|
12253
|
+
source: exports_external.literal("timeback-qti"),
|
|
12254
|
+
graderVersion: IdentitySchema
|
|
12255
|
+
}).strict(),
|
|
12256
|
+
exports_external.object({
|
|
12257
|
+
source: exports_external.literal("platform-qti-adapter"),
|
|
12258
|
+
graderVersion: IdentitySchema,
|
|
12259
|
+
qtiGraderVersion: IdentitySchema
|
|
12260
|
+
}).strict(),
|
|
12261
|
+
exports_external.object({
|
|
12262
|
+
source: exports_external.literal("platform-artifact"),
|
|
12263
|
+
artifactVersion: IdentitySchema,
|
|
12264
|
+
graderVersion: IdentitySchema
|
|
12265
|
+
}).strict()
|
|
12266
|
+
]);
|
|
12267
|
+
ReviewChildResultContextV1Schema = exports_external.object({
|
|
12268
|
+
kind: exports_external.literal("review"),
|
|
12269
|
+
bankRevision: IdentitySchema,
|
|
12270
|
+
standard: AssessmentStandardRefSchema
|
|
12271
|
+
}).strict();
|
|
12272
|
+
DiagnosticChildResultContextV1Schema = exports_external.object({
|
|
12273
|
+
kind: exports_external.literal("platform-routed-diagnostic"),
|
|
12274
|
+
definitionId: IdentitySchema,
|
|
12275
|
+
diagnosticKey: IdentitySchema,
|
|
12276
|
+
routingRevision: IdentitySchema,
|
|
12277
|
+
transition: DiagnosticRoutingLedgerEntryV1Schema
|
|
12278
|
+
}).strict();
|
|
12279
|
+
CommonPayloadShape = {
|
|
12280
|
+
attempt: AssessmentChildResultAttemptV1Schema,
|
|
12281
|
+
administration: AssessmentChildResultAdministrationV1Schema,
|
|
12282
|
+
responses: exports_external.record(ResponseKeySchema, AssessmentResponseValueSchema),
|
|
12283
|
+
score: AssessmentScoreSchema,
|
|
12284
|
+
grading: AssessmentChildResultGradingV1Schema
|
|
12285
|
+
};
|
|
12286
|
+
ReviewChildResultPayloadV1Schema = exports_external.object({
|
|
12287
|
+
...CommonPayloadShape,
|
|
12288
|
+
isCorrect: exports_external.boolean().optional(),
|
|
12289
|
+
context: ReviewChildResultContextV1Schema
|
|
12290
|
+
}).strict();
|
|
12291
|
+
DiagnosticChildResultPayloadV1Schema = exports_external.object({
|
|
12292
|
+
...CommonPayloadShape,
|
|
12293
|
+
isCorrect: exports_external.boolean(),
|
|
12294
|
+
context: DiagnosticChildResultContextV1Schema
|
|
12295
|
+
}).strict().superRefine((payload, context) => {
|
|
12296
|
+
if (payload.context.transition.itemIdentifier !== payload.administration.itemIdentifier || payload.context.transition.isCorrect !== payload.isCorrect) {
|
|
12297
|
+
context.addIssue({
|
|
12298
|
+
code: exports_external.ZodIssueCode.custom,
|
|
12299
|
+
message: "Diagnostic transition must match the administered item and correctness",
|
|
12300
|
+
path: ["context", "transition"]
|
|
12301
|
+
});
|
|
12302
|
+
}
|
|
12303
|
+
});
|
|
12304
|
+
AssessmentChildResultPayloadV1Schema = exports_external.union([
|
|
12305
|
+
ReviewChildResultPayloadV1Schema,
|
|
12306
|
+
DiagnosticChildResultPayloadV1Schema
|
|
12307
|
+
]);
|
|
12308
|
+
AssessmentChildResultCommandV1Schema = exports_external.object({
|
|
12309
|
+
kind: exports_external.literal(ASSESSMENT_CHILD_RESULT_KIND),
|
|
12310
|
+
version: exports_external.literal(ASSESSMENT_CHILD_RESULT_VERSION),
|
|
12311
|
+
recordedAt: RecordedAtSchema,
|
|
12312
|
+
payloadHash: exports_external.string().regex(/^[a-f0-9]{64}$/),
|
|
12313
|
+
payload: AssessmentChildResultPayloadV1Schema
|
|
12314
|
+
}).strict();
|
|
11851
12315
|
RuntimeSubjectSchema = exports_external.enum(TIMEBACK_SUBJECTS);
|
|
11852
12316
|
RuntimeGradeSchema = exports_external.number().refine(isTimebackGrade, {
|
|
11853
12317
|
message: `Grade must be one of: ${TIMEBACK_GRADES.join(", ")}`
|
|
@@ -11858,14 +12322,14 @@ var init_assessment_runtime2 = __esm(() => {
|
|
|
11858
12322
|
}
|
|
11859
12323
|
return typeof value === "string" && value.trim() !== "" ? Number(value) : Number.NaN;
|
|
11860
12324
|
}, RuntimeGradeSchema.optional());
|
|
11861
|
-
|
|
12325
|
+
AssessmentResponseValueSchema2 = exports_external.custom(isAssessmentResponseValue, {
|
|
11862
12326
|
message: `Response values are non-empty text, at most ${TIMEBACK_ASSESSMENT_RESPONSE_VALUE_MAX_LENGTH} characters each`
|
|
11863
12327
|
});
|
|
11864
12328
|
AssessmentRuntimeIdentitySchema = exports_external.object({
|
|
11865
12329
|
gameId: exports_external.string().uuid(),
|
|
11866
12330
|
studentId: exports_external.string().trim().min(1)
|
|
11867
12331
|
});
|
|
11868
|
-
|
|
12332
|
+
ResponseKeySchema2 = exports_external.string().refine((key) => key.length > 0 && key.trim() === key, {
|
|
11869
12333
|
message: "Response keys must be non-empty without surrounding whitespace"
|
|
11870
12334
|
});
|
|
11871
12335
|
StartAssessmentBaseSchema = exports_external.object({
|
|
@@ -11873,7 +12337,7 @@ var init_assessment_runtime2 = __esm(() => {
|
|
|
11873
12337
|
subject: RuntimeSubjectSchema.optional(),
|
|
11874
12338
|
grade: RuntimeGradeSchema.optional()
|
|
11875
12339
|
});
|
|
11876
|
-
|
|
12340
|
+
AssessmentStandardRefSchema2 = exports_external.object({
|
|
11877
12341
|
framework: exports_external.string().trim().min(1).max(TIMEBACK_ASSESSMENT_REVIEW_REQUEST_LIMITS.standardFieldLength),
|
|
11878
12342
|
identifier: exports_external.string().trim().min(1).max(TIMEBACK_ASSESSMENT_REVIEW_REQUEST_LIMITS.standardFieldLength)
|
|
11879
12343
|
}).refine((standard) => {
|
|
@@ -11890,7 +12354,7 @@ var init_assessment_runtime2 = __esm(() => {
|
|
|
11890
12354
|
}),
|
|
11891
12355
|
LatestAssessmentFilterBaseSchema.extend({
|
|
11892
12356
|
purpose: exports_external.literal("mastery"),
|
|
11893
|
-
standard:
|
|
12357
|
+
standard: AssessmentStandardRefSchema2
|
|
11894
12358
|
})
|
|
11895
12359
|
]);
|
|
11896
12360
|
LatestRuntimeAssessmentQuerySchema = exports_external.intersection(AssessmentRuntimeIdentitySchema, LatestAssessmentFiltersSchema);
|
|
@@ -11904,26 +12368,26 @@ var init_assessment_runtime2 = __esm(() => {
|
|
|
11904
12368
|
}),
|
|
11905
12369
|
StartAssessmentBaseSchema.extend({
|
|
11906
12370
|
purpose: exports_external.literal("review"),
|
|
11907
|
-
standards: exports_external.array(
|
|
12371
|
+
standards: exports_external.array(AssessmentStandardRefSchema2).min(1).max(TIMEBACK_ASSESSMENT_REVIEW_REQUEST_LIMITS.standards),
|
|
11908
12372
|
candidateItemsPerStandard: exports_external.number().int().positive().max(TIMEBACK_ASSESSMENT_REVIEW_REQUEST_LIMITS.candidateItemsPerStandard).optional()
|
|
11909
12373
|
}),
|
|
11910
12374
|
StartAssessmentBaseSchema.extend({
|
|
11911
12375
|
purpose: exports_external.literal("mastery"),
|
|
11912
|
-
standard:
|
|
12376
|
+
standard: AssessmentStandardRefSchema2
|
|
11913
12377
|
})
|
|
11914
12378
|
]);
|
|
11915
12379
|
AssessmentPreparationReceiptSchema = exports_external.string().max(32000);
|
|
11916
12380
|
StartAssessmentRequestBodySchema = exports_external.intersection(StartAssessmentBodySchema, exports_external.object({ preparationReceipt: AssessmentPreparationReceiptSchema.optional() }));
|
|
11917
12381
|
SaveAssessmentBodySchema = exports_external.object({
|
|
11918
12382
|
expectedResponseVersion: exports_external.number().int().nonnegative(),
|
|
11919
|
-
responses: exports_external.record(
|
|
12383
|
+
responses: exports_external.record(ResponseKeySchema2, exports_external.record(ResponseKeySchema2, AssessmentResponseValueSchema2.nullable()))
|
|
11920
12384
|
});
|
|
11921
12385
|
SubmitAssessmentItemBodySchema = exports_external.object({
|
|
11922
12386
|
expectedResponseVersion: exports_external.number().int().nonnegative(),
|
|
11923
12387
|
submissionId: exports_external.string().trim().min(1),
|
|
11924
|
-
itemIdentifier:
|
|
11925
|
-
routingNodeKey:
|
|
11926
|
-
responses: exports_external.record(
|
|
12388
|
+
itemIdentifier: ResponseKeySchema2,
|
|
12389
|
+
routingNodeKey: ResponseKeySchema2.optional(),
|
|
12390
|
+
responses: exports_external.record(ResponseKeySchema2, AssessmentResponseValueSchema2.nullable())
|
|
11927
12391
|
});
|
|
11928
12392
|
SubmitAssessmentBodySchema = exports_external.object({
|
|
11929
12393
|
expectedResponseVersion: exports_external.number().int().nonnegative(),
|
|
@@ -36815,7 +37279,7 @@ function kebabToTitleCase(kebabStr) {
|
|
|
36815
37279
|
}
|
|
36816
37280
|
|
|
36817
37281
|
// ../api-core/src/utils/timeback.util.ts
|
|
36818
|
-
function
|
|
37282
|
+
function isRecord4(value) {
|
|
36819
37283
|
return typeof value === "object" && value !== null;
|
|
36820
37284
|
}
|
|
36821
37285
|
function resolveIntegrationSubject(patchSubject, liveSubject, storedSubject) {
|
|
@@ -36892,9 +37356,9 @@ function getGeneratedMetricValue(event, type) {
|
|
|
36892
37356
|
return Number.isFinite(value) ? value : undefined;
|
|
36893
37357
|
}
|
|
36894
37358
|
function getMergedCaliperExtensions(event) {
|
|
36895
|
-
const objectActivityExtensions =
|
|
36896
|
-
const generatedExtensions =
|
|
36897
|
-
const eventExtensions =
|
|
37359
|
+
const objectActivityExtensions = isRecord4(event.object.activity?.extensions) ? event.object.activity.extensions : undefined;
|
|
37360
|
+
const generatedExtensions = isRecord4(event.generated?.extensions) ? event.generated.extensions : undefined;
|
|
37361
|
+
const eventExtensions = isRecord4(event.extensions) ? event.extensions : undefined;
|
|
36898
37362
|
return {
|
|
36899
37363
|
...objectActivityExtensions,
|
|
36900
37364
|
...generatedExtensions,
|
|
@@ -36903,7 +37367,7 @@ function getMergedCaliperExtensions(event) {
|
|
|
36903
37367
|
}
|
|
36904
37368
|
function getPlaycademyMetadata(event) {
|
|
36905
37369
|
const extensions = getMergedCaliperExtensions(event);
|
|
36906
|
-
return
|
|
37370
|
+
return isRecord4(extensions.playcademy) ? extensions.playcademy : undefined;
|
|
36907
37371
|
}
|
|
36908
37372
|
function getActivityId(event, playcademy) {
|
|
36909
37373
|
const metadataActivityId = getStringValue(playcademy?.activityId);
|
|
@@ -36955,7 +37419,7 @@ function buildResourceMetadata({
|
|
|
36955
37419
|
}
|
|
36956
37420
|
function getDurationSecondsFromExtensions(event) {
|
|
36957
37421
|
const extensions = getMergedCaliperExtensions(event);
|
|
36958
|
-
const playcademy =
|
|
37422
|
+
const playcademy = isRecord4(extensions.playcademy) ? extensions.playcademy : undefined;
|
|
36959
37423
|
const rawValue = extensions.durationSeconds ?? playcademy?.durationSeconds;
|
|
36960
37424
|
const value = typeof rawValue === "number" ? rawValue : Number(rawValue);
|
|
36961
37425
|
return Number.isFinite(value) ? value : undefined;
|
|
@@ -47458,7 +47922,7 @@ function rejectSystemManagedReview(input, context2) {
|
|
|
47458
47922
|
});
|
|
47459
47923
|
}
|
|
47460
47924
|
}
|
|
47461
|
-
var TimebackGradeSchema, TimebackSubjectSchema, CourseGoalsSchema, UpdateGameTimebackIntegrationRequestSchema, CreateGameTimebackIntegrationRequestSchema, TimebackActivityDataSchema, EndActivityRequestSchema, GameRunMetricsSchema, GameCourseMetricsSchema, GameMetricsResponseSchema, AdvanceCourseRequestSchema, UnenrollCourseRequestSchema, HeartbeatRequestSchema, PopulateStudentRequestSchema, DerivedPlatformCourseConfigSchema, TimebackBaseConfigSchema, PlatformTimebackSetupRequestSchema, AdminTimebackMutationBaseSchema, AdminAttributionDateSchema, ADMIN_GRANT_XP_MIN = -1e5, ADMIN_GRANT_XP_MAX = 1e5, ADMIN_GRANT_XP_AMOUNT_RANGE_MESSAGE, GrantTimebackXpRequestSchema, AdjustTimebackTimeRequestSchema, AdjustTimebackMasteryRequestSchema, ReconcileMasteryForConfigChangeSchema, EnrollStudentRequestSchema, UnenrollStudentRequestSchema, ReactivateEnrollmentRequestSchema, VerifyTimebackMetricDiscrepancyRequestSchema, AssessmentPurposeSchema, AssessmentStatusSchema, AssessmentKeySchema, DiagnosticKeySchema, DiagnosticRoutingManifestSchema, DiagnosticAssessmentDefinitionInputSchema,
|
|
47925
|
+
var TimebackGradeSchema, TimebackSubjectSchema, CourseGoalsSchema, UpdateGameTimebackIntegrationRequestSchema, CreateGameTimebackIntegrationRequestSchema, TimebackActivityDataSchema, EndActivityRequestSchema, GameRunMetricsSchema, GameCourseMetricsSchema, GameMetricsResponseSchema, AdvanceCourseRequestSchema, UnenrollCourseRequestSchema, HeartbeatRequestSchema, PopulateStudentRequestSchema, DerivedPlatformCourseConfigSchema, TimebackBaseConfigSchema, PlatformTimebackSetupRequestSchema, AdminTimebackMutationBaseSchema, AdminAttributionDateSchema, ADMIN_GRANT_XP_MIN = -1e5, ADMIN_GRANT_XP_MAX = 1e5, ADMIN_GRANT_XP_AMOUNT_RANGE_MESSAGE, GrantTimebackXpRequestSchema, AdjustTimebackTimeRequestSchema, AdjustTimebackMasteryRequestSchema, ReconcileMasteryForConfigChangeSchema, EnrollStudentRequestSchema, UnenrollStudentRequestSchema, ReactivateEnrollmentRequestSchema, VerifyTimebackMetricDiscrepancyRequestSchema, AssessmentPurposeSchema, AssessmentStatusSchema, AssessmentKeySchema, DiagnosticKeySchema, DiagnosticRoutingManifestSchema, DiagnosticAssessmentDefinitionInputSchema, AssessmentStandardRefSchema3, CreateAssessmentRequestSchema, UpdateAssessmentRequestSchema, CopyAssessmentRequestSchema, AssessmentAssociationImportEntrySchema, AttachExistingAssessmentsRequestSchema, ReorderQuestionsRequestSchema;
|
|
47462
47926
|
var init_schemas4 = __esm(() => {
|
|
47463
47927
|
init_esm();
|
|
47464
47928
|
init_src();
|
|
@@ -47710,7 +48174,7 @@ var init_schemas4 = __esm(() => {
|
|
|
47710
48174
|
diagnosticKey: DiagnosticKeySchema,
|
|
47711
48175
|
routingManifest: DiagnosticRoutingManifestSchema
|
|
47712
48176
|
});
|
|
47713
|
-
|
|
48177
|
+
AssessmentStandardRefSchema3 = exports_external.object({
|
|
47714
48178
|
framework: exports_external.string().trim().min(1).max(TIMEBACK_ASSESSMENT_REVIEW_REQUEST_LIMITS.standardFieldLength),
|
|
47715
48179
|
identifier: exports_external.string().trim().min(1).max(TIMEBACK_ASSESSMENT_REVIEW_REQUEST_LIMITS.standardFieldLength)
|
|
47716
48180
|
});
|
|
@@ -47718,7 +48182,7 @@ var init_schemas4 = __esm(() => {
|
|
|
47718
48182
|
assessmentKey: AssessmentKeySchema,
|
|
47719
48183
|
title: exports_external.string().min(1, "Assessment title is required"),
|
|
47720
48184
|
purpose: AssessmentPurposeSchema,
|
|
47721
|
-
standard:
|
|
48185
|
+
standard: AssessmentStandardRefSchema3.optional()
|
|
47722
48186
|
}).superRefine((input, context2) => {
|
|
47723
48187
|
requireMasteryStandard(input, context2);
|
|
47724
48188
|
rejectSystemManagedReview(input, context2);
|
|
@@ -47726,7 +48190,7 @@ var init_schemas4 = __esm(() => {
|
|
|
47726
48190
|
UpdateAssessmentRequestSchema = exports_external.object({
|
|
47727
48191
|
title: exports_external.string().trim().min(1, "Assessment title is required").optional(),
|
|
47728
48192
|
purpose: AssessmentPurposeSchema.optional(),
|
|
47729
|
-
standard:
|
|
48193
|
+
standard: AssessmentStandardRefSchema3.optional(),
|
|
47730
48194
|
diagnostic: DiagnosticAssessmentDefinitionInputSchema.nullable().optional(),
|
|
47731
48195
|
status: AssessmentStatusSchema.optional()
|
|
47732
48196
|
}).refine((input) => input.title !== undefined || input.purpose !== undefined || input.standard !== undefined || input.diagnostic !== undefined || input.status !== undefined, {
|
|
@@ -47736,7 +48200,7 @@ var init_schemas4 = __esm(() => {
|
|
|
47736
48200
|
assessmentKey: AssessmentKeySchema,
|
|
47737
48201
|
testIdentifier: exports_external.string().trim().min(1, "Assessment identifier is required"),
|
|
47738
48202
|
purpose: AssessmentPurposeSchema,
|
|
47739
|
-
standard:
|
|
48203
|
+
standard: AssessmentStandardRefSchema3.optional()
|
|
47740
48204
|
}).superRefine((input, context2) => {
|
|
47741
48205
|
requireMasteryStandard(input, context2);
|
|
47742
48206
|
rejectSystemManagedReview(input, context2);
|
|
@@ -47745,7 +48209,7 @@ var init_schemas4 = __esm(() => {
|
|
|
47745
48209
|
assessmentKey: AssessmentKeySchema,
|
|
47746
48210
|
qtiTestIdentifier: exports_external.string().trim().min(1, "Assessment identifier is required"),
|
|
47747
48211
|
purpose: AssessmentPurposeSchema,
|
|
47748
|
-
standard:
|
|
48212
|
+
standard: AssessmentStandardRefSchema3.optional()
|
|
47749
48213
|
}).superRefine((input, context2) => {
|
|
47750
48214
|
requireMasteryStandard(input, context2);
|
|
47751
48215
|
rejectSystemManagedReview(input, context2);
|
|
@@ -94752,16 +95216,16 @@ function qtiResponseMappingMaximum(input) {
|
|
|
94752
95216
|
return null;
|
|
94753
95217
|
}
|
|
94754
95218
|
const defaultValue = mapping.defaultValue ?? 0;
|
|
94755
|
-
const
|
|
95219
|
+
const responseValues3 = input.responseValues ? [...new Set(input.responseValues)].map((value) => mapping.entries[value] ?? defaultValue) : entryValues;
|
|
94756
95220
|
let maximum;
|
|
94757
95221
|
if (input.cardinality === "single") {
|
|
94758
|
-
maximum = Math.max(0, defaultValue, ...
|
|
95222
|
+
maximum = Math.max(0, defaultValue, ...responseValues3);
|
|
94759
95223
|
} else {
|
|
94760
95224
|
if (!input.responseValues && defaultValue > 0) {
|
|
94761
95225
|
return null;
|
|
94762
95226
|
}
|
|
94763
|
-
const limit = Math.min(input.maximumResponses ??
|
|
94764
|
-
maximum =
|
|
95227
|
+
const limit = Math.min(input.maximumResponses ?? responseValues3.length, responseValues3.length);
|
|
95228
|
+
maximum = responseValues3.toSorted((left, right) => right - left).slice(0, limit).reduce((sum, value) => sum + Math.max(0, value), 0);
|
|
94765
95229
|
}
|
|
94766
95230
|
if (mapping.lowerBound !== undefined) {
|
|
94767
95231
|
maximum = Math.max(maximum, mapping.lowerBound);
|
|
@@ -95323,16 +95787,16 @@ function authoredMappingContext(interaction, declaration) {
|
|
|
95323
95787
|
return {};
|
|
95324
95788
|
}
|
|
95325
95789
|
const type = qtiAuthoringInteractionType(interaction);
|
|
95326
|
-
let
|
|
95790
|
+
let responseValues3;
|
|
95327
95791
|
if (type === "choice" || type === "inline-choice" || type === "order" || type === "hotspot") {
|
|
95328
|
-
|
|
95792
|
+
responseValues3 = interaction.choices?.map((choice) => String(choice.identifier)) ?? [];
|
|
95329
95793
|
} else if (type === "hottext") {
|
|
95330
|
-
|
|
95794
|
+
responseValues3 = (interaction.textSegments ?? []).flatMap((segment) => segment.selectable && segment.identifier ? [segment.identifier] : []);
|
|
95331
95795
|
} else if (type === "match" || type === "graphic-gap-match") {
|
|
95332
95796
|
const [left = [], right = []] = interaction.choiceSets ?? [];
|
|
95333
|
-
|
|
95797
|
+
responseValues3 = authoredResponsePairs(left.map((choice) => String(choice.identifier)), right.map((choice) => String(choice.identifier)));
|
|
95334
95798
|
}
|
|
95335
|
-
const capacity =
|
|
95799
|
+
const capacity = responseValues3?.length ?? Object.keys(declaration.mapping?.entries ?? {}).length;
|
|
95336
95800
|
const limitAttribute = type === "match" || type === "gap-match" || type === "graphic-associate" || type === "graphic-gap-match" ? "max-associations" : "max-choices";
|
|
95337
95801
|
const rawLimit = interaction.attributes?.[limitAttribute];
|
|
95338
95802
|
const declaredLimit = rawLimit === undefined ? undefined : Number(rawLimit);
|
|
@@ -95351,7 +95815,7 @@ function authoredMappingContext(interaction, declaration) {
|
|
|
95351
95815
|
maximumResponses = capacity;
|
|
95352
95816
|
}
|
|
95353
95817
|
return {
|
|
95354
|
-
...
|
|
95818
|
+
...responseValues3 ? { responseValues: responseValues3 } : {},
|
|
95355
95819
|
...maximumResponses > 0 ? { maximumResponses } : {}
|
|
95356
95820
|
};
|
|
95357
95821
|
}
|
|
@@ -95535,6 +95999,13 @@ function qtiGapContentMatchesGaps(nodes, gaps) {
|
|
|
95535
95999
|
const identifiers = gapContentIdentifiers(nodes);
|
|
95536
96000
|
return identifiers.length === gaps.length && identifiers.every((identifier, index2) => identifier === gaps[index2]);
|
|
95537
96001
|
}
|
|
96002
|
+
function qtiFloatValue(value) {
|
|
96003
|
+
if (value === undefined || !QTI_FLOAT_VALUE.test(value.trim())) {
|
|
96004
|
+
return;
|
|
96005
|
+
}
|
|
96006
|
+
const number7 = Number(value);
|
|
96007
|
+
return Number.isFinite(number7) ? number7 : undefined;
|
|
96008
|
+
}
|
|
95538
96009
|
function normalizedInteractionType(value) {
|
|
95539
96010
|
if (typeof value !== "string") {
|
|
95540
96011
|
return;
|
|
@@ -95753,24 +96224,24 @@ function interactionResponseSpace(item, declaration) {
|
|
|
95753
96224
|
}
|
|
95754
96225
|
case "match": {
|
|
95755
96226
|
const [source = [], target = []] = interaction.choiceSets;
|
|
95756
|
-
const
|
|
96227
|
+
const responseValues3 = responsePairs(source.map((choice) => choice.identifier), target.map((choice) => choice.identifier));
|
|
95757
96228
|
return {
|
|
95758
|
-
responseValues:
|
|
95759
|
-
maximumResponses: qtiAttributeMaximum(attributes2, "max-associations", 1,
|
|
96229
|
+
responseValues: responseValues3,
|
|
96230
|
+
maximumResponses: qtiAttributeMaximum(attributes2, "max-associations", 1, responseValues3.length)
|
|
95760
96231
|
};
|
|
95761
96232
|
}
|
|
95762
96233
|
case "hottext": {
|
|
95763
|
-
const
|
|
96234
|
+
const responseValues3 = interaction.textSegments.flatMap((segment) => segment.selectable && segment.identifier ? [segment.identifier] : []);
|
|
95764
96235
|
return {
|
|
95765
|
-
responseValues:
|
|
95766
|
-
maximumResponses: qtiAttributeMaximum(attributes2, "max-choices", 1,
|
|
96236
|
+
responseValues: responseValues3,
|
|
96237
|
+
maximumResponses: qtiAttributeMaximum(attributes2, "max-choices", 1, responseValues3.length)
|
|
95767
96238
|
};
|
|
95768
96239
|
}
|
|
95769
96240
|
case "gap-match": {
|
|
95770
|
-
const
|
|
96241
|
+
const responseValues3 = responsePairs(interaction.gapTexts.map((choice) => choice.identifier), interaction.gaps);
|
|
95771
96242
|
return {
|
|
95772
|
-
responseValues:
|
|
95773
|
-
maximumResponses: qtiAttributeMaximum(attributes2, "max-associations", 0,
|
|
96243
|
+
responseValues: responseValues3,
|
|
96244
|
+
maximumResponses: qtiAttributeMaximum(attributes2, "max-associations", 0, responseValues3.length)
|
|
95774
96245
|
};
|
|
95775
96246
|
}
|
|
95776
96247
|
case "hotspot": {
|
|
@@ -95780,10 +96251,10 @@ function interactionResponseSpace(item, declaration) {
|
|
|
95780
96251
|
};
|
|
95781
96252
|
}
|
|
95782
96253
|
case "graphic-gap-match": {
|
|
95783
|
-
const
|
|
96254
|
+
const responseValues3 = responsePairs(interaction.gapImages.map((choice) => choice.identifier), interaction.hotspots.map((hotspot) => hotspot.identifier));
|
|
95784
96255
|
return {
|
|
95785
|
-
responseValues:
|
|
95786
|
-
maximumResponses: qtiAttributeMaximum(attributes2, "max-associations", 0,
|
|
96256
|
+
responseValues: responseValues3,
|
|
96257
|
+
maximumResponses: qtiAttributeMaximum(attributes2, "max-associations", 0, responseValues3.length)
|
|
95787
96258
|
};
|
|
95788
96259
|
}
|
|
95789
96260
|
case "graphic-associate": {
|
|
@@ -95828,8 +96299,8 @@ function responseMappingMaximum(item, declaration, space) {
|
|
|
95828
96299
|
function qtiItemScoringValidationMessage(item) {
|
|
95829
96300
|
for (const declaration of item.responseDeclarations) {
|
|
95830
96301
|
const space = interactionResponseSpace(item, declaration);
|
|
95831
|
-
const { responseValues:
|
|
95832
|
-
if (
|
|
96302
|
+
const { responseValues: responseValues3, maximumResponses } = space;
|
|
96303
|
+
if (responseValues3 && declaration.correctValues.some((value) => !responseValues3.includes(value))) {
|
|
95833
96304
|
return `Response ${declaration.identifier} declares a correct value its interaction cannot produce.`;
|
|
95834
96305
|
}
|
|
95835
96306
|
const interaction = item.interactions.find((candidate) => candidate.responseIdentifier === declaration.identifier);
|
|
@@ -95869,13 +96340,13 @@ function qtiItemScoringValidationMessage(item) {
|
|
|
95869
96340
|
}
|
|
95870
96341
|
function qtiItemMaxScore(item) {
|
|
95871
96342
|
const maxScore = item.outcomeDeclarations.find((declaration) => declaration.identifier.toUpperCase() === "MAXSCORE");
|
|
95872
|
-
const declaredMaxScore =
|
|
95873
|
-
if (
|
|
96343
|
+
const declaredMaxScore = qtiFloatValue(maxScore?.defaultValues[0]);
|
|
96344
|
+
if (declaredMaxScore !== undefined && declaredMaxScore > 0) {
|
|
95874
96345
|
return declaredMaxScore;
|
|
95875
96346
|
}
|
|
95876
96347
|
const score = item.outcomeDeclarations.find((declaration) => declaration.identifier.toUpperCase() === "SCORE");
|
|
95877
|
-
const normalMaximum =
|
|
95878
|
-
if (
|
|
96348
|
+
const normalMaximum = qtiFloatValue(score?.attributes["normal-maximum"]);
|
|
96349
|
+
if (normalMaximum !== undefined && normalMaximum > 0) {
|
|
95879
96350
|
return normalMaximum;
|
|
95880
96351
|
}
|
|
95881
96352
|
const inferredMaximum = item.responseDeclarations.reduce((total, declaration) => {
|
|
@@ -95903,7 +96374,7 @@ function scoresResponsesIndependently(item, scoredIdentifiers) {
|
|
|
95903
96374
|
if (!processing) {
|
|
95904
96375
|
return true;
|
|
95905
96376
|
}
|
|
95906
|
-
if (processing.attributes.template || processing.conditions.length !== scoredIdentifiers.size || !processing.elements.every((element) => INDEPENDENT_PROCESSING_TAGS2.has(element.tagName)) || !processing.baseValues.every((base) =>
|
|
96377
|
+
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)) {
|
|
95907
96378
|
return false;
|
|
95908
96379
|
}
|
|
95909
96380
|
const declaredIdentifiers = new Set(item.responseDeclarations.map((declaration) => declaration.identifier));
|
|
@@ -95991,6 +96462,40 @@ function supportedQtiQuestionInteractionType(question) {
|
|
|
95991
96462
|
return null;
|
|
95992
96463
|
}
|
|
95993
96464
|
}
|
|
96465
|
+
function validNumericMatchValue2(value, baseType) {
|
|
96466
|
+
const syntax = baseType === "integer" ? /^[+-]?\d+$/ : /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?$/i;
|
|
96467
|
+
if (!syntax.test(value.trim())) {
|
|
96468
|
+
return false;
|
|
96469
|
+
}
|
|
96470
|
+
const numeric3 = Number(value);
|
|
96471
|
+
return Number.isFinite(numeric3) && (baseType === "float" || Number.isSafeInteger(numeric3));
|
|
96472
|
+
}
|
|
96473
|
+
function sameScore2(left, right) {
|
|
96474
|
+
return Number.isFinite(left) && Number.isFinite(right) && Math.abs(left - right) <= Number.EPSILON * Math.max(1, Math.abs(left), Math.abs(right));
|
|
96475
|
+
}
|
|
96476
|
+
function scoringItemMatchesPlayableItem(scoringItem, item) {
|
|
96477
|
+
if (!sameScore2(scoringItem.maxScore, item.maxScore)) {
|
|
96478
|
+
return false;
|
|
96479
|
+
}
|
|
96480
|
+
if (scoringItem.strategy === "qti") {
|
|
96481
|
+
return true;
|
|
96482
|
+
}
|
|
96483
|
+
const playableResponses = new Set(item.interactions.map((interaction) => interaction.responseIdentifier));
|
|
96484
|
+
const scoringResponses = new Set(scoringItem.rules.map((rule) => rule.responseIdentifier));
|
|
96485
|
+
return playableResponses.size === item.interactions.length && scoringResponses.size === scoringItem.rules.length && playableResponses.size === scoringResponses.size && [...playableResponses].every((identifier) => scoringResponses.has(identifier));
|
|
96486
|
+
}
|
|
96487
|
+
function normalizedKey(key) {
|
|
96488
|
+
return key.replaceAll(/[^a-z0-9]/gi, "").toLowerCase();
|
|
96489
|
+
}
|
|
96490
|
+
function containsPrivatePlayableAssessmentData(value) {
|
|
96491
|
+
if (Array.isArray(value)) {
|
|
96492
|
+
return value.some(containsPrivatePlayableAssessmentData);
|
|
96493
|
+
}
|
|
96494
|
+
if (!isRecord(value)) {
|
|
96495
|
+
return false;
|
|
96496
|
+
}
|
|
96497
|
+
return Object.entries(value).some(([key, entry]) => PRIVATE_PLAYABLE_KEYS2.has(normalizedKey(key)) || containsPrivatePlayableAssessmentData(entry));
|
|
96498
|
+
}
|
|
95994
96499
|
function childElements(node, localName2) {
|
|
95995
96500
|
return node.children.filter((child) => child.kind === "element" && (!localName2 || child.localName === localName2));
|
|
95996
96501
|
}
|
|
@@ -96032,6 +96537,22 @@ function numericCorrectValue(declaration) {
|
|
|
96032
96537
|
}
|
|
96033
96538
|
return parsedNumericValue(declaration.correctValues[0], declaration.baseType);
|
|
96034
96539
|
}
|
|
96540
|
+
function rawCorrectValues(declaration) {
|
|
96541
|
+
const correctResponses = childElements(declaration, "qti-correct-response");
|
|
96542
|
+
const correctResponse = correctResponses.length === 1 ? correctResponses[0] : undefined;
|
|
96543
|
+
if (!correctResponse || !onlyWhitespaceText(correctResponse.children)) {
|
|
96544
|
+
return null;
|
|
96545
|
+
}
|
|
96546
|
+
const values = childElements(correctResponse);
|
|
96547
|
+
if (values.length === 0 || values.some((value) => value.localName !== "qti-value" || !onlyAttributes(value, []))) {
|
|
96548
|
+
return null;
|
|
96549
|
+
}
|
|
96550
|
+
const rawValues = values.map((value) => {
|
|
96551
|
+
const text3 = directText(value);
|
|
96552
|
+
return text3 === null ? null : decodeXmlEntities(text3).trim();
|
|
96553
|
+
});
|
|
96554
|
+
return rawValues.every((value) => value !== null) ? rawValues : null;
|
|
96555
|
+
}
|
|
96035
96556
|
function declarationResult(raw, normalized) {
|
|
96036
96557
|
const identifier = raw.attributes.identifier?.trim();
|
|
96037
96558
|
if (!normalized || !identifier || normalized.identifier !== identifier) {
|
|
@@ -96048,16 +96569,23 @@ function declarationResult(raw, normalized) {
|
|
|
96048
96569
|
if (baseType === "directedPair" && cardinality !== "multiple") {
|
|
96049
96570
|
return { reason: "unsupported-cardinality" };
|
|
96050
96571
|
}
|
|
96572
|
+
const correctValues = rawCorrectValues(raw);
|
|
96573
|
+
if (!correctValues) {
|
|
96574
|
+
return { reason: "invalid-correct-response" };
|
|
96575
|
+
}
|
|
96051
96576
|
const declaration = {
|
|
96052
96577
|
identifier,
|
|
96053
96578
|
cardinality,
|
|
96054
96579
|
baseType,
|
|
96055
|
-
correctValues
|
|
96580
|
+
correctValues
|
|
96056
96581
|
};
|
|
96057
|
-
if (declaration.correctValues.length === 0 || declaration.correctValues.some((value) => value.trim() === "") || declaration.cardinality === "single" && declaration.correctValues.length !== 1 && declaration.baseType !== "string") {
|
|
96582
|
+
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") {
|
|
96583
|
+
return { reason: "invalid-correct-response" };
|
|
96584
|
+
}
|
|
96585
|
+
if (declaration.baseType === "identifier" && declaration.correctValues.some((value) => !IDENTIFIER_VALUE2.test(value))) {
|
|
96058
96586
|
return { reason: "invalid-correct-response" };
|
|
96059
96587
|
}
|
|
96060
|
-
if (declaration.baseType === "directedPair" &&
|
|
96588
|
+
if (declaration.baseType === "directedPair" && declaration.correctValues.some((value) => !DIRECTED_PAIR_VALUE2.test(value))) {
|
|
96061
96589
|
return { reason: "invalid-correct-response" };
|
|
96062
96590
|
}
|
|
96063
96591
|
if ((declaration.baseType === "integer" || declaration.baseType === "float") && declaration.correctValues.some((value) => parsedNumericValue(value, declaration.baseType) === null)) {
|
|
@@ -96073,16 +96601,14 @@ function scoringDeclarations(rawDeclarations, normalizedDeclarations) {
|
|
|
96073
96601
|
const declarations = new Map;
|
|
96074
96602
|
for (const [index2, rawDeclaration] of rawDeclarations.entries()) {
|
|
96075
96603
|
const normalized = normalizedDeclarations[index2];
|
|
96076
|
-
|
|
96077
|
-
|
|
96078
|
-
|
|
96079
|
-
|
|
96080
|
-
|
|
96081
|
-
|
|
96082
|
-
return { reason: "invalid-correct-response" };
|
|
96083
|
-
}
|
|
96084
|
-
declarations.set(result.declaration.identifier, result.declaration);
|
|
96604
|
+
const result = declarationResult(rawDeclaration, normalized);
|
|
96605
|
+
if ("reason" in result) {
|
|
96606
|
+
return result;
|
|
96607
|
+
}
|
|
96608
|
+
if (declarations.has(result.declaration.identifier)) {
|
|
96609
|
+
return { reason: "invalid-correct-response" };
|
|
96085
96610
|
}
|
|
96611
|
+
declarations.set(result.declaration.identifier, result.declaration);
|
|
96086
96612
|
}
|
|
96087
96613
|
return declarations.size > 0 ? { declarations } : { reason: "no-scorable-response" };
|
|
96088
96614
|
}
|
|
@@ -96092,15 +96618,12 @@ function declarationConstructReason(declarations) {
|
|
|
96092
96618
|
}
|
|
96093
96619
|
return declarations.some((declaration) => childElements(declaration, "qti-area-mapping").length > 0) ? "area-mapping" : null;
|
|
96094
96620
|
}
|
|
96095
|
-
function sameScore(left, right) {
|
|
96096
|
-
return Math.abs(left - right) <= Number.EPSILON * Math.max(1, Math.abs(left), Math.abs(right));
|
|
96097
|
-
}
|
|
96098
96621
|
function platformResult(itemIdentifier, parsedMaxScore, rules) {
|
|
96099
96622
|
const ruleMaximum = rules.reduce((sum, rule) => sum + rule.points, 0);
|
|
96100
96623
|
if (!Number.isFinite(ruleMaximum) || ruleMaximum <= 0) {
|
|
96101
96624
|
return fallback(itemIdentifier, parsedMaxScore, "no-scorable-response");
|
|
96102
96625
|
}
|
|
96103
|
-
if (!
|
|
96626
|
+
if (!sameScore2(parsedMaxScore, ruleMaximum)) {
|
|
96104
96627
|
return fallback(itemIdentifier, parsedMaxScore, "inconsistent-max-score");
|
|
96105
96628
|
}
|
|
96106
96629
|
return {
|
|
@@ -96161,9 +96684,8 @@ function comparisonResult(comparison, declaration) {
|
|
|
96161
96684
|
return null;
|
|
96162
96685
|
}
|
|
96163
96686
|
const roundingMode = comparison.attributes["rounding-mode"];
|
|
96164
|
-
const figures =
|
|
96165
|
-
|
|
96166
|
-
if (roundingMode !== "decimalPlaces" && roundingMode !== "significantFigures" || !validFigures) {
|
|
96687
|
+
const figures = parsedNumericValue(comparison.attributes.figures ?? "", "integer");
|
|
96688
|
+
if (roundingMode !== "decimalPlaces" && roundingMode !== "significantFigures" || figures === null || (roundingMode === "decimalPlaces" ? figures < 0 : figures <= 0)) {
|
|
96167
96689
|
return null;
|
|
96168
96690
|
}
|
|
96169
96691
|
return {
|
|
@@ -96195,8 +96717,47 @@ function additivePoints(setOutcome) {
|
|
|
96195
96717
|
return null;
|
|
96196
96718
|
}
|
|
96197
96719
|
const rawPoints = directText(pointValue);
|
|
96198
|
-
const points = rawPoints === null ?
|
|
96199
|
-
return
|
|
96720
|
+
const points = rawPoints === null ? null : parsedNumericValue(rawPoints, "float");
|
|
96721
|
+
return points !== null && points > 0 ? points : null;
|
|
96722
|
+
}
|
|
96723
|
+
function constantOutcomeValue(setOutcome, identifier, baseType) {
|
|
96724
|
+
if (!setOutcome || setOutcome.localName !== "qti-set-outcome-value" || !onlyAttributes(setOutcome, ["identifier"]) || setOutcome.attributes.identifier !== identifier || !onlyWhitespaceText(setOutcome.children)) {
|
|
96725
|
+
return null;
|
|
96726
|
+
}
|
|
96727
|
+
const values = childElements(setOutcome);
|
|
96728
|
+
const value = values.length === 1 ? values[0] : undefined;
|
|
96729
|
+
return value && value.localName === "qti-base-value" && onlyAttributes(value, ["base-type"]) && value.attributes["base-type"] === baseType ? directText(value) : null;
|
|
96730
|
+
}
|
|
96731
|
+
function feedbackOutcomeIsDeclared(item) {
|
|
96732
|
+
const feedbackOutcomes = childElements(item, "qti-outcome-declaration").filter((outcome) => outcome.attributes.identifier === "FEEDBACK");
|
|
96733
|
+
const feedback = feedbackOutcomes[0];
|
|
96734
|
+
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);
|
|
96735
|
+
}
|
|
96736
|
+
function binaryBranchRule(condition, declaration) {
|
|
96737
|
+
if (!onlyAttributes(condition, []) || !onlyWhitespaceText(condition.children)) {
|
|
96738
|
+
return null;
|
|
96739
|
+
}
|
|
96740
|
+
const branches = childElements(condition);
|
|
96741
|
+
const responseIf = branches[0];
|
|
96742
|
+
const responseElse = branches[1];
|
|
96743
|
+
if (branches.length !== 2 || responseIf?.localName !== "qti-response-if" || responseElse?.localName !== "qti-response-else" || !onlyAttributes(responseIf, []) || !onlyAttributes(responseElse, []) || !onlyWhitespaceText(responseIf.children) || !onlyWhitespaceText(responseElse.children)) {
|
|
96744
|
+
return null;
|
|
96745
|
+
}
|
|
96746
|
+
const ifChildren = childElements(responseIf);
|
|
96747
|
+
const elseChildren = childElements(responseElse);
|
|
96748
|
+
const [comparison, ifFeedback, ifScore] = ifChildren;
|
|
96749
|
+
const [elseFeedback, elseScore] = elseChildren;
|
|
96750
|
+
const comparisonPayload = comparison ? comparisonResult(comparison, declaration) : null;
|
|
96751
|
+
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") {
|
|
96752
|
+
return null;
|
|
96753
|
+
}
|
|
96754
|
+
return {
|
|
96755
|
+
responseIdentifier: declaration.identifier,
|
|
96756
|
+
cardinality: declaration.cardinality,
|
|
96757
|
+
baseType: declaration.baseType,
|
|
96758
|
+
points: 1,
|
|
96759
|
+
comparison: comparisonPayload
|
|
96760
|
+
};
|
|
96200
96761
|
}
|
|
96201
96762
|
function additiveRule(condition, declarations) {
|
|
96202
96763
|
const responseIdentifiers = new Set(declarations.keys());
|
|
@@ -96274,7 +96835,7 @@ function scoreDefaultIsZero(item) {
|
|
|
96274
96835
|
}
|
|
96275
96836
|
const values = childElements(defaults[0], "qti-value");
|
|
96276
96837
|
const defaultText = values.length === 1 ? directText(values[0]) : null;
|
|
96277
|
-
return defaultText !== null && defaultText
|
|
96838
|
+
return defaultText !== null && parsedNumericValue(defaultText, "float") === 0;
|
|
96278
96839
|
}
|
|
96279
96840
|
function compileResponseProcessing(item, itemIdentifier, maxScore, declarations) {
|
|
96280
96841
|
const processingElements = childElements(item, "qti-response-processing");
|
|
@@ -96297,7 +96858,17 @@ function compileResponseProcessing(item, itemIdentifier, maxScore, declarations)
|
|
|
96297
96858
|
const response = declarations.get("RESPONSE");
|
|
96298
96859
|
return response && declarations.size === 1 ? platformResult(itemIdentifier, maxScore, [matchRule(response, 1)]) : fallback(itemIdentifier, maxScore, "unsupported-response-processing");
|
|
96299
96860
|
}
|
|
96300
|
-
if (!onlyAttributes(processing, []) || !onlyWhitespaceText(processing.children) || processingRules.length === 0 || processingRules.some((rule) => rule.localName !== "qti-response-condition")
|
|
96861
|
+
if (!onlyAttributes(processing, []) || !onlyWhitespaceText(processing.children) || processingRules.length === 0 || processingRules.some((rule) => rule.localName !== "qti-response-condition")) {
|
|
96862
|
+
return fallback(itemIdentifier, maxScore, "unsupported-response-processing");
|
|
96863
|
+
}
|
|
96864
|
+
if (processingRules.length === 1 && declarations.size === 1 && scoreDefaultIsZero(item) && feedbackOutcomeIsDeclared(item)) {
|
|
96865
|
+
const declaration = [...declarations.values()][0];
|
|
96866
|
+
const rule = binaryBranchRule(processingRules[0], declaration);
|
|
96867
|
+
if (rule) {
|
|
96868
|
+
return platformResult(itemIdentifier, maxScore, [rule]);
|
|
96869
|
+
}
|
|
96870
|
+
}
|
|
96871
|
+
if (!scoreDefaultIsZero(item)) {
|
|
96301
96872
|
return fallback(itemIdentifier, maxScore, "unsupported-response-processing");
|
|
96302
96873
|
}
|
|
96303
96874
|
const compiled = additiveRules(processingRules, declarations);
|
|
@@ -96350,7 +96921,7 @@ function compileQtiScoringArtifact(questions) {
|
|
|
96350
96921
|
});
|
|
96351
96922
|
return { items };
|
|
96352
96923
|
}
|
|
96353
|
-
function
|
|
96924
|
+
function isRecord32(value) {
|
|
96354
96925
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
96355
96926
|
}
|
|
96356
96927
|
function stringField2(value) {
|
|
@@ -96434,13 +97005,13 @@ function dedupeStandards2(standards) {
|
|
|
96434
97005
|
return [...deduped.values()];
|
|
96435
97006
|
}
|
|
96436
97007
|
function qtiAlignmentStandardsFromMetadata2(metadata2) {
|
|
96437
|
-
if (!
|
|
97008
|
+
if (!isRecord32(metadata2)) {
|
|
96438
97009
|
return [];
|
|
96439
97010
|
}
|
|
96440
97011
|
const standards = [];
|
|
96441
97012
|
const alignmentGroups = Array.isArray(metadata2.alignment) ? metadata2.alignment : [];
|
|
96442
97013
|
for (const group of alignmentGroups) {
|
|
96443
|
-
if (
|
|
97014
|
+
if (isRecord32(group)) {
|
|
96444
97015
|
const source = stringField2(group.curriculum) || "QTI";
|
|
96445
97016
|
const domains2 = Array.isArray(group.domains) ? group.domains : [];
|
|
96446
97017
|
if (!domains2.length) {
|
|
@@ -96458,11 +97029,11 @@ function qtiAlignmentStandardsFromMetadata2(metadata2) {
|
|
|
96458
97029
|
}
|
|
96459
97030
|
} else {
|
|
96460
97031
|
for (const domain3 of domains2) {
|
|
96461
|
-
if (
|
|
97032
|
+
if (isRecord32(domain3)) {
|
|
96462
97033
|
const domainName = stringField2(domain3.name) || undefined;
|
|
96463
97034
|
const domainStandards = Array.isArray(domain3.standards) ? domain3.standards : [];
|
|
96464
97035
|
for (const standard of domainStandards) {
|
|
96465
|
-
if (
|
|
97036
|
+
if (isRecord32(standard)) {
|
|
96466
97037
|
const identifier = stringField2(standard.identifier).trim();
|
|
96467
97038
|
const id = stringField2(standard.id).trim() || identifier;
|
|
96468
97039
|
if (id) {
|
|
@@ -96485,13 +97056,13 @@ function qtiAlignmentStandardsFromMetadata2(metadata2) {
|
|
|
96485
97056
|
return dedupeStandards2(standards);
|
|
96486
97057
|
}
|
|
96487
97058
|
function qtiStandardsFromMetadata(metadata2) {
|
|
96488
|
-
if (!
|
|
97059
|
+
if (!isRecord32(metadata2)) {
|
|
96489
97060
|
return [];
|
|
96490
97061
|
}
|
|
96491
97062
|
const standards = [];
|
|
96492
97063
|
const objectiveSets = Array.isArray(metadata2.learningObjectiveSet) ? metadata2.learningObjectiveSet : [];
|
|
96493
97064
|
for (const set3 of objectiveSets) {
|
|
96494
|
-
if (
|
|
97065
|
+
if (isRecord32(set3)) {
|
|
96495
97066
|
const source = stringField2(set3.source) || "QTI";
|
|
96496
97067
|
const ids = Array.isArray(set3.learningObjectiveIds) ? set3.learningObjectiveIds : [];
|
|
96497
97068
|
for (const id of ids) {
|
|
@@ -96505,7 +97076,7 @@ function qtiStandardsFromMetadata(metadata2) {
|
|
|
96505
97076
|
standards.push(...qtiAlignmentStandardsFromMetadata2(metadata2));
|
|
96506
97077
|
return dedupeStandards2(standards);
|
|
96507
97078
|
}
|
|
96508
|
-
var EVENT_HANDLER_ATTRIBUTE, SCRIPT_SCHEMES2, DATA_RASTER_IMAGE_PATTERN2, QTI_MATHML_ALLOWED_TAGS2, metadataSymbol2, parser2, SUPPORTED_QTI_INTERACTION_TYPES2, URL_ATTRIBUTES3, IMAGE_URL_ATTRIBUTES3, INTERACTION_TYPES2, XML_NAMED_ENTITIES, EMPHASIS_TAGS2, STRONG_TAGS2, CONTENT_SKIPPED_TAGS2, BLOCK_CONTENT_KINDS2, GAP_MATCH_TOKEN_TAGS2, STAGE_GRAPHIC_EXCLUDED_CONTAINERS2, BLANK_SENTINEL2 = "", MARKED_BLANK_PATTERN2, MARKED_BLANK_GLOBAL_PATTERN2, REGION_SHAPE_COORDS, QTI_NAMESPACE = "http://www.imsglobal.org/xsd/imsqtiasi_v3p0", QTI_AUTHORING_BLANK_MARKER = "___", MATCH_CORRECT_TEMPLATE2 = "http://www.imsglobal.org/question/qti_v3p0/rptemplates/match_correct", MAP_RESPONSE_TEMPLATE2 = "https://purl.imsglobal.org/spec/qti/v3p0/rptemplates/map_response", MAP_RESPONSE_TEMPLATES2, RESPONSE_PROCESSING_TEMPLATES2, GLOBAL_BODY_ATTRIBUTES, BODY_ELEMENT_ATTRIBUTES, MATHML_ATTRIBUTES, VOID_BODY_ELEMENTS2, URL_ATTRIBUTES22, IMAGE_URL_ATTRIBUTES22, INTERACTION_ATTRIBUTES, CHOICE_ATTRIBUTES, CHOICE_ELEMENT_ATTRIBUTES, VOID_CHOICE_ELEMENTS2, NUMERIC_COMPARISON_ATTRIBUTES2, RESPONSE_CARDINALITIES2, RESPONSE_BASE_TYPES2, BLANK_SENTINEL22 = "", PLAYABLE_POINT_VALUE, POINT_INTERACTION_TYPES2, INDEPENDENT_PROCESSING_TAGS2, SUPPORTED_BASE_TYPES2, SUPPORTED_CARDINALITIES2, MATCH_CORRECT_TEMPLATES2,
|
|
97079
|
+
var EVENT_HANDLER_ATTRIBUTE, SCRIPT_SCHEMES2, DATA_RASTER_IMAGE_PATTERN2, QTI_MATHML_ALLOWED_TAGS2, metadataSymbol2, parser2, SUPPORTED_QTI_INTERACTION_TYPES2, URL_ATTRIBUTES3, IMAGE_URL_ATTRIBUTES3, INTERACTION_TYPES2, XML_NAMED_ENTITIES, EMPHASIS_TAGS2, STRONG_TAGS2, CONTENT_SKIPPED_TAGS2, BLOCK_CONTENT_KINDS2, GAP_MATCH_TOKEN_TAGS2, STAGE_GRAPHIC_EXCLUDED_CONTAINERS2, BLANK_SENTINEL2 = "", MARKED_BLANK_PATTERN2, MARKED_BLANK_GLOBAL_PATTERN2, REGION_SHAPE_COORDS, QTI_NAMESPACE = "http://www.imsglobal.org/xsd/imsqtiasi_v3p0", QTI_AUTHORING_BLANK_MARKER = "___", MATCH_CORRECT_TEMPLATE2 = "http://www.imsglobal.org/question/qti_v3p0/rptemplates/match_correct", MAP_RESPONSE_TEMPLATE2 = "https://purl.imsglobal.org/spec/qti/v3p0/rptemplates/map_response", MAP_RESPONSE_TEMPLATES2, RESPONSE_PROCESSING_TEMPLATES2, GLOBAL_BODY_ATTRIBUTES, BODY_ELEMENT_ATTRIBUTES, MATHML_ATTRIBUTES, VOID_BODY_ELEMENTS2, URL_ATTRIBUTES22, IMAGE_URL_ATTRIBUTES22, INTERACTION_ATTRIBUTES, CHOICE_ATTRIBUTES, CHOICE_ELEMENT_ATTRIBUTES, VOID_CHOICE_ELEMENTS2, NUMERIC_COMPARISON_ATTRIBUTES2, RESPONSE_CARDINALITIES2, RESPONSE_BASE_TYPES2, BLANK_SENTINEL22 = "", QTI_FLOAT_VALUE, PLAYABLE_POINT_VALUE, POINT_INTERACTION_TYPES2, INDEPENDENT_PROCESSING_TAGS2, PRIVATE_PLAYABLE_KEYS2, trimmedNonEmptyString2, finiteNumber22, positiveFiniteNumber2, IDENTIFIER_VALUE2, DIRECTED_PAIR_VALUE2, scoringMatchComparisonSchema2, scoringNumericEqualComparisonSchema2, scoringNumericEqualRoundedComparisonSchema2, scoringRuleSchema2, scoringRulesSchema2, scoringItemSchema2, assessmentScoringArtifactItemsSchema2, assessmentScoringArtifactSchema2, SUPPORTED_BASE_TYPES2, SUPPORTED_CARDINALITIES2, MATCH_CORRECT_TEMPLATES2, QtiScoringArtifactValidationError, COMMON_CORE_MATH_FRAMEWORK_ALIASES2, COMMON_CORE_ELA_FRAMEWORK_ALIASES2, COMMON_CORE_FRAMEWORK_ALIASES2;
|
|
96509
97080
|
var init_qti = __esm(() => {
|
|
96510
97081
|
init_timeback3();
|
|
96511
97082
|
init_timeback3();
|
|
@@ -96513,6 +97084,7 @@ var init_qti = __esm(() => {
|
|
|
96513
97084
|
init_timeback3();
|
|
96514
97085
|
init_timeback3();
|
|
96515
97086
|
init_timeback3();
|
|
97087
|
+
init_esm();
|
|
96516
97088
|
EVENT_HANDLER_ATTRIBUTE = /^on/i;
|
|
96517
97089
|
SCRIPT_SCHEMES2 = {
|
|
96518
97090
|
javascript: ["java", "script:"].join(""),
|
|
@@ -96737,6 +97309,7 @@ var init_qti = __esm(() => {
|
|
|
96737
97309
|
"string",
|
|
96738
97310
|
"uri"
|
|
96739
97311
|
]);
|
|
97312
|
+
QTI_FLOAT_VALUE = /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?$/i;
|
|
96740
97313
|
PLAYABLE_POINT_VALUE = /^-?\d+(?:\.\d+)? -?\d+(?:\.\d+)?$/;
|
|
96741
97314
|
POINT_INTERACTION_TYPES2 = new Set([
|
|
96742
97315
|
"select-point",
|
|
@@ -96755,6 +97328,93 @@ var init_qti = __esm(() => {
|
|
|
96755
97328
|
"qti-sum",
|
|
96756
97329
|
"qti-base-value"
|
|
96757
97330
|
]);
|
|
97331
|
+
PRIVATE_PLAYABLE_KEYS2 = new Set([
|
|
97332
|
+
"answer",
|
|
97333
|
+
"answerkey",
|
|
97334
|
+
"correctanswer",
|
|
97335
|
+
"correctresponse",
|
|
97336
|
+
"correctresponses",
|
|
97337
|
+
"correct",
|
|
97338
|
+
"correctvalue",
|
|
97339
|
+
"correctvalues",
|
|
97340
|
+
"iscorrect",
|
|
97341
|
+
"responseprocessing",
|
|
97342
|
+
"rule",
|
|
97343
|
+
"rules",
|
|
97344
|
+
"scoring",
|
|
97345
|
+
"scoringrules",
|
|
97346
|
+
"solution",
|
|
97347
|
+
"expected",
|
|
97348
|
+
"expectedanswer",
|
|
97349
|
+
"expectedvalue",
|
|
97350
|
+
"expectedvalues"
|
|
97351
|
+
]);
|
|
97352
|
+
trimmedNonEmptyString2 = exports_external.string().min(1).refine((value) => value === value.trim());
|
|
97353
|
+
finiteNumber22 = exports_external.number().finite();
|
|
97354
|
+
positiveFiniteNumber2 = finiteNumber22.positive();
|
|
97355
|
+
IDENTIFIER_VALUE2 = /^[A-Za-z_][\w.-]*$/;
|
|
97356
|
+
DIRECTED_PAIR_VALUE2 = /^[A-Za-z_][\w.-]* [A-Za-z_][\w.-]*$/;
|
|
97357
|
+
scoringMatchComparisonSchema2 = exports_external.object({
|
|
97358
|
+
kind: exports_external.literal("match"),
|
|
97359
|
+
correctValues: exports_external.array(trimmedNonEmptyString2).min(1).refine((values) => new Set(values).size === values.length)
|
|
97360
|
+
}).strict();
|
|
97361
|
+
scoringNumericEqualComparisonSchema2 = exports_external.object({ kind: exports_external.literal("numeric-equal"), correctValue: finiteNumber22 }).strict();
|
|
97362
|
+
scoringNumericEqualRoundedComparisonSchema2 = exports_external.object({
|
|
97363
|
+
kind: exports_external.literal("numeric-equal-rounded"),
|
|
97364
|
+
correctValue: finiteNumber22,
|
|
97365
|
+
roundingMode: exports_external.enum(["decimalPlaces", "significantFigures"]),
|
|
97366
|
+
figures: exports_external.number().int().safe().nonnegative()
|
|
97367
|
+
}).strict();
|
|
97368
|
+
scoringRuleSchema2 = exports_external.object({
|
|
97369
|
+
responseIdentifier: trimmedNonEmptyString2,
|
|
97370
|
+
cardinality: exports_external.enum(["single", "multiple", "ordered"]),
|
|
97371
|
+
baseType: exports_external.enum(["string", "identifier", "integer", "float", "directedPair"]),
|
|
97372
|
+
points: positiveFiniteNumber2,
|
|
97373
|
+
comparison: exports_external.discriminatedUnion("kind", [
|
|
97374
|
+
scoringMatchComparisonSchema2,
|
|
97375
|
+
scoringNumericEqualComparisonSchema2,
|
|
97376
|
+
scoringNumericEqualRoundedComparisonSchema2
|
|
97377
|
+
])
|
|
97378
|
+
}).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) => {
|
|
97379
|
+
if (rule.comparison.kind !== "match") {
|
|
97380
|
+
return rule.baseType !== "integer" || Number.isSafeInteger(rule.comparison.correctValue);
|
|
97381
|
+
}
|
|
97382
|
+
if (rule.baseType === "integer" || rule.baseType === "float") {
|
|
97383
|
+
return rule.comparison.correctValues.every((value) => validNumericMatchValue2(value, rule.baseType));
|
|
97384
|
+
}
|
|
97385
|
+
if (rule.baseType === "identifier") {
|
|
97386
|
+
return rule.comparison.correctValues.every((value) => IDENTIFIER_VALUE2.test(value));
|
|
97387
|
+
}
|
|
97388
|
+
return rule.baseType !== "directedPair" || rule.comparison.correctValues.every((value) => DIRECTED_PAIR_VALUE2.test(value));
|
|
97389
|
+
});
|
|
97390
|
+
scoringRulesSchema2 = exports_external.array(scoringRuleSchema2).min(1).refine((rules) => new Set(rules.map((rule) => rule.responseIdentifier)).size === rules.length);
|
|
97391
|
+
scoringItemSchema2 = exports_external.discriminatedUnion("strategy", [
|
|
97392
|
+
exports_external.object({
|
|
97393
|
+
strategy: exports_external.literal("qti"),
|
|
97394
|
+
itemIdentifier: trimmedNonEmptyString2,
|
|
97395
|
+
maxScore: positiveFiniteNumber2,
|
|
97396
|
+
reason: exports_external.enum([
|
|
97397
|
+
"response-mapping",
|
|
97398
|
+
"area-mapping",
|
|
97399
|
+
"unsupported-base-type",
|
|
97400
|
+
"unsupported-cardinality",
|
|
97401
|
+
"invalid-correct-response",
|
|
97402
|
+
"no-scorable-response",
|
|
97403
|
+
"unsupported-processing-template",
|
|
97404
|
+
"unsupported-response-processing",
|
|
97405
|
+
"cross-response-processing",
|
|
97406
|
+
"inconsistent-max-score"
|
|
97407
|
+
])
|
|
97408
|
+
}).strict(),
|
|
97409
|
+
exports_external.object({
|
|
97410
|
+
strategy: exports_external.literal("platform"),
|
|
97411
|
+
itemIdentifier: trimmedNonEmptyString2,
|
|
97412
|
+
maxScore: positiveFiniteNumber2,
|
|
97413
|
+
rules: scoringRulesSchema2
|
|
97414
|
+
}).strict()
|
|
97415
|
+
]).refine((item) => item.strategy === "qti" || sameScore2(item.rules.reduce((total, rule) => total + rule.points, 0), item.maxScore));
|
|
97416
|
+
assessmentScoringArtifactItemsSchema2 = exports_external.array(scoringItemSchema2).refine((items) => new Set(items.map((item) => item.itemIdentifier)).size === items.length);
|
|
97417
|
+
assessmentScoringArtifactSchema2 = exports_external.object({ items: assessmentScoringArtifactItemsSchema2 }).strict();
|
|
96758
97418
|
SUPPORTED_BASE_TYPES2 = new Set([
|
|
96759
97419
|
"string",
|
|
96760
97420
|
"identifier",
|
|
@@ -96772,7 +97432,6 @@ var init_qti = __esm(() => {
|
|
|
96772
97432
|
"https://www.imsglobal.org/question/qti_v3p0/rptemplates/match_correct",
|
|
96773
97433
|
"https://purl.imsglobal.org/spec/qti/v3p0/rptemplates/match_correct"
|
|
96774
97434
|
].flatMap((template) => [template, `${template}.xml`]));
|
|
96775
|
-
DIRECTED_PAIR_VALUE = /^[A-Za-z_][\w.-]* [A-Za-z_][\w.-]*$/;
|
|
96776
97435
|
QtiScoringArtifactValidationError = class QtiScoringArtifactValidationError extends Error {
|
|
96777
97436
|
constructor(message) {
|
|
96778
97437
|
super(message);
|
|
@@ -99140,50 +99799,38 @@ var init_timeback_admin_service = __esm(async () => {
|
|
|
99140
99799
|
});
|
|
99141
99800
|
|
|
99142
99801
|
// ../api-core/src/utils/assessment-artifact-runtime.util.ts
|
|
99143
|
-
function normalizedKey(key) {
|
|
99144
|
-
return key.replaceAll(/[^a-z0-9]/gi, "").toLowerCase();
|
|
99145
|
-
}
|
|
99146
|
-
function containsPrivatePlayableKey(value) {
|
|
99147
|
-
if (Array.isArray(value)) {
|
|
99148
|
-
return value.some(containsPrivatePlayableKey);
|
|
99149
|
-
}
|
|
99150
|
-
if (!isRecord(value)) {
|
|
99151
|
-
return false;
|
|
99152
|
-
}
|
|
99153
|
-
return Object.entries(value).some(([key, entry]) => PRIVATE_PLAYABLE_KEYS.has(normalizedKey(key)) || containsPrivatePlayableKey(entry));
|
|
99154
|
-
}
|
|
99155
99802
|
function playableSchema(identity) {
|
|
99156
99803
|
return exports_external.object({
|
|
99157
99804
|
contractVersion: exports_external.literal(PLAYCADEMY_ASSESSMENT_CONTRACT_VERSION),
|
|
99158
99805
|
identifier: exports_external.literal(identity.qtiTestIdentifier),
|
|
99159
99806
|
contentRevision: exports_external.literal(identity.kind === "assessment" ? identity.contentRevision : identity.sourceContentRevision),
|
|
99160
|
-
title:
|
|
99807
|
+
title: trimmedNonEmptyString3,
|
|
99161
99808
|
items: exports_external.array(playableItemSchema)
|
|
99162
99809
|
}).strict();
|
|
99163
99810
|
}
|
|
99164
99811
|
function scoringSchema(identity) {
|
|
99165
99812
|
if (identity.kind === "assessment") {
|
|
99166
99813
|
return exports_external.object({
|
|
99167
|
-
items:
|
|
99814
|
+
items: assessmentScoringArtifactItemsSchema2,
|
|
99168
99815
|
reviewBank: exports_external.undefined(),
|
|
99169
99816
|
diagnostic: diagnosticContextSchema.optional()
|
|
99170
|
-
});
|
|
99817
|
+
}).strict();
|
|
99171
99818
|
}
|
|
99172
99819
|
return exports_external.object({
|
|
99173
|
-
items:
|
|
99820
|
+
items: assessmentScoringArtifactItemsSchema2,
|
|
99174
99821
|
reviewBank: exports_external.object({
|
|
99175
99822
|
sourceContentRevision: exports_external.literal(identity.sourceContentRevision),
|
|
99176
99823
|
bankRevision: exports_external.literal(identity.bankRevision),
|
|
99177
99824
|
items: exports_external.array(exports_external.object({
|
|
99178
|
-
itemIdentifier:
|
|
99825
|
+
itemIdentifier: trimmedNonEmptyString3,
|
|
99179
99826
|
standards: exports_external.array(exports_external.unknown())
|
|
99180
|
-
}))
|
|
99181
|
-
}),
|
|
99827
|
+
}).strict())
|
|
99828
|
+
}).strict(),
|
|
99182
99829
|
diagnostic: exports_external.undefined()
|
|
99183
|
-
}).refine((payload) => payload.reviewBank.items.length === payload.items.length && payload.reviewBank.items.every((item, index2) => item.itemIdentifier === payload.items[index2]?.itemIdentifier));
|
|
99830
|
+
}).strict().refine((payload) => payload.reviewBank.items.length === payload.items.length && payload.reviewBank.items.every((item, index2) => item.itemIdentifier === payload.items[index2]?.itemIdentifier));
|
|
99184
99831
|
}
|
|
99185
99832
|
function assertPlayableAssessmentArtifact(payload, identity) {
|
|
99186
|
-
if (!playableSchema(identity).safeParse(payload).success ||
|
|
99833
|
+
if (!playableSchema(identity).safeParse(payload).success || containsPrivatePlayableAssessmentData(payload)) {
|
|
99187
99834
|
throw new ValidationError("Playable assessment artifact payload is invalid or private");
|
|
99188
99835
|
}
|
|
99189
99836
|
}
|
|
@@ -99193,8 +99840,11 @@ function assertScoringAssessmentArtifact(payload, identity) {
|
|
|
99193
99840
|
}
|
|
99194
99841
|
}
|
|
99195
99842
|
function assertScoringArtifactMatchesPlayable(assessment, scoring, identity) {
|
|
99196
|
-
const scoringByIdentifier = new Map(scoring.items.map((item) => [item.itemIdentifier, item
|
|
99197
|
-
const playableMatches = assessment.items.every((item) =>
|
|
99843
|
+
const scoringByIdentifier = new Map(scoring.items.map((item) => [item.itemIdentifier, item]));
|
|
99844
|
+
const playableMatches = assessment.items.every((item) => {
|
|
99845
|
+
const scoringItem = scoringByIdentifier.get(item.identifier);
|
|
99846
|
+
return scoringItem !== undefined && scoringItemMatchesPlayableItem(scoringItem, item);
|
|
99847
|
+
});
|
|
99198
99848
|
const hasExactManagedMembership = identity.kind === "review-bank" || scoring.items.length === assessment.items.length;
|
|
99199
99849
|
if (!playableMatches || !hasExactManagedMembership) {
|
|
99200
99850
|
throw new ValidationError("Scoring artifact does not match the playable assessment");
|
|
@@ -99232,73 +99882,54 @@ function pinnedReviewBankFromAttempt(metadata2, bankIdentifier) {
|
|
|
99232
99882
|
}))
|
|
99233
99883
|
};
|
|
99234
99884
|
}
|
|
99235
|
-
var
|
|
99885
|
+
var trimmedNonEmptyString3, finiteNumber3, positiveFiniteNumber3, nonnegativeInteger, playableContentNodesSchema, playableChoiceSchema, playableMatchChoiceSchema, playableGraphicSchema, playableHotspotSchema, playableGapTextSchema, playableGapImageSchema, playableInteractionBase, playableInteractionSchema, playableItemSchema, diagnosticContextSchema;
|
|
99236
99886
|
var init_assessment_artifact_runtime_util = __esm(() => {
|
|
99237
99887
|
init_esm();
|
|
99238
99888
|
init_src();
|
|
99889
|
+
init_qti();
|
|
99239
99890
|
init_errors2();
|
|
99240
|
-
|
|
99241
|
-
|
|
99242
|
-
|
|
99243
|
-
"correctanswer",
|
|
99244
|
-
"correctresponse",
|
|
99245
|
-
"correctresponses",
|
|
99246
|
-
"correct",
|
|
99247
|
-
"correctvalue",
|
|
99248
|
-
"correctvalues",
|
|
99249
|
-
"iscorrect",
|
|
99250
|
-
"responseprocessing",
|
|
99251
|
-
"rule",
|
|
99252
|
-
"rules",
|
|
99253
|
-
"scoring",
|
|
99254
|
-
"scoringrules",
|
|
99255
|
-
"solution",
|
|
99256
|
-
"expected",
|
|
99257
|
-
"expectedanswer",
|
|
99258
|
-
"expectedvalue",
|
|
99259
|
-
"expectedvalues"
|
|
99260
|
-
]);
|
|
99261
|
-
trimmedNonEmptyString = exports_external.string().min(1).refine((value) => value === value.trim());
|
|
99262
|
-
finiteNumber2 = exports_external.number().finite();
|
|
99891
|
+
trimmedNonEmptyString3 = exports_external.string().min(1).refine((value) => value === value.trim());
|
|
99892
|
+
finiteNumber3 = exports_external.number().finite();
|
|
99893
|
+
positiveFiniteNumber3 = finiteNumber3.positive();
|
|
99263
99894
|
nonnegativeInteger = exports_external.number().int().nonnegative();
|
|
99264
99895
|
playableContentNodesSchema = exports_external.array(exports_external.unknown());
|
|
99265
99896
|
playableChoiceSchema = exports_external.object({
|
|
99266
|
-
identifier:
|
|
99897
|
+
identifier: trimmedNonEmptyString3,
|
|
99267
99898
|
content: exports_external.string(),
|
|
99268
99899
|
contentNodes: playableContentNodesSchema.optional()
|
|
99269
99900
|
}).strict();
|
|
99270
99901
|
playableMatchChoiceSchema = exports_external.object({
|
|
99271
|
-
identifier:
|
|
99902
|
+
identifier: trimmedNonEmptyString3,
|
|
99272
99903
|
content: exports_external.string(),
|
|
99273
99904
|
contentNodes: playableContentNodesSchema.optional(),
|
|
99274
99905
|
matchMax: nonnegativeInteger
|
|
99275
99906
|
}).strict();
|
|
99276
99907
|
playableGraphicSchema = exports_external.object({
|
|
99277
|
-
src:
|
|
99278
|
-
width:
|
|
99279
|
-
height:
|
|
99908
|
+
src: trimmedNonEmptyString3,
|
|
99909
|
+
width: finiteNumber3.optional(),
|
|
99910
|
+
height: finiteNumber3.optional(),
|
|
99280
99911
|
description: exports_external.string().optional()
|
|
99281
99912
|
}).strict();
|
|
99282
99913
|
playableHotspotSchema = exports_external.object({
|
|
99283
|
-
identifier:
|
|
99914
|
+
identifier: trimmedNonEmptyString3,
|
|
99284
99915
|
shape: exports_external.enum(["circle", "ellipse", "rect", "poly", "default"]),
|
|
99285
|
-
coords: exports_external.array(
|
|
99916
|
+
coords: exports_external.array(finiteNumber3),
|
|
99286
99917
|
matchMax: nonnegativeInteger.optional(),
|
|
99287
99918
|
description: exports_external.string().optional()
|
|
99288
99919
|
}).strict();
|
|
99289
99920
|
playableGapTextSchema = exports_external.object({
|
|
99290
|
-
identifier:
|
|
99921
|
+
identifier: trimmedNonEmptyString3,
|
|
99291
99922
|
content: exports_external.string(),
|
|
99292
99923
|
matchMax: nonnegativeInteger
|
|
99293
99924
|
}).strict();
|
|
99294
99925
|
playableGapImageSchema = exports_external.object({
|
|
99295
|
-
identifier:
|
|
99926
|
+
identifier: trimmedNonEmptyString3,
|
|
99296
99927
|
image: playableGraphicSchema.optional(),
|
|
99297
99928
|
matchMax: nonnegativeInteger,
|
|
99298
99929
|
description: exports_external.string().optional()
|
|
99299
99930
|
}).strict();
|
|
99300
99931
|
playableInteractionBase = {
|
|
99301
|
-
responseIdentifier:
|
|
99932
|
+
responseIdentifier: trimmedNonEmptyString3,
|
|
99302
99933
|
prompt: exports_external.string().optional()
|
|
99303
99934
|
};
|
|
99304
99935
|
playableInteractionSchema = exports_external.discriminatedUnion("type", [
|
|
@@ -99338,7 +99969,7 @@ var init_assessment_artifact_runtime_util = __esm(() => {
|
|
|
99338
99969
|
...playableInteractionBase,
|
|
99339
99970
|
type: exports_external.literal("hottext"),
|
|
99340
99971
|
segments: exports_external.array(exports_external.object({
|
|
99341
|
-
identifier:
|
|
99972
|
+
identifier: trimmedNonEmptyString3.optional(),
|
|
99342
99973
|
content: exports_external.string(),
|
|
99343
99974
|
selectable: exports_external.boolean()
|
|
99344
99975
|
}).strict()),
|
|
@@ -99358,7 +99989,7 @@ var init_assessment_artifact_runtime_util = __esm(() => {
|
|
|
99358
99989
|
content: exports_external.string(),
|
|
99359
99990
|
contentNodes: playableContentNodesSchema,
|
|
99360
99991
|
gapTexts: exports_external.array(playableGapTextSchema),
|
|
99361
|
-
gaps: exports_external.array(
|
|
99992
|
+
gaps: exports_external.array(trimmedNonEmptyString3),
|
|
99362
99993
|
maxAssociations: nonnegativeInteger
|
|
99363
99994
|
}).strict(),
|
|
99364
99995
|
exports_external.object({
|
|
@@ -99392,59 +100023,17 @@ var init_assessment_artifact_runtime_util = __esm(() => {
|
|
|
99392
100023
|
}).strict()
|
|
99393
100024
|
]);
|
|
99394
100025
|
playableItemSchema = exports_external.object({
|
|
99395
|
-
identifier:
|
|
100026
|
+
identifier: trimmedNonEmptyString3,
|
|
99396
100027
|
title: exports_external.string(),
|
|
99397
100028
|
prompt: exports_external.string(),
|
|
99398
100029
|
promptContent: playableContentNodesSchema.optional(),
|
|
99399
|
-
maxScore:
|
|
100030
|
+
maxScore: positiveFiniteNumber3,
|
|
99400
100031
|
interactions: exports_external.array(playableInteractionSchema)
|
|
99401
100032
|
}).strict();
|
|
99402
|
-
scoringRuleSchema = exports_external.object({
|
|
99403
|
-
responseIdentifier: trimmedNonEmptyString,
|
|
99404
|
-
cardinality: exports_external.enum(["single", "multiple", "ordered"]),
|
|
99405
|
-
baseType: exports_external.enum(["string", "identifier", "integer", "float", "directedPair"]),
|
|
99406
|
-
points: finiteNumber2,
|
|
99407
|
-
comparison: exports_external.discriminatedUnion("kind", [
|
|
99408
|
-
exports_external.object({ kind: exports_external.literal("match"), correctValues: exports_external.array(exports_external.string()) }),
|
|
99409
|
-
exports_external.object({ kind: exports_external.literal("numeric-equal"), correctValue: finiteNumber2 }),
|
|
99410
|
-
exports_external.object({
|
|
99411
|
-
kind: exports_external.literal("numeric-equal-rounded"),
|
|
99412
|
-
correctValue: finiteNumber2,
|
|
99413
|
-
roundingMode: exports_external.enum(["decimalPlaces", "significantFigures"]),
|
|
99414
|
-
figures: exports_external.number().int().nonnegative()
|
|
99415
|
-
})
|
|
99416
|
-
])
|
|
99417
|
-
});
|
|
99418
|
-
scoringItemSchema = exports_external.discriminatedUnion("strategy", [
|
|
99419
|
-
exports_external.object({
|
|
99420
|
-
strategy: exports_external.literal("qti"),
|
|
99421
|
-
itemIdentifier: trimmedNonEmptyString,
|
|
99422
|
-
maxScore: finiteNumber2,
|
|
99423
|
-
reason: exports_external.enum([
|
|
99424
|
-
"response-mapping",
|
|
99425
|
-
"area-mapping",
|
|
99426
|
-
"unsupported-base-type",
|
|
99427
|
-
"unsupported-cardinality",
|
|
99428
|
-
"invalid-correct-response",
|
|
99429
|
-
"no-scorable-response",
|
|
99430
|
-
"unsupported-processing-template",
|
|
99431
|
-
"unsupported-response-processing",
|
|
99432
|
-
"cross-response-processing",
|
|
99433
|
-
"inconsistent-max-score"
|
|
99434
|
-
])
|
|
99435
|
-
}),
|
|
99436
|
-
exports_external.object({
|
|
99437
|
-
strategy: exports_external.literal("platform"),
|
|
99438
|
-
itemIdentifier: trimmedNonEmptyString,
|
|
99439
|
-
maxScore: finiteNumber2,
|
|
99440
|
-
rules: exports_external.array(scoringRuleSchema)
|
|
99441
|
-
})
|
|
99442
|
-
]);
|
|
99443
|
-
uniqueScoringItemsSchema = exports_external.array(scoringItemSchema).refine((items) => new Set(items.map((item) => item.itemIdentifier)).size === items.length);
|
|
99444
100033
|
diagnosticContextSchema = exports_external.object({
|
|
99445
|
-
routingRevision:
|
|
100034
|
+
routingRevision: trimmedNonEmptyString3,
|
|
99446
100035
|
routingManifest: exports_external.record(exports_external.unknown())
|
|
99447
|
-
});
|
|
100036
|
+
}).strict();
|
|
99448
100037
|
});
|
|
99449
100038
|
|
|
99450
100039
|
// ../api-core/src/utils/assessment-artifact-loader.util.ts
|
|
@@ -100366,16 +100955,8 @@ function prepareDiagnosticAssessmentResponses(assessment, current, input) {
|
|
|
100366
100955
|
const update2 = { [input.itemIdentifier]: input.responses };
|
|
100367
100956
|
validateAssessmentResponseUpdate(assessment, update2);
|
|
100368
100957
|
const responses = applyAssessmentResponseUpdate(current, update2);
|
|
100369
|
-
const item = assessment.items.find((candidate) => candidate.identifier === input.itemIdentifier);
|
|
100370
100958
|
const itemResponses = responses[input.itemIdentifier];
|
|
100371
|
-
|
|
100372
|
-
if (!item || !itemResponses || missingResponse) {
|
|
100373
|
-
throw new AssessmentRuntimeError("INVALID_RESPONSE", `Diagnostic item ${input.itemIdentifier} requires a response for every interaction.`, {
|
|
100374
|
-
itemIdentifier: input.itemIdentifier,
|
|
100375
|
-
...missingResponse ? { responseIdentifier: missingResponse.responseIdentifier } : {}
|
|
100376
|
-
});
|
|
100377
|
-
}
|
|
100378
|
-
validateAssessmentResponses(assessment, { [input.itemIdentifier]: itemResponses });
|
|
100959
|
+
validateAssessmentDraftResponses(assessment, itemResponses ? { [input.itemIdentifier]: itemResponses } : {});
|
|
100379
100960
|
return responses;
|
|
100380
100961
|
}
|
|
100381
100962
|
function validateAssessmentResponseUpdate(assessment, update2) {
|
|
@@ -101839,6 +102420,164 @@ var init_timeback_assessment_artifacts_util = __esm(() => {
|
|
|
101839
102420
|
init_timeback_review_mapping_util();
|
|
101840
102421
|
});
|
|
101841
102422
|
|
|
102423
|
+
// ../api-core/src/utils/timeback-assessment-child-result.util.ts
|
|
102424
|
+
async function buildSubmissionChildResultCommand(input) {
|
|
102425
|
+
const { submission, context: context2 } = input;
|
|
102426
|
+
const { isCorrect } = submission;
|
|
102427
|
+
const common2 = {
|
|
102428
|
+
attempt: input.attempt,
|
|
102429
|
+
administration: {
|
|
102430
|
+
submissionId: submission.submissionId,
|
|
102431
|
+
responseVersion: submission.responseVersion,
|
|
102432
|
+
itemIdentifier: submission.itemIdentifier
|
|
102433
|
+
},
|
|
102434
|
+
responses: input.responses ?? {},
|
|
102435
|
+
score: submission.score,
|
|
102436
|
+
grading: input.grading
|
|
102437
|
+
};
|
|
102438
|
+
if (context2.kind === "review") {
|
|
102439
|
+
return buildAssessmentChildResultCommand({
|
|
102440
|
+
...common2,
|
|
102441
|
+
...typeof isCorrect === "boolean" ? { isCorrect } : {},
|
|
102442
|
+
context: context2
|
|
102443
|
+
}, submission.submittedAt);
|
|
102444
|
+
}
|
|
102445
|
+
if (typeof isCorrect !== "boolean") {
|
|
102446
|
+
throw new Error("Diagnostic child results require determinate correctness");
|
|
102447
|
+
}
|
|
102448
|
+
return buildAssessmentChildResultCommand({ ...common2, isCorrect, context: context2 }, submission.submittedAt);
|
|
102449
|
+
}
|
|
102450
|
+
function reviewDefinitionWithoutStandards(definition) {
|
|
102451
|
+
return Object.fromEntries(Object.entries(definition).filter(([field]) => field !== "standards"));
|
|
102452
|
+
}
|
|
102453
|
+
function assessmentChildLineItemMetadataMatches(key, stored, expected) {
|
|
102454
|
+
if (key !== "playcademyReviewQuestionDefinition" || typeof stored !== "object" || stored === null || typeof expected !== "object" || expected === null) {
|
|
102455
|
+
return canonicalJson2(stored) === canonicalJson2(expected);
|
|
102456
|
+
}
|
|
102457
|
+
const storedDefinition = stored;
|
|
102458
|
+
const expectedDefinition = expected;
|
|
102459
|
+
const storedStandards = storedDefinition.standards;
|
|
102460
|
+
const expectedStandards = expectedDefinition.standards;
|
|
102461
|
+
if (!Array.isArray(storedStandards) || !Array.isArray(expectedStandards)) {
|
|
102462
|
+
return false;
|
|
102463
|
+
}
|
|
102464
|
+
const sameDefinition = canonicalJson2(reviewDefinitionWithoutStandards(storedDefinition)) === canonicalJson2(reviewDefinitionWithoutStandards(expectedDefinition));
|
|
102465
|
+
function contains(standards, candidate) {
|
|
102466
|
+
return standards.some((standard) => canonicalJson2(standard) === canonicalJson2(candidate));
|
|
102467
|
+
}
|
|
102468
|
+
return sameDefinition && (expectedStandards.every((standard) => contains(storedStandards, standard)) || storedStandards.every((standard) => contains(expectedStandards, standard)));
|
|
102469
|
+
}
|
|
102470
|
+
function assessmentChildLineItemMatches(stored, expected) {
|
|
102471
|
+
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) {
|
|
102472
|
+
return false;
|
|
102473
|
+
}
|
|
102474
|
+
return Object.entries(expected.metadata).every(([key, value]) => assessmentChildLineItemMetadataMatches(key, stored.metadata[key], value));
|
|
102475
|
+
}
|
|
102476
|
+
function buildAssessmentChildResultUpdate(command, childLineItemId) {
|
|
102477
|
+
return {
|
|
102478
|
+
status: "active",
|
|
102479
|
+
assessmentLineItem: { sourcedId: childLineItemId },
|
|
102480
|
+
student: { sourcedId: command.payload.attempt.studentId },
|
|
102481
|
+
score: command.payload.score.earned,
|
|
102482
|
+
scoreDate: command.recordedAt,
|
|
102483
|
+
...ASSESSMENT_ATTEMPT_COMPLETED,
|
|
102484
|
+
metadata: {
|
|
102485
|
+
[PLAYCADEMY_ASSESSMENT_ITEM_RESULT_METADATA_KEY]: command
|
|
102486
|
+
}
|
|
102487
|
+
};
|
|
102488
|
+
}
|
|
102489
|
+
async function classifyAssessmentChildResult(input) {
|
|
102490
|
+
const { result } = input;
|
|
102491
|
+
if (typeof result.metadata !== "object" || result.metadata === null || !(PLAYCADEMY_ASSESSMENT_ITEM_RESULT_METADATA_KEY in result.metadata)) {
|
|
102492
|
+
return { outcome: "conflict", reason: "malformed-command" };
|
|
102493
|
+
}
|
|
102494
|
+
const verification2 = await verifyAssessmentChildResultCommand(result.metadata);
|
|
102495
|
+
if (!verification2.ok) {
|
|
102496
|
+
return {
|
|
102497
|
+
outcome: "conflict",
|
|
102498
|
+
reason: verification2.reason === "payload_hash_mismatch" ? "payload-hash-mismatch" : "malformed-command"
|
|
102499
|
+
};
|
|
102500
|
+
}
|
|
102501
|
+
if (verification2.command.payloadHash !== input.command.payloadHash) {
|
|
102502
|
+
return { outcome: "conflict", reason: "payload-conflict" };
|
|
102503
|
+
}
|
|
102504
|
+
if (result.status === "tobedeleted") {
|
|
102505
|
+
return { outcome: "conflict", reason: "tombstoned" };
|
|
102506
|
+
}
|
|
102507
|
+
const expected = buildAssessmentChildResultUpdate(verification2.command, input.childLineItemId);
|
|
102508
|
+
const projectionChecks = [
|
|
102509
|
+
["status-mismatch", result.status, expected.status],
|
|
102510
|
+
["line-item-mismatch", result.assessmentLineItem?.sourcedId, input.childLineItemId],
|
|
102511
|
+
["student-mismatch", result.student?.sourcedId, expected.student.sourcedId],
|
|
102512
|
+
["score-mismatch", result.score, expected.score],
|
|
102513
|
+
["score-date-mismatch", result.scoreDate, expected.scoreDate],
|
|
102514
|
+
["score-status-mismatch", result.scoreStatus, expected.scoreStatus],
|
|
102515
|
+
["in-progress-mismatch", result.inProgress, expected.inProgress]
|
|
102516
|
+
];
|
|
102517
|
+
const mismatch = projectionChecks.find(([, stored, wanted]) => stored !== wanted);
|
|
102518
|
+
if (mismatch) {
|
|
102519
|
+
return { outcome: "conflict", reason: mismatch[0] };
|
|
102520
|
+
}
|
|
102521
|
+
const auxiliaryFields = [
|
|
102522
|
+
result.textScore,
|
|
102523
|
+
result.scoreScale,
|
|
102524
|
+
result.scorePercentile,
|
|
102525
|
+
result.comment,
|
|
102526
|
+
result.learningObjectiveSet,
|
|
102527
|
+
result.incomplete,
|
|
102528
|
+
result.late,
|
|
102529
|
+
result.missing
|
|
102530
|
+
];
|
|
102531
|
+
if (auxiliaryFields.some((value) => value !== undefined && value !== null)) {
|
|
102532
|
+
return { outcome: "conflict", reason: "auxiliary-field-mismatch" };
|
|
102533
|
+
}
|
|
102534
|
+
return { outcome: "replay" };
|
|
102535
|
+
}
|
|
102536
|
+
function hasApiStatus(error88, statusCode) {
|
|
102537
|
+
return isApiError(error88) && error88.statusCode === statusCode;
|
|
102538
|
+
}
|
|
102539
|
+
async function persistAssessmentChildResult(input) {
|
|
102540
|
+
const { assessmentResults, childLineItemId } = input;
|
|
102541
|
+
const verification2 = await verifyAssessmentChildResultCommand(input.command);
|
|
102542
|
+
if (!verification2.ok) {
|
|
102543
|
+
throw new Error(`Invalid assessment child result command: ${verification2.reason}`);
|
|
102544
|
+
}
|
|
102545
|
+
const { command } = verification2;
|
|
102546
|
+
const resultId = await assessmentChildResultId(command.payload.attempt.attemptId, command.payload.administration.submissionId);
|
|
102547
|
+
async function classifyOccupant() {
|
|
102548
|
+
const resolution = await classifyAssessmentChildResult({
|
|
102549
|
+
result: await assessmentResults.get(resultId),
|
|
102550
|
+
command,
|
|
102551
|
+
childLineItemId
|
|
102552
|
+
});
|
|
102553
|
+
return resolution.outcome === "replay" ? { outcome: "replay", resultId } : { outcome: "conflict", resultId, reason: resolution.reason };
|
|
102554
|
+
}
|
|
102555
|
+
try {
|
|
102556
|
+
return await classifyOccupant();
|
|
102557
|
+
} catch (error88) {
|
|
102558
|
+
if (!hasApiStatus(error88, 404)) {
|
|
102559
|
+
throw error88;
|
|
102560
|
+
}
|
|
102561
|
+
}
|
|
102562
|
+
try {
|
|
102563
|
+
await assessmentResults.create({
|
|
102564
|
+
sourcedId: resultId,
|
|
102565
|
+
...buildAssessmentChildResultUpdate(command, childLineItemId)
|
|
102566
|
+
});
|
|
102567
|
+
return { outcome: "created", resultId };
|
|
102568
|
+
} catch (error88) {
|
|
102569
|
+
if (!hasApiStatus(error88, 409)) {
|
|
102570
|
+
throw error88;
|
|
102571
|
+
}
|
|
102572
|
+
}
|
|
102573
|
+
return classifyOccupant();
|
|
102574
|
+
}
|
|
102575
|
+
var init_timeback_assessment_child_result_util = __esm(async () => {
|
|
102576
|
+
init_src();
|
|
102577
|
+
init_assessment_runtime2();
|
|
102578
|
+
await init_errors8();
|
|
102579
|
+
});
|
|
102580
|
+
|
|
101842
102581
|
// ../api-core/src/utils/timeback-assessment-preparation.util.ts
|
|
101843
102582
|
class AssessmentPreparationFlights {
|
|
101844
102583
|
limit;
|
|
@@ -101971,6 +102710,7 @@ function buildPreparedAssessmentMetadata(plan, timestamp6, testName) {
|
|
|
101971
102710
|
testName,
|
|
101972
102711
|
routingRevision: plan.diagnostic.routingRevision,
|
|
101973
102712
|
ledger: [],
|
|
102713
|
+
routingSnapshots: [],
|
|
101974
102714
|
state: plan.diagnostic.initialState
|
|
101975
102715
|
}
|
|
101976
102716
|
};
|
|
@@ -102024,7 +102764,7 @@ function declarationNeutralQtiXml(source) {
|
|
|
102024
102764
|
}
|
|
102025
102765
|
function managedMetadataRecord(metadata2, key) {
|
|
102026
102766
|
const managed = metadata2?.[key];
|
|
102027
|
-
return
|
|
102767
|
+
return isRecord4(managed) ? managed : null;
|
|
102028
102768
|
}
|
|
102029
102769
|
function sourceMetadata(metadata2) {
|
|
102030
102770
|
return Object.fromEntries(Object.entries(metadata2 ?? {}).filter(([key]) => !GENERATED_METADATA_KEYS.has(key)));
|
|
@@ -102225,7 +102965,7 @@ function assertManagedAssessmentPublication(test, expected) {
|
|
|
102225
102965
|
function managedAssessmentDiagnosticRoutingManifest(test, assessmentKey) {
|
|
102226
102966
|
const managed = managedMetadataRecord(test.metadata, PLAYCADEMY_MANAGED_ASSESSMENT_METADATA_KEY);
|
|
102227
102967
|
const manifest = managed?.diagnosticRoutingManifest;
|
|
102228
|
-
return managed?.assessmentKey === assessmentKey &&
|
|
102968
|
+
return managed?.assessmentKey === assessmentKey && isRecord4(manifest) ? manifest : null;
|
|
102229
102969
|
}
|
|
102230
102970
|
async function assertManagedAssessmentItem(item, expected) {
|
|
102231
102971
|
const managed = managedMetadataRecord(item.metadata, PLAYCADEMY_MANAGED_ITEM_METADATA_KEY);
|
|
@@ -102265,6 +103005,198 @@ var init_timeback_assessment_publication_util = __esm(() => {
|
|
|
102265
103005
|
MANAGED_ASSET_URL = new RegExp(`https?://[^\\s"')]+/(${ASSESSMENT_ASSET_KEY_PREFIX}v1/sha256/[a-f0-9]{64}\\.[a-z0-9]+)`, "gi");
|
|
102266
103006
|
});
|
|
102267
103007
|
|
|
103008
|
+
// ../api-core/src/utils/timeback-assessment-reconciliation.util.ts
|
|
103009
|
+
function assessmentChildProjectionIsTerminal(result) {
|
|
103010
|
+
const state = { inProgress: result.inProgress ?? "", scoreStatus: result.scoreStatus };
|
|
103011
|
+
return isAssessmentAttemptAwaitingAward(state) || isAssessmentAttemptCompleted(state);
|
|
103012
|
+
}
|
|
103013
|
+
async function reconcileReviewChildResults({
|
|
103014
|
+
initial,
|
|
103015
|
+
writeOpen,
|
|
103016
|
+
crossBarrier,
|
|
103017
|
+
readLatest,
|
|
103018
|
+
restoreTerminal
|
|
103019
|
+
}) {
|
|
103020
|
+
if (initial.metadata.purpose !== "review") {
|
|
103021
|
+
throw new Error("A standards-review request selected a non-review attempt");
|
|
103022
|
+
}
|
|
103023
|
+
if (assessmentChildProjectionIsTerminal(initial.result)) {
|
|
103024
|
+
const settled = { result: initial.result, metadata: initial.metadata };
|
|
103025
|
+
await restoreTerminal(settled);
|
|
103026
|
+
return settled;
|
|
103027
|
+
}
|
|
103028
|
+
await writeOpen();
|
|
103029
|
+
await crossBarrier();
|
|
103030
|
+
const current = await readLatest();
|
|
103031
|
+
if (current.metadata.purpose !== "review") {
|
|
103032
|
+
throw new Error("A standards-review request selected a non-review attempt");
|
|
103033
|
+
}
|
|
103034
|
+
const reviewed = { result: current.result, metadata: current.metadata };
|
|
103035
|
+
if (assessmentChildProjectionIsTerminal(current.result)) {
|
|
103036
|
+
await restoreTerminal(reviewed);
|
|
103037
|
+
}
|
|
103038
|
+
return reviewed;
|
|
103039
|
+
}
|
|
103040
|
+
var init_timeback_assessment_reconciliation_util = __esm(() => {
|
|
103041
|
+
init_assessment_runtime2();
|
|
103042
|
+
});
|
|
103043
|
+
|
|
103044
|
+
// ../api-core/src/utils/timeback-assessment-replay.util.ts
|
|
103045
|
+
function resolveDiagnosticItemReplay(metadata2, input) {
|
|
103046
|
+
const priorIndex = metadata2.itemSubmissions.findIndex((submission) => submission.submissionId === input.submissionId);
|
|
103047
|
+
if (priorIndex === -1) {
|
|
103048
|
+
return { action: "missing" };
|
|
103049
|
+
}
|
|
103050
|
+
const prior = metadata2.itemSubmissions[priorIndex];
|
|
103051
|
+
const ledgerEntry = metadata2.diagnostic.ledger[priorIndex];
|
|
103052
|
+
if (!prior || !ledgerEntry || prior.itemIdentifier !== input.itemIdentifier || ledgerEntry.routingNodeKey !== input.routingNodeKey || !itemResponseUpdateMatches(metadata2.responses[input.itemIdentifier], input.responses)) {
|
|
103053
|
+
return {
|
|
103054
|
+
action: "conflict",
|
|
103055
|
+
failure: assessmentFlowViolation("This diagnostic submission ID was already used for a different request.", {
|
|
103056
|
+
submissionId: input.submissionId,
|
|
103057
|
+
routingNodeKey: input.routingNodeKey,
|
|
103058
|
+
itemIdentifier: input.itemIdentifier
|
|
103059
|
+
})
|
|
103060
|
+
};
|
|
103061
|
+
}
|
|
103062
|
+
return {
|
|
103063
|
+
action: "replay",
|
|
103064
|
+
submission: prior,
|
|
103065
|
+
ledgerEntry,
|
|
103066
|
+
priorIndex,
|
|
103067
|
+
routing: metadata2.diagnostic.routingSnapshots?.[priorIndex]
|
|
103068
|
+
};
|
|
103069
|
+
}
|
|
103070
|
+
function buildAssessmentItemReplayResult(attemptId, metadata2, submission) {
|
|
103071
|
+
return {
|
|
103072
|
+
attemptId,
|
|
103073
|
+
responseVersion: metadata2.responseVersion,
|
|
103074
|
+
status: "in_progress",
|
|
103075
|
+
responses: metadata2.responses,
|
|
103076
|
+
itemSubmissions: metadata2.itemSubmissions,
|
|
103077
|
+
submission
|
|
103078
|
+
};
|
|
103079
|
+
}
|
|
103080
|
+
function diagnosticReceipt(submission, ledgerEntry) {
|
|
103081
|
+
return {
|
|
103082
|
+
submissionId: submission.submissionId,
|
|
103083
|
+
routingNodeKey: ledgerEntry.routingNodeKey,
|
|
103084
|
+
itemIdentifier: submission.itemIdentifier,
|
|
103085
|
+
submittedAt: submission.submittedAt,
|
|
103086
|
+
responseVersion: submission.responseVersion,
|
|
103087
|
+
answered: submission.answered,
|
|
103088
|
+
score: submission.score,
|
|
103089
|
+
isCorrect: ledgerEntry.isCorrect
|
|
103090
|
+
};
|
|
103091
|
+
}
|
|
103092
|
+
function buildDiagnosticItemReplayResult(attemptId, submission, ledgerEntry, routing) {
|
|
103093
|
+
return {
|
|
103094
|
+
attemptId,
|
|
103095
|
+
responseVersion: submission.responseVersion,
|
|
103096
|
+
status: "in_progress",
|
|
103097
|
+
routing,
|
|
103098
|
+
submission: diagnosticReceipt(submission, ledgerEntry)
|
|
103099
|
+
};
|
|
103100
|
+
}
|
|
103101
|
+
function requireAssessmentMutationAccess(attemptId, integrationStatus, enrollment) {
|
|
103102
|
+
if (!enrollment || integrationStatus === "deactivated") {
|
|
103103
|
+
throw AssessmentRuntimeError.from(assessmentAttemptUnauthorized(attemptId));
|
|
103104
|
+
}
|
|
103105
|
+
return enrollment;
|
|
103106
|
+
}
|
|
103107
|
+
function committedReviewItemResults(metadata2, assessment) {
|
|
103108
|
+
const available = new Set(assessment.items.map((item) => item.identifier));
|
|
103109
|
+
const seen = new Set;
|
|
103110
|
+
return metadata2.itemSubmissions.map((submission) => {
|
|
103111
|
+
if (!available.has(submission.itemIdentifier) || seen.has(submission.itemIdentifier)) {
|
|
103112
|
+
throw AssessmentRuntimeError.from(assessmentFlowViolation("The persisted review item-submission ledger is corrupt.", {
|
|
103113
|
+
itemIdentifier: submission.itemIdentifier
|
|
103114
|
+
}));
|
|
103115
|
+
}
|
|
103116
|
+
seen.add(submission.itemIdentifier);
|
|
103117
|
+
return {
|
|
103118
|
+
itemIdentifier: submission.itemIdentifier,
|
|
103119
|
+
score: submission.score,
|
|
103120
|
+
isCorrect: submission.isCorrect
|
|
103121
|
+
};
|
|
103122
|
+
});
|
|
103123
|
+
}
|
|
103124
|
+
var init_timeback_assessment_replay_util = __esm(() => {
|
|
103125
|
+
init_assessment_runtime2();
|
|
103126
|
+
init_errors2();
|
|
103127
|
+
});
|
|
103128
|
+
|
|
103129
|
+
// ../api-core/src/utils/timeback-assessment-routing.util.ts
|
|
103130
|
+
function diagnosticRoutingFallbackReason(artifact, routingRevision) {
|
|
103131
|
+
if (!artifact) {
|
|
103132
|
+
return "artifact-unavailable";
|
|
103133
|
+
}
|
|
103134
|
+
if (!artifact.diagnostic) {
|
|
103135
|
+
return "diagnostic-context-missing";
|
|
103136
|
+
}
|
|
103137
|
+
return artifact.diagnostic.routingRevision === routingRevision ? "routing-manifest-invalid" : "routing-revision-mismatch";
|
|
103138
|
+
}
|
|
103139
|
+
function assertDiagnosticItemsAvailable(definitionId, manifest, assessment) {
|
|
103140
|
+
const itemIdentifiers = new Set(assessment.items.map((item) => item.identifier));
|
|
103141
|
+
const unknownItem = manifest.nodes.find((node) => !itemIdentifiers.has(node.itemIdentifier));
|
|
103142
|
+
if (unknownItem) {
|
|
103143
|
+
throw new AssessmentRuntimeError("SELECTED_TEST_VERSION_UNAVAILABLE", `Diagnostic routing references unavailable item ${unknownItem.itemIdentifier}.`, {
|
|
103144
|
+
definitionId,
|
|
103145
|
+
nodeKey: unknownItem.key,
|
|
103146
|
+
itemIdentifier: unknownItem.itemIdentifier
|
|
103147
|
+
});
|
|
103148
|
+
}
|
|
103149
|
+
}
|
|
103150
|
+
function replayAttemptDiagnosticRouting(metadata2, manifest) {
|
|
103151
|
+
const replayed = replayDiagnosticRouting(manifest, metadata2.diagnostic.ledger);
|
|
103152
|
+
if (!replayed.ok || canonicalJson2(replayed.state) !== canonicalJson2(metadata2.diagnostic.state)) {
|
|
103153
|
+
throw AssessmentRuntimeError.from(assessmentFlowViolation("The persisted diagnostic routing state is corrupt.", {
|
|
103154
|
+
definitionId: metadata2.diagnostic.definitionId,
|
|
103155
|
+
...replayed.ok ? {} : { failure: replayed.failure }
|
|
103156
|
+
}));
|
|
103157
|
+
}
|
|
103158
|
+
return replayed.state;
|
|
103159
|
+
}
|
|
103160
|
+
async function resolveCompiledDiagnosticRouting(metadata2, assessment, artifact) {
|
|
103161
|
+
const diagnostic = artifact?.diagnostic;
|
|
103162
|
+
if (diagnostic?.routingRevision !== metadata2.diagnostic.routingRevision) {
|
|
103163
|
+
return null;
|
|
103164
|
+
}
|
|
103165
|
+
const initialized = initializeDiagnosticRouting(diagnostic.routingManifest);
|
|
103166
|
+
if (!initialized.ok) {
|
|
103167
|
+
return null;
|
|
103168
|
+
}
|
|
103169
|
+
const routingRevision = await diagnosticRoutingRevision(initialized.manifest);
|
|
103170
|
+
if (routingRevision !== metadata2.diagnostic.routingRevision) {
|
|
103171
|
+
return null;
|
|
103172
|
+
}
|
|
103173
|
+
assertDiagnosticItemsAvailable(metadata2.diagnostic.definitionId, initialized.manifest, assessment);
|
|
103174
|
+
return {
|
|
103175
|
+
manifest: initialized.manifest,
|
|
103176
|
+
routingRevision,
|
|
103177
|
+
state: replayAttemptDiagnosticRouting(metadata2, initialized.manifest)
|
|
103178
|
+
};
|
|
103179
|
+
}
|
|
103180
|
+
function summarizeDiagnosticSubmission(metadata2, manifest) {
|
|
103181
|
+
const state = replayAttemptDiagnosticRouting(metadata2, manifest);
|
|
103182
|
+
const tracks = diagnosticTrackResults(state);
|
|
103183
|
+
if (!tracks) {
|
|
103184
|
+
throw AssessmentRuntimeError.from(assessmentFlowViolation("Diagnostic routing must be ready to complete before final submit.", { routingStatus: state.status }));
|
|
103185
|
+
}
|
|
103186
|
+
if (metadata2.itemSubmissions.length === 0) {
|
|
103187
|
+
throw AssessmentRuntimeError.from(assessmentFlowViolation("A diagnostic cannot be finalized without an administered item."));
|
|
103188
|
+
}
|
|
103189
|
+
const possible = metadata2.itemSubmissions.reduce((total, submission) => total + submission.score.possible, 0);
|
|
103190
|
+
const score = aggregateAssessmentScore(metadata2.itemSubmissions.map((submission) => submission.score.earned), possible);
|
|
103191
|
+
const correctQuestions = countCorrectQuestions(metadata2.itemSubmissions.map((submission) => submission.isCorrect));
|
|
103192
|
+
return { tracks, score, correctQuestions };
|
|
103193
|
+
}
|
|
103194
|
+
var init_timeback_assessment_routing_util = __esm(() => {
|
|
103195
|
+
init_assessment_runtime2();
|
|
103196
|
+
init_errors2();
|
|
103197
|
+
init_timeback_assessment_runtime_util();
|
|
103198
|
+
});
|
|
103199
|
+
|
|
102268
103200
|
// ../api-core/src/utils/timeback-assessment-rules.util.ts
|
|
102269
103201
|
function validateAssessmentStatusTransition(current, next) {
|
|
102270
103202
|
if (current === next) {
|
|
@@ -102343,6 +103275,156 @@ var init_timeback_assessment_rules_util = __esm(() => {
|
|
|
102343
103275
|
init_errors2();
|
|
102344
103276
|
});
|
|
102345
103277
|
|
|
103278
|
+
// ../api-core/src/utils/timeback-assessment-scoring.util.ts
|
|
103279
|
+
async function scoreAssessmentItems({
|
|
103280
|
+
assessment,
|
|
103281
|
+
responses,
|
|
103282
|
+
artifactIdentity,
|
|
103283
|
+
purpose,
|
|
103284
|
+
itemIdentifiers,
|
|
103285
|
+
artifact,
|
|
103286
|
+
processResponse,
|
|
103287
|
+
onEvent
|
|
103288
|
+
}) {
|
|
103289
|
+
const scoringItems = assessmentScoringRequests(assessment, responses).filter((item) => !itemIdentifiers || itemIdentifiers.has(item.itemIdentifier));
|
|
103290
|
+
const artifactItems = new Map(artifact?.items.map((item) => [item.itemIdentifier, item]));
|
|
103291
|
+
const scored = new Map;
|
|
103292
|
+
const qti2 = [];
|
|
103293
|
+
for (const item of scoringItems) {
|
|
103294
|
+
const artifactItem = artifactItems.get(item.itemIdentifier);
|
|
103295
|
+
let fallback2 = null;
|
|
103296
|
+
if (!artifact) {
|
|
103297
|
+
fallback2 = { reason: "artifact-unavailable" };
|
|
103298
|
+
} else if (!artifactItem) {
|
|
103299
|
+
fallback2 = { reason: "artifact-item-missing" };
|
|
103300
|
+
} else if (artifactItem.strategy === "qti") {
|
|
103301
|
+
fallback2 = { reason: "artifact-qti", artifactReason: artifactItem.reason };
|
|
103302
|
+
} else {
|
|
103303
|
+
const startedAt = Date.now();
|
|
103304
|
+
try {
|
|
103305
|
+
scored.set(item.itemIdentifier, {
|
|
103306
|
+
...scorePlatformAssessmentItem(artifactItem, responses[item.itemIdentifier]),
|
|
103307
|
+
grading: PLATFORM_CHILD_GRADING
|
|
103308
|
+
});
|
|
103309
|
+
onEvent("assessment.item_scoring", {
|
|
103310
|
+
"app.assessment.qti_item_identifier": item.itemIdentifier,
|
|
103311
|
+
"app.assessment.purpose": purpose,
|
|
103312
|
+
"app.assessment.artifact_identity_kind": artifactIdentity.kind,
|
|
103313
|
+
"app.assessment.grading_source": "platform-artifact",
|
|
103314
|
+
"app.assessment.grading_duration_ms": Date.now() - startedAt
|
|
103315
|
+
});
|
|
103316
|
+
} catch (error88) {
|
|
103317
|
+
fallback2 = { reason: "platform-grader-error", error: error88 };
|
|
103318
|
+
}
|
|
103319
|
+
}
|
|
103320
|
+
if (fallback2) {
|
|
103321
|
+
qti2.push({ item, ...fallback2 });
|
|
103322
|
+
}
|
|
103323
|
+
}
|
|
103324
|
+
if (qti2.length > 0) {
|
|
103325
|
+
const fallbackTasks = qti2.flatMap(({ item, reason, artifactReason, error: graderError }) => {
|
|
103326
|
+
const interactionResults = [];
|
|
103327
|
+
let startedAt;
|
|
103328
|
+
let completedInteractions = 0;
|
|
103329
|
+
let failed = false;
|
|
103330
|
+
const omittedResponseCount = item.requests.filter((request) => responses[item.itemIdentifier]?.[request.identifier] === undefined).length;
|
|
103331
|
+
let qtiRemoteInteractionCount = 0;
|
|
103332
|
+
let syntheticIncorrectInteractionCount = 0;
|
|
103333
|
+
function fallbackEvent(outcome) {
|
|
103334
|
+
onEvent("assessment.item_scoring_fallback", {
|
|
103335
|
+
"app.assessment.qti_item_identifier": item.itemIdentifier,
|
|
103336
|
+
"app.assessment.purpose": purpose,
|
|
103337
|
+
"app.assessment.artifact_identity_kind": artifactIdentity.kind,
|
|
103338
|
+
"app.assessment.grading_source": omittedResponseCount > 0 ? "platform-qti-adapter" : "timeback-qti",
|
|
103339
|
+
"app.assessment.fallback_reason": reason,
|
|
103340
|
+
"app.assessment.fallback_duration_ms": startedAt === undefined ? 0 : Date.now() - startedAt,
|
|
103341
|
+
"app.assessment.qti_remote_interaction_count": qtiRemoteInteractionCount,
|
|
103342
|
+
"app.assessment.synthetic_incorrect_interaction_count": syntheticIncorrectInteractionCount,
|
|
103343
|
+
...artifactReason ? { "app.assessment.artifact_fallback_reason": artifactReason } : {},
|
|
103344
|
+
...graderError ? {
|
|
103345
|
+
"app.assessment.platform_grader_exception_type": errorType(graderError)
|
|
103346
|
+
} : {},
|
|
103347
|
+
...outcome
|
|
103348
|
+
});
|
|
103349
|
+
}
|
|
103350
|
+
function completeItem() {
|
|
103351
|
+
const earned = assessmentItemEarnedScore(interactionResults.map((result) => Number.isFinite(result.score) ? result.score : 0), item.maxScore);
|
|
103352
|
+
const possible = item.maxScore;
|
|
103353
|
+
fallbackEvent({ "app.assessment.fallback_outcome": "succeeded" });
|
|
103354
|
+
scored.set(item.itemIdentifier, {
|
|
103355
|
+
itemIdentifier: item.itemIdentifier,
|
|
103356
|
+
score: {
|
|
103357
|
+
earned,
|
|
103358
|
+
possible,
|
|
103359
|
+
normalized: possible === 0 ? 0 : earned / possible
|
|
103360
|
+
},
|
|
103361
|
+
isCorrect: assessmentItemCorrectness(interactionResults.map((result) => result.isCorrect)),
|
|
103362
|
+
grading: omittedResponseCount > 0 ? PLATFORM_QTI_ADAPTER_CHILD_GRADING : QTI_CHILD_GRADING
|
|
103363
|
+
});
|
|
103364
|
+
}
|
|
103365
|
+
if (item.requests.length === 0) {
|
|
103366
|
+
completeItem();
|
|
103367
|
+
return [];
|
|
103368
|
+
}
|
|
103369
|
+
return item.requests.map((request, index2) => async () => {
|
|
103370
|
+
startedAt ??= Date.now();
|
|
103371
|
+
try {
|
|
103372
|
+
const response = responses[item.itemIdentifier]?.[request.identifier];
|
|
103373
|
+
if (response === undefined) {
|
|
103374
|
+
syntheticIncorrectInteractionCount += 1;
|
|
103375
|
+
interactionResults[index2] = { score: 0, isCorrect: false };
|
|
103376
|
+
} else {
|
|
103377
|
+
qtiRemoteInteractionCount += 1;
|
|
103378
|
+
interactionResults[index2] = await processResponse(item.itemIdentifier, request);
|
|
103379
|
+
}
|
|
103380
|
+
completedInteractions += 1;
|
|
103381
|
+
if (completedInteractions === item.requests.length && !failed) {
|
|
103382
|
+
completeItem();
|
|
103383
|
+
}
|
|
103384
|
+
} catch (error88) {
|
|
103385
|
+
if (!failed) {
|
|
103386
|
+
failed = true;
|
|
103387
|
+
fallbackEvent({
|
|
103388
|
+
"app.assessment.fallback_outcome": "failed",
|
|
103389
|
+
"exception.type": errorType(error88),
|
|
103390
|
+
"app.error.message": errorMessage(error88)
|
|
103391
|
+
});
|
|
103392
|
+
}
|
|
103393
|
+
throw error88;
|
|
103394
|
+
}
|
|
103395
|
+
});
|
|
103396
|
+
});
|
|
103397
|
+
await runWithConcurrency(fallbackTasks, SCORING_CONCURRENCY, (task) => task());
|
|
103398
|
+
}
|
|
103399
|
+
return scoringItems.map((item) => {
|
|
103400
|
+
const result = scored.get(item.itemIdentifier);
|
|
103401
|
+
if (!result) {
|
|
103402
|
+
throw new Error(`Assessment item ${item.itemIdentifier} was not scored`);
|
|
103403
|
+
}
|
|
103404
|
+
return result;
|
|
103405
|
+
});
|
|
103406
|
+
}
|
|
103407
|
+
var QTI_CHILD_GRADING, PLATFORM_QTI_ADAPTER_CHILD_GRADING, PLATFORM_CHILD_GRADING, SCORING_CONCURRENCY = 4;
|
|
103408
|
+
var init_timeback_assessment_scoring_util = __esm(() => {
|
|
103409
|
+
init_assessment_runtime2();
|
|
103410
|
+
init_assessment_artifact_envelope_util();
|
|
103411
|
+
init_timeback_assessment_runtime_util();
|
|
103412
|
+
QTI_CHILD_GRADING = {
|
|
103413
|
+
source: "timeback-qti",
|
|
103414
|
+
graderVersion: "timeback-qti-process-response-v1"
|
|
103415
|
+
};
|
|
103416
|
+
PLATFORM_QTI_ADAPTER_CHILD_GRADING = {
|
|
103417
|
+
source: "platform-qti-adapter",
|
|
103418
|
+
graderVersion: "platform-qti-adapter-v1",
|
|
103419
|
+
qtiGraderVersion: "timeback-qti-process-response-v1"
|
|
103420
|
+
};
|
|
103421
|
+
PLATFORM_CHILD_GRADING = {
|
|
103422
|
+
source: "platform-artifact",
|
|
103423
|
+
artifactVersion: `assessment-artifact-v${ASSESSMENT_ARTIFACT_SCHEMA_VERSION}`,
|
|
103424
|
+
graderVersion: `platform-grader-v${ASSESSMENT_GRADER_PROTOCOL_VERSION}`
|
|
103425
|
+
};
|
|
103426
|
+
});
|
|
103427
|
+
|
|
102346
103428
|
// ../api-core/src/utils/timeback-qti-hydration.util.ts
|
|
102347
103429
|
function qtiItemHref(client2, itemIdentifier) {
|
|
102348
103430
|
return `${client2.getQtiBaseUrl()}/assessment-items/${itemIdentifier}`;
|
|
@@ -102420,6 +103502,7 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
102420
103502
|
init_spans();
|
|
102421
103503
|
init_assessment_runtime2();
|
|
102422
103504
|
init_constants3();
|
|
103505
|
+
init_qti();
|
|
102423
103506
|
init_types2();
|
|
102424
103507
|
init_uuid();
|
|
102425
103508
|
init_errors2();
|
|
@@ -102428,13 +103511,18 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
102428
103511
|
init_timeback_assessment_artifacts_util();
|
|
102429
103512
|
init_timeback_assessment_preparation_util();
|
|
102430
103513
|
init_timeback_assessment_publication_util();
|
|
103514
|
+
init_timeback_assessment_reconciliation_util();
|
|
103515
|
+
init_timeback_assessment_replay_util();
|
|
103516
|
+
init_timeback_assessment_routing_util();
|
|
102431
103517
|
init_timeback_assessment_rules_util();
|
|
102432
103518
|
init_timeback_assessment_runtime_util();
|
|
103519
|
+
init_timeback_assessment_scoring_util();
|
|
102433
103520
|
init_timeback_qti_hydration_util();
|
|
102434
103521
|
await __promiseAll([
|
|
102435
103522
|
init_dist5(),
|
|
102436
103523
|
init_errors8(),
|
|
102437
|
-
init_assessment_artifact_loader_util()
|
|
103524
|
+
init_assessment_artifact_loader_util(),
|
|
103525
|
+
init_timeback_assessment_child_result_util()
|
|
102438
103526
|
]);
|
|
102439
103527
|
AssessmentPreparationPrerequisiteError = class AssessmentPreparationPrerequisiteError extends Error {
|
|
102440
103528
|
original;
|
|
@@ -102815,12 +103903,19 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
102815
103903
|
activityId: params.input.activityId,
|
|
102816
103904
|
source
|
|
102817
103905
|
}));
|
|
102818
|
-
await this.provisionPreparationPrerequisites(() => runWithConcurrency(assessment.items, TimebackAssessmentRuntimeService.SCORING_CONCURRENCY, (item) =>
|
|
102819
|
-
|
|
102820
|
-
|
|
102821
|
-
|
|
102822
|
-
|
|
102823
|
-
|
|
103906
|
+
await this.provisionPreparationPrerequisites(() => runWithConcurrency(assessment.items, TimebackAssessmentRuntimeService.SCORING_CONCURRENCY, (item) => {
|
|
103907
|
+
const indexed = catalog.bank.items.find((candidate) => candidate.itemIdentifier === item.identifier);
|
|
103908
|
+
if (!indexed) {
|
|
103909
|
+
throw new Error(`Review-bank index is missing item ${item.identifier}`);
|
|
103910
|
+
}
|
|
103911
|
+
return this.ensureReviewQuestionLineItem({
|
|
103912
|
+
parentLineItemId,
|
|
103913
|
+
integration: context2.integration,
|
|
103914
|
+
bankRevision: catalog.bank.bankRevision,
|
|
103915
|
+
standards: [...indexed.standards],
|
|
103916
|
+
item
|
|
103917
|
+
});
|
|
103918
|
+
}));
|
|
102824
103919
|
const attempt = await this.provisionPreparationPrerequisites(() => this.claimAttemptId({
|
|
102825
103920
|
gameId: params.gameId,
|
|
102826
103921
|
studentId: params.studentId,
|
|
@@ -103009,6 +104104,7 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
103009
104104
|
testName: assessment.title,
|
|
103010
104105
|
routingRevision: routing.routingRevision,
|
|
103011
104106
|
ledger: [],
|
|
104107
|
+
routingSnapshots: [],
|
|
103012
104108
|
state: routing.state
|
|
103013
104109
|
}
|
|
103014
104110
|
};
|
|
@@ -103168,15 +104264,6 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
103168
104264
|
grade: context2.integration.grade
|
|
103169
104265
|
})
|
|
103170
104266
|
});
|
|
103171
|
-
await this.ensureReviewChildResults({
|
|
103172
|
-
result,
|
|
103173
|
-
metadata: metadata2,
|
|
103174
|
-
integration: context2.integration,
|
|
103175
|
-
source,
|
|
103176
|
-
assessment,
|
|
103177
|
-
existingChildResults: refreshedListing.reviewChildren,
|
|
103178
|
-
lookupMissingResults: false
|
|
103179
|
-
});
|
|
103180
104267
|
await this.persistPendingSupersessionsBestEffort(db2, pendingSupersessions);
|
|
103181
104268
|
this.recordReviewPreparationPhase("provisioning", provisioningStartedAt);
|
|
103182
104269
|
this.recordReviewPreparationPhase("total", preparationStartedAt, {
|
|
@@ -103311,13 +104398,39 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
103311
104398
|
let preparationAttempts = 1;
|
|
103312
104399
|
while (true) {
|
|
103313
104400
|
const committed = await this.deps.assessmentRuntimeLock(this.deps.db, [assessmentRuntimeAttemptLockKey(params.attemptId)], async (db2) => {
|
|
103314
|
-
const attempt = await this.
|
|
104401
|
+
const { attempt, enrollment } = await this.resolveAttemptAuthorization(params, {
|
|
103315
104402
|
db: db2,
|
|
103316
104403
|
developerAccessValidated: true
|
|
103317
104404
|
});
|
|
103318
104405
|
if (this.isPlatformRoutedDiagnosticMetadata(attempt.metadata)) {
|
|
104406
|
+
this.requireActiveAttempt({ attempt, enrollment }, params.attemptId);
|
|
103319
104407
|
throw AssessmentRuntimeError.from(assessmentFlowViolation("Platform-routed diagnostic submissions require routingNodeKey.", { flow: "platform-routed-item-submit" }));
|
|
103320
104408
|
}
|
|
104409
|
+
const replay = resolveAssessmentItemReplay(attempt.metadata, input);
|
|
104410
|
+
if (replay.action === "conflict") {
|
|
104411
|
+
this.requireActiveAttempt({ attempt, enrollment }, params.attemptId);
|
|
104412
|
+
throw AssessmentRuntimeError.from(replay.failure);
|
|
104413
|
+
}
|
|
104414
|
+
if (replay.action === "replay") {
|
|
104415
|
+
const prior = replay.submission;
|
|
104416
|
+
const child2 = attempt.metadata.purpose === "review" ? await this.prepareReviewAssessmentChildResult({
|
|
104417
|
+
attempt,
|
|
104418
|
+
metadata: attempt.metadata,
|
|
104419
|
+
submission: prior
|
|
104420
|
+
}) : null;
|
|
104421
|
+
return {
|
|
104422
|
+
action: "complete",
|
|
104423
|
+
response: buildAssessmentItemReplayResult(attempt.result.sourcedId, attempt.metadata, prior),
|
|
104424
|
+
checkpoint: child2,
|
|
104425
|
+
projection: attempt.metadata.purpose === "review" ? {
|
|
104426
|
+
result: attempt.result,
|
|
104427
|
+
metadata: attempt.metadata,
|
|
104428
|
+
itemIdentifier: input.itemIdentifier,
|
|
104429
|
+
integration: attempt.integration
|
|
104430
|
+
} : null
|
|
104431
|
+
};
|
|
104432
|
+
}
|
|
104433
|
+
this.requireActiveAttempt({ attempt, enrollment }, params.attemptId);
|
|
103321
104434
|
this.assertInProgress(attempt.result);
|
|
103322
104435
|
if (!preview || preview.responseVersion !== attempt.metadata.responseVersion || preview.submissionId !== input.submissionId || preview.itemIdentifier !== input.itemIdentifier) {
|
|
103323
104436
|
if (preparationAttempts >= TimebackAssessmentRuntimeService.ITEM_SUBMISSION_MAX_PREPARATION_ATTEMPTS) {
|
|
@@ -103336,6 +104449,11 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
103336
104449
|
throw AssessmentRuntimeError.from(transition.failure);
|
|
103337
104450
|
}
|
|
103338
104451
|
if (transition.action === "replay") {
|
|
104452
|
+
const child2 = attempt.metadata.purpose === "review" ? await this.prepareReviewAssessmentChildResult({
|
|
104453
|
+
attempt,
|
|
104454
|
+
metadata: attempt.metadata,
|
|
104455
|
+
submission: transition.submission
|
|
104456
|
+
}) : null;
|
|
103339
104457
|
return {
|
|
103340
104458
|
action: "complete",
|
|
103341
104459
|
response: {
|
|
@@ -103346,10 +104464,12 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
103346
104464
|
itemSubmissions: attempt.metadata.itemSubmissions,
|
|
103347
104465
|
submission: transition.submission
|
|
103348
104466
|
},
|
|
104467
|
+
checkpoint: child2,
|
|
103349
104468
|
projection: attempt.metadata.purpose === "review" ? {
|
|
103350
104469
|
result: attempt.result,
|
|
103351
104470
|
metadata: attempt.metadata,
|
|
103352
|
-
itemIdentifier: input.itemIdentifier
|
|
104471
|
+
itemIdentifier: input.itemIdentifier,
|
|
104472
|
+
integration: attempt.integration
|
|
103353
104473
|
} : null
|
|
103354
104474
|
};
|
|
103355
104475
|
}
|
|
@@ -103365,6 +104485,11 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
103365
104485
|
itemSubmissions: completed.itemSubmissions,
|
|
103366
104486
|
updatedAt: submittedAt
|
|
103367
104487
|
};
|
|
104488
|
+
const child = metadata2.purpose === "review" ? await this.prepareReviewAssessmentChildResult({
|
|
104489
|
+
attempt,
|
|
104490
|
+
metadata: metadata2,
|
|
104491
|
+
submission: completed.submission
|
|
104492
|
+
}) : null;
|
|
103368
104493
|
await this.putResultMetadata(attempt.result, metadata2);
|
|
103369
104494
|
return {
|
|
103370
104495
|
action: "complete",
|
|
@@ -103376,10 +104501,12 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
103376
104501
|
itemSubmissions: completed.itemSubmissions,
|
|
103377
104502
|
submission: completed.submission
|
|
103378
104503
|
},
|
|
104504
|
+
checkpoint: child,
|
|
103379
104505
|
projection: metadata2.purpose === "review" ? {
|
|
103380
104506
|
result: attempt.result,
|
|
103381
104507
|
metadata: metadata2,
|
|
103382
|
-
itemIdentifier: input.itemIdentifier
|
|
104508
|
+
itemIdentifier: input.itemIdentifier,
|
|
104509
|
+
integration: attempt.integration
|
|
103383
104510
|
} : null
|
|
103384
104511
|
};
|
|
103385
104512
|
});
|
|
@@ -103388,7 +104515,7 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
103388
104515
|
preview = await this.scoreItemSubmission(params, input);
|
|
103389
104516
|
} else {
|
|
103390
104517
|
if (committed.projection) {
|
|
103391
|
-
|
|
104518
|
+
this.projectReviewItemResponse(params, committed.projection, committed.checkpoint);
|
|
103392
104519
|
}
|
|
103393
104520
|
return committed.response;
|
|
103394
104521
|
}
|
|
@@ -103399,40 +104526,39 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
103399
104526
|
let preparationAttempts = 1;
|
|
103400
104527
|
while (true) {
|
|
103401
104528
|
const committed = await this.deps.assessmentRuntimeLock(this.deps.db, [assessmentRuntimeAttemptLockKey(params.attemptId)], async (db2) => {
|
|
103402
|
-
const attempt = await this.
|
|
103403
|
-
db: db2,
|
|
103404
|
-
developerAccessValidated: true
|
|
103405
|
-
});
|
|
104529
|
+
const { attempt, enrollment } = await this.resolveAttemptAuthorization(params, { db: db2, developerAccessValidated: true });
|
|
103406
104530
|
if (!this.isPlatformRoutedDiagnosticMetadata(attempt.metadata)) {
|
|
104531
|
+
this.requireActiveAttempt({ attempt, enrollment }, params.attemptId);
|
|
103407
104532
|
throw AssessmentRuntimeError.from(assessmentFlowViolation("routingNodeKey is accepted only for a platform-routed diagnostic.", { routingNodeKey: input.routingNodeKey }));
|
|
103408
104533
|
}
|
|
103409
104534
|
const metadata2 = attempt.metadata;
|
|
103410
|
-
const
|
|
103411
|
-
if (
|
|
103412
|
-
|
|
103413
|
-
|
|
103414
|
-
|
|
103415
|
-
|
|
103416
|
-
|
|
103417
|
-
|
|
103418
|
-
|
|
103419
|
-
|
|
103420
|
-
|
|
103421
|
-
|
|
103422
|
-
|
|
103423
|
-
|
|
103424
|
-
|
|
103425
|
-
|
|
104535
|
+
const replay = resolveDiagnosticItemReplay(metadata2, input);
|
|
104536
|
+
if (replay.action === "conflict") {
|
|
104537
|
+
this.requireActiveAttempt({ attempt, enrollment }, params.attemptId);
|
|
104538
|
+
throw AssessmentRuntimeError.from(replay.failure);
|
|
104539
|
+
}
|
|
104540
|
+
if (replay.action === "replay") {
|
|
104541
|
+
const { submission: prior, ledgerEntry, priorIndex } = replay;
|
|
104542
|
+
let routing = replay.routing;
|
|
104543
|
+
if (!routing) {
|
|
104544
|
+
const assessment = preview?.assessment ?? await this.loadAttemptAssessment(metadata2);
|
|
104545
|
+
const loaded2 = await this.loadAttemptDiagnosticRouting(metadata2, assessment, db2);
|
|
104546
|
+
const replayed = replayDiagnosticRouting(loaded2.manifest, metadata2.diagnostic.ledger.slice(0, priorIndex + 1));
|
|
104547
|
+
if (!replayed.ok) {
|
|
104548
|
+
throw AssessmentRuntimeError.from(assessmentFlowViolation("The committed diagnostic routing state cannot be replayed.", { failure: replayed.failure }));
|
|
104549
|
+
}
|
|
104550
|
+
routing = this.diagnosticRoutingSnapshot(metadata2, replayed.state);
|
|
103426
104551
|
}
|
|
104552
|
+
const child2 = await this.prepareDiagnosticAssessmentChildResult({
|
|
104553
|
+
attempt,
|
|
104554
|
+
metadata: metadata2,
|
|
104555
|
+
submission: prior,
|
|
104556
|
+
transition: ledgerEntry
|
|
104557
|
+
});
|
|
103427
104558
|
return {
|
|
103428
104559
|
action: "complete",
|
|
103429
|
-
response:
|
|
103430
|
-
|
|
103431
|
-
responseVersion: prior.responseVersion,
|
|
103432
|
-
status: "in_progress",
|
|
103433
|
-
routing: this.diagnosticRoutingSnapshot(metadata2, replayed.state),
|
|
103434
|
-
submission: this.diagnosticReceipt(prior, ledgerEntry)
|
|
103435
|
-
},
|
|
104560
|
+
response: buildDiagnosticItemReplayResult(attempt.result.sourcedId, prior, ledgerEntry, routing),
|
|
104561
|
+
checkpoint: child2,
|
|
103436
104562
|
projection: {
|
|
103437
104563
|
result: attempt.result,
|
|
103438
104564
|
metadata: metadata2,
|
|
@@ -103441,6 +104567,7 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
103441
104567
|
}
|
|
103442
104568
|
};
|
|
103443
104569
|
}
|
|
104570
|
+
this.requireActiveAttempt({ attempt, enrollment }, params.attemptId);
|
|
103444
104571
|
this.assertInProgress(attempt.result);
|
|
103445
104572
|
if (!preview || preview.responseVersion !== metadata2.responseVersion || preview.submissionId !== input.submissionId || preview.routingNodeKey !== input.routingNodeKey || preview.itemIdentifier !== input.itemIdentifier) {
|
|
103446
104573
|
if (preparationAttempts >= TimebackAssessmentRuntimeService.ITEM_SUBMISSION_MAX_PREPARATION_ATTEMPTS) {
|
|
@@ -103474,14 +104601,17 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
103474
104601
|
}
|
|
103475
104602
|
const submittedAt = new Date().toISOString();
|
|
103476
104603
|
const responseVersion = metadata2.responseVersion + 1;
|
|
104604
|
+
const routingSnapshot = this.diagnosticRoutingSnapshot(metadata2, advanced.state);
|
|
104605
|
+
const routingSnapshots = metadata2.diagnostic.routingSnapshots ? [...metadata2.diagnostic.routingSnapshots, routingSnapshot] : undefined;
|
|
103477
104606
|
const submission = {
|
|
103478
104607
|
submissionId: input.submissionId,
|
|
103479
104608
|
itemIdentifier: input.itemIdentifier,
|
|
103480
104609
|
submittedAt,
|
|
103481
104610
|
responseVersion,
|
|
103482
|
-
answered:
|
|
104611
|
+
answered: Object.keys(responses[input.itemIdentifier] ?? {}).length > 0,
|
|
103483
104612
|
score: scoring.score,
|
|
103484
|
-
isCorrect: scoring.isCorrect
|
|
104613
|
+
isCorrect: scoring.isCorrect,
|
|
104614
|
+
grading: scoring.grading
|
|
103485
104615
|
};
|
|
103486
104616
|
const nextMetadata = {
|
|
103487
104617
|
...metadata2,
|
|
@@ -103492,9 +104622,16 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
103492
104622
|
diagnostic: {
|
|
103493
104623
|
...metadata2.diagnostic,
|
|
103494
104624
|
ledger: [...metadata2.diagnostic.ledger, advanced.ledgerEntry],
|
|
104625
|
+
...routingSnapshots ? { routingSnapshots } : {},
|
|
103495
104626
|
state: advanced.state
|
|
103496
104627
|
}
|
|
103497
104628
|
};
|
|
104629
|
+
const child = await this.prepareDiagnosticAssessmentChildResult({
|
|
104630
|
+
attempt,
|
|
104631
|
+
metadata: nextMetadata,
|
|
104632
|
+
submission,
|
|
104633
|
+
transition: advanced.ledgerEntry
|
|
104634
|
+
});
|
|
103498
104635
|
await this.putResultMetadata(attempt.result, nextMetadata);
|
|
103499
104636
|
return {
|
|
103500
104637
|
action: "complete",
|
|
@@ -103502,9 +104639,10 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
103502
104639
|
attemptId: attempt.result.sourcedId,
|
|
103503
104640
|
responseVersion,
|
|
103504
104641
|
status: "in_progress",
|
|
103505
|
-
routing:
|
|
103506
|
-
submission:
|
|
104642
|
+
routing: routingSnapshot,
|
|
104643
|
+
submission: diagnosticReceipt(submission, advanced.ledgerEntry)
|
|
103507
104644
|
},
|
|
104645
|
+
checkpoint: child,
|
|
103508
104646
|
projection: {
|
|
103509
104647
|
result: attempt.result,
|
|
103510
104648
|
metadata: nextMetadata,
|
|
@@ -103517,21 +104655,11 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
103517
104655
|
preparationAttempts += 1;
|
|
103518
104656
|
preview = await this.scoreDiagnosticItemSubmission(params, input);
|
|
103519
104657
|
} else {
|
|
103520
|
-
|
|
104658
|
+
this.projectDiagnosticItemResponse(committed.projection, preview?.assessment, committed.checkpoint);
|
|
103521
104659
|
return committed.response;
|
|
103522
104660
|
}
|
|
103523
104661
|
}
|
|
103524
104662
|
}
|
|
103525
|
-
diagnosticReceipt(submission, ledgerEntry) {
|
|
103526
|
-
return {
|
|
103527
|
-
submissionId: submission.submissionId,
|
|
103528
|
-
routingNodeKey: ledgerEntry.routingNodeKey,
|
|
103529
|
-
itemIdentifier: submission.itemIdentifier,
|
|
103530
|
-
submittedAt: submission.submittedAt,
|
|
103531
|
-
responseVersion: submission.responseVersion,
|
|
103532
|
-
answered: submission.answered
|
|
103533
|
-
};
|
|
103534
|
-
}
|
|
103535
104663
|
async prepareDiagnosticItemSubmission(params, input) {
|
|
103536
104664
|
try {
|
|
103537
104665
|
return await this.scoreDiagnosticItemSubmission(params, input);
|
|
@@ -103551,22 +104679,24 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
103551
104679
|
}
|
|
103552
104680
|
const prior = attempt.metadata.itemSubmissions.find((submission) => submission.submissionId === input.submissionId);
|
|
103553
104681
|
if (prior || !this.isOpen(attempt.result)) {
|
|
103554
|
-
return
|
|
103555
|
-
responseVersion: attempt.metadata.responseVersion,
|
|
103556
|
-
submissionId: input.submissionId,
|
|
103557
|
-
routingNodeKey: input.routingNodeKey,
|
|
103558
|
-
itemIdentifier: input.itemIdentifier,
|
|
103559
|
-
assessment: await this.loadAttemptAssessment(attempt.metadata),
|
|
103560
|
-
scoring: null
|
|
103561
|
-
};
|
|
104682
|
+
return null;
|
|
103562
104683
|
}
|
|
103563
104684
|
const assessment = await this.loadAttemptAssessment(attempt.metadata);
|
|
103564
|
-
const
|
|
104685
|
+
const artifactIdentity = assessmentArtifactIdentityFromAttempt(attempt.metadata);
|
|
104686
|
+
const scoringArtifact = await this.artifactLoader.loadMatchedScoring(assessment, artifactIdentity);
|
|
104687
|
+
const loaded = await this.loadAttemptDiagnosticRouting(attempt.metadata, assessment, this.deps.db, scoringArtifact);
|
|
103565
104688
|
const expected = loaded.state.next;
|
|
103566
104689
|
let scoring = null;
|
|
103567
104690
|
if (loaded.state.status === "in-progress" && expected?.nodeKey === input.routingNodeKey && expected.itemIdentifier === input.itemIdentifier && attempt.metadata.responseVersion === input.expectedResponseVersion) {
|
|
103568
104691
|
const responses = prepareDiagnosticAssessmentResponses(assessment, attempt.metadata.responses, input);
|
|
103569
|
-
scoring = await this.scoreItem(
|
|
104692
|
+
scoring = await this.scoreItem({
|
|
104693
|
+
assessment,
|
|
104694
|
+
responses,
|
|
104695
|
+
itemIdentifier: input.itemIdentifier,
|
|
104696
|
+
artifactIdentity,
|
|
104697
|
+
purpose: attempt.metadata.purpose,
|
|
104698
|
+
preparedArtifact: scoringArtifact
|
|
104699
|
+
});
|
|
103570
104700
|
}
|
|
103571
104701
|
return {
|
|
103572
104702
|
responseVersion: attempt.metadata.responseVersion,
|
|
@@ -103577,39 +104707,27 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
103577
104707
|
scoring
|
|
103578
104708
|
};
|
|
103579
104709
|
}
|
|
103580
|
-
async scoreItems(
|
|
103581
|
-
|
|
103582
|
-
|
|
103583
|
-
|
|
103584
|
-
const
|
|
103585
|
-
|
|
103586
|
-
|
|
103587
|
-
|
|
103588
|
-
|
|
103589
|
-
|
|
103590
|
-
const scoresByItem = scoringItems.map(() => []);
|
|
103591
|
-
const verdictsByItem = scoringItems.map(() => []);
|
|
103592
|
-
scoringTasks.forEach((task, taskIndex) => {
|
|
103593
|
-
const processed = interactionResults[taskIndex];
|
|
103594
|
-
scoresByItem[task.itemIndex].push(Number.isFinite(processed.score) ? processed.score : 0);
|
|
103595
|
-
verdictsByItem[task.itemIndex].push(processed.isCorrect);
|
|
103596
|
-
});
|
|
103597
|
-
return scoringItems.map((item, itemIndex) => {
|
|
103598
|
-
const earned = assessmentItemEarnedScore(scoresByItem[itemIndex], item.maxScore);
|
|
103599
|
-
const possible = item.maxScore;
|
|
103600
|
-
return {
|
|
103601
|
-
itemIdentifier: item.itemIdentifier,
|
|
103602
|
-
score: {
|
|
103603
|
-
earned,
|
|
103604
|
-
possible,
|
|
103605
|
-
normalized: possible === 0 ? 0 : earned / possible
|
|
103606
|
-
},
|
|
103607
|
-
isCorrect: assessmentItemCorrectness(verdictsByItem[itemIndex])
|
|
103608
|
-
};
|
|
104710
|
+
async scoreItems({
|
|
104711
|
+
preparedArtifact,
|
|
104712
|
+
...input
|
|
104713
|
+
}) {
|
|
104714
|
+
const artifact = preparedArtifact === undefined ? await this.artifactLoader.loadMatchedScoring(input.assessment, input.artifactIdentity) : preparedArtifact;
|
|
104715
|
+
return scoreAssessmentItems({
|
|
104716
|
+
...input,
|
|
104717
|
+
artifact,
|
|
104718
|
+
processResponse: (identifier, request) => this.requireClient().qtiApi.assessmentItems.processResponse(identifier, request),
|
|
104719
|
+
onEvent: addEvent
|
|
103609
104720
|
});
|
|
103610
104721
|
}
|
|
103611
|
-
async scoreItem(
|
|
103612
|
-
const [scoring] = await this.scoreItems(
|
|
104722
|
+
async scoreItem(input) {
|
|
104723
|
+
const [scoring] = await this.scoreItems({
|
|
104724
|
+
assessment: input.assessment,
|
|
104725
|
+
responses: input.responses,
|
|
104726
|
+
artifactIdentity: input.artifactIdentity,
|
|
104727
|
+
purpose: input.purpose,
|
|
104728
|
+
itemIdentifiers: new Set([input.itemIdentifier]),
|
|
104729
|
+
preparedArtifact: input.preparedArtifact
|
|
104730
|
+
});
|
|
103613
104731
|
return scoring;
|
|
103614
104732
|
}
|
|
103615
104733
|
async prepareItemSubmission(params, input) {
|
|
@@ -103626,7 +104744,8 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
103626
104744
|
}
|
|
103627
104745
|
async scoreItemSubmission(params, input) {
|
|
103628
104746
|
const attempt = await this.peekAttempt(params);
|
|
103629
|
-
|
|
104747
|
+
const prior = attempt.metadata.itemSubmissions.find((submission) => submission.submissionId === input.submissionId);
|
|
104748
|
+
if (prior || !this.isOpen(attempt.result)) {
|
|
103630
104749
|
return null;
|
|
103631
104750
|
}
|
|
103632
104751
|
const assessment = await this.loadAttemptAssessment(attempt.metadata);
|
|
@@ -103637,7 +104756,13 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
103637
104756
|
itemSubmissions: attempt.metadata.itemSubmissions,
|
|
103638
104757
|
assessment
|
|
103639
104758
|
}, input);
|
|
103640
|
-
const scoring = transition.action === "commit" ? await this.scoreItem(
|
|
104759
|
+
const scoring = transition.action === "commit" ? await this.scoreItem({
|
|
104760
|
+
assessment,
|
|
104761
|
+
responses: transition.responses,
|
|
104762
|
+
itemIdentifier: input.itemIdentifier,
|
|
104763
|
+
artifactIdentity: assessmentArtifactIdentityFromAttempt(attempt.metadata),
|
|
104764
|
+
purpose: attempt.metadata.purpose
|
|
104765
|
+
}) : null;
|
|
103641
104766
|
return {
|
|
103642
104767
|
responseVersion: attempt.metadata.responseVersion,
|
|
103643
104768
|
submissionId: input.submissionId,
|
|
@@ -103649,8 +104774,12 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
103649
104774
|
async scoreSubmission(attempt) {
|
|
103650
104775
|
const assessment = await this.loadAttemptAssessment(attempt.metadata);
|
|
103651
104776
|
validateAssessmentResponses(assessment, attempt.metadata.responses);
|
|
103652
|
-
const
|
|
103653
|
-
|
|
104777
|
+
const itemResults = attempt.metadata.purpose === "review" ? committedReviewItemResults(attempt.metadata, assessment) : await this.scoreItems({
|
|
104778
|
+
assessment,
|
|
104779
|
+
responses: attempt.metadata.responses,
|
|
104780
|
+
artifactIdentity: assessmentArtifactIdentityFromAttempt(attempt.metadata),
|
|
104781
|
+
purpose: attempt.metadata.purpose
|
|
104782
|
+
});
|
|
103654
104783
|
return {
|
|
103655
104784
|
responseVersion: attempt.metadata.responseVersion,
|
|
103656
104785
|
assessment,
|
|
@@ -103659,7 +104788,10 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
103659
104788
|
correctQuestions: countCorrectQuestions(itemResults.map((result) => result.isCorrect))
|
|
103660
104789
|
};
|
|
103661
104790
|
}
|
|
103662
|
-
async peekAttempt({
|
|
104791
|
+
async peekAttempt({
|
|
104792
|
+
studentId,
|
|
104793
|
+
attemptId
|
|
104794
|
+
}) {
|
|
103663
104795
|
const result = await this.requireClient().api.oneroster.assessmentResults.get(attemptId).catch((error88) => {
|
|
103664
104796
|
if (isApiError(error88) && error88.statusCode === 404) {
|
|
103665
104797
|
throw this.unauthorizedAttempt(attemptId);
|
|
@@ -103704,21 +104836,19 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
103704
104836
|
await this.deps.validateDeveloperAccess(params.user, params.gameId);
|
|
103705
104837
|
const preview = await this.prepareSubmission(params, input);
|
|
103706
104838
|
const submission = await this.deps.assessmentRuntimeLock(this.deps.db, [assessmentRuntimeAttemptLockKey(params.attemptId)], async (db2) => {
|
|
103707
|
-
const attempt = await this.
|
|
103708
|
-
db: db2,
|
|
103709
|
-
developerAccessValidated: true
|
|
103710
|
-
});
|
|
104839
|
+
const { attempt: identity, enrollment } = await this.resolveAttemptAuthorization(params, { db: db2, developerAccessValidated: true });
|
|
103711
104840
|
const submissionDisposition = classifyAssessmentSubmission({
|
|
103712
|
-
inProgress:
|
|
103713
|
-
scoreStatus:
|
|
103714
|
-
submissionId:
|
|
104841
|
+
inProgress: identity.result.inProgress ?? "",
|
|
104842
|
+
scoreStatus: identity.result.scoreStatus,
|
|
104843
|
+
submissionId: identity.metadata.submissionId
|
|
103715
104844
|
}, input.submissionId);
|
|
103716
104845
|
if (submissionDisposition === "replay") {
|
|
103717
104846
|
return {
|
|
103718
|
-
response: this.submittedResult(
|
|
103719
|
-
emission: this.replayCompletion(
|
|
104847
|
+
response: this.submittedResult(identity.result, identity.metadata),
|
|
104848
|
+
emission: this.replayCompletion(identity, input.submissionId, params, game2)
|
|
103720
104849
|
};
|
|
103721
104850
|
}
|
|
104851
|
+
const attempt = this.requireActiveAttempt({ attempt: identity, enrollment }, params.attemptId);
|
|
103722
104852
|
if (submissionDisposition === "reject") {
|
|
103723
104853
|
throw AssessmentRuntimeError.from(assessmentAlreadySubmitted(attempt.result.sourcedId));
|
|
103724
104854
|
}
|
|
@@ -103746,12 +104876,9 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
103746
104876
|
const { assessment, itemResults, score, correctQuestions } = scored;
|
|
103747
104877
|
const client2 = this.requireClient();
|
|
103748
104878
|
const timestamp6 = new Date().toISOString();
|
|
103749
|
-
const reviewOutcomes = attempt.metadata.purpose === "review" ?
|
|
103750
|
-
result: attempt.result,
|
|
104879
|
+
const reviewOutcomes = attempt.metadata.purpose === "review" ? this.reviewItemOutcomes({
|
|
103751
104880
|
metadata: attempt.metadata,
|
|
103752
|
-
itemResults
|
|
103753
|
-
submissionId: input.submissionId,
|
|
103754
|
-
timestamp: timestamp6
|
|
104881
|
+
itemResults
|
|
103755
104882
|
}) : null;
|
|
103756
104883
|
const finalization = buildAssessmentResultSubmission({
|
|
103757
104884
|
result: attempt.result,
|
|
@@ -103814,9 +104941,11 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
103814
104941
|
...submission.emission.attempt,
|
|
103815
104942
|
metadata: submission.emission.attempt.metadata
|
|
103816
104943
|
} : null;
|
|
104944
|
+
const { result, metadata: metadata2 } = submission.emission.attempt;
|
|
103817
104945
|
await Promise.allSettled([
|
|
103818
104946
|
this.emitSessionBestEffort(submission.emission),
|
|
103819
|
-
...diagnosticCompletion ? [this.restoreDiagnosticChildResults(diagnosticCompletion)] : []
|
|
104947
|
+
...diagnosticCompletion ? [this.restoreDiagnosticChildResults(diagnosticCompletion)] : [],
|
|
104948
|
+
...metadata2.purpose === "review" ? [this.restoreAllCompletedReviewChildResponses(result, metadata2)] : []
|
|
103820
104949
|
]);
|
|
103821
104950
|
}
|
|
103822
104951
|
return submission.response;
|
|
@@ -103966,17 +105095,7 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
103966
105095
|
async finalizeDiagnosticSubmission(input) {
|
|
103967
105096
|
const { attempt } = input;
|
|
103968
105097
|
const routing = await this.loadAttemptDiagnosticManifest(attempt.metadata, input.db);
|
|
103969
|
-
const
|
|
103970
|
-
const tracks = diagnosticTrackResults(state);
|
|
103971
|
-
if (!tracks) {
|
|
103972
|
-
throw AssessmentRuntimeError.from(assessmentFlowViolation("Diagnostic routing must be ready to complete before final submit.", { routingStatus: state.status }));
|
|
103973
|
-
}
|
|
103974
|
-
if (attempt.metadata.itemSubmissions.length === 0) {
|
|
103975
|
-
throw AssessmentRuntimeError.from(assessmentFlowViolation("A diagnostic cannot be finalized without an administered item."));
|
|
103976
|
-
}
|
|
103977
|
-
const possible = attempt.metadata.itemSubmissions.reduce((total, submission) => total + submission.score.possible, 0);
|
|
103978
|
-
const score = aggregateAssessmentScore(attempt.metadata.itemSubmissions.map((submission) => submission.score.earned), possible);
|
|
103979
|
-
const correctQuestions = countCorrectQuestions(attempt.metadata.itemSubmissions.map((submission) => submission.isCorrect));
|
|
105098
|
+
const { tracks, score, correctQuestions } = summarizeDiagnosticSubmission(attempt.metadata, routing.manifest);
|
|
103980
105099
|
const timestamp6 = new Date().toISOString();
|
|
103981
105100
|
const finalization = buildAssessmentResultSubmission({
|
|
103982
105101
|
result: attempt.result,
|
|
@@ -104064,10 +105183,13 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
104064
105183
|
}) : undefined;
|
|
104065
105184
|
const diagnostic = definition?.diagnosticKey ? await this.initializeHostedDiagnostic(definition, loaded.assessment) : null;
|
|
104066
105185
|
const reviewBank = purpose === "review" ? await buildReviewBankIndex(loaded.assessment, loaded.questions, { customOnly: true }) : null;
|
|
105186
|
+
const scoring = compileQtiScoringArtifact(loaded.questions);
|
|
105187
|
+
const fullyPlatformScored = scoring.items.every((item) => item.strategy === "platform");
|
|
104067
105188
|
return {
|
|
104068
105189
|
...test,
|
|
104069
105190
|
live: true,
|
|
104070
105191
|
assessment: loaded.assessment,
|
|
105192
|
+
...fullyPlatformScored ? { scoring } : {},
|
|
104071
105193
|
...diagnostic ? {
|
|
104072
105194
|
diagnostic: {
|
|
104073
105195
|
diagnosticKey: diagnostic.definition.diagnosticKey,
|
|
@@ -104412,21 +105534,31 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
104412
105534
|
if (resumed.metadata.purpose !== "review") {
|
|
104413
105535
|
throw new Error("A standards-review request selected a non-review attempt");
|
|
104414
105536
|
}
|
|
104415
|
-
|
|
104416
|
-
|
|
104417
|
-
|
|
104418
|
-
|
|
104419
|
-
|
|
104420
|
-
|
|
104421
|
-
|
|
104422
|
-
|
|
104423
|
-
|
|
104424
|
-
|
|
104425
|
-
|
|
104426
|
-
|
|
104427
|
-
|
|
105537
|
+
const reviewMetadata = resumed.metadata;
|
|
105538
|
+
let assessment;
|
|
105539
|
+
const current = await reconcileReviewChildResults({
|
|
105540
|
+
initial: resumed,
|
|
105541
|
+
writeOpen: async () => {
|
|
105542
|
+
const source = await this.loadPinnedReviewBankSource(reviewMetadata);
|
|
105543
|
+
assessment = projectReviewAssessment(source.assessment, source.bank, reviewMetadata.review.selections);
|
|
105544
|
+
await this.ensureReviewChildResults({
|
|
105545
|
+
result: resumed.result,
|
|
105546
|
+
metadata: reviewMetadata,
|
|
105547
|
+
integration: context2.integration,
|
|
105548
|
+
source,
|
|
105549
|
+
assessment,
|
|
105550
|
+
existingChildResults,
|
|
105551
|
+
lookupMissingResults: true
|
|
105552
|
+
});
|
|
105553
|
+
},
|
|
105554
|
+
crossBarrier: () => crossAssessmentAttemptLockBarrier(this.deps.assessmentRuntimeLock, this.deps.db, resumed.result.sourcedId),
|
|
105555
|
+
readLatest: () => this.peekAttempt({
|
|
105556
|
+
studentId: resumed.result.student.sourcedId,
|
|
105557
|
+
attemptId: resumed.result.sourcedId
|
|
105558
|
+
}),
|
|
105559
|
+
restoreTerminal: (latest) => this.restoreAllCompletedReviewChildResponses(latest.result, latest.metadata)
|
|
104428
105560
|
});
|
|
104429
|
-
return this.snapshot(
|
|
105561
|
+
return assessmentChildProjectionIsTerminal(current.result) ? this.snapshotForResult(current.result, current.metadata, context2.integration) : this.snapshot(current.result, current.metadata, assessment, context2.integration);
|
|
104430
105562
|
}
|
|
104431
105563
|
recordReviewPreparationPhase(phase, startedAt, counts = {}) {
|
|
104432
105564
|
addEvent("assessment.review_preparation_phase", {
|
|
@@ -104648,20 +105780,9 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
104648
105780
|
}
|
|
104649
105781
|
async initializeHostedDiagnostic(definition, assessment) {
|
|
104650
105782
|
const initialized = await this.initializeHostedDiagnosticManifest(definition);
|
|
104651
|
-
|
|
105783
|
+
assertDiagnosticItemsAvailable(definition.id, initialized.manifest, assessment);
|
|
104652
105784
|
return initialized;
|
|
104653
105785
|
}
|
|
104654
|
-
assertDiagnosticItemsAvailable(definition, manifest, assessment) {
|
|
104655
|
-
const itemIdentifiers = new Set(assessment.items.map((item) => item.identifier));
|
|
104656
|
-
const unknownItem = manifest.nodes.find((node) => !itemIdentifiers.has(node.itemIdentifier));
|
|
104657
|
-
if (unknownItem) {
|
|
104658
|
-
throw new AssessmentRuntimeError("SELECTED_TEST_VERSION_UNAVAILABLE", `Diagnostic routing references unavailable item ${unknownItem.itemIdentifier}.`, {
|
|
104659
|
-
definitionId: definition.id,
|
|
104660
|
-
nodeKey: unknownItem.key,
|
|
104661
|
-
itemIdentifier: unknownItem.itemIdentifier
|
|
104662
|
-
});
|
|
104663
|
-
}
|
|
104664
|
-
}
|
|
104665
105786
|
async initializeHostedDiagnosticManifest(definition) {
|
|
104666
105787
|
const initialized = initializeDiagnosticRouting(definition.diagnosticRoutingManifest);
|
|
104667
105788
|
if (!initialized.ok) {
|
|
@@ -104685,21 +105806,32 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
104685
105806
|
}
|
|
104686
105807
|
return initialized;
|
|
104687
105808
|
}
|
|
104688
|
-
|
|
104689
|
-
const
|
|
104690
|
-
|
|
104691
|
-
|
|
104692
|
-
|
|
104693
|
-
|
|
104694
|
-
|
|
104695
|
-
|
|
104696
|
-
|
|
104697
|
-
|
|
104698
|
-
|
|
105809
|
+
async loadAttemptDiagnosticRouting(metadata2, assessment, db2 = this.deps.db, preparedArtifact) {
|
|
105810
|
+
const startedAt = Date.now();
|
|
105811
|
+
const artifactIdentity = assessmentArtifactIdentityFromAttempt(metadata2);
|
|
105812
|
+
const artifact = preparedArtifact === undefined ? await this.artifactLoader.loadMatchedScoring(assessment, artifactIdentity) : preparedArtifact;
|
|
105813
|
+
const compiled = await resolveCompiledDiagnosticRouting(metadata2, assessment, artifact);
|
|
105814
|
+
if (compiled) {
|
|
105815
|
+
addEvent("assessment.diagnostic_routing_load", {
|
|
105816
|
+
"app.assessment.diagnostic_definition_id": metadata2.diagnostic.definitionId,
|
|
105817
|
+
"app.assessment.routing_source": "platform-artifact",
|
|
105818
|
+
"app.assessment.routing_duration_ms": Date.now() - startedAt
|
|
105819
|
+
});
|
|
105820
|
+
return compiled;
|
|
105821
|
+
}
|
|
105822
|
+
addEvent("assessment.diagnostic_routing_fallback", {
|
|
105823
|
+
"app.assessment.diagnostic_definition_id": metadata2.diagnostic.definitionId,
|
|
105824
|
+
"app.assessment.fallback_reason": diagnosticRoutingFallbackReason(artifact, metadata2.diagnostic.routingRevision),
|
|
105825
|
+
"app.assessment.fallback_duration_ms": Date.now() - startedAt
|
|
105826
|
+
});
|
|
104699
105827
|
const pinned = await this.loadAttemptDiagnosticManifest(metadata2, db2);
|
|
104700
|
-
|
|
104701
|
-
const state =
|
|
104702
|
-
return {
|
|
105828
|
+
assertDiagnosticItemsAvailable(pinned.definition.id, pinned.manifest, assessment);
|
|
105829
|
+
const state = replayAttemptDiagnosticRouting(metadata2, pinned.manifest);
|
|
105830
|
+
return {
|
|
105831
|
+
manifest: pinned.manifest,
|
|
105832
|
+
routingRevision: pinned.routingRevision,
|
|
105833
|
+
state
|
|
105834
|
+
};
|
|
104703
105835
|
}
|
|
104704
105836
|
async loadAssessmentSource(identifier, expectedRevision) {
|
|
104705
105837
|
const client2 = this.requireClient();
|
|
@@ -104834,11 +105966,167 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
104834
105966
|
}
|
|
104835
105967
|
return lineItemId;
|
|
104836
105968
|
}
|
|
105969
|
+
assessmentChildAttempt(attempt) {
|
|
105970
|
+
const { metadata: metadata2 } = attempt;
|
|
105971
|
+
const assessmentKey = metadata2.selectedTest.assessmentKey ?? assessmentKeyFromManagedQtiIdentifier(metadata2.selectedTest.identifier);
|
|
105972
|
+
if (!assessmentKey) {
|
|
105973
|
+
addEvent("assessment.child_result_legacy_attempt_skipped", {
|
|
105974
|
+
"app.assessment.attempt_id": attempt.result.sourcedId
|
|
105975
|
+
});
|
|
105976
|
+
return null;
|
|
105977
|
+
}
|
|
105978
|
+
return {
|
|
105979
|
+
attemptId: attempt.result.sourcedId,
|
|
105980
|
+
gameId: attempt.integration.gameId,
|
|
105981
|
+
studentId: attempt.result.student.sourcedId,
|
|
105982
|
+
activityId: metadata2.activityId,
|
|
105983
|
+
courseId: metadata2.courseId,
|
|
105984
|
+
integrationId: metadata2.integrationId,
|
|
105985
|
+
enrollmentId: metadata2.enrollmentId,
|
|
105986
|
+
selectedTest: {
|
|
105987
|
+
assessmentKey,
|
|
105988
|
+
identifier: metadata2.selectedTest.identifier,
|
|
105989
|
+
contentRevision: metadata2.selectedTest.contentRevision
|
|
105990
|
+
}
|
|
105991
|
+
};
|
|
105992
|
+
}
|
|
105993
|
+
reviewChildResultContext(metadata2, submission) {
|
|
105994
|
+
const selection = metadata2.review.selections.find((candidate) => candidate.itemIdentifier === submission.itemIdentifier);
|
|
105995
|
+
if (!selection) {
|
|
105996
|
+
throw new Error(`Committed review item ${submission.itemIdentifier} is not in its pinned selection`);
|
|
105997
|
+
}
|
|
105998
|
+
return {
|
|
105999
|
+
kind: "review",
|
|
106000
|
+
bankRevision: metadata2.review.bankRevision,
|
|
106001
|
+
standard: selection.standard
|
|
106002
|
+
};
|
|
106003
|
+
}
|
|
106004
|
+
prepareReviewAssessmentChildResult(input) {
|
|
106005
|
+
return this.prepareAssessmentChildResult({
|
|
106006
|
+
attempt: input.attempt,
|
|
106007
|
+
metadata: input.metadata,
|
|
106008
|
+
submission: input.submission,
|
|
106009
|
+
context: () => this.reviewChildResultContext(input.metadata, input.submission)
|
|
106010
|
+
});
|
|
106011
|
+
}
|
|
106012
|
+
prepareDiagnosticAssessmentChildResult(input) {
|
|
106013
|
+
return this.prepareAssessmentChildResult({
|
|
106014
|
+
attempt: input.attempt,
|
|
106015
|
+
metadata: input.metadata,
|
|
106016
|
+
submission: input.submission,
|
|
106017
|
+
context: () => ({
|
|
106018
|
+
kind: "platform-routed-diagnostic",
|
|
106019
|
+
definitionId: input.metadata.diagnostic.definitionId,
|
|
106020
|
+
diagnosticKey: input.metadata.diagnostic.diagnosticKey,
|
|
106021
|
+
routingRevision: input.metadata.diagnostic.routingRevision,
|
|
106022
|
+
transition: input.transition
|
|
106023
|
+
})
|
|
106024
|
+
});
|
|
106025
|
+
}
|
|
106026
|
+
async prepareAssessmentChildResult(input) {
|
|
106027
|
+
try {
|
|
106028
|
+
const attemptContext = this.assessmentChildAttempt({
|
|
106029
|
+
...input.attempt,
|
|
106030
|
+
metadata: input.metadata
|
|
106031
|
+
});
|
|
106032
|
+
if (!attemptContext) {
|
|
106033
|
+
return null;
|
|
106034
|
+
}
|
|
106035
|
+
const parentLineItemId = input.attempt.result.assessmentLineItem.sourcedId;
|
|
106036
|
+
const { itemIdentifier } = input.submission;
|
|
106037
|
+
const context2 = input.context();
|
|
106038
|
+
const [command, childLineItemId] = await Promise.all([
|
|
106039
|
+
buildSubmissionChildResultCommand({
|
|
106040
|
+
attempt: attemptContext,
|
|
106041
|
+
submission: input.submission,
|
|
106042
|
+
responses: input.metadata.responses[itemIdentifier],
|
|
106043
|
+
grading: input.submission.grading ?? QTI_CHILD_GRADING,
|
|
106044
|
+
context: context2
|
|
106045
|
+
}),
|
|
106046
|
+
context2.kind === "review" ? reviewQuestionLineItemId(parentLineItemId, itemIdentifier) : diagnosticQuestionLineItemId(parentLineItemId, itemIdentifier)
|
|
106047
|
+
]);
|
|
106048
|
+
return { command, childLineItemId };
|
|
106049
|
+
} catch (error88) {
|
|
106050
|
+
addEvent("assessment.child_result_prepare_failed", {
|
|
106051
|
+
"app.assessment.attempt_id": input.attempt.result.sourcedId,
|
|
106052
|
+
"app.assessment.submission_id": input.submission.submissionId,
|
|
106053
|
+
"app.assessment.qti_item_identifier": input.submission.itemIdentifier,
|
|
106054
|
+
"exception.type": errorType(error88),
|
|
106055
|
+
"app.error.message": errorMessage(error88)
|
|
106056
|
+
});
|
|
106057
|
+
return null;
|
|
106058
|
+
}
|
|
106059
|
+
}
|
|
106060
|
+
async projectPreparedAssessmentChildResult(prepared) {
|
|
106061
|
+
if (!prepared) {
|
|
106062
|
+
return;
|
|
106063
|
+
}
|
|
106064
|
+
const startedAt = Date.now();
|
|
106065
|
+
const identity = {
|
|
106066
|
+
"app.assessment.attempt_id": prepared.command.payload.attempt.attemptId,
|
|
106067
|
+
"app.assessment.submission_id": prepared.command.payload.administration.submissionId
|
|
106068
|
+
};
|
|
106069
|
+
try {
|
|
106070
|
+
const disposition = await persistAssessmentChildResult({
|
|
106071
|
+
assessmentResults: this.requireClient().api.oneroster.assessmentResults,
|
|
106072
|
+
command: prepared.command,
|
|
106073
|
+
childLineItemId: prepared.childLineItemId
|
|
106074
|
+
});
|
|
106075
|
+
if (disposition.outcome === "conflict") {
|
|
106076
|
+
addEvent("assessment.child_result_conflict", {
|
|
106077
|
+
...identity,
|
|
106078
|
+
"app.assessment.child_result_id": disposition.resultId,
|
|
106079
|
+
"app.assessment.child_result_conflict_reason": disposition.reason
|
|
106080
|
+
});
|
|
106081
|
+
return;
|
|
106082
|
+
}
|
|
106083
|
+
addEvent("assessment.child_result_persisted", {
|
|
106084
|
+
...identity,
|
|
106085
|
+
"app.assessment.child_result_id": disposition.resultId,
|
|
106086
|
+
"app.assessment.child_result_outcome": disposition.outcome,
|
|
106087
|
+
"app.assessment.duration_ms": Date.now() - startedAt
|
|
106088
|
+
});
|
|
106089
|
+
} catch (error88) {
|
|
106090
|
+
addEvent("assessment.child_result_persist_failed", {
|
|
106091
|
+
...identity,
|
|
106092
|
+
"app.assessment.duration_ms": Date.now() - startedAt,
|
|
106093
|
+
"exception.type": errorType(error88),
|
|
106094
|
+
"app.error.message": errorMessage(error88)
|
|
106095
|
+
});
|
|
106096
|
+
}
|
|
106097
|
+
}
|
|
106098
|
+
assertAssessmentChildLineItem(lineItemId, stored, expected) {
|
|
106099
|
+
if (assessmentChildLineItemMatches(stored, expected)) {
|
|
106100
|
+
return;
|
|
106101
|
+
}
|
|
106102
|
+
addEvent("assessment.question_line_item_conflict", {
|
|
106103
|
+
"app.assessment.line_item_id": lineItemId
|
|
106104
|
+
});
|
|
106105
|
+
throw AssessmentRuntimeError.from(assessmentFlowViolation("A deterministic assessment question line item is occupied by incompatible data.", { lineItemId }));
|
|
106106
|
+
}
|
|
104837
106107
|
async ensureDiagnosticQuestionLineItem(input) {
|
|
104838
106108
|
const client2 = this.requireClient();
|
|
104839
106109
|
const lineItemId = await diagnosticQuestionLineItemId(input.parentLineItemId, input.item.identifier);
|
|
106110
|
+
const expected = {
|
|
106111
|
+
status: ONEROSTER_STATUS2.active,
|
|
106112
|
+
parentAssessmentLineItem: { sourcedId: input.parentLineItemId },
|
|
106113
|
+
course: { sourcedId: input.integration.courseId },
|
|
106114
|
+
resultValueMin: 0,
|
|
106115
|
+
resultValueMax: input.item.maxScore,
|
|
106116
|
+
metadata: {
|
|
106117
|
+
playcademyDiagnosticQuestionDefinition: {
|
|
106118
|
+
version: 1,
|
|
106119
|
+
definitionId: input.metadata.diagnostic.definitionId,
|
|
106120
|
+
diagnosticKey: input.metadata.diagnostic.diagnosticKey,
|
|
106121
|
+
routingRevision: input.metadata.diagnostic.routingRevision,
|
|
106122
|
+
contentRevision: input.metadata.selectedTest.contentRevision,
|
|
106123
|
+
itemIdentifier: input.item.identifier
|
|
106124
|
+
}
|
|
106125
|
+
}
|
|
106126
|
+
};
|
|
104840
106127
|
try {
|
|
104841
|
-
await client2.api.oneroster.assessmentLineItems.get(lineItemId);
|
|
106128
|
+
const stored = await client2.api.oneroster.assessmentLineItems.get(lineItemId);
|
|
106129
|
+
this.assertAssessmentChildLineItem(lineItemId, stored, expected);
|
|
104842
106130
|
return lineItemId;
|
|
104843
106131
|
} catch (error88) {
|
|
104844
106132
|
if (!isApiError(error88) || error88.statusCode !== 404) {
|
|
@@ -104848,35 +106136,23 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
104848
106136
|
try {
|
|
104849
106137
|
await client2.api.oneroster.assessmentLineItems.create({
|
|
104850
106138
|
sourcedId: lineItemId,
|
|
104851
|
-
status: ONEROSTER_STATUS2.active,
|
|
104852
106139
|
title: input.item.title,
|
|
104853
106140
|
description: "adaptive diagnostic question",
|
|
104854
|
-
|
|
104855
|
-
course: { sourcedId: input.integration.courseId },
|
|
104856
|
-
resultValueMin: 0,
|
|
104857
|
-
resultValueMax: input.item.maxScore,
|
|
104858
|
-
metadata: {
|
|
104859
|
-
playcademyDiagnosticQuestionDefinition: {
|
|
104860
|
-
version: 1,
|
|
104861
|
-
definitionId: input.metadata.diagnostic.definitionId,
|
|
104862
|
-
diagnosticKey: input.metadata.diagnostic.diagnosticKey,
|
|
104863
|
-
routingRevision: input.metadata.diagnostic.routingRevision,
|
|
104864
|
-
contentRevision: input.metadata.selectedTest.contentRevision,
|
|
104865
|
-
itemIdentifier: input.item.identifier
|
|
104866
|
-
}
|
|
104867
|
-
}
|
|
106141
|
+
...expected
|
|
104868
106142
|
});
|
|
104869
106143
|
} catch (error88) {
|
|
104870
106144
|
if (!isApiError(error88) || error88.statusCode !== 409) {
|
|
104871
106145
|
throw error88;
|
|
104872
106146
|
}
|
|
106147
|
+
const stored = await client2.api.oneroster.assessmentLineItems.get(lineItemId);
|
|
106148
|
+
this.assertAssessmentChildLineItem(lineItemId, stored, expected);
|
|
104873
106149
|
addEvent("assessment.diagnostic_question_line_item_create_conflict", {
|
|
104874
106150
|
"app.assessment.line_item_id": lineItemId
|
|
104875
106151
|
});
|
|
104876
106152
|
}
|
|
104877
106153
|
return lineItemId;
|
|
104878
106154
|
}
|
|
104879
|
-
async projectDiagnosticItemResponse(projection, assessment) {
|
|
106155
|
+
async projectDiagnosticItemResponse(projection, assessment, checkpoint = null) {
|
|
104880
106156
|
try {
|
|
104881
106157
|
const loadedAssessment = assessment ?? await this.loadAttemptAssessment(projection.metadata);
|
|
104882
106158
|
const item = loadedAssessment.items.find((candidate) => candidate.identifier === projection.ledgerEntry.itemIdentifier);
|
|
@@ -104889,6 +106165,9 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
104889
106165
|
metadata: projection.metadata,
|
|
104890
106166
|
item
|
|
104891
106167
|
});
|
|
106168
|
+
if (checkpoint && checkpoint.childLineItemId !== childLineItemId) {
|
|
106169
|
+
throw new Error("Prepared diagnostic checkpoint resolved a different line item");
|
|
106170
|
+
}
|
|
104892
106171
|
const childResultId = await diagnosticQuestionResultId(projection.result.sourcedId, childLineItemId);
|
|
104893
106172
|
const finalized = buildFinalizedDiagnosticChildResult({
|
|
104894
106173
|
childLineItemId,
|
|
@@ -104897,7 +106176,10 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
104897
106176
|
metadata: projection.metadata,
|
|
104898
106177
|
ledgerEntry: projection.ledgerEntry
|
|
104899
106178
|
});
|
|
104900
|
-
await
|
|
106179
|
+
await Promise.all([
|
|
106180
|
+
this.projectPreparedAssessmentChildResult(checkpoint),
|
|
106181
|
+
this.requireClient().api.oneroster.assessmentResults.upsert(childResultId, finalized.resultUpdate)
|
|
106182
|
+
]);
|
|
104901
106183
|
} catch (error88) {
|
|
104902
106184
|
addEvent("assessment.diagnostic_child_projection_failed", {
|
|
104903
106185
|
"app.assessment.attempt_id": projection.result.sourcedId,
|
|
@@ -104958,41 +106240,43 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
104958
106240
|
async ensureReviewQuestionLineItem(input) {
|
|
104959
106241
|
const client2 = this.requireClient();
|
|
104960
106242
|
const lineItemId = await reviewQuestionLineItemId(input.parentLineItemId, input.item.identifier);
|
|
106243
|
+
const expected = {
|
|
106244
|
+
status: ONEROSTER_STATUS2.active,
|
|
106245
|
+
parentAssessmentLineItem: { sourcedId: input.parentLineItemId },
|
|
106246
|
+
course: { sourcedId: input.integration.courseId },
|
|
106247
|
+
resultValueMin: 0,
|
|
106248
|
+
resultValueMax: input.item.maxScore,
|
|
106249
|
+
metadata: {
|
|
106250
|
+
playcademyReviewQuestionDefinition: {
|
|
106251
|
+
version: 1,
|
|
106252
|
+
bankRevision: input.bankRevision,
|
|
106253
|
+
itemIdentifier: input.item.identifier,
|
|
106254
|
+
standards: input.standards
|
|
106255
|
+
}
|
|
106256
|
+
}
|
|
106257
|
+
};
|
|
104961
106258
|
try {
|
|
104962
|
-
await client2.api.oneroster.assessmentLineItems.get(lineItemId);
|
|
106259
|
+
const stored = await client2.api.oneroster.assessmentLineItems.get(lineItemId);
|
|
106260
|
+
this.assertAssessmentChildLineItem(lineItemId, stored, expected);
|
|
104963
106261
|
return lineItemId;
|
|
104964
106262
|
} catch (error88) {
|
|
104965
106263
|
if (!isApiError(error88) || error88.statusCode !== 404) {
|
|
104966
106264
|
throw error88;
|
|
104967
106265
|
}
|
|
104968
106266
|
}
|
|
104969
|
-
const indexed = input.bank.items.find((candidate) => candidate.itemIdentifier === input.item.identifier);
|
|
104970
|
-
if (!indexed) {
|
|
104971
|
-
throw new Error(`Review-bank index is missing item ${input.item.identifier}`);
|
|
104972
|
-
}
|
|
104973
106267
|
try {
|
|
104974
106268
|
await client2.api.oneroster.assessmentLineItems.create({
|
|
104975
106269
|
sourcedId: lineItemId,
|
|
104976
|
-
status: ONEROSTER_STATUS2.active,
|
|
104977
106270
|
title: input.item.title,
|
|
104978
106271
|
description: "standards review question",
|
|
104979
|
-
|
|
104980
|
-
course: { sourcedId: input.integration.courseId },
|
|
104981
|
-
resultValueMin: 0,
|
|
104982
|
-
resultValueMax: input.item.maxScore,
|
|
104983
|
-
metadata: {
|
|
104984
|
-
playcademyReviewQuestionDefinition: {
|
|
104985
|
-
version: 1,
|
|
104986
|
-
bankRevision: input.bank.bankRevision,
|
|
104987
|
-
itemIdentifier: input.item.identifier,
|
|
104988
|
-
standards: indexed.standards
|
|
104989
|
-
}
|
|
104990
|
-
}
|
|
106272
|
+
...expected
|
|
104991
106273
|
});
|
|
104992
106274
|
} catch (error88) {
|
|
104993
106275
|
if (!isApiError(error88) || error88.statusCode !== 409) {
|
|
104994
106276
|
throw error88;
|
|
104995
106277
|
}
|
|
106278
|
+
const stored = await client2.api.oneroster.assessmentLineItems.get(lineItemId);
|
|
106279
|
+
this.assertAssessmentChildLineItem(lineItemId, stored, expected);
|
|
104996
106280
|
addEvent("assessment.review_question_line_item_create_conflict", {
|
|
104997
106281
|
"app.assessment.line_item_id": lineItemId
|
|
104998
106282
|
});
|
|
@@ -105013,6 +106297,7 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
105013
106297
|
}
|
|
105014
106298
|
async ensureReviewChildResults(input) {
|
|
105015
106299
|
const itemByIdentifier = new Map(input.assessment.items.map((item) => [item.identifier, item]));
|
|
106300
|
+
const bankItemByIdentifier = new Map(input.source.bank.items.map((item) => [item.itemIdentifier, item]));
|
|
105016
106301
|
const physicalItems = new Set(input.metadata.review.selections.map((selection) => selection.itemIdentifier));
|
|
105017
106302
|
if (physicalItems.size !== input.metadata.review.selections.length) {
|
|
105018
106303
|
throw new Error("MVP review selection must use each physical item at most once");
|
|
@@ -105020,19 +106305,20 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
105020
106305
|
const administeredItems = administeredItemIdentifiers(input.metadata.itemSubmissions);
|
|
105021
106306
|
await runWithConcurrency(input.metadata.review.selections, TimebackAssessmentRuntimeService.SCORING_CONCURRENCY, async (selection) => {
|
|
105022
106307
|
const item = itemByIdentifier.get(selection.itemIdentifier);
|
|
105023
|
-
|
|
105024
|
-
|
|
106308
|
+
const indexed = bankItemByIdentifier.get(selection.itemIdentifier);
|
|
106309
|
+
if (!item || !indexed) {
|
|
106310
|
+
throw new Error(`Review assessment or bank is missing selected item ${selection.itemIdentifier}`);
|
|
105025
106311
|
}
|
|
106312
|
+
const childLineItemId = await this.ensureReviewQuestionLineItem({
|
|
106313
|
+
parentLineItemId: input.result.assessmentLineItem.sourcedId,
|
|
106314
|
+
integration: input.integration,
|
|
106315
|
+
bankRevision: input.metadata.review.bankRevision,
|
|
106316
|
+
standards: [...indexed.standards],
|
|
106317
|
+
item
|
|
106318
|
+
});
|
|
105026
106319
|
if (!administeredItems.has(selection.itemIdentifier)) {
|
|
105027
|
-
await this.ensureReviewQuestionLineItem({
|
|
105028
|
-
parentLineItemId: input.result.assessmentLineItem.sourcedId,
|
|
105029
|
-
integration: input.integration,
|
|
105030
|
-
bank: input.source.bank,
|
|
105031
|
-
item
|
|
105032
|
-
});
|
|
105033
106320
|
return;
|
|
105034
106321
|
}
|
|
105035
|
-
const childLineItemId = await reviewQuestionLineItemId(input.result.assessmentLineItem.sourcedId, item.identifier);
|
|
105036
106322
|
const childResultId = await reviewQuestionResultId(input.result.sourcedId, childLineItemId);
|
|
105037
106323
|
const streamedChildResult = input.existingChildResults.get(childResultId);
|
|
105038
106324
|
let childResult = streamedChildResult;
|
|
@@ -105055,12 +106341,6 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
105055
106341
|
if (resolution.action === "reuse") {
|
|
105056
106342
|
return;
|
|
105057
106343
|
}
|
|
105058
|
-
await this.ensureReviewQuestionLineItem({
|
|
105059
|
-
parentLineItemId: input.result.assessmentLineItem.sourcedId,
|
|
105060
|
-
integration: input.integration,
|
|
105061
|
-
bank: input.source.bank,
|
|
105062
|
-
item
|
|
105063
|
-
});
|
|
105064
106344
|
if (resolution.action === "restore") {
|
|
105065
106345
|
addEvent("assessment.review_child_result_restore", {
|
|
105066
106346
|
"app.assessment.review_child_restore_reason": resolution.reason,
|
|
@@ -105078,24 +106358,43 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
105078
106358
|
});
|
|
105079
106359
|
});
|
|
105080
106360
|
}
|
|
105081
|
-
async putReviewChildResponse(
|
|
106361
|
+
async putReviewChildResponse(projection, checkpoint) {
|
|
106362
|
+
const { result, metadata: metadata2, itemIdentifier, integration } = projection;
|
|
105082
106363
|
const selection = metadata2.review.selections.find((candidate) => candidate.itemIdentifier === itemIdentifier);
|
|
105083
106364
|
if (!selection) {
|
|
105084
106365
|
return;
|
|
105085
106366
|
}
|
|
105086
106367
|
try {
|
|
105087
|
-
const
|
|
106368
|
+
const source = await this.loadPinnedReviewBankSource(metadata2);
|
|
106369
|
+
const item = source.assessment.items.find((candidate) => candidate.identifier === selection.itemIdentifier);
|
|
106370
|
+
const indexed = source.bank.items.find((candidate) => candidate.itemIdentifier === selection.itemIdentifier);
|
|
106371
|
+
if (!item || !indexed) {
|
|
106372
|
+
throw new Error(`Review assessment or bank is missing selected item ${selection.itemIdentifier}`);
|
|
106373
|
+
}
|
|
106374
|
+
const childLineItemId = await this.ensureReviewQuestionLineItem({
|
|
106375
|
+
parentLineItemId: result.assessmentLineItem.sourcedId,
|
|
106376
|
+
integration,
|
|
106377
|
+
bankRevision: metadata2.review.bankRevision,
|
|
106378
|
+
standards: [...indexed.standards],
|
|
106379
|
+
item
|
|
106380
|
+
});
|
|
106381
|
+
if (checkpoint && checkpoint.childLineItemId !== childLineItemId) {
|
|
106382
|
+
throw new Error("Prepared review checkpoint resolved a different line item");
|
|
106383
|
+
}
|
|
105088
106384
|
const childResultId = await reviewQuestionResultId(result.sourcedId, childLineItemId);
|
|
105089
106385
|
const childMetadata = buildReviewItemResultMetadata({
|
|
105090
106386
|
metadata: metadata2,
|
|
105091
106387
|
selection,
|
|
105092
106388
|
parentAttemptId: result.sourcedId
|
|
105093
106389
|
});
|
|
105094
|
-
await
|
|
105095
|
-
|
|
105096
|
-
|
|
105097
|
-
|
|
105098
|
-
|
|
106390
|
+
await Promise.all([
|
|
106391
|
+
this.projectPreparedAssessmentChildResult(checkpoint),
|
|
106392
|
+
this.requireClient().api.oneroster.assessmentResults.upsert(childResultId, buildOpenReviewChildResult({
|
|
106393
|
+
childLineItemId,
|
|
106394
|
+
student: result.student,
|
|
106395
|
+
metadata: childMetadata
|
|
106396
|
+
}))
|
|
106397
|
+
]);
|
|
105099
106398
|
} catch (error88) {
|
|
105100
106399
|
addEvent("assessment.review_child_response_projection_failed", {
|
|
105101
106400
|
"app.assessment.attempt_id": result.sourcedId,
|
|
@@ -105105,14 +106404,26 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
105105
106404
|
});
|
|
105106
106405
|
}
|
|
105107
106406
|
}
|
|
105108
|
-
async projectReviewItemResponse(params, projection) {
|
|
105109
|
-
|
|
106407
|
+
async projectReviewItemResponse(params, projection, checkpoint) {
|
|
106408
|
+
const terminal = assessmentChildProjectionIsTerminal(projection.result);
|
|
105110
106409
|
try {
|
|
105111
|
-
await
|
|
105112
|
-
|
|
105113
|
-
|
|
105114
|
-
|
|
105115
|
-
|
|
106410
|
+
await reconcileReviewChildResults({
|
|
106411
|
+
initial: projection,
|
|
106412
|
+
writeOpen: () => this.putReviewChildResponse(projection, checkpoint),
|
|
106413
|
+
crossBarrier: () => crossAssessmentAttemptLockBarrier(this.deps.assessmentRuntimeLock, this.deps.db, params.attemptId),
|
|
106414
|
+
readLatest: () => this.peekAttempt(params),
|
|
106415
|
+
restoreTerminal: async (latest) => {
|
|
106416
|
+
const repair = this.restoreCompletedReviewChildResponses(latest.result, latest.metadata, new Set([projection.itemIdentifier]));
|
|
106417
|
+
if (terminal) {
|
|
106418
|
+
await Promise.allSettled([
|
|
106419
|
+
this.projectPreparedAssessmentChildResult(checkpoint),
|
|
106420
|
+
repair
|
|
106421
|
+
]);
|
|
106422
|
+
} else {
|
|
106423
|
+
await repair;
|
|
106424
|
+
}
|
|
106425
|
+
}
|
|
106426
|
+
});
|
|
105116
106427
|
} catch (error88) {
|
|
105117
106428
|
addEvent("assessment.review_child_projection_barrier_failed", {
|
|
105118
106429
|
"app.assessment.attempt_id": params.attemptId,
|
|
@@ -105122,6 +106433,9 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
105122
106433
|
});
|
|
105123
106434
|
}
|
|
105124
106435
|
}
|
|
106436
|
+
restoreAllCompletedReviewChildResponses(result, metadata2) {
|
|
106437
|
+
return this.restoreCompletedReviewChildResponses(result, metadata2, new Set(metadata2.itemSubmissions.map((item) => item.itemIdentifier)));
|
|
106438
|
+
}
|
|
105125
106439
|
async restoreCompletedReviewChildResponses(result, metadata2, changedItemIdentifiers) {
|
|
105126
106440
|
const outcomes = metadata2.completion?.itemOutcomes;
|
|
105127
106441
|
const submissionId = metadata2.submissionId;
|
|
@@ -105165,28 +106479,22 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
105165
106479
|
}
|
|
105166
106480
|
});
|
|
105167
106481
|
}
|
|
105168
|
-
|
|
106482
|
+
reviewItemOutcomes(input) {
|
|
105169
106483
|
const resultByItem = new Map(input.itemResults.map((itemResult) => [itemResult.itemIdentifier, itemResult]));
|
|
105170
106484
|
const administeredItems = administeredItemIdentifiers(input.metadata.itemSubmissions);
|
|
105171
|
-
return
|
|
106485
|
+
return input.metadata.review.selections.filter((selection) => administeredItems.has(selection.itemIdentifier)).map((selection) => {
|
|
105172
106486
|
const itemResult = resultByItem.get(selection.itemIdentifier);
|
|
105173
|
-
|
|
106487
|
+
const submission = itemAdministration(input.metadata.itemSubmissions, selection.itemIdentifier);
|
|
106488
|
+
if (!itemResult || !submission) {
|
|
105174
106489
|
throw new Error(`Scoring omitted selected review item ${selection.itemIdentifier}`);
|
|
105175
106490
|
}
|
|
105176
|
-
|
|
105177
|
-
|
|
105178
|
-
|
|
105179
|
-
|
|
105180
|
-
|
|
105181
|
-
|
|
105182
|
-
|
|
105183
|
-
selection,
|
|
105184
|
-
itemResult,
|
|
105185
|
-
submissionId: input.submissionId,
|
|
105186
|
-
timestamp: input.timestamp
|
|
105187
|
-
});
|
|
105188
|
-
await this.requireClient().api.oneroster.assessmentResults.upsert(childResultId, finalized.resultUpdate);
|
|
105189
|
-
return finalized.outcome;
|
|
106491
|
+
return {
|
|
106492
|
+
itemIdentifier: selection.itemIdentifier,
|
|
106493
|
+
standard: selection.standard,
|
|
106494
|
+
answered: submission.answered,
|
|
106495
|
+
score: itemResult.score,
|
|
106496
|
+
isCorrect: itemResult.isCorrect
|
|
106497
|
+
};
|
|
105190
106498
|
});
|
|
105191
106499
|
}
|
|
105192
106500
|
async resolveAttemptAuthorization({ gameId, studentId, attemptId, user }, options = {}) {
|
|
@@ -105218,11 +106526,12 @@ var init_timeback_assessment_runtime_service = __esm(async () => {
|
|
|
105218
106526
|
return { attempt: { result, metadata: metadata2, integration }, enrollment };
|
|
105219
106527
|
}
|
|
105220
106528
|
async authorizeAttempt(params, options = {}) {
|
|
105221
|
-
const
|
|
105222
|
-
|
|
105223
|
-
|
|
105224
|
-
|
|
105225
|
-
|
|
106529
|
+
const authorization = await this.resolveAttemptAuthorization(params, options);
|
|
106530
|
+
return this.requireActiveAttempt(authorization, params.attemptId);
|
|
106531
|
+
}
|
|
106532
|
+
requireActiveAttempt(authorization, attemptId) {
|
|
106533
|
+
const enrollment = requireAssessmentMutationAccess(attemptId, authorization.attempt.integration.status, authorization.enrollment);
|
|
106534
|
+
return { ...authorization.attempt, enrollment };
|
|
105226
106535
|
}
|
|
105227
106536
|
async authorizeAttemptRead(params) {
|
|
105228
106537
|
const { attempt, enrollment } = await this.resolveAttemptAuthorization(params);
|
|
@@ -107100,7 +108409,7 @@ function getTotalXpFromTimebackConfig(config5) {
|
|
|
107100
108409
|
return courseMetadata.metrics.totalXp;
|
|
107101
108410
|
}
|
|
107102
108411
|
const resourceMetadata = config5.resource.metadata;
|
|
107103
|
-
if (
|
|
108412
|
+
if (isRecord4(resourceMetadata) && typeof resourceMetadata.xp === "number") {
|
|
107104
108413
|
return resourceMetadata.xp;
|
|
107105
108414
|
}
|
|
107106
108415
|
return null;
|
|
@@ -108123,8 +109432,8 @@ var init_timeback_service = __esm(async () => {
|
|
|
108123
109432
|
});
|
|
108124
109433
|
}
|
|
108125
109434
|
static patchCourseMetadata(metadata2, totalXp, options) {
|
|
108126
|
-
const nextMetadata =
|
|
108127
|
-
const currentMetrics =
|
|
109435
|
+
const nextMetadata = isRecord4(metadata2) ? { ...metadata2 } : {};
|
|
109436
|
+
const currentMetrics = isRecord4(nextMetadata.metrics) ? nextMetadata.metrics : {};
|
|
108128
109437
|
const metrics2 = { ...currentMetrics };
|
|
108129
109438
|
if (totalXp === null) {
|
|
108130
109439
|
delete metrics2.totalXp;
|
|
@@ -108149,7 +109458,7 @@ var init_timeback_service = __esm(async () => {
|
|
|
108149
109458
|
if (goals === null) {
|
|
108150
109459
|
delete nextMetadata.goals;
|
|
108151
109460
|
} else {
|
|
108152
|
-
const currentGoals =
|
|
109461
|
+
const currentGoals = isRecord4(nextMetadata.goals) ? nextMetadata.goals : {};
|
|
108153
109462
|
const nextGoals = { ...currentGoals };
|
|
108154
109463
|
for (const [key, value] of Object.entries(goals)) {
|
|
108155
109464
|
if (value === null) {
|
|
@@ -108168,7 +109477,7 @@ var init_timeback_service = __esm(async () => {
|
|
|
108168
109477
|
if (options?.publishStatus !== undefined) {
|
|
108169
109478
|
if (options.publishStatus === null) {
|
|
108170
109479
|
delete nextMetadata.publishStatus;
|
|
108171
|
-
const alphaLearn =
|
|
109480
|
+
const alphaLearn = isRecord4(nextMetadata.AlphaLearn) ? { ...nextMetadata.AlphaLearn } : {};
|
|
108172
109481
|
delete alphaLearn.publishStatus;
|
|
108173
109482
|
if (Object.keys(alphaLearn).length > 0) {
|
|
108174
109483
|
nextMetadata.AlphaLearn = alphaLearn;
|
|
@@ -108177,7 +109486,7 @@ var init_timeback_service = __esm(async () => {
|
|
|
108177
109486
|
}
|
|
108178
109487
|
} else {
|
|
108179
109488
|
nextMetadata.publishStatus = options.publishStatus;
|
|
108180
|
-
const alphaLearn =
|
|
109489
|
+
const alphaLearn = isRecord4(nextMetadata.AlphaLearn) ? { ...nextMetadata.AlphaLearn } : {};
|
|
108181
109490
|
alphaLearn.publishStatus = options.publishStatus === "published" ? "active" : options.publishStatus;
|
|
108182
109491
|
nextMetadata.AlphaLearn = alphaLearn;
|
|
108183
109492
|
}
|
|
@@ -108195,9 +109504,9 @@ var init_timeback_service = __esm(async () => {
|
|
|
108195
109504
|
return Object.keys(nextMetadata).length > 0 ? nextMetadata : undefined;
|
|
108196
109505
|
}
|
|
108197
109506
|
static patchResourceMetadata(metadata2, options) {
|
|
108198
|
-
const nextMetadata =
|
|
108199
|
-
const playcademyMetadata =
|
|
108200
|
-
const masteryMetadata =
|
|
109507
|
+
const nextMetadata = isRecord4(metadata2) ? { ...metadata2 } : {};
|
|
109508
|
+
const playcademyMetadata = isRecord4(nextMetadata.playcademy) ? { ...nextMetadata.playcademy } : {};
|
|
109509
|
+
const masteryMetadata = isRecord4(playcademyMetadata.mastery) ? { ...playcademyMetadata.mastery } : {};
|
|
108201
109510
|
nextMetadata.subject = options.subject;
|
|
108202
109511
|
nextMetadata.grades = [options.grade];
|
|
108203
109512
|
if (options.totalXp === null) {
|