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